mirror of
https://github.com/keycloak/keycloak.git
synced 2026-04-24 08:00:45 -05:00
[OID4VCI] Fix creation of clientScopes with protocol oid4vc (#39556)
closes #39527 Signed-off-by: Pascal Knüppel <pascal.knueppel@governikus.de>
This commit is contained in:
+3
-3
@@ -361,7 +361,7 @@ public class DefaultExportImportManager implements ExportImportManager {
|
||||
|
||||
Map<String, ClientScopeModel> clientScopes = new HashMap<>();
|
||||
if (rep.getClientScopes() != null) {
|
||||
clientScopes = createClientScopes(session, rep.getClientScopes(), newRealm);
|
||||
clientScopes = createClientScopes(rep.getClientScopes(), newRealm);
|
||||
}
|
||||
if (rep.getDefaultDefaultClientScopes() != null) {
|
||||
for (String clientScopeName : rep.getDefaultDefaultClientScopes()) {
|
||||
@@ -584,10 +584,10 @@ public class DefaultExportImportManager implements ExportImportManager {
|
||||
return appMap;
|
||||
}
|
||||
|
||||
private static Map<String, ClientScopeModel> createClientScopes(KeycloakSession session, List<ClientScopeRepresentation> clientScopes, RealmModel realm) {
|
||||
private static Map<String, ClientScopeModel> createClientScopes(List<ClientScopeRepresentation> clientScopes, RealmModel realm) {
|
||||
Map<String, ClientScopeModel> appMap = new HashMap<>();
|
||||
for (ClientScopeRepresentation resourceRep : clientScopes) {
|
||||
ClientScopeModel app = RepresentationToModel.createClientScope(session, realm, resourceRep);
|
||||
ClientScopeModel app = RepresentationToModel.createClientScope(realm, resourceRep);
|
||||
appMap.put(app.getName(), app);
|
||||
}
|
||||
return appMap;
|
||||
|
||||
+1
-2
@@ -702,7 +702,7 @@ public class RepresentationToModel {
|
||||
// CLIENT SCOPES
|
||||
|
||||
|
||||
public static ClientScopeModel createClientScope(KeycloakSession session, RealmModel realm, ClientScopeRepresentation resourceRep) {
|
||||
public static ClientScopeModel createClientScope(RealmModel realm, ClientScopeRepresentation resourceRep) {
|
||||
logger.debugv("Create client scope: {0}", resourceRep.getName());
|
||||
|
||||
ClientScopeModel clientScope = resourceRep.getId() != null ? realm.addClientScope(resourceRep.getId(), resourceRep.getName()) : realm.addClientScope(resourceRep.getName());
|
||||
@@ -725,7 +725,6 @@ public class RepresentationToModel {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return clientScope;
|
||||
}
|
||||
|
||||
|
||||
+20
-2
@@ -31,12 +31,16 @@ import org.keycloak.events.admin.OperationType;
|
||||
import org.keycloak.events.admin.ResourceType;
|
||||
import org.keycloak.models.ClientScopeModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.KeycloakSessionFactory;
|
||||
import org.keycloak.models.ModelDuplicateException;
|
||||
import org.keycloak.models.ModelException;
|
||||
import org.keycloak.models.ModelIllegalStateException;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.utils.ModelToRepresentation;
|
||||
import org.keycloak.models.utils.RepresentationToModel;
|
||||
import org.keycloak.protocol.LoginProtocol;
|
||||
import org.keycloak.protocol.LoginProtocolFactory;
|
||||
import org.keycloak.protocol.oid4vc.OID4VCLoginProtocolFactory;
|
||||
import org.keycloak.representations.idm.ClientScopeRepresentation;
|
||||
import org.keycloak.saml.common.util.StringUtil;
|
||||
import org.keycloak.services.ErrorResponse;
|
||||
@@ -55,7 +59,9 @@ import jakarta.ws.rs.core.Response;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
|
||||
/**
|
||||
@@ -230,9 +236,21 @@ public class ClientScopeResource {
|
||||
}
|
||||
}
|
||||
|
||||
public static void validateClientScopeProtocol(String protocol)throws ErrorResponseException{
|
||||
if(protocol==null || (!protocol.equals("openid-connect") && !protocol.equals("saml"))) throw ErrorResponse.error("Unexpected protocol",Response.Status.BAD_REQUEST);
|
||||
public static void validateClientScopeProtocol(KeycloakSession session, String protocol)
|
||||
throws ErrorResponseException {
|
||||
KeycloakSessionFactory sessionFactory = session.getKeycloakSessionFactory();
|
||||
Set<String> acceptedProtocols = sessionFactory.getProviderFactoriesStream(LoginProtocol.class)
|
||||
.map(type -> (LoginProtocolFactory) type)
|
||||
.map(LoginProtocolFactory::getId)
|
||||
.collect(Collectors.toSet());
|
||||
// the OID4VC protocol is not registered to prevent it from being displayed in the client-details ui
|
||||
acceptedProtocols.add(OID4VCLoginProtocolFactory.PROTOCOL_ID);
|
||||
|
||||
if (protocol == null || !acceptedProtocols.contains(protocol)) {
|
||||
throw ErrorResponse.error("Unexpected protocol", Response.Status.BAD_REQUEST);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes sure that an update that makes a Client Scope Dynamic is rejected if the Client Scope is assigned to a client
|
||||
* as a default scope.
|
||||
|
||||
+3
-2
@@ -48,6 +48,7 @@ import jakarta.ws.rs.PathParam;
|
||||
import jakarta.ws.rs.Produces;
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
import jakarta.ws.rs.core.Response;
|
||||
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
@@ -116,10 +117,10 @@ public class ClientScopesResource {
|
||||
public Response createClientScope(ClientScopeRepresentation rep) {
|
||||
auth.clients().requireManageClientScopes();
|
||||
ClientScopeResource.validateClientScopeName(rep.getName());
|
||||
ClientScopeResource.validateClientScopeProtocol(rep.getProtocol());
|
||||
ClientScopeResource.validateClientScopeProtocol(session, rep.getProtocol());
|
||||
ClientScopeResource.validateDynamicClientScope(rep);
|
||||
try {
|
||||
ClientScopeModel clientModel = RepresentationToModel.createClientScope(session, realm, rep);
|
||||
ClientScopeModel clientModel = RepresentationToModel.createClientScope(realm, rep);
|
||||
|
||||
adminEvent.operation(OperationType.CREATE).resourcePath(session.getContext().getUri(), clientModel.getId()).representation(rep).success();
|
||||
|
||||
|
||||
@@ -19,10 +19,14 @@ package org.keycloak.tests.admin.client;
|
||||
|
||||
import jakarta.ws.rs.ClientErrorException;
|
||||
import jakarta.ws.rs.NotFoundException;
|
||||
import jakarta.ws.rs.core.HttpHeaders;
|
||||
import jakarta.ws.rs.core.Response;
|
||||
import jakarta.ws.rs.core.Response.Status;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.ValueSource;
|
||||
import org.keycloak.admin.client.resource.ClientResource;
|
||||
import org.keycloak.admin.client.resource.ProtocolMappersResource;
|
||||
import org.keycloak.admin.client.resource.RealmResource;
|
||||
@@ -52,6 +56,7 @@ import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@@ -216,13 +221,6 @@ public class ClientScopeTest extends AbstractClientScopeTest {
|
||||
Assertions.assertEquals("someValue", scopeRep.getAttributes().get("emptyAttr"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValidateClientScopeProtocol() {
|
||||
org.keycloak.services.resources.admin.ClientScopeResource.validateClientScopeProtocol("saml");
|
||||
org.keycloak.services.resources.admin.ClientScopeResource.validateClientScopeProtocol("openid-connect");
|
||||
Assertions.assertThrows(RuntimeException.class, () -> org.keycloak.services.resources.admin.ClientScopeResource.validateClientScopeProtocol("other"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRenameScope() {
|
||||
// Create two scopes
|
||||
@@ -726,6 +724,49 @@ public class ClientScopeTest extends AbstractClientScopeTest {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createClientScopeWithoutProtocol() {
|
||||
ClientScopeRepresentation clientScope = new ClientScopeRepresentation();
|
||||
clientScope.setName("test-client-scope");
|
||||
clientScope.setDescription("test-client-scope-description");
|
||||
clientScope.setProtocol(null); // this should cause a BadRequestException
|
||||
clientScope.setAttributes(Map.of("test-attribute", "test-value"));
|
||||
|
||||
try (Response response = clientScopes().create(clientScope)) {
|
||||
Assertions.assertEquals(Response.Status.BAD_REQUEST.getStatusCode(), response.getStatus());
|
||||
String errorMessage = response.readEntity(String.class);
|
||||
Assertions.assertTrue(errorMessage.contains("Unexpected protocol"));
|
||||
}
|
||||
}
|
||||
|
||||
@DisplayName("Create ClientScope with protocol:")
|
||||
@ParameterizedTest
|
||||
@ValueSource(strings = {"openid-connect", "saml", "oid4vc"})
|
||||
public void createClientScopeWithOpenIdProtocol(String protocol) {
|
||||
createClientScope(protocol);
|
||||
}
|
||||
|
||||
private void createClientScope(String protocol) {
|
||||
ClientScopeRepresentation clientScope = new ClientScopeRepresentation();
|
||||
clientScope.setName("test-client-scope");
|
||||
clientScope.setDescription("test-client-scope-description");
|
||||
clientScope.setProtocol(protocol);
|
||||
clientScope.setAttributes(Map.of("test-attribute", "test-value"));
|
||||
|
||||
String clientScopeId = null;
|
||||
try (Response response = clientScopes().create(clientScope)) {
|
||||
Assertions.assertEquals(Response.Status.CREATED.getStatusCode(), response.getStatus());
|
||||
String location = (String) Optional.ofNullable(response.getHeaders().get(HttpHeaders.LOCATION))
|
||||
.map(list -> list.get(0))
|
||||
.orElse(null);
|
||||
Assertions.assertNotNull(location);
|
||||
clientScopeId = location.substring(location.lastIndexOf("/") + 1);
|
||||
} finally {
|
||||
// cleanup
|
||||
clientScopes().get(clientScopeId).remove();
|
||||
}
|
||||
}
|
||||
|
||||
private void removeClientScopeMustFail(String clientScopeId) {
|
||||
try {
|
||||
clientScopes().get(clientScopeId).remove();
|
||||
|
||||
Reference in New Issue
Block a user