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"
}
|