keycloak-aplcache

Changes

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');
+            }
         });
     };