Merge pull request #16179 from spray/w/15933-import-security-directives
+htp #15933 import SecurityDirectives (+ infrastructure) from spray
This commit is contained in:
commit
c6ab4c74ba
9 changed files with 318 additions and 20 deletions
|
|
@ -4,6 +4,8 @@
|
|||
|
||||
package akka.http.server
|
||||
|
||||
import akka.http.marshalling.Marshaller
|
||||
import akka.http.server.directives.AuthenticationDirectives._
|
||||
import com.typesafe.config.{ ConfigFactory, Config }
|
||||
import scala.concurrent.duration._
|
||||
import akka.actor.ActorSystem
|
||||
|
|
@ -28,11 +30,24 @@ object TestServer extends App {
|
|||
|
||||
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 {
|
||||
get {
|
||||
path("") {
|
||||
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") {
|
||||
complete("PONG!")
|
||||
} ~
|
||||
|
|
@ -47,16 +62,16 @@ object TestServer extends App {
|
|||
Console.readLine()
|
||||
system.shutdown()
|
||||
|
||||
lazy val index = HttpResponse(
|
||||
entity = HttpEntity(MediaTypes.`text/html`,
|
||||
"""|<html>
|
||||
| <body>
|
||||
| <h1>Say hello to <i>akka-http-core</i>!</h1>
|
||||
| <p>Defined resources:</p>
|
||||
| <ul>
|
||||
| <li><a href="/ping">/ping</a></li>
|
||||
| <li><a href="/crash">/crash</a></li>
|
||||
| </ul>
|
||||
| </body>
|
||||
|</html>""".stripMargin))
|
||||
lazy val index =
|
||||
<html>
|
||||
<body>
|
||||
<h1>Say hello to <i>akka-http-core</i>!</h1>
|
||||
<p>Defined resources:</p>
|
||||
<ul>
|
||||
<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>
|
||||
</ul>
|
||||
</body>
|
||||
</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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue