aboutsummaryrefslogtreecommitdiff
path: root/core-rest/src/main/scala/xyz/driver/core/rest/auth/AuthProvider.scala
diff options
context:
space:
mode:
Diffstat (limited to 'core-rest/src/main/scala/xyz/driver/core/rest/auth/AuthProvider.scala')
-rw-r--r--core-rest/src/main/scala/xyz/driver/core/rest/auth/AuthProvider.scala75
1 files changed, 75 insertions, 0 deletions
diff --git a/core-rest/src/main/scala/xyz/driver/core/rest/auth/AuthProvider.scala b/core-rest/src/main/scala/xyz/driver/core/rest/auth/AuthProvider.scala
new file mode 100644
index 0000000..e1a94e1
--- /dev/null
+++ b/core-rest/src/main/scala/xyz/driver/core/rest/auth/AuthProvider.scala
@@ -0,0 +1,75 @@
+package xyz.driver.core.rest.auth
+
+import akka.http.scaladsl.server.directives.Credentials
+import com.typesafe.scalalogging.Logger
+import scalaz.OptionT
+import xyz.driver.core.auth.{AuthToken, Permission, User}
+import xyz.driver.core.rest.errors.{ExternalServiceException, UnauthorizedException}
+import xyz.driver.core.rest.{AuthorizedServiceRequestContext, ContextHeaders, ServiceRequestContext, serviceContext}
+
+import scala.concurrent.{ExecutionContext, Future}
+
+abstract class AuthProvider[U <: User](
+ val authorization: Authorization[U],
+ log: Logger,
+ val realm: String
+)(implicit execution: ExecutionContext) {
+
+ import akka.http.scaladsl.server._
+ import Directives.{authorize => akkaAuthorize, _}
+
+ def this(authorization: Authorization[U], log: Logger)(implicit executionContext: ExecutionContext) =
+ this(authorization, log, "driver.xyz")
+
+ /**
+ * Specific implementation on how to extract user from request context,
+ * can either need to do a network call to auth server or extract everything from self-contained token
+ *
+ * @param ctx set of request values which can be relevant to authenticate user
+ * @return authenticated user
+ */
+ def authenticatedUser(implicit ctx: ServiceRequestContext): OptionT[Future, U]
+
+ protected def authenticator(context: ServiceRequestContext): AsyncAuthenticator[U] = {
+ case Credentials.Missing =>
+ log.info(s"Request (${context.trackingId}) missing authentication credentials")
+ Future.successful(None)
+ case Credentials.Provided(authToken) =>
+ authenticatedUser(context.withAuthToken(AuthToken(authToken))).run.recover({
+ case ExternalServiceException(_, _, Some(UnauthorizedException(_))) => None
+ })
+ }
+
+ /**
+ * Verifies that a user agent is properly authenticated, and (optionally) authorized with the specified permissions
+ */
+ def authorize(
+ context: ServiceRequestContext,
+ permissions: Permission*): Directive1[AuthorizedServiceRequestContext[U]] = {
+ authenticateOAuth2Async[U](realm, authenticator(context)) flatMap { authenticatedUser =>
+ val authCtx = context.withAuthenticatedUser(context.authToken.get, authenticatedUser)
+ onSuccess(authorization.userHasPermissions(authenticatedUser, permissions)(authCtx)) flatMap {
+ case AuthorizationResult(authorized, token) =>
+ val allAuthorized = permissions.forall(authorized.getOrElse(_, false))
+ akkaAuthorize(allAuthorized) tflatMap { _ =>
+ val cachedPermissionsCtx = token.fold(authCtx)(authCtx.withPermissionsToken)
+ provide(cachedPermissionsCtx)
+ }
+ }
+ }
+ }
+
+ /**
+ * Verifies if request is authenticated and authorized to have `permissions`
+ */
+ def authorize(permissions: Permission*): Directive1[AuthorizedServiceRequestContext[U]] = {
+ serviceContext flatMap (authorize(_, permissions: _*))
+ }
+}
+
+object AuthProvider {
+ val AuthenticationTokenHeader: String = ContextHeaders.AuthenticationTokenHeader
+ val PermissionsTokenHeader: String = ContextHeaders.PermissionsTokenHeader
+ val SetAuthenticationTokenHeader: String = "set-authorization"
+ val SetPermissionsTokenHeader: String = "set-permissions"
+}