From 3660be1ec9e8911cff27706b1529ee107b80d617 Mon Sep 17 00:00:00 2001 From: Joshua Newman Date: Mon, 18 Jul 2016 23:28:07 -0700 Subject: [PATCH 1/2] StructuredAccessControl.anyOf --- .../access/StructuredAccessControl.scala | 48 ++++++++++++++++++- .../access/authorizer/Authorizer.scala | 9 ++-- 2 files changed, 51 insertions(+), 6 deletions(-) diff --git a/naptime/src/main/scala/org/coursera/naptime/access/StructuredAccessControl.scala b/naptime/src/main/scala/org/coursera/naptime/access/StructuredAccessControl.scala index 4adcd17b..3c3d25f3 100644 --- a/naptime/src/main/scala/org/coursera/naptime/access/StructuredAccessControl.scala +++ b/naptime/src/main/scala/org/coursera/naptime/access/StructuredAccessControl.scala @@ -42,7 +42,7 @@ case class StructuredAccessControl[A]( Authenticator.authenticateAndRecover(authenticator, requestHeader).map { decoratedOption => decoratedOption.map { either => either.right.flatMap { decorated => - Authorizer.toResponse(authorizer.authorize(decorated), decorated) + Authorizer.toResponse(authorizer, decorated) } }.getOrElse { StructuredAccessControl.missingResponse @@ -51,7 +51,7 @@ case class StructuredAccessControl[A]( } override private[naptime] def check(authInfo: A): Either[NaptimeActionException, A] = { - Authorizer.toResponse(authorizer.authorize(authInfo), authInfo) + Authorizer.toResponse(authorizer, authInfo) } } @@ -64,4 +64,48 @@ object StructuredAccessControl { Some("Missing authentication"))) } + /** + * Implementation note: The [[Authenticator]] is supposed to generate the authentication data. + * In this case, the resulting authentication data depends on the _authorizers_ in the input + * access controls because it reflects which authorizers accepted. Thus we run the individual + * authorizers in the combined access control's authenticator. + */ + def anyOf[A, B]( + controlA: StructuredAccessControl[A], + controlB: StructuredAccessControl[B]): + StructuredAccessControl[(Option[A], Option[B])] = { + + type AA = (Option[A], Option[B]) + + val authenticator: Authenticator[AA] = new Authenticator[AA] { + def maybeAuthenticate( + requestHeader: RequestHeader) + (implicit ec: ExecutionContext): Future[Option[Either[NaptimeActionException, AA]]] = { + for { + maybeAuthenticationA <- Authenticator.authenticateAndRecover( + controlA.authenticator, requestHeader) + maybeAuthenticationB <- Authenticator.authenticateAndRecover( + controlB.authenticator, requestHeader) + } yield { + val resultA = maybeAuthenticationA + .map(_.right.flatMap(Authorizer.toResponse(controlA.authorizer, _))) + val resultB = maybeAuthenticationB + .map(_.right.flatMap(Authorizer.toResponse(controlB.authorizer, _))) + + (resultA, resultB) match { + case (None, None) => None + case (Some(Left(errorA)), Some(Left(_))) => + Some(Left(errorA)) // Ignore the other error. + case _ => + Some(Right((resultA.flatMap(_.right.toOption), resultB.flatMap(_.right.toOption)))) + } + } + } + } + + val authorizer: Authorizer[AA] = Authorizer(_ => AuthorizeResult.Authorized) + + StructuredAccessControl(authenticator, authorizer) + } + } diff --git a/naptime/src/main/scala/org/coursera/naptime/access/authorizer/Authorizer.scala b/naptime/src/main/scala/org/coursera/naptime/access/authorizer/Authorizer.scala index 3f35dd04..7de67a91 100644 --- a/naptime/src/main/scala/org/coursera/naptime/access/authorizer/Authorizer.scala +++ b/naptime/src/main/scala/org/coursera/naptime/access/authorizer/Authorizer.scala @@ -29,7 +29,7 @@ trait Authorizer[-A] { def authorize(authentication: A): AuthorizeResult def check(authentication: A): Unit = { - Authorizer.toResponse(authorize(authentication), ()).left.foreach(throw _) + Authorizer.toResponse(this, authentication).left.foreach(throw _) } /** @@ -45,9 +45,10 @@ object Authorizer { override def authorize(authentication: A): AuthorizeResult = f(authentication) } - def toResponse[T](result: AuthorizeResult, rawResponse: T): Either[NaptimeActionException, T] = { - result match { - case AuthorizeResult.Authorized => Right(rawResponse) + private[access] def toResponse[A](authorizer: Authorizer[A], authentication: A): + Either[NaptimeActionException, A] = { + authorizer.authorize(authentication) match { + case AuthorizeResult.Authorized => Right(authentication) case AuthorizeResult.Rejected(message, details) => Left(NaptimeActionException(FORBIDDEN, Some("auth.perms"), Some(message), details)) case AuthorizeResult.Failed(message, details) => From 397ab003b12470132a1675c4f47e1ea1c6a7bf6f Mon Sep 17 00:00:00 2001 From: Joshua Newman Date: Mon, 18 Jul 2016 23:31:28 -0700 Subject: [PATCH 2/2] Reject in the authorizer instead --- .../naptime/access/StructuredAccessControl.scala | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/naptime/src/main/scala/org/coursera/naptime/access/StructuredAccessControl.scala b/naptime/src/main/scala/org/coursera/naptime/access/StructuredAccessControl.scala index 3c3d25f3..cf4bb050 100644 --- a/naptime/src/main/scala/org/coursera/naptime/access/StructuredAccessControl.scala +++ b/naptime/src/main/scala/org/coursera/naptime/access/StructuredAccessControl.scala @@ -68,7 +68,8 @@ object StructuredAccessControl { * Implementation note: The [[Authenticator]] is supposed to generate the authentication data. * In this case, the resulting authentication data depends on the _authorizers_ in the input * access controls because it reflects which authorizers accepted. Thus we run the individual - * authorizers in the combined access control's authenticator. + * authorizers in the combined access control's authenticator and generate an aggregated result + * in the combined authorizer. */ def anyOf[A, B]( controlA: StructuredAccessControl[A], @@ -93,9 +94,9 @@ object StructuredAccessControl { .map(_.right.flatMap(Authorizer.toResponse(controlB.authorizer, _))) (resultA, resultB) match { - case (None, None) => None case (Some(Left(errorA)), Some(Left(_))) => - Some(Left(errorA)) // Ignore the other error. + // If all return errors, return one of them, just so error information is not lost. + Some(Left(errorA)) case _ => Some(Right((resultA.flatMap(_.right.toOption), resultB.flatMap(_.right.toOption)))) } @@ -103,7 +104,10 @@ object StructuredAccessControl { } } - val authorizer: Authorizer[AA] = Authorizer(_ => AuthorizeResult.Authorized) + val authorizer: Authorizer[AA] = Authorizer { + case (None, None) => AuthorizeResult.Rejected("No successful checks") + case _ => AuthorizeResult.Authorized + } StructuredAccessControl(authenticator, authorizer) }