aboutsummaryrefslogtreecommitdiff
path: root/core-rest/src/main/scala/xyz/driver/core/rest/auth/AuthProvider.scala
blob: e1a94e1778634ec503a1b6b614edbde5e4f501ee (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
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"
}