From 88a7a082ece98e7679abe27449756ad5e82c8f2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20Silv=C3=A9rio?= Date: Thu, 4 Apr 2019 15:29:12 -0300 Subject: [PATCH 1/3] Adding the ability to control permissions on Morbid. --- backend/schema.sql | 12 ++ backend/src/main/resources/routes | 23 ++-- .../scala/controllers/ControllerSupport.scala | 8 +- .../scala/controllers/UserController.scala | 5 + backend/src/main/scala/domain/Domain.scala | 52 +++++++-- .../src/main/scala/services/Services.scala | 23 ++-- .../src/main/scala/store/Permissions.scala | 66 +++++++++++ backend/src/main/scala/store/RootActors.scala | 3 +- backend/src/main/scala/store/Stores.scala | 23 ++-- backend/src/main/scala/store/Users.scala | 109 ++++++++++++++---- 10 files changed, 251 insertions(+), 73 deletions(-) create mode 100644 backend/src/main/scala/store/Permissions.scala diff --git a/backend/schema.sql b/backend/schema.sql index e070bad..9fda75b 100644 --- a/backend/schema.sql +++ b/backend/schema.sql @@ -35,3 +35,15 @@ CREATE TABLE secret ( token VARCHAR(128) NOT NULL UNIQUE , PRIMARY KEY(id) ); + +DROP TABLE IF EXISTS permissions CASCADE; +CREATE TABLE permissions ( + id SERIAL , + user_id BIGINT REFERENCES users (id) , + permission VARCHAR(50) NOT NULL , + created_by BIGINT REFERENCES users (id) , + created TIMESTAMP NOT NULL , + deleted_by BIGINT REFERENCES users (id) , + deleted TIMESTAMP , + PRIMARY KEY(id) +); diff --git a/backend/src/main/resources/routes b/backend/src/main/resources/routes index b88b951..1e3ab5c 100644 --- a/backend/src/main/resources/routes +++ b/backend/src/main/resources/routes @@ -1,11 +1,12 @@ -GET /i/ping xingu.commons.play.controllers.InternalController.ping() -GET /i/conf xingu.commons.play.controllers.InternalController.conf() -GET /i/stat xingu.commons.play.controllers.InternalController.stat() -POST /account/create controllers.AccountController.create() -GET /account/id/:it controllers.AccountController.byId(it: Long) -POST /user/create controllers.UserController.create() -POST /user/login controllers.UserController.login() -GET /user/id/:it controllers.UserController.byId(it: Long) -GET /user/token/:it controllers.UserController.byToken(it: String) -POST /user/password/reset controllers.UserController.resetPassword() -POST /user/password/change controllers.UserController.changePassword() \ No newline at end of file +GET /i/ping xingu.commons.play.controllers.InternalController.ping() +GET /i/conf xingu.commons.play.controllers.InternalController.conf() +GET /i/stat xingu.commons.play.controllers.InternalController.stat() +POST /account/create controllers.AccountController.create() +GET /account/id/:it controllers.AccountController.byId(it: Long) +POST /user/create controllers.UserController.create() +POST /user/login controllers.UserController.login() +GET /user/id/:it controllers.UserController.byId(it: Long) +GET /user/token/:it controllers.UserController.byToken(it: String) +POST /user/password/reset controllers.UserController.resetPassword() +POST /user/password/change controllers.UserController.changePassword() +POST /user/permission/create controllers.UserController.addPermissionsFor() \ No newline at end of file diff --git a/backend/src/main/scala/controllers/ControllerSupport.scala b/backend/src/main/scala/controllers/ControllerSupport.scala index 20216d7..8e158a8 100644 --- a/backend/src/main/scala/controllers/ControllerSupport.scala +++ b/backend/src/main/scala/controllers/ControllerSupport.scala @@ -21,16 +21,20 @@ class ControllerSupport (services: AppServices) extends InjectedController with val log = LoggerFactory.getLogger(getClass) def createResource[RESOURCE, CREATE](actor: ActorRef)(implicit req: Request[JsValue], writer: Writes[RESOURCE], reader: Reads[CREATE]): Future[Result] = + createResource[RESOURCE, CREATE](actor, withPayload = true)(req, writer, reader) + + def createResource[RESOURCE, CREATE](actor: ActorRef, withPayload: Boolean)(implicit req: Request[JsValue], writer: Writes[RESOURCE], reader: Reads[CREATE]): Future[Result] = req.body.validate[CREATE] match { case success: JsSuccess[CREATE] => inquire(actor) { success.get } map { case ResourceAlreadyExists => Conflict("Resource Already Exists") case Failure(e) => log.error("Error Creating Resource", e); InternalServerError - case resource: RESOURCE => Ok(Json.toJson(resource)) + case resource: RESOURCE => if(withPayload) Ok(Json.toJson(resource)) else Ok } recover { case NonFatal(e) => log.error("Error Creating Resource", e); InternalServerError } - case JsError(err) => Future.successful(BadRequest) + case JsError(err) => + Future.successful(BadRequest(JsError.toJson(err).toString())) } def createResourceDirectly[RESOURCE, REQUEST](collection: ObjectStore[RESOURCE, REQUEST])(implicit req: Request[JsValue], writer: Writes[RESOURCE], reader: Reads[REQUEST]): Future[Result] = { diff --git a/backend/src/main/scala/controllers/UserController.scala b/backend/src/main/scala/controllers/UserController.scala index 855e6bf..622dc9f 100644 --- a/backend/src/main/scala/controllers/UserController.scala +++ b/backend/src/main/scala/controllers/UserController.scala @@ -20,6 +20,11 @@ class UserController @Inject()( val SuccessToken = TypeCase[Success[Token]] + def addPermissionsFor() = Action.async(parse.json) { + implicit r => + createResource[List[Permission], AddPermissionRequest](actors.users(), withPayload = false) + } + def create() = Action.async(parse.json) { implicit r => createResource[User, CreateUserRequest](actors.users()) } diff --git a/backend/src/main/scala/domain/Domain.scala b/backend/src/main/scala/domain/Domain.scala index eab5c54..baafe37 100644 --- a/backend/src/main/scala/domain/Domain.scala +++ b/backend/src/main/scala/domain/Domain.scala @@ -8,6 +8,7 @@ import play.api.libs.json.Reads.dateReads import play.api.libs.json.Writes.dateWrites case object UnknownUser +case object UnknownPermission case class GetByToken(token: String) case class GetById(id: Long) case class CreateResource[T](request: T) @@ -32,15 +33,16 @@ case class Password( ) case class User( - id : Long, - account : Long, - created : Date, - deleted : Option[Date], - active : Boolean, - username : String, - email : String, - `type` : String, - password : Option[Password] + id : Long, + account : Long, + created : Date, + deleted : Option[Date], + active : Boolean, + username : String, + email : String, + `type` : String, + password : Option[Password], + permissions : Option[List[Permission]] ) case class Token( @@ -51,6 +53,17 @@ case class Token( expiresAt : Option[Date] ) +case class Permission( + id : Long, + userId : Long, + permission : String, + createdBy : Long, + created : Date, + deletedBy : Option[Long], + deleted : Option[Date] +) + +case class AddPermissionRequest(userId: Long, permissions: Option[List[String]], createdBy: Option[Long]) case class AuthenticateRequest(username: String, password: String) case class CreateAccountRequest(name: String) case class CreateUserRequest(account: Long, username: String, password: Option[String], email: String, `type`: String) @@ -66,8 +79,10 @@ object json { implicit val ServerTimeWriter = Json.writes[ServerTime] implicit val AccountWriter = Json.writes[Account] implicit val PasswordWriter = Json.writes[Password] + implicit val PermissionWriter = Json.writes[Permission] implicit val UserWriter = Json.writes[User] implicit val TokenWriter = Json.writes[Token] + implicit val AddPermissionRequestReader = Json.reads[AddPermissionRequest] implicit val AuthenticateRequestReader = Json.reads[AuthenticateRequest] implicit val CreateAccountRequestReader = Json.reads[CreateAccountRequest] implicit val CreateUserRequestReader = Json.reads[CreateUserRequest] @@ -96,6 +111,7 @@ class UserTable(tag: Tag) extends Table[(Long, Long, Timestamp, Option[Timestamp def `type` : Rep[String] = column[String] ("type") def * = (id, account, created, deleted, active, username, email, `type`) } + class SecretTable(tag: Tag) extends Table[(Long, Long, Timestamp, Option[Timestamp], String, String, String)](tag, "secret") { def id : Rep[Long] = column[Long] ("id", O.PrimaryKey, O.AutoInc) def user : Rep[Long] = column[Long] ("user_id") @@ -107,8 +123,20 @@ class SecretTable(tag: Tag) extends Table[(Long, Long, Timestamp, Option[Timesta def * = (id, user, created, deleted, method, password, token) } +class PermissionTable(tag: Tag) extends Table[(Long, Long, String, Long, Timestamp, Option[Long], Option[Timestamp])](tag, "permissions") { + def id : Rep[Long] = column[Long] ("id", O.PrimaryKey, O.AutoInc) + def user : Rep[Long] = column[Long] ("user_id") + def permission: Rep[String] = column[String] ("permission") + def createdBy : Rep[Long] = column[Long] ("created_by") + def created : Rep[Timestamp] = column[Timestamp] ("created") + def deletedBy : Rep[Option[Long]] = column[Long] ("deleted_by") + def deleted : Rep[Option[Timestamp]] = column[Option[Timestamp]] ("deleted") + def * = (id, user, permission, createdBy, created, deletedBy, deleted) +} + object collections { - val accounts = TableQuery[AccountTable] - val users = TableQuery[UserTable] - val secrets = TableQuery[SecretTable] + val accounts = TableQuery[AccountTable] + val users = TableQuery[UserTable] + val secrets = TableQuery[SecretTable] + val permissions = TableQuery[PermissionTable] } \ No newline at end of file diff --git a/backend/src/main/scala/services/Services.scala b/backend/src/main/scala/services/Services.scala index 3a0e84b..3419473 100644 --- a/backend/src/main/scala/services/Services.scala +++ b/backend/src/main/scala/services/Services.scala @@ -5,25 +5,26 @@ import java.time.Clock import akka.actor.ActorSystem import javax.inject.{Inject, Singleton} import play.api.{Configuration, Environment} +import store.Permissions import xingu.commons.play.services.{BasicServices, Services} import scala.concurrent.ExecutionContext trait AppServices extends Services { - def rnd() : Random - def secrets() : SecretValidator + def rnd() : Random + def secrets(): SecretValidator } @Singleton class AppServicesImpl @Inject() ( - ec : ExecutionContext, - random : Random, - env : Environment, - config : Configuration, - clock : Clock, - validator : SecretValidator, - system : ActorSystem) extends BasicServices(ec, env, config, clock, system) with AppServices { + ec : ExecutionContext, + random : Random, + env : Environment, + config : Configuration, + clock : Clock, + validator : SecretValidator, + system : ActorSystem) extends BasicServices(ec, env, config, clock, system) with AppServices { - override def rnd() : Random = random - override def secrets() : SecretValidator = validator + override def rnd() : Random = random + override def secrets(): SecretValidator = validator } diff --git a/backend/src/main/scala/store/Permissions.scala b/backend/src/main/scala/store/Permissions.scala new file mode 100644 index 0000000..7d7e9ef --- /dev/null +++ b/backend/src/main/scala/store/Permissions.scala @@ -0,0 +1,66 @@ +package store + +import java.sql.Timestamp +import java.util +import java.util.Date + +import domain._ +import domain.collections._ +import javax.inject.Inject +import org.slf4j.{Logger, LoggerFactory} +import services.{AppServices, TokenGenerator} +import slick.jdbc.PostgresProfile.api._ + +import scala.concurrent.{ExecutionContext, Future} +import scala.language.postfixOps + +trait Permissions extends ObjectStore[List[Permission], AddPermissionRequest] { + def byId(userId: Long) : Future[Option[List[Permission]]] +} + +class DatabasePermissions(services: AppServices, db: Database) extends Permissions { + + implicit val ec: ExecutionContext = services.ec() + val log: Logger = LoggerFactory.getLogger(getClass) + + override def byId(it: Long): Future[Option[List[Permission]]] = Future.failed(new Exception("ERR - TODO")) + + override def create(request: AddPermissionRequest): Future[List[Permission]] = { + val option: Option[Seq[String]] = request.permissions + val permissionsList = List[Permission]() + + if(option.isEmpty) + Future.failed(new Exception("Permissions must be provided.")) + + option.get.foreach { permission => + + val instant = services.clock().instant() + val created = new Timestamp(instant.toEpochMilli) + + val v = db.run { + (permissions returning permissions.map(_.id)) += ( + 0l, + request.userId, + permission, + request.createdBy.get, + created, + None, + None, + ) + } map { id => + Permission( + id = id, + userId = request.userId, + permission = permission, + createdBy = request.createdBy.get, + created = Date.from(instant), + deletedBy = None, + deleted = None) + } + + permissionsList.::(v) + } + + Future.successful(permissionsList) + } +} \ No newline at end of file diff --git a/backend/src/main/scala/store/RootActors.scala b/backend/src/main/scala/store/RootActors.scala index 55a471f..0d9b6f9 100644 --- a/backend/src/main/scala/store/RootActors.scala +++ b/backend/src/main/scala/store/RootActors.scala @@ -6,7 +6,6 @@ import services.{AppServices, TokenGenerator} trait RootActors { def users(): ActorRef - } @Singleton @@ -19,6 +18,6 @@ class RootActorsImpl @Inject() ( .actorSystem() .actorOf(UsersSupervisor.props(services, tokens, accountManager), "users") - override def users() = usersRef + override def users(): ActorRef = usersRef } diff --git a/backend/src/main/scala/store/Stores.scala b/backend/src/main/scala/store/Stores.scala index c0ef97e..dfc62d5 100644 --- a/backend/src/main/scala/store/Stores.scala +++ b/backend/src/main/scala/store/Stores.scala @@ -14,9 +14,10 @@ trait ObjectStore[T, CREATE] { } trait Stores { - def accounts() : Accounts - def users() : Users - def passwords() : Passwords + def accounts() : Accounts + def users() : Users + def passwords() : Passwords + def permissions() : Permissions } @Singleton @@ -24,11 +25,13 @@ class StoresImpl @Inject()( services: AppServices, tokens : TokenGenerator) extends Stores { - private val db : PostgresProfile.backend.Database = Database.forConfig("database") - private val ACCOUNTS : Accounts = new DatabaseAccounts (services, db) - private val USERS : Users = new DatabaseUsers (services, db, tokens) - private val PASSWORDS : Passwords = new DatabasePasswords (services, db, tokens) - override def accounts() : Accounts = ACCOUNTS - override def users() : Users = USERS - override def passwords() : Passwords = PASSWORDS + private val db : PostgresProfile.backend.Database = Database.forConfig("database") + private val ACCOUNTS : Accounts = new DatabaseAccounts (services, db) + private val USERS : Users = new DatabaseUsers (services, db, tokens) + private val PASSWORDS : Passwords = new DatabasePasswords (services, db, tokens) + private val PERMISSIONS : Permissions = new DatabasePermissions(services, db) + override def accounts() : Accounts = ACCOUNTS + override def users() : Users = USERS + override def passwords() : Passwords = PASSWORDS + override def permissions(): Permissions = PERMISSIONS } diff --git a/backend/src/main/scala/store/Users.scala b/backend/src/main/scala/store/Users.scala index 113007c..1619691 100644 --- a/backend/src/main/scala/store/Users.scala +++ b/backend/src/main/scala/store/Users.scala @@ -1,5 +1,6 @@ package store +import java.security.Permissions import java.sql.Timestamp import java.time.temporal.ChronoUnit import java.util.Date @@ -15,7 +16,7 @@ import xingu.commons.play.akka.XinguActor import xingu.commons.utils._ import scala.collection.mutable -import scala.concurrent.Future +import scala.concurrent.{Await, Future} import scala.concurrent.duration._ import scala.language.postfixOps import scala.util.control.NonFatal @@ -26,34 +27,50 @@ trait Users extends ObjectStore[User, CreateUserRequest] { def byUsername(username: String) : Future[Option[User]] } -class DatabaseUsers (services: AppServices, db: Database, tokens: TokenGenerator) extends Users { +class DatabaseUsers(services: AppServices, db: Database, tokens: TokenGenerator) extends Users { - type RowFilterParams = (UserTable, Rep[Option[SecretTable]]) // remove warning from intellij + type RowFilterParams = ((UserTable, Rep[Option[SecretTable]]), Rep[Option[PermissionTable]]) // remove warning from intellij implicit val ec = services.ec() val log = LoggerFactory.getLogger(getClass) def selectOne(rowFilter: RowFilterParams => Rep[Boolean]): Future[Option[User]] = { val query = for { - (user, secret) <- users joinLeft secrets on { _.id === _.user } filter { rowFilter } + ((user, secret), permission) <- { + users joinLeft secrets on { + _.id === _.user + } joinLeft permissions on { + _._1.id === _.user + } filter { + rowFilter + } + } } yield ( - (user.id, user.account, user.created, user.deleted, user.active, user.username, user.email , user.`type`), - secret.map(s => (s.id, s.user , s.created, s.deleted , s.method , s.password, s.token)) + (user.id, user.account, user.created, user.deleted, user.active, user.username, user.email, user.`type`), + secret.map(s => (s.id, s.user, s.created, s.deleted, s.method, s.password, s.token)), + permission.map(p => (p.id, p.user, p.permission, p.createdBy, p.created, p.deletedBy, p.deleted)) ) - /* merge users and their secrets */ + /* merge users and their secrets and permissions */ db.run(query.result) map { - _.map(pair => (toUser(pair._1), toPassword(pair._2))) + _.map(x => ((toUser(x._1), toPassword(x._2)), toPermission(x._3))) } map { _.groupBy(_._1) .map({ - case (user, tuples) => - val password = tuples.flatMap(_._2) + case ((user, password), tuples) => + val password = tuples.flatMap(_._1._2) .filter(_.deleted.isEmpty) /* not deleted */ .sortBy(_.created) .reverse /* most recent */ .headOption - (user.copy(password = password), tuples) + + val perms: List[Permission] = tuples.flatMap(_._2) + .filter(_.deleted.isEmpty) /* not deleted */ + .sortBy(_.created) + .reverse /* most recent */ + .toList + + (user.copy(password = password, permissions = Option[List[Permission]](perms)), tuples) }) .keys .toSeq @@ -61,6 +78,18 @@ class DatabaseUsers (services: AppServices, db: Database, tokens: TokenGenerator } } + def toPermission(tuple: Option[(Long, Long, String, Long, Timestamp, Option[Long], Option[Timestamp])]) = tuple map { + case (id, account, permission, createdBy, created, deletedBy, deleted) => + Permission( + id = id, + userId = account, + permission = permission, + createdBy = createdBy, + created = new Date(created.getTime), + deletedBy = deletedBy, + deleted = deleted.map(it => new Date(it.getTime))) + } + def toPassword(tuple: Option[(Long, Long, Timestamp, Option[Timestamp], String, String, String)]) = tuple map { case (id, user, created, deleted, method, password, token) => Password( @@ -84,12 +113,13 @@ class DatabaseUsers (services: AppServices, db: Database, tokens: TokenGenerator username = username, email = email, `type` = accType, - password = None) + password = None, + permissions = None) } - override def byId (it: Long) : Future[Option[User]] = selectOne { case (user, _) => user.id === it } - override def byUsername (it: String) : Future[Option[User]] = selectOne { case (user, _) => user.username === it } - override def byToken (it: String) : Future[Option[User]] = selectOne { case (_, secret) => + override def byId (it: Long) : Future[Option[User]] = selectOne { case ((user, _), _) => user.id === it } + override def byUsername (it: String) : Future[Option[User]] = selectOne { case ((user, _), _) => user.username === it } + override def byToken (it: String) : Future[Option[User]] = selectOne { case ((_, secret), _) => secret.map(value => value.token === it && value.deleted.isEmpty) getOrElse false } @@ -110,15 +140,16 @@ class DatabaseUsers (services: AppServices, db: Database, tokens: TokenGenerator ) } map { id => User( - id = id, - account = request.account, - created = Date.from(instant), - deleted = None, - active = true, - username = request.username, - email = request.email, - `type` = request.`type`, - password = None) + id = id, + account = request.account, + created = Date.from(instant), + deleted = None, + active = true, + username = request.username, + email = request.email, + `type` = request.`type`, + password = None, + permissions = None) } } } @@ -184,7 +215,6 @@ class UsersSupervisor ( .recover { case NonFatal(e) => replyTo ! Failure(e) } } - def validatePassword(it: CreateUserRequest): Future[Try[String]] = Future.successful { it .password @@ -220,6 +250,9 @@ class UsersSupervisor ( case it @ GetByToken(token) => fw(it, byToken.get(token), users.byToken(token)) + case it @ AddPermissionRequest(userId, _, _) => + fw(it, byId.get(userId), users.byId(userId)) + case it @ AuthenticateRequest(username, _) => fw(it, byUsername.get(username), users.byUsername(username)) @@ -266,6 +299,31 @@ class SingleUserSupervisor ( services.clock().instant().minus(daysAgo) } + def addPermissionFor(userId: Long, permissions: Option[List[String]]): Future[Future[List[Permission]]] = { + val permissionIssuer = Option(user.id) + val target = accountManager.users().byId(userId) + + target map { + case Some(targetUser) => + val t = targetUser.permissions.get + var valid = true + + t.foreach { + perm => + if (permissions.getOrElse(Seq()).contains(perm.permission)) + valid = false + } + + if(!valid) { + Future.failed(new Exception("User already have one of the given permissions.")) + } else { + val request = AddPermissionRequest(userId = userId, permissions = permissions, createdBy = permissionIssuer) + val perms = accountManager.permissions() + perms.create(request) + } + } + } + def checkPassword(provided: String)(password: Password): Try[Token] = { if(password.created.before(passwordLifetimeLimit())) { Failure { new Exception("Password Too Old") } @@ -292,6 +350,7 @@ class SingleUserSupervisor ( override def receive = { case Refresh => println("refreshing") + case AddPermissionRequest(userId, perms, _) => sender ! addPermissionFor(userId, perms) case ReceiveTimeout => context.parent ! DecommissionSupervisor(user) case AuthenticateRequest(_, password) => sender ! authenticate(password) case GetByToken(_) => sender ! user From 1386f41335723e04dc417e2008b18034880530ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20Silv=C3=A9rio?= Date: Thu, 4 Apr 2019 17:23:00 -0300 Subject: [PATCH 2/3] Returning the correct message and status code when a given permission is already in the user scope. --- .../main/scala/controllers/UserController.scala | 15 ++++++++++++--- backend/src/main/scala/domain/Domain.scala | 5 ++++- backend/src/main/scala/store/Users.scala | 12 +++++++----- 3 files changed, 23 insertions(+), 9 deletions(-) diff --git a/backend/src/main/scala/controllers/UserController.scala b/backend/src/main/scala/controllers/UserController.scala index 622dc9f..7af8ad6 100644 --- a/backend/src/main/scala/controllers/UserController.scala +++ b/backend/src/main/scala/controllers/UserController.scala @@ -4,6 +4,7 @@ import domain._ import domain.json._ import javax.inject.Inject import play.api.libs.json._ +import play.api.mvc.Request import services.AppServices import shapeless.TypeCase import store.{RootActors, Stores} @@ -20,9 +21,17 @@ class UserController @Inject()( val SuccessToken = TypeCase[Success[Token]] - def addPermissionsFor() = Action.async(parse.json) { - implicit r => - createResource[List[Permission], AddPermissionRequest](actors.users(), withPayload = false) + def addPermissionsFor() = Action.async(parse.json) { implicit r => + validateThen[AddPermission[AddPermissionRequest]] { req => + inquire(actors.users()) { req } map { + case UnknownUser => NotFound + case Failure(e) => Forbidden(e.getMessage) + case _ => Ok + } recover { + case NonFatal(e) => log.error("", e); InternalServerError + case _ => InternalServerError("") + } + } } def create() = Action.async(parse.json) { implicit r => diff --git a/backend/src/main/scala/domain/Domain.scala b/backend/src/main/scala/domain/Domain.scala index baafe37..d866f28 100644 --- a/backend/src/main/scala/domain/Domain.scala +++ b/backend/src/main/scala/domain/Domain.scala @@ -8,7 +8,7 @@ import play.api.libs.json.Reads.dateReads import play.api.libs.json.Writes.dateWrites case object UnknownUser -case object UnknownPermission +case class AddPermission[T](userId: Long, permissions: Option[List[String]]) case class GetByToken(token: String) case class GetById(id: Long) case class CreateResource[T](request: T) @@ -77,12 +77,15 @@ object json { implicit val CustomDateWrites = dateWrites(format) implicit val CustomDateReads = dateReads(format) implicit val ServerTimeWriter = Json.writes[ServerTime] + implicit val AddPermissionRequestWriter = Json.writes[AddPermissionRequest] + implicit val AddPermissionWriter = Json.writes[AddPermission[AddPermissionRequest]] implicit val AccountWriter = Json.writes[Account] implicit val PasswordWriter = Json.writes[Password] implicit val PermissionWriter = Json.writes[Permission] implicit val UserWriter = Json.writes[User] implicit val TokenWriter = Json.writes[Token] implicit val AddPermissionRequestReader = Json.reads[AddPermissionRequest] + implicit val AddPermissionReader = Json.reads[AddPermission[AddPermissionRequest]] implicit val AuthenticateRequestReader = Json.reads[AuthenticateRequest] implicit val CreateAccountRequestReader = Json.reads[CreateAccountRequest] implicit val CreateUserRequestReader = Json.reads[CreateUserRequest] diff --git a/backend/src/main/scala/store/Users.scala b/backend/src/main/scala/store/Users.scala index 1619691..ebc22ab 100644 --- a/backend/src/main/scala/store/Users.scala +++ b/backend/src/main/scala/store/Users.scala @@ -250,7 +250,7 @@ class UsersSupervisor ( case it @ GetByToken(token) => fw(it, byToken.get(token), users.byToken(token)) - case it @ AddPermissionRequest(userId, _, _) => + case it @ AddPermission(userId, _) => fw(it, byId.get(userId), users.byId(userId)) case it @ AuthenticateRequest(username, _) => @@ -299,7 +299,7 @@ class SingleUserSupervisor ( services.clock().instant().minus(daysAgo) } - def addPermissionFor(userId: Long, permissions: Option[List[String]]): Future[Future[List[Permission]]] = { + def addPermissionFor(userId: Long, permissions: Option[List[String]]) = { val permissionIssuer = Option(user.id) val target = accountManager.users().byId(userId) @@ -315,12 +315,14 @@ class SingleUserSupervisor ( } if(!valid) { - Future.failed(new Exception("User already have one of the given permissions.")) + throw new Exception("User already have one of the given permissions.") } else { val request = AddPermissionRequest(userId = userId, permissions = permissions, createdBy = permissionIssuer) val perms = accountManager.permissions() - perms.create(request) + Success { perms.create(request) } } + } recover { + case e: Exception => Failure { e } } } @@ -350,7 +352,7 @@ class SingleUserSupervisor ( override def receive = { case Refresh => println("refreshing") - case AddPermissionRequest(userId, perms, _) => sender ! addPermissionFor(userId, perms) + case AddPermission(userId, perms) => to(sender) { addPermissionFor(userId, perms) } case ReceiveTimeout => context.parent ! DecommissionSupervisor(user) case AuthenticateRequest(_, password) => sender ! authenticate(password) case GetByToken(_) => sender ! user From 486cf7ea8e209a394738e407f8946539b75cd3ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20Silv=C3=A9rio?= Date: Fri, 5 Apr 2019 09:26:29 -0300 Subject: [PATCH 3/3] Getting rid of AddPermission. --- backend/src/main/scala/controllers/UserController.scala | 2 +- backend/src/main/scala/domain/Domain.scala | 3 --- backend/src/main/scala/store/Users.scala | 7 +++---- 3 files changed, 4 insertions(+), 8 deletions(-) diff --git a/backend/src/main/scala/controllers/UserController.scala b/backend/src/main/scala/controllers/UserController.scala index 7af8ad6..4f9cbf0 100644 --- a/backend/src/main/scala/controllers/UserController.scala +++ b/backend/src/main/scala/controllers/UserController.scala @@ -22,7 +22,7 @@ class UserController @Inject()( val SuccessToken = TypeCase[Success[Token]] def addPermissionsFor() = Action.async(parse.json) { implicit r => - validateThen[AddPermission[AddPermissionRequest]] { req => + validateThen[AddPermissionRequest] { req => inquire(actors.users()) { req } map { case UnknownUser => NotFound case Failure(e) => Forbidden(e.getMessage) diff --git a/backend/src/main/scala/domain/Domain.scala b/backend/src/main/scala/domain/Domain.scala index d866f28..a4ef0c5 100644 --- a/backend/src/main/scala/domain/Domain.scala +++ b/backend/src/main/scala/domain/Domain.scala @@ -8,7 +8,6 @@ import play.api.libs.json.Reads.dateReads import play.api.libs.json.Writes.dateWrites case object UnknownUser -case class AddPermission[T](userId: Long, permissions: Option[List[String]]) case class GetByToken(token: String) case class GetById(id: Long) case class CreateResource[T](request: T) @@ -78,14 +77,12 @@ object json { implicit val CustomDateReads = dateReads(format) implicit val ServerTimeWriter = Json.writes[ServerTime] implicit val AddPermissionRequestWriter = Json.writes[AddPermissionRequest] - implicit val AddPermissionWriter = Json.writes[AddPermission[AddPermissionRequest]] implicit val AccountWriter = Json.writes[Account] implicit val PasswordWriter = Json.writes[Password] implicit val PermissionWriter = Json.writes[Permission] implicit val UserWriter = Json.writes[User] implicit val TokenWriter = Json.writes[Token] implicit val AddPermissionRequestReader = Json.reads[AddPermissionRequest] - implicit val AddPermissionReader = Json.reads[AddPermission[AddPermissionRequest]] implicit val AuthenticateRequestReader = Json.reads[AuthenticateRequest] implicit val CreateAccountRequestReader = Json.reads[CreateAccountRequest] implicit val CreateUserRequestReader = Json.reads[CreateUserRequest] diff --git a/backend/src/main/scala/store/Users.scala b/backend/src/main/scala/store/Users.scala index ebc22ab..95285bc 100644 --- a/backend/src/main/scala/store/Users.scala +++ b/backend/src/main/scala/store/Users.scala @@ -1,6 +1,5 @@ package store -import java.security.Permissions import java.sql.Timestamp import java.time.temporal.ChronoUnit import java.util.Date @@ -16,7 +15,7 @@ import xingu.commons.play.akka.XinguActor import xingu.commons.utils._ import scala.collection.mutable -import scala.concurrent.{Await, Future} +import scala.concurrent.Future import scala.concurrent.duration._ import scala.language.postfixOps import scala.util.control.NonFatal @@ -250,7 +249,7 @@ class UsersSupervisor ( case it @ GetByToken(token) => fw(it, byToken.get(token), users.byToken(token)) - case it @ AddPermission(userId, _) => + case it @ AddPermissionRequest(userId, _, _) => fw(it, byId.get(userId), users.byId(userId)) case it @ AuthenticateRequest(username, _) => @@ -352,7 +351,7 @@ class SingleUserSupervisor ( override def receive = { case Refresh => println("refreshing") - case AddPermission(userId, perms) => to(sender) { addPermissionFor(userId, perms) } + case AddPermissionRequest(userId, perms, _) => to(sender) { addPermissionFor(userId, perms) } case ReceiveTimeout => context.parent ! DecommissionSupervisor(user) case AuthenticateRequest(_, password) => sender ! authenticate(password) case GetByToken(_) => sender ! user