+htp #15933 import + improve SecurityDirectives (+ infrastructure) from spray
This commit is contained in:
parent
34363374b6
commit
dad5e568cd
9 changed files with 318 additions and 20 deletions
|
|
@ -4,6 +4,8 @@
|
||||||
|
|
||||||
package akka.http.server
|
package akka.http.server
|
||||||
|
|
||||||
|
import akka.http.marshalling.Marshaller
|
||||||
|
import akka.http.server.directives.AuthenticationDirectives._
|
||||||
import com.typesafe.config.{ ConfigFactory, Config }
|
import com.typesafe.config.{ ConfigFactory, Config }
|
||||||
import scala.concurrent.duration._
|
import scala.concurrent.duration._
|
||||||
import akka.actor.ActorSystem
|
import akka.actor.ActorSystem
|
||||||
|
|
@ -28,11 +30,24 @@ object TestServer extends App {
|
||||||
|
|
||||||
import ScalaRoutingDSL._
|
import ScalaRoutingDSL._
|
||||||
|
|
||||||
|
def auth =
|
||||||
|
HttpBasicAuthenticator.provideUserName {
|
||||||
|
case p @ UserCredentials.Provided(name) ⇒ p.verifySecret(name + "-password")
|
||||||
|
case _ ⇒ false
|
||||||
|
}
|
||||||
|
|
||||||
|
implicit val html = Marshaller.nodeSeqMarshaller(MediaTypes.`text/html`)
|
||||||
|
|
||||||
handleConnections(bindingFuture) withRoute {
|
handleConnections(bindingFuture) withRoute {
|
||||||
get {
|
get {
|
||||||
path("") {
|
path("") {
|
||||||
complete(index)
|
complete(index)
|
||||||
} ~
|
} ~
|
||||||
|
path("secure") {
|
||||||
|
HttpBasicAuthentication("My very secure site")(auth) { user ⇒
|
||||||
|
complete(<html><body>Hello <b>{ user }</b>. Access has been granted!</body></html>)
|
||||||
|
}
|
||||||
|
} ~
|
||||||
path("ping") {
|
path("ping") {
|
||||||
complete("PONG!")
|
complete("PONG!")
|
||||||
} ~
|
} ~
|
||||||
|
|
@ -47,16 +62,16 @@ object TestServer extends App {
|
||||||
Console.readLine()
|
Console.readLine()
|
||||||
system.shutdown()
|
system.shutdown()
|
||||||
|
|
||||||
lazy val index = HttpResponse(
|
lazy val index =
|
||||||
entity = HttpEntity(MediaTypes.`text/html`,
|
<html>
|
||||||
"""|<html>
|
<body>
|
||||||
| <body>
|
<h1>Say hello to <i>akka-http-core</i>!</h1>
|
||||||
| <h1>Say hello to <i>akka-http-core</i>!</h1>
|
<p>Defined resources:</p>
|
||||||
| <p>Defined resources:</p>
|
<ul>
|
||||||
| <ul>
|
<li><a href="/ping">/ping</a></li>
|
||||||
| <li><a href="/ping">/ping</a></li>
|
<li><a href="/secure">/secure</a> Use any username and '<username>-password' as credentials</li>
|
||||||
| <li><a href="/crash">/crash</a></li>
|
<li><a href="/crash">/crash</a></li>
|
||||||
| </ul>
|
</ul>
|
||||||
| </body>
|
</body>
|
||||||
|</html>""".stripMargin))
|
</html>
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,79 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
|
||||||
|
*/
|
||||||
|
|
||||||
|
package akka.http.server
|
||||||
|
package directives
|
||||||
|
|
||||||
|
import akka.http.model._
|
||||||
|
import akka.http.model.headers._
|
||||||
|
import akka.http.server.AuthenticationFailedRejection.{ CredentialsRejected, CredentialsMissing }
|
||||||
|
import akka.http.server.directives.AuthenticationDirectives._
|
||||||
|
|
||||||
|
import scala.concurrent.Future
|
||||||
|
|
||||||
|
class AuthenticationDirectivesSpec extends RoutingSpec {
|
||||||
|
val dontAuth = HttpBasicAuthentication("MyRealm")(HttpBasicAuthenticator[String](_ ⇒ Future.successful(None)))
|
||||||
|
val doAuth = HttpBasicAuthentication("MyRealm")(HttpBasicAuthenticator.provideUserName(_ ⇒ true))
|
||||||
|
val authWithAnonymous = doAuth.withAnonymousUser("We are Legion")
|
||||||
|
|
||||||
|
val challenge = HttpChallenge("Basic", "MyRealm")
|
||||||
|
|
||||||
|
"the 'HttpBasicAuthentication' directive" should {
|
||||||
|
"reject requests without Authorization header with an AuthenticationFailedRejection" in {
|
||||||
|
Get() ~> {
|
||||||
|
dontAuth { echoComplete }
|
||||||
|
} ~> check { rejection shouldEqual AuthenticationFailedRejection(CredentialsMissing, challenge) }
|
||||||
|
}
|
||||||
|
"reject unauthenticated requests with Authorization header with an AuthenticationFailedRejection" in {
|
||||||
|
Get() ~> Authorization(BasicHttpCredentials("Bob", "")) ~> {
|
||||||
|
dontAuth { echoComplete }
|
||||||
|
} ~> check { rejection shouldEqual AuthenticationFailedRejection(CredentialsRejected, challenge) }
|
||||||
|
}
|
||||||
|
"reject requests with illegal Authorization header with 401" in {
|
||||||
|
Get() ~> RawHeader("Authorization", "bob alice") ~> sealRoute {
|
||||||
|
dontAuth { echoComplete }
|
||||||
|
} ~> check {
|
||||||
|
status shouldEqual StatusCodes.Unauthorized
|
||||||
|
responseAs[String] shouldEqual "The resource requires authentication, which was not supplied with the request"
|
||||||
|
header[`WWW-Authenticate`] shouldEqual Some(`WWW-Authenticate`(challenge))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"extract the object representing the user identity created by successful authentication" in {
|
||||||
|
Get() ~> Authorization(BasicHttpCredentials("Alice", "")) ~> {
|
||||||
|
doAuth { echoComplete }
|
||||||
|
} ~> check { responseAs[String] shouldEqual "Alice" }
|
||||||
|
}
|
||||||
|
"extract the object representing the user identity created for the anonymous user" in {
|
||||||
|
Get() ~> {
|
||||||
|
authWithAnonymous { echoComplete }
|
||||||
|
} ~> check { responseAs[String] shouldEqual "We are Legion" }
|
||||||
|
}
|
||||||
|
"properly handle exceptions thrown in its inner route" in {
|
||||||
|
object TestException extends RuntimeException
|
||||||
|
Get() ~> Authorization(BasicHttpCredentials("Alice", "")) ~> {
|
||||||
|
sealRoute {
|
||||||
|
doAuth { _ ⇒ throw TestException }
|
||||||
|
}
|
||||||
|
} ~> check { status shouldEqual StatusCodes.InternalServerError }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"AuthenticationDirectives facilities" should {
|
||||||
|
"properly stack several authentication directives" in {
|
||||||
|
val otherChallenge = HttpChallenge("MyAuth", "MyRealm2")
|
||||||
|
val otherAuth: Directive1[String] = AuthenticationDirectives.authenticateOrRejectWithChallenge { (cred: Option[HttpCredentials]) ⇒
|
||||||
|
Future.successful(Left(otherChallenge))
|
||||||
|
}
|
||||||
|
val bothAuth = dontAuth | otherAuth
|
||||||
|
|
||||||
|
Get() ~> sealRoute {
|
||||||
|
bothAuth { echoComplete }
|
||||||
|
} ~> check {
|
||||||
|
status shouldEqual StatusCodes.Unauthorized
|
||||||
|
headers.collect {
|
||||||
|
case `WWW-Authenticate`(challenge +: Nil) ⇒ challenge
|
||||||
|
} shouldEqual Seq(challenge, otherChallenge)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -13,7 +13,7 @@ import FastFuture._
|
||||||
/**
|
/**
|
||||||
* A directive that provides a tuple of values of type `L` to create an inner route.
|
* A directive that provides a tuple of values of type `L` to create an inner route.
|
||||||
*/
|
*/
|
||||||
sealed abstract class Directive[L] private (implicit val ev: Tuple[L]) {
|
abstract class Directive[L](implicit val ev: Tuple[L]) {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Calls the inner route with a tuple of extracted values of type `L`.
|
* Calls the inner route with a tuple of extracted values of type `L`.
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ import directives._
|
||||||
// FIXME: the comments are kept as a reminder which directives are not yet imported
|
// FIXME: the comments are kept as a reminder which directives are not yet imported
|
||||||
|
|
||||||
trait Directives extends RouteConcatenation
|
trait Directives extends RouteConcatenation
|
||||||
|
with AuthenticationDirectives
|
||||||
with BasicDirectives
|
with BasicDirectives
|
||||||
with CacheConditionDirectives
|
with CacheConditionDirectives
|
||||||
//with ChunkingDirectives
|
//with ChunkingDirectives
|
||||||
|
|
@ -30,6 +31,6 @@ trait Directives extends RouteConcatenation
|
||||||
with RespondWithDirectives
|
with RespondWithDirectives
|
||||||
with RouteDirectives
|
with RouteDirectives
|
||||||
with SchemeDirectives
|
with SchemeDirectives
|
||||||
//with SecurityDirectives
|
with SecurityDirectives
|
||||||
|
|
||||||
object Directives extends Directives
|
object Directives extends Directives
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@
|
||||||
package akka.http.server
|
package akka.http.server
|
||||||
|
|
||||||
import akka.http.model._
|
import akka.http.model._
|
||||||
import akka.http.model.headers.{ ByteRange, HttpEncoding }
|
import akka.http.model.headers.{ HttpChallenge, ByteRange, HttpEncoding }
|
||||||
|
|
||||||
import scala.collection.immutable
|
import scala.collection.immutable
|
||||||
|
|
||||||
|
|
@ -125,7 +125,7 @@ case class UnacceptedResponseEncodingRejection(supported: HttpEncoding) extends
|
||||||
* specified in the cause.
|
* specified in the cause.
|
||||||
*/
|
*/
|
||||||
case class AuthenticationFailedRejection(cause: AuthenticationFailedRejection.Cause,
|
case class AuthenticationFailedRejection(cause: AuthenticationFailedRejection.Cause,
|
||||||
challengeHeaders: List[HttpHeader]) extends Rejection
|
challenge: HttpChallenge) extends Rejection
|
||||||
|
|
||||||
object AuthenticationFailedRejection {
|
object AuthenticationFailedRejection {
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -31,14 +31,23 @@ object RejectionHandler {
|
||||||
def default(implicit ec: ExecutionContext) = apply(default = true) {
|
def default(implicit ec: ExecutionContext) = apply(default = true) {
|
||||||
case Nil ⇒ complete(NotFound, "The requested resource could not be found.")
|
case Nil ⇒ complete(NotFound, "The requested resource could not be found.")
|
||||||
|
|
||||||
case AuthenticationFailedRejection(cause, challengeHeaders) +: _ ⇒
|
case rejections @ (AuthenticationFailedRejection(cause, _) +: _) ⇒
|
||||||
val rejectionMessage = cause match {
|
val rejectionMessage = cause match {
|
||||||
case CredentialsMissing ⇒ "The resource requires authentication, which was not supplied with the request"
|
case CredentialsMissing ⇒ "The resource requires authentication, which was not supplied with the request"
|
||||||
case CredentialsRejected ⇒ "The supplied authentication is invalid"
|
case CredentialsRejected ⇒ "The supplied authentication is invalid"
|
||||||
}
|
}
|
||||||
ctx ⇒ ctx.complete(Unauthorized, challengeHeaders, rejectionMessage)
|
val challenges = rejections.collect { case AuthenticationFailedRejection(_, challenge) ⇒ challenge }
|
||||||
|
// Multiple challenges per WWW-Authenticate header are allowed per spec,
|
||||||
|
// however, it seems many browsers will ignore all challenges but the first.
|
||||||
|
// Therefore, multiple WWW-Authenticate headers are rendered, instead.
|
||||||
|
//
|
||||||
|
// See https://code.google.com/p/chromium/issues/detail?id=103220
|
||||||
|
// and https://bugzilla.mozilla.org/show_bug.cgi?id=669675
|
||||||
|
val authenticateHeaders = challenges.map(`WWW-Authenticate`(_))
|
||||||
|
|
||||||
case AuthorizationFailedRejection +: _ ⇒
|
complete(Unauthorized, authenticateHeaders, rejectionMessage)
|
||||||
|
|
||||||
|
case AuthorizationFailedRejection +: _ ⇒
|
||||||
complete(Forbidden, "The supplied authentication is not authorized to access this resource")
|
complete(Forbidden, "The supplied authentication is not authorized to access this resource")
|
||||||
|
|
||||||
case MalformedFormFieldRejection(name, msg, _) +: _ ⇒
|
case MalformedFormFieldRejection(name, msg, _) +: _ ⇒
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,169 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
|
||||||
|
*/
|
||||||
|
|
||||||
|
package akka.http.server
|
||||||
|
package directives
|
||||||
|
|
||||||
|
import scala.reflect.ClassTag
|
||||||
|
|
||||||
|
import scala.concurrent.{ ExecutionContext, Future }
|
||||||
|
|
||||||
|
import akka.http.util._
|
||||||
|
import akka.http.util.FastFuture._
|
||||||
|
|
||||||
|
import akka.http.model.headers._
|
||||||
|
import akka.http.server.AuthenticationFailedRejection.{ CredentialsRejected, CredentialsMissing }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
trait AuthenticationDirectives {
|
||||||
|
import BasicDirectives._
|
||||||
|
import AuthenticationDirectives._
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The result of an HTTP authentication attempt is either the user object or
|
||||||
|
* an HttpChallenge to present to the browser.
|
||||||
|
*/
|
||||||
|
type AuthenticationResult[+T] = Either[HttpChallenge, T]
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given [[UserCredentials]] the HttpBasicAuthenticator
|
||||||
|
* returns a Future of either the authenticated user object or None of the user
|
||||||
|
* couldn't be authenticated.
|
||||||
|
*/
|
||||||
|
type HttpBasicAuthenticator[T] = UserCredentials ⇒ Future[Option[T]]
|
||||||
|
|
||||||
|
object HttpBasicAuthentication {
|
||||||
|
def challengeFor(realm: String) = HttpChallenge(scheme = "Basic", realm = realm, params = Map.empty)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A directive that wraps the inner route with Http Basic authentication support. The given authenticator
|
||||||
|
* is used to determine if the credentials in the request are valid and which user object to supply
|
||||||
|
* to the inner route.
|
||||||
|
*/
|
||||||
|
def apply[T](realm: String)(authenticator: HttpBasicAuthenticator[T]): AuthenticationDirective[T] =
|
||||||
|
extractExecutionContext.flatMap { implicit ctx ⇒
|
||||||
|
authenticateOrRejectWithChallenge[BasicHttpCredentials, T] { basic ⇒
|
||||||
|
authenticator(authDataFor(basic)).fast.map {
|
||||||
|
case Some(t) ⇒ AuthenticationResult.success(t)
|
||||||
|
case None ⇒ AuthenticationResult.failWithChallenge(challengeFor(realm))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private def authDataFor(cred: Option[BasicHttpCredentials]): UserCredentials =
|
||||||
|
cred match {
|
||||||
|
case Some(BasicHttpCredentials(username, receivedSecret)) ⇒
|
||||||
|
new UserCredentials.Provided(username) {
|
||||||
|
def verifySecret(secret: String): Boolean = secret secure_== receivedSecret
|
||||||
|
}
|
||||||
|
case None ⇒ UserCredentials.Missing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
object AuthenticationDirectives extends AuthenticationDirectives {
|
||||||
|
import BasicDirectives._
|
||||||
|
import RouteDirectives._
|
||||||
|
import FutureDirectives._
|
||||||
|
import HeaderDirectives._
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents authentication credentials supplied with a request. Credentials can either be
|
||||||
|
* [[UserCredentials.Missing]] or can be [[UserCredentials.Provided]] in which case a username is
|
||||||
|
* supplied and a function to check the known secret against the provided one in a secure fashion.
|
||||||
|
*/
|
||||||
|
sealed trait UserCredentials
|
||||||
|
object UserCredentials {
|
||||||
|
case object Missing extends UserCredentials
|
||||||
|
abstract case class Provided(username: String) extends UserCredentials {
|
||||||
|
def verifySecret(secret: String): Boolean
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
object AuthenticationResult {
|
||||||
|
def success[T](user: T): AuthenticationResult[T] = Right(user)
|
||||||
|
def failWithChallenge(challenge: HttpChallenge): AuthenticationResult[Nothing] = Left(challenge)
|
||||||
|
}
|
||||||
|
|
||||||
|
object HttpBasicAuthenticator {
|
||||||
|
implicit def apply[T](f: UserCredentials ⇒ Future[Option[T]]): HttpBasicAuthenticator[T] =
|
||||||
|
new HttpBasicAuthenticator[T] {
|
||||||
|
def apply(credentials: UserCredentials): Future[Option[T]] = f(credentials)
|
||||||
|
}
|
||||||
|
def fromPF[T](pf: PartialFunction[UserCredentials, Future[T]])(implicit ec: ExecutionContext): HttpBasicAuthenticator[T] =
|
||||||
|
new HttpBasicAuthenticator[T] {
|
||||||
|
def apply(credentials: UserCredentials): Future[Option[T]] =
|
||||||
|
if (pf.isDefinedAt(credentials)) pf(credentials).fast.map(Some(_))
|
||||||
|
else FastFuture.successful(None)
|
||||||
|
}
|
||||||
|
|
||||||
|
def checkAndProvide[T](check: UserCredentials.Provided ⇒ Boolean)(provide: String ⇒ T)(implicit ec: ExecutionContext): HttpBasicAuthenticator[T] =
|
||||||
|
HttpBasicAuthenticator.fromPF {
|
||||||
|
case p @ UserCredentials.Provided(name) if check(p) ⇒ FastFuture.successful(provide(name))
|
||||||
|
}
|
||||||
|
def provideUserName(check: UserCredentials.Provided ⇒ Boolean)(implicit ec: ExecutionContext): HttpBasicAuthenticator[String] =
|
||||||
|
checkAndProvide(check)(identity)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lifts an authenticator function into a directive. The authenticator function gets passed in credentials from the
|
||||||
|
* [[Authorization]] header of the request. If the function returns ``Right(user)`` the user object is provided
|
||||||
|
* to the inner route. If the function returns ``Left(challenge)`` the request is rejected with an
|
||||||
|
* [[AuthenticationFailedRejection]] that contains this challenge to be added to the response.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
def authenticateOrRejectWithChallenge[T](authenticator: Option[HttpCredentials] ⇒ Future[AuthenticationResult[T]]): AuthenticationDirective[T] =
|
||||||
|
extractExecutionContext.flatMap { implicit ctx ⇒
|
||||||
|
extractCredentials.flatMap { cred ⇒
|
||||||
|
onSuccess(authenticator(cred)).flatMap {
|
||||||
|
case Right(user) ⇒ provide(user)
|
||||||
|
case Left(challenge) ⇒
|
||||||
|
val cause = if (cred.isEmpty) CredentialsMissing else CredentialsRejected
|
||||||
|
reject(AuthenticationFailedRejection(cause, challenge)): Directive1[T]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lifts an authenticator function into a directive. Same as ``authenticateOrRejectWithChallenge`` above but only applies
|
||||||
|
* the authenticator function with a certain type of credentials.
|
||||||
|
*/
|
||||||
|
def authenticateOrRejectWithChallenge[C <: HttpCredentials: ClassTag, T](authenticator: Option[C] ⇒ Future[AuthenticationResult[T]]): AuthenticationDirective[T] =
|
||||||
|
authenticateOrRejectWithChallenge[T] { cred ⇒
|
||||||
|
authenticator {
|
||||||
|
cred.collect {
|
||||||
|
case c: C ⇒ c
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
trait AuthenticationDirective[T] extends Directive1[T] {
|
||||||
|
/**
|
||||||
|
* Returns a copy of this authenticationDirective that will provide ``Some(user)`` if credentials
|
||||||
|
* were supplied and otherwise ``None``.
|
||||||
|
*/
|
||||||
|
def optional: Directive1[Option[T]] =
|
||||||
|
this.map(Some(_): Option[T]).recover {
|
||||||
|
case AuthenticationFailedRejection(CredentialsMissing, _) +: _ ⇒ provide(None)
|
||||||
|
case rejs ⇒ reject(rejs: _*)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a copy of this authenticationDirective that uses the given object as the
|
||||||
|
* anonymous user which will be used if no credentials were supplied in the request.
|
||||||
|
*/
|
||||||
|
def withAnonymousUser(anonymous: T): Directive1[T] =
|
||||||
|
optional.map(_.getOrElse(anonymous))
|
||||||
|
}
|
||||||
|
object AuthenticationDirective {
|
||||||
|
implicit def apply[T](other: Directive1[T]): AuthenticationDirective[T] =
|
||||||
|
new AuthenticationDirective[T] { def tapply(inner: Tuple1[T] ⇒ Route) = other.tapply(inner) }
|
||||||
|
}
|
||||||
|
|
||||||
|
def extractCredentials: Directive1[Option[HttpCredentials]] =
|
||||||
|
optionalHeaderValueByType[`Authorization`]().map(_.map(_.credentials))
|
||||||
|
}
|
||||||
|
|
@ -16,7 +16,7 @@ trait CookieDirectives {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Extracts an HttpCookie with the given name. If the cookie is not present the
|
* Extracts an HttpCookie with the given name. If the cookie is not present the
|
||||||
* request is rejected with a respective [[spray.routing.MissingCookieRejection]].
|
* request is rejected with a respective [[MissingCookieRejection]].
|
||||||
*/
|
*/
|
||||||
def cookie(name: String): Directive1[HttpCookie] =
|
def cookie(name: String): Directive1[HttpCookie] =
|
||||||
headerValue(findCookie(name)) | reject(MissingCookieRejection(name))
|
headerValue(findCookie(name)) | reject(MissingCookieRejection(name))
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
|
||||||
|
*/
|
||||||
|
|
||||||
|
package akka.http.server
|
||||||
|
package directives
|
||||||
|
|
||||||
|
import BasicDirectives._
|
||||||
|
import RouteDirectives._
|
||||||
|
|
||||||
|
trait SecurityDirectives {
|
||||||
|
/**
|
||||||
|
* Applies the given authorization check to the request.
|
||||||
|
* If the check fails the route is rejected with an [[AuthorizationFailedRejection]].
|
||||||
|
*/
|
||||||
|
def authorize(check: ⇒ Boolean): Directive0 = authorize(_ ⇒ check)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Applies the given authorization check to the request.
|
||||||
|
* 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)
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue