All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.jaroop.play.sentry.TokenAccessor.scala Maven / Gradle / Ivy

package com.jaroop.play.sentry

import play.api.mvc.{ RequestHeader, Result }
import play.api.libs.crypto.CookieSigner

/**
 *  Provides an interface for managing sessions client-side via requests and results. A [[TokenAccessor]] should be able to add
 *  or remove a [[SignedToken]] from a `Result`, as well as verify the signature of a [[SignedToken]] from a `RequestHeader`.
 *
 *  While it is not required to use them, token accessors should use the available signing methods in this trait to sign
 *  and verify tokens so that they cannot be tampered with by an attacker.
 */
trait TokenAccessor {

    /** Requires a `CookieSigner` to sign tokens and verify token signatures. */
    def signer: CookieSigner

    /**
     *  Attempts to extract an [[AuthenticityToken]] from a `RequestHeader`.
     *
     *  @param request The `RequestHeader` to extract the token from.
     *  @return An [[AuthenticityToken]] if the request contains a token with a valid signature. Otherwise, `None`.
     */
    def extract(request: RequestHeader): Option[AuthenticityToken]

    /**
     *  Puts an [[AuthenticityToken]] into a `Result` to return to a user.
     *
     *  @param token The [[AuthenticityToken]] to be issued to a user.
     *  @return A `Result` containing a [[SignedToken]] or [[AuthenticityToken]].
     */
    def put(token: AuthenticityToken)(result: Result)(implicit request: RequestHeader): Result

    /**
     *  Removes any issued [[AuthenticityToken]] or [[SignedToken]] from a `Result`.
     *
     *  @param result The `Result` to remove all issued tokens from.
     *  @return A new `Result` without any issued tokens.
     */
    def delete(result: Result)(implicit request: RequestHeader): Result

    /**
     *  Verifies that a [[SignedToken]] token is valid by comparing the stored signature in the [[SignedToken]] to the
     *  signature of the raw [[AuthenticityToken]] that is part of the [[SignedToken]]. In order for ''any'' [[SignedToken]]
     *  to be valid, it must be issued via the [[TokenAccessor#sign]] method.
     *
     *  @param token The [[SignedToken]] to validate.
     *  @return The contained [[AuthenticityToken]] if the signature is valid, otherwise `None`.
     */
    protected def verifyHmac(token: SignedToken): Option[AuthenticityToken] = {
        val (hmac, value) = token.splitAt(40)
        if (safeEquals(signer.sign(value), hmac)) Some(value) else None
    }

    /**
     *  Signs an [[AuthenticityToken]] and concatenates it with its signature. ("$${signature}$${token}") The resulting
     *  [[SignedToken]] is meant to be issued to a user (e.g., within a cookie).
     *
     *  @param token The [[AuthenticityToken]] to sign.
     *  @return The signature of the [[AuthenticityToken]] concatenated with the token itself.
     */
    protected def sign(token: AuthenticityToken): SignedToken = signer.sign(token) + token

    // Do not change this unless you understand the security issues behind timing attacks.
    // This method intentionally runs in constant time if the two strings have the same length.
    // If it didn't, it would be vulnerable to a timing attack.
    protected def safeEquals(a: String, b: String) = {
        if (a.length != b.length) {
            false
        } else {
            var equal = 0
            for (i <- Array.range(0, a.length)) {
                equal |= a(i) ^ b(i)
            }
            equal == 0
        }
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy