From 7f7bd651122754a3df47894b64ddb0456561bbe7 Mon Sep 17 00:00:00 2001 From: vlad Date: Thu, 13 Jul 2017 02:27:55 -0700 Subject: Updates for authentication --- .../scala/xyz/driver/pdsuicommon/acl/ACL.scala | 18 ++- .../auth/AuthenticatedRequestContext.scala | 8 +- .../pdsuicommon/computation/Computation.scala | 9 +- .../driver/pdsuicommon/domain/PasswordHash.scala | 36 ------ .../scala/xyz/driver/pdsuicommon/domain/User.scala | 88 +++++++++++++- .../driver/pdsuicommon/json/Serialization.scala | 6 - .../pdsuidomain/entities/LinkedPatient.scala | 14 --- .../json/linkedpatient/ApiLinkedPatient.scala | 29 ----- .../formats/json/user/ApiPartialUser.scala | 25 ++-- .../pdsuidomain/formats/json/user/ApiUser.scala | 19 ++- .../services/LinkedPatientService.scala | 58 ---------- .../driver/pdsuidomain/services/MailService.scala | 39 ------- .../driver/pdsuidomain/services/UserService.scala | 127 --------------------- .../services/fake/StubMailService.scala | 13 --- .../services/rest/SendGridMailService.scala | 37 ------ 15 files changed, 124 insertions(+), 402 deletions(-) delete mode 100644 src/main/scala/xyz/driver/pdsuicommon/domain/PasswordHash.scala delete mode 100644 src/main/scala/xyz/driver/pdsuidomain/entities/LinkedPatient.scala delete mode 100644 src/main/scala/xyz/driver/pdsuidomain/formats/json/linkedpatient/ApiLinkedPatient.scala delete mode 100644 src/main/scala/xyz/driver/pdsuidomain/services/LinkedPatientService.scala delete mode 100644 src/main/scala/xyz/driver/pdsuidomain/services/MailService.scala delete mode 100644 src/main/scala/xyz/driver/pdsuidomain/services/UserService.scala delete mode 100644 src/main/scala/xyz/driver/pdsuidomain/services/fake/StubMailService.scala delete mode 100644 src/main/scala/xyz/driver/pdsuidomain/services/rest/SendGridMailService.scala (limited to 'src/main') 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) diff --git a/src/main/scala/xyz/driver/pdsuidomain/entities/LinkedPatient.scala b/src/main/scala/xyz/driver/pdsuidomain/entities/LinkedPatient.scala deleted file mode 100644 index d10310a..0000000 --- a/src/main/scala/xyz/driver/pdsuidomain/entities/LinkedPatient.scala +++ /dev/null @@ -1,14 +0,0 @@ -package xyz.driver.pdsuidomain.entities - -import xyz.driver.pdsuicommon.domain._ -import xyz.driver.pdsuicommon.logging._ - -object LinkedPatient { - - implicit def toPhiString(x: LinkedPatient): PhiString = { - import x._ - phi"LinkedPatient(userId=$userId, patientId=$patientId, trialId=$trialId)" - } -} - -final case class LinkedPatient(userId: StringId[User], patientId: UuidId[Patient], trialId: StringId[Trial]) diff --git a/src/main/scala/xyz/driver/pdsuidomain/formats/json/linkedpatient/ApiLinkedPatient.scala b/src/main/scala/xyz/driver/pdsuidomain/formats/json/linkedpatient/ApiLinkedPatient.scala deleted file mode 100644 index 327bda2..0000000 --- a/src/main/scala/xyz/driver/pdsuidomain/formats/json/linkedpatient/ApiLinkedPatient.scala +++ /dev/null @@ -1,29 +0,0 @@ -package xyz.driver.pdsuidomain.formats.json.linkedpatient - -import java.util.UUID - -import play.api.libs.json.{Format, Json} -import xyz.driver.pdsuicommon.domain._ -import xyz.driver.pdsuidomain.services.LinkedPatientService.RichLinkedPatient - -final case class ApiLinkedPatient(email: String, name: String, patientId: UUID, trialId: String) { - - def toDomain = RichLinkedPatient( - email = Email(email), - name = name, - patientId = UuidId(patientId), - trialId = StringId(trialId) - ) -} - -object ApiLinkedPatient { - - implicit val format: Format[ApiLinkedPatient] = Json.format[ApiLinkedPatient] - - def fromDomain(entity: RichLinkedPatient) = ApiLinkedPatient( - email = entity.email.value, - name = entity.name, - patientId = entity.patientId.id, - trialId = entity.trialId.id - ) -} diff --git a/src/main/scala/xyz/driver/pdsuidomain/formats/json/user/ApiPartialUser.scala b/src/main/scala/xyz/driver/pdsuidomain/formats/json/user/ApiPartialUser.scala index 5fad653..cf20d32 100644 --- a/src/main/scala/xyz/driver/pdsuidomain/formats/json/user/ApiPartialUser.scala +++ b/src/main/scala/xyz/driver/pdsuidomain/formats/json/user/ApiPartialUser.scala @@ -9,11 +9,11 @@ import play.api.libs.json._ import scala.collection._ import scala.util.Try -import User._ import xyz.driver.pdsuicommon.json.JsonValidationException +import xyz.driver.pdsuicommon.json.Serialization.seqJsonFormat import xyz.driver.pdsuicommon.validation.{AdditionalConstraints, JsonValidationErrors} -final case class ApiPartialUser(email: Option[String], name: Option[String], roleId: Option[String]) { +final case class ApiPartialUser(email: Option[String], name: Option[String], roles: Option[Seq[String]]) { def applyTo(orig: User): Try[User] = Try { val validation = Map( @@ -33,9 +33,9 @@ final case class ApiPartialUser(email: Option[String], name: Option[String], rol def toDomain(id: StringId[User] = StringId(UUID.randomUUID().toString)): Try[User] = Try { val validation = Map( - JsPath \ "email" -> AdditionalConstraints.optionNonEmptyConstraint(email), - JsPath \ "name" -> AdditionalConstraints.optionNonEmptyConstraint(name), - JsPath \ "roleId" -> AdditionalConstraints.optionNonEmptyConstraint(roleId) + JsPath \ "email" -> AdditionalConstraints.optionNonEmptyConstraint(email), + JsPath \ "name" -> AdditionalConstraints.optionNonEmptyConstraint(name), + JsPath \ "roles" -> AdditionalConstraints.optionNonEmptyConstraint(roles) ) val validationErrors: JsonValidationErrors = validation.collect({ @@ -48,8 +48,7 @@ final case class ApiPartialUser(email: Option[String], name: Option[String], rol id = id, email = userEmail, name = name.get, - role = roleId.map(UserRole.roleFromString).get, - passwordHash = PasswordHash(createPassword), + roles = roles.toSeq.flatMap(_.map(UserRole.roleFromString)).toSet, latestActivity = None, deleted = None ) @@ -69,13 +68,9 @@ object ApiPartialUser { _.length > 255), Writes.StringWrites )) and - (JsPath \ "roleId").formatNullable[String]( - Format(Reads - .of[String] - .filter(ValidationError("unknown role"))({ - case x if UserRole.roleFromString.isDefinedAt(x) => true - case _ => false - }), - Writes.of[String])) + (JsPath \ "roles").formatNullable[Seq[String]]( + Format(seqJsonFormat[String].filter(ValidationError("unknown roles"))( + _.forall(UserRole.roleFromString.isDefinedAt)), + Writes.of[Seq[String]])) )(ApiPartialUser.apply, unlift(ApiPartialUser.unapply)) } diff --git a/src/main/scala/xyz/driver/pdsuidomain/formats/json/user/ApiUser.scala b/src/main/scala/xyz/driver/pdsuidomain/formats/json/user/ApiUser.scala index 8dbedfe..c21edd1 100644 --- a/src/main/scala/xyz/driver/pdsuidomain/formats/json/user/ApiUser.scala +++ b/src/main/scala/xyz/driver/pdsuidomain/formats/json/user/ApiUser.scala @@ -3,14 +3,17 @@ package xyz.driver.pdsuidomain.formats.json.user import java.time.{ZoneId, ZonedDateTime} import xyz.driver.pdsuicommon.domain.User +import xyz.driver.pdsuicommon.json.Serialization.seqJsonFormat import play.api.data.validation.ValidationError import play.api.libs.functional.syntax._ import play.api.libs.json._ +import scala.collection.Seq + final case class ApiUser(id: String, email: String, name: String, - roleId: String, + roles: Seq[String], latestActivity: Option[ZonedDateTime]) object ApiUser { @@ -19,14 +22,10 @@ object ApiUser { (JsPath \ "id").format[String] and (JsPath \ "email").format[String](Reads.email) and (JsPath \ "name").format[String] and - (JsPath \ "roleId").format[String]( - Format(Reads - .of[String] - .filter(ValidationError("unknown role"))({ - case x if UserRole.roleFromString.isDefinedAt(x) => true - case _ => false - }), - Writes.of[String])) and + (JsPath \ "roles").format( + Format( + seqJsonFormat[String].filter(ValidationError("unknown role"))(_.forall(UserRole.roleFromString.isDefinedAt)), + Writes.of[Seq[String]])) and (JsPath \ "latestActivity").formatNullable[ZonedDateTime] )(ApiUser.apply, unlift(ApiUser.unapply)) @@ -34,7 +33,7 @@ object ApiUser { user.id.id, user.email.value, user.name, - UserRole.roleToString(user.role), + user.roles.map(UserRole.roleToString).toSeq, user.latestActivity.map(ZonedDateTime.of(_, ZoneId.of("Z"))) ) } diff --git a/src/main/scala/xyz/driver/pdsuidomain/services/LinkedPatientService.scala b/src/main/scala/xyz/driver/pdsuidomain/services/LinkedPatientService.scala deleted file mode 100644 index a69283a..0000000 --- a/src/main/scala/xyz/driver/pdsuidomain/services/LinkedPatientService.scala +++ /dev/null @@ -1,58 +0,0 @@ -package xyz.driver.pdsuidomain.services - -import xyz.driver.pdsuicommon.domain._ -import xyz.driver.pdsuicommon.error.DomainError -import xyz.driver.pdsuicommon.logging._ -import xyz.driver.pdsuidomain.entities.{LinkedPatient, Patient, Trial} - -import scala.concurrent.Future - -object LinkedPatientService { - - trait DefaultTrialNotFoundError { - def userMessage: String = "Trial not found" - } - - trait DefaultPatientNotFoundError { - def userMessage: String = "Patient not found" - } - - final case class RichLinkedPatient(email: Email, name: String, patientId: UuidId[Patient], trialId: StringId[Trial]) { - def toLinkedPatient(user: User) = LinkedPatient( - userId = user.id, - patientId = patientId, - trialId = trialId - ) - } - - object RichLinkedPatient { - implicit def toPhiString(x: RichLinkedPatient): PhiString = { - import x._ - phi"RichLinkedPatient(email=${Unsafe(email)}, patientId=$patientId, trialId=$trialId)" - } - } - - sealed trait CreateReply - object CreateReply { - type Error = CreateReply with DomainError - - /** - * @param createdUser None if a user was created before - */ - final case class Created(x: RichLinkedPatient, createdUser: Option[User]) extends CreateReply - - case object PatientNotFoundError - extends CreateReply with DefaultPatientNotFoundError with DomainError.NotFoundError - - case object TrialNotFoundError extends CreateReply with DefaultPatientNotFoundError with DomainError.NotFoundError - - final case class CommonError(userMessage: String) extends CreateReply with DomainError - } -} - -trait LinkedPatientService { - - import LinkedPatientService._ - - def create(entity: RichLinkedPatient): Future[CreateReply] -} diff --git a/src/main/scala/xyz/driver/pdsuidomain/services/MailService.scala b/src/main/scala/xyz/driver/pdsuidomain/services/MailService.scala deleted file mode 100644 index 53f897a..0000000 --- a/src/main/scala/xyz/driver/pdsuidomain/services/MailService.scala +++ /dev/null @@ -1,39 +0,0 @@ -package xyz.driver.pdsuidomain.services - -import java.io.{InputStream, StringReader, StringWriter} - -import xyz.driver.pdsuidomain.services.MailService.Template -import com.github.mustachejava.DefaultMustacheFactory -import com.twitter.mustache.ScalaObjectHandler - -import scala.io.Source - -object MailService { - - trait Template { - val subject: String - def parameters: Map[String, Any] - def filename: String - val contentType: String = "text/html" - - protected val factory = new DefaultMustacheFactory() - factory.setObjectHandler(new ScalaObjectHandler) - - protected def inputStream: InputStream = getClass.getClassLoader.getResourceAsStream(filename) - protected def templateContent: String = Source.fromInputStream(inputStream).getLines().mkString - - def content: String = { - val template = factory.compile(new StringReader(templateContent), filename) - val writer = new StringWriter - template - .execute(writer, parameters) - .close() - writer.toString - } - } -} - -trait MailService { - - def sendTo(email: String, template: Template): Boolean -} diff --git a/src/main/scala/xyz/driver/pdsuidomain/services/UserService.scala b/src/main/scala/xyz/driver/pdsuidomain/services/UserService.scala deleted file mode 100644 index 85b93ed..0000000 --- a/src/main/scala/xyz/driver/pdsuidomain/services/UserService.scala +++ /dev/null @@ -1,127 +0,0 @@ -package xyz.driver.pdsuidomain.services - -import xyz.driver.pdsuicommon.auth.{AnonymousRequestContext, AuthenticatedRequestContext} -import xyz.driver.pdsuicommon.db.{Pagination, SearchFilterExpr, Sorting} -import xyz.driver.pdsuicommon.domain._ -import xyz.driver.pdsuicommon.error.DomainError - -import scala.concurrent.Future - -object UserService { - - trait DefaultCredentialsError { - def userMessage: String = "Incorrect email/password. Try again." - } - - trait DefaultAccessDeniedError { - def userMessage: String = "Access denied" - } - - trait DefaultNotFoundError { - def userMessage: String = "User not found" - } - - sealed trait ActivateExecutorReply - object ActivateExecutorReply { - type Error = ActivateExecutorReply with DomainError - final case class Entity(x: User) extends ActivateExecutorReply - case object NotFoundError extends ActivateExecutorReply with DomainError.NotFoundError { - val userMessage = "Info about you is not found on the server" - } - } - - sealed trait GetByIdReply - object GetByIdReply { - type Error = GetByIdReply with DomainError - final case class Entity(x: User) extends GetByIdReply - case object AuthorizationError - extends GetByIdReply with DomainError.AuthorizationError with DefaultAccessDeniedError - case object NotFoundError extends GetByIdReply with DomainError.NotFoundError with DefaultNotFoundError - final case class CommonError(userMessage: String) extends GetByIdReply with DomainError - } - - sealed trait GetByEmailReply - object GetByEmailReply { - final case class Entity(x: User) extends GetByEmailReply - case object NotFoundError extends GetByEmailReply with DefaultNotFoundError with DomainError.NotFoundError { - override def userMessage: String = "Incorrect email. Try again." - } - } - - sealed trait GetByCredentialsReply - object GetByCredentialsReply { - final case class Entity(x: User) extends GetByCredentialsReply - case object AuthenticationError - extends GetByCredentialsReply with DefaultCredentialsError with DomainError.AuthenticationError - case object NotFoundError extends GetByCredentialsReply with DomainError.NotFoundError with DefaultNotFoundError - } - - sealed trait GetListReply - object GetListReply { - final case class EntityList(xs: Seq[User], totalFound: Int) extends GetListReply - case object AuthorizationError extends GetListReply with DomainError.AuthorizationError with DefaultNotFoundError - } - - sealed trait CreateReply - object CreateReply { - type Error = CreateReply with DomainError - final case class Created(x: User) extends CreateReply - case object AuthorizationError extends CreateReply with DefaultNotFoundError with DomainError.AuthorizationError - final case class UserAlreadyExistsError(email: Email) extends CreateReply with DomainError { - val userMessage = s"The user with this email already exists." - } - final case class CommonError(userMessage: String) extends CreateReply with DomainError - } - - sealed trait UpdateReply - object UpdateReply { - type Error = UpdateReply with DomainError - final case class Updated(updated: User) extends UpdateReply - case object AuthorizationError - extends UpdateReply with DefaultAccessDeniedError with DomainError.AuthorizationError - final case class CommonError(userMessage: String) extends UpdateReply with DomainError - } - - sealed trait DeleteReply - object DeleteReply { - type Error = DeleteReply with DomainError - case object Deleted extends DeleteReply - final case class AuthorizationError(user: User) - extends DeleteReply with DefaultAccessDeniedError with DomainError.AuthorizationError - case object AssignedToRecordAndDocumentError extends DeleteReply with DomainError { - val userMessage = "User is can not be deleted because he has record and document in work" - } - case object NotFoundError extends DeleteReply with DefaultNotFoundError with DomainError.NotFoundError - final case class CommonError(userMessage: String) extends DeleteReply with DomainError - } -} - -trait UserService { - - import UserService._ - - /** - * Utility method for getting request executor. - */ - def activateExecutor(executorId: StringId[User])( - implicit requestContext: AnonymousRequestContext): Future[ActivateExecutorReply] - - def getById(userId: StringId[User])(implicit requestContext: AuthenticatedRequestContext): Future[GetByIdReply] - - def getByEmail(email: Email)(implicit requestContext: AnonymousRequestContext): Future[GetByEmailReply] - - def getByCredentials(email: Email, password: String)( - implicit requestContext: AnonymousRequestContext): Future[GetByCredentialsReply] - - def getAll(filter: SearchFilterExpr = SearchFilterExpr.Empty, - sorting: Option[Sorting] = None, - pagination: Option[Pagination] = None)( - implicit requestContext: AuthenticatedRequestContext): Future[GetListReply] - - def create(draftUser: User)(implicit requestContext: AuthenticatedRequestContext): Future[CreateReply] - - def update(origUser: User, draftUser: User)( - implicit requestContext: AuthenticatedRequestContext): Future[UpdateReply] - - def delete(userId: StringId[User])(implicit requestContext: AuthenticatedRequestContext): Future[DeleteReply] -} diff --git a/src/main/scala/xyz/driver/pdsuidomain/services/fake/StubMailService.scala b/src/main/scala/xyz/driver/pdsuidomain/services/fake/StubMailService.scala deleted file mode 100644 index 932da67..0000000 --- a/src/main/scala/xyz/driver/pdsuidomain/services/fake/StubMailService.scala +++ /dev/null @@ -1,13 +0,0 @@ -package xyz.driver.pdsuidomain.services.fake - -import xyz.driver.pdsuicommon.logging._ -import xyz.driver.pdsuidomain.services.MailService -import xyz.driver.pdsuidomain.services.MailService.Template - -object StubMailService extends MailService with PhiLogging { - - override def sendTo(email: String, template: Template): Boolean = { - logger.debug(phi"sendTo(email=${Unsafe(email)}") - true - } -} diff --git a/src/main/scala/xyz/driver/pdsuidomain/services/rest/SendGridMailService.scala b/src/main/scala/xyz/driver/pdsuidomain/services/rest/SendGridMailService.scala deleted file mode 100644 index bb3228e..0000000 --- a/src/main/scala/xyz/driver/pdsuidomain/services/rest/SendGridMailService.scala +++ /dev/null @@ -1,37 +0,0 @@ -package xyz.driver.pdsuidomain.services.rest - -import com.sendgrid._ -import xyz.driver.pdsuicommon.logging._ -import xyz.driver.pdsuidomain.services.MailService -import xyz.driver.pdsuidomain.services.MailService.Template - -import scala.util.control.NonFatal - -class SendGridMailService(apiKey: String, from: String) extends MailService with PhiLogging { - private val ExpectedHttpCode = 202 - - def sendTo(email: String, template: Template): Boolean = { - val to = new Email(email) - val content = new Content(template.contentType, template.content) - val mail = new Mail(new Email(from), template.subject, to, content) - - val request = new Request() - val sendGrid = new SendGrid(apiKey) - - try { - request.method = Method.POST - request.endpoint = "mail/send" - request.body = mail.build() - val response = sendGrid.api(request) - if (response.statusCode != ExpectedHttpCode) { - logger.error(phi"Unexpected response: ${Unsafe(response.statusCode)}, ${Unsafe(response.body.take(100))}") - } - - response.statusCode == ExpectedHttpCode - } catch { - case NonFatal(e) => - logger.error(phi"Can not send an email: $e") - false - } - } -} -- cgit v1.2.3