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

import java.nio.file.{Files, Path}
import java.security.{KeyFactory, PublicKey}
import java.security.spec.X509EncodedKeySpec

import pdi.jwt.{Jwt, JwtAlgorithm}
import xyz.driver.core.auth.{Permission, User}
import xyz.driver.core.rest.ServiceRequestContext
import xyz.driver.core.json.idFormat

import scala.concurrent.Future
import scalaz.syntax.std.boolean._

class CachedTokenAuthorization[U <: User](publicKey: => PublicKey, issuer: String) extends Authorization[U] {
  override def userHasPermissions(user: U, permissions: Seq[Permission])(
      implicit ctx: ServiceRequestContext): Future[AuthorizationResult] = {
    import spray.json._

    def extractPermissionsFromTokenJSON(tokenObject: JsObject): Option[Map[String, Boolean]] =
      tokenObject.fields.get("permissions").collect {
        case JsObject(fields) =>
          fields.collect {
            case (key, JsBoolean(value)) => key -> value
          }
      }

    val result = for {
      token <- ctx.permissionsToken
      jwt   <- Jwt.decode(token.value, publicKey, Seq(JwtAlgorithm.RS256)).toOption
      jwtJson = jwt.parseJson.asJsObject

      // Ensure jwt is for the currently authenticated user and the correct issuer, otherwise return None
      _ <- jwtJson.fields.get("sub").contains(user.id.toJson).option(())
      _ <- jwtJson.fields.get("iss").contains(JsString(issuer)).option(())

      permissionsMap <- extractPermissionsFromTokenJSON(jwtJson)

      authorized = permissions.map(p => p -> permissionsMap.getOrElse(p.toString, false)).toMap
    } yield AuthorizationResult(authorized, Some(token))

    Future.successful(result.getOrElse(AuthorizationResult.unauthorized))
  }
}

object CachedTokenAuthorization {
  def apply[U <: User](publicKeyFile: Path, issuer: String): CachedTokenAuthorization[U] = {
    lazy val publicKey: PublicKey = {
      val publicKeyBase64Encoded = new String(Files.readAllBytes(publicKeyFile)).trim
      val publicKeyBase64Decoded = java.util.Base64.getDecoder.decode(publicKeyBase64Encoded)
      val spec                   = new X509EncodedKeySpec(publicKeyBase64Decoded)
      KeyFactory.getInstance("RSA").generatePublic(spec)
    }
    new CachedTokenAuthorization[U](publicKey, issuer)
  }
}