From cc22ed45607500ec0703a1ec214b76c34a9feef6 Mon Sep 17 00:00:00 2001 From: Hawstein Date: Sun, 19 Jun 2016 08:06:19 +0800 Subject: [PATCH] +doc example snippet for akka http java dsl: SecurityDirectives (#20717) --- .../SecurityDirectivesExamplesTest.java | 364 ++++++++++++++++++ .../security-directives/authenticateBasic.rst | 3 +- .../authenticateBasicAsync.rst | 3 +- .../authenticateBasicPF.rst | 3 +- .../authenticateBasicPFAsync.rst | 3 +- .../authenticateOrRejectWithChallenge.rst | 3 +- .../security-directives/authorize.rst | 3 +- .../security-directives/authorizeAsync.rst | 3 +- .../extractCredentials.rst | 3 +- .../akka/http/javadsl/model/HttpMessage.java | 6 + .../http/scaladsl/model/HttpMessage.scala | 2 + .../directives/SecurityDirectives.scala | 52 ++- project/MiMa.scala | 5 + 13 files changed, 442 insertions(+), 11 deletions(-) create mode 100644 akka-docs/rst/java/code/docs/http/javadsl/server/directives/SecurityDirectivesExamplesTest.java diff --git a/akka-docs/rst/java/code/docs/http/javadsl/server/directives/SecurityDirectivesExamplesTest.java b/akka-docs/rst/java/code/docs/http/javadsl/server/directives/SecurityDirectivesExamplesTest.java new file mode 100644 index 0000000000..68d7386bbe --- /dev/null +++ b/akka-docs/rst/java/code/docs/http/javadsl/server/directives/SecurityDirectivesExamplesTest.java @@ -0,0 +1,364 @@ +/* + * Copyright (C) 2016-2016 Lightbend Inc. + */ +package docs.http.javadsl.server.directives; + +import akka.http.javadsl.model.HttpRequest; +import akka.http.javadsl.model.StatusCodes; +import akka.http.javadsl.model.headers.BasicHttpCredentials; +import akka.http.javadsl.model.headers.HttpChallenge; +import akka.http.javadsl.model.headers.HttpCredentials; +import akka.http.javadsl.server.Route; +import akka.http.javadsl.testkit.JUnitRouteTest; +import akka.japi.JavaPartialFunction; +import org.junit.Test; +import scala.PartialFunction; +import scala.util.Either; +import scala.util.Left; +import scala.util.Right; + +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; +import java.util.function.Function; +import java.util.Optional; + +public class SecurityDirectivesExamplesTest extends JUnitRouteTest { + + @Test + public void testAuthenticateBasic() { + //#authenticateBasic + final Function, Optional> myUserPassAuthenticator = + credentials -> + credentials.filter(c -> c.verify("p4ssw0rd")).map(ProvidedCredentials::identifier); + + final Route route = path("secured", () -> + authenticateBasic("secure site", myUserPassAuthenticator, userName -> + complete("The user is '" + userName + "'") + ) + ).seal(system(), materializer()); + + // tests: + testRoute(route).run(HttpRequest.GET("/secured")) + .assertStatusCode(StatusCodes.UNAUTHORIZED) + .assertEntity("The resource requires authentication, which was not supplied with the request") + .assertHeaderExists("WWW-Authenticate", "Basic realm=\"secure site\""); + + final HttpCredentials validCredentials = + BasicHttpCredentials.createBasicHttpCredentials("John", "p4ssw0rd"); + testRoute(route).run(HttpRequest.GET("/secured").addCredentials(validCredentials)) + .assertEntity("The user is 'John'"); + + final HttpCredentials invalidCredentials = + BasicHttpCredentials.createBasicHttpCredentials("Peter", "pan"); + testRoute(route).run(HttpRequest.GET("/secured").addCredentials(invalidCredentials)) + .assertStatusCode(StatusCodes.UNAUTHORIZED) + .assertEntity("The supplied authentication is invalid") + .assertHeaderExists("WWW-Authenticate", "Basic realm=\"secure site\""); + //#authenticateBasic + } + + + @Test + public void testAuthenticateBasicPF() { + //#authenticateBasicPF + final PartialFunction, String> myUserPassAuthenticator = + new JavaPartialFunction, String>() { + @Override + public String apply(Optional opt, boolean isCheck) throws Exception { + if (opt.filter(c -> (c != null) && c.verify("p4ssw0rd")).isPresent()) { + if (isCheck) return null; + else return opt.get().identifier(); + } else if (opt.filter(c -> (c != null) && c.verify("p4ssw0rd-special")).isPresent()) { + if (isCheck) return null; + else return opt.get().identifier() + "-admin"; + } else { + throw noMatch(); + } + } + }; + + final Route route = path("secured", () -> + authenticateBasicPF("secure site", myUserPassAuthenticator, userName -> + complete("The user is '" + userName + "'") + ) + ).seal(system(), materializer()); + + // tests: + testRoute(route).run(HttpRequest.GET("/secured")) + .assertStatusCode(StatusCodes.UNAUTHORIZED) + .assertEntity("The resource requires authentication, which was not supplied with the request") + .assertHeaderExists("WWW-Authenticate", "Basic realm=\"secure site\""); + + final HttpCredentials validCredentials = + BasicHttpCredentials.createBasicHttpCredentials("John", "p4ssw0rd"); + testRoute(route).run(HttpRequest.GET("/secured").addCredentials(validCredentials)) + .assertEntity("The user is 'John'"); + + final HttpCredentials validAdminCredentials = + BasicHttpCredentials.createBasicHttpCredentials("John", "p4ssw0rd-special"); + testRoute(route).run(HttpRequest.GET("/secured").addCredentials(validAdminCredentials)) + .assertEntity("The user is 'John-admin'"); + + final HttpCredentials invalidCredentials = + BasicHttpCredentials.createBasicHttpCredentials("Peter", "pan"); + testRoute(route).run(HttpRequest.GET("/secured").addCredentials(invalidCredentials)) + .assertStatusCode(StatusCodes.UNAUTHORIZED) + .assertEntity("The supplied authentication is invalid") + .assertHeaderExists("WWW-Authenticate", "Basic realm=\"secure site\""); + //#authenticateBasicPF + } + + @Test + public void testAuthenticateBasicPFAsync() { + //#authenticateBasicPFAsync + class User { + private final String id; + public User(String id) { + this.id = id; + } + public String getId() { + return id; + } + } + + final PartialFunction, CompletionStage> myUserPassAuthenticator = + new JavaPartialFunction,CompletionStage>() { + @Override + public CompletionStage apply(Optional opt, boolean isCheck) throws Exception { + if (opt.filter(c -> (c != null) && c.verify("p4ssw0rd")).isPresent()) { + if (isCheck) return CompletableFuture.completedFuture(null); + else return CompletableFuture.completedFuture(new User(opt.get().identifier())); + } else { + throw noMatch(); + } + } + }; + + final Route route = path("secured", () -> + authenticateBasicPFAsync("secure site", myUserPassAuthenticator, user -> + complete("The user is '" + user.getId() + "'")) + ).seal(system(), materializer()); + + // tests: + testRoute(route).run(HttpRequest.GET("/secured")) + .assertStatusCode(StatusCodes.UNAUTHORIZED) + .assertEntity("The resource requires authentication, which was not supplied with the request") + .assertHeaderExists("WWW-Authenticate", "Basic realm=\"secure site\""); + + final HttpCredentials validCredentials = + BasicHttpCredentials.createBasicHttpCredentials("John", "p4ssw0rd"); + testRoute(route).run(HttpRequest.GET("/secured").addCredentials(validCredentials)) + .assertEntity("The user is 'John'"); + + final HttpCredentials invalidCredentials = + BasicHttpCredentials.createBasicHttpCredentials("Peter", "pan"); + testRoute(route).run(HttpRequest.GET("/secured").addCredentials(invalidCredentials)) + .assertStatusCode(StatusCodes.UNAUTHORIZED) + .assertEntity("The supplied authentication is invalid") + .assertHeaderExists("WWW-Authenticate", "Basic realm=\"secure site\""); + //#authenticateBasicPFAsync + } + + @Test + public void testAuthenticateBasicAsync() { + //#authenticateBasicAsync + final Function, CompletionStage>> myUserPassAuthenticator = opt -> { + if (opt.filter(c -> (c != null) && c.verify("p4ssw0rd")).isPresent()) { + return CompletableFuture.completedFuture(Optional.of(opt.get().identifier())); + } else { + return CompletableFuture.completedFuture(Optional.empty()); + } + }; + + final Route route = path("secured", () -> + authenticateBasicAsync("secure site", myUserPassAuthenticator, userName -> + complete("The user is '" + userName + "'") + ) + ).seal(system(), materializer()); + + // tests: + testRoute(route).run(HttpRequest.GET("/secured")) + .assertStatusCode(StatusCodes.UNAUTHORIZED) + .assertEntity("The resource requires authentication, which was not supplied with the request") + .assertHeaderExists("WWW-Authenticate", "Basic realm=\"secure site\""); + + final HttpCredentials validCredentials = + BasicHttpCredentials.createBasicHttpCredentials("John", "p4ssw0rd"); + testRoute(route).run(HttpRequest.GET("/secured").addCredentials(validCredentials)) + .assertEntity("The user is 'John'"); + + final HttpCredentials invalidCredentials = + BasicHttpCredentials.createBasicHttpCredentials("Peter", "pan"); + testRoute(route).run(HttpRequest.GET("/secured").addCredentials(invalidCredentials)) + .assertStatusCode(StatusCodes.UNAUTHORIZED) + .assertEntity("The supplied authentication is invalid") + .assertHeaderExists("WWW-Authenticate", "Basic realm=\"secure site\""); + //#authenticateBasicAsync + } + + @Test + public void testAuthenticateOrRejectWithChallenge() { + //#authenticateOrRejectWithChallenge + final HttpChallenge challenge = HttpChallenge.create("MyAuth", "MyRealm"); + + // your custom authentication logic: + final Function auth = credentials -> true; + + final Function, CompletionStage>> myUserPassAuthenticator = + opt -> { + if (opt.isPresent() && auth.apply(opt.get())) { + return CompletableFuture.completedFuture(Right.apply("some-user-name-from-creds")); + } else { + return CompletableFuture.completedFuture(Left.apply(challenge)); + } + }; + + final Route route = path("secured", () -> + authenticateOrRejectWithChallenge(myUserPassAuthenticator, userName -> + complete("Authenticated!") + ) + ).seal(system(), materializer()); + + // tests: + testRoute(route).run(HttpRequest.GET("/secured")) + .assertStatusCode(StatusCodes.UNAUTHORIZED) + .assertEntity("The resource requires authentication, which was not supplied with the request") + .assertHeaderExists("WWW-Authenticate", "MyAuth realm=\"MyRealm\""); + + final HttpCredentials validCredentials = + BasicHttpCredentials.createBasicHttpCredentials("John", "p4ssw0rd"); + testRoute(route).run(HttpRequest.GET("/secured").addCredentials(validCredentials)) + .assertStatusCode(StatusCodes.OK) + .assertEntity("Authenticated!"); + //#authenticateOrRejectWithChallenge + } + + @Test + public void testAuthorize() { + //#authorize + class User { + private final String name; + public User(String name) { + this.name = name; + } + public String getName() { + return name; + } + } + + // authenticate the user: + final Function, Optional> myUserPassAuthenticator = + opt -> { + if (opt.isPresent()) { + return Optional.of(new User(opt.get().identifier())); + } else { + return Optional.empty(); + } + }; + + // check if user is authorized to perform admin actions: + final Set admins = new HashSet<>(); + admins.add("Peter"); + final Function hasAdminPermissions = user -> admins.contains(user.getName()); + + final Route route = authenticateBasic("secure site", myUserPassAuthenticator, user -> + path("peters-lair", () -> + authorize(() -> hasAdminPermissions.apply(user), () -> + complete("'" + user.getName() +"' visited Peter's lair") + ) + ) + ).seal(system(), materializer()); + + // tests: + final HttpCredentials johnsCred = + BasicHttpCredentials.createBasicHttpCredentials("John", "p4ssw0rd"); + testRoute(route).run(HttpRequest.GET("/peters-lair").addCredentials(johnsCred)) + .assertStatusCode(StatusCodes.FORBIDDEN) + .assertEntity("The supplied authentication is not authorized to access this resource"); + + final HttpCredentials petersCred = + BasicHttpCredentials.createBasicHttpCredentials("Peter", "pan"); + testRoute(route).run(HttpRequest.GET("/peters-lair").addCredentials(petersCred)) + .assertEntity("'Peter' visited Peter's lair"); + //#authorize + } + + @Test + public void testAuthorizeAsync() { + //#authorizeAsync + class User { + private final String name; + public User(String name) { + this.name = name; + } + public String getName() { + return name; + } + } + + // authenticate the user: + final Function, Optional> myUserPassAuthenticator = + opt -> { + if (opt.isPresent()) { + return Optional.of(new User(opt.get().identifier())); + } else { + return Optional.empty(); + } + }; + + // check if user is authorized to perform admin actions, + // this could potentially be a long operation so it would return a Future + final Set admins = new HashSet<>(); + admins.add("Peter"); + final Set synchronizedAdmins = Collections.synchronizedSet(admins); + + final Function> hasAdminPermissions = + user -> CompletableFuture.completedFuture(synchronizedAdmins.contains(user.getName())); + + final Route route = authenticateBasic("secure site", myUserPassAuthenticator, user -> + path("peters-lair", () -> + authorizeAsync(() -> hasAdminPermissions.apply(user), () -> + complete("'" + user.getName() +"' visited Peter's lair") + ) + ) + ).seal(system(), materializer()); + + // tests: + final HttpCredentials johnsCred = + BasicHttpCredentials.createBasicHttpCredentials("John", "p4ssw0rd"); + testRoute(route).run(HttpRequest.GET("/peters-lair").addCredentials(johnsCred)) + .assertStatusCode(StatusCodes.FORBIDDEN) + .assertEntity("The supplied authentication is not authorized to access this resource"); + + final HttpCredentials petersCred = + BasicHttpCredentials.createBasicHttpCredentials("Peter", "pan"); + testRoute(route).run(HttpRequest.GET("/peters-lair").addCredentials(petersCred)) + .assertEntity("'Peter' visited Peter's lair"); + //#authorizeAsync + } + + @Test + public void testExtractCredentials() { + //#extractCredentials + final Route route = extractCredentials(optCreds -> { + if (optCreds.isPresent()) { + return complete("Credentials: " + optCreds.get()); + } else { + return complete("No credentials"); + } + }); + + // tests: + final HttpCredentials johnsCred = + BasicHttpCredentials.createBasicHttpCredentials("John", "p4ssw0rd"); + testRoute(route).run(HttpRequest.GET("/").addCredentials(johnsCred)) + .assertEntity("Credentials: Basic Sm9objpwNHNzdzByZA=="); + + testRoute(route).run(HttpRequest.GET("/")) + .assertEntity("No credentials"); + //#extractCredentials + } +} diff --git a/akka-docs/rst/java/http/routing-dsl/directives/security-directives/authenticateBasic.rst b/akka-docs/rst/java/http/routing-dsl/directives/security-directives/authenticateBasic.rst index fb3999f259..16fd9479c8 100644 --- a/akka-docs/rst/java/http/routing-dsl/directives/security-directives/authenticateBasic.rst +++ b/akka-docs/rst/java/http/routing-dsl/directives/security-directives/authenticateBasic.rst @@ -27,4 +27,5 @@ See :ref:`credentials-and-timing-attacks-java` for details about verifying the s Example ------- -TODO: Example snippets for JavaDSL are subject to community contributions! Help us complete the docs, read more about it here: `write example snippets for Akka HTTP Java DSL #20466 `_. + +.. includecode:: ../../../../code/docs/http/javadsl/server/directives/SecurityDirectivesExamplesTest.java#authenticateBasic diff --git a/akka-docs/rst/java/http/routing-dsl/directives/security-directives/authenticateBasicAsync.rst b/akka-docs/rst/java/http/routing-dsl/directives/security-directives/authenticateBasicAsync.rst index 4cd3f54777..2267737a5a 100644 --- a/akka-docs/rst/java/http/routing-dsl/directives/security-directives/authenticateBasicAsync.rst +++ b/akka-docs/rst/java/http/routing-dsl/directives/security-directives/authenticateBasicAsync.rst @@ -25,4 +25,5 @@ See :ref:`credentials-and-timing-attacks-java` for details about verifying the s Example ------- -TODO: Example snippets for JavaDSL are subject to community contributions! Help us complete the docs, read more about it here: `write example snippets for Akka HTTP Java DSL #20466 `_. + +.. includecode:: ../../../../code/docs/http/javadsl/server/directives/SecurityDirectivesExamplesTest.java#authenticateBasicAsync diff --git a/akka-docs/rst/java/http/routing-dsl/directives/security-directives/authenticateBasicPF.rst b/akka-docs/rst/java/http/routing-dsl/directives/security-directives/authenticateBasicPF.rst index f5731af93f..9617e2a3c1 100644 --- a/akka-docs/rst/java/http/routing-dsl/directives/security-directives/authenticateBasicPF.rst +++ b/akka-docs/rst/java/http/routing-dsl/directives/security-directives/authenticateBasicPF.rst @@ -25,4 +25,5 @@ See :ref:`credentials-and-timing-attacks-java` for details about verifying the s Example ------- -TODO: Example snippets for JavaDSL are subject to community contributions! Help us complete the docs, read more about it here: `write example snippets for Akka HTTP Java DSL #20466 `_. + +.. includecode:: ../../../../code/docs/http/javadsl/server/directives/SecurityDirectivesExamplesTest.java#authenticateBasicPF diff --git a/akka-docs/rst/java/http/routing-dsl/directives/security-directives/authenticateBasicPFAsync.rst b/akka-docs/rst/java/http/routing-dsl/directives/security-directives/authenticateBasicPFAsync.rst index ff0e95174e..e0c5e5118d 100644 --- a/akka-docs/rst/java/http/routing-dsl/directives/security-directives/authenticateBasicPFAsync.rst +++ b/akka-docs/rst/java/http/routing-dsl/directives/security-directives/authenticateBasicPFAsync.rst @@ -22,4 +22,5 @@ See :ref:`credentials-and-timing-attacks-java` for details about verifying the s Example ------- -TODO: Example snippets for JavaDSL are subject to community contributions! Help us complete the docs, read more about it here: `write example snippets for Akka HTTP Java DSL #20466 `_. + +.. includecode:: ../../../../code/docs/http/javadsl/server/directives/SecurityDirectivesExamplesTest.java#authenticateBasicPFAsync diff --git a/akka-docs/rst/java/http/routing-dsl/directives/security-directives/authenticateOrRejectWithChallenge.rst b/akka-docs/rst/java/http/routing-dsl/directives/security-directives/authenticateOrRejectWithChallenge.rst index 76509bdb2d..4b96af6747 100644 --- a/akka-docs/rst/java/http/routing-dsl/directives/security-directives/authenticateOrRejectWithChallenge.rst +++ b/akka-docs/rst/java/http/routing-dsl/directives/security-directives/authenticateOrRejectWithChallenge.rst @@ -16,4 +16,5 @@ More details about challenge-response authentication are available in the `RFC 2 Example ------- -TODO: Example snippets for JavaDSL are subject to community contributions! Help us complete the docs, read more about it here: `write example snippets for Akka HTTP Java DSL #20466 `_. + +.. includecode:: ../../../../code/docs/http/javadsl/server/directives/SecurityDirectivesExamplesTest.java#authenticateOrRejectWithChallenge diff --git a/akka-docs/rst/java/http/routing-dsl/directives/security-directives/authorize.rst b/akka-docs/rst/java/http/routing-dsl/directives/security-directives/authorize.rst index caa435d414..6a9306ba8a 100644 --- a/akka-docs/rst/java/http/routing-dsl/directives/security-directives/authorize.rst +++ b/akka-docs/rst/java/http/routing-dsl/directives/security-directives/authorize.rst @@ -24,4 +24,5 @@ See also :ref:`-authorize-java-` for the asynchronous version of this directive. Example ------- -TODO: Example snippets for JavaDSL are subject to community contributions! Help us complete the docs, read more about it here: `write example snippets for Akka HTTP Java DSL #20466 `_. + +.. includecode:: ../../../../code/docs/http/javadsl/server/directives/SecurityDirectivesExamplesTest.java#authorize diff --git a/akka-docs/rst/java/http/routing-dsl/directives/security-directives/authorizeAsync.rst b/akka-docs/rst/java/http/routing-dsl/directives/security-directives/authorizeAsync.rst index c1920a79d8..32fa84a65a 100644 --- a/akka-docs/rst/java/http/routing-dsl/directives/security-directives/authorizeAsync.rst +++ b/akka-docs/rst/java/http/routing-dsl/directives/security-directives/authorizeAsync.rst @@ -25,4 +25,5 @@ See also :ref:`-authorize-java-` for the synchronous version of this directive. Example ------- -TODO: Example snippets for JavaDSL are subject to community contributions! Help us complete the docs, read more about it here: `write example snippets for Akka HTTP Java DSL #20466 `_. + +.. includecode:: ../../../../code/docs/http/javadsl/server/directives/SecurityDirectivesExamplesTest.java#authorizeAsync diff --git a/akka-docs/rst/java/http/routing-dsl/directives/security-directives/extractCredentials.rst b/akka-docs/rst/java/http/routing-dsl/directives/security-directives/extractCredentials.rst index d8c61a5d64..d24acf4484 100644 --- a/akka-docs/rst/java/http/routing-dsl/directives/security-directives/extractCredentials.rst +++ b/akka-docs/rst/java/http/routing-dsl/directives/security-directives/extractCredentials.rst @@ -13,4 +13,5 @@ See :ref:`credentials-and-timing-attacks-java` for details about verifying the s Example ------- -TODO: Example snippets for JavaDSL are subject to community contributions! Help us complete the docs, read more about it here: `write example snippets for Akka HTTP Java DSL #20466 `_. + +.. includecode:: ../../../../code/docs/http/javadsl/server/directives/SecurityDirectivesExamplesTest.java#extractCredentials diff --git a/akka-http-core/src/main/java/akka/http/javadsl/model/HttpMessage.java b/akka-http-core/src/main/java/akka/http/javadsl/model/HttpMessage.java index aac8d8d3d8..5f1f1ae812 100644 --- a/akka-http-core/src/main/java/akka/http/javadsl/model/HttpMessage.java +++ b/akka-http-core/src/main/java/akka/http/javadsl/model/HttpMessage.java @@ -6,6 +6,7 @@ package akka.http.javadsl.model; import akka.Done; import akka.stream.Materializer; +import akka.http.javadsl.model.headers.HttpCredentials; import akka.util.ByteString; import scala.concurrent.Future; @@ -113,6 +114,11 @@ public interface HttpMessage { */ Self addHeaders(Iterable headers); + /** + * Returns a copy of this message with the given http credential header added to the list of headers. + */ + Self addCredentials(HttpCredentials credentials); + /** * Returns a copy of this message with all headers of the given name (case-insensitively) removed. */ diff --git a/akka-http-core/src/main/scala/akka/http/scaladsl/model/HttpMessage.scala b/akka-http-core/src/main/scala/akka/http/scaladsl/model/HttpMessage.scala index d8d87357db..9cf985f448 100644 --- a/akka-http-core/src/main/scala/akka/http/scaladsl/model/HttpMessage.scala +++ b/akka-http-core/src/main/scala/akka/http/scaladsl/model/HttpMessage.scala @@ -109,6 +109,8 @@ sealed trait HttpMessage extends jm.HttpMessage { def addHeader(header: jm.HttpHeader): Self = mapHeaders(_ :+ header.asInstanceOf[HttpHeader]) + def addCredentials(credentials: jm.headers.HttpCredentials): Self = addHeader(jm.headers.Authorization.create(credentials)) + /** Removes the header with the given name (case-insensitive) */ def removeHeader(headerName: String): Self = { val lowerHeaderName = headerName.toRootLowerCase diff --git a/akka-http/src/main/scala/akka/http/javadsl/server/directives/SecurityDirectives.scala b/akka-http/src/main/scala/akka/http/javadsl/server/directives/SecurityDirectives.scala index f25d08aa42..2670c60a55 100644 --- a/akka-http/src/main/scala/akka/http/javadsl/server/directives/SecurityDirectives.scala +++ b/akka-http/src/main/scala/akka/http/javadsl/server/directives/SecurityDirectives.scala @@ -12,9 +12,11 @@ import scala.compat.java8.FutureConverters._ import scala.compat.java8.OptionConverters._ import akka.http.javadsl.model.headers.HttpChallenge import akka.http.javadsl.model.headers.HttpCredentials -import akka.http.javadsl.server.{ RequestContext, Route } +import akka.http.javadsl.server.{ Route, RequestContext } import akka.http.scaladsl -import akka.http.scaladsl.server.{ AuthorizationFailedRejection, Directives ⇒ D } +import akka.http.scaladsl.server.{ Directives ⇒ D } + +import scala.concurrent.{ ExecutionContextExecutor, Future } object SecurityDirectives { /** @@ -68,6 +70,50 @@ abstract class SecurityDirectives extends SchemeDirectives { } } + /** + * Wraps the inner route with Http Basic authentication support. + * The given authenticator determines whether the credentials in the request are valid + * and, if so, which user object to supply to the inner route. + * + * Authentication is required in this variant, i.e. the request is rejected if [authenticator] returns Optional.empty. + */ + def authenticateBasicPF[T](realm: String, authenticator: PartialFunction[Optional[ProvidedCredentials], T], + inner: JFunction[T, Route]): Route = RouteAdapter { + def pf: PartialFunction[scaladsl.server.directives.Credentials, Option[T]] = { + case c ⇒ Option(authenticator.applyOrElse(toJava(c), (_: Any) ⇒ null.asInstanceOf[T])) + } + + D.authenticateBasic(realm, pf) { t ⇒ + inner.apply(t).delegate + } + } + + /** + * Wraps the inner route with Http Basic authentication support. + * The given authenticator determines whether the credentials in the request are valid + * and, if so, which user object to supply to the inner route. + * + * Authentication is required in this variant, i.e. the request is rejected if [authenticator] returns Optional.empty. + */ + def authenticateBasicPFAsync[T](realm: String, authenticator: PartialFunction[Optional[ProvidedCredentials], CompletionStage[T]], + inner: JFunction[T, Route]): Route = RouteAdapter { + def pf(implicit ec: ExecutionContextExecutor): PartialFunction[scaladsl.server.directives.Credentials, Future[Option[T]]] = { + case credentials ⇒ + val jCredentials = toJava(credentials) + if (authenticator isDefinedAt jCredentials) { + authenticator(jCredentials).toScala.map(Some(_)) + } else { + Future.successful(None) + } + } + + D.extractExecutionContext { implicit ec ⇒ + D.authenticateBasicAsync(realm, pf) { t ⇒ + inner.apply(t).delegate + } + } + } + /** * Wraps the inner route with Http Basic authentication support using a given `Authenticator[T]`. * The given authenticator determines whether the credentials in the request are valid @@ -261,4 +307,4 @@ abstract class SecurityDirectives extends SchemeDirectives { */ def challengeFor(realm: String): HttpChallenge = HttpChallenge.create("Basic", realm) -} \ No newline at end of file +} diff --git a/project/MiMa.scala b/project/MiMa.scala index 0366eba2d9..1b2029ccee 100644 --- a/project/MiMa.scala +++ b/project/MiMa.scala @@ -886,6 +886,11 @@ object MiMa extends AutoPlugin { // #20288 migrate BodyPartRenderer to GraphStage ProblemFilters.exclude[IncompatibleResultTypeProblem]("akka.http.impl.engine.rendering.BodyPartRenderer.streamed") + ), + "2.4.8" -> Seq( + // #20717 example snippet for akka http java dsl: SecurityDirectives + ProblemFilters.exclude[ReversedMissingMethodProblem]("akka.http.javadsl.model.HttpMessage#MessageTransformations.addCredentials"), + ProblemFilters.exclude[ReversedMissingMethodProblem]("akka.http.scaladsl.model.HttpMessage.addCredentials") ) ) }