#55 Can be assign a role to members of my Workgroup

Remove Owner on Workgroup (use role MASTER instead)
"find_citizen_by_id" not return user anymore, use "find_citizen_by_id_with_user" instead
This commit is contained in:
2020-06-01 13:44:25 +02:00
parent 8ff6fcc970
commit 7874f5cec4
49 changed files with 331 additions and 217 deletions

View File

@@ -130,7 +130,7 @@ fun Application.module(env: Env = PROD) {
decode { values, _ ->
val id = values.singleOrNull()?.let { UUID.fromString(it) }
?: throw InternalError("Cannot convert $values to UUID")
get<RepositoryCitizen>().findById(id, true) ?: throw NotFoundException("Citizen $values not found")
get<RepositoryCitizen>().findById(id) ?: throw NotFoundException("Citizen $values not found")
}
}

View File

@@ -21,7 +21,12 @@ class Citizen(
) : CitizenFull,
CitizenBasic(id, name, email, birthday, voteAnonymous, followAnonymous, user),
EntityCreatedAt by EntityCreatedAtImp() {
var workgroups: List<WorkgroupSimple<CitizenRef>> = emptyList()
var workgroups: List<WorkgroupAndRoles> = emptyList()
class WorkgroupAndRoles(
val roles: List<String>,
val workgroup: WorkgroupSimple<CitizenRef>
)
}
open class CitizenBasic(

View File

@@ -1,5 +1,8 @@
package fr.dcproject.entity
import fr.dcproject.entity.WorkgroupWithMembersI.Member
import fr.dcproject.entity.WorkgroupWithMembersI.Member.Role
import fr.postgresjson.entity.EntityI
import fr.postgresjson.entity.immutable.*
import fr.postgresjson.entity.mutable.EntityDeletedAt
import fr.postgresjson.entity.mutable.EntityDeletedAtImp
@@ -11,9 +14,8 @@ class Workgroup(
description: String,
logo: String? = null,
anonymous: Boolean = true,
owner: CitizenBasic,
createdBy: CitizenBasic,
override var members: List<CitizenBasic> = emptyList()
override var members: List<Member<CitizenBasic>> = emptyList()
) : WorkgroupWithAuthI<CitizenBasic>,
WorkgroupSimple<CitizenBasic>(
id,
@@ -21,7 +23,6 @@ class Workgroup(
description,
logo,
anonymous,
owner,
createdBy
),
EntityCreatedAt by EntityCreatedAtImp(),
@@ -33,7 +34,6 @@ open class WorkgroupSimple<Z : CitizenRef>(
var description: String,
var logo: String? = null,
var anonymous: Boolean = true,
var owner: Z,
createdBy: Z
) : WorkgroupRef(id),
EntityCreatedBy<Z> by EntityCreatedByImp(createdBy),
@@ -45,19 +45,51 @@ open class WorkgroupRef(
interface WorkgroupWithAuthI<Z : CitizenWithUserI> : WorkgroupWithMembersI<Z>, EntityCreatedBy<Z>, EntityDeletedAt {
val anonymous: Boolean
val owner: Z
fun isMember(user: UserI): Boolean =
members.map { it.user.id }.contains(user.id) || owner.user.id == user.id
fun isMember(user: UserI): Boolean = members.isMember(user)
fun isMember(citizen: CitizenWithUserI): Boolean = members.isMember(citizen)
fun isMember(citizen: CitizenWithUserI): Boolean =
isMember(citizen.user)
fun hasRole(expectedRole: Role, user: UserI): Boolean = members.hasRole(expectedRole, user)
fun hasRole(expectedRole: Role, citizen: CitizenI): Boolean = members.hasRole(expectedRole, citizen)
fun getRoles(user: UserI): List<Role> = members.getRoles(user)
fun getRoles(citizen: CitizenI): List<Role> = members.getRoles(citizen)
}
interface WorkgroupWithMembersI<Z : CitizenI> : WorkgroupI {
var members: List<Z>
var members: List<Member<Z>>
class Member<C : CitizenI> (
val citizen: C,
val roles: List<Role> = emptyList()
) : EntityI {
enum class Role {
MASTER,
MANAGER,
EDITOR,
REPORTER
}
}
}
fun List<CitizenI>.asCitizen(citizen: CitizenI): Boolean = this.map { it.id }.contains(citizen.id)
fun List<CitizenI>.hasCitizen(citizen: CitizenI): Boolean = this.map { it.id }.contains(citizen.id)
fun <Z : CitizenWithUserI> List<Member<Z>>.isMember(user: UserI): Boolean =
map { it.citizen.user.id }.contains(user.id)
fun <Z : CitizenI> List<Member<Z>>.isMember(citizen: CitizenI): Boolean =
map { it.citizen.id }.contains(citizen.id)
fun <Z : CitizenI> List<Member<Z>>.hasRole(expectedRole: Role, citizen: CitizenI): Boolean =
any { member -> member.citizen.id == citizen.id && member.roles.any { it == expectedRole } }
fun <Z : CitizenWithUserI> List<Member<Z>>.hasRole(expectedRole: Role, user: UserI): Boolean =
any { member -> member.citizen.user.id == user.id && member.roles.any { it == expectedRole } }
fun <Z : CitizenWithUserI> List<Member<Z>>.getRoles(user: UserI): List<Role> =
firstOrNull { it.citizen.user.id == user.id }?.roles ?: emptyList()
fun <Z : CitizenWithUserI> List<Member<Z>>.getRoles(citizen: CitizenI): List<Role> =
firstOrNull { it.citizen.id == citizen.id }?.roles ?: emptyList()
interface WorkgroupI : UuidEntityI

View File

@@ -12,29 +12,21 @@ import java.util.*
import fr.dcproject.entity.Citizen as CitizenEntity
class Citizen(override var requester: Requester) : RepositoryI {
fun findById(id: UUID, withUser: Boolean = false): CitizenEntity? {
return requester
.getFunction(if (withUser) "find_citizen_by_id_with_user" else "find_citizen_by_id")
.selectOne("id" to id)
}
fun findById(id: UUID): CitizenEntity? = requester
.getFunction("find_citizen_by_id_with_user_and_workgroups")
.selectOne("id" to id)
fun findByUser(user: UserI): CitizenEntity? {
return requester
.getFunction("find_citizen_by_user_id")
.selectOne("user_id" to user.id)
}
fun findByUser(user: UserI): CitizenEntity? = requester
.getFunction("find_citizen_by_user_id")
.selectOne("user_id" to user.id)
fun findByUsername(unsername: String): CitizenEntity? {
return requester
.getFunction("find_citizen_by_username")
.selectOne("username" to unsername)
}
fun findByUsername(unsername: String): CitizenEntity? = requester
.getFunction("find_citizen_by_username")
.selectOne("username" to unsername)
fun findByEmail(email: String): CitizenEntity? {
return requester
.getFunction("find_citizen_by_email")
.selectOne("email" to email)
}
fun findByEmail(email: String): CitizenEntity? = requester
.getFunction("find_citizen_by_email")
.selectOne("email" to email)
fun find(
page: Int = 1,
@@ -42,8 +34,7 @@ class Citizen(override var requester: Requester) : RepositoryI {
sort: String? = null,
direction: Direction? = null,
search: String? = null
): Paginated<CitizenBasic> {
return requester
): Paginated<CitizenBasic> = requester
.getFunction("find_citizens")
.select(
page, limit,
@@ -51,17 +42,12 @@ class Citizen(override var requester: Requester) : RepositoryI {
"direction" to direction,
"search" to search
)
}
fun upsert(citizen: CitizenFull): CitizenEntity? {
return requester
fun upsert(citizen: CitizenFull): CitizenEntity? = requester
.getFunction("upsert_citizen")
.selectOne("resource" to citizen)
}
fun insertWithUser(citizen: CitizenFull): CitizenEntity? {
return requester
fun insertWithUser(citizen: CitizenFull): CitizenEntity? = requester
.getFunction("insert_citizen_with_user")
.selectOne("resource" to citizen)
}
}

View File

@@ -1,6 +1,7 @@
package fr.dcproject.repository
import fr.dcproject.entity.*
import fr.dcproject.entity.WorkgroupWithMembersI.Member
import fr.postgresjson.connexion.Paginated
import fr.postgresjson.connexion.Requester
import fr.postgresjson.entity.Parameter
@@ -44,36 +45,43 @@ class Workgroup(override var requester: Requester) : RepositoryI {
.getFunction("delete_workgroup")
.perform("id" to workgroup.id)
fun addMember(workgroup: WorkgroupI, member: CitizenI): List<CitizenBasic> =
addMembers(workgroup, listOf(member))
fun addMember(workgroup: WorkgroupI, member: Member<CitizenI>): Member<CitizenBasic>? =
addMember(workgroup, member.citizen, member.roles)
fun addMembers(workgroup: WorkgroupI, members: List<CitizenI>): List<CitizenBasic> = requester
fun addMember(workgroup: WorkgroupI, citizen: CitizenI, roles: List<Member.Role>): Member<CitizenBasic>? = requester
.getFunction("add_workgroup_member")
.selectOne(
"id" to workgroup.id,
"members" to Member(citizen, roles).serialize()
)
fun <Z : CitizenI> addMembers(workgroup: WorkgroupI, members: List<Member<Z>>): List<Member<CitizenBasic>> = requester
.getFunction("add_workgroup_members")
.select(
"id" to workgroup.id,
"resource" to members.serialize()
"members" to members.serialize()
)
fun removeMember(workgroup: WorkgroupI, memberToDelete: CitizenI): List<CitizenBasic> =
fun <Z : CitizenI> removeMember(workgroup: WorkgroupI, memberToDelete: Member<Z>): List<Member<CitizenBasic>> =
removeMembers(workgroup, listOf(memberToDelete))
fun removeMembers(workgroup: WorkgroupI, membersToDelete: List<CitizenI>): List<CitizenBasic> = requester
fun <Z : CitizenI> removeMembers(workgroup: WorkgroupI, membersToDelete: List<Member<Z>>): List<Member<CitizenBasic>> = requester
.getFunction("remove_workgroup_members")
.select(
"id" to workgroup.id,
"resource" to membersToDelete
"members" to membersToDelete
)
fun updateMembers(workgroup: WorkgroupI, members: List<CitizenI>): List<CitizenBasic> = requester
fun <Z : CitizenI> updateMembers(workgroup: WorkgroupI, members: List<Member<Z>>): List<Member<CitizenBasic>> = requester
.getFunction("update_workgroup_members")
.select(
"id" to workgroup.id,
"resource" to members
"members" to members
)
fun <W : WorkgroupWithMembersI<CitizenI>> updateMembers(workgroup: W): W {
fun <W : WorkgroupWithMembersI<Z>, Z : CitizenI> updateMembers(workgroup: W): W {
updateMembers(workgroup, workgroup.members).let {
workgroup.members = it
workgroup.members = it as List<Member<Z>>
}
return workgroup

View File

@@ -3,6 +3,8 @@ package fr.dcproject.routes
import fr.dcproject.citizen
import fr.dcproject.entity.CitizenRef
import fr.dcproject.entity.WorkgroupSimple
import fr.dcproject.entity.WorkgroupWithMembersI.Member
import fr.dcproject.entity.WorkgroupWithMembersI.Member.Role
import fr.dcproject.repository.Workgroup.Filter
import fr.dcproject.security.voter.WorkgroupVoter.Action.VIEW
import fr.dcproject.security.voter.WorkgroupVoter.Action.CREATE
@@ -54,8 +56,7 @@ object WorkgroupsPaths {
val name: String,
val description: String,
val logo: String?,
val anonymous: Boolean?,
val owner: UUID?
val anonymous: Boolean?
)
suspend fun getNewWorkgroup(call: ApplicationCall): WorkgroupSimple<CitizenRef> = call.receive<Body>().run {
@@ -65,7 +66,6 @@ object WorkgroupsPaths {
description,
logo,
anonymous ?: true,
owner?.let { CitizenRef(it) } ?: call.citizen,
call.citizen
)
}
@@ -77,8 +77,7 @@ object WorkgroupsPaths {
val name: String?,
val description: String?,
val logo: String?,
val anonymous: Boolean?,
val owner: UUID?
val anonymous: Boolean?
)
suspend fun updateWorkgroup(call: ApplicationCall): Unit = call.receive<Body>().run {
@@ -98,13 +97,19 @@ object WorkgroupsMembersPaths {
@Location("/workgroups/{workgroup}/members")
class WorkgroupsMembersRequest(val workgroup: WorkgroupEntity) {
class Body : MutableList<Body.Item> by mutableListOf() {
class Item(id: String) {
val id = id.toUUID()
class Item(id: String, roles: List<String>) {
val citizen = CitizenRef(id.toUUID())
val roles: List<Role> = roles.map {
Role.valueOf(it)
}
}
}
suspend fun getMembers(call: ApplicationCall): List<CitizenRef> = call.receive<Body>().map {
CitizenRef(it.id)
suspend fun getMembers(call: ApplicationCall): List<Member<CitizenRef>> = call.receive<Body>().map {
Member(
citizen = it.citizen,
roles = it.roles
)
}
}
}

View File

@@ -1,7 +1,7 @@
package fr.dcproject.security.voter
import fr.dcproject.citizenOrNull
import fr.dcproject.entity.*
import fr.dcproject.entity.WorkgroupWithMembersI.Member.Role
import fr.dcproject.user
import fr.ktorVoter.ActionI
import fr.ktorVoter.Vote
@@ -46,11 +46,11 @@ class WorkgroupVoter : Voter {
}
if (subject is WorkgroupWithAuthI<*>) {
if (action == Action.DELETE && user is UserI && subject.owner.user.id == user.id) {
if (action == Action.DELETE && user is UserI && subject.hasRole(Role.MASTER, user)) {
return Vote.GRANTED
}
if (action == Action.UPDATE && user is UserI && subject.owner.user.id == user.id) {
if (action == Action.UPDATE && user is UserI && subject.hasRole(Role.MASTER, user)) {
return Vote.GRANTED
}
@@ -61,32 +61,29 @@ class WorkgroupVoter : Voter {
}
if (action == ActionMembers.ADD) {
val citizen = call.citizenOrNull
// TODO create ROLES
return Vote.isGranted {
citizen != null &&
subject is WorkgroupWithMembersI<*> &&
subject.members.asCitizen(citizen)
user is UserI &&
subject is WorkgroupWithAuthI<*> &&
subject.hasRole(Role.MASTER, user)
}
}
if (action == ActionMembers.UPDATE) {
val citizen = call.citizenOrNull
// TODO create ROLES
return Vote.isGranted {
citizen != null &&
subject is WorkgroupWithMembersI<*> &&
subject.members.asCitizen(citizen)
user is UserI &&
subject is WorkgroupWithAuthI<*> &&
subject.hasRole(Role.MASTER, user)
}
}
if (action == ActionMembers.REMOVE) {
val citizen = call.citizenOrNull
// TODO create ROLES
return Vote.isGranted {
citizen != null &&
subject is WorkgroupWithMembersI<*> &&
subject.members.asCitizen(citizen)
user is UserI &&
subject is WorkgroupWithAuthI<*> &&
subject.hasRole(Role.MASTER, user)
}
}