|
trait ExtendedOAuth2StateProvider extends OAuth2StateProvider { |
|
|
|
override type State <: ExtendedOAuth2State |
|
|
|
} |
|
|
|
/** |
|
* |
|
*/ |
|
class ExtendedCookieStateProvider @Inject()( |
|
settings: ExtendedCookieStateSettings, |
|
idGenerator: IDGenerator, |
|
crypto: Crypto, |
|
clock: Clock) extends ExtendedOAuth2StateProvider { |
|
|
|
override type State = ExtendedCookieState |
|
|
|
object stateCrypto { |
|
def sign(state: String) = { |
|
val nonce = System.currentTimeMillis() |
|
val joined = nonce + "-" + state |
|
crypto.sign(joined) + "-" + nonce |
|
} |
|
|
|
def validate(signed: String, raw: String) = { |
|
signed.split("-", 2) match { |
|
case Array(signature, nonce) => signature == crypto.sign(nonce + "-" + raw) |
|
case _ => false |
|
} |
|
} |
|
} |
|
|
|
/** |
|
* Builds the state. |
|
* |
|
* @param request The request. |
|
* @param ec The execution context to handle the asynchronous operations. |
|
* @tparam B The type of the request body. |
|
* @return The build state. |
|
*/ |
|
override def build[B](implicit request: ExtractableRequest[B], ec: ExecutionContext): Future[ExtendedCookieState] = { |
|
val returnTo = request.extractString(settings.returnToParameter) |
|
idGenerator.generate.map { id => |
|
ExtendedCookieState(id, clock.now.plusSeconds(settings.cookieState.expirationTime.toSeconds.toInt), returnTo) |
|
} |
|
} |
|
|
|
/** |
|
* Sends a cookie to the client containing the serialized state. |
|
* |
|
* @param result The result to send to the client. |
|
* @param state The state to publish. |
|
* @param request The request. |
|
* @tparam B The type of the request body. |
|
* @return The result to send to the client. |
|
*/ |
|
override def publish[B](result: Result, state: State)(implicit request: ExtractableRequest[B]): Result = { |
|
result.withCookies(Cookie(name = settings.cookieState.cookieName, |
|
value = stateCrypto.sign(state.serialize), |
|
maxAge = Some(settings.cookieState.expirationTime.toSeconds.toInt), |
|
path = settings.cookieState.cookiePath, |
|
domain = settings.cookieState.cookieDomain, |
|
secure = settings.cookieState.secureCookie, |
|
httpOnly = settings.cookieState.httpOnlyCookie)) |
|
} |
|
|
|
/** |
|
* Validates the provider and the client state. |
|
* |
|
* @param request The request. |
|
* @param ec The execution context to handle the asynchronous operations. |
|
* @tparam B The type of the request body. |
|
* @return The state on success, otherwise an failure. |
|
*/ |
|
override def validate[B](implicit request: ExtractableRequest[B], ec: ExecutionContext): Future[ExtendedCookieState] = Future.from { |
|
for { |
|
(serializedState, providerState) <- providerState |
|
clientState <- clientState |
|
result <- { |
|
if (!stateCrypto.validate(clientState, serializedState)) Failure(new OAuth2StateException(StateIsNotEqual)) |
|
else if (providerState.isExpired) Failure(new OAuth2StateException(StateIsExpired)) |
|
else Success(providerState) |
|
} |
|
} yield result |
|
} |
|
|
|
|
|
/** |
|
* Gets the state from request the after the provider has redirected back from the authorization auth.server |
|
* with the access code. |
|
* |
|
* @param request The request. |
|
* @tparam B The type of the request body. |
|
* @return The OAuth2 state on success, otherwise a failure. |
|
*/ |
|
private def providerState[B](implicit request: ExtractableRequest[B]): Try[(String, ExtendedCookieState)] = { |
|
request.extractString(State) match { |
|
case Some(state) => ExtendedCookieState.deserialize(state).map(state -> _) |
|
case _ => Failure(new OAuth2StateException(ProviderStateDoesNotExists.format(State))) |
|
} |
|
} |
|
|
|
/** |
|
* Gets the state from cookie. |
|
* |
|
* @param request The request header. |
|
* @return The OAuth2 state on success, otherwise a failure. |
|
*/ |
|
private def clientState(implicit request: RequestHeader): Try[String] = { |
|
request.cookies.get(settings.cookieState.cookieName) match { |
|
case Some(cookie) => Success(cookie.value) |
|
case None => Failure(new OAuth2StateException(ClientStateDoesNotExists.format(settings.cookieState.cookieName))) |
|
} |
|
} |
|
|
|
|
|
} |
|
|
|
object ExtendedCookieStateProvider { |
|
val InvalidStateFormat = "[Silhouette][ExtendedCookieState] Cannot build OAuth2State because of invalid parameter format." |
|
} |