Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding the ability to control permissions on Morbid. #2

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions backend/schema.sql
Original file line number Diff line number Diff line change
Expand Up @@ -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)
);
23 changes: 12 additions & 11 deletions backend/src/main/resources/routes
Original file line number Diff line number Diff line change
@@ -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()
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()
8 changes: 6 additions & 2 deletions backend/src/main/scala/controllers/ControllerSupport.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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] = {
Expand Down
14 changes: 14 additions & 0 deletions backend/src/main/scala/controllers/UserController.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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}
Expand All @@ -20,6 +21,19 @@ class UserController @Inject()(

val SuccessToken = TypeCase[Success[Token]]

def addPermissionsFor() = Action.async(parse.json) { implicit r =>
validateThen[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 =>
createResource[User, CreateUserRequest](actors.users())
}
Expand Down
52 changes: 40 additions & 12 deletions backend/src/main/scala/domain/Domain.scala
Original file line number Diff line number Diff line change
Expand Up @@ -32,15 +32,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(
Expand All @@ -51,6 +52,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)
Expand All @@ -64,10 +76,13 @@ 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 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]
Expand Down Expand Up @@ -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")
Expand All @@ -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]
}
23 changes: 12 additions & 11 deletions backend/src/main/scala/services/Services.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
66 changes: 66 additions & 0 deletions backend/src/main/scala/store/Permissions.scala
Original file line number Diff line number Diff line change
@@ -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)
}
}
3 changes: 1 addition & 2 deletions backend/src/main/scala/store/RootActors.scala
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import services.{AppServices, TokenGenerator}

trait RootActors {
def users(): ActorRef

}

@Singleton
Expand All @@ -19,6 +18,6 @@ class RootActorsImpl @Inject() (
.actorSystem()
.actorOf(UsersSupervisor.props(services, tokens, accountManager), "users")

override def users() = usersRef
override def users(): ActorRef = usersRef
}

23 changes: 13 additions & 10 deletions backend/src/main/scala/store/Stores.scala
Original file line number Diff line number Diff line change
Expand Up @@ -14,21 +14,24 @@ 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
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
}
Loading