keycloak-aplcache
Changes
services/src/main/java/org/keycloak/protocol/saml/clientregistration/EntityDescriptorClientRegistrationProvider.java 4(+3 -1)
services/src/main/java/org/keycloak/services/clientregistration/AbstractClientRegistrationProvider.java 64(+13 -51)
services/src/main/java/org/keycloak/services/clientregistration/ClientRegistrationContext.java 36(+36 -0)
services/src/main/java/org/keycloak/services/clientregistration/DefaultClientRegistrationProvider.java 13(+11 -2)
services/src/main/java/org/keycloak/services/clientregistration/oidc/DescriptionConverter.java 19(+10 -9)
services/src/main/java/org/keycloak/services/clientregistration/oidc/OIDCClientRegistrationContext.java 39(+39 -0)
services/src/main/java/org/keycloak/services/clientregistration/oidc/OIDCClientRegistrationProvider.java 110(+107 -3)
testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractKeycloakIdentityProviderTest.java 13(+13 -0)
testsuite/integration/src/test/java/org/keycloak/testsuite/broker/OIDCBrokerUserPropertyTest.java 10(+10 -0)
testsuite/integration/src/test/resources/broker-test/realm-with-oidc-property-mappers.json 17(+16 -1)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/AbstractClientRegistrationTest.java 8(+8 -0)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/OIDCClientRegistrationTest.java 132(+0 -132)
Details
diff --git a/services/src/main/java/org/keycloak/broker/oidc/mappers/AbstractClaimMapper.java b/services/src/main/java/org/keycloak/broker/oidc/mappers/AbstractClaimMapper.java
index c5f0ef1..44c7185 100755
--- a/services/src/main/java/org/keycloak/broker/oidc/mappers/AbstractClaimMapper.java
+++ b/services/src/main/java/org/keycloak/broker/oidc/mappers/AbstractClaimMapper.java
@@ -18,11 +18,13 @@
package org.keycloak.broker.oidc.mappers;
import org.keycloak.broker.oidc.KeycloakOIDCIdentityProvider;
+import org.keycloak.broker.oidc.OIDCIdentityProvider;
import org.keycloak.broker.provider.AbstractIdentityProviderMapper;
import org.keycloak.broker.provider.BrokeredIdentityContext;
import org.keycloak.models.IdentityProviderMapperModel;
import org.keycloak.representations.JsonWebToken;
+import com.fasterxml.jackson.databind.JsonNode;
import java.util.List;
import java.util.Map;
@@ -71,6 +73,12 @@ public abstract class AbstractClaimMapper extends AbstractIdentityProviderMapper
}
}
+ {
+ // Search the OIDC UserInfo claim set (if any)
+ JsonNode profileJsonNode = (JsonNode) context.getContextData().get(OIDCIdentityProvider.USER_INFO);
+ String value = AbstractJsonUserAttributeMapper.getJsonValue(profileJsonNode, claim);
+ if (value != null) return value;
+ }
return null;
}
diff --git a/services/src/main/java/org/keycloak/broker/oidc/mappers/UserAttributeMapper.java b/services/src/main/java/org/keycloak/broker/oidc/mappers/UserAttributeMapper.java
index 3cd35d7..52ad5de 100755
--- a/services/src/main/java/org/keycloak/broker/oidc/mappers/UserAttributeMapper.java
+++ b/services/src/main/java/org/keycloak/broker/oidc/mappers/UserAttributeMapper.java
@@ -126,7 +126,7 @@ public class UserAttributeMapper extends AbstractClaimMapper {
@Override
public String getHelpText() {
- return "Import declared claim if it exists in ID or access token into the specified user property or attribute.";
+ return "Import declared claim if it exists in ID, access token or the claim set returned by the user profile endpoint into the specified user property or attribute.";
}
}
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/mappers/AbstractPairwiseSubMapper.java b/services/src/main/java/org/keycloak/protocol/oidc/mappers/AbstractPairwiseSubMapper.java
index b153fe4..e372c73 100644
--- a/services/src/main/java/org/keycloak/protocol/oidc/mappers/AbstractPairwiseSubMapper.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/mappers/AbstractPairwiseSubMapper.java
@@ -108,12 +108,6 @@ public abstract class AbstractPairwiseSubMapper extends AbstractOIDCProtocolMapp
PairwiseSubMapperValidator.validate(session, client, mapperModel);
}
validateAdditionalConfig(session, realm, mapperContainer, mapperModel);
-
- if (client != null) {
- // Propagate changes to the sector identifier uri
- OIDCAdvancedConfigWrapper configWrapper = OIDCAdvancedConfigWrapper.fromClientModel(client);
- configWrapper.setSectorIdentifierUri(PairwiseSubMapperHelper.getSectorIdentifierUri(mapperModel));
- }
}
@Override
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/mappers/PairwiseSubMapperHelper.java b/services/src/main/java/org/keycloak/protocol/oidc/mappers/PairwiseSubMapperHelper.java
index a2aa4b3..0f6138a 100644
--- a/services/src/main/java/org/keycloak/protocol/oidc/mappers/PairwiseSubMapperHelper.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/mappers/PairwiseSubMapperHelper.java
@@ -2,6 +2,7 @@ package org.keycloak.protocol.oidc.mappers;
import org.keycloak.models.ProtocolMapperModel;
import org.keycloak.provider.ProviderConfigProperty;
+import org.keycloak.representations.idm.ProtocolMapperRepresentation;
import org.keycloak.services.ServicesLogger;
public class PairwiseSubMapperHelper {
@@ -15,6 +16,14 @@ public class PairwiseSubMapperHelper {
public static final String PAIRWISE_SUB_ALGORITHM_SALT_LABEL = "pairwiseSubAlgorithmSalt.label";
public static final String PAIRWISE_SUB_ALGORITHM_SALT_HELP_TEXT = "pairwiseSubAlgorithmSalt.tooltip";
+ public static String getSectorIdentifierUri(ProtocolMapperRepresentation mappingModel) {
+ return mappingModel.getConfig().get(SECTOR_IDENTIFIER_URI);
+ }
+
+ public static void setSectorIdentifierUri(ProtocolMapperModel mappingModel, String sectorIdentifierUri) {
+ mappingModel.getConfig().put(SECTOR_IDENTIFIER_URI, sectorIdentifierUri);
+ }
+
public static String getSectorIdentifierUri(ProtocolMapperModel mappingModel) {
return mappingModel.getConfig().get(SECTOR_IDENTIFIER_URI);
}
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/OIDCAdvancedConfigWrapper.java b/services/src/main/java/org/keycloak/protocol/oidc/OIDCAdvancedConfigWrapper.java
index e16d7b8..756c12a 100644
--- a/services/src/main/java/org/keycloak/protocol/oidc/OIDCAdvancedConfigWrapper.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/OIDCAdvancedConfigWrapper.java
@@ -19,7 +19,6 @@ package org.keycloak.protocol.oidc;
import org.keycloak.jose.jws.Algorithm;
import org.keycloak.models.ClientModel;
-import org.keycloak.protocol.oidc.utils.SubjectType;
import org.keycloak.representations.idm.ClientRepresentation;
import java.util.HashMap;
@@ -33,11 +32,6 @@ public class OIDCAdvancedConfigWrapper {
private static final String REQUEST_OBJECT_SIGNATURE_ALG = "request.object.signature.alg";
- private static final String SUBJECT_TYPE = "oidc.subject_type";
- private static final String SECTOR_IDENTIFIER_URI = "oidc.sector_identifier_uri";
- private static final String PUBLIC = "public";
- private static final String PAIRWISE = "pairwise";
-
private final ClientModel clientModel;
private final ClientRepresentation clientRep;
@@ -80,27 +74,6 @@ public class OIDCAdvancedConfigWrapper {
setAttribute(REQUEST_OBJECT_SIGNATURE_ALG, algStr);
}
- public void setSubjectType(SubjectType subjectType) {
- if (subjectType == null) {
- setAttribute(SUBJECT_TYPE, SubjectType.PUBLIC.toString());
- return;
- }
- setAttribute(SUBJECT_TYPE, subjectType.toString());
- }
-
- public SubjectType getSubjectType() {
- String subjectType = getAttribute(SUBJECT_TYPE);
- return subjectType == null ? SubjectType.PUBLIC : Enum.valueOf(SubjectType.class, subjectType);
- }
-
- public void setSectorIdentifierUri(String sectorIdentifierUri) {
- setAttribute(SECTOR_IDENTIFIER_URI, sectorIdentifierUri);
- }
-
- public String getSectorIdentifierUri() {
- return getAttribute(SECTOR_IDENTIFIER_URI);
- }
-
private String getAttribute(String attrKey) {
if (clientModel != null) {
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocolFactory.java b/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocolFactory.java
index aa56dc7..3b90eca 100755
--- a/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocolFactory.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocolFactory.java
@@ -130,9 +130,6 @@ public class OIDCLoginProtocolFactory extends AbstractLoginProtocolFactory {
ProtocolMapperModel address = AddressMapper.createAddressMapper();
builtins.add(address);
- ProtocolMapperModel pairwise = SHA265PairwiseSubMapper.createPairwiseMapper();
- builtins.add(pairwise);
-
model = UserSessionNoteMapper.createClaimMapper(KerberosConstants.GSS_DELEGATION_CREDENTIAL_DISPLAY_NAME,
KerberosConstants.GSS_DELEGATION_CREDENTIAL,
KerberosConstants.GSS_DELEGATION_CREDENTIAL, "String",
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/utils/PairwiseSubMapperUtils.java b/services/src/main/java/org/keycloak/protocol/oidc/utils/PairwiseSubMapperUtils.java
index 5e2dadc..313a06d 100644
--- a/services/src/main/java/org/keycloak/protocol/oidc/utils/PairwiseSubMapperUtils.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/utils/PairwiseSubMapperUtils.java
@@ -1,5 +1,7 @@
package org.keycloak.protocol.oidc.utils;
+import org.keycloak.models.ClientModel;
+import org.keycloak.models.ProtocolMapperModel;
import org.keycloak.protocol.oidc.mappers.AbstractPairwiseSubMapper;
import org.keycloak.protocol.oidc.mappers.PairwiseSubMapperHelper;
import org.keycloak.representations.idm.ClientRepresentation;
@@ -9,6 +11,7 @@ import org.keycloak.services.ServicesLogger;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.HashSet;
+import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
@@ -142,18 +145,18 @@ public class PairwiseSubMapperUtils {
return relative;
}
- public static ProtocolMapperRepresentation getPairwiseSubMapperRepresentation(ClientRepresentation client) {
+ public static List<ProtocolMapperRepresentation> getPairwiseSubMappers(ClientRepresentation client) {
+ List<ProtocolMapperRepresentation> pairwiseMappers = new LinkedList<>();
List<ProtocolMapperRepresentation> mappers = client.getProtocolMappers();
- if (mappers == null) {
- return null;
- }
- for (ProtocolMapperRepresentation mapper : mappers) {
- if (mapper.getProtocolMapper().endsWith(AbstractPairwiseSubMapper.PROVIDER_ID_SUFFIX)) return mapper;
+
+ if (mappers != null) {
+ client.getProtocolMappers().stream().filter((ProtocolMapperRepresentation mapping) -> {
+ return mapping.getProtocolMapper().endsWith(AbstractPairwiseSubMapper.PROVIDER_ID_SUFFIX);
+ }).forEach((ProtocolMapperRepresentation mapping) -> {
+ pairwiseMappers.add(mapping);
+ });
}
- return null;
- }
- public static String getSubjectIdentifierUri(ProtocolMapperRepresentation pairwiseMapper) {
- return pairwiseMapper.getConfig().get(PairwiseSubMapperHelper.SECTOR_IDENTIFIER_URI);
+ return pairwiseMappers;
}
}
diff --git a/services/src/main/java/org/keycloak/protocol/saml/clientregistration/EntityDescriptorClientRegistrationProvider.java b/services/src/main/java/org/keycloak/protocol/saml/clientregistration/EntityDescriptorClientRegistrationProvider.java
index 2cf6639..aa8d5ca 100755
--- a/services/src/main/java/org/keycloak/protocol/saml/clientregistration/EntityDescriptorClientRegistrationProvider.java
+++ b/services/src/main/java/org/keycloak/protocol/saml/clientregistration/EntityDescriptorClientRegistrationProvider.java
@@ -22,6 +22,7 @@ import org.keycloak.models.KeycloakSession;
import org.keycloak.protocol.saml.EntityDescriptorDescriptionConverter;
import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.services.clientregistration.AbstractClientRegistrationProvider;
+import org.keycloak.services.clientregistration.ClientRegistrationContext;
import javax.ws.rs.Consumes;
import javax.ws.rs.POST;
@@ -44,7 +45,8 @@ public class EntityDescriptorClientRegistrationProvider extends AbstractClientRe
@Produces(MediaType.APPLICATION_JSON)
public Response createSaml(String descriptor) {
ClientRepresentation client = session.getProvider(ClientDescriptionConverter.class, EntityDescriptorDescriptionConverter.ID).convertToInternal(descriptor);
- client = create(client);
+ ClientRegistrationContext context = new ClientRegistrationContext(client);
+ client = create(context);
URI uri = session.getContext().getUri().getAbsolutePathBuilder().path(client.getClientId()).build();
return Response.created(uri).entity(client).build();
}
diff --git a/services/src/main/java/org/keycloak/services/clientregistration/AbstractClientRegistrationProvider.java b/services/src/main/java/org/keycloak/services/clientregistration/AbstractClientRegistrationProvider.java
index f5ad8b0..2fe7902 100755
--- a/services/src/main/java/org/keycloak/services/clientregistration/AbstractClientRegistrationProvider.java
+++ b/services/src/main/java/org/keycloak/services/clientregistration/AbstractClientRegistrationProvider.java
@@ -22,16 +22,10 @@ import org.keycloak.events.EventType;
import org.keycloak.models.*;
import org.keycloak.models.utils.ModelToRepresentation;
import org.keycloak.models.utils.RepresentationToModel;
-import org.keycloak.protocol.oidc.OIDCAdvancedConfigWrapper;
-import org.keycloak.protocol.oidc.mappers.AbstractPairwiseSubMapper;
-import org.keycloak.protocol.oidc.mappers.PairwiseSubMapperHelper;
-import org.keycloak.protocol.oidc.mappers.SHA265PairwiseSubMapper;
-import org.keycloak.protocol.oidc.utils.SubjectType;
import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.services.ErrorResponseException;
import org.keycloak.services.ForbiddenException;
import org.keycloak.services.validation.ClientValidator;
-import org.keycloak.services.validation.PairwiseClientValidator;
import org.keycloak.services.validation.ValidationMessages;
import javax.ws.rs.core.Response;
@@ -49,13 +43,15 @@ public abstract class AbstractClientRegistrationProvider implements ClientRegist
this.session = session;
}
- public ClientRepresentation create(ClientRepresentation client) {
+ public ClientRepresentation create(ClientRegistrationContext context) {
+ ClientRepresentation client = context.getClient();
+
event.event(EventType.CLIENT_REGISTER);
auth.requireCreate();
ValidationMessages validationMessages = new ValidationMessages();
- if (!ClientValidator.validate(client, validationMessages) || !PairwiseClientValidator.validate(session, client, validationMessages)) {
+ if (!validateClient(context, validationMessages)) {
String errorCode = validationMessages.fieldHasError("redirectUris") ? ErrorCodes.INVALID_REDIRECT_URI : ErrorCodes.INVALID_CLIENT_METADATA;
throw new ErrorResponseException(
errorCode,
@@ -66,10 +62,6 @@ public abstract class AbstractClientRegistrationProvider implements ClientRegist
try {
ClientModel clientModel = RepresentationToModel.createClient(session, session.getContext().getRealm(), client, true);
- OIDCAdvancedConfigWrapper configWrapper = OIDCAdvancedConfigWrapper.fromClientRepresentation(client);
- if (configWrapper.getSubjectType().equals(SubjectType.PAIRWISE)) {
- addPairwiseSubMapper(clientModel, configWrapper.getSectorIdentifierUri());
- }
client = ModelToRepresentation.toRepresentation(clientModel);
@@ -112,7 +104,9 @@ public abstract class AbstractClientRegistrationProvider implements ClientRegist
return rep;
}
- public ClientRepresentation update(String clientId, ClientRepresentation rep) {
+ public ClientRepresentation update(String clientId, ClientRegistrationContext context) {
+ ClientRepresentation rep = context.getClient();
+
event.event(EventType.CLIENT_UPDATE).client(clientId);
ClientModel client = session.getContext().getRealm().getClientByClientId(clientId);
@@ -123,7 +117,7 @@ public abstract class AbstractClientRegistrationProvider implements ClientRegist
}
ValidationMessages validationMessages = new ValidationMessages();
- if (!ClientValidator.validate(rep, validationMessages) || !PairwiseClientValidator.validate(session, rep, validationMessages)) {
+ if (!validateClient(context, validationMessages)) {
String errorCode = validationMessages.fieldHasError("redirectUris") ? ErrorCodes.INVALID_REDIRECT_URI : ErrorCodes.INVALID_CLIENT_METADATA;
throw new ErrorResponseException(
errorCode,
@@ -132,7 +126,6 @@ public abstract class AbstractClientRegistrationProvider implements ClientRegist
);
}
- updateSubjectType(rep, client);
RepresentationToModel.updateClient(rep, client);
rep = ModelToRepresentation.toRepresentation(client);
@@ -145,42 +138,6 @@ public abstract class AbstractClientRegistrationProvider implements ClientRegist
return rep;
}
- private void updateSubjectType(ClientRepresentation rep, ClientModel client) {
- OIDCAdvancedConfigWrapper repConfigWrapper = OIDCAdvancedConfigWrapper.fromClientRepresentation(rep);
- SubjectType repSubjectType = repConfigWrapper.getSubjectType();
- OIDCAdvancedConfigWrapper clientConfigWrapper = OIDCAdvancedConfigWrapper.fromClientModel(client);
- SubjectType clientSubjectType = clientConfigWrapper.getSubjectType();
-
- if (repSubjectType.equals(SubjectType.PAIRWISE) && clientSubjectType.equals(SubjectType.PAIRWISE)) {
- updateSectorIdentifier(client, repConfigWrapper.getSectorIdentifierUri());
- }
-
- if (repSubjectType.equals(SubjectType.PAIRWISE) && clientSubjectType.equals(SubjectType.PUBLIC)) {
- addPairwiseSubMapper(client, repConfigWrapper.getSectorIdentifierUri());
- }
-
- if (repSubjectType.equals(SubjectType.PUBLIC) && clientSubjectType.equals(SubjectType.PAIRWISE)) {
- removePairwiseSubMapper(client);
- }
- }
-
- private void updateSectorIdentifier(ClientModel client, String sectorIdentifierUri) {
- client.getProtocolMappers().stream().filter(mapping -> mapping.getProtocolMapper().endsWith(AbstractPairwiseSubMapper.PROVIDER_ID_SUFFIX)).forEach(mapping -> {
- mapping.getConfig().put(PairwiseSubMapperHelper.SECTOR_IDENTIFIER_URI, sectorIdentifierUri);
- });
- }
-
- private void addPairwiseSubMapper(ClientModel client, String sectorIdentifierUri) {
- client.addProtocolMapper(SHA265PairwiseSubMapper.createPairwiseMapper(sectorIdentifierUri));
- }
-
- private void removePairwiseSubMapper(ClientModel client) {
- for (ProtocolMapperModel mapping : client.getProtocolMappers()) {
- if (mapping.getProtocolMapper().endsWith(AbstractPairwiseSubMapper.PROVIDER_ID_SUFFIX)) {
- client.removeProtocolMapper(mapping);
- }
- }
- }
public void delete(String clientId) {
event.event(EventType.CLIENT_DELETE).client(clientId);
@@ -209,4 +166,9 @@ public abstract class AbstractClientRegistrationProvider implements ClientRegist
public void close() {
}
+ protected boolean validateClient(ClientRegistrationContext context, ValidationMessages validationMessages) {
+ ClientRepresentation client = context.getClient();
+ return ClientValidator.validate(client, validationMessages);
+ }
+
}
diff --git a/services/src/main/java/org/keycloak/services/clientregistration/ClientRegistrationContext.java b/services/src/main/java/org/keycloak/services/clientregistration/ClientRegistrationContext.java
new file mode 100644
index 0000000..be612e6
--- /dev/null
+++ b/services/src/main/java/org/keycloak/services/clientregistration/ClientRegistrationContext.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.services.clientregistration;
+
+import org.keycloak.representations.idm.ClientRepresentation;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class ClientRegistrationContext {
+
+ private final ClientRepresentation client;
+
+ public ClientRegistrationContext(ClientRepresentation client) {
+ this.client = client;
+ }
+
+ public ClientRepresentation getClient() {
+ return client;
+ }
+}
diff --git a/services/src/main/java/org/keycloak/services/clientregistration/DefaultClientRegistrationProvider.java b/services/src/main/java/org/keycloak/services/clientregistration/DefaultClientRegistrationProvider.java
index 836ec5b..63f9fef 100755
--- a/services/src/main/java/org/keycloak/services/clientregistration/DefaultClientRegistrationProvider.java
+++ b/services/src/main/java/org/keycloak/services/clientregistration/DefaultClientRegistrationProvider.java
@@ -20,6 +20,8 @@ package org.keycloak.services.clientregistration;
import org.keycloak.events.EventBuilder;
import org.keycloak.models.KeycloakSession;
import org.keycloak.representations.idm.ClientRepresentation;
+import org.keycloak.services.validation.PairwiseClientValidator;
+import org.keycloak.services.validation.ValidationMessages;
import javax.ws.rs.*;
import javax.ws.rs.core.MediaType;
@@ -39,7 +41,8 @@ public class DefaultClientRegistrationProvider extends AbstractClientRegistratio
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
public Response createDefault(ClientRepresentation client) {
- client = create(client);
+ ClientRegistrationContext context = new ClientRegistrationContext(client);
+ client = create(context);
URI uri = session.getContext().getUri().getAbsolutePathBuilder().path(client.getClientId()).build();
return Response.created(uri).entity(client).build();
}
@@ -56,7 +59,8 @@ public class DefaultClientRegistrationProvider extends AbstractClientRegistratio
@Path("{clientId}")
@Consumes(MediaType.APPLICATION_JSON)
public Response updateDefault(@PathParam("clientId") String clientId, ClientRepresentation client) {
- client = update(clientId, client);
+ ClientRegistrationContext context = new ClientRegistrationContext(client);
+ client = update(clientId, context);
return Response.ok(client).build();
}
@@ -80,4 +84,9 @@ public class DefaultClientRegistrationProvider extends AbstractClientRegistratio
public void close() {
}
+ @Override
+ protected boolean validateClient(ClientRegistrationContext context, ValidationMessages validationMessages) {
+ ClientRepresentation client = context.getClient();
+ return super.validateClient(context, validationMessages) && PairwiseClientValidator.validate(session, client, validationMessages);
+ }
}
diff --git a/services/src/main/java/org/keycloak/services/clientregistration/oidc/DescriptionConverter.java b/services/src/main/java/org/keycloak/services/clientregistration/oidc/DescriptionConverter.java
index c377313..2024a6f 100644
--- a/services/src/main/java/org/keycloak/services/clientregistration/oidc/DescriptionConverter.java
+++ b/services/src/main/java/org/keycloak/services/clientregistration/oidc/DescriptionConverter.java
@@ -29,12 +29,15 @@ import org.keycloak.models.KeycloakSession;
import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.protocol.oidc.OIDCAdvancedConfigWrapper;
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
+import org.keycloak.protocol.oidc.mappers.PairwiseSubMapperHelper;
import org.keycloak.protocol.oidc.utils.AuthorizeClientUtil;
import org.keycloak.protocol.oidc.utils.JWKSUtils;
import org.keycloak.protocol.oidc.utils.OIDCResponseType;
+import org.keycloak.protocol.oidc.utils.PairwiseSubMapperUtils;
import org.keycloak.protocol.oidc.utils.SubjectType;
import org.keycloak.representations.idm.CertificateRepresentation;
import org.keycloak.representations.idm.ClientRepresentation;
+import org.keycloak.representations.idm.ProtocolMapperRepresentation;
import org.keycloak.representations.oidc.OIDCClientRepresentation;
import org.keycloak.services.clientregistration.ClientRegistrationException;
import org.keycloak.services.util.CertificateInfoHelper;
@@ -115,12 +118,6 @@ public class DescriptionConverter {
configWrapper.setRequestObjectSignatureAlg(algorithm);
}
- SubjectType subjectType = SubjectType.parse(clientOIDC.getSubjectType());
- configWrapper.setSubjectType(subjectType);
- if (subjectType.equals(SubjectType.PAIRWISE)) {
- configWrapper.setSectorIdentifierUri(clientOIDC.getSectorIdentifierUri());
- }
-
return client;
}
@@ -180,9 +177,13 @@ public class DescriptionConverter {
response.setRequestObjectSigningAlg(config.getRequestObjectSignatureAlg().toString());
}
- response.setSubjectType(config.getSubjectType().toString().toLowerCase());
- if (config.getSubjectType().equals(SubjectType.PAIRWISE)) {
- response.setSectorIdentifierUri(config.getSectorIdentifierUri());
+ List<ProtocolMapperRepresentation> foundPairwiseMappers = PairwiseSubMapperUtils.getPairwiseSubMappers(client);
+ SubjectType subjectType = foundPairwiseMappers.isEmpty() ? SubjectType.PUBLIC : SubjectType.PAIRWISE;
+ response.setSubjectType(subjectType.toString().toLowerCase());
+ if (subjectType.equals(SubjectType.PAIRWISE)) {
+ // Get sectorIdentifier from 1st found
+ String sectorIdentifierUri = PairwiseSubMapperHelper.getSectorIdentifierUri(foundPairwiseMappers.get(0));
+ response.setSectorIdentifierUri(sectorIdentifierUri);
}
return response;
diff --git a/services/src/main/java/org/keycloak/services/clientregistration/oidc/OIDCClientRegistrationContext.java b/services/src/main/java/org/keycloak/services/clientregistration/oidc/OIDCClientRegistrationContext.java
new file mode 100644
index 0000000..556bbac
--- /dev/null
+++ b/services/src/main/java/org/keycloak/services/clientregistration/oidc/OIDCClientRegistrationContext.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.services.clientregistration.oidc;
+
+import org.keycloak.representations.idm.ClientRepresentation;
+import org.keycloak.representations.oidc.OIDCClientRepresentation;
+import org.keycloak.services.clientregistration.ClientRegistrationContext;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class OIDCClientRegistrationContext extends ClientRegistrationContext {
+
+ private final OIDCClientRepresentation oidcRep;
+
+ public OIDCClientRegistrationContext(ClientRepresentation client, OIDCClientRepresentation oidcRep) {
+ super(client);
+ this.oidcRep = oidcRep;
+ }
+
+ public OIDCClientRepresentation getOidcRep() {
+ return oidcRep;
+ }
+}
diff --git a/services/src/main/java/org/keycloak/services/clientregistration/oidc/OIDCClientRegistrationProvider.java b/services/src/main/java/org/keycloak/services/clientregistration/oidc/OIDCClientRegistrationProvider.java
index 8bc1ecb..5277505 100755
--- a/services/src/main/java/org/keycloak/services/clientregistration/oidc/OIDCClientRegistrationProvider.java
+++ b/services/src/main/java/org/keycloak/services/clientregistration/oidc/OIDCClientRegistrationProvider.java
@@ -18,20 +18,46 @@ package org.keycloak.services.clientregistration.oidc;
import org.keycloak.common.util.Time;
import org.keycloak.events.EventBuilder;
+import org.keycloak.models.ClientModel;
import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.ProtocolMapperModel;
+import org.keycloak.models.utils.ModelToRepresentation;
+import org.keycloak.models.utils.RepresentationToModel;
+import org.keycloak.protocol.ProtocolMapperConfigException;
+import org.keycloak.protocol.oidc.mappers.AbstractPairwiseSubMapper;
+import org.keycloak.protocol.oidc.mappers.PairwiseSubMapperHelper;
+import org.keycloak.protocol.oidc.mappers.SHA256PairwiseSubMapper;
+import org.keycloak.protocol.oidc.utils.PairwiseSubMapperValidator;
+import org.keycloak.protocol.oidc.utils.SubjectType;
+import org.keycloak.representations.idm.ProtocolMapperRepresentation;
import org.keycloak.representations.oidc.OIDCClientRepresentation;
import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.services.ErrorResponseException;
import org.keycloak.services.ServicesLogger;
import org.keycloak.services.clientregistration.AbstractClientRegistrationProvider;
import org.keycloak.services.clientregistration.ClientRegistrationAuth;
+import org.keycloak.services.clientregistration.ClientRegistrationContext;
import org.keycloak.services.clientregistration.ClientRegistrationException;
import org.keycloak.services.clientregistration.ErrorCodes;
+import org.keycloak.services.validation.PairwiseClientValidator;
+import org.keycloak.services.validation.ValidationMessages;
-import javax.ws.rs.*;
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import java.net.URI;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicBoolean;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
@@ -54,7 +80,13 @@ public class OIDCClientRegistrationProvider extends AbstractClientRegistrationPr
try {
ClientRepresentation client = DescriptionConverter.toInternal(session, clientOIDC);
- client = create(client);
+ OIDCClientRegistrationContext oidcContext = new OIDCClientRegistrationContext(client, clientOIDC);
+ client = create(oidcContext);
+
+ ClientModel clientModel = session.getContext().getRealm().getClientByClientId(client.getClientId());
+ updatePairwiseSubMappers(clientModel, SubjectType.parse(clientOIDC.getSubjectType()), clientOIDC.getSectorIdentifierUri());
+ updateClientRepWithProtocolMappers(clientModel, client);
+
URI uri = session.getContext().getUri().getAbsolutePathBuilder().path(client.getClientId()).build();
clientOIDC = DescriptionConverter.toExternalResponse(session, client, uri);
clientOIDC.setClientIdIssuedAt(Time.currentTime());
@@ -80,7 +112,13 @@ public class OIDCClientRegistrationProvider extends AbstractClientRegistrationPr
public Response updateOIDC(@PathParam("clientId") String clientId, OIDCClientRepresentation clientOIDC) {
try {
ClientRepresentation client = DescriptionConverter.toInternal(session, clientOIDC);
- client = update(clientId, client);
+ OIDCClientRegistrationContext oidcContext = new OIDCClientRegistrationContext(client, clientOIDC);
+ client = update(clientId, oidcContext);
+
+ ClientModel clientModel = session.getContext().getRealm().getClientByClientId(client.getClientId());
+ updatePairwiseSubMappers(clientModel, SubjectType.parse(clientOIDC.getSubjectType()), clientOIDC.getSectorIdentifierUri());
+ updateClientRepWithProtocolMappers(clientModel, client);
+
URI uri = session.getContext().getUri().getAbsolutePathBuilder().path(client.getClientId()).build();
clientOIDC = DescriptionConverter.toExternalResponse(session, client, uri);
return Response.ok(clientOIDC).build();
@@ -110,4 +148,70 @@ public class OIDCClientRegistrationProvider extends AbstractClientRegistrationPr
public void close() {
}
+
+ private void updatePairwiseSubMappers(ClientModel clientModel, SubjectType subjectType, String sectorIdentifierUri) {
+ if (subjectType == SubjectType.PAIRWISE) {
+
+ // See if we have existing pairwise mapper and update it. Otherwise create new
+ AtomicBoolean foundPairwise = new AtomicBoolean(false);
+
+ clientModel.getProtocolMappers().stream().filter((ProtocolMapperModel mapping) -> {
+ if (mapping.getProtocolMapper().endsWith(AbstractPairwiseSubMapper.PROVIDER_ID_SUFFIX)) {
+ foundPairwise.set(true);
+ return true;
+ } else {
+ return false;
+ }
+ }).forEach((ProtocolMapperModel mapping) -> {
+ PairwiseSubMapperHelper.setSectorIdentifierUri(mapping, sectorIdentifierUri);
+ clientModel.updateProtocolMapper(mapping);
+ });
+
+ // We don't have existing pairwise mapper. So create new
+ if (!foundPairwise.get()) {
+ ProtocolMapperRepresentation newPairwise = SHA256PairwiseSubMapper.createPairwiseMapper(sectorIdentifierUri, null);
+ clientModel.addProtocolMapper(RepresentationToModel.toModel(newPairwise));
+ }
+
+ } else {
+ // Rather find and remove all pairwise mappers
+ clientModel.getProtocolMappers().stream().filter((ProtocolMapperModel mapperRep) -> {
+ return mapperRep.getProtocolMapper().endsWith(AbstractPairwiseSubMapper.PROVIDER_ID_SUFFIX);
+ }).forEach((ProtocolMapperModel mapping) -> {
+ clientModel.getProtocolMappers().remove(mapping);
+ });
+ }
+ }
+
+ @Override
+ protected boolean validateClient(ClientRegistrationContext context, ValidationMessages validationMessages) {
+ OIDCClientRegistrationContext oidcContext = (OIDCClientRegistrationContext) context;
+ OIDCClientRepresentation oidcRep = oidcContext.getOidcRep();
+
+ boolean valid = super.validateClient(context, validationMessages);
+
+ ClientRepresentation client = oidcContext.getClient();
+
+ String rootUrl = client.getRootUrl();
+ Set<String> redirectUris = new HashSet<>();
+ if (client.getRedirectUris() != null) redirectUris.addAll(client.getRedirectUris());
+
+ SubjectType subjectType = SubjectType.parse(oidcRep.getSubjectType());
+ String sectorIdentifierUri = oidcRep.getSectorIdentifierUri();
+
+ // If sector_identifier_uri is in oidc config, then always validate it
+ if (SubjectType.PAIRWISE == subjectType || (sectorIdentifierUri != null && !sectorIdentifierUri.isEmpty())) {
+ valid = valid && PairwiseClientValidator.validate(session, rootUrl, redirectUris, oidcRep.getSectorIdentifierUri(), validationMessages);
+ }
+ return valid;
+ }
+
+ private void updateClientRepWithProtocolMappers(ClientModel clientModel, ClientRepresentation rep) {
+ List<ProtocolMapperRepresentation> mappings = new LinkedList<>();
+ for (ProtocolMapperModel model : clientModel.getProtocolMappers()) {
+ mappings.add(ModelToRepresentation.toRepresentation(model));
+ }
+ rep.setProtocolMappers(mappings);
+ }
+
}
diff --git a/services/src/main/java/org/keycloak/services/validation/PairwiseClientValidator.java b/services/src/main/java/org/keycloak/services/validation/PairwiseClientValidator.java
index 6cc2033..f502fae 100644
--- a/services/src/main/java/org/keycloak/services/validation/PairwiseClientValidator.java
+++ b/services/src/main/java/org/keycloak/services/validation/PairwiseClientValidator.java
@@ -2,12 +2,14 @@ package org.keycloak.services.validation;
import org.keycloak.models.KeycloakSession;
import org.keycloak.protocol.ProtocolMapperConfigException;
-import org.keycloak.protocol.oidc.OIDCAdvancedConfigWrapper;
+import org.keycloak.protocol.oidc.mappers.PairwiseSubMapperHelper;
+import org.keycloak.protocol.oidc.utils.PairwiseSubMapperUtils;
import org.keycloak.protocol.oidc.utils.PairwiseSubMapperValidator;
-import org.keycloak.protocol.oidc.utils.SubjectType;
import org.keycloak.representations.idm.ClientRepresentation;
+import org.keycloak.representations.idm.ProtocolMapperRepresentation;
import java.util.HashSet;
+import java.util.List;
import java.util.Set;
@@ -17,14 +19,18 @@ import java.util.Set;
public class PairwiseClientValidator {
public static boolean validate(KeycloakSession session, ClientRepresentation client, ValidationMessages messages) {
- OIDCAdvancedConfigWrapper configWrapper = OIDCAdvancedConfigWrapper.fromClientRepresentation(client);
- if (configWrapper.getSubjectType().equals(SubjectType.PAIRWISE)) {
- String sectorIdentifierUri = configWrapper.getSectorIdentifierUri();
- String rootUrl = client.getRootUrl();
- Set<String> redirectUris = new HashSet<>();
+ String rootUrl = client.getRootUrl();
+ Set<String> redirectUris = new HashSet<>();
+ boolean valid = true;
+
+ List<ProtocolMapperRepresentation> foundPairwiseMappers = PairwiseSubMapperUtils.getPairwiseSubMappers(client);
+
+ for (ProtocolMapperRepresentation foundPairwise : foundPairwiseMappers) {
+ String sectorIdentifierUri = PairwiseSubMapperHelper.getSectorIdentifierUri(foundPairwise);
if (client.getRedirectUris() != null) redirectUris.addAll(client.getRedirectUris());
- return validate(session, rootUrl, redirectUris, sectorIdentifierUri, messages);
+ valid = valid && validate(session, rootUrl, redirectUris, sectorIdentifierUri, messages);
}
+
return true;
}
diff --git a/services/src/main/resources/META-INF/services/org.keycloak.protocol.ProtocolMapper b/services/src/main/resources/META-INF/services/org.keycloak.protocol.ProtocolMapper
index 77830dc..04f090e 100755
--- a/services/src/main/resources/META-INF/services/org.keycloak.protocol.ProtocolMapper
+++ b/services/src/main/resources/META-INF/services/org.keycloak.protocol.ProtocolMapper
@@ -34,5 +34,5 @@ org.keycloak.protocol.saml.mappers.UserSessionNoteStatementMapper
org.keycloak.protocol.saml.mappers.GroupMembershipMapper
org.keycloak.protocol.oidc.mappers.UserClientRoleMappingMapper
org.keycloak.protocol.oidc.mappers.UserRealmRoleMappingMapper
-org.keycloak.protocol.oidc.mappers.SHA265PairwiseSubMapper
+org.keycloak.protocol.oidc.mappers.SHA256PairwiseSubMapper
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractKeycloakIdentityProviderTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractKeycloakIdentityProviderTest.java
index f6a85e4..1583853 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractKeycloakIdentityProviderTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractKeycloakIdentityProviderTest.java
@@ -316,6 +316,19 @@ public abstract class AbstractKeycloakIdentityProviderTest extends AbstractIdent
}
}
+ /**
+ * Test for KEYCLOAK-3505 - Verify the claims from the claim set returned by the OIDC UserInfo are correctly mapped
+ * by the user attribute mapper
+ *
+ */
+ protected void verifyAttributeMapperHandlesUserInfoClaims() {
+ IdentityProviderModel identityProviderModel = getIdentityProviderModel();
+ setUpdateProfileFirstLogin(IdentityProviderRepresentation.UPFLM_ON);
+
+ UserModel user = assertSuccessfulAuthentication(identityProviderModel, "test-user", "new@email.com", true);
+ Assert.assertEquals("A00", user.getFirstAttribute("tenantid"));
+ }
+
@Test
public void testSuccessfulAuthenticationWithoutUpdateProfile_newUser_emailAsUsername() {
RealmModel realm = getRealm();
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/OIDCBrokerUserPropertyTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/OIDCBrokerUserPropertyTest.java
index 49b4426..c23bfed 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/OIDCBrokerUserPropertyTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/OIDCBrokerUserPropertyTest.java
@@ -100,6 +100,16 @@ public class OIDCBrokerUserPropertyTest extends AbstractKeycloakIdentityProvider
}
}
+ /**
+ * Test for KEYCLOAK-3505 - Verify the claims from the claim set returned by the OIDC UserInfo are correctly mapped
+ * by the user attribute mapper
+ *
+ */
+ @Test
+ public void testSuccessfulAuthentication_verifyAttributeMapperHandlesUserInfoClaims() {
+ verifyAttributeMapperHandlesUserInfoClaims();
+ }
+
@Override
@Test
public void testSuccessfulAuthenticationWithoutUpdateProfile() {
diff --git a/testsuite/integration/src/test/resources/broker-test/realm-with-oidc-property-mappers.json b/testsuite/integration/src/test/resources/broker-test/realm-with-oidc-property-mappers.json
index f75bc45..9d3c7ac 100755
--- a/testsuite/integration/src/test/resources/broker-test/realm-with-oidc-property-mappers.json
+++ b/testsuite/integration/src/test/resources/broker-test/realm-with-oidc-property-mappers.json
@@ -18,6 +18,20 @@
],
"protocolMappers": [
{
+ "name": "tenantid",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-usermodel-attribute-mapper",
+ "consentRequired": false,
+ "config": {
+ "user.attribute": "tenantid",
+ "claim.name": "tenantid",
+ "Claim JSON Type": "String",
+ "access.token.claim": "false",
+ "id.token.claim": "false",
+ "userinfo.token.claim": "true"
+ }
+ },
+ {
"name": "mobile",
"protocol": "openid-connect",
"protocolMapper": "oidc-usermodel-attribute-mapper",
@@ -109,7 +123,8 @@
],
"realmRoles": ["manager"],
"attributes": {
- "mobile": "617-666-7777"
+ "mobile": "617-666-7777",
+ "tenantid": "A00"
}
},
{
diff --git a/testsuite/integration/src/test/resources/broker-test/test-realm-with-broker.json b/testsuite/integration/src/test/resources/broker-test/test-realm-with-broker.json
index 99c0245..dba9c15 100755
--- a/testsuite/integration/src/test/resources/broker-test/test-realm-with-broker.json
+++ b/testsuite/integration/src/test/resources/broker-test/test-realm-with-broker.json
@@ -244,6 +244,15 @@
}
},
{
+ "name": "kc-tenantid-mapper",
+ "identityProviderAlias": "kc-oidc-idp-property-mappers",
+ "identityProviderMapper": "oidc-user-attribute-idp-mapper",
+ "config": {
+ "user.attribute": "tenantid",
+ "claim": "tenantid"
+ }
+ },
+ {
"name": "manager-mapper",
"identityProviderAlias": "kc-oidc-idp",
"identityProviderMapper": "oidc-role-idp-mapper",
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/AbstractClientRegistrationTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/AbstractClientRegistrationTest.java
index 2ea1961..c72bdba 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/AbstractClientRegistrationTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/AbstractClientRegistrationTest.java
@@ -89,6 +89,14 @@ public abstract class AbstractClientRegistrationTest extends AbstractKeycloakTes
rep.getUsers().add(user3);
+ UserRepresentation appUser = new UserRepresentation();
+ appUser.setEnabled(true);
+ appUser.setUsername("test-user");
+ appUser.setEmail("test-user@localhost");
+ appUser.setCredentials(credentials);
+
+ rep.getUsers().add(appUser);
+
testRealms.add(rep);
}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/OIDCClientRegistrationTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/OIDCClientRegistrationTest.java
index d13bfa1..e88c538 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/OIDCClientRegistrationTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/OIDCClientRegistrationTest.java
@@ -61,7 +61,6 @@ import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriBuilder;
import static org.junit.Assert.*;
-import static org.keycloak.testsuite.admin.AbstractAdminTest.loadJson;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
@@ -283,137 +282,6 @@ public class OIDCClientRegistrationTest extends AbstractClientRegistrationTest {
}
@Test
- public void createPairwiseClient() throws Exception {
- OIDCClientRepresentation clientRep = createRep();
- clientRep.setSubjectType("pairwise");
-
- OIDCClientRepresentation response = reg.oidc().create(clientRep);
- Assert.assertEquals("pairwise", response.getSubjectType());
- }
-
- @Test
- public void updateClientToPairwise() throws Exception {
- OIDCClientRepresentation response = create();
- Assert.assertEquals("public", response.getSubjectType());
-
- reg.auth(Auth.token(response));
- response.setSubjectType("pairwise");
- OIDCClientRepresentation updated = reg.oidc().update(response);
-
- Assert.assertEquals("pairwise", updated.getSubjectType());
- }
-
- @Test
- public void updateSectorIdentifierUri() throws Exception {
- OIDCClientRepresentation clientRep = createRep();
- clientRep.setSubjectType("pairwise");
- OIDCClientRepresentation response = reg.oidc().create(clientRep);
- Assert.assertEquals("pairwise", response.getSubjectType());
- Assert.assertNull(response.getSectorIdentifierUri());
-
- reg.auth(Auth.token(response));
-
- // Push redirect uris to the sector identifier URI
- List<String> sectorRedirects = new ArrayList<>();
- sectorRedirects.addAll(response.getRedirectUris());
- TestOIDCEndpointsApplicationResource oidcClientEndpointsResource = testingClient.testApp().oidcClientEndpoints();
- oidcClientEndpointsResource.setSectorIdentifierRedirectUris(sectorRedirects);
-
- response.setSectorIdentifierUri(TestApplicationResourceUrls.pairwiseSectorIdentifierUri());
-
- OIDCClientRepresentation updated = reg.oidc().update(response);
-
- Assert.assertEquals("pairwise", updated.getSubjectType());
- Assert.assertEquals(TestApplicationResourceUrls.pairwiseSectorIdentifierUri(), updated.getSectorIdentifierUri());
-
- }
-
- @Test
- public void createPairwiseClientWithSectorIdentifierURI() throws Exception {
- OIDCClientRepresentation clientRep = createRep();
-
- // Push redirect uris to the sector identifier URI
- List<String> sectorRedirects = new ArrayList<>();
- sectorRedirects.addAll(clientRep.getRedirectUris());
- TestOIDCEndpointsApplicationResource oidcClientEndpointsResource = testingClient.testApp().oidcClientEndpoints();
- oidcClientEndpointsResource.setSectorIdentifierRedirectUris(sectorRedirects);
-
- clientRep.setSubjectType("pairwise");
- clientRep.setSectorIdentifierUri(TestApplicationResourceUrls.pairwiseSectorIdentifierUri());
-
- OIDCClientRepresentation response = reg.oidc().create(clientRep);
- Assert.assertEquals("pairwise", response.getSubjectType());
- Assert.assertEquals(TestApplicationResourceUrls.pairwiseSectorIdentifierUri(), response.getSectorIdentifierUri());
- }
-
- @Test
- public void createPairwiseClientWithRedirectsToMultipleHostsWithoutSectorIdentifierURI() throws Exception {
- OIDCClientRepresentation clientRep = createRep();
-
- List<String> redirects = new ArrayList<>();
- redirects.add("http://redirect1");
- redirects.add("http://redirect2");
-
- clientRep.setSubjectType("pairwise");
- clientRep.setRedirectUris(redirects);
-
- assertCreateFail(clientRep, 400, "Without a configured Sector Identifier URI, client redirect URIs must not contain multiple host components.");
- }
-
- @Test
- public void createPairwiseClientWithRedirectsToMultipleHosts() throws Exception {
- OIDCClientRepresentation clientRep = createRep();
-
- // Push redirect URIs to the sector identifier URI
- List<String> redirects = new ArrayList<>();
- redirects.add("http://redirect1");
- redirects.add("http://redirect2");
- TestOIDCEndpointsApplicationResource oidcClientEndpointsResource = testingClient.testApp().oidcClientEndpoints();
- oidcClientEndpointsResource.setSectorIdentifierRedirectUris(redirects);
-
- clientRep.setSubjectType("pairwise");
- clientRep.setSectorIdentifierUri(TestApplicationResourceUrls.pairwiseSectorIdentifierUri());
- clientRep.setRedirectUris(redirects);
-
- OIDCClientRepresentation response = reg.oidc().create(clientRep);
- Assert.assertEquals("pairwise", response.getSubjectType());
- Assert.assertEquals(TestApplicationResourceUrls.pairwiseSectorIdentifierUri(), response.getSectorIdentifierUri());
- Assert.assertNames(response.getRedirectUris(), "http://redirect1", "http://redirect2");
- }
-
- @Test
- public void createPairwiseClientWithSectorIdentifierURIContainingMismatchedRedirects() throws Exception {
- OIDCClientRepresentation clientRep = createRep();
-
- // Push redirect uris to the sector identifier URI
- List<String> sectorRedirects = new ArrayList<>();
- sectorRedirects.add("http://someotherredirect");
- TestOIDCEndpointsApplicationResource oidcClientEndpointsResource = testingClient.testApp().oidcClientEndpoints();
- oidcClientEndpointsResource.setSectorIdentifierRedirectUris(sectorRedirects);
-
- clientRep.setSubjectType("pairwise");
- clientRep.setSectorIdentifierUri(TestApplicationResourceUrls.pairwiseSectorIdentifierUri());
-
- assertCreateFail(clientRep, 400, "Client redirect URIs does not match redirect URIs fetched from the Sector Identifier URI.");
- }
-
- @Test
- public void createPairwiseClientWithInvalidSectorIdentifierURI() throws Exception {
- OIDCClientRepresentation clientRep = createRep();
- clientRep.setSubjectType("pairwise");
- clientRep.setSectorIdentifierUri("malformed");
- assertCreateFail(clientRep, 400, "Invalid Sector Identifier URI.");
- }
-
- @Test
- public void createPairwiseClientWithUnreachableSectorIdentifierURI() throws Exception {
- OIDCClientRepresentation clientRep = createRep();
- clientRep.setSubjectType("pairwise");
- clientRep.setSectorIdentifierUri("http://localhost/dummy");
- assertCreateFail(clientRep, 400, "Failed to get redirect URIs from the Sector Identifier URI.");
- }
-
- @Test
public void testSignaturesRequired() throws Exception {
OIDCClientRepresentation clientRep = createRep();
clientRep.setUserinfoSignedResponseAlg(Algorithm.RS256.toString());
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/OIDCPairwiseClientRegistrationTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/OIDCPairwiseClientRegistrationTest.java
new file mode 100644
index 0000000..6d8e18c
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/OIDCPairwiseClientRegistrationTest.java
@@ -0,0 +1,328 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.testsuite.client;
+
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import javax.ws.rs.core.Response;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.keycloak.admin.client.resource.ClientResource;
+import org.keycloak.admin.client.resource.RealmResource;
+import org.keycloak.client.registration.Auth;
+import org.keycloak.client.registration.ClientRegistrationException;
+import org.keycloak.client.registration.HttpErrorException;
+import org.keycloak.protocol.oidc.mappers.SHA256PairwiseSubMapper;
+import org.keycloak.representations.AccessToken;
+import org.keycloak.representations.idm.ClientInitialAccessCreatePresentation;
+import org.keycloak.representations.idm.ClientInitialAccessPresentation;
+import org.keycloak.representations.idm.ProtocolMapperRepresentation;
+import org.keycloak.representations.idm.UserRepresentation;
+import org.keycloak.representations.oidc.OIDCClientRepresentation;
+import org.keycloak.testsuite.Assert;
+import org.keycloak.testsuite.admin.ApiUtil;
+import org.keycloak.testsuite.client.resources.TestApplicationResourceUrls;
+import org.keycloak.testsuite.client.resources.TestOIDCEndpointsApplicationResource;
+import org.keycloak.testsuite.util.ClientManager;
+import org.keycloak.testsuite.util.OAuthClient;
+
+import static org.junit.Assert.assertTrue;
+
+public class OIDCPairwiseClientRegistrationTest extends AbstractClientRegistrationTest {
+
+ @Before
+ public void before() throws Exception {
+ super.before();
+
+ ClientInitialAccessPresentation token = adminClient.realm(REALM_NAME).clientInitialAccess().create(new ClientInitialAccessCreatePresentation(0, 10));
+ reg.auth(Auth.token(token));
+ }
+
+ private OIDCClientRepresentation createRep() {
+ OIDCClientRepresentation client = new OIDCClientRepresentation();
+ client.setClientName("RegistrationAccessTokenTest");
+ client.setClientUri(OAuthClient.APP_ROOT);
+ client.setRedirectUris(Collections.singletonList(oauth.getRedirectUri()));
+ return client;
+ }
+
+ public OIDCClientRepresentation create() throws ClientRegistrationException {
+ OIDCClientRepresentation client = createRep();
+
+ OIDCClientRepresentation response = reg.oidc().create(client);
+
+ return response;
+ }
+
+
+ private void assertCreateFail(OIDCClientRepresentation client, int expectedStatusCode, String expectedErrorContains) {
+ try {
+ reg.oidc().create(client);
+ Assert.fail("Not expected to successfuly register client");
+ } catch (ClientRegistrationException expected) {
+ HttpErrorException httpEx = (HttpErrorException) expected.getCause();
+ Assert.assertEquals(expectedStatusCode, httpEx.getStatusLine().getStatusCode());
+ if (expectedErrorContains != null) {
+ assertTrue("Error response doesn't contain expected text", httpEx.getErrorResponse().contains(expectedErrorContains));
+ }
+ }
+ }
+
+ @Test
+ public void createPairwiseClient() throws Exception {
+ OIDCClientRepresentation clientRep = createRep();
+ clientRep.setSubjectType("pairwise");
+
+ OIDCClientRepresentation response = reg.oidc().create(clientRep);
+ Assert.assertEquals("pairwise", response.getSubjectType());
+ }
+
+ @Test
+ public void updateClientToPairwise() throws Exception {
+ OIDCClientRepresentation response = create();
+ Assert.assertEquals("public", response.getSubjectType());
+
+ reg.auth(Auth.token(response));
+ response.setSubjectType("pairwise");
+ OIDCClientRepresentation updated = reg.oidc().update(response);
+
+ Assert.assertEquals("pairwise", updated.getSubjectType());
+ }
+
+ @Test
+ public void updateSectorIdentifierUri() throws Exception {
+ OIDCClientRepresentation clientRep = createRep();
+ clientRep.setSubjectType("pairwise");
+ OIDCClientRepresentation response = reg.oidc().create(clientRep);
+ Assert.assertEquals("pairwise", response.getSubjectType());
+ Assert.assertNull(response.getSectorIdentifierUri());
+
+ reg.auth(Auth.token(response));
+
+ // Push redirect uris to the sector identifier URI
+ List<String> sectorRedirects = new ArrayList<>();
+ sectorRedirects.addAll(response.getRedirectUris());
+ TestOIDCEndpointsApplicationResource oidcClientEndpointsResource = testingClient.testApp().oidcClientEndpoints();
+ oidcClientEndpointsResource.setSectorIdentifierRedirectUris(sectorRedirects);
+
+ response.setSectorIdentifierUri(TestApplicationResourceUrls.pairwiseSectorIdentifierUri());
+
+ OIDCClientRepresentation updated = reg.oidc().update(response);
+
+ Assert.assertEquals("pairwise", updated.getSubjectType());
+ Assert.assertEquals(TestApplicationResourceUrls.pairwiseSectorIdentifierUri(), updated.getSectorIdentifierUri());
+
+ }
+
+ @Test
+ public void updateToPairwiseThroughAdminRESTSuccess() throws Exception {
+ OIDCClientRepresentation response = create();
+ Assert.assertEquals("public", response.getSubjectType());
+ Assert.assertNull(response.getSectorIdentifierUri());
+
+ // Push redirect uris to the sector identifier URI
+ List<String> sectorRedirects = new ArrayList<>();
+ sectorRedirects.addAll(response.getRedirectUris());
+ TestOIDCEndpointsApplicationResource oidcClientEndpointsResource = testingClient.testApp().oidcClientEndpoints();
+ oidcClientEndpointsResource.setSectorIdentifierRedirectUris(sectorRedirects);
+
+ String sectorIdentifierUri = TestApplicationResourceUrls.pairwiseSectorIdentifierUri();
+
+ // Add protocolMapper through admin REST endpoint
+ String clientId = response.getClientId();
+ ProtocolMapperRepresentation pairwiseProtMapper = SHA256PairwiseSubMapper.createPairwiseMapper(sectorIdentifierUri, null);
+ RealmResource realmResource = realmsResouce().realm("test");
+ ClientManager.realm(realmResource).clientId(clientId).addProtocolMapper(pairwiseProtMapper);
+
+ reg.auth(Auth.token(response));
+ OIDCClientRepresentation rep = reg.oidc().get(response.getClientId());
+ Assert.assertEquals("pairwise", rep.getSubjectType());
+ Assert.assertEquals(sectorIdentifierUri, rep.getSectorIdentifierUri());
+
+ }
+
+ @Test
+ public void updateToPairwiseThroughAdminRESTFailure() throws Exception {
+ OIDCClientRepresentation response = create();
+ Assert.assertEquals("public", response.getSubjectType());
+ Assert.assertNull(response.getSectorIdentifierUri());
+
+ // Push empty list to the sector identifier URI
+ TestOIDCEndpointsApplicationResource oidcClientEndpointsResource = testingClient.testApp().oidcClientEndpoints();
+ oidcClientEndpointsResource.setSectorIdentifierRedirectUris(new ArrayList<>());
+
+ String sectorIdentifierUri = TestApplicationResourceUrls.pairwiseSectorIdentifierUri();
+
+ // Add protocolMapper through admin REST endpoint
+ String clientId = response.getClientId();
+ ProtocolMapperRepresentation pairwiseProtMapper = SHA256PairwiseSubMapper.createPairwiseMapper(sectorIdentifierUri, null);
+ RealmResource realmResource = realmsResouce().realm("test");
+ ClientResource clientResource = ApiUtil.findClientByClientId(realmsResouce().realm("test"), clientId);
+ Response resp = clientResource.getProtocolMappers().createMapper(pairwiseProtMapper);
+ Assert.assertEquals(400, resp.getStatus());
+
+ // Assert still public
+ reg.auth(Auth.token(response));
+ OIDCClientRepresentation rep = reg.oidc().get(response.getClientId());
+ Assert.assertEquals("public", rep.getSubjectType());
+ Assert.assertNull(rep.getSectorIdentifierUri());
+ }
+
+ @Test
+ public void createPairwiseClientWithSectorIdentifierURI() throws Exception {
+ OIDCClientRepresentation clientRep = createRep();
+
+ // Push redirect uris to the sector identifier URI
+ List<String> sectorRedirects = new ArrayList<>();
+ sectorRedirects.addAll(clientRep.getRedirectUris());
+ TestOIDCEndpointsApplicationResource oidcClientEndpointsResource = testingClient.testApp().oidcClientEndpoints();
+ oidcClientEndpointsResource.setSectorIdentifierRedirectUris(sectorRedirects);
+
+ clientRep.setSubjectType("pairwise");
+ clientRep.setSectorIdentifierUri(TestApplicationResourceUrls.pairwiseSectorIdentifierUri());
+
+ OIDCClientRepresentation response = reg.oidc().create(clientRep);
+ Assert.assertEquals("pairwise", response.getSubjectType());
+ Assert.assertEquals(TestApplicationResourceUrls.pairwiseSectorIdentifierUri(), response.getSectorIdentifierUri());
+ }
+
+ @Test
+ public void createPairwiseClientWithRedirectsToMultipleHostsWithoutSectorIdentifierURI() throws Exception {
+ OIDCClientRepresentation clientRep = createRep();
+
+ List<String> redirects = new ArrayList<>();
+ redirects.add("http://redirect1");
+ redirects.add("http://redirect2");
+
+ clientRep.setSubjectType("pairwise");
+ clientRep.setRedirectUris(redirects);
+
+ assertCreateFail(clientRep, 400, "Without a configured Sector Identifier URI, client redirect URIs must not contain multiple host components.");
+ }
+
+ @Test
+ public void createPairwiseClientWithRedirectsToMultipleHosts() throws Exception {
+ OIDCClientRepresentation clientRep = createRep();
+
+ // Push redirect URIs to the sector identifier URI
+ List<String> redirects = new ArrayList<>();
+ redirects.add("http://redirect1");
+ redirects.add("http://redirect2");
+ TestOIDCEndpointsApplicationResource oidcClientEndpointsResource = testingClient.testApp().oidcClientEndpoints();
+ oidcClientEndpointsResource.setSectorIdentifierRedirectUris(redirects);
+
+ clientRep.setSubjectType("pairwise");
+ clientRep.setSectorIdentifierUri(TestApplicationResourceUrls.pairwiseSectorIdentifierUri());
+ clientRep.setRedirectUris(redirects);
+
+ OIDCClientRepresentation response = reg.oidc().create(clientRep);
+ Assert.assertEquals("pairwise", response.getSubjectType());
+ Assert.assertEquals(TestApplicationResourceUrls.pairwiseSectorIdentifierUri(), response.getSectorIdentifierUri());
+ Assert.assertNames(response.getRedirectUris(), "http://redirect1", "http://redirect2");
+ }
+
+ @Test
+ public void createPairwiseClientWithSectorIdentifierURIContainingMismatchedRedirects() throws Exception {
+ OIDCClientRepresentation clientRep = createRep();
+
+ // Push redirect uris to the sector identifier URI
+ List<String> sectorRedirects = new ArrayList<>();
+ sectorRedirects.add("http://someotherredirect");
+ TestOIDCEndpointsApplicationResource oidcClientEndpointsResource = testingClient.testApp().oidcClientEndpoints();
+ oidcClientEndpointsResource.setSectorIdentifierRedirectUris(sectorRedirects);
+
+ clientRep.setSubjectType("pairwise");
+ clientRep.setSectorIdentifierUri(TestApplicationResourceUrls.pairwiseSectorIdentifierUri());
+
+ assertCreateFail(clientRep, 400, "Client redirect URIs does not match redirect URIs fetched from the Sector Identifier URI.");
+ }
+
+ @Test
+ public void createPairwiseClientWithSectorIdentifierURIContainingMismatchedRedirectsPublicSubject() throws Exception {
+ OIDCClientRepresentation clientRep = createRep();
+
+ // Push redirect uris to the sector identifier URI
+ List<String> sectorRedirects = new ArrayList<>();
+ sectorRedirects.add("http://someotherredirect");
+ TestOIDCEndpointsApplicationResource oidcClientEndpointsResource = testingClient.testApp().oidcClientEndpoints();
+ oidcClientEndpointsResource.setSectorIdentifierRedirectUris(sectorRedirects);
+
+ clientRep.setSubjectType("public");
+ clientRep.setSectorIdentifierUri(TestApplicationResourceUrls.pairwiseSectorIdentifierUri());
+
+ assertCreateFail(clientRep, 400, "Client redirect URIs does not match redirect URIs fetched from the Sector Identifier URI.");
+ }
+
+ @Test
+ public void createPairwiseClientWithInvalidSectorIdentifierURI() throws Exception {
+ OIDCClientRepresentation clientRep = createRep();
+ clientRep.setSubjectType("pairwise");
+ clientRep.setSectorIdentifierUri("malformed");
+ assertCreateFail(clientRep, 400, "Invalid Sector Identifier URI.");
+ }
+
+ @Test
+ public void createPairwiseClientWithUnreachableSectorIdentifierURI() throws Exception {
+ OIDCClientRepresentation clientRep = createRep();
+ clientRep.setSubjectType("pairwise");
+ clientRep.setSectorIdentifierUri("http://localhost/dummy");
+ assertCreateFail(clientRep, 400, "Failed to get redirect URIs from the Sector Identifier URI.");
+ }
+
+ @Test
+ public void loginUserToPairwiseClient() throws Exception {
+ // Create public client
+ OIDCClientRepresentation publicClient = create();
+
+ // Login to public client
+ oauth.clientId(publicClient.getClientId());
+ OAuthClient.AuthorizationEndpointResponse loginResponse = oauth.doLogin("test-user@localhost", "password");
+ OAuthClient.AccessTokenResponse accessTokenResponse = oauth.doAccessTokenRequest(loginResponse.getCode(), publicClient.getClientSecret());
+ AccessToken accessToken = oauth.verifyToken(accessTokenResponse.getAccessToken());
+ Assert.assertEquals("test-user", accessToken.getPreferredUsername());
+ Assert.assertEquals("test-user@localhost", accessToken.getEmail());
+ String tokenUserId = accessToken.getSubject();
+
+ // Assert public client has same subject like userId
+ UserRepresentation user = realmsResouce().realm("test").users().search("test-user", 0, 1).get(0);
+ Assert.assertEquals(user.getId(), tokenUserId);
+
+ // Create pairwise client
+ OIDCClientRepresentation clientRep = createRep();
+ clientRep.setSubjectType("pairwise");
+ OIDCClientRepresentation pairwiseClient = reg.oidc().create(clientRep);
+ Assert.assertEquals("pairwise", pairwiseClient.getSubjectType());
+
+ // Login to pairwise client
+ oauth.clientId(pairwiseClient.getClientId());
+ oauth.openLoginForm();
+ loginResponse = new OAuthClient.AuthorizationEndpointResponse(oauth);
+ accessTokenResponse = oauth.doAccessTokenRequest(loginResponse.getCode(), pairwiseClient.getClientSecret());
+ accessToken = oauth.verifyToken(accessTokenResponse.getAccessToken());
+ Assert.assertEquals("test-user", accessToken.getPreferredUsername());
+ Assert.assertEquals("test-user@localhost", accessToken.getEmail());
+
+ // Assert pairwise client has different subject like userId
+ String pairwiseUserId = accessToken.getSubject();
+ Assert.assertNotEquals(pairwiseUserId, user.getId());
+ }
+}
diff --git a/themes/src/main/resources/theme/base/admin/resources/js/controllers/clients.js b/themes/src/main/resources/theme/base/admin/resources/js/controllers/clients.js
index 12af561..f46f2d8 100755
--- a/themes/src/main/resources/theme/base/admin/resources/js/controllers/clients.js
+++ b/themes/src/main/resources/theme/base/admin/resources/js/controllers/clients.js
@@ -1774,6 +1774,12 @@ module.controller('ClientProtocolMapperCreateCtrl', function($scope, realm, serv
var id = l.substring(l.lastIndexOf("/") + 1);
$location.url("/realms/" + realm.realm + '/clients/' + client.id + "/mappers/" + id);
Notifications.success("Mapper has been created.");
+ }, function(error) {
+ if (error.status == 400 && error.data.error_description) {
+ Notifications.error(error.data.error_description);
+ } else {
+ Notifications.error('Unexpected error when updating protocol mapper');
+ }
});
};