diff --git a/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.2.0.Beta1.xml b/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.2.0.Beta1.xml index 0de32d8650..71fc2c4be4 100755 --- a/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.2.0.Beta1.xml +++ b/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.2.0.Beta1.xml @@ -5,14 +5,24 @@ - + - + + + + + + + + + + + @@ -80,11 +90,13 @@ + + diff --git a/core/src/main/java/org/keycloak/representations/idm/ProtocolMapperRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/ProtocolMapperRepresentation.java index e7219410de..4f132e106a 100755 --- a/core/src/main/java/org/keycloak/representations/idm/ProtocolMapperRepresentation.java +++ b/core/src/main/java/org/keycloak/representations/idm/ProtocolMapperRepresentation.java @@ -1,17 +1,21 @@ package org.keycloak.representations.idm; +import java.util.HashMap; +import java.util.Map; + /** * @author Bill Burke * @version $Revision: 1 $ */ public class ProtocolMapperRepresentation { protected String id; - protected String protocolClaim; + protected String name; protected String protocol; - protected String source; - protected String sourceAttribute; protected String protocolMapper; protected boolean appliedByDefault; + protected boolean consentRequired; + protected String consentText; + protected Map config = new HashMap(); public String getId() { @@ -22,12 +26,12 @@ public class ProtocolMapperRepresentation { this.id = id; } - public String getProtocolClaim() { - return protocolClaim; + public String getName() { + return name; } - public void setProtocolClaim(String protocolClaim) { - this.protocolClaim = protocolClaim; + public void setName(String name) { + this.name = name; } public String getProtocol() { @@ -38,14 +42,6 @@ public class ProtocolMapperRepresentation { this.protocol = protocol; } - public String getSourceAttribute() { - return sourceAttribute; - } - - public void setSourceAttribute(String sourceAttribute) { - this.sourceAttribute = sourceAttribute; - } - public boolean isAppliedByDefault() { return appliedByDefault; } @@ -54,14 +50,6 @@ public class ProtocolMapperRepresentation { this.appliedByDefault = appliedByDefault; } - public String getSource() { - return source; - } - - public void setSource(String source) { - this.source = source; - } - public String getProtocolMapper() { return protocolMapper; } @@ -69,4 +57,28 @@ public class ProtocolMapperRepresentation { public void setProtocolMapper(String protocolMapper) { this.protocolMapper = protocolMapper; } + + public Map getConfig() { + return config; + } + + public void setConfig(Map config) { + this.config = config; + } + + public boolean isConsentRequired() { + return consentRequired; + } + + public void setConsentRequired(boolean consentRequired) { + this.consentRequired = consentRequired; + } + + public String getConsentText() { + return consentText; + } + + public void setConsentText(String consentText) { + this.consentText = consentText; + } } diff --git a/docbook/reference/en/en-US/modules/adapter-config.xml b/docbook/reference/en/en-US/modules/adapter-config.xml index d4ef6423c3..21cc77a27c 100755 --- a/docbook/reference/en/en-US/modules/adapter-config.xml +++ b/docbook/reference/en/en-US/modules/adapter-config.xml @@ -14,7 +14,7 @@ "use-resource-role-mappings" : false, "enable-cors" : true, "cors-max-age" : 1000, - "cors-allowed-methods" : [ "POST", "PUT", "DELETE", "GET" ], + "cors-allowed-methods" : "POST, PUT, DELETE, GET", "bearer-only" : false, "enable-basic-auth" : false, "expose-token" : true, diff --git a/model/api/src/main/java/org/keycloak/models/ProtocolMapperModel.java b/model/api/src/main/java/org/keycloak/models/ProtocolMapperModel.java index 001bb9600f..f27a9ea5ee 100755 --- a/model/api/src/main/java/org/keycloak/models/ProtocolMapperModel.java +++ b/model/api/src/main/java/org/keycloak/models/ProtocolMapperModel.java @@ -1,5 +1,7 @@ package org.keycloak.models; +import java.util.Map; + /** * Specifies a mapping from user data to a protocol claim assertion. If protocolMapper is set, this points * to a @Provider that will perform the mapping. If you have this set, then no other attributes of this class need to be set. @@ -10,21 +12,14 @@ package org.keycloak.models; * @version $Revision: 1 $ */ public class ProtocolMapperModel { - public static enum Source { - USER_MODEL, - USER_ATTRIBUTE, - USER_SESSION_NOTE, - CLIENT_SESSION_NOTE - } - protected String id; protected String name; - protected String protocolClaim; protected String protocol; - protected Source source; - protected String sourceAttribute; protected String protocolMapper; + protected boolean consentRequired; + protected String consentText; protected boolean appliedByDefault; + protected Map config; public String getId() { @@ -43,14 +38,6 @@ public class ProtocolMapperModel { this.name = name; } - public String getProtocolClaim() { - return protocolClaim; - } - - public void setProtocolClaim(String protocolClaim) { - this.protocolClaim = protocolClaim; - } - public String getProtocol() { return protocol; } @@ -59,14 +46,6 @@ public class ProtocolMapperModel { this.protocol = protocol; } - public String getSourceAttribute() { - return sourceAttribute; - } - - public void setSourceAttribute(String sourceAttribute) { - this.sourceAttribute = sourceAttribute; - } - public boolean isAppliedByDefault() { return appliedByDefault; } @@ -75,14 +54,6 @@ public class ProtocolMapperModel { this.appliedByDefault = appliedByDefault; } - public Source getSource() { - return source; - } - - public void setSource(Source source) { - this.source = source; - } - public String getProtocolMapper() { return protocolMapper; } @@ -91,6 +62,30 @@ public class ProtocolMapperModel { this.protocolMapper = protocolMapper; } + public boolean isConsentRequired() { + return consentRequired; + } + + public void setConsentRequired(boolean consentRequired) { + this.consentRequired = consentRequired; + } + + public String getConsentText() { + return consentText; + } + + public void setConsentText(String consentText) { + this.consentText = consentText; + } + + public Map getConfig() { + return config; + } + + public void setConfig(Map config) { + this.config = config; + } + @Override public boolean equals(Object o) { if (this == o) return true; diff --git a/model/api/src/main/java/org/keycloak/models/RealmListenerHelper.java b/model/api/src/main/java/org/keycloak/models/RealmListenerHelper.java new file mode 100755 index 0000000000..b7c074dd92 --- /dev/null +++ b/model/api/src/main/java/org/keycloak/models/RealmListenerHelper.java @@ -0,0 +1,31 @@ +package org.keycloak.models; + +import java.util.LinkedList; + +/** + * @author Bill Burke + * @version $Revision: 1 $ + */ +public class RealmListenerHelper { + protected LinkedList list = new LinkedList(); + + public void registerListener(RealmProvider.RealmCreationListener listener) { + synchronized (list) { + list.add(listener); + } + } + + public void unregisterListener(RealmProvider.RealmCreationListener listener) { + synchronized (list) { + list.remove(listener); + } + } + + public void executeCreationListeners(RealmModel realm) { + synchronized (list) { + for (RealmProvider.RealmCreationListener listener : list) { + listener.created(realm); + } + } + } +} diff --git a/model/api/src/main/java/org/keycloak/models/RealmProvider.java b/model/api/src/main/java/org/keycloak/models/RealmProvider.java index 4ec3c87591..2c04cc1627 100755 --- a/model/api/src/main/java/org/keycloak/models/RealmProvider.java +++ b/model/api/src/main/java/org/keycloak/models/RealmProvider.java @@ -9,10 +9,16 @@ import java.util.List; * @version $Revision: 1 $ */ public interface RealmProvider extends Provider { + public interface RealmCreationListener { + void created(RealmModel realm); + } + // Note: The reason there are so many query methods here is for layering a cache on top of an persistent KeycloakSession RealmModel createRealm(String name); RealmModel createRealm(String id, String name); + void registerListener(RealmCreationListener listener); + void unregisterListener(RealmCreationListener listener); RealmModel getRealm(String id); RealmModel getRealmByName(String name); diff --git a/model/api/src/main/java/org/keycloak/models/entities/ProtocolMapperEntity.java b/model/api/src/main/java/org/keycloak/models/entities/ProtocolMapperEntity.java index de8f793eec..13f4cfc333 100755 --- a/model/api/src/main/java/org/keycloak/models/entities/ProtocolMapperEntity.java +++ b/model/api/src/main/java/org/keycloak/models/entities/ProtocolMapperEntity.java @@ -2,18 +2,21 @@ package org.keycloak.models.entities; import org.keycloak.models.ProtocolMapperModel; +import java.util.Map; + /** * @author Bill Burke * @version $Revision: 1 $ */ public class ProtocolMapperEntity { protected String id; - protected String protocolClaim; + protected String name; protected String protocol; - protected ProtocolMapperModel.Source source; - protected String sourceAttribute; protected String protocolMapper; protected boolean appliedByDefault; + protected boolean consentRequired; + protected String consentText; + protected Map config; public String getId() { return id; @@ -23,12 +26,12 @@ public class ProtocolMapperEntity { this.id = id; } - public String getProtocolClaim() { - return protocolClaim; + public String getName() { + return name; } - public void setProtocolClaim(String protocolClaim) { - this.protocolClaim = protocolClaim; + public void setName(String name) { + this.name = name; } public String getProtocol() { @@ -39,22 +42,6 @@ public class ProtocolMapperEntity { this.protocol = protocol; } - public ProtocolMapperModel.Source getSource() { - return source; - } - - public void setSource(ProtocolMapperModel.Source source) { - this.source = source; - } - - public String getSourceAttribute() { - return sourceAttribute; - } - - public void setSourceAttribute(String sourceAttribute) { - this.sourceAttribute = sourceAttribute; - } - public boolean isAppliedByDefault() { return appliedByDefault; } @@ -70,4 +57,28 @@ public class ProtocolMapperEntity { public void setProtocolMapper(String protocolMapper) { this.protocolMapper = protocolMapper; } + + public Map getConfig() { + return config; + } + + public void setConfig(Map config) { + this.config = config; + } + + public boolean isConsentRequired() { + return consentRequired; + } + + public void setConsentRequired(boolean consentRequired) { + this.consentRequired = consentRequired; + } + + public String getConsentText() { + return consentText; + } + + public void setConsentText(String consentText) { + this.consentText = consentText; + } } diff --git a/model/api/src/main/java/org/keycloak/models/entities/RealmEntity.java b/model/api/src/main/java/org/keycloak/models/entities/RealmEntity.java index 7db0bac78f..b511a32896 100755 --- a/model/api/src/main/java/org/keycloak/models/entities/RealmEntity.java +++ b/model/api/src/main/java/org/keycloak/models/entities/RealmEntity.java @@ -53,7 +53,7 @@ public class RealmEntity extends AbstractIdentifiableEntity { private List userFederationProviders = new ArrayList(); private List identityProviders = new ArrayList(); private List claimTypes = new ArrayList(); - private List claimMappings = new ArrayList(); + private List protocolMappers = new ArrayList(); private Map browserSecurityHeaders = new HashMap(); private Map smtpConfig = new HashMap(); @@ -401,12 +401,12 @@ public class RealmEntity extends AbstractIdentifiableEntity { this.claimTypes = claimTypes; } - public List getClaimMappings() { - return claimMappings; + public List getProtocolMappers() { + return protocolMappers; } - public void setClaimMappings(List claimMappings) { - this.claimMappings = claimMappings; + public void setProtocolMappers(List protocolMappers) { + this.protocolMappers = protocolMappers; } } diff --git a/model/api/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java b/model/api/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java index 5d0c582615..69d5477f07 100755 --- a/model/api/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java +++ b/model/api/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java @@ -341,11 +341,14 @@ public class ModelToRepresentation { ProtocolMapperRepresentation rep = new ProtocolMapperRepresentation(); rep.setId(model.getId()); rep.setProtocol(model.getProtocol()); - rep.setProtocolClaim(model.getProtocolClaim()); - rep.setSourceAttribute(model.getSourceAttribute()); - rep.setSource(model.getSource().name()); + Map config = new HashMap(); + config.putAll(model.getConfig()); + rep.setConfig(config); + rep.setName(model.getName()); rep.setProtocolMapper(model.getProtocolMapper()); rep.setAppliedByDefault(model.isAppliedByDefault()); + rep.setConsentText(model.getConsentText()); + rep.setConsentRequired(model.isConsentRequired()); return rep; } diff --git a/model/api/src/main/java/org/keycloak/models/utils/RepresentationToModel.java b/model/api/src/main/java/org/keycloak/models/utils/RepresentationToModel.java index 6c41613797..2fb49af311 100755 --- a/model/api/src/main/java/org/keycloak/models/utils/RepresentationToModel.java +++ b/model/api/src/main/java/org/keycloak/models/utils/RepresentationToModel.java @@ -118,7 +118,7 @@ public class RepresentationToModel { importIdentityProviders(rep, newRealm); importClaimTypes(rep, newRealm); - importProtocolClaimMappings(rep, newRealm); + importProtocolMappers(rep, newRealm); if (rep.getApplications() != null) { Map appMap = createApplications(rep, newRealm); @@ -772,10 +772,15 @@ public class RepresentationToModel { } } - private static void importProtocolClaimMappings(RealmRepresentation rep, RealmModel newRealm) { + private static void importProtocolMappers(RealmRepresentation rep, RealmModel newRealm) { if (rep.getProtocolClaimMappings() != null) { + // we make sure we don't recreate mappers that are automatically created by the protocol providers. for (ProtocolMapperRepresentation representation : rep.getProtocolClaimMappings()) { - newRealm.addProtocolMapper(toModel(representation)); + if (representation.getId() == null || newRealm.getProtocolMapperById(representation.getId()) == null) { + newRealm.addProtocolMapper(toModel(representation)); + } else { + newRealm.updateProtocolMapper(toModel(representation)); + } } } } @@ -808,12 +813,13 @@ public class RepresentationToModel { public static ProtocolMapperModel toModel(ProtocolMapperRepresentation rep) { ProtocolMapperModel model = new ProtocolMapperModel(); model.setId(rep.getId()); + model.setName(rep.getName()); model.setAppliedByDefault(rep.isAppliedByDefault()); - model.setSource(ProtocolMapperModel.Source.valueOf(rep.getSource())); - model.setSourceAttribute(rep.getSourceAttribute()); + model.setConsentRequired(rep.isConsentRequired()); + model.setConsentText(rep.getConsentText()); model.setProtocol(rep.getProtocol()); - model.setProtocolClaim(rep.getProtocolClaim()); model.setProtocolMapper(rep.getProtocolMapper()); + model.setConfig(rep.getConfig()); return model; } } diff --git a/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/DefaultCacheRealmProvider.java b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/DefaultCacheRealmProvider.java index a1d232ecb3..9419c91ee2 100755 --- a/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/DefaultCacheRealmProvider.java +++ b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/DefaultCacheRealmProvider.java @@ -50,6 +50,17 @@ public class DefaultCacheRealmProvider implements CacheRealmProvider { session.getTransaction().enlistAfterCompletion(getTransaction()); } + @Override + public void registerListener(RealmCreationListener listener) { + getDelegate().registerListener(listener); + } + + @Override + public void unregisterListener(RealmCreationListener listener) { + getDelegate().unregisterListener(listener); + + } + @Override public boolean isEnabled() { return cache.isEnabled(); diff --git a/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/NoCacheRealmProvider.java b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/NoCacheRealmProvider.java index e7b1551c2d..9896e51281 100755 --- a/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/NoCacheRealmProvider.java +++ b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/NoCacheRealmProvider.java @@ -38,6 +38,16 @@ public class NoCacheRealmProvider implements CacheRealmProvider { return delegate; } + @Override + public void registerListener(RealmCreationListener listener) { + getDelegate().registerListener(listener); + } + + @Override + public void unregisterListener(RealmCreationListener listener) { + getDelegate().unregisterListener(listener); + + } @Override public void registerRealmInvalidation(String id) { } diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/ClientAdapter.java b/model/jpa/src/main/java/org/keycloak/models/jpa/ClientAdapter.java index 9faace4dde..c06cdecf57 100755 --- a/model/jpa/src/main/java/org/keycloak/models/jpa/ClientAdapter.java +++ b/model/jpa/src/main/java/org/keycloak/models/jpa/ClientAdapter.java @@ -362,11 +362,16 @@ public abstract class ClientAdapter implements ClientModel { for (ProtocolMapperEntity entity : this.entity.getProtocolMappers()) { ProtocolMapperModel mapping = new ProtocolMapperModel(); mapping.setId(entity.getId()); + mapping.setName(entity.getName()); mapping.setProtocol(entity.getProtocol()); - mapping.setProtocolClaim(entity.getProtocolClaim()); mapping.setAppliedByDefault(entity.isAppliedByDefault()); - mapping.setSource(ProtocolMapperModel.Source.valueOf(entity.getSource())); - mapping.setSourceAttribute(entity.getSourceAttribute()); + mapping.setConsentRequired(entity.isConsentRequired()); + mapping.setConsentText(entity.getConsentText()); + Map config = new HashMap(); + if (entity.getConfig() != null) { + config.putAll(entity.getConfig()); + } + mapping.setConfig(config); mappings.add(mapping); } return mappings; diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/JpaRealmProvider.java b/model/jpa/src/main/java/org/keycloak/models/jpa/JpaRealmProvider.java index 755fb6bff9..5529b83749 100755 --- a/model/jpa/src/main/java/org/keycloak/models/jpa/JpaRealmProvider.java +++ b/model/jpa/src/main/java/org/keycloak/models/jpa/JpaRealmProvider.java @@ -3,6 +3,7 @@ package org.keycloak.models.jpa; import org.keycloak.models.ApplicationModel; import org.keycloak.models.KeycloakSession; import org.keycloak.models.OAuthClientModel; +import org.keycloak.models.RealmListenerHelper; import org.keycloak.models.RealmModel; import org.keycloak.models.RealmProvider; import org.keycloak.models.RoleModel; @@ -25,10 +26,24 @@ import java.util.List; public class JpaRealmProvider implements RealmProvider { private final KeycloakSession session; protected EntityManager em; + protected RealmListenerHelper listeners; - public JpaRealmProvider(KeycloakSession session, EntityManager em) { + + public JpaRealmProvider(KeycloakSession session, EntityManager em, RealmListenerHelper listeners) { this.session = session; this.em = em; + this.listeners = listeners; + } + + @Override + public void registerListener(RealmCreationListener listener) { + listeners.registerListener(listener); + } + + @Override + public void unregisterListener(RealmCreationListener listener) { + listeners.unregisterListener(listener); + } @Override @@ -43,7 +58,9 @@ public class JpaRealmProvider implements RealmProvider { realm.setId(id); em.persist(realm); em.flush(); - return new RealmAdapter(session, em, realm); + RealmModel model = new RealmAdapter(session, em, realm); + listeners.executeCreationListeners(model); + return model; } @Override diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/JpaRealmProviderFactory.java b/model/jpa/src/main/java/org/keycloak/models/jpa/JpaRealmProviderFactory.java index 7e090c2142..4869ca745c 100755 --- a/model/jpa/src/main/java/org/keycloak/models/jpa/JpaRealmProviderFactory.java +++ b/model/jpa/src/main/java/org/keycloak/models/jpa/JpaRealmProviderFactory.java @@ -3,6 +3,7 @@ package org.keycloak.models.jpa; import org.keycloak.Config; import org.keycloak.connections.jpa.JpaConnectionProvider; import org.keycloak.models.KeycloakSession; +import org.keycloak.models.RealmListenerHelper; import org.keycloak.models.RealmProvider; import org.keycloak.models.RealmProviderFactory; @@ -14,6 +15,8 @@ import javax.persistence.EntityManager; */ public class JpaRealmProviderFactory implements RealmProviderFactory { + protected RealmListenerHelper listeners = new RealmListenerHelper(); + @Override public void init(Config.Scope config) { } @@ -26,7 +29,7 @@ public class JpaRealmProviderFactory implements RealmProviderFactory { @Override public RealmProvider create(KeycloakSession session) { EntityManager em = session.getProvider(JpaConnectionProvider.class).getEntityManager(); - return new JpaRealmProvider(session, em); + return new JpaRealmProvider(session, em, listeners); } @Override diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java b/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java index f294bbde4b..7578fd0ae0 100755 --- a/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java +++ b/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java @@ -1262,12 +1262,17 @@ public class RealmAdapter implements RealmModel { for (ProtocolMapperEntity entity : realm.getProtocolClaimMappings()) { ProtocolMapperModel mapping = new ProtocolMapperModel(); mapping.setId(entity.getId()); + mapping.setName(entity.getName()); mapping.setProtocol(entity.getProtocol()); - mapping.setProtocolClaim(entity.getProtocolClaim()); mapping.setAppliedByDefault(entity.isAppliedByDefault()); - mapping.setSource(ProtocolMapperModel.Source.valueOf(entity.getSource())); - mapping.setSourceAttribute(entity.getSourceAttribute()); mapping.setProtocolMapper(entity.getProtocolMapper()); + mapping.setConsentRequired(entity.isConsentRequired()); + mapping.setConsentText(entity.getConsentText()); + Map config = new HashMap(); + if (entity.getConfig() != null) { + config.putAll(entity.getConfig()); + } + mapping.setConfig(config); mappings.add(mapping); } return mappings; @@ -1278,26 +1283,29 @@ public class RealmAdapter implements RealmModel { String id = model.getId() == null ? KeycloakModelUtils.generateId() : model.getId(); ProtocolMapperEntity entity = new ProtocolMapperEntity(); entity.setId(id); - entity.setSourceAttribute(model.getSourceAttribute()); + entity.setName(model.getName()); entity.setProtocol(model.getProtocol()); - entity.setProtocolClaim(model.getProtocolClaim()); - entity.setSource(model.getSource().name()); entity.setProtocolMapper(model.getProtocolMapper()); entity.setAppliedByDefault(model.isAppliedByDefault()); entity.setRealm(realm); + entity.setConfig(model.getConfig()); + entity.setConsentRequired(model.isConsentRequired()); + entity.setConsentText(model.getConsentText()); + em.persist(entity); ProtocolMapperModel mapping = new ProtocolMapperModel(); mapping.setId(entity.getId()); + mapping.setName(model.getName()); mapping.setProtocol(entity.getProtocol()); mapping.setProtocolMapper(entity.getProtocolMapper()); - mapping.setProtocolClaim(entity.getProtocolClaim()); mapping.setAppliedByDefault(entity.isAppliedByDefault()); - mapping.setSource(ProtocolMapperModel.Source.valueOf(entity.getSource())); - mapping.setSourceAttribute(entity.getSourceAttribute()); + mapping.setConfig(model.getConfig()); + mapping.setConsentRequired(entity.isConsentRequired()); + mapping.setConsentText(entity.getConsentText()); return mapping; } - protected ProtocolMapperEntity getProtocolClaimMapping(String id) { + protected ProtocolMapperEntity getProtocolMapper(String id) { for (ProtocolMapperEntity entity : realm.getProtocolClaimMappings()) { if (entity.getId().equals(id)) { return entity; @@ -1309,7 +1317,7 @@ public class RealmAdapter implements RealmModel { @Override public void removeProtocolMapper(ProtocolMapperModel mapping) { - ProtocolMapperEntity toDelete = getProtocolClaimMapping(mapping.getId()); + ProtocolMapperEntity toDelete = getProtocolMapper(mapping.getId()); if (toDelete != null) { realm.getProtocolClaimMappings().remove(toDelete); em.remove(toDelete); @@ -1319,27 +1327,35 @@ public class RealmAdapter implements RealmModel { @Override public void updateProtocolMapper(ProtocolMapperModel mapping) { - ProtocolMapperEntity entity = getProtocolClaimMapping(mapping.getId()); - entity.setProtocol(mapping.getProtocol()); - entity.setProtocolClaim(mapping.getProtocolClaim()); + ProtocolMapperEntity entity = getProtocolMapper(mapping.getId()); + entity.setProtocolMapper(mapping.getProtocolMapper()); entity.setAppliedByDefault(mapping.isAppliedByDefault()); - entity.setSource(mapping.getSource().name()); - entity.setSourceAttribute(mapping.getSourceAttribute()); + entity.setConsentRequired(mapping.isConsentRequired()); + entity.setConsentText(mapping.getConsentText()); + if (entity.getConfig() == null) { + entity.setConfig(mapping.getConfig()); + } else { + entity.getConfig().clear(); + entity.getConfig().putAll(mapping.getConfig()); + } em.flush(); } @Override public ProtocolMapperModel getProtocolMapperById(String id) { - ProtocolMapperEntity entity = getProtocolClaimMapping(id); + ProtocolMapperEntity entity = getProtocolMapper(id); if (entity == null) return null; ProtocolMapperModel mapping = new ProtocolMapperModel(); mapping.setId(entity.getId()); + mapping.setName(entity.getName()); mapping.setProtocol(entity.getProtocol()); - mapping.setProtocolClaim(entity.getProtocolClaim()); mapping.setAppliedByDefault(entity.isAppliedByDefault()); - mapping.setSource(ProtocolMapperModel.Source.valueOf(entity.getSource())); - mapping.setSourceAttribute(entity.getSourceAttribute()); + mapping.setConsentRequired(entity.isConsentRequired()); + mapping.setConsentText(entity.getConsentText()); + Map config = new HashMap(); + if (entity.getConfig() != null) config.putAll(entity.getConfig()); + mapping.setConfig(config); return mapping; } } \ No newline at end of file diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/ProtocolMapperEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/ProtocolMapperEntity.java index 59e077fb3f..bd8bbe2a0d 100755 --- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/ProtocolMapperEntity.java +++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/ProtocolMapperEntity.java @@ -1,14 +1,18 @@ package org.keycloak.models.jpa.entities; +import javax.persistence.CollectionTable; import javax.persistence.Column; +import javax.persistence.ElementCollection; import javax.persistence.Entity; import javax.persistence.FetchType; import javax.persistence.Id; import javax.persistence.JoinColumn; import javax.persistence.ManyToOne; +import javax.persistence.MapKeyColumn; import javax.persistence.NamedQueries; import javax.persistence.NamedQuery; import javax.persistence.Table; +import java.util.Map; /** * @author Bill Burke @@ -25,18 +29,25 @@ public class ProtocolMapperEntity { @Column(name="ID", length = 36) protected String id; - @Column(name = "PROTOCOL_CLAIM") - protected String protocolClaim; + @Column(name="NAME") + protected String name; + @Column(name = "PROTOCOL") protected String protocol; - @Column(name = "SOURCE") - protected String source; - @Column(name = "SOURCE_ATTRIBUTE") - protected String sourceAttribute; @Column(name = "PROTOCOL_MAPPER_NAME") protected String protocolMapper; @Column(name = "APPLIED_BY_DEFAULT") protected boolean appliedByDefault; + @Column(name="CONSENT_REQUIRED") + protected boolean consentRequired; + @Column(name="CONSENT_TEXT") + protected String consentText; + + @ElementCollection + @MapKeyColumn(name="name") + @Column(name="value") + @CollectionTable(name="PROTOCOL_MAPPER_CONFIG", joinColumns={ @JoinColumn(name="PROTOCOL_MAPPER_ID") }) + private Map config; @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "REALM_ID") @@ -50,12 +61,12 @@ public class ProtocolMapperEntity { this.id = id; } - public String getProtocolClaim() { - return protocolClaim; + public String getName() { + return name; } - public void setProtocolClaim(String protocolClaim) { - this.protocolClaim = protocolClaim; + public void setName(String name) { + this.name = name; } public String getProtocol() { @@ -66,22 +77,6 @@ public class ProtocolMapperEntity { this.protocol = protocol; } - public String getSource() { - return source; - } - - public void setSource(String source) { - this.source = source; - } - - public String getSourceAttribute() { - return sourceAttribute; - } - - public void setSourceAttribute(String sourceAttribute) { - this.sourceAttribute = sourceAttribute; - } - public String getProtocolMapper() { return protocolMapper; } @@ -98,6 +93,14 @@ public class ProtocolMapperEntity { this.appliedByDefault = appliedByDefault; } + public Map getConfig() { + return config; + } + + public void setConfig(Map config) { + this.config = config; + } + public RealmEntity getRealm() { return realm; } @@ -106,6 +109,22 @@ public class ProtocolMapperEntity { this.realm = realm; } + public boolean isConsentRequired() { + return consentRequired; + } + + public void setConsentRequired(boolean consentRequired) { + this.consentRequired = consentRequired; + } + + public String getConsentText() { + return consentText; + } + + public void setConsentText(String consentText) { + this.consentText = consentText; + } + @Override public boolean equals(Object o) { if (this == o) return true; diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoRealmProvider.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoRealmProvider.java index 691bce4925..3d6b735096 100755 --- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoRealmProvider.java +++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoRealmProvider.java @@ -8,6 +8,7 @@ import org.keycloak.connections.mongo.api.context.MongoStoreInvocationContext; import org.keycloak.models.ApplicationModel; import org.keycloak.models.KeycloakSession; import org.keycloak.models.OAuthClientModel; +import org.keycloak.models.RealmListenerHelper; import org.keycloak.models.RealmModel; import org.keycloak.models.RealmProvider; import org.keycloak.models.RoleModel; @@ -27,10 +28,12 @@ public class MongoRealmProvider implements RealmProvider { private final MongoStoreInvocationContext invocationContext; private final KeycloakSession session; + protected RealmListenerHelper listeners; - public MongoRealmProvider(KeycloakSession session, MongoStoreInvocationContext invocationContext) { + public MongoRealmProvider(KeycloakSession session, MongoStoreInvocationContext invocationContext, RealmListenerHelper listeners) { this.session = session; this.invocationContext = invocationContext; + this.listeners = listeners; } @Override @@ -38,6 +41,18 @@ public class MongoRealmProvider implements RealmProvider { // TODO } + @Override + public void registerListener(RealmCreationListener listener) { + listeners.registerListener(listener); + } + + @Override + public void unregisterListener(RealmCreationListener listener) { + listeners.unregisterListener(listener); + + } + + @Override public RealmModel createRealm(String name) { return createRealm(KeycloakModelUtils.generateId(), name); @@ -51,7 +66,9 @@ public class MongoRealmProvider implements RealmProvider { getMongoStore().insertEntity(newRealm, invocationContext); - return new RealmAdapter(session, newRealm, invocationContext); + RealmModel model = new RealmAdapter(session, newRealm, invocationContext); + listeners.executeCreationListeners(model); + return model; } @Override diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoRealmProviderFactory.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoRealmProviderFactory.java index 96067533a4..c8f2f1186a 100755 --- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoRealmProviderFactory.java +++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoRealmProviderFactory.java @@ -4,6 +4,7 @@ import org.jboss.logging.Logger; import org.keycloak.Config; import org.keycloak.connections.mongo.MongoConnectionProvider; import org.keycloak.models.KeycloakSession; +import org.keycloak.models.RealmListenerHelper; import org.keycloak.models.RealmProvider; import org.keycloak.models.RealmProviderFactory; @@ -15,6 +16,8 @@ import org.keycloak.models.RealmProviderFactory; public class MongoRealmProviderFactory implements RealmProviderFactory { protected static final Logger logger = Logger.getLogger(MongoRealmProviderFactory.class); + protected RealmListenerHelper listeners = new RealmListenerHelper(); + @Override public String getId() { return "mongo"; @@ -27,7 +30,7 @@ public class MongoRealmProviderFactory implements RealmProviderFactory { @Override public RealmProvider create(KeycloakSession session) { MongoConnectionProvider connection = session.getProvider(MongoConnectionProvider.class); - return new MongoRealmProvider(session, connection.getInvocationContext()); + return new MongoRealmProvider(session, connection.getInvocationContext(), listeners); } @Override diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java index 0dfac8be71..9b20f2a869 100755 --- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java +++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java @@ -789,14 +789,19 @@ public class RealmAdapter extends AbstractMongoAdapter impleme @Override public Set getProtocolMappers() { Set result = new HashSet(); - for (ProtocolMapperEntity entity : realm.getClaimMappings()) { + for (ProtocolMapperEntity entity : realm.getProtocolMappers()) { ProtocolMapperModel mapping = new ProtocolMapperModel(); mapping.setId(entity.getId()); - mapping.setProtocolClaim(entity.getProtocolClaim()); + mapping.setName(entity.getName()); mapping.setProtocol(entity.getProtocol()); - mapping.setSource(entity.getSource()); - mapping.setSourceAttribute(entity.getSourceAttribute()); mapping.setAppliedByDefault(entity.isAppliedByDefault()); + mapping.setConsentRequired(entity.isConsentRequired()); + mapping.setConsentText(entity.getConsentText()); + Map config = new HashMap(); + if (entity.getConfig() != null) { + config.putAll(entity.getConfig()); + } + mapping.setConfig(config); } return result; } @@ -806,30 +811,31 @@ public class RealmAdapter extends AbstractMongoAdapter impleme ProtocolMapperEntity entity = new ProtocolMapperEntity(); if (model.getId() != null) entity.setId(model.getId()); else entity.setId(KeycloakModelUtils.generateId()); - entity.setSourceAttribute(model.getSourceAttribute()); entity.setProtocol(model.getProtocol()); - entity.setProtocolClaim(model.getProtocolClaim()); - entity.setSource(model.getSource()); + entity.setName(model.getName()); entity.setAppliedByDefault(model.isAppliedByDefault()); entity.setProtocolMapper(model.getProtocolMapper()); - realm.getClaimMappings().add(entity); + entity.setConfig(model.getConfig()); + entity.setConsentRequired(model.isConsentRequired()); + entity.setConsentText(model.getConsentText()); + realm.getProtocolMappers().add(entity); updateRealm(); ProtocolMapperModel mapping = new ProtocolMapperModel(); mapping.setId(entity.getId()); - mapping.setProtocol(entity.getProtocol()); - mapping.setProtocolClaim(entity.getProtocolClaim()); - mapping.setAppliedByDefault(entity.isAppliedByDefault()); - mapping.setSource(entity.getSource()); - mapping.setSourceAttribute(entity.getSourceAttribute()); - mapping.setProtocolMapper(entity.getProtocolMapper()); + mapping.setProtocol(model.getProtocol()); + mapping.setAppliedByDefault(model.isAppliedByDefault()); + mapping.setProtocolMapper(model.getProtocolMapper()); + mapping.setConfig(model.getConfig()); + mapping.setConsentText(model.getConsentText()); + mapping.setConsentRequired(model.isConsentRequired()); return mapping; } @Override public void removeProtocolMapper(ProtocolMapperModel mapping) { - for (ProtocolMapperEntity entity : realm.getClaimMappings()) { + for (ProtocolMapperEntity entity : realm.getProtocolMappers()) { if (entity.getId().equals(mapping.getId())) { - realm.getClaimMappings().remove(entity); + realm.getProtocolMappers().remove(entity); updateRealm(); break; } @@ -837,8 +843,8 @@ public class RealmAdapter extends AbstractMongoAdapter impleme } - protected ProtocolMapperEntity getProtocolClaimMapping(String id) { - for (ProtocolMapperEntity entity : realm.getClaimMappings()) { + protected ProtocolMapperEntity getProtocolMapper(String id) { + for (ProtocolMapperEntity entity : realm.getProtocolMappers()) { if (entity.getId().equals(id)) { return entity; } @@ -850,29 +856,36 @@ public class RealmAdapter extends AbstractMongoAdapter impleme @Override public void updateProtocolMapper(ProtocolMapperModel mapping) { - ProtocolMapperEntity entity = getProtocolClaimMapping(mapping.getId()); - entity.setProtocol(mapping.getProtocol()); - entity.setProtocolClaim(mapping.getProtocolClaim()); + ProtocolMapperEntity entity = getProtocolMapper(mapping.getId()); entity.setAppliedByDefault(mapping.isAppliedByDefault()); - entity.setSource(mapping.getSource()); - entity.setSourceAttribute(mapping.getSourceAttribute()); entity.setProtocolMapper(mapping.getProtocolMapper()); + entity.setConsentRequired(mapping.isConsentRequired()); + entity.setConsentText(mapping.getConsentText()); + if (entity.getConfig() != null) { + entity.getConfig().clear(); + entity.getConfig().putAll(mapping.getConfig()); + } else { + entity.setConfig(mapping.getConfig()); + } updateRealm(); } @Override public ProtocolMapperModel getProtocolMapperById(String id) { - ProtocolMapperEntity entity = getProtocolClaimMapping(id); + ProtocolMapperEntity entity = getProtocolMapper(id); if (entity == null) return null; ProtocolMapperModel mapping = new ProtocolMapperModel(); mapping.setId(entity.getId()); + mapping.setName(entity.getName()); mapping.setProtocol(entity.getProtocol()); - mapping.setProtocolClaim(entity.getProtocolClaim()); mapping.setAppliedByDefault(entity.isAppliedByDefault()); - mapping.setSource(entity.getSource()); - mapping.setSourceAttribute(entity.getSourceAttribute()); mapping.setProtocolMapper(entity.getProtocolMapper()); + mapping.setConsentRequired(entity.isConsentRequired()); + mapping.setConsentText(entity.getConsentText()); + Map config = new HashMap(); + if (entity.getConfig() != null) config.putAll(config); + mapping.setConfig(config); return mapping; } diff --git a/services/src/main/java/org/keycloak/protocol/ProtocolMapper.java b/services/src/main/java/org/keycloak/protocol/ProtocolMapper.java index 8697d72f20..c2028479f5 100755 --- a/services/src/main/java/org/keycloak/protocol/ProtocolMapper.java +++ b/services/src/main/java/org/keycloak/protocol/ProtocolMapper.java @@ -3,6 +3,8 @@ package org.keycloak.protocol; import org.keycloak.provider.Provider; import org.keycloak.provider.ProviderFactory; +import java.util.List; + /** * @author Bill Burke * @version $Revision: 1 $ @@ -10,4 +12,36 @@ import org.keycloak.provider.ProviderFactory; public interface ProtocolMapper extends Provider, ProviderFactory { String getProtocol(); String getDisplayType(); + + public static class ConfigProperty { + protected String name; + protected String label; + protected String helpText; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getLabel() { + return label; + } + + public void setLabel(String label) { + this.label = label; + } + + public String getHelpText() { + return helpText; + } + + public void setHelpText(String helpText) { + this.helpText = helpText; + } + } + + List getConfigProperties(); } diff --git a/services/src/main/java/org/keycloak/protocol/oidc/OIDCAttributeToTokenMapper.java b/services/src/main/java/org/keycloak/protocol/oidc/OIDCAttributeToTokenMapper.java deleted file mode 100755 index 9ff207f324..0000000000 --- a/services/src/main/java/org/keycloak/protocol/oidc/OIDCAttributeToTokenMapper.java +++ /dev/null @@ -1,84 +0,0 @@ -package org.keycloak.protocol.oidc; - -import org.keycloak.models.ClientSessionModel; -import org.keycloak.models.KeycloakSession; -import org.keycloak.models.ProtocolMapperModel; -import org.keycloak.models.UserModel; -import org.keycloak.models.UserSessionModel; -import org.keycloak.representations.AccessToken; - -import java.lang.reflect.Method; -import java.util.HashMap; -import java.util.Map; - -/** - * Mappings user data to an ID Token claim. Source can be from UserModel.getAttributes(), a get method on UserModel, UserSession.getNote - * or ClientSession.getNote. Claim can be a full qualified nested object name, i.e. "address.country". This will create a nested - * json object within the toke claim. - * - * @author Bill Burke - * @version $Revision: 1 $ - */ -public class OIDCAttributeToTokenMapper extends AbstractOIDCProtocolMapper implements OIDCAccessTokenTransformer { - @Override - public String getId() { - return "oidc-attribute-claim-mapper"; - } - - @Override - public String getDisplayType() { - return "Attribute Claim Mapper"; - } - - @Override - public AccessToken transformToken(AccessToken token, ProtocolMapperModel mappingModel, KeycloakSession session, - UserSessionModel userSession, ClientSessionModel clientSession) { - String attributeValue = null; - UserModel user = userSession.getUser(); - switch (mappingModel.getSource()) { - case USER_ATTRIBUTE: - attributeValue = user.getAttribute(mappingModel.getSourceAttribute()); - break; - case USER_SESSION_NOTE: - attributeValue = userSession.getNote(mappingModel.getSourceAttribute()); - break; - case CLIENT_SESSION_NOTE: - attributeValue = clientSession.getNote(mappingModel.getSourceAttribute()); - break; - case USER_MODEL: - attributeValue = getUserModelValue(user, mappingModel); - break; - } - if (attributeValue == null) return token; - String[] split = mappingModel.getProtocolClaim().split("."); - Map jsonObject = token.getOtherClaims(); - for (int i = 0; i < split.length; i++) { - if (i == split.length - 1) { - jsonObject.put(split[i], attributeValue); - } else { - Map nested = (Map)jsonObject.get(split[i]); - if (nested == null) { - nested = new HashMap(); - jsonObject.put(split[i], nested); - jsonObject = nested; - } - } - } - return token; - } - - protected String getUserModelValue(UserModel user, ProtocolMapperModel model) { - String sourceAttribute = model.getSourceAttribute(); - if (sourceAttribute == null) return null; - - String methodName = "get" + Character.toUpperCase(sourceAttribute.charAt(0)) + sourceAttribute.substring(1); - try { - Method method = UserModel.class.getMethod(methodName); - Object val = method.invoke(user); - if (val != null) return val.toString(); - } catch (Exception ignore) { - - } - return 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 3c4a800ad0..b0e3605080 100755 --- a/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocolFactory.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocolFactory.java @@ -20,6 +20,7 @@ public class OIDCLoginProtocolFactory implements LoginProtocolFactory { @Override public void init(Config.Scope config) { + System.out.println("here"); } diff --git a/services/src/main/java/org/keycloak/protocol/oidc/AbstractOIDCProtocolMapper.java b/services/src/main/java/org/keycloak/protocol/oidc/mappers/AbstractOIDCProtocolMapper.java similarity index 83% rename from services/src/main/java/org/keycloak/protocol/oidc/AbstractOIDCProtocolMapper.java rename to services/src/main/java/org/keycloak/protocol/oidc/mappers/AbstractOIDCProtocolMapper.java index 9b3fb60192..dc949834d9 100755 --- a/services/src/main/java/org/keycloak/protocol/oidc/AbstractOIDCProtocolMapper.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/mappers/AbstractOIDCProtocolMapper.java @@ -1,8 +1,9 @@ -package org.keycloak.protocol.oidc; +package org.keycloak.protocol.oidc.mappers; import org.keycloak.Config; import org.keycloak.models.KeycloakSession; import org.keycloak.protocol.ProtocolMapper; +import org.keycloak.protocol.oidc.OIDCLoginProtocol; /** * @author Bill Burke @@ -27,7 +28,6 @@ public abstract class AbstractOIDCProtocolMapper implements ProtocolMapper { @Override public void init(Config.Scope config) { - } } diff --git a/services/src/main/java/org/keycloak/protocol/oidc/OIDCAccessTokenTransformer.java b/services/src/main/java/org/keycloak/protocol/oidc/mappers/OIDCAccessTokenMapper.java similarity index 84% rename from services/src/main/java/org/keycloak/protocol/oidc/OIDCAccessTokenTransformer.java rename to services/src/main/java/org/keycloak/protocol/oidc/mappers/OIDCAccessTokenMapper.java index c63385f297..e2c479fb4a 100755 --- a/services/src/main/java/org/keycloak/protocol/oidc/OIDCAccessTokenTransformer.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/mappers/OIDCAccessTokenMapper.java @@ -1,4 +1,4 @@ -package org.keycloak.protocol.oidc; +package org.keycloak.protocol.oidc.mappers; import org.keycloak.models.ClientSessionModel; import org.keycloak.models.KeycloakSession; @@ -10,7 +10,7 @@ import org.keycloak.representations.AccessToken; * @author Bill Burke * @version $Revision: 1 $ */ -public interface OIDCAccessTokenTransformer { +public interface OIDCAccessTokenMapper { AccessToken transformToken(AccessToken token, ProtocolMapperModel mappingModel, KeycloakSession session, UserSessionModel userSession, ClientSessionModel clientSession); diff --git a/services/src/main/java/org/keycloak/protocol/oidc/mappers/OIDCClientSessionNoteMapper.java b/services/src/main/java/org/keycloak/protocol/oidc/mappers/OIDCClientSessionNoteMapper.java new file mode 100755 index 0000000000..3e38ed29e4 --- /dev/null +++ b/services/src/main/java/org/keycloak/protocol/oidc/mappers/OIDCClientSessionNoteMapper.java @@ -0,0 +1,60 @@ +package org.keycloak.protocol.oidc.mappers; + +import org.keycloak.models.ClientSessionModel; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.ProtocolMapperModel; +import org.keycloak.models.UserSessionModel; +import org.keycloak.representations.AccessToken; + +import java.util.ArrayList; +import java.util.List; + +/** + * Mappings UserSessionModel.note to an ID Token claim. Token claim name can be a full qualified nested object name, + * i.e. "address.country". This will create a nested + * json object within the toke claim. + * + * @author Bill Burke + * @version $Revision: 1 $ + */ +public class OIDCClientSessionNoteMapper extends AbstractOIDCProtocolMapper implements OIDCAccessTokenMapper { + private static final List configProperties = new ArrayList(); + public static final String CLIENT_SESSION_NOTE = "ClientSession Note"; + + static { + ConfigProperty property; + property = new ConfigProperty(); + property.setName(CLIENT_SESSION_NOTE); + property.setLabel(CLIENT_SESSION_NOTE); + property.setHelpText("Name of the note to map in the UserSessionModel"); + configProperties.add(property); + property.setName(OIDCUserAttributeMapper.TOKEN_CLAIM_NAME); + property.setLabel(OIDCUserAttributeMapper.TOKEN_CLAIM_NAME); + property.setHelpText("Name of the claim to insert into the token. This can be a fully qualified name like 'address.street'. In this case, a nested json object will be created."); + configProperties.add(property); + + } + + public List getConfigProperties() { + return configProperties; + } + @Override + public String getId() { + return "oidc-client-session-note-mapper"; + } + + @Override + public String getDisplayType() { + return "ClientSession Note Mapper"; + } + + @Override + public AccessToken transformToken(AccessToken token, ProtocolMapperModel mappingModel, KeycloakSession session, + UserSessionModel userSession, ClientSessionModel clientSession) { + String note = mappingModel.getConfig().get(CLIENT_SESSION_NOTE); + String noteValue = clientSession.getNote(note); + OIDCUserAttributeMapper.mapClaim(token, mappingModel, noteValue); + return token; + } + +} diff --git a/services/src/main/java/org/keycloak/protocol/oidc/mappers/OIDCUserAttributeMapper.java b/services/src/main/java/org/keycloak/protocol/oidc/mappers/OIDCUserAttributeMapper.java new file mode 100755 index 0000000000..1c687be3b5 --- /dev/null +++ b/services/src/main/java/org/keycloak/protocol/oidc/mappers/OIDCUserAttributeMapper.java @@ -0,0 +1,88 @@ +package org.keycloak.protocol.oidc.mappers; + +import org.keycloak.models.ClientSessionModel; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.ProtocolMapperModel; +import org.keycloak.models.UserModel; +import org.keycloak.models.UserSessionModel; +import org.keycloak.representations.AccessToken; + +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Mappings UserModel.attribute to an ID Token claim. Token claim name can be a full qualified nested object name, + * i.e. "address.country". This will create a nested + * json object within the toke claim. + * + * @author Bill Burke + * @version $Revision: 1 $ + */ +public class OIDCUserAttributeMapper extends AbstractOIDCProtocolMapper implements OIDCAccessTokenMapper { + + public static final String TOKEN_CLAIM_NAME = "Token Claim Name"; + private static final List configProperties = new ArrayList(); + public static final String USER_MODEL_ATTRIBUTE_NAME = "UserModel Attribute Name"; + + static { + ConfigProperty property; + property = new ConfigProperty(); + property.setName(USER_MODEL_ATTRIBUTE_NAME); + property.setLabel(USER_MODEL_ATTRIBUTE_NAME); + property.setHelpText("Name of stored user attribute which is the name of an attribute within the UserModel.attribute map."); + configProperties.add(property); + property.setName(TOKEN_CLAIM_NAME); + property.setLabel(TOKEN_CLAIM_NAME); + property.setHelpText("Name of the claim to insert into the token. This can be a fully qualified name like 'address.street'. In this case, a nested json object will be created."); + configProperties.add(property); + + } + + + public List getConfigProperties() { + return configProperties; + } + + @Override + public String getId() { + return "oidc-usermodel-attribute-mapper"; + } + + @Override + public String getDisplayType() { + return "UserModel Attribute Mapper"; + } + + @Override + public AccessToken transformToken(AccessToken token, ProtocolMapperModel mappingModel, KeycloakSession session, + UserSessionModel userSession, ClientSessionModel clientSession) { + UserModel user = userSession.getUser(); + String attributeName = mappingModel.getConfig().get(USER_MODEL_ATTRIBUTE_NAME); + String attributeValue = user.getAttribute(attributeName); + if (attributeValue == null) return token; + mapClaim(token, mappingModel, attributeValue); + return token; + } + + protected static void mapClaim(AccessToken token, ProtocolMapperModel mappingModel, String attributeValue) { + if (attributeValue == null) return; + String protocolClaim = mappingModel.getConfig().get(TOKEN_CLAIM_NAME); + String[] split = protocolClaim.split("."); + Map jsonObject = token.getOtherClaims(); + for (int i = 0; i < split.length; i++) { + if (i == split.length - 1) { + jsonObject.put(split[i], attributeValue); + } else { + Map nested = (Map)jsonObject.get(split[i]); + if (nested == null) { + nested = new HashMap(); + jsonObject.put(split[i], nested); + jsonObject = nested; + } + } + } + } +} diff --git a/services/src/main/java/org/keycloak/protocol/oidc/mappers/OIDCUserModelMapper.java b/services/src/main/java/org/keycloak/protocol/oidc/mappers/OIDCUserModelMapper.java new file mode 100755 index 0000000000..4af3178680 --- /dev/null +++ b/services/src/main/java/org/keycloak/protocol/oidc/mappers/OIDCUserModelMapper.java @@ -0,0 +1,79 @@ +package org.keycloak.protocol.oidc.mappers; + +import org.keycloak.models.ClientSessionModel; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.ProtocolMapperModel; +import org.keycloak.models.UserModel; +import org.keycloak.models.UserSessionModel; +import org.keycloak.representations.AccessToken; + +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Mappings UserModel property (the property name of a getter method) to an ID Token claim. Token claim name can be a full qualified nested object name, + * i.e. "address.country". This will create a nested + * json object within the toke claim. + * + * @author Bill Burke + * @version $Revision: 1 $ + */ +public class OIDCUserModelMapper extends AbstractOIDCProtocolMapper implements OIDCAccessTokenMapper { + private static final List configProperties = new ArrayList(); + public static final String USER_MODEL_PROPERTY = "UserModel Property"; + + static { + ConfigProperty property; + property = new ConfigProperty(); + property.setName(USER_MODEL_PROPERTY); + property.setLabel(USER_MODEL_PROPERTY); + property.setHelpText("Name of the property method in the UserModel interface. For example, a value of 'email' would reference the UserModel.getEmail() method."); + configProperties.add(property); + property.setName(OIDCUserAttributeMapper.TOKEN_CLAIM_NAME); + property.setLabel(OIDCUserAttributeMapper.TOKEN_CLAIM_NAME); + property.setHelpText("Name of the claim to insert into the token. This can be a fully qualified name like 'address.street'. In this case, a nested json object will be created."); + configProperties.add(property); + + } + + + public List getConfigProperties() { + return configProperties; + } + @Override + public String getId() { + return "oidc-usermodel-property-mapper"; + } + + @Override + public String getDisplayType() { + return "UserModel Property Mapper"; + } + + @Override + public AccessToken transformToken(AccessToken token, ProtocolMapperModel mappingModel, KeycloakSession session, + UserSessionModel userSession, ClientSessionModel clientSession) { + UserModel user = userSession.getUser(); + String propertyName = mappingModel.getConfig().get(USER_MODEL_PROPERTY); + String propertyValue = getUserModelValue(user,propertyName); + OIDCUserAttributeMapper.mapClaim(token, mappingModel, propertyValue); + + return token; + } + + protected String getUserModelValue(UserModel user, String propertyName) { + + String methodName = "get" + Character.toUpperCase(propertyName.charAt(0)) + propertyName.substring(1); + try { + Method method = UserModel.class.getMethod(methodName); + Object val = method.invoke(user); + if (val != null) return val.toString(); + } catch (Exception ignore) { + + } + return null; + } +} diff --git a/services/src/main/java/org/keycloak/protocol/oidc/mappers/OIDCUserSessionNoteMapper.java b/services/src/main/java/org/keycloak/protocol/oidc/mappers/OIDCUserSessionNoteMapper.java new file mode 100755 index 0000000000..0069d5a25d --- /dev/null +++ b/services/src/main/java/org/keycloak/protocol/oidc/mappers/OIDCUserSessionNoteMapper.java @@ -0,0 +1,61 @@ +package org.keycloak.protocol.oidc.mappers; + +import org.keycloak.models.ClientSessionModel; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.ProtocolMapperModel; +import org.keycloak.models.UserSessionModel; +import org.keycloak.representations.AccessToken; + +import java.util.ArrayList; +import java.util.List; + +/** + * Mappings UserSessionModel.note to an ID Token claim. Token claim name can be a full qualified nested object name, + * i.e. "address.country". This will create a nested + * json object within the toke claim. + * + * @author Bill Burke + * @version $Revision: 1 $ + */ +public class OIDCUserSessionNoteMapper extends AbstractOIDCProtocolMapper implements OIDCAccessTokenMapper { + private static final List configProperties = new ArrayList(); + public static final String USER_SESSION_NOTE = "UserSession Note"; + + static { + ConfigProperty property; + property = new ConfigProperty(); + property.setName(USER_SESSION_NOTE); + property.setLabel("UserSession Note"); + property.setHelpText("Name of the note to map in the UserSessionModel"); + configProperties.add(property); + property.setName(OIDCUserAttributeMapper.TOKEN_CLAIM_NAME); + property.setLabel(OIDCUserAttributeMapper.TOKEN_CLAIM_NAME); + property.setHelpText("Name of the claim to insert into the token. This can be a fully qualified name like 'address.street'. In this case, a nested json object will be created."); + configProperties.add(property); + + } + + public List getConfigProperties() { + return configProperties; + } + + @Override + public String getId() { + return "oidc-user-session-note-mapper"; + } + + @Override + public String getDisplayType() { + return "UserSession Note Mapper"; + } + + @Override + public AccessToken transformToken(AccessToken token, ProtocolMapperModel mappingModel, KeycloakSession session, + UserSessionModel userSession, ClientSessionModel clientSession) { + String note = mappingModel.getConfig().get(USER_SESSION_NOTE); + String noteValue = userSession.getNote(note); + OIDCUserAttributeMapper.mapClaim(token, mappingModel, noteValue); + return token; + } + +} 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 0310583b52..0f9da65a17 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 @@ -1 +1 @@ -org.keycloak.protocol.oidc.OIDCAttributeToTokenMapper \ No newline at end of file +org.keycloak.protocol.oidc.mappers.OIDCUserAttributeMapper \ No newline at end of file