diff --git a/akka-docs/rst/scala/code/docs/http/scaladsl/server/directives/SecurityDirectivesExamplesSpec.scala b/akka-docs/rst/scala/code/docs/http/scaladsl/server/directives/SecurityDirectivesExamplesSpec.scala index 00981f4f06..4cc8d7ee77 100644 --- a/akka-docs/rst/scala/code/docs/http/scaladsl/server/directives/SecurityDirectivesExamplesSpec.scala +++ b/akka-docs/rst/scala/code/docs/http/scaladsl/server/directives/SecurityDirectivesExamplesSpec.scala @@ -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") diff --git a/akka-http-core/src/main/java/akka/http/javadsl/model/headers/HttpChallenge.java b/akka-http-core/src/main/java/akka/http/javadsl/model/headers/HttpChallenge.java index c018313d53..2d7b15d52b 100644 --- a/akka-http-core/src/main/java/akka/http/javadsl/model/headers/HttpChallenge.java +++ b/akka-http-core/src/main/java/akka/http/javadsl/model/headers/HttpChallenge.java @@ -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 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 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 realm) { + return akka.http.scaladsl.model.headers.HttpChallenge.apply(scheme, realm.asScala(), Util.emptyMap); + } + + public static HttpChallenge create(String scheme, Option realm, Map params) { + return akka.http.scaladsl.model.headers.HttpChallenge.apply(scheme, realm.asScala(), Util.convertMapToScala(params)); } public static HttpChallenge createBasic(String realm) { diff --git a/akka-http-core/src/main/scala/akka/http/impl/model/parser/CommonRules.scala b/akka-http-core/src/main/scala/akka/http/impl/model/parser/CommonRules.scala index 1176c33133..e3b8c95f4a 100644 --- a/akka-http-core/src/main/scala/akka/http/impl/model/parser/CommonRules.scala +++ b/akka-http-core/src/main/scala/akka/http/impl/model/parser/CommonRules.scala @@ -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) } } diff --git a/akka-http-core/src/main/scala/akka/http/scaladsl/model/headers/HttpChallenge.scala b/akka-http-core/src/main/scala/akka/http/scaladsl/model/headers/HttpChallenge.scala index 633d0d221f..f4692f1339 100644 --- a/akka-http-core/src/main/scala/akka/http/scaladsl/model/headers/HttpChallenge.scala +++ b/akka-http-core/src/main/scala/akka/http/scaladsl/model/headers/HttpChallenge.scala @@ -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) diff --git a/akka-http-core/src/test/scala/akka/http/impl/model/parser/HttpHeaderSpec.scala b/akka-http-core/src/test/scala/akka/http/impl/model/parser/HttpHeaderSpec.scala index f7e02d5534..e4e3b92998 100644 --- a/akka-http-core/src/test/scala/akka/http/impl/model/parser/HttpHeaderSpec.scala +++ b/akka-http-core/src/test/scala/akka/http/impl/model/parser/HttpHeaderSpec.scala @@ -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=\"fooue\", 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 { diff --git a/akka-http-core/src/test/scala/akka/http/scaladsl/model/headers/HeaderSpec.scala b/akka-http-core/src/test/scala/akka/http/scaladsl/model/headers/HeaderSpec.scala index b84d0d104b..c8dd27e196 100644 --- a/akka-http-core/src/test/scala/akka/http/scaladsl/model/headers/HeaderSpec.scala +++ b/akka-http-core/src/test/scala/akka/http/scaladsl/model/headers/HeaderSpec.scala @@ -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 diff --git a/akka-http-tests/src/test/scala/akka/http/scaladsl/server/directives/SecurityDirectivesSpec.scala b/akka-http-tests/src/test/scala/akka/http/scaladsl/server/directives/SecurityDirectivesSpec.scala index 9cb6b52811..82b61d1f63 100644 --- a/akka-http-tests/src/test/scala/akka/http/scaladsl/server/directives/SecurityDirectivesSpec.scala +++ b/akka-http-tests/src/test/scala/akka/http/scaladsl/server/directives/SecurityDirectivesSpec.scala @@ -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)) }