aboutsummaryrefslogtreecommitdiff
path: root/src/main/scala/com/drivergrp/core/auth.scala
blob: 6b8cdaae0b403c492e99971fc3b4217d6d4f13bc (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
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
package com.drivergrp.core

import akka.http.scaladsl.model.headers.HttpChallenges
import akka.http.scaladsl.server.AuthenticationFailedRejection.CredentialsRejected

import scala.concurrent.Future
import scala.util.{Failure, Success, Try}
import scalaz.OptionT

object auth {

  sealed trait Permission
  case object CanSeeUser                extends Permission
  case object CanSeeAssay               extends Permission
  case object CanSeeReport              extends Permission
  case object CanCreateReport           extends Permission
  case object CanEditReport             extends Permission
  case object CanEditReviewingReport    extends Permission
  case object CanSignOutReport          extends Permission
  case object CanShareReportWithPatient extends Permission
  case object CanAssignRoles            extends Permission

  trait Role {
    val id: Id[Role]
    val name: Name[Role]
    val permissions: Set[Permission]

    def hasPermission(permission: Permission): Boolean = permissions.contains(permission)
  }

  case object ObserverRole extends Role {
    val id          = Id(1L)
    val name        = Name("observer")
    val permissions = Set[Permission](CanSeeUser, CanSeeAssay, CanSeeReport)
  }

  case object PatientRole extends Role {
    val id          = Id(2L)
    val name        = Name("patient")
    val permissions = Set.empty[Permission]
  }

  case object CuratorRole extends Role {
    val id          = Id(3L)
    val name        = Name("curator")
    val permissions = Set[Permission](CanSeeUser, CanSeeAssay, CanSeeReport, CanEditReport)
  }

  case object PathologistRole extends Role {
    val id   = Id(4L)
    val name = Name("pathologist")
    val permissions =
      Set[Permission](CanSeeUser, CanSeeAssay, CanSeeReport, CanEditReport, CanSignOutReport, CanEditReviewingReport)
  }

  case object AdministratorRole extends Role {
    val id   = Id(5L)
    val name = Name("administrator")
    val permissions = Set[Permission](
        CanSeeUser,
        CanSeeAssay,
        CanSeeReport,
        CanCreateReport,
        CanEditReport,
        CanEditReviewingReport,
        CanSignOutReport,
        CanShareReportWithPatient,
        CanAssignRoles
    )
  }

  trait User {
    def id: Id[User]
    def roles: Set[Role]
    def permissions: Set[Permission] = roles.flatMap(_.permissions)
  }

  final case class Macaroon(value: String)

  final case class Base64[T](value: String)

  final case class AuthToken(value: Base64[Macaroon])

  final case class PasswordHash(value: String)

  object AuthService {
    val AuthenticationTokenHeader = "WWW-Authenticate"
  }

  trait AuthService[U <: User] {

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

    protected def authStatus(authToken: AuthToken): OptionT[Future, U]

    def authorize(permission: Permission): Directive1[(AuthToken, U)] = {
      parameters('authToken.?).flatMap { parameterTokenValue =>
        optionalHeaderValueByName(AuthService.AuthenticationTokenHeader).flatMap { headerTokenValue =>
          verifyAuthToken(headerTokenValue.orElse(parameterTokenValue), permission)
        }
      }
    }

    private def verifyAuthToken(tokenOption: Option[String], permission: Permission): Directive1[(AuthToken, U)] =
      tokenOption match {
        case Some(tokenValue) =>
          val token = AuthToken(Base64[Macaroon](tokenValue))

          onComplete(authStatus(token).run).flatMap { tokenUserResult =>
            checkPermissions(tokenUserResult, permission, token)
          }

        case None =>
          reject(MissingHeaderRejection(AuthService.AuthenticationTokenHeader))
      }

    private def checkPermissions(userResult: Try[Option[U]],
                                 permission: Permission,
                                 token: AuthToken): Directive1[(AuthToken, U)] = {
      userResult match {
        case Success(Some(user)) =>
          if (user.roles.exists(_.hasPermission(permission))) provide(token -> user)
          else {
            val challenge = HttpChallenges.basic(s"User does not have the required permission $permission")
            reject(AuthenticationFailedRejection(CredentialsRejected, challenge))
          }

        case Success(None) =>
          reject(ValidationRejection(s"Wasn't able to find authenticated user for the token provided"))

        case Failure(t) =>
          reject(ValidationRejection(s"Wasn't able to verify token for authenticated user", Some(t)))
      }
    }
  }
}