Add route put/delete
This commit is contained in:
@@ -338,7 +338,7 @@ fun Application.module(env: Env = PROD) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
exception<NotFoundException> { e ->
|
exception<NotFoundException> { e ->
|
||||||
call.respond(HttpStatusCode.BadRequest, e.message!!)
|
call.respond(HttpStatusCode.NotFound, e.message!!)
|
||||||
}
|
}
|
||||||
exception<ForbiddenException> {
|
exception<ForbiddenException> {
|
||||||
call.respond(HttpStatusCode.Forbidden)
|
call.respond(HttpStatusCode.Forbidden)
|
||||||
|
|||||||
@@ -58,4 +58,6 @@ interface WorkgroupWithMembersI<Z : CitizenI> : WorkgroupI {
|
|||||||
var members: List<Z>
|
var members: List<Z>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun List<CitizenI>.asCitizen(citizen: CitizenI): Boolean = this.map { it.id }.contains(citizen.id)
|
||||||
|
|
||||||
interface WorkgroupI : UuidEntityI
|
interface WorkgroupI : UuidEntityI
|
||||||
@@ -9,6 +9,9 @@ import fr.dcproject.repository.Workgroup.Filter
|
|||||||
import fr.dcproject.security.voter.WorkgroupVoter.Action.VIEW
|
import fr.dcproject.security.voter.WorkgroupVoter.Action.VIEW
|
||||||
import fr.dcproject.security.voter.WorkgroupVoter.Action.CREATE
|
import fr.dcproject.security.voter.WorkgroupVoter.Action.CREATE
|
||||||
import fr.dcproject.security.voter.WorkgroupVoter.Action.UPDATE
|
import fr.dcproject.security.voter.WorkgroupVoter.Action.UPDATE
|
||||||
|
import fr.dcproject.security.voter.WorkgroupVoter.ActionMembers.ADD as ADD_MEMBERS
|
||||||
|
import fr.dcproject.security.voter.WorkgroupVoter.ActionMembers.UPDATE as UPDATE_MEMBERS
|
||||||
|
import fr.dcproject.security.voter.WorkgroupVoter.ActionMembers.REMOVE as REMOVE_MEMBERS
|
||||||
import fr.dcproject.security.voter.assertCan
|
import fr.dcproject.security.voter.assertCan
|
||||||
import fr.dcproject.utils.toUUID
|
import fr.dcproject.utils.toUUID
|
||||||
import fr.postgresjson.repository.RepositoryI
|
import fr.postgresjson.repository.RepositoryI
|
||||||
@@ -48,14 +51,14 @@ object WorkgroupsPaths {
|
|||||||
class WorkgroupRequest(val workgroup: WorkgroupEntity)
|
class WorkgroupRequest(val workgroup: WorkgroupEntity)
|
||||||
|
|
||||||
@Location("/workgroups")
|
@Location("/workgroups")
|
||||||
class PostWorkgroupRequest : RequestBuilder<WorkgroupSimple<CitizenRef>> {
|
open class PostWorkgroupRequest : RequestBuilder<WorkgroupSimple<CitizenRef>> {
|
||||||
class Content(
|
class Content(
|
||||||
val id: UUID?,
|
val id: UUID?,
|
||||||
val name: String,
|
val name: String,
|
||||||
val description: String,
|
val description: String,
|
||||||
val logo: String?,
|
val logo: String?,
|
||||||
val anonymous: Boolean?,
|
val anonymous: Boolean?,
|
||||||
val owner: CitizenRef?
|
val owner: UUID?
|
||||||
) : KoinComponent {
|
) : KoinComponent {
|
||||||
fun create(creator: CitizenRef): WorkgroupSimple<CitizenRef> {
|
fun create(creator: CitizenRef): WorkgroupSimple<CitizenRef> {
|
||||||
return WorkgroupSimple(
|
return WorkgroupSimple(
|
||||||
@@ -64,7 +67,7 @@ object WorkgroupsPaths {
|
|||||||
description,
|
description,
|
||||||
logo,
|
logo,
|
||||||
anonymous ?: true,
|
anonymous ?: true,
|
||||||
owner ?: creator,
|
owner?.let { CitizenRef(it) } ?: creator,
|
||||||
creator
|
creator
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -74,11 +77,38 @@ object WorkgroupsPaths {
|
|||||||
return call.receive<Content>().create(call.citizen)
|
return call.receive<Content>().create(call.citizen)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Location("/workgroups/{workgroup}")
|
||||||
|
class PutWorkgroupRequest(val workgroup: WorkgroupEntity) : RequestBuilder<WorkgroupEntity> {
|
||||||
|
class Content(
|
||||||
|
val name: String?,
|
||||||
|
val description: String?,
|
||||||
|
val logo: String?,
|
||||||
|
val anonymous: Boolean?,
|
||||||
|
val owner: UUID?
|
||||||
|
) : KoinComponent {
|
||||||
|
fun update(workgroup: WorkgroupEntity): WorkgroupEntity {
|
||||||
|
name?.let { workgroup.name = it }
|
||||||
|
description?.let { workgroup.description = it }
|
||||||
|
logo?.let { workgroup.logo = it }
|
||||||
|
anonymous?.let { workgroup.anonymous = it }
|
||||||
|
|
||||||
|
return workgroup
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun getContent(call: ApplicationCall): WorkgroupEntity {
|
||||||
|
return call.receive<Content>().update(workgroup)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Location("/workgroups/{workgroup}")
|
||||||
|
class DeleteWorkgroupRequest(val workgroup: WorkgroupEntity)
|
||||||
}
|
}
|
||||||
|
|
||||||
@KtorExperimentalLocationsAPI
|
@KtorExperimentalLocationsAPI
|
||||||
object WorkgroupsMembersPaths {
|
object WorkgroupsMembersPaths {
|
||||||
@Location("/workgroups/members/{workgroup}")
|
@Location("/workgroups/{workgroup}/members")
|
||||||
class WorkgroupsMembersRequest(val workgroup: WorkgroupEntity) : RequestBuilder<List<CitizenRef>> {
|
class WorkgroupsMembersRequest(val workgroup: WorkgroupEntity) : RequestBuilder<List<CitizenRef>> {
|
||||||
class Content : MutableList<Content.Item> by mutableListOf() {
|
class Content : MutableList<Content.Item> by mutableListOf() {
|
||||||
class Item(val id: String)
|
class Item(val id: String)
|
||||||
@@ -117,14 +147,30 @@ fun Route.workgroup(repo: WorkgroupRepository) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
put<WorkgroupsPaths.PutWorkgroupRequest> {
|
||||||
|
call.getContent(it)
|
||||||
|
.let { workgroup ->
|
||||||
|
assertCan(UPDATE, workgroup)
|
||||||
|
repo.upsert(workgroup as WorkgroupSimple<CitizenRef>)
|
||||||
|
}.let {
|
||||||
|
call.respond(HttpStatusCode.OK, it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
delete<WorkgroupsPaths.DeleteWorkgroupRequest> {
|
||||||
|
assertCan(UPDATE, it.workgroup)
|
||||||
|
repo.delete(it.workgroup)
|
||||||
|
call.respond(HttpStatusCode.NoContent, it)
|
||||||
|
}
|
||||||
|
|
||||||
/* Add members to workgroup */
|
/* Add members to workgroup */
|
||||||
post<WorkgroupsMembersPaths.WorkgroupsMembersRequest> {
|
post<WorkgroupsMembersPaths.WorkgroupsMembersRequest> {
|
||||||
call.getContent(it)
|
call.getContent(it)
|
||||||
.let { members ->
|
.let { members ->
|
||||||
assertCan(UPDATE, it.workgroup)
|
assertCan(ADD_MEMBERS, it.workgroup)
|
||||||
repo.addMembers(it.workgroup, members)
|
repo.addMembers(it.workgroup, members)
|
||||||
}.let {
|
}.let {
|
||||||
call.respond(HttpStatusCode.OK, it)
|
call.respond(HttpStatusCode.Created, it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -132,7 +178,7 @@ fun Route.workgroup(repo: WorkgroupRepository) {
|
|||||||
delete<WorkgroupsMembersPaths.WorkgroupsMembersRequest> {
|
delete<WorkgroupsMembersPaths.WorkgroupsMembersRequest> {
|
||||||
call.getContent(it)
|
call.getContent(it)
|
||||||
.let { members ->
|
.let { members ->
|
||||||
assertCan(UPDATE, it.workgroup)
|
assertCan(REMOVE_MEMBERS, it.workgroup)
|
||||||
repo.removeMembers(it.workgroup, members)
|
repo.removeMembers(it.workgroup, members)
|
||||||
}.let {
|
}.let {
|
||||||
call.respond(HttpStatusCode.OK, it)
|
call.respond(HttpStatusCode.OK, it)
|
||||||
@@ -143,7 +189,7 @@ fun Route.workgroup(repo: WorkgroupRepository) {
|
|||||||
put<WorkgroupsMembersPaths.WorkgroupsMembersRequest> {
|
put<WorkgroupsMembersPaths.WorkgroupsMembersRequest> {
|
||||||
call.getContent(it)
|
call.getContent(it)
|
||||||
.let { members ->
|
.let { members ->
|
||||||
assertCan(UPDATE, it.workgroup)
|
assertCan(UPDATE_MEMBERS, it.workgroup)
|
||||||
repo.updateMembers(it.workgroup, members)
|
repo.updateMembers(it.workgroup, members)
|
||||||
}.let {
|
}.let {
|
||||||
call.respond(HttpStatusCode.OK, it)
|
call.respond(HttpStatusCode.OK, it)
|
||||||
|
|||||||
@@ -33,7 +33,13 @@ fun List<Voter>.can(action: ActionI, call: ApplicationCall, subject: Any? = null
|
|||||||
enum class Vote {
|
enum class Vote {
|
||||||
GRANTED,
|
GRANTED,
|
||||||
ABSTAIN,
|
ABSTAIN,
|
||||||
DENIED
|
DENIED;
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun isGranted(lambda: () -> Boolean): Vote {
|
||||||
|
return if (lambda()) GRANTED else DENIED
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private val votersAttributeKey = AttributeKey<List<Voter>>("voters")
|
private val votersAttributeKey = AttributeKey<List<Voter>>("voters")
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
package fr.dcproject.security.voter
|
package fr.dcproject.security.voter
|
||||||
|
|
||||||
import fr.dcproject.entity.UserI
|
import fr.dcproject.citizenOrNull
|
||||||
import fr.dcproject.entity.WorkgroupI
|
import fr.dcproject.entity.*
|
||||||
import fr.dcproject.entity.WorkgroupWithAuthI
|
|
||||||
import io.ktor.application.ApplicationCall
|
import io.ktor.application.ApplicationCall
|
||||||
|
|
||||||
class WorkgroupVoter : Voter {
|
class WorkgroupVoter : Voter {
|
||||||
@@ -10,11 +9,18 @@ class WorkgroupVoter : Voter {
|
|||||||
CREATE,
|
CREATE,
|
||||||
UPDATE,
|
UPDATE,
|
||||||
VIEW,
|
VIEW,
|
||||||
DELETE
|
DELETE,
|
||||||
|
}
|
||||||
|
|
||||||
|
enum class ActionMembers : ActionI {
|
||||||
|
ADD,
|
||||||
|
UPDATE,
|
||||||
|
VIEW,
|
||||||
|
REMOVE,
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun supports(action: ActionI, call: ApplicationCall, subject: Any?): Boolean {
|
override fun supports(action: ActionI, call: ApplicationCall, subject: Any?): Boolean {
|
||||||
return (action is Action)
|
return (action is Action || action is ActionMembers)
|
||||||
.and(subject is WorkgroupI?)
|
.and(subject is WorkgroupI?)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -49,6 +55,36 @@ class WorkgroupVoter : Voter {
|
|||||||
VoterException("Unable to define if your are granted, the subject must implement 'WorkgroupWithAuthI'") {}
|
VoterException("Unable to define if your are granted, the subject must implement 'WorkgroupWithAuthI'") {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (action == ActionMembers.ADD) {
|
||||||
|
val citizen = call.citizenOrNull
|
||||||
|
// TODO create ROLES
|
||||||
|
return Vote.isGranted {
|
||||||
|
citizen != null &&
|
||||||
|
subject is WorkgroupWithMembersI<*> &&
|
||||||
|
subject.members.asCitizen(citizen)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (action == ActionMembers.UPDATE) {
|
||||||
|
val citizen = call.citizenOrNull
|
||||||
|
// TODO create ROLES
|
||||||
|
return Vote.isGranted {
|
||||||
|
citizen != null &&
|
||||||
|
subject is WorkgroupWithMembersI<*> &&
|
||||||
|
subject.members.asCitizen(citizen)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (action == ActionMembers.REMOVE) {
|
||||||
|
val citizen = call.citizenOrNull
|
||||||
|
// TODO create ROLES
|
||||||
|
return Vote.isGranted {
|
||||||
|
citizen != null &&
|
||||||
|
subject is WorkgroupWithMembersI<*> &&
|
||||||
|
subject.members.asCitizen(citizen)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return Vote.ABSTAIN
|
return Vote.ABSTAIN
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import fr.dcproject.utils.toUUID
|
|||||||
import io.cucumber.datatable.DataTable
|
import io.cucumber.datatable.DataTable
|
||||||
import io.cucumber.java8.En
|
import io.cucumber.java8.En
|
||||||
import org.joda.time.DateTime
|
import org.joda.time.DateTime
|
||||||
|
import org.junit.Assert
|
||||||
import org.koin.test.KoinTest
|
import org.koin.test.KoinTest
|
||||||
import org.koin.test.get
|
import org.koin.test.get
|
||||||
import java.util.*
|
import java.util.*
|
||||||
@@ -56,5 +57,9 @@ class WorkgroupSteps : En, KoinTest {
|
|||||||
|
|
||||||
get<WorkgroupRepository>().upsert(workgroup)
|
get<WorkgroupRepository>().upsert(workgroup)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Then("The workgroup {string} exists") { id: String ->
|
||||||
|
Assert.assertNotNull(get<CitizenRepository>().findById(id.toUUID()))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -32,6 +32,15 @@ Feature: Workgroup
|
|||||||
| description | La vie est belle |
|
| description | La vie est belle |
|
||||||
| anonymous | false |
|
| anonymous | false |
|
||||||
|
|
||||||
|
Scenario: Can delete a workgroup
|
||||||
|
Given I have citizen Werner Heisenberg
|
||||||
|
And I am authenticated as Werner Heisenberg
|
||||||
|
And I have workgroup:
|
||||||
|
| id | ab469134-bf14-4856-b093-ae1aa990f977 |
|
||||||
|
When I send a DELETE request to "/workgroups/ab469134-bf14-4856-b093-ae1aa990f977"
|
||||||
|
Then the response status code should be 204
|
||||||
|
And The workgroup "ab469134-bf14-4856-b093-ae1aa990f977" exists
|
||||||
|
|
||||||
Scenario: Can get workgroups list
|
Scenario: Can get workgroups list
|
||||||
Given I have citizen Max Planck
|
Given I have citizen Max Planck
|
||||||
And I am authenticated as Max Planck
|
And I am authenticated as Max Planck
|
||||||
@@ -52,7 +61,7 @@ Feature: Workgroup
|
|||||||
| id | b0ea1922-3bc6-44e2-aa7c-40158998cfbb |
|
| id | b0ea1922-3bc6-44e2-aa7c-40158998cfbb |
|
||||||
| name | Les bonobos |
|
| name | Les bonobos |
|
||||||
| owner | Blaise Pascal |
|
| owner | Blaise Pascal |
|
||||||
When I send a POST request to "/workgroups/members/b0ea1922-3bc6-44e2-aa7c-40158998cfbb" with body:
|
When I send a POST request to "/workgroups/b0ea1922-3bc6-44e2-aa7c-40158998cfbb/members" with body:
|
||||||
"""
|
"""
|
||||||
[
|
[
|
||||||
{"id":"6d883fe7-5fc0-4a50-8858-72230673eba4"},
|
{"id":"6d883fe7-5fc0-4a50-8858-72230673eba4"},
|
||||||
@@ -73,7 +82,7 @@ Feature: Workgroup
|
|||||||
And I have members in workgroup "b6c975df-dd44-4e99-adc1-f605746b0e11":
|
And I have members in workgroup "b6c975df-dd44-4e99-adc1-f605746b0e11":
|
||||||
| 87909ba3-2069-431c-9924-219fd8411cf2 |
|
| 87909ba3-2069-431c-9924-219fd8411cf2 |
|
||||||
| 1baf48bb-02bc-4d8f-ac86-33335354f5e7 |
|
| 1baf48bb-02bc-4d8f-ac86-33335354f5e7 |
|
||||||
When I send a DELETE request to "/workgroups/members/b6c975df-dd44-4e99-adc1-f605746b0e11" with body:
|
When I send a DELETE request to "/workgroups/b6c975df-dd44-4e99-adc1-f605746b0e11/members" with body:
|
||||||
"""
|
"""
|
||||||
[
|
[
|
||||||
{"id":"87909ba3-2069-431c-9924-219fd8411cf2"}
|
{"id":"87909ba3-2069-431c-9924-219fd8411cf2"}
|
||||||
@@ -97,7 +106,7 @@ Feature: Workgroup
|
|||||||
And I have members in workgroup "784fe6bc-7635-4ae2-b080-3a4743b998bf":
|
And I have members in workgroup "784fe6bc-7635-4ae2-b080-3a4743b998bf":
|
||||||
| be3b0926-8628-4426-804a-75188a6eb315 |
|
| be3b0926-8628-4426-804a-75188a6eb315 |
|
||||||
| d9671eca-abaf-4b67-9230-3ece700c1ddb |
|
| d9671eca-abaf-4b67-9230-3ece700c1ddb |
|
||||||
When I send a PUT request to "/workgroups/members/784fe6bc-7635-4ae2-b080-3a4743b998bf" with body:
|
When I send a PUT request to "/workgroups/784fe6bc-7635-4ae2-b080-3a4743b998bf/members" with body:
|
||||||
"""
|
"""
|
||||||
[
|
[
|
||||||
{"id":"be3b0926-8628-4426-804a-75188a6eb315"},
|
{"id":"be3b0926-8628-4426-804a-75188a6eb315"},
|
||||||
|
|||||||
Reference in New Issue
Block a user