"Allowed Protocol Mapper Types" prevents clients from self-updating via client registration api (#27578)

closes #27558 

Signed-off-by: Réda Housni Alaoui <reda-alaoui@hey.com>
This commit is contained in:
Réda Housni Alaoui 2024-03-13 14:00:34 +01:00 committed by GitHub
parent 7b755e02d3
commit 1bf90321ad
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 131 additions and 14 deletions

View file

@ -18,12 +18,15 @@
package org.keycloak.services.clientregistration.policy.impl;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;
import org.jboss.logging.Logger;
import org.keycloak.component.ComponentModel;
import org.keycloak.models.ClientModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.ProtocolMapperModel;
import org.keycloak.representations.idm.ProtocolMapperRepresentation;
import org.keycloak.services.ServicesLogger;
import org.keycloak.services.clientregistration.ClientRegistrationContext;
@ -48,10 +51,10 @@ public class ProtocolMappersClientRegistrationPolicy implements ClientRegistrati
@Override
public void beforeRegister(ClientRegistrationContext context) throws ClientRegistrationPolicyException {
testMappers(context);
testMappers(context, null);
}
protected void testMappers(ClientRegistrationContext context) throws ClientRegistrationPolicyException {
protected void testMappers(ClientRegistrationContext context, ClientModel clientModel) throws ClientRegistrationPolicyException {
List<ProtocolMapperRepresentation> protocolMappers = context.getClient().getProtocolMappers();
if (protocolMappers == null) {
return;
@ -59,15 +62,41 @@ public class ProtocolMappersClientRegistrationPolicy implements ClientRegistrati
List<String> allowedMapperProviders = getAllowedMapperProviders();
for (ProtocolMapperRepresentation mapper : protocolMappers) {
String mapperType = mapper.getProtocolMapper();
for (ProtocolMapperRepresentation mapperRepresentation : protocolMappers) {
String mapperType = mapperRepresentation.getProtocolMapper();
if (!allowedMapperProviders.contains(mapperType)) {
ServicesLogger.LOGGER.clientRegistrationMapperNotAllowed(mapper.getName(), mapperType);
if (allowedMapperProviders.contains(mapperType)) {
continue;
}
if (clientModel == null) {
failWithProtocolMapperTypeNotAllowedError(mapperRepresentation);
return;
}
String mapperRepresentationId = mapperRepresentation.getId();
if (mapperRepresentationId == null) {
String message = "Missing id for mapper named '%s'".formatted(mapperRepresentation.getName());
ServicesLogger.LOGGER.warn(message);
throw new ClientRegistrationPolicyException(message);
}
ProtocolMapperModel mapperModel = clientModel.getProtocolMapperById(mapperRepresentationId);
if (mapperModel == null) {
String message = "No existing mapper model found for id '%s'".formatted(mapperRepresentationId);
ServicesLogger.LOGGER.warn(message);
throw new ClientRegistrationPolicyException(message);
}
Map<String, String> modelConfig = mapperModel.getConfig();
Map<String, String> representationConfig = mapperRepresentation.getConfig();
if (!Objects.equals(representationConfig, modelConfig)) {
failWithProtocolMapperTypeNotAllowedError(mapperRepresentation);
return;
}
}
}
protected void failWithProtocolMapperTypeNotAllowedError(ProtocolMapperRepresentation mapper) {
ServicesLogger.LOGGER.clientRegistrationMapperNotAllowed(mapper.getName(), mapper.getProtocolMapper());
throw new ClientRegistrationPolicyException("ProtocolMapper type not allowed");
}
}
}
// Remove builtin mappers of unsupported types too
@Override
@ -82,10 +111,9 @@ public class ProtocolMappersClientRegistrationPolicy implements ClientRegistrati
.forEach(clientModel::removeProtocolMapper);
}
// We don't take already existing protocolMappers into consideration for now
@Override
public void beforeUpdate(ClientRegistrationContext context, ClientModel clientModel) throws ClientRegistrationPolicyException {
testMappers(context);
testMappers(context, clientModel);
}
@Override

View file

@ -21,6 +21,7 @@ import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.stream.Collectors;
import org.junit.After;
@ -537,7 +538,7 @@ public class ClientRegistrationPoliciesTest extends AbstractClientRegistrationTe
// Revert policy change
ApiUtil.findClientResourceByClientId(realmResource(), "test-app").remove();
protocolMapperPolicyRep.getConfig().remove(ProtocolMappersClientRegistrationPolicyFactory.ALLOWED_PROTOCOL_MAPPER_TYPES, HardcodedRole.PROVIDER_ID);
protocolMapperPolicyRep.getConfig().get(ProtocolMappersClientRegistrationPolicyFactory.ALLOWED_PROTOCOL_MAPPER_TYPES).remove(HardcodedRole.PROVIDER_ID);
realmResource().components().component(protocolMapperPolicyRep.getId()).update(protocolMapperPolicyRep);
}
@ -572,7 +573,7 @@ public class ClientRegistrationPoliciesTest extends AbstractClientRegistrationTe
registeredClient.getProtocolMappers().add(createHardcodedMapperRep());
// Check I can't update client because of protocolMapper
assertFail(ClientRegOp.UPDATE, registeredClient, 403, "ProtocolMapper type not allowed");
assertFail(ClientRegOp.UPDATE, registeredClient, 403, "Missing id for mapper named 'Hardcoded foo role'");
// Remove "bad" protocolMapper
registeredClient.getProtocolMappers().removeIf((ProtocolMapperRepresentation mapper) -> {
@ -586,6 +587,94 @@ public class ClientRegistrationPoliciesTest extends AbstractClientRegistrationTe
ApiUtil.findClientResourceByClientId(realmResource(), "test-app").remove();
}
@Test
public void passingAnAlreadyAssignedProtocolMapperWithDisallowedTypeDoesNotFail() throws Exception {
setTrustedHost("localhost");
ClientRepresentation clientRep = createRep("test-app");
// Create the client with a client registration disallowed protocolMapper
clientRep.setProtocolMappers(List.of(createHardcodedMapperRep()));
Response adminClientCreationResponse = realmResource().clients().create(clientRep);
String clientTechnicalId = ApiUtil.getCreatedId(adminClientCreationResponse);
adminClientCreationResponse.close();
ClientResource clientResource = realmResource().clients().get(clientTechnicalId);
reg.auth(Auth.token(clientResource.regenerateRegistrationAccessToken()));
// Check the client can be updated with a representation keeping the disallowed protocolMapper
reg.update(clientResource.toRepresentation());
// Revert client
ApiUtil.findClientResourceByClientId(realmResource(), "test-app").remove();
}
@Test
public void passingNewProtocolMapperOfDisallowedTypeInAdditionToAnAlreadyAssignedOneOfTheSameTypeFails() {
setTrustedHost("localhost");
ClientRepresentation clientRep = createRep("test-app");
// Create the client with a client registration disallowed protocolMapper
clientRep.setProtocolMappers(List.of(createHardcodedMapperRep()));
Response adminClientCreationResponse = realmResource().clients().create(clientRep);
String clientTechnicalId = ApiUtil.getCreatedId(adminClientCreationResponse);
adminClientCreationResponse.close();
ClientResource clientResource = realmResource().clients().get(clientTechnicalId);
reg.auth(Auth.token(clientResource.regenerateRegistrationAccessToken()));
ClientRepresentation representation = clientResource.toRepresentation();
representation.getProtocolMappers().add(createHardcodedMapperRep());
assertFail(ClientRegOp.UPDATE, representation, 403, "Missing id for mapper named 'Hardcoded foo role'");
// Revert client
ApiUtil.findClientResourceByClientId(realmResource(), "test-app").remove();
}
@Test
public void alteringAnAlreadyAssignedProtocolMappersConfigWithDisallowedTypeFails() {
setTrustedHost("localhost");
ClientRepresentation clientRep = createRep("test-app");
// Create the client with a client registration disallowed protocolMapper
clientRep.setProtocolMappers(List.of(createHardcodedMapperRep()));
Response adminClientCreationResponse = realmResource().clients().create(clientRep);
String clientTechnicalId = ApiUtil.getCreatedId(adminClientCreationResponse);
adminClientCreationResponse.close();
ClientResource clientResource = realmResource().clients().get(clientTechnicalId);
reg.auth(Auth.token(clientResource.regenerateRegistrationAccessToken()));
// Check the client can be updated with a representation keeping the disallowed protocolMapper
ClientRepresentation representation = clientResource.toRepresentation();
representation.getProtocolMappers().forEach(mapper -> mapper.getConfig().put("foo", "bar"));
assertFail(ClientRegOp.UPDATE, representation, 403, "ProtocolMapper type not allowed");
// Revert client
ApiUtil.findClientResourceByClientId(realmResource(), "test-app").remove();
}
@Test
public void alteringAnAlreadyAssignedProtocolMappersIdWithDisallowedTypeFails() {
setTrustedHost("localhost");
ClientRepresentation clientRep = createRep("test-app");
// Create the client with a client registration disallowed protocolMapper
clientRep.setProtocolMappers(List.of(createHardcodedMapperRep()));
Response adminClientCreationResponse = realmResource().clients().create(clientRep);
String clientTechnicalId = ApiUtil.getCreatedId(adminClientCreationResponse);
adminClientCreationResponse.close();
ClientResource clientResource = realmResource().clients().get(clientTechnicalId);
reg.auth(Auth.token(clientResource.regenerateRegistrationAccessToken()));
ClientRepresentation representation = clientResource.toRepresentation();
representation.getProtocolMappers().forEach(mapper -> mapper.setId(UUID.randomUUID().toString()));
assertFail(ClientRegOp.UPDATE, representation, 403, "No existing mapper model found for id");
// Revert client
ApiUtil.findClientResourceByClientId(realmResource(), "test-app").remove();
}
@Test
public void testProtocolMappersConsentRequired() throws Exception {
@ -622,7 +711,7 @@ public class ClientRegistrationPoliciesTest extends AbstractClientRegistrationTe
// Revert
ApiUtil.findClientResourceByClientId(realmResource(), "test-app").remove();
protocolMapperPolicyRep.getConfig().remove(ProtocolMappersClientRegistrationPolicyFactory.ALLOWED_PROTOCOL_MAPPER_TYPES, HardcodedRole.PROVIDER_ID);
protocolMapperPolicyRep.getConfig().get(ProtocolMappersClientRegistrationPolicyFactory.ALLOWED_PROTOCOL_MAPPER_TYPES).remove(HardcodedRole.PROVIDER_ID);
realmResource().components().component(protocolMapperPolicyRep.getId()).update(protocolMapperPolicyRep);
}