Skip to content

Commit

Permalink
feat(api): Add list and delete users to API
Browse files Browse the repository at this point in the history
Also move all user testing under admin route tests.

Signed-off-by: Jyrki Keisala <[email protected]>
  • Loading branch information
Etsija committed Oct 16, 2024
1 parent d46a73c commit 038e1e2
Show file tree
Hide file tree
Showing 5 changed files with 176 additions and 65 deletions.
21 changes: 21 additions & 0 deletions core/src/main/kotlin/api/AdminRoute.kt
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

package org.eclipse.apoapsis.ortserver.core.api

import io.github.smiley4.ktorswaggerui.dsl.routing.delete
import io.github.smiley4.ktorswaggerui.dsl.routing.get
import io.github.smiley4.ktorswaggerui.dsl.routing.post

Expand All @@ -33,10 +34,14 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext

import org.eclipse.apoapsis.ortserver.api.v1.mapping.mapToApi
import org.eclipse.apoapsis.ortserver.api.v1.model.CreateUser
import org.eclipse.apoapsis.ortserver.core.apiDocs.deleteUserByUsername
import org.eclipse.apoapsis.ortserver.core.apiDocs.getUsers
import org.eclipse.apoapsis.ortserver.core.apiDocs.postUsers
import org.eclipse.apoapsis.ortserver.core.apiDocs.runPermissionsSync
import org.eclipse.apoapsis.ortserver.core.authorization.requireSuperuser
import org.eclipse.apoapsis.ortserver.core.utils.requireParameter
import org.eclipse.apoapsis.ortserver.services.AuthorizationService
import org.eclipse.apoapsis.ortserver.services.UserService

Expand Down Expand Up @@ -64,6 +69,13 @@ fun Route.admin() = route("admin") {
route("users") {
val userService by inject<UserService>()

get(getUsers) {
requireSuperuser()

val users = userService.getUsers().map { user -> user.mapToApi() }
call.respond(users)
}

post(postUsers) {
requireSuperuser()

Expand All @@ -72,5 +84,14 @@ fun Route.admin() = route("admin") {

call.respond(HttpStatusCode.Created)
}

delete(deleteUserByUsername) {
requireSuperuser()

val username = call.requireParameter("username")
userService.deleteUser(username)

call.respond(HttpStatusCode.NoContent)
}
}
}
37 changes: 37 additions & 0 deletions core/src/main/kotlin/apiDocs/AdminDocs.kt
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,21 @@ val runPermissionsSync: OpenApiRoute.() -> Unit = {
}
}

val getUsers: OpenApiRoute.() -> Unit = {
operationId = "getUsers"
summary = "Get all users of the server."
tags = listOf("Admin")

request {
}

response {
HttpStatusCode.OK to {
description = "Successfully retrieved the users."
}
}
}

val postUsers: OpenApiRoute.() -> Unit = {
operationId = "postUsers"
summary = "Create a user, possibly with a password. This is enabled for server administrators only."
Expand All @@ -70,3 +85,25 @@ val postUsers: OpenApiRoute.() -> Unit = {
}
}
}

val deleteUserByUsername: OpenApiRoute.() -> Unit = {
operationId = "deleteUserByUsername"
summary = "Delete a user from the server."
tags = listOf("Admin")

request {
queryParameter<String>("username") {
description = "The username of the user to delete."
}
}

response {
HttpStatusCode.NoContent to {
description = "Successfully deleted the user."
}

HttpStatusCode.InternalServerError to {
description = "The user does not exist."
}
}
}
113 changes: 113 additions & 0 deletions core/src/test/kotlin/api/AdminRouteIntegrationTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,32 @@
package org.eclipse.apoapsis.ortserver.core.api

import io.kotest.assertions.ktor.client.shouldHaveStatus
import io.kotest.matchers.collections.shouldContain
import io.kotest.matchers.nulls.shouldNotBeNull

import io.ktor.client.request.delete
import io.ktor.client.request.get
import io.ktor.client.request.parameter
import io.ktor.client.request.post
import io.ktor.client.request.setBody
import io.ktor.client.statement.bodyAsText
import io.ktor.http.HttpStatusCode

import kotlinx.serialization.json.Json

import org.eclipse.apoapsis.ortserver.api.v1.model.CreateUser
import org.eclipse.apoapsis.ortserver.api.v1.model.User
import org.eclipse.apoapsis.ortserver.core.SUPERUSER
import org.eclipse.apoapsis.ortserver.core.TEST_USER
import org.eclipse.apoapsis.ortserver.model.authorization.Superuser
import org.eclipse.apoapsis.ortserver.utils.test.Integration

class AdminRouteIntegrationTest : AbstractIntegrationTest({
tags(Integration)

val testUsername = "test123"
val testPassword = "password123"
val testTemporary = true

"GET /admin/sync-roles" should {
"start sync process for permissions and roles" {
Expand All @@ -42,4 +61,98 @@ class AdminRouteIntegrationTest : AbstractIntegrationTest({
}
}
}

"GET /admin/users" should {
"return a list of users" {
integrationTestApplication {
val response = superuserClient.get("/api/v1/admin/users")

response shouldHaveStatus HttpStatusCode.OK
val users = Json.decodeFromString<Set<User>>(response.bodyAsText())
users.shouldNotBeNull()
users shouldContain User(
username = SUPERUSER.username.value,
firstName = SUPERUSER.firstName,
lastName = SUPERUSER.lastName,
email = SUPERUSER.email
)
}
}

"require superuser role" {
requestShouldRequireRole(Superuser.ROLE_NAME, HttpStatusCode.OK) {
get("/api/v1/admin/users")
}
}
}

"POST /admin/users" should {
"create a new user" {
integrationTestApplication {
val user = CreateUser(testUsername, testPassword, testTemporary)

val response = superuserClient.post("/api/v1/admin/users") {
setBody(user)
}

response shouldHaveStatus HttpStatusCode.Created
}
}

"respond with an internal error if the user already exists" {
integrationTestApplication {
val user = CreateUser(testUsername, testPassword, testTemporary)

superuserClient.post("/api/v1/admin/users") {
setBody(user)
}
val response = superuserClient.post("/api/v1/admin/users") {
setBody(user)
}

response shouldHaveStatus HttpStatusCode.InternalServerError
}
}

"require superuser role" {
requestShouldRequireRole(Superuser.ROLE_NAME, HttpStatusCode.Created) {
post("/api/v1/admin/users") {
setBody(CreateUser(testUsername, testPassword, testTemporary))
}
}
}
}

"DELETE /admin/users" should {
"delete a user" {
integrationTestApplication {
val response = superuserClient.delete("/api/v1/admin/users") {
parameter("username", TEST_USER.username.value)
}

response shouldHaveStatus HttpStatusCode.NoContent
}
}

"respond with an internal error if the user doesn't exist" {
integrationTestApplication {
superuserClient.delete("/api/v1/admin/users") {
parameter("username", TEST_USER.username.value)
}
val response = superuserClient.delete("/api/v1/admin/users") {
parameter("username", TEST_USER.username.value)
}

response shouldHaveStatus HttpStatusCode.InternalServerError
}
}

"require superuser role" {
requestShouldRequireRole(Superuser.ROLE_NAME, HttpStatusCode.NoContent) {
delete("/api/v1/admin/users") {
parameter("username", TEST_USER.username.value)
}
}
}
}
})
65 changes: 0 additions & 65 deletions core/src/test/kotlin/api/UsersRouteIntegrationTest.kt

This file was deleted.

5 changes: 5 additions & 0 deletions scripts/requests/users.http
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
### Get all Users
GET {{host}}/admin/users
Authorization: Bearer {{$auth.token("keycloak")}}

### Create a User
POST {{host}}/admin/users
Authorization: Bearer {{$auth.token("keycloak")}}
Expand All @@ -8,3 +12,4 @@ Content-Type: application/json
"password": "password",
"temporary": "false",
}

0 comments on commit 038e1e2

Please sign in to comment.