diff options
Diffstat (limited to 'src/main/scala/xyz/driver/pdsuicommon')
6 files changed, 105 insertions, 60 deletions
diff --git a/src/main/scala/xyz/driver/pdsuicommon/acl/ACL.scala b/src/main/scala/xyz/driver/pdsuicommon/acl/ACL.scala index 6d78ba9..276ef9f 100644 --- a/src/main/scala/xyz/driver/pdsuicommon/acl/ACL.scala +++ b/src/main/scala/xyz/driver/pdsuicommon/acl/ACL.scala @@ -142,7 +142,7 @@ object ACL extends PhiLogging { extends BaseACL( label = "criterion", create = Set(CriteriaCurator, TrialAdmin), - read = Set(CriteriaCurator, TrialAdmin), + read = Set(CriteriaCurator, TrialAdmin, RoutesCurator, TreatmentMatchingAdmin, ResearchOncologist), update = Set(CriteriaCurator, TrialAdmin), delete = Set(CriteriaCurator, TrialAdmin) ) @@ -227,28 +227,26 @@ object ACL extends PhiLogging { delete: AclCheck = Forbid) { def isCreateAllow()(implicit requestContext: AuthenticatedRequestContext): Boolean = { - check("create", create)(requestContext.executor.role) + check("create", create)(requestContext.executor.roles) } def isReadAllow()(implicit requestContext: AuthenticatedRequestContext): Boolean = { - check("read", read)(requestContext.executor.role) + check("read", read)(requestContext.executor.roles) } def isUpdateAllow()(implicit requestContext: AuthenticatedRequestContext): Boolean = { - check("update", update)(requestContext.executor.role) + check("update", update)(requestContext.executor.roles) } def isDeleteAllow()(implicit requestContext: AuthenticatedRequestContext): Boolean = { - check("delete", delete)(requestContext.executor.role) + check("delete", delete)(requestContext.executor.roles) } - private def check(action: String, isAllowed: AclCheck)(executorRole: Role): Boolean = { + private def check(action: String, isAllowed: AclCheck)(executorRoles: Set[Role]): Boolean = { loggedError( - isAllowed(executorRole), - phi"$executorRole has no access to ${Unsafe(action)} a ${Unsafe(label)}" + executorRoles.exists(isAllowed), + phi"${Unsafe(executorRoles.mkString(", "))} has no access to ${Unsafe(action)} a ${Unsafe(label)}" ) } - } - } diff --git a/src/main/scala/xyz/driver/pdsuicommon/auth/AuthenticatedRequestContext.scala b/src/main/scala/xyz/driver/pdsuicommon/auth/AuthenticatedRequestContext.scala index e9da132..16265c6 100644 --- a/src/main/scala/xyz/driver/pdsuicommon/auth/AuthenticatedRequestContext.scala +++ b/src/main/scala/xyz/driver/pdsuicommon/auth/AuthenticatedRequestContext.scala @@ -1,11 +1,14 @@ package xyz.driver.pdsuicommon.auth +import xyz.driver.entities.users.UserInfo import xyz.driver.pdsuicommon.logging._ import xyz.driver.pdsuicommon.domain.User -class AuthenticatedRequestContext(val executor: User, override val requestId: RequestId, val authToken: String = "") +class AuthenticatedRequestContext(val driverUser: UserInfo, 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] @@ -22,7 +25,8 @@ class AuthenticatedRequestContext(val executor: User, override val requestId: Re object AuthenticatedRequestContext { - def apply(executor: User, authToken: String) = new AuthenticatedRequestContext(executor, RequestId(), authToken) + def apply(driverUser: UserInfo, 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/computation/Computation.scala b/src/main/scala/xyz/driver/pdsuicommon/computation/Computation.scala index 159c144..a9430e3 100644 --- a/src/main/scala/xyz/driver/pdsuicommon/computation/Computation.scala +++ b/src/main/scala/xyz/driver/pdsuicommon/computation/Computation.scala @@ -72,6 +72,14 @@ final case class Computation[+R, +T](future: Future[Either[R, T]]) { }) } + def mapAll[R2, T2](onLeft: R => Computation[R2, T2])(onRight: T => Computation[R2, T2])( + onFailure: () => Computation[R2, T2])(implicit ec: ExecutionContext): Computation[R2, T2] = { + + Computation(future.flatMap(_.fold(onLeft, onRight).future).recoverWith { + case _ => onFailure().future + }) + } + def andThen(f: T => Any)(implicit ec: ExecutionContext): Computation[R, T] = map { a => f(a) a @@ -98,7 +106,6 @@ final case class Computation[+R, +T](future: Future[Either[R, T]]) { case Left(x) => x case Right(x) => x } - } object Computation { diff --git a/src/main/scala/xyz/driver/pdsuicommon/domain/PasswordHash.scala b/src/main/scala/xyz/driver/pdsuicommon/domain/PasswordHash.scala deleted file mode 100644 index 337d925..0000000 --- a/src/main/scala/xyz/driver/pdsuicommon/domain/PasswordHash.scala +++ /dev/null @@ -1,36 +0,0 @@ -package xyz.driver.pdsuicommon.domain - -import java.nio.charset.Charset - -import org.mindrot.jbcrypt.BCrypt - -final case class PasswordHash(value: Array[Byte]) { - - lazy val hashString: String = new String(value, Charset.forName("UTF-8")) - - override def toString: String = { - s"${this.getClass.getSimpleName}($hashString)" - } - - override def equals(that: Any): Boolean = { - that match { - case thatHash: PasswordHash => java.util.Arrays.equals(this.value, thatHash.value) - case _ => false - } - } - - override def hashCode(): Int = - 42 + java.util.Arrays.hashCode(this.value) - - def is(password: String): Boolean = - BCrypt.checkpw(password, hashString) -} - -object PasswordHash { - - def apply(password: String): PasswordHash = - new PasswordHash(getHash(password)) - - private def getHash(str: String): Array[Byte] = - BCrypt.hashpw(str, BCrypt.gensalt()).getBytes(Charset.forName("UTF-8")) -} diff --git a/src/main/scala/xyz/driver/pdsuicommon/domain/User.scala b/src/main/scala/xyz/driver/pdsuicommon/domain/User.scala index 4920176..654af1a 100644 --- a/src/main/scala/xyz/driver/pdsuicommon/domain/User.scala +++ b/src/main/scala/xyz/driver/pdsuicommon/domain/User.scala @@ -2,7 +2,7 @@ package xyz.driver.pdsuicommon.domain import java.math.BigInteger import java.security.SecureRandom -import java.time.LocalDateTime +import java.time.{Instant, LocalDateTime, ZoneId} import xyz.driver.pdsuicommon.logging._ import xyz.driver.pdsuicommon.domain.User.Role @@ -11,10 +11,22 @@ import xyz.driver.pdsuicommon.utils.Utils final case class User(id: StringId[User], email: Email, name: String, - role: Role, - passwordHash: PasswordHash, + roles: Set[Role], latestActivity: Option[LocalDateTime], - deleted: Option[LocalDateTime]) + deleted: Option[LocalDateTime]) { + + def this(driverUser: xyz.driver.entities.users.UserInfo) { + 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 { @@ -74,7 +86,7 @@ object User { implicit def toPhiString(x: User): PhiString = { import x._ - phi"User(id=$id, role=$role)" + phi"User(id=$id, roles=${Unsafe(roles.map(_.toString).mkString(", "))})" } // SecureRandom is thread-safe, see the implementation @@ -82,4 +94,70 @@ object User { 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/json/Serialization.scala b/src/main/scala/xyz/driver/pdsuicommon/json/Serialization.scala index 9800903..8231ddb 100644 --- a/src/main/scala/xyz/driver/pdsuicommon/json/Serialization.scala +++ b/src/main/scala/xyz/driver/pdsuicommon/json/Serialization.scala @@ -40,12 +40,6 @@ object Serialization { private val emailJsonWrites: Writes[Email] = Writes(email => JsString(email.value)) implicit val emailJsonFormat: Format[Email] = Format(emailJsonReads, emailJsonWrites) - private val passwordHashJsonReads: Reads[PasswordHash] = - Reads.StringReads.map(hash => PasswordHash(hash.getBytes("UTF-8"))) - private val passwordHashJsonWrites: Writes[PasswordHash] = Writes( - passwordHash => JsString(passwordHash.value.toString)) - implicit val passwordHashJsonFormat: Format[PasswordHash] = Format(passwordHashJsonReads, passwordHashJsonWrites) - private val caseIdJsonReads: Reads[CaseId] = Reads.StringReads.map(CaseId(_)) private val caseIdJsonWrites: Writes[CaseId] = Writes(caseId => JsString(caseId.id)) implicit val caseIdJsonFormat: Format[CaseId] = Format(caseIdJsonReads, caseIdJsonWrites) |