From 3941af72dc63ada2a1da5d77b0c2e4f1845c4445 Mon Sep 17 00:00:00 2001 From: Pedro Igor Date: Tue, 3 Nov 2026 11:59:29 -0300 Subject: [PATCH] Fail assigning an ID when creating resources Closes #54452 Signed-off-by: Pedro Igor --- .../protection/resource/ResourceService.java | 5 + .../authz/services/uma/ProtectionApiTest.java | 112 ++++++++++++++++++ 2 files changed, 117 insertions(+) create mode 100644 tests/base/src/test/java/org/keycloak/tests/authz/services/uma/ProtectionApiTest.java diff --git a/services/src/main/java/org/keycloak/authorization/protection/resource/ResourceService.java b/services/src/main/java/org/keycloak/authorization/protection/resource/ResourceService.java index 741dd3633c66..1a248572ab3a 100644 --- a/services/src/main/java/org/keycloak/authorization/protection/resource/ResourceService.java +++ b/services/src/main/java/org/keycloak/authorization/protection/resource/ResourceService.java @@ -31,6 +31,7 @@ import jakarta.ws.rs.core.Response; import jakarta.ws.rs.core.Response.Status; +import org.keycloak.OAuthErrorException; import org.keycloak.authorization.AuthorizationProvider; import org.keycloak.authorization.admin.ResourceSetService; import org.keycloak.authorization.common.KeycloakIdentity; @@ -69,6 +70,10 @@ public Response create(UmaResourceRepresentation resource) { return Response.status(Status.BAD_REQUEST).build(); } + if (resource.getId() != null) { + throw new ErrorResponseException(OAuthErrorException.INVALID_REQUEST, "Resource identifier must not be provided when creating a resource.", Status.BAD_REQUEST); + } + ResourceOwnerRepresentation owner = new ResourceOwnerRepresentation(); owner.setId(identity.getId()); resource.setOwner(owner); diff --git a/tests/base/src/test/java/org/keycloak/tests/authz/services/uma/ProtectionApiTest.java b/tests/base/src/test/java/org/keycloak/tests/authz/services/uma/ProtectionApiTest.java new file mode 100644 index 000000000000..cc5e017d337a --- /dev/null +++ b/tests/base/src/test/java/org/keycloak/tests/authz/services/uma/ProtectionApiTest.java @@ -0,0 +1,112 @@ +package org.keycloak.tests.authz.services.uma; + +import java.util.List; +import java.util.Map; +import java.util.Set; + +import jakarta.ws.rs.core.Response.Status; + +import org.keycloak.authorization.client.AuthzClient; +import org.keycloak.authorization.client.Configuration; +import org.keycloak.authorization.client.resource.ProtectionResource; +import org.keycloak.authorization.client.util.HttpResponseException; +import org.keycloak.representations.idm.RoleRepresentation; +import org.keycloak.representations.idm.authorization.ResourceRepresentation; +import org.keycloak.representations.idm.authorization.ResourceServerRepresentation; +import org.keycloak.testframework.annotations.InjectClient; +import org.keycloak.testframework.annotations.InjectHttpClient; +import org.keycloak.testframework.annotations.InjectKeycloakUrls; +import org.keycloak.testframework.annotations.InjectRealm; +import org.keycloak.testframework.annotations.KeycloakIntegrationTest; +import org.keycloak.testframework.injection.LifeCycle; +import org.keycloak.testframework.realm.ClientBuilder; +import org.keycloak.testframework.realm.ClientConfig; +import org.keycloak.testframework.realm.ManagedClient; +import org.keycloak.testframework.realm.ManagedRealm; +import org.keycloak.testframework.realm.RealmBuilder; +import org.keycloak.testframework.realm.RealmConfig; +import org.keycloak.testframework.realm.UserBuilder; +import org.keycloak.testframework.server.KeycloakUrls; + +import org.apache.http.client.HttpClient; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; + +@KeycloakIntegrationTest +public class ProtectionApiTest { + + private static final String RESOURCE_SERVER_ID = "resource-server"; + + @InjectRealm(config = TestRealmConfig.class, lifecycle = LifeCycle.METHOD) + ManagedRealm realm; + + @InjectClient(config = TestResourceServerConfig.class, lifecycle = LifeCycle.METHOD) + ManagedClient resourceServer; + + @InjectKeycloakUrls + KeycloakUrls keycloakUrls; + + @InjectHttpClient + HttpClient httpClient; + + private AuthzClient authzClient; + + @BeforeEach + public void enableRemoteResourceManagement() { + RoleRepresentation umaProtectionRole = resourceServer.admin().roles().get("uma_protection").toRepresentation(); + for (String username : List.of("alice", "marta")) { + String userId = realm.admin().users().search(username).get(0).getId(); + realm.admin().users().get(userId).roles().clientLevel(resourceServer.getId()).add(List.of(umaProtectionRole)); + } + + ResourceServerRepresentation settings = resourceServer.admin().authorization().getSettings(); + settings.setAllowRemoteResourceManagement(true); + resourceServer.admin().authorization().update(settings); + + authzClient = AuthzClient.create(new Configuration(keycloakUrls.getBase(), realm.getName(), resourceServer.getClientId(), + Map.of("secret", resourceServer.getSecret()), httpClient)); + } + + @Test + public void testRejectsClientSuppliedIdWithoutChangingExistingResource() { + ProtectionResource alice = authzClient.protection("alice", "password"); + ResourceRepresentation existing = alice.resource().create(new ResourceRepresentation("alice-resource", Set.of())); + + ResourceRepresentation registration = new ResourceRepresentation("overwritten-resource", Set.of()); + registration.setId(existing.getId()); + + RuntimeException exception = assertThrows(RuntimeException.class, + () -> authzClient.protection("marta", "password").resource().create(registration)); + assertEquals(Status.BAD_REQUEST.getStatusCode(), ((HttpResponseException) exception.getCause()).getStatusCode()); + assertEquals("alice-resource", alice.resource().findById(existing.getId()).getName()); + + ResourceRepresentation created = authzClient.protection("marta", "password").resource() + .create(new ResourceRepresentation("marta-resource", Set.of())); + assertNotNull(created.getId()); + } + + public static class TestRealmConfig implements RealmConfig { + + @Override + public RealmBuilder configure(RealmBuilder realm) { + return realm + .users(UserBuilder.create("alice").name("Alice", "Test").email("alice@example.org").emailVerified(true).password("password")) + .users(UserBuilder.create("marta").name("Marta", "Test").email("marta@example.org").emailVerified(true).password("password")); + } + } + + public static class TestResourceServerConfig implements ClientConfig { + + @Override + public ClientBuilder configure(ClientBuilder client) { + return client.clientId(RESOURCE_SERVER_ID) + .secret("secret") + .authorizationServicesEnabled(true) + .directAccessGrantsEnabled(); + } + } +}