diff --git a/silhouette/app/com/mohiva/play/silhouette/api/Authorization.scala b/silhouette/app/com/mohiva/play/silhouette/api/Authorization.scala index 65c374045..513784567 100644 --- a/silhouette/app/com/mohiva/play/silhouette/api/Authorization.scala +++ b/silhouette/app/com/mohiva/play/silhouette/api/Authorization.scala @@ -21,6 +21,8 @@ package com.mohiva.play.silhouette.api import play.api.i18n.Messages import play.api.mvc.RequestHeader +import scala.concurrent.Future +import scala.concurrent.ExecutionContext.Implicits.global /** * A trait to define Authorization objects that let you hook @@ -38,7 +40,7 @@ trait Authorization[I <: Identity] { * @param messages The messages for the current language. * @return True if the user is authorized, false otherwise. */ - def isAuthorized(identity: I)(implicit request: RequestHeader, messages: Messages): Boolean + def isAuthorized(identity: I)(implicit request: RequestHeader, messages: Messages): Future[Boolean] } /** @@ -59,8 +61,8 @@ object Authorization { * @return The authorization. */ def unary_! : Authorization[I] = new Authorization[I] { - def isAuthorized(identity: I)(implicit request: RequestHeader, messages: Messages): Boolean = { - !self.isAuthorized(identity) + def isAuthorized(identity: I)(implicit request: RequestHeader, messages: Messages): Future[Boolean] = { + self.isAuthorized(identity).map(x => !x) } } @@ -71,8 +73,13 @@ object Authorization { * @return The authorization. */ def &&(authorization: Authorization[I]): Authorization[I] = new Authorization[I] { - def isAuthorized(identity: I)(implicit request: RequestHeader, messages: Messages): Boolean = { - self.isAuthorized(identity) && authorization.isAuthorized(identity) + def isAuthorized(identity: I)(implicit request: RequestHeader, messages: Messages): Future[Boolean] = { + val leftF = self.isAuthorized(identity) + val rightF = authorization.isAuthorized(identity) + for { + left <- leftF + right <- rightF + } yield left && right } } @@ -83,8 +90,13 @@ object Authorization { * @return The authorization. */ def ||(authorization: Authorization[I]): Authorization[I] = new Authorization[I] { - def isAuthorized(identity: I)(implicit request: RequestHeader, messages: Messages): Boolean = { - self.isAuthorized(identity) || authorization.isAuthorized(identity) + def isAuthorized(identity: I)(implicit request: RequestHeader, messages: Messages): Future[Boolean] = { + val leftF = self.isAuthorized(identity) + val rightF = authorization.isAuthorized(identity) + for { + left <- leftF + right <- rightF + } yield left || right } } } diff --git a/silhouette/app/com/mohiva/play/silhouette/api/Silhouette.scala b/silhouette/app/com/mohiva/play/silhouette/api/Silhouette.scala index 15c01ff2d..8b583ca9a 100644 --- a/silhouette/app/com/mohiva/play/silhouette/api/Silhouette.scala +++ b/silhouette/app/com/mohiva/play/silhouette/api/Silhouette.scala @@ -361,17 +361,17 @@ trait Silhouette[I <: Identity, A <: Authenticator] extends Controller with Logg * @return A handler result. */ protected def invokeBlock[B, T](block: SecuredRequest[B] => Future[HandlerResult[T]])(implicit request: Request[B]): Future[HandlerResult[T]] = { - handleAuthentication.flatMap { + withAuthorization(handleAuthentication).flatMap { // A user is both authenticated and authorized. The request will be granted - case (Some(authenticator), Some(identity)) if authorize.isEmpty || authorize.get.isAuthorized(identity) => + case (Some(authenticator), Some(identity), Some(authorized)) if authorized => env.eventBus.publish(AuthenticatedEvent(identity, request, request2Messages)) handleBlock(authenticator, a => block(SecuredRequest(identity, a, request))) // A user is authenticated but not authorized. The request will be forbidden - case (Some(authenticator), Some(identity)) => + case (Some(authenticator), Some(identity), _) => env.eventBus.publish(NotAuthorizedEvent(identity, request, request2Messages)) handleBlock(authenticator, _ => handleNotAuthorized(request).map(r => HandlerResult(r))) // An authenticator but no user was found. The request will ask for authentication and the authenticator will be discarded - case (Some(authenticator), None) => + case (Some(authenticator), None, _) => env.eventBus.publish(NotAuthenticatedEvent(request, request2Messages)) for { result <- handleNotAuthenticated(request) @@ -383,6 +383,22 @@ trait Silhouette[I <: Identity, A <: Authenticator] extends Controller with Logg handleNotAuthenticated(request).map(r => HandlerResult(r)) } } + + /** + * Adds the authorization status to the authentication result. + * + * @param result The authentication result. + * @param request The current request header. + * @return The authentication result with the additional authorization status. + */ + private def withAuthorization(result: Future[(Option[Either[A, A]], Option[I])])(implicit request: RequestHeader) = { + result.flatMap { + case (a, Some(i)) => + authorize.map(_.isAuthorized(i)).getOrElse(Future.successful(true)).map(b => (a, Some(i), Some(b))) + case (a, i) => + Future.successful((a, i, None)) + } + } } /** diff --git a/silhouette/test/com/mohiva/play/silhouette/api/SilhouetteSpec.scala b/silhouette/test/com/mohiva/play/silhouette/api/SilhouetteSpec.scala index 610614f9e..2125adabd 100644 --- a/silhouette/test/com/mohiva/play/silhouette/api/SilhouetteSpec.scala +++ b/silhouette/test/com/mohiva/play/silhouette/api/SilhouetteSpec.scala @@ -1060,6 +1060,8 @@ class SilhouetteSpec extends PlaySpecification with Mockito with JsonMatchers { * @param messages The messages for the current language. * @return True if the user is authorized, false otherwise. */ - def isAuthorized(identity: FakeIdentity)(implicit request: RequestHeader, messages: Messages): Boolean = isAuthorized + def isAuthorized(identity: FakeIdentity)(implicit request: RequestHeader, messages: Messages): Future[Boolean] = { + Future.successful(isAuthorized) + } } }