Merge pull request #20010 from ktoso/wip-authorizeAsync-ktoso
+htp #20002 add authorizeAsync
This commit is contained in:
commit
f679c9f0b1
8 changed files with 140 additions and 4 deletions
|
|
@ -219,7 +219,7 @@ class SecurityDirectivesExamplesSpec extends RoutingSpec {
|
|||
}
|
||||
}
|
||||
|
||||
"0authorize" in {
|
||||
"0authorize-0" in {
|
||||
case class User(name: String)
|
||||
|
||||
// authenticate the user:
|
||||
|
|
@ -260,6 +260,48 @@ class SecurityDirectivesExamplesSpec extends RoutingSpec {
|
|||
}
|
||||
}
|
||||
|
||||
"0authorizeAsync" in {
|
||||
case class User(name: String)
|
||||
|
||||
// authenticate the user:
|
||||
def myUserPassAuthenticator(credentials: Credentials): Option[User] =
|
||||
credentials match {
|
||||
case Credentials.Provided(id) => Some(User(id))
|
||||
case _ => None
|
||||
}
|
||||
|
||||
// check if user is authorized to perform admin actions,
|
||||
// this could potentially be a long operation so it would return a Future
|
||||
val admins = Set("Peter")
|
||||
def hasAdminPermissions(user: User): Future[Boolean] =
|
||||
Future.successful(admins.contains(user.name))
|
||||
|
||||
val route =
|
||||
Route.seal {
|
||||
authenticateBasic(realm = "secure site", myUserPassAuthenticator) { user =>
|
||||
path("peters-lair") {
|
||||
authorizeAsync(_ => hasAdminPermissions(user)) {
|
||||
complete(s"'${user.name}' visited Peter's lair")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// tests:
|
||||
val johnsCred = BasicHttpCredentials("John", "p4ssw0rd")
|
||||
Get("/peters-lair") ~> addCredentials(johnsCred) ~> // adds Authorization header
|
||||
route ~> check {
|
||||
status shouldEqual StatusCodes.Forbidden
|
||||
responseAs[String] shouldEqual "The supplied authentication is not authorized to access this resource"
|
||||
}
|
||||
|
||||
val petersCred = BasicHttpCredentials("Peter", "pan")
|
||||
Get("/peters-lair") ~> addCredentials(petersCred) ~> // adds Authorization header
|
||||
route ~> check {
|
||||
responseAs[String] shouldEqual "'Peter' visited Peter's lair"
|
||||
}
|
||||
}
|
||||
|
||||
"0extractCredentials" in {
|
||||
val route =
|
||||
extractCredentials { creds =>
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ Directive Description
|
|||
a given ``AsyncAuthenticatorPF[T]``
|
||||
:ref:`-authenticateOrRejectWithChallenge-` Lifts an authenticator function into a directive
|
||||
:ref:`-authorize-` Applies the given authorization check to the request
|
||||
:ref:`-authorizeAsync-` Applies the given asynchronous authorization check to the request
|
||||
:ref:`-cancelRejection-` Adds a ``TransformationRejection`` cancelling all rejections equal to the
|
||||
given one to the rejections potentially coming back from the inner route.
|
||||
:ref:`-cancelRejections-` Adds a ``TransformationRejection`` cancelling all matching rejections
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ If the check returns ``true`` the request is passed on to the inner route unchan
|
|||
In a common use-case you would check if a user (e.g. supplied by any of the ``authenticate*`` family of directives,
|
||||
e.g. :ref:`-authenticateBasic-`) is allowed to access the inner routes, e.g. by checking if the user has the needed permissions.
|
||||
|
||||
See also :ref:`-authorize-` for the asynchronous version of this directive.
|
||||
|
||||
.. note::
|
||||
See also :ref:`authentication-vs-authorization-scala` to understand the differences between those.
|
||||
|
|
@ -32,4 +33,4 @@ Example
|
|||
-------
|
||||
|
||||
.. includecode2:: ../../../../code/docs/http/scaladsl/server/directives/SecurityDirectivesExamplesSpec.scala
|
||||
:snippet: 0authorize
|
||||
:snippet: 0authorize-0
|
||||
|
|
|
|||
|
|
@ -0,0 +1,36 @@
|
|||
.. _-authorizeAsync-:
|
||||
|
||||
authorizeAsync
|
||||
==============
|
||||
|
||||
Signature
|
||||
---------
|
||||
|
||||
.. includecode2:: /../../akka-http/src/main/scala/akka/http/scaladsl/server/directives/SecurityDirectives.scala
|
||||
:snippet: authorizeAsync
|
||||
|
||||
Description
|
||||
-----------
|
||||
Applies the given authorization check to the request.
|
||||
|
||||
The user-defined authorization check can either be supplied as a ``=> Future[Boolean]`` value which is calculated
|
||||
just from information out of the lexical scope, or as a function ``RequestContext => Future[Boolean]`` which can also
|
||||
take information from the request itself into account.
|
||||
|
||||
If the check returns ``true`` or the ``Future`` is failed the request is passed on to the inner route unchanged,
|
||||
otherwise an ``AuthorizationFailedRejection`` is created, triggering a ``403 Forbidden`` response by default
|
||||
(the same as in the case of an ``AuthenticationFailedRejection``).
|
||||
|
||||
In a common use-case you would check if a user (e.g. supplied by any of the ``authenticate*`` family of directives,
|
||||
e.g. :ref:`-authenticateBasic-`) is allowed to access the inner routes, e.g. by checking if the user has the needed permissions.
|
||||
|
||||
See also :ref:`-authorize-` for the synchronous version of this directive.
|
||||
|
||||
.. note::
|
||||
See also :ref:`authentication-vs-authorization-scala` to understand the differences between those.
|
||||
|
||||
Example
|
||||
-------
|
||||
|
||||
.. includecode2:: ../../../../code/docs/http/scaladsl/server/directives/SecurityDirectivesExamplesSpec.scala
|
||||
:snippet: 0authorizeAsync
|
||||
|
|
@ -17,6 +17,7 @@ SecurityDirectives
|
|||
authenticateOAuth2PFAsync
|
||||
authenticateOrRejectWithChallenge
|
||||
authorize
|
||||
authorizeAsync
|
||||
extractCredentials
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -136,4 +136,34 @@ class SecurityDirectivesSpec extends RoutingSpec {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
"authorization directives" should {
|
||||
"authorize" in {
|
||||
Get() ~> {
|
||||
authorize(_ ⇒ true) { complete("OK") }
|
||||
} ~> check { responseAs[String] shouldEqual "OK" }
|
||||
}
|
||||
"not authorize" in {
|
||||
Get() ~> {
|
||||
authorize(_ ⇒ false) { complete("OK") }
|
||||
} ~> check { rejection shouldEqual AuthorizationFailedRejection }
|
||||
}
|
||||
|
||||
"authorizeAsync" in {
|
||||
Get() ~> {
|
||||
authorizeAsync(_ ⇒ Future.successful(true)) { complete("OK") }
|
||||
} ~> check { responseAs[String] shouldEqual "OK" }
|
||||
}
|
||||
"not authorizeAsync" in {
|
||||
Get() ~> {
|
||||
authorizeAsync(_ ⇒ Future.successful(false)) { complete("OK") }
|
||||
} ~> check { rejection shouldEqual AuthorizationFailedRejection }
|
||||
}
|
||||
"not authorizeAsync when future fails" in {
|
||||
Get() ~> {
|
||||
authorizeAsync(_ ⇒ Future.failed(new Exception("Boom!"))) { complete("OK") }
|
||||
} ~> check { rejection shouldEqual AuthorizationFailedRejection }
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,4 +7,5 @@ OSGi.http
|
|||
Dependencies.http
|
||||
|
||||
enablePlugins(spray.boilerplate.BoilerplatePlugin)
|
||||
disablePlugins(MimaPlugin)
|
||||
scalacOptions in Compile += "-language:_"
|
||||
|
|
|
|||
|
|
@ -13,6 +13,8 @@ import akka.http.scaladsl.util.FastFuture._
|
|||
import akka.http.scaladsl.model.headers._
|
||||
import akka.http.scaladsl.server.AuthenticationFailedRejection.{ CredentialsRejected, CredentialsMissing }
|
||||
|
||||
import scala.util.{Try, Success}
|
||||
|
||||
/**
|
||||
* Provides directives for securing an inner route using the standard Http authentication headers [[`WWW-Authenticate`]]
|
||||
* and [[Authorization]]. Most prominently, HTTP Basic authentication as defined in RFC 2617.
|
||||
|
|
@ -176,8 +178,30 @@ trait SecurityDirectives {
|
|||
* If the check fails the route is rejected with an [[AuthorizationFailedRejection]].
|
||||
*/
|
||||
def authorize(check: RequestContext ⇒ Boolean): Directive0 =
|
||||
extract(check).flatMap[Unit](if (_) pass else reject(AuthorizationFailedRejection)) &
|
||||
cancelRejection(AuthorizationFailedRejection)
|
||||
authorizeAsync(ctx => Future.successful(check(ctx)))
|
||||
|
||||
/**
|
||||
* Asynchronous version of [[authorize]].
|
||||
* If the [[Future]] fails or is completed with `false`
|
||||
* authorization fails and the route is rejected with an [[AuthorizationFailedRejection]].
|
||||
*/
|
||||
def authorizeAsync(check: ⇒ Future[Boolean]): Directive0 =
|
||||
authorizeAsync(ctx => check)
|
||||
|
||||
/**
|
||||
* Asynchronous version of [[authorize]].
|
||||
* If the [[Future]] fails or is completed with `false`
|
||||
* authorization fails and the route is rejected with an [[AuthorizationFailedRejection]].
|
||||
*/
|
||||
def authorizeAsync(check: RequestContext ⇒ Future[Boolean]): Directive0 =
|
||||
extractExecutionContext.flatMap { implicit ec ⇒
|
||||
extract(check).flatMap[Unit] { fa =>
|
||||
onComplete(fa).flatMap {
|
||||
case Success(true) => pass
|
||||
case _ => reject(AuthorizationFailedRejection)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a `Basic` [[HttpChallenge]] for the given realm.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue