aboutsummaryrefslogtreecommitdiff
path: root/src/main/scala/xyz/driver/core/rest/auth/AuthProvider.scala
blob: 9c89fc6786f0946243898a16fe9dc0c17b8790ad (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
package xyz.driver.core.rest.auth

import akka.http.scaladsl.model.headers.HttpChallenges
import akka.http.scaladsl.server.AuthenticationFailedRejection.CredentialsRejected
import com.typesafe.scalalogging.Logger
import xyz.driver.core._
import xyz.driver.core.auth.{Permission, User}
import xyz.driver.core.rest.{AuthorizedServiceRequestContext, ServiceRequestContext, serviceContext}

import scala.concurrent.{ExecutionContext, Future}
import scala.util.{Failure, Success}

import scalaz.Scalaz.futureInstance
import scalaz.OptionT

abstract class AuthProvider[U <: User](val authorization: Authorization[U], log: Logger)(
    implicit execution: ExecutionContext) {

  import akka.http.scaladsl.server._
  import Directives._

  /**
    * 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]

  /**
    * Verifies if request is authenticated and authorized to have `permissions`
    */
  def authorize(permissions: Permission*): Directive1[AuthorizedServiceRequestContext[U]] = {
    serviceContext flatMap { ctx =>
      onComplete {
        (for {
          authToken <- OptionT.optionT(Future.successful(ctx.authToken))
          user      <- authenticatedUser(ctx)
          authCtx = ctx.withAuthenticatedUser(authToken, user)
          authorizationResult <- authorization.userHasPermissions(user, permissions)(authCtx).toOptionT

          cachedPermissionsAuthCtx = authorizationResult.token.fold(authCtx)(authCtx.withPermissionsToken)
          allAuthorized            = permissions.forall(authorizationResult.authorized.getOrElse(_, false))
        } yield (cachedPermissionsAuthCtx, allAuthorized)).run
      } flatMap {
        case Success(Some((authCtx, true))) => provide(authCtx)
        case Success(Some((authCtx, false))) =>
          val challenge =
            HttpChallenges.basic(s"User does not have the required permissions: ${permissions.mkString(", ")}")
          log.warn(
            s"User ${authCtx.authenticatedUser} does not have the required permissions: ${permissions.mkString(", ")}")
          reject(AuthenticationFailedRejection(CredentialsRejected, challenge))
        case Success(None) =>
          log.warn(
            s"Wasn't able to find authenticated user for the token provided to verify ${permissions.mkString(", ")}")
          reject(ValidationRejection(s"Wasn't able to find authenticated user for the token provided"))
        case Failure(t) =>
          log.warn(s"Wasn't able to verify token for authenticated user to verify ${permissions.mkString(", ")}", t)
          reject(ValidationRejection(s"Wasn't able to verify token for authenticated user", Some(t)))
      }
    }
  }
}