+htp #15933 import + improve SecurityDirectives (+ infrastructure) from spray

This commit is contained in:
Johannes Rudolph 2014-11-03 16:27:38 +01:00
parent 34363374b6
commit dad5e568cd
9 changed files with 318 additions and 20 deletions

View file

@ -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 '&lt;username&gt;-password' as credentials</li>
<li><a href="/crash">/crash</a></li>
</ul>
</body>
</html>
}

View file

@ -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)
}
}
}
}