=htc #20786 Fix for RFC 7235 so HttpChallenge only renders the realm when it has a value (#21043)

This commit is contained in:
Ian Clegg 2016-07-28 12:32:50 +01:00 committed by Konrad Malawski
parent 95280bf16f
commit f33f2197b6
7 changed files with 54 additions and 22 deletions

View file

@ -34,7 +34,7 @@ class SecurityDirectivesExamplesSpec extends RoutingSpec {
Get("/secured") ~> route ~> check {
status shouldEqual StatusCodes.Unauthorized
responseAs[String] shouldEqual "The resource requires authentication, which was not supplied with the request"
header[`WWW-Authenticate`].get.challenges.head shouldEqual HttpChallenge("Basic", "secure site")
header[`WWW-Authenticate`].get.challenges.head shouldEqual HttpChallenge("Basic", Some("secure site"))
}
val validCredentials = BasicHttpCredentials("John", "p4ssw0rd")
@ -49,7 +49,7 @@ class SecurityDirectivesExamplesSpec extends RoutingSpec {
route ~> check {
status shouldEqual StatusCodes.Unauthorized
responseAs[String] shouldEqual "The supplied authentication is invalid"
header[`WWW-Authenticate`].get.challenges.head shouldEqual HttpChallenge("Basic", "secure site")
header[`WWW-Authenticate`].get.challenges.head shouldEqual HttpChallenge("Basic", Some("secure site"))
}
}
"authenticateBasicPF-0" in {
@ -71,7 +71,7 @@ class SecurityDirectivesExamplesSpec extends RoutingSpec {
Get("/secured") ~> route ~> check {
status shouldEqual StatusCodes.Unauthorized
responseAs[String] shouldEqual "The resource requires authentication, which was not supplied with the request"
header[`WWW-Authenticate`].get.challenges.head shouldEqual HttpChallenge("Basic", "secure site")
header[`WWW-Authenticate`].get.challenges.head shouldEqual HttpChallenge("Basic", Some("secure site"))
}
val validCredentials = BasicHttpCredentials("John", "p4ssw0rd")
@ -92,7 +92,7 @@ class SecurityDirectivesExamplesSpec extends RoutingSpec {
route ~> check {
status shouldEqual StatusCodes.Unauthorized
responseAs[String] shouldEqual "The supplied authentication is invalid"
header[`WWW-Authenticate`].get.challenges.head shouldEqual HttpChallenge("Basic", "secure site")
header[`WWW-Authenticate`].get.challenges.head shouldEqual HttpChallenge("Basic", Some("secure site"))
}
}
"authenticateBasicPFAsync-0" in {
@ -120,7 +120,7 @@ class SecurityDirectivesExamplesSpec extends RoutingSpec {
Get("/secured") ~> route ~> check {
status shouldEqual StatusCodes.Unauthorized
responseAs[String] shouldEqual "The resource requires authentication, which was not supplied with the request"
header[`WWW-Authenticate`].get.challenges.head shouldEqual HttpChallenge("Basic", "secure site")
header[`WWW-Authenticate`].get.challenges.head shouldEqual HttpChallenge("Basic", Some("secure site"))
}
val validCredentials = BasicHttpCredentials("John", "p4ssw0rd")
@ -135,7 +135,7 @@ class SecurityDirectivesExamplesSpec extends RoutingSpec {
route ~> check {
status shouldEqual StatusCodes.Unauthorized
responseAs[String] shouldEqual "The supplied authentication is invalid"
header[`WWW-Authenticate`].get.challenges.head shouldEqual HttpChallenge("Basic", "secure site")
header[`WWW-Authenticate`].get.challenges.head shouldEqual HttpChallenge("Basic", Some("secure site"))
}
}
"authenticateBasicAsync-0" in {
@ -163,7 +163,7 @@ class SecurityDirectivesExamplesSpec extends RoutingSpec {
Get("/secured") ~> route ~> check {
status shouldEqual StatusCodes.Unauthorized
responseAs[String] shouldEqual "The resource requires authentication, which was not supplied with the request"
header[`WWW-Authenticate`].get.challenges.head shouldEqual HttpChallenge("Basic", "secure site")
header[`WWW-Authenticate`].get.challenges.head shouldEqual HttpChallenge("Basic", Some("secure site"))
}
val validCredentials = BasicHttpCredentials("John", "p4ssw0rd")
@ -178,11 +178,11 @@ class SecurityDirectivesExamplesSpec extends RoutingSpec {
route ~> check {
status shouldEqual StatusCodes.Unauthorized
responseAs[String] shouldEqual "The supplied authentication is invalid"
header[`WWW-Authenticate`].get.challenges.head shouldEqual HttpChallenge("Basic", "secure site")
header[`WWW-Authenticate`].get.challenges.head shouldEqual HttpChallenge("Basic", Some("secure site"))
}
}
"authenticateOrRejectWithChallenge-0" in {
val challenge = HttpChallenge("MyAuth", "MyRealm")
val challenge = HttpChallenge("MyAuth", Some("MyRealm"))
// your custom authentication logic:
def auth(creds: HttpCredentials): Boolean = true
@ -208,7 +208,7 @@ class SecurityDirectivesExamplesSpec extends RoutingSpec {
Get("/secured") ~> route ~> check {
status shouldEqual StatusCodes.Unauthorized
responseAs[String] shouldEqual "The resource requires authentication, which was not supplied with the request"
header[`WWW-Authenticate`].get.challenges.head shouldEqual HttpChallenge("MyAuth", "MyRealm")
header[`WWW-Authenticate`].get.challenges.head shouldEqual HttpChallenge("MyAuth", Some("MyRealm"))
}
val validCredentials = BasicHttpCredentials("John", "p4ssw0rd")

View file

@ -5,6 +5,8 @@
package akka.http.javadsl.model.headers;
import akka.http.impl.util.Util;
import akka.japi.Option;
import java.util.Map;
@ -14,11 +16,28 @@ public abstract class HttpChallenge {
public abstract Map<String, String> getParams();
/**
* @deprecated Use constructor with optional realm parameter instead.
*/
@Deprecated
public static HttpChallenge create(String scheme, String realm) {
return new akka.http.scaladsl.model.headers.HttpChallenge(scheme, realm, Util.emptyMap);
return akka.http.scaladsl.model.headers.HttpChallenge.apply(scheme, scala.Option.apply(realm), Util.emptyMap);
}
/**
* @deprecated Use constructor with optional realm parameter instead.
*/
@Deprecated
public static HttpChallenge create(String scheme, String realm, Map<String, String> params) {
return new akka.http.scaladsl.model.headers.HttpChallenge(scheme, realm, Util.convertMapToScala(params));
return akka.http.scaladsl.model.headers.HttpChallenge.apply(scheme, scala.Option.apply(realm), Util.convertMapToScala(params));
}
public static HttpChallenge create(String scheme, Option<String> realm) {
return akka.http.scaladsl.model.headers.HttpChallenge.apply(scheme, realm.asScala(), Util.emptyMap);
}
public static HttpChallenge create(String scheme, Option<String> realm, Map<String, String> params) {
return akka.http.scaladsl.model.headers.HttpChallenge.apply(scheme, realm.asScala(), Util.convertMapToScala(params));
}
public static HttpChallenge createBasic(String realm) {

View file

@ -174,7 +174,7 @@ private[parser] trait CommonRules { this: Parser with StringBuilding ⇒
def challenge = rule {
`challenge-or-credentials` ~> { (scheme, params)
val (realms, otherParams) = params.partition(_._1 equalsIgnoreCase "realm")
HttpChallenge(scheme, realms.headOption.map(_._2).getOrElse(""), otherParams.toMap)
HttpChallenge(scheme, realms.headOption.map(_._2), otherParams.toMap)
}
}

View file

@ -13,7 +13,8 @@ final case class HttpChallenge(scheme: String, realm: String,
params: Map[String, String] = Map.empty) extends jm.headers.HttpChallenge with ValueRenderable {
def render[R <: Rendering](r: R): r.type = {
r ~~ scheme ~~ " realm=" ~~#! realm
r ~~ scheme
if (realm != null) r ~~ " realm=" ~~#! realm
if (params.nonEmpty) params.foreach { case (k, v) r ~~ ',' ~~ k ~~ '=' ~~# v }
r
}
@ -22,6 +23,16 @@ final case class HttpChallenge(scheme: String, realm: String,
def getParams: util.Map[String, String] = params.asJava
}
// FIXME: AbstractFunction3 required for bin compat. remove in Akka 3.0 and change realm in case class to option #20786
object HttpChallenge extends scala.runtime.AbstractFunction3[String, String, Map[String, String], HttpChallenge] {
def apply(scheme: String, realm: Option[String]): HttpChallenge =
HttpChallenge(scheme, realm.orNull, Map.empty[String, String])
def apply(scheme: String, realm: Option[String], params: Map[String, String]): HttpChallenge =
HttpChallenge(scheme, realm.orNull, params)
}
object HttpChallenges {
def basic(realm: String): HttpChallenge = HttpChallenge("Basic", realm)

View file

@ -373,7 +373,7 @@ class HttpHeaderSpec extends FreeSpec with Matchers {
"Proxy-Authenticate" in {
"Proxy-Authenticate: Basic realm=\"WallyWorld\",attr=\"val>ue\", Fancy realm=\"yeah\"" =!=
`Proxy-Authenticate`(HttpChallenge("Basic", "WallyWorld", Map("attr" "val>ue")), HttpChallenge("Fancy", "yeah"))
`Proxy-Authenticate`(HttpChallenge("Basic", Some("WallyWorld"), Map("attr" "val>ue")), HttpChallenge("Fancy", Some("yeah")))
}
"Proxy-Authorization" in {
@ -544,11 +544,13 @@ class HttpHeaderSpec extends FreeSpec with Matchers {
}
"WWW-Authenticate" in {
"WWW-Authenticate: Basic" =!=
`WWW-Authenticate`(HttpChallenge("Basic", None))
"WWW-Authenticate: Basic realm=\"WallyWorld\"" =!=
`WWW-Authenticate`(HttpChallenge("Basic", "WallyWorld"))
`WWW-Authenticate`(HttpChallenge("Basic", Some("WallyWorld")))
"WWW-Authenticate: BaSiC rEaLm=WallyWorld" =!=
`WWW-Authenticate`(HttpChallenge("BaSiC", "WallyWorld")).renderedTo("BaSiC realm=\"WallyWorld\"")
"WWW-Authenticate: Basic realm=\"foo<bar\"" =!= `WWW-Authenticate`(HttpChallenge("Basic", "foo<bar"))
"WWW-Authenticate: Basic realm=\"foo<bar\"" =!= `WWW-Authenticate`(HttpChallenge("Basic", Some("foo<bar")))
"""WWW-Authenticate: Digest
realm="testrealm@host.com",
qop="auth,auth-int",
@ -559,9 +561,9 @@ class HttpHeaderSpec extends FreeSpec with Matchers {
"nonce" "dcd98b7102dd2f0e8b11d0f600bfb0c093", "opaque" "5ccc069c403ebaf9f0171e9517f40e41"))).renderedTo(
"Digest realm=\"testrealm@host.com\",qop=\"auth,auth-int\",nonce=dcd98b7102dd2f0e8b11d0f600bfb0c093,opaque=5ccc069c403ebaf9f0171e9517f40e41")
"WWW-Authenticate: Basic realm=\"WallyWorld\",attr=\"val>ue\", Fancy realm=\"yeah\"" =!=
`WWW-Authenticate`(HttpChallenge("Basic", "WallyWorld", Map("attr" "val>ue")), HttpChallenge("Fancy", "yeah"))
`WWW-Authenticate`(HttpChallenge("Basic", Some("WallyWorld"), Map("attr" "val>ue")), HttpChallenge("Fancy", Some("yeah")))
"""WWW-Authenticate: Fancy realm="Secure Area",nonce=42""" =!=
`WWW-Authenticate`(HttpChallenge("Fancy", "Secure Area", Map("nonce" "42")))
`WWW-Authenticate`(HttpChallenge("Fancy", Some("Secure Area"), Map("nonce" "42")))
}
"X-Forwarded-For" in {

View file

@ -153,7 +153,7 @@ class HeaderSpec extends FreeSpec with Matchers {
`Last-Modified`(DateTime(2016, 2, 4, 9, 9, 0)),
Link(Uri("http://example.com"), LinkParams.`title*`("example")),
Location(Uri("http://example.com")),
`Proxy-Authenticate`(HttpChallenge("Basic", "example.com")),
`Proxy-Authenticate`(HttpChallenge("Basic", Some("example.com"))),
`Sec-WebSocket-Accept`("dGhlIHNhbXBsZSBub25jZQ"),
`Sec-WebSocket-Extensions`(Vector(WebSocketExtension("foo"))),
`Sec-WebSocket-Version`(Vector(13)),
@ -161,7 +161,7 @@ class HeaderSpec extends FreeSpec with Matchers {
`Set-Cookie`(HttpCookie("sessionId", "b0eb8b8b3ad246")),
`Transfer-Encoding`(TransferEncodings.chunked),
Upgrade(Vector(UpgradeProtocol("HTTP", Some("2.0")))),
`WWW-Authenticate`(HttpChallenge("Basic", "example.com")))
`WWW-Authenticate`(HttpChallenge("Basic", Some("example.com"))))
responseHeaders.foreach { header
header shouldBe 'renderInResponses

View file

@ -123,7 +123,7 @@ class SecurityDirectivesSpec extends RoutingSpec {
}
"authentication directives" should {
"properly stack" in {
val otherChallenge = HttpChallenge("MyAuth", "MyRealm2")
val otherChallenge = HttpChallenge("MyAuth", Some("MyRealm2"))
val otherAuth: Directive1[String] = authenticateOrRejectWithChallenge { (cred: Option[HttpCredentials])
Future.successful(Left(otherChallenge))
}