mirror of
https://github.com/OpenBankProject/OBP-API.git
synced 2026-02-06 11:06:49 +00:00
Added preliminary support for user roles
This commit is contained in:
parent
40df926919
commit
04c9032aef
@ -166,6 +166,8 @@ class Boot extends Loggable{
|
||||
Props.get("connector").getOrElse("") == "kafka" )
|
||||
schemifyAll()
|
||||
|
||||
val superuser = Role.findOrCreateAndSave("superuser", "a category", code.model.dataAccess.MappedPermission.fromAPermission(APermission.all))
|
||||
|
||||
// This sets up MongoDB config (for the mongodb connector)
|
||||
if(Props.get("connector").getOrElse("") == "mongodb")
|
||||
MongoConfig.init
|
||||
@ -382,6 +384,8 @@ class Boot extends Loggable{
|
||||
|
||||
object ToSchemify {
|
||||
val models = List(OBPUser,
|
||||
Role,
|
||||
MappedPermission,
|
||||
Admin,
|
||||
Nonce,
|
||||
Token,
|
||||
|
||||
72
src/main/scala/code/model/dataAccess/APermission.scala
Normal file
72
src/main/scala/code/model/dataAccess/APermission.scala
Normal file
@ -0,0 +1,72 @@
|
||||
package code.model.dataAccess
|
||||
|
||||
|
||||
|
||||
|
||||
/* APermission.scala */
|
||||
|
||||
/*
|
||||
* A permission that has three parts; domain, actions, and entities
|
||||
*/
|
||||
case class APermission(
|
||||
val domain: String,
|
||||
val actions: Set[String] = Set(APermission.wildcardToken),
|
||||
val entities: Set[String] = Set(APermission.wildcardToken))
|
||||
{
|
||||
def implies(p: APermission) = p match {
|
||||
case APermission(d, a, e) =>
|
||||
if (d == APermission.wildcardToken)
|
||||
true
|
||||
else if (d == this.domain) {
|
||||
if (a.contains(APermission.wildcardToken))
|
||||
true
|
||||
else if (this.actions.headOption.map(it => a.contains(it)).getOrElse(false))
|
||||
if (e.contains(APermission.wildcardToken))
|
||||
true
|
||||
else
|
||||
this.entities.headOption.map(it => e.contains(it)).getOrElse(false)
|
||||
else
|
||||
false
|
||||
}
|
||||
else
|
||||
false
|
||||
case _ => false
|
||||
}
|
||||
|
||||
def implies(ps: Set[APermission]): Boolean = ps.exists(this.implies)
|
||||
|
||||
override def toString = {
|
||||
domain+APermission.partDivider+actions.mkString(APermission.subpartDivider)+APermission.partDivider+entities.mkString(APermission.subpartDivider)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
object APermission {
|
||||
// Permission
|
||||
val permissionWilcardToken = "*"
|
||||
val permissionPartDivider = ":"
|
||||
val permissionSubpartDivider = ","
|
||||
val permissionCaseSensitive = true
|
||||
|
||||
lazy val wildcardToken = permissionWilcardToken
|
||||
lazy val partDivider = permissionPartDivider
|
||||
lazy val subpartDivider = permissionSubpartDivider
|
||||
|
||||
def apply(domain: String, actions: String): APermission =
|
||||
apply(domain, actions.split(APermission.subpartDivider).toSet)
|
||||
|
||||
def apply(domain: String, actions: String, entities: String): APermission =
|
||||
apply(domain, actions.split(APermission.subpartDivider).toSet, entities.split(APermission.subpartDivider).toSet)
|
||||
|
||||
def fromString(s: String): APermission = s.split(partDivider).toList match {
|
||||
case s :: Nil if (s == wildcardToken) => all
|
||||
case s :: Nil if (s.length == 0) => none
|
||||
case dom :: Nil => APermission(dom)
|
||||
case dom :: acts :: Nil => APermission(dom, acts)
|
||||
case dom :: acts :: ents :: Nil => APermission(dom, acts, ents)
|
||||
case _ => none
|
||||
}
|
||||
|
||||
lazy val all = APermission(wildcardToken)
|
||||
lazy val none = APermission("")
|
||||
}
|
||||
45
src/main/scala/code/model/dataAccess/MappedPermission.scala
Normal file
45
src/main/scala/code/model/dataAccess/MappedPermission.scala
Normal file
@ -0,0 +1,45 @@
|
||||
package code.model.dataAccess
|
||||
|
||||
import net.liftweb.mapper._
|
||||
|
||||
|
||||
/*
|
||||
* Simple record for storing permissions.
|
||||
*/
|
||||
object MappedPermission extends MappedPermission with LongKeyedMetaMapper[MappedPermission] {
|
||||
|
||||
def createUserPermission(uid: Long, aPerm: APermission) = {
|
||||
create.userId(uid).permission(aPerm.toString)
|
||||
}
|
||||
|
||||
def removeAllUserPermissions(uid: Long) = {
|
||||
MappedPermission.findAll(By(userId, uid)).map(_.delete_!)
|
||||
}
|
||||
|
||||
def toAPermission(perm: MappedPermission) = APermission.fromString(perm.permission.get)
|
||||
def fromAPermission(aPerm: APermission): MappedPermission = MappedPermission.create.permission(aPerm.toString)
|
||||
|
||||
def userPermissions(uid: Long): List[APermission] = MappedPermission.findAll(By(userId, uid)).map(toAPermission)
|
||||
|
||||
}
|
||||
|
||||
class MappedPermission extends LongKeyedMapper[MappedPermission] with IdPK {
|
||||
def getSingleton = MappedPermission
|
||||
|
||||
/**
|
||||
* This field is empty for permissions attached directly to the user
|
||||
*/
|
||||
object roleId extends MappedStringForeignKey(this, Role, 32) {
|
||||
def foreignMeta = Role
|
||||
}
|
||||
|
||||
/**
|
||||
* This field is empty for permissions attached to a role
|
||||
*/
|
||||
object userId extends MappedLong(this) {
|
||||
override def dbIndexed_? = true
|
||||
}
|
||||
|
||||
object permission extends MappedString(this, 1024)
|
||||
}
|
||||
|
||||
53
src/main/scala/code/model/dataAccess/MappedRole.scala
Normal file
53
src/main/scala/code/model/dataAccess/MappedRole.scala
Normal file
@ -0,0 +1,53 @@
|
||||
package code.model.dataAccess
|
||||
|
||||
import net.liftweb.common.{Box, Empty, Full}
|
||||
import net.liftweb.mapper.{MappedString, Mapper}
|
||||
|
||||
|
||||
abstract class MappedRole[T<:Mapper[T]](_fieldOwner: T) extends MappedString[T](_fieldOwner, 1024) {
|
||||
|
||||
import scala.xml._
|
||||
import net.liftweb.http.SHtml._
|
||||
|
||||
def buildDisplayList: List[(String, String)] = {
|
||||
Role.allRoles(Role.CAT_TEAM) map (r => (r.id.get, r.displayName))
|
||||
}
|
||||
|
||||
def chosenElement = names match {
|
||||
case name :: _ => Full(name)
|
||||
case _ => Empty
|
||||
}
|
||||
|
||||
override def _toForm: Box[Elem] =
|
||||
Full(selectObj[String](buildDisplayList, chosenElement, v => this.setRole(v)))
|
||||
|
||||
override def asHtml = firstRole.map(_.asHtml).openOr(Text(""))
|
||||
|
||||
def permissions: List[APermission] = names.flatMap(n => Role.find(n).map(_.permissions.allPerms).openOr(Nil))
|
||||
|
||||
def names: List[String] = {
|
||||
val current = if (get == null) "" else get
|
||||
current.split(",").toList
|
||||
}
|
||||
|
||||
def addRole(role: String*) = {
|
||||
val current = if (get == null) "" else get
|
||||
val add = (if (current.length() > 0) "," else "") + role.mkString(",")
|
||||
set(current + add)
|
||||
fieldOwner
|
||||
}
|
||||
|
||||
def setRole(role: String): T = {
|
||||
removeAll
|
||||
addRole(role)
|
||||
fieldOwner
|
||||
}
|
||||
|
||||
def setRole(role: Role): T = setRole(role.id.get)
|
||||
|
||||
def removeAll = {
|
||||
set(""); this
|
||||
}
|
||||
|
||||
def firstRole: Box[Role] = names.headOption.flatMap(name => Role.find(name))
|
||||
}
|
||||
@ -36,7 +36,7 @@ import net.liftweb.util.Mailer.{BCC, To, Subject, From}
|
||||
import net.liftweb.util._
|
||||
import net.liftweb.common._
|
||||
import scala.xml.{Text, NodeSeq}
|
||||
import net.liftweb.http.{SHtml, SessionVar, Templates, S}
|
||||
import net.liftweb.http._
|
||||
import net.liftweb.http.js.JsCmds.FocusOnLoad
|
||||
|
||||
import java.util.UUID
|
||||
@ -49,6 +49,45 @@ import code.sandbox.SandboxUserImport
|
||||
class OBPUser extends MegaProtoUser[OBPUser] with Logger {
|
||||
def getSingleton = OBPUser // what's the "meta" server
|
||||
|
||||
/* Roles START */
|
||||
|
||||
/*
|
||||
* String representing the User ID
|
||||
*/
|
||||
override def userIdAsString: String = id.toString
|
||||
|
||||
/*
|
||||
* A list of this user's permissions
|
||||
*/
|
||||
object userRoles extends MappedRole(this)
|
||||
lazy val authPermissions: Set[APermission] = (MappedPermission.userPermissions(id.get) ::: userRoles.permissions).toSet
|
||||
|
||||
/*
|
||||
* A list of this user's roles
|
||||
*/
|
||||
lazy val authRoles: Set[String] = userRoles.names.toSet
|
||||
|
||||
// Request var that holds the User instance
|
||||
//private object curUserId extends SessionVar[Box[String]](Empty)
|
||||
//def currentUserId: Box[String] = curUserId.is
|
||||
//private object curUser extends RequestVar[Box[OBPUser]](currentUserId.flatMap(findByStringId))
|
||||
//with CleanRequestVarOnSessionTransition {
|
||||
// override lazy val __nameSalt = Helpers.nextFuncName
|
||||
//}
|
||||
//def currentUser: Box[OBPUser] = curUser.is
|
||||
|
||||
import net.liftweb.mapper._
|
||||
|
||||
def findByStringId(strId: String): Box[OBPUser] =
|
||||
try {
|
||||
OBPUser.find(By(id, strId.toLong))
|
||||
} catch {
|
||||
case e: Exception => Empty
|
||||
}
|
||||
|
||||
/* Roles END */
|
||||
|
||||
|
||||
object user extends MappedLongForeignKey(this, APIUser)
|
||||
|
||||
/**
|
||||
@ -126,8 +165,8 @@ import net.liftweb.util.Helpers._
|
||||
|
||||
override def screenWrap = Full(<lift:surround with="default" at="content"><lift:bind /></lift:surround>)
|
||||
// define the order fields will appear in forms and output
|
||||
override def fieldOrder = List(id, firstName, lastName, email, password, provider)
|
||||
override def signupFields = List(firstName, lastName, email, password)
|
||||
//override def fieldOrder = List(id, firstName, lastName, email, password, provider)
|
||||
//override def signupFields = List(firstName, lastName, email, password)
|
||||
|
||||
// comment this line out to require email validations
|
||||
override def skipEmailValidation = true
|
||||
@ -260,26 +299,40 @@ import net.liftweb.util.Helpers._
|
||||
val producer: KafkaProducer = new KafkaProducer()
|
||||
// Send request to Kafka, marked with reqId
|
||||
// so we can fetch the corresponding response
|
||||
val argList = Map( "email" -> username,
|
||||
"password" -> password )
|
||||
producer.send(reqId, "getUser", argList, "1")
|
||||
val isTemenos = username.endsWith("@TEMENOS")
|
||||
|
||||
val argList = if (isTemenos) {
|
||||
Map("username" -> username.replaceAll("@TEMENOS", ""),
|
||||
"password" -> password)
|
||||
} else {
|
||||
Map("email" -> username,
|
||||
"password" -> password)
|
||||
}
|
||||
|
||||
producer.send(reqId, "getUser", argList, "1")
|
||||
// Request sent, now we wait for response with the same reqId
|
||||
val consumer = new KafkaConsumer()
|
||||
// Create entry only for the first item on returned list
|
||||
val r = consumer.getResponse(reqId).head
|
||||
|
||||
println("-------------------> " + r)
|
||||
// For testing without Kafka
|
||||
//val r = Map("email"->"test@email.me","password"->"secret","display_name"->"DN")
|
||||
|
||||
var recDisplayName = r.getOrElse("display_name", "Not Found")
|
||||
var recEmail = r.getOrElse("email", "Not Found")
|
||||
val recDisplayName = if (isTemenos) r.getOrElse("Name", "Not Found")
|
||||
else r.getOrElse("display_name", "Not Found")
|
||||
|
||||
val recEmail = if (isTemenos) r.getOrElse("Id", "Not Found") + "@TEMENOS"
|
||||
else r.getOrElse("email", "Not Found")
|
||||
|
||||
val recRoles = if (isTemenos) List(r.getOrElse("UserType", "Not Found"))
|
||||
else r.getOrElse("roles", List()).asInstanceOf[List[String]]
|
||||
|
||||
if (recEmail == username && recEmail != "Not Found") {
|
||||
if (recDisplayName == "")
|
||||
Full(new SandboxUserImport( username, password, recEmail))
|
||||
Full(new SandboxUserImport( username, password, recRoles, recEmail))
|
||||
else
|
||||
Full(new SandboxUserImport( username, password, recDisplayName))
|
||||
Full(new SandboxUserImport( username, password, recRoles, recDisplayName))
|
||||
} else {
|
||||
// If empty result from Kafka return empty data
|
||||
Empty
|
||||
@ -308,10 +361,13 @@ import net.liftweb.util.Helpers._
|
||||
case _ => 0
|
||||
}
|
||||
}
|
||||
def hasRole(role: String): Boolean = currentUser.map(u => hasRole(u, role)).openOr(false)
|
||||
|
||||
def hasRole(user: OBPUser, role: String): Boolean = user.authRoles.exists(_ == role)
|
||||
|
||||
def getExternalUser(username: String, password: String):Box[OBPUser] = {
|
||||
getUserViaKafka(username, password) match {
|
||||
case Full(SandboxUserImport(extEmail, extPassword, extDisplayName)) => {
|
||||
case Full(SandboxUserImport(extEmail, extPassword, extRoles, extDisplayName)) => {
|
||||
val preLoginState = capturePreLoginState()
|
||||
info("external user authenticated. login redir: " + loginRedirect.get)
|
||||
val redir = loginRedirect.get match {
|
||||
@ -346,6 +402,16 @@ import net.liftweb.util.Helpers._
|
||||
.password(dummyPassword)
|
||||
.provider(extProvider)
|
||||
.validated(true)
|
||||
|
||||
for ( r <- extRoles ) {
|
||||
info("adding role (" + r + ") to new user")
|
||||
if (Role.find(r).openOr("") != "") {
|
||||
newUser.userRoles.addRole(r).saveMe
|
||||
info("role (" + r + ") added")
|
||||
}
|
||||
else
|
||||
info("role (" + r + ") not defined")
|
||||
}
|
||||
// Save the user in order to be able to log in
|
||||
newUser.save()
|
||||
// Return created user
|
||||
@ -398,6 +464,8 @@ import net.liftweb.util.Helpers._
|
||||
val user = getExternalUser(S.param("username").get, S.param("password").get)
|
||||
|
||||
if (!user.isEmpty) {
|
||||
println ("-----------------> admin: " + hasRole(user.get, "admin") )
|
||||
println ("-----------------> test: " + hasRole(user.get, "test") )
|
||||
logUserIn(user.get, () => {
|
||||
S.notice(S.?("logged.in"))
|
||||
|
||||
@ -460,3 +528,6 @@ import net.liftweb.util.Helpers._
|
||||
innerSignup
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
74
src/main/scala/code/model/dataAccess/Role.scala
Normal file
74
src/main/scala/code/model/dataAccess/Role.scala
Normal file
@ -0,0 +1,74 @@
|
||||
package code.model.dataAccess
|
||||
|
||||
import net.liftweb.common.Loggable
|
||||
import net.liftweb.http.S
|
||||
import net.liftweb.mapper._
|
||||
|
||||
/*
|
||||
* Simple record for storing roles. Role name is the PK.
|
||||
*/
|
||||
object Role extends Role with KeyedMetaMapper[String, Role] with Loggable {
|
||||
|
||||
val R_SUPERUSER = "superuser"
|
||||
val R_USER = "user"
|
||||
val R_TEAM_OWNER = "owner"
|
||||
val R_TEAM_MEMBER = "member"
|
||||
val R_TEAM_WATCHER = "watcher"
|
||||
|
||||
val CAT_SYSTEM = "system"
|
||||
val CAT_TEAM = "team"
|
||||
|
||||
override def dbTableName = "roles"
|
||||
|
||||
def findOrCreate(roleId: String): Role = find(roleId).openOr(create.id(roleId))
|
||||
def findOrCreateAndSave(roleId: String, category: String, perms: MappedPermission*): Role = {
|
||||
find(roleId).openOr {
|
||||
logger.info("Create Role %s for category %s".format(roleId, category))
|
||||
val r = create.id(roleId).category(category)
|
||||
r.permissions.appendAll(perms)
|
||||
r.saveMe
|
||||
}
|
||||
}
|
||||
|
||||
def allRoles(cat: String) = Role.findAll(By(category, cat))
|
||||
|
||||
lazy val TeamOwner = Role.find(R_TEAM_OWNER).openOrThrowException("No Owner Role found")
|
||||
lazy val TeamMember = Role.find(R_TEAM_MEMBER).openOrThrowException("No Member Role found")
|
||||
lazy val TeamWatcher = Role.find(R_TEAM_WATCHER).openOrThrowException("No Watcher Role found")
|
||||
|
||||
}
|
||||
|
||||
class Role extends KeyedMapper[String, Role] with OneToMany[String,Role] {
|
||||
def getSingleton = Role
|
||||
def primaryKeyField = id
|
||||
object id extends MappedStringIndex(this, 32) {
|
||||
override def writePermission_? = true
|
||||
override def dbAutogenerated_? = false
|
||||
override def dbNotNull_? = true
|
||||
override def dbIndexed_? = true
|
||||
override def displayName = "Name"
|
||||
}
|
||||
|
||||
object category extends MappedString(this, 50)
|
||||
|
||||
object permissions extends MappedOneToMany(MappedPermission, MappedPermission.roleId) {
|
||||
def allPerms: List[APermission] = all.map(MappedPermission.toAPermission)
|
||||
}
|
||||
|
||||
override def equals(other: Any): Boolean = other match {
|
||||
case r: Role => r.id.get == this.id.get
|
||||
case _ => false
|
||||
}
|
||||
|
||||
def displayName() = S ? ("userClientConnection.role."+id.get)
|
||||
|
||||
override def asHtml = {
|
||||
val cls = "label" + (id.get match {
|
||||
case Role.R_TEAM_OWNER => " label-important"
|
||||
case Role.R_TEAM_MEMBER => " label-info"
|
||||
case _ => ""
|
||||
})
|
||||
<span class={cls}>{displayName}</span>
|
||||
}
|
||||
|
||||
}
|
||||
@ -597,6 +597,7 @@ case class SandboxLocationImport(
|
||||
case class SandboxUserImport(
|
||||
email : String,
|
||||
password : String,
|
||||
roles: List[String],
|
||||
display_name : String)
|
||||
|
||||
case class SandboxAccountImport(
|
||||
|
||||
@ -457,8 +457,8 @@ class SandboxDataLoadingTest extends FlatSpec with SendServerRequests with Shoul
|
||||
val standardProducts = product1AtBank1 :: product2AtBank1 :: Nil
|
||||
|
||||
|
||||
val user1 = SandboxUserImport(email = "user1@example.com", password = "qwerty", display_name = "User 1")
|
||||
val user2 = SandboxUserImport(email = "user2@example.com", password = "qwerty", display_name = "User 2")
|
||||
val user1 = SandboxUserImport(email = "user1@example.com", password = "qwerty", display_name = "User 1", roles = List())
|
||||
val user2 = SandboxUserImport(email = "user2@example.com", password = "qwerty", display_name = "User 2", roles = List())
|
||||
|
||||
val standardUsers = user1 :: user2 :: Nil
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user