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
|
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 a service context is authenticated and authorized to have `permissions`
*/
def authorize(
context: ServiceRequestContext,
permissions: Permission*): Directive1[AuthorizedServiceRequestContext[U]] = {
onComplete {
(for {
authToken <- OptionT.optionT(Future.successful(context.authToken))
user <- authenticatedUser(context)
authCtx = context.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) =>
val challenge = HttpChallenges.basic("Failed to authenticate user")
log.warn(s"Failed to authenticate user to verify ${permissions.mkString(", ")}")
reject(AuthenticationFailedRejection(CredentialsRejected, challenge))
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)))
}
}
/**
* Verifies if request is authenticated and authorized to have `permissions`
*/
def authorize(permissions: Permission*): Directive1[AuthorizedServiceRequestContext[U]] = {
serviceContext flatMap { ctx =>
authorize(ctx, permissions: _*)
}
}
}
|