Add route put/delete

This commit is contained in:
2020-03-25 00:23:40 +01:00
parent 8c25f7633e
commit 07315a5624
7 changed files with 122 additions and 18 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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