Added preliminary support for user roles

This commit is contained in:
Petar Bozin 2016-03-20 22:17:56 +01:00
parent 40df926919
commit 04c9032aef
8 changed files with 333 additions and 13 deletions

View File

@ -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,

View 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("")
}

View 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)
}

View 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))
}

View File

@ -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
}
}

View 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>
}
}

View File

@ -597,6 +597,7 @@ case class SandboxLocationImport(
case class SandboxUserImport(
email : String,
password : String,
roles: List[String],
display_name : String)
case class SandboxAccountImport(

View File

@ -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