From 07315a562497d02d3d76a42c34b9dd79e30c8b69 Mon Sep 17 00:00:00 2001 From: Fabrice Lecomte Date: Wed, 25 Mar 2020 00:23:40 +0100 Subject: [PATCH] Add route put/delete --- src/main/kotlin/fr/dcproject/Application.kt | 2 +- .../kotlin/fr/dcproject/entity/Workgroup.kt | 2 + .../kotlin/fr/dcproject/routes/Workgroup.kt | 62 ++++++++++++++++--- .../fr/dcproject/security/voter/Voter.kt | 8 ++- .../security/voter/WorkgroupVoter.kt | 46 ++++++++++++-- src/test/kotlin/feature/WorkgroupSteps.kt | 5 ++ src/test/resources/feature/workgroup.feature | 15 ++++- 7 files changed, 122 insertions(+), 18 deletions(-) diff --git a/src/main/kotlin/fr/dcproject/Application.kt b/src/main/kotlin/fr/dcproject/Application.kt index 0c429c8..8496075 100644 --- a/src/main/kotlin/fr/dcproject/Application.kt +++ b/src/main/kotlin/fr/dcproject/Application.kt @@ -338,7 +338,7 @@ fun Application.module(env: Env = PROD) { } } exception { e -> - call.respond(HttpStatusCode.BadRequest, e.message!!) + call.respond(HttpStatusCode.NotFound, e.message!!) } exception { call.respond(HttpStatusCode.Forbidden) diff --git a/src/main/kotlin/fr/dcproject/entity/Workgroup.kt b/src/main/kotlin/fr/dcproject/entity/Workgroup.kt index e507271..9d7c8ec 100644 --- a/src/main/kotlin/fr/dcproject/entity/Workgroup.kt +++ b/src/main/kotlin/fr/dcproject/entity/Workgroup.kt @@ -58,4 +58,6 @@ interface WorkgroupWithMembersI : WorkgroupI { var members: List } +fun List.asCitizen(citizen: CitizenI): Boolean = this.map { it.id }.contains(citizen.id) + interface WorkgroupI : UuidEntityI \ No newline at end of file diff --git a/src/main/kotlin/fr/dcproject/routes/Workgroup.kt b/src/main/kotlin/fr/dcproject/routes/Workgroup.kt index 6e6cc0f..61322bd 100644 --- a/src/main/kotlin/fr/dcproject/routes/Workgroup.kt +++ b/src/main/kotlin/fr/dcproject/routes/Workgroup.kt @@ -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.CREATE 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.utils.toUUID import fr.postgresjson.repository.RepositoryI @@ -48,14 +51,14 @@ object WorkgroupsPaths { class WorkgroupRequest(val workgroup: WorkgroupEntity) @Location("/workgroups") - class PostWorkgroupRequest : RequestBuilder> { + open class PostWorkgroupRequest : RequestBuilder> { class Content( val id: UUID?, val name: String, val description: String, val logo: String?, val anonymous: Boolean?, - val owner: CitizenRef? + val owner: UUID? ) : KoinComponent { fun create(creator: CitizenRef): WorkgroupSimple { return WorkgroupSimple( @@ -64,7 +67,7 @@ object WorkgroupsPaths { description, logo, anonymous ?: true, - owner ?: creator, + owner?.let { CitizenRef(it) } ?: creator, creator ) } @@ -74,11 +77,38 @@ object WorkgroupsPaths { return call.receive().create(call.citizen) } } + + @Location("/workgroups/{workgroup}") + class PutWorkgroupRequest(val workgroup: WorkgroupEntity) : RequestBuilder { + 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().update(workgroup) + } + } + + @Location("/workgroups/{workgroup}") + class DeleteWorkgroupRequest(val workgroup: WorkgroupEntity) } @KtorExperimentalLocationsAPI object WorkgroupsMembersPaths { - @Location("/workgroups/members/{workgroup}") + @Location("/workgroups/{workgroup}/members") class WorkgroupsMembersRequest(val workgroup: WorkgroupEntity) : RequestBuilder> { class Content : MutableList by mutableListOf() { class Item(val id: String) @@ -117,14 +147,30 @@ fun Route.workgroup(repo: WorkgroupRepository) { } } + put { + call.getContent(it) + .let { workgroup -> + assertCan(UPDATE, workgroup) + repo.upsert(workgroup as WorkgroupSimple) + }.let { + call.respond(HttpStatusCode.OK, it) + } + } + + delete { + assertCan(UPDATE, it.workgroup) + repo.delete(it.workgroup) + call.respond(HttpStatusCode.NoContent, it) + } + /* Add members to workgroup */ post { call.getContent(it) .let { members -> - assertCan(UPDATE, it.workgroup) + assertCan(ADD_MEMBERS, it.workgroup) repo.addMembers(it.workgroup, members) }.let { - call.respond(HttpStatusCode.OK, it) + call.respond(HttpStatusCode.Created, it) } } @@ -132,7 +178,7 @@ fun Route.workgroup(repo: WorkgroupRepository) { delete { call.getContent(it) .let { members -> - assertCan(UPDATE, it.workgroup) + assertCan(REMOVE_MEMBERS, it.workgroup) repo.removeMembers(it.workgroup, members) }.let { call.respond(HttpStatusCode.OK, it) @@ -143,7 +189,7 @@ fun Route.workgroup(repo: WorkgroupRepository) { put { call.getContent(it) .let { members -> - assertCan(UPDATE, it.workgroup) + assertCan(UPDATE_MEMBERS, it.workgroup) repo.updateMembers(it.workgroup, members) }.let { call.respond(HttpStatusCode.OK, it) diff --git a/src/main/kotlin/fr/dcproject/security/voter/Voter.kt b/src/main/kotlin/fr/dcproject/security/voter/Voter.kt index 7b3eea1..56333a3 100644 --- a/src/main/kotlin/fr/dcproject/security/voter/Voter.kt +++ b/src/main/kotlin/fr/dcproject/security/voter/Voter.kt @@ -33,7 +33,13 @@ fun List.can(action: ActionI, call: ApplicationCall, subject: Any? = null enum class Vote { GRANTED, ABSTAIN, - DENIED + DENIED; + + companion object { + fun isGranted(lambda: () -> Boolean): Vote { + return if (lambda()) GRANTED else DENIED + } + } } private val votersAttributeKey = AttributeKey>("voters") diff --git a/src/main/kotlin/fr/dcproject/security/voter/WorkgroupVoter.kt b/src/main/kotlin/fr/dcproject/security/voter/WorkgroupVoter.kt index f2cf2f3..ca5507d 100644 --- a/src/main/kotlin/fr/dcproject/security/voter/WorkgroupVoter.kt +++ b/src/main/kotlin/fr/dcproject/security/voter/WorkgroupVoter.kt @@ -1,8 +1,7 @@ package fr.dcproject.security.voter -import fr.dcproject.entity.UserI -import fr.dcproject.entity.WorkgroupI -import fr.dcproject.entity.WorkgroupWithAuthI +import fr.dcproject.citizenOrNull +import fr.dcproject.entity.* import io.ktor.application.ApplicationCall class WorkgroupVoter : Voter { @@ -10,11 +9,18 @@ class WorkgroupVoter : Voter { CREATE, UPDATE, VIEW, - DELETE + DELETE, + } + + enum class ActionMembers : ActionI { + ADD, + UPDATE, + VIEW, + REMOVE, } 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?) } @@ -49,6 +55,36 @@ class WorkgroupVoter : Voter { 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 } } diff --git a/src/test/kotlin/feature/WorkgroupSteps.kt b/src/test/kotlin/feature/WorkgroupSteps.kt index 8144c30..5ca64e0 100644 --- a/src/test/kotlin/feature/WorkgroupSteps.kt +++ b/src/test/kotlin/feature/WorkgroupSteps.kt @@ -5,6 +5,7 @@ import fr.dcproject.utils.toUUID import io.cucumber.datatable.DataTable import io.cucumber.java8.En import org.joda.time.DateTime +import org.junit.Assert import org.koin.test.KoinTest import org.koin.test.get import java.util.* @@ -56,5 +57,9 @@ class WorkgroupSteps : En, KoinTest { get().upsert(workgroup) } + + Then("The workgroup {string} exists") { id: String -> + Assert.assertNotNull(get().findById(id.toUUID())) + } } } \ No newline at end of file diff --git a/src/test/resources/feature/workgroup.feature b/src/test/resources/feature/workgroup.feature index b29731f..d5e2955 100644 --- a/src/test/resources/feature/workgroup.feature +++ b/src/test/resources/feature/workgroup.feature @@ -32,6 +32,15 @@ Feature: Workgroup | description | La vie est belle | | 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 Given I have citizen Max Planck And I am authenticated as Max Planck @@ -52,7 +61,7 @@ Feature: Workgroup | id | b0ea1922-3bc6-44e2-aa7c-40158998cfbb | | name | Les bonobos | | 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"}, @@ -73,7 +82,7 @@ Feature: Workgroup And I have members in workgroup "b6c975df-dd44-4e99-adc1-f605746b0e11": | 87909ba3-2069-431c-9924-219fd8411cf2 | | 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"} @@ -97,7 +106,7 @@ Feature: Workgroup And I have members in workgroup "784fe6bc-7635-4ae2-b080-3a4743b998bf": | be3b0926-8628-4426-804a-75188a6eb315 | | 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"},