diff options
Diffstat (limited to 'src/main/scala/xyz/driver/pdsuicommon')
9 files changed, 42 insertions, 272 deletions
diff --git a/src/main/scala/xyz/driver/pdsuicommon/acl/ACL.scala b/src/main/scala/xyz/driver/pdsuicommon/acl/ACL.scala index 3eb1a65..c1907cd 100644 --- a/src/main/scala/xyz/driver/pdsuicommon/acl/ACL.scala +++ b/src/main/scala/xyz/driver/pdsuicommon/acl/ACL.scala @@ -1,22 +1,32 @@ package xyz.driver.pdsuicommon.acl +import xyz.driver.core.auth.Role +import xyz.driver.core.rest.AuthorizedServiceRequestContext +import xyz.driver.entities.auth._ +import xyz.driver.entities.users.AuthUserInfo import xyz.driver.pdsuicommon.logging._ -import xyz.driver.pdsuicommon.auth.AuthenticatedRequestContext /** * @see https://driverinc.atlassian.net/wiki/display/RA/User+permissions#UserPermissions-AccessControlList */ object ACL extends PhiLogging { - import xyz.driver.pdsuicommon.domain.User.Role - import Role._ - type AclCheck = Role => Boolean val Forbid: AclCheck = _ => false val Allow: AclCheck = _ => true + val RepRoles: Set[Role] = Set[Role](RecordAdmin, RecordCleaner, RecordOrganizer, DocumentExtractor) + + val TcRoles: Set[Role] = Set[Role](TrialSummarizer, CriteriaCurator, TrialAdmin) + + val TreatmentMatchingRoles: Set[Role] = Set[Role](RoutesCurator, EligibilityVerifier, TreatmentMatchingAdmin) + + val PepRoles: Set[Role] = Set[Role](ResearchOncologist) + + val All: Set[Role] = RepRoles ++ TcRoles ++ TreatmentMatchingRoles ++ PepRoles + AdministratorRole + // Common object UserHistory @@ -28,9 +38,9 @@ object ACL extends PhiLogging { object Queue extends BaseACL( label = "queue", - create = Set(SystemUser), - read = Set(SystemUser), - update = Set(SystemUser) + create = Set(AdministratorRole), + read = Set(AdministratorRole), + update = Set(AdministratorRole) ) // REP @@ -38,7 +48,7 @@ object ACL extends PhiLogging { object MedicalRecord extends BaseACL( label = "medical record", - read = RepRoles + RoutesCurator + TreatmentMatchingAdmin + ResearchOncologist + SystemUser, + read = RepRoles + RoutesCurator + TreatmentMatchingAdmin + ResearchOncologist + AdministratorRole, update = RepRoles - DocumentExtractor ) @@ -116,7 +126,7 @@ object ACL extends PhiLogging { object Trial extends BaseACL( label = "trial", - read = TcRoles + RoutesCurator + TreatmentMatchingAdmin + ResearchOncologist + SystemUser, + read = TcRoles + RoutesCurator + TreatmentMatchingAdmin + ResearchOncologist + AdministratorRole, update = TcRoles ) @@ -187,7 +197,7 @@ object ACL extends PhiLogging { object Patient extends BaseACL( label = "patient", - read = TreatmentMatchingRoles + ResearchOncologist + SystemUser, + read = TreatmentMatchingRoles + ResearchOncologist + AdministratorRole, update = TreatmentMatchingRoles ) @@ -248,20 +258,20 @@ object ACL extends PhiLogging { update: AclCheck = Forbid, delete: AclCheck = Forbid) { - def isCreateAllow()(implicit requestContext: AuthenticatedRequestContext): Boolean = { - check("create", create)(requestContext.executor.roles) + def isCreateAllow()(implicit requestContext: AuthorizedServiceRequestContext[AuthUserInfo]): Boolean = { + check("create", create)(requestContext.authenticatedUser.roles) } - def isReadAllow()(implicit requestContext: AuthenticatedRequestContext): Boolean = { - check("read", read)(requestContext.executor.roles) + def isReadAllow()(implicit requestContext: AuthorizedServiceRequestContext[AuthUserInfo]): Boolean = { + check("read", read)(requestContext.authenticatedUser.roles) } - def isUpdateAllow()(implicit requestContext: AuthenticatedRequestContext): Boolean = { - check("update", update)(requestContext.executor.roles) + def isUpdateAllow()(implicit requestContext: AuthorizedServiceRequestContext[AuthUserInfo]): Boolean = { + check("update", update)(requestContext.authenticatedUser.roles) } - def isDeleteAllow()(implicit requestContext: AuthenticatedRequestContext): Boolean = { - check("delete", delete)(requestContext.executor.roles) + def isDeleteAllow()(implicit requestContext: AuthorizedServiceRequestContext[AuthUserInfo]): Boolean = { + check("delete", delete)(requestContext.authenticatedUser.roles) } private def check(action: String, isAllowed: AclCheck)(executorRoles: Set[Role]): Boolean = { diff --git a/src/main/scala/xyz/driver/pdsuicommon/auth/AnonymousRequestContext.scala b/src/main/scala/xyz/driver/pdsuicommon/auth/AnonymousRequestContext.scala deleted file mode 100644 index c677ef8..0000000 --- a/src/main/scala/xyz/driver/pdsuicommon/auth/AnonymousRequestContext.scala +++ /dev/null @@ -1,11 +0,0 @@ -package xyz.driver.pdsuicommon.auth - -class AnonymousRequestContext(val requestId: RequestId) { - - override def equals(that: Any): Boolean = { - that.getClass == classOf[AnonymousRequestContext] && - that.asInstanceOf[AnonymousRequestContext].requestId == requestId - } - - override def hashCode(): Int = requestId.hashCode() -} diff --git a/src/main/scala/xyz/driver/pdsuicommon/auth/AuthenticatedRequestContext.scala b/src/main/scala/xyz/driver/pdsuicommon/auth/AuthenticatedRequestContext.scala deleted file mode 100644 index 5c07a9a..0000000 --- a/src/main/scala/xyz/driver/pdsuicommon/auth/AuthenticatedRequestContext.scala +++ /dev/null @@ -1,36 +0,0 @@ -package xyz.driver.pdsuicommon.auth - -import xyz.driver.entities.users.AuthUserInfo -import xyz.driver.pdsuicommon.logging._ -import xyz.driver.pdsuicommon.domain.User - -class AuthenticatedRequestContext(val driverUser: AuthUserInfo, - override val requestId: RequestId, - val authToken: String) - extends AnonymousRequestContext(requestId) { - - val executor: User = new User(driverUser) - - override def equals(that: Any): Boolean = { - that.getClass == this.getClass && { - val another = that.asInstanceOf[AuthenticatedRequestContext] - another.executor == executor && another.requestId == requestId - } - } - - override def hashCode(): Int = { - val initial = 37 - val first = initial * 17 + executor.hashCode() - first * 17 + requestId.hashCode() - } -} - -object AuthenticatedRequestContext { - - def apply(driverUser: AuthUserInfo, authToken: String = "") = - new AuthenticatedRequestContext(driverUser, RequestId(), authToken) - - implicit def toPhiString(x: AuthenticatedRequestContext): PhiString = { - phi"AuthenticatedRequestContext(executor=${x.executor}, requestId=${x.requestId})" - } -} diff --git a/src/main/scala/xyz/driver/pdsuicommon/auth/RequestId.scala b/src/main/scala/xyz/driver/pdsuicommon/auth/RequestId.scala deleted file mode 100644 index 9982bb0..0000000 --- a/src/main/scala/xyz/driver/pdsuicommon/auth/RequestId.scala +++ /dev/null @@ -1,14 +0,0 @@ -package xyz.driver.pdsuicommon.auth - -import xyz.driver.pdsuicommon.logging._ -import xyz.driver.pdsuicommon.auth.RequestId._ -import xyz.driver.pdsuicommon.utils.RandomUtils - -final case class RequestId(value: String = RandomUtils.randomString(IdLength)) - -object RequestId { - - private val IdLength = 20 - - implicit def toPhiString(x: RequestId): PhiString = phi"RequestId(${Unsafe(x.value)})" -} diff --git a/src/main/scala/xyz/driver/pdsuicommon/domain/Email.scala b/src/main/scala/xyz/driver/pdsuicommon/domain/Email.scala deleted file mode 100644 index 092ad40..0000000 --- a/src/main/scala/xyz/driver/pdsuicommon/domain/Email.scala +++ /dev/null @@ -1,3 +0,0 @@ -package xyz.driver.pdsuicommon.domain - -final case class Email(value: String) diff --git a/src/main/scala/xyz/driver/pdsuicommon/domain/User.scala b/src/main/scala/xyz/driver/pdsuicommon/domain/User.scala deleted file mode 100644 index f75f391..0000000 --- a/src/main/scala/xyz/driver/pdsuicommon/domain/User.scala +++ /dev/null @@ -1,163 +0,0 @@ -package xyz.driver.pdsuicommon.domain - -import java.math.BigInteger -import java.security.SecureRandom -import java.time.{Instant, LocalDateTime, ZoneId} - -import xyz.driver.pdsuicommon.logging._ -import xyz.driver.pdsuicommon.domain.User.Role -import xyz.driver.pdsuicommon.utils.Utils - -final case class User(id: StringId[User], - email: Email, - name: String, - roles: Set[Role], - latestActivity: Option[LocalDateTime], - deleted: Option[LocalDateTime]) { - - def this(driverUser: xyz.driver.entities.users.AuthUserInfo) { - this( - id = StringId[xyz.driver.pdsuicommon.domain.User](driverUser.id.value), - email = Email(driverUser.email.toString), - name = driverUser.name.toString, - roles = driverUser.roles.flatMap(User.mapRoles), - latestActivity = - driverUser.lastLoginTime.map(t => Instant.ofEpochMilli(t.millis).atZone(ZoneId.of("Z")).toLocalDateTime), - deleted = Option.empty[LocalDateTime] - ) - } -} - -object User { - - sealed trait Role extends Product with Serializable { - - /** - * Bit representation of this role - */ - def bit: Int - - def is(that: Role): Boolean = this == that - - def oneOf(roles: Role*): Boolean = roles.contains(this) - - def oneOf(roles: Set[Role]): Boolean = roles.contains(this) - } - - object Role extends PhiLogging { - case object RecordAdmin extends Role { val bit = 1 << 0 } - case object RecordCleaner extends Role { val bit = 1 << 1 } - case object RecordOrganizer extends Role { val bit = 1 << 2 } - case object DocumentExtractor extends Role { val bit = 1 << 3 } - case object TrialSummarizer extends Role { val bit = 1 << 4 } - case object CriteriaCurator extends Role { val bit = 1 << 5 } - case object TrialAdmin extends Role { val bit = 1 << 6 } - case object EligibilityVerifier extends Role { val bit = 1 << 7 } - case object TreatmentMatchingAdmin extends Role { val bit = 1 << 8 } - case object RoutesCurator extends Role { val bit = 1 << 9 } - case object SystemUser extends Role { val bit = 1 << 10 } - case object ResearchOncologist extends Role { val bit = 1 << 11 } - - val RepRoles = Set[Role](RecordAdmin, RecordCleaner, RecordOrganizer, DocumentExtractor) - - val TcRoles = Set[Role](TrialSummarizer, CriteriaCurator, TrialAdmin) - - val TreatmentMatchingRoles = Set[Role](RoutesCurator, EligibilityVerifier, TreatmentMatchingAdmin) - - val PepRoles = Set[Role](ResearchOncologist) - - val All = RepRoles ++ TcRoles ++ TreatmentMatchingRoles ++ PepRoles + SystemUser - - def apply(bitMask: Int): Role = { - def extractRole(role: Role): Set[Role] = - if ((bitMask & role.bit) != 0) Set(role) else Set.empty[Role] - - val roles = All.flatMap(extractRole) - roles.size match { - case 1 => roles.head - case _ => - logger.error(phi"Can't convert a bit mask ${Unsafe(bitMask)} to any role") - throw new IllegalArgumentException() - } - } - - implicit def toPhiString(x: Role): PhiString = Unsafe(Utils.getClassSimpleName(x.getClass)) - } - - implicit def toPhiString(x: User): PhiString = { - import x._ - phi"User(id=$id, roles=${Unsafe(roles.map(_.toString).mkString(", "))})" - } - - // SecureRandom is thread-safe, see the implementation - private val random = new SecureRandom() - - def createPassword: String = new BigInteger(240, random).toString(32) - - def mapRoles(coreRole: xyz.driver.core.auth.Role): Set[xyz.driver.pdsuicommon.domain.User.Role] = { - coreRole match { - case xyz.driver.entities.auth.AdministratorRole => - Set( - xyz.driver.pdsuicommon.domain.User.Role.SystemUser, - xyz.driver.pdsuicommon.domain.User.Role.RecordAdmin, - xyz.driver.pdsuicommon.domain.User.Role.TrialAdmin, - xyz.driver.pdsuicommon.domain.User.Role.TreatmentMatchingAdmin - ) - case xyz.driver.entities.auth.RecordAdmin => - Set(xyz.driver.pdsuicommon.domain.User.Role.RecordAdmin) - case xyz.driver.entities.auth.RecordCleaner => - Set(xyz.driver.pdsuicommon.domain.User.Role.RecordCleaner) - case xyz.driver.entities.auth.RecordOrganizer => - Set(xyz.driver.pdsuicommon.domain.User.Role.RecordOrganizer) - case xyz.driver.entities.auth.DocumentExtractor => - Set(xyz.driver.pdsuicommon.domain.User.Role.DocumentExtractor) - case xyz.driver.entities.auth.TrialSummarizer => - Set(xyz.driver.pdsuicommon.domain.User.Role.TrialSummarizer) - case xyz.driver.entities.auth.CriteriaCurator => - Set(xyz.driver.pdsuicommon.domain.User.Role.CriteriaCurator) - case xyz.driver.entities.auth.TrialAdmin => - Set(xyz.driver.pdsuicommon.domain.User.Role.TrialAdmin) - case xyz.driver.entities.auth.EligibilityVerifier => - Set(xyz.driver.pdsuicommon.domain.User.Role.EligibilityVerifier) - case xyz.driver.entities.auth.TreatmentMatchingAdmin => - Set(xyz.driver.pdsuicommon.domain.User.Role.TreatmentMatchingAdmin) - case xyz.driver.entities.auth.RoutesCurator => - Set(xyz.driver.pdsuicommon.domain.User.Role.RoutesCurator) - case xyz.driver.entities.auth.ResearchOncologist => - Set(xyz.driver.pdsuicommon.domain.User.Role.ResearchOncologist) - case _ => - Set.empty[xyz.driver.pdsuicommon.domain.User.Role] - } - } - - def mapRolesToDriver(pdsuiRole: xyz.driver.pdsuicommon.domain.User.Role): Set[xyz.driver.core.auth.Role] = { - pdsuiRole match { - case xyz.driver.pdsuicommon.domain.User.Role.SystemUser => - Set(xyz.driver.entities.auth.AdministratorRole) - case xyz.driver.pdsuicommon.domain.User.Role.RecordAdmin => - Set(xyz.driver.entities.auth.RecordAdmin) - case xyz.driver.pdsuicommon.domain.User.Role.RecordCleaner => - Set(xyz.driver.entities.auth.RecordCleaner) - case xyz.driver.pdsuicommon.domain.User.Role.RecordOrganizer => - Set(xyz.driver.entities.auth.RecordOrganizer) - case xyz.driver.pdsuicommon.domain.User.Role.DocumentExtractor => - Set(xyz.driver.entities.auth.DocumentExtractor) - case xyz.driver.pdsuicommon.domain.User.Role.TrialSummarizer => - Set(xyz.driver.entities.auth.TrialSummarizer) - case xyz.driver.pdsuicommon.domain.User.Role.CriteriaCurator => - Set(xyz.driver.entities.auth.CriteriaCurator) - case xyz.driver.pdsuicommon.domain.User.Role.TrialAdmin => - Set(xyz.driver.entities.auth.TrialAdmin) - case xyz.driver.pdsuicommon.domain.User.Role.EligibilityVerifier => - Set(xyz.driver.entities.auth.EligibilityVerifier) - case xyz.driver.pdsuicommon.domain.User.Role.TreatmentMatchingAdmin => - Set(xyz.driver.entities.auth.TreatmentMatchingAdmin) - case xyz.driver.pdsuicommon.domain.User.Role.RoutesCurator => - Set(xyz.driver.entities.auth.RoutesCurator) - case xyz.driver.pdsuicommon.domain.User.Role.ResearchOncologist => - Set(xyz.driver.entities.auth.ResearchOncologist) - case _ => - Set.empty[xyz.driver.core.auth.Role] - } - } -} diff --git a/src/main/scala/xyz/driver/pdsuicommon/error/ErrorsResponse.scala b/src/main/scala/xyz/driver/pdsuicommon/error/ErrorsResponse.scala index ccb84c2..4d6aa0b 100644 --- a/src/main/scala/xyz/driver/pdsuicommon/error/ErrorsResponse.scala +++ b/src/main/scala/xyz/driver/pdsuicommon/error/ErrorsResponse.scala @@ -2,9 +2,8 @@ package xyz.driver.pdsuicommon.error import spray.json._ import ErrorsResponse.ResponseError -import xyz.driver.pdsuicommon.auth.RequestId -final case class ErrorsResponse(errors: Seq[ResponseError], requestId: RequestId) +final case class ErrorsResponse(errors: Seq[ResponseError], requestId: String) object ErrorsResponse { import DefaultJsonProtocol._ @@ -28,11 +27,11 @@ object ErrorsResponse { override def write(obj: ErrorsResponse): JsValue = { JsObject( "errors" -> obj.errors.map(_.toJson).toJson, - "requestId" -> obj.requestId.value.toJson + "requestId" -> obj.requestId.toJson ) } - override def read(json: JsValue) = json match { + override def read(json: JsValue): ErrorsResponse = json match { case JsObject(fields) => val errors = fields .get("errors") @@ -41,7 +40,7 @@ object ErrorsResponse { val requestId = fields .get("requestId") - .map(id => RequestId(id.convertTo[String])) + .map(id => id.convertTo[String]) .getOrElse(deserializationError(s"ErrorsResponse json object does not contain `requestId` field: $json")) ErrorsResponse(errors, requestId) diff --git a/src/main/scala/xyz/driver/pdsuicommon/http/Directives.scala b/src/main/scala/xyz/driver/pdsuicommon/http/Directives.scala index 4f5cd8a..93eb62f 100644 --- a/src/main/scala/xyz/driver/pdsuicommon/http/Directives.scala +++ b/src/main/scala/xyz/driver/pdsuicommon/http/Directives.scala @@ -4,9 +4,6 @@ import akka.http.scaladsl.server._ import akka.http.scaladsl.server.Directives._ import akka.http.scaladsl.model._ import xyz.driver.core.app.DriverApp -import xyz.driver.core.rest.ContextHeaders -import xyz.driver.entities.users.AuthUserInfo -import xyz.driver.pdsuicommon.auth._ import xyz.driver.pdsuicommon.error._ import xyz.driver.pdsuicommon.error.DomainError._ import xyz.driver.pdsuicommon.error.ErrorsResponse.ResponseError @@ -14,7 +11,9 @@ import xyz.driver.pdsuicommon.parsers._ import xyz.driver.pdsuicommon.db.{Pagination, SearchFilterExpr, Sorting} import xyz.driver.pdsuicommon.domain._ import akka.http.scaladsl.marshallers.sprayjson.SprayJsonSupport._ -import xyz.driver.core.rest.auth.AuthProvider +import xyz.driver.core.generators +import xyz.driver.core.rest.ContextHeaders + import scala.util.control._ import scala.util._ @@ -69,7 +68,7 @@ trait Directives { case other => other } - def domainExceptionHandler(req: RequestId): ExceptionHandler = { + def domainExceptionHandler(req: String): ExceptionHandler = { def errorResponse(ex: Throwable) = ErrorsResponse(Seq(ResponseError(None, ex.getMessage, 1)), req) ExceptionHandler { @@ -81,7 +80,7 @@ trait Directives { } } - def domainRejectionHandler(req: RequestId): RejectionHandler = { + def domainRejectionHandler(req: String): RejectionHandler = { def wrapContent(message: String) = { import ErrorsResponse._ val err: ErrorsResponse = ErrorsResponse(Seq(ResponseError(None, message, 1)), req) @@ -95,27 +94,15 @@ trait Directives { } } - val tracked: Directive1[RequestId] = optionalHeaderValueByName(ContextHeaders.TrackingIdHeader) flatMap { - case Some(id) => provide(RequestId(id)) - case None => provide(RequestId()) + val tracked: Directive1[String] = optionalHeaderValueByName(ContextHeaders.TrackingIdHeader) flatMap { + case Some(id) => provide(id) + case None => provide(generators.nextUuid().toString) } val domainResponse: Directive0 = tracked.flatMap { id => handleExceptions(domainExceptionHandler(id)) & handleRejections(domainRejectionHandler(id)) } - implicit class AuthProviderWrapper(provider: AuthProvider[AuthUserInfo]) { - val authenticated: Directive1[AuthenticatedRequestContext] = (provider.authorize() & tracked) tflatMap { - case (core, requestId) => - provide( - new AuthenticatedRequestContext( - core.authenticatedUser, - requestId, - core.contextHeaders(ContextHeaders.AuthenticationTokenHeader) - )) - } - } - } object Directives extends Directives diff --git a/src/main/scala/xyz/driver/pdsuicommon/logging/TimeLogger.scala b/src/main/scala/xyz/driver/pdsuicommon/logging/TimeLogger.scala index 297b0e1..349d925 100644 --- a/src/main/scala/xyz/driver/pdsuicommon/logging/TimeLogger.scala +++ b/src/main/scala/xyz/driver/pdsuicommon/logging/TimeLogger.scala @@ -2,7 +2,8 @@ package xyz.driver.pdsuicommon.logging import java.time.{LocalDateTime, ZoneId} -import xyz.driver.pdsuicommon.domain.{StringId, User} +import xyz.driver.core.auth.User +import xyz.driver.pdsuicommon.domain.StringId object TimeLogger extends PhiLogging { |