From e9940394eced640dea5cf9e9332b1f9eb5bdd0bd Mon Sep 17 00:00:00 2001 From: Anar Sultanov Date: Thu, 22 Feb 2024 19:51:57 +0100 Subject: [PATCH] feature: Add tenant update endpoint (#33) --- .../multitenancy/model/TenantModel.java | 2 + .../multitenancy/model/jpa/TenantAdapter.java | 5 +++ .../multitenancy/resource/TenantResource.java | 20 +++++++++- .../multitenancy/ApiIntegrationTest.java | 38 ++++++++++++++++++- .../support/api/TenantResource.java | 8 ++++ 5 files changed, 71 insertions(+), 2 deletions(-) diff --git a/src/main/java/dev/sultanov/keycloak/multitenancy/model/TenantModel.java b/src/main/java/dev/sultanov/keycloak/multitenancy/model/TenantModel.java index 18f401d..79ee154 100644 --- a/src/main/java/dev/sultanov/keycloak/multitenancy/model/TenantModel.java +++ b/src/main/java/dev/sultanov/keycloak/multitenancy/model/TenantModel.java @@ -14,6 +14,8 @@ public interface TenantModel { String getName(); + void setName(String name); + RealmModel getRealm(); /* Membership */ diff --git a/src/main/java/dev/sultanov/keycloak/multitenancy/model/jpa/TenantAdapter.java b/src/main/java/dev/sultanov/keycloak/multitenancy/model/jpa/TenantAdapter.java index 3393365..d91bc64 100644 --- a/src/main/java/dev/sultanov/keycloak/multitenancy/model/jpa/TenantAdapter.java +++ b/src/main/java/dev/sultanov/keycloak/multitenancy/model/jpa/TenantAdapter.java @@ -44,6 +44,11 @@ public String getName() { return tenant.getName(); } + @Override + public void setName(String name) { + tenant.setName(name); + } + @Override public RealmModel getRealm() { return session.realms().getRealm(tenant.getRealmId()); diff --git a/src/main/java/dev/sultanov/keycloak/multitenancy/resource/TenantResource.java b/src/main/java/dev/sultanov/keycloak/multitenancy/resource/TenantResource.java index a063649..0a0b0a4 100644 --- a/src/main/java/dev/sultanov/keycloak/multitenancy/resource/TenantResource.java +++ b/src/main/java/dev/sultanov/keycloak/multitenancy/resource/TenantResource.java @@ -2,14 +2,16 @@ import dev.sultanov.keycloak.multitenancy.model.TenantModel; import dev.sultanov.keycloak.multitenancy.resource.representation.TenantRepresentation; +import jakarta.ws.rs.Consumes; import jakarta.ws.rs.DELETE; import jakarta.ws.rs.GET; +import jakarta.ws.rs.PUT; import jakarta.ws.rs.Path; import jakarta.ws.rs.Produces; import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; import org.eclipse.microprofile.openapi.annotations.Operation; import org.keycloak.events.admin.OperationType; -import org.keycloak.models.KeycloakSession; public class TenantResource extends AbstractAdminResource { @@ -27,6 +29,22 @@ public TenantRepresentation getTenant() { return ModelMapper.toRepresentation(tenant); } + @PUT + @Produces(MediaType.APPLICATION_JSON) + @Consumes(value = MediaType.APPLICATION_JSON) + @Operation(operationId = "updateTenant", summary = "Update tenant") + public Response updateTenant(TenantRepresentation request) { + + tenant.setName(request.getName()); + + adminEvent.operation(OperationType.UPDATE) + .resourcePath(session.getContext().getUri()) + .representation(ModelMapper.toRepresentation(tenant)) + .success(); + + return Response.noContent().build(); + } + @DELETE @Operation(operationId = "deleteTenant", summary = "Delete tenant") public void deleteTenant() { diff --git a/src/test/java/dev/sultanov/keycloak/multitenancy/ApiIntegrationTest.java b/src/test/java/dev/sultanov/keycloak/multitenancy/ApiIntegrationTest.java index 1dd1da9..3c19a9f 100644 --- a/src/test/java/dev/sultanov/keycloak/multitenancy/ApiIntegrationTest.java +++ b/src/test/java/dev/sultanov/keycloak/multitenancy/ApiIntegrationTest.java @@ -4,6 +4,7 @@ import dev.sultanov.keycloak.multitenancy.resource.representation.TenantInvitationRepresentation; import dev.sultanov.keycloak.multitenancy.resource.representation.TenantMembershipRepresentation; +import dev.sultanov.keycloak.multitenancy.resource.representation.TenantRepresentation; import dev.sultanov.keycloak.multitenancy.support.BaseIntegrationTest; import dev.sultanov.keycloak.multitenancy.support.actor.KeycloakAdminCli; import dev.sultanov.keycloak.multitenancy.support.browser.AccountPage; @@ -24,7 +25,7 @@ void setUp() { } @Test - void admin_shouldBeAbleToRevokeMembership_whenUserAcceptsInvitation() { + void adminRevokesMembership_shouldSucceed_whenUserHasAcceptedInvitation() { // given var adminUser = keycloakAdminClient.createVerifiedUser(); var tenantResource = adminUser.createTenant(); @@ -61,4 +62,39 @@ void admin_shouldBeAbleToRevokeMembership_whenUserAcceptsInvitation() { .containsExactly(adminUser.getUserData().getEmail().toLowerCase()); } } + + @Test + void adminUpdatesTenant_shouldReturnNoContent_whenTenantIsSuccessfullyUpdated() { + // given + var adminUser = keycloakAdminClient.createVerifiedUser(); + var tenantResource = adminUser.createTenant(); + var newName = "new-name"; + + // when + var request = new TenantRepresentation(); + request.setName(newName); + try (var response = tenantResource.updateTenant(request)) { + + // then + assertThat(response.getStatus()).isEqualTo(HttpStatus.SC_NO_CONTENT); + assertThat(tenantResource.toRepresentation().getName()).isEqualTo(newName); + } + } + + @Test + void adminUpdatesTenant_shouldReturnConflict_whenUpdatedTenantNameAlreadyExists() { + // given + var adminUser = keycloakAdminClient.createVerifiedUser(); + var tenantResource = adminUser.createTenant(); + var existingTenantName = keycloakAdminClient.createVerifiedUser().createTenant().toRepresentation().getName(); + + // when + var request = new TenantRepresentation(); + request.setName(existingTenantName); + try (var response = tenantResource.updateTenant(request)) { + + // then + assertThat(response.getStatus()).isEqualTo(HttpStatus.SC_CONFLICT); + } + } } diff --git a/src/test/java/dev/sultanov/keycloak/multitenancy/support/api/TenantResource.java b/src/test/java/dev/sultanov/keycloak/multitenancy/support/api/TenantResource.java index b462c42..e7c6aca 100644 --- a/src/test/java/dev/sultanov/keycloak/multitenancy/support/api/TenantResource.java +++ b/src/test/java/dev/sultanov/keycloak/multitenancy/support/api/TenantResource.java @@ -1,12 +1,15 @@ package dev.sultanov.keycloak.multitenancy.support.api; import dev.sultanov.keycloak.multitenancy.resource.representation.TenantRepresentation; +import jakarta.ws.rs.Consumes; import jakarta.ws.rs.DELETE; import jakarta.ws.rs.GET; +import jakarta.ws.rs.PUT; import jakarta.ws.rs.Path; import jakarta.ws.rs.Produces; import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.Response; +import org.eclipse.microprofile.openapi.annotations.Operation; public interface TenantResource { @@ -14,6 +17,11 @@ public interface TenantResource { @Produces(MediaType.APPLICATION_JSON) TenantRepresentation toRepresentation(); + @PUT + @Produces(MediaType.APPLICATION_JSON) + @Consumes(value = MediaType.APPLICATION_JSON) + Response updateTenant(TenantRepresentation request); + @DELETE @Produces(MediaType.APPLICATION_JSON) Response deleteTenant();