Merge pull request #987 from patriot1burke/master

more claims work
This commit is contained in:
Bill Burke 2015-02-21 10:50:54 -05:00
commit 58299d3373
31 changed files with 698 additions and 269 deletions

View file

@ -5,14 +5,24 @@
<column name="ID" type="VARCHAR(36)">
<constraints nullable="false"/>
</column>
<column name="PROTOCOL_CLAIM" type="VARCHAR(255)"/>
<column name="NAME" type="VARCHAR(255)"/>
<column name="PROTOCOL" type="VARCHAR(255)"/>
<column name="SOURCE" type="VARCHAR(255)"/>
<column name="PROTOCOL_MAPPER_NAME" type="VARCHAR(255)"/>
<column name="SOURCE_ATTRIBUTE" type="VARCHAR(255)"/>
<column name="APPLIED_BY_DEFAULT" type="BOOLEAN(1)"/>
<column name="CONSENT_REQUIRED" type="BOOLEAN(1)"/>
<column name="CONSENT_TEXT" type="VARCHAR(255)"/>
<column name="REALM_ID" type="VARCHAR(36)"/>
</createTable>
<createTable tableName="PROTOCOL_MAPPER_CONFIG">
<column name="PROTOCOL_MAPPER_ID" type="VARCHAR(36)">
<constraints nullable="false"/>
</column>
<column name="VALUE" type="CLOB"/>
<column name="NAME" type="VARCHAR(255)">
<constraints nullable="false"/>
</column>
</createTable>
<createTable tableName="CLAIM_TYPE">
<column name="ID" type="VARCHAR(36)">
<constraints nullable="false"/>
@ -80,11 +90,13 @@
<addPrimaryKey columnNames="INTERNAL_ID" constraintName="CONSTRAINT_2B" tableName="IDENTITY_PROVIDER"/>
<addPrimaryKey columnNames="IDENTITY_PROVIDER, USER_ID" constraintName="CONSTRAINT_40" tableName="FEDERATED_IDENTITY"/>
<addPrimaryKey columnNames="IDENTITY_PROVIDER_ID, NAME" constraintName="CONSTRAINT_D" tableName="IDENTITY_PROVIDER_CONFIG"/>
<addPrimaryKey columnNames="PROTOCOL_MAPPER_ID, NAME" constraintName="CONSTRAINT_PMConfig" tableName="PROTOCOL_MAPPER_CONFIG"/>
<addForeignKeyConstraint baseColumnNames="REALM_ID" baseTableName="IDENTITY_PROVIDER" constraintName="FK2B4EBC52AE5C3B34" deferrable="false" initiallyDeferred="false" onDelete="RESTRICT" onUpdate="RESTRICT" referencedColumnNames="ID" referencedTableName="REALM"/>
<addForeignKeyConstraint baseColumnNames="REALM_ID" baseTableName="CLAIM_TYPE" constraintName="FK_CT_REALM" deferrable="false" initiallyDeferred="false" onDelete="RESTRICT" onUpdate="RESTRICT" referencedColumnNames="ID" referencedTableName="REALM"/>
<addForeignKeyConstraint baseColumnNames="REALM_ID" baseTableName="PROTOCOL_MAPPER" constraintName="FK_PCM_REALM" deferrable="false" initiallyDeferred="false" onDelete="RESTRICT" onUpdate="RESTRICT" referencedColumnNames="ID" referencedTableName="REALM"/>
<addForeignKeyConstraint baseColumnNames="USER_ID" baseTableName="FEDERATED_IDENTITY" constraintName="FK404288B92EF007A6" deferrable="false" initiallyDeferred="false" onDelete="RESTRICT" onUpdate="RESTRICT" referencedColumnNames="ID" referencedTableName="USER_ENTITY"/>
<addForeignKeyConstraint baseColumnNames="IDENTITY_PROVIDER_ID" baseTableName="IDENTITY_PROVIDER_CONFIG" constraintName="FKDC4897CF864C4E43" deferrable="false" initiallyDeferred="false" onDelete="RESTRICT" onUpdate="RESTRICT" referencedColumnNames="INTERNAL_ID" referencedTableName="IDENTITY_PROVIDER"/>
<addForeignKeyConstraint baseColumnNames="PROTOCOL_MAPPER_ID" baseTableName="PROTOCOL_MAPPER_CONFIG" constraintName="FK_PMConfig" deferrable="false" initiallyDeferred="false" onDelete="RESTRICT" onUpdate="RESTRICT" referencedColumnNames="ID" referencedTableName="PROTOCOL_MAPPER"/>
<addForeignKeyConstraint baseColumnNames="INTERNAL_ID" baseTableName="CLIENT_ALLOWED_IDENTITY_PROVIDER" constraintName="FK_7CELWNIBJI49AVXSRTUF6XJ12" referencedColumnNames="INTERNAL_ID" referencedTableName="IDENTITY_PROVIDER"/>
<addUniqueConstraint columnNames="INTERNAL_ID,CLIENT_ID" constraintName="UK_7CAELWNIBJI49AVXSRTUF6XJ12" tableName="CLIENT_ALLOWED_IDENTITY_PROVIDER"/>
<addForeignKeyConstraint baseColumnNames="MAPPING_ID" baseTableName="CLIENT_PROTOCOL_MAPPER" constraintName="FK_CPCM" referencedColumnNames="ID" referencedTableName="PROTOCOL_MAPPER"/>

View file

@ -1,17 +1,21 @@
package org.keycloak.representations.idm;
import java.util.HashMap;
import java.util.Map;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @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<String, String> config = new HashMap<String, String>();
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<String, String> getConfig() {
return config;
}
public void setConfig(Map<String, String> 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;
}
}

View file

@ -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,

View file

@ -218,7 +218,7 @@
<span tooltip-placement="right" tooltip="Allowed CORS origins. Only useful if the client adapter has CORS processing enabled." class="fa fa-info-circle"></span>
</div>
</fieldset>
<fieldset>
<fieldset data-ng-show="protocol == 'saml'">
<legend collapsed><span class="text">Fine Grain SAML Endpoint Configuration</span> <span tooltip-placement="right" tooltip="Expand this section to configure exact URLs for Assertion Consumer and Single Logout Service." class="fa fa-info-circle"></span></legend>
<div class="form-group clearfix block" data-ng-show="protocol == 'saml'">
<label class="col-sm-2 control-label" for="consumerServicePost">Assertion Consumer Service POST Binding URL</label>

View file

@ -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<String, String> 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<String, String> getConfig() {
return config;
}
public void setConfig(Map<String, String> config) {
this.config = config;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;

View file

@ -0,0 +1,31 @@
package org.keycloak.models;
import java.util.LinkedList;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class RealmListenerHelper {
protected LinkedList<RealmProvider.RealmCreationListener> list = new LinkedList<RealmProvider.RealmCreationListener>();
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);
}
}
}
}

View file

@ -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);

View file

@ -2,18 +2,21 @@ package org.keycloak.models.entities;
import org.keycloak.models.ProtocolMapperModel;
import java.util.Map;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @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<String, String> 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<String, String> getConfig() {
return config;
}
public void setConfig(Map<String, String> 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;
}
}

View file

@ -53,7 +53,7 @@ public class RealmEntity extends AbstractIdentifiableEntity {
private List<UserFederationProviderEntity> userFederationProviders = new ArrayList<UserFederationProviderEntity>();
private List<IdentityProviderEntity> identityProviders = new ArrayList<IdentityProviderEntity>();
private List<ClaimTypeEntity> claimTypes = new ArrayList<ClaimTypeEntity>();
private List<ProtocolMapperEntity> claimMappings = new ArrayList<ProtocolMapperEntity>();
private List<ProtocolMapperEntity> protocolMappers = new ArrayList<ProtocolMapperEntity>();
private Map<String, String> browserSecurityHeaders = new HashMap<String, String>();
private Map<String, String> smtpConfig = new HashMap<String, String>();
@ -401,12 +401,12 @@ public class RealmEntity extends AbstractIdentifiableEntity {
this.claimTypes = claimTypes;
}
public List<ProtocolMapperEntity> getClaimMappings() {
return claimMappings;
public List<ProtocolMapperEntity> getProtocolMappers() {
return protocolMappers;
}
public void setClaimMappings(List<ProtocolMapperEntity> claimMappings) {
this.claimMappings = claimMappings;
public void setProtocolMappers(List<ProtocolMapperEntity> protocolMappers) {
this.protocolMappers = protocolMappers;
}
}

View file

@ -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<String, String> config = new HashMap<String, String>();
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;
}

View file

@ -118,7 +118,7 @@ public class RepresentationToModel {
importIdentityProviders(rep, newRealm);
importClaimTypes(rep, newRealm);
importProtocolClaimMappings(rep, newRealm);
importProtocolMappers(rep, newRealm);
if (rep.getApplications() != null) {
Map<String, ApplicationModel> 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;
}
}

View file

@ -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();

View file

@ -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) {
}

View file

@ -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<String, String> config = new HashMap<String, String>();
if (entity.getConfig() != null) {
config.putAll(entity.getConfig());
}
mapping.setConfig(config);
mappings.add(mapping);
}
return mappings;

View file

@ -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

View file

@ -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

View file

@ -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<String, String> config = new HashMap<String, String>();
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<String, String> config = new HashMap<String, String>();
if (entity.getConfig() != null) config.putAll(entity.getConfig());
mapping.setConfig(config);
return mapping;
}
}

View file

@ -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 <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@ -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<String, String> 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<String, String> getConfig() {
return config;
}
public void setConfig(Map<String, String> 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;

View file

@ -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

View file

@ -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

View file

@ -789,14 +789,19 @@ public class RealmAdapter extends AbstractMongoAdapter<MongoRealmEntity> impleme
@Override
public Set<ProtocolMapperModel> getProtocolMappers() {
Set<ProtocolMapperModel> result = new HashSet<ProtocolMapperModel>();
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<String, String> config = new HashMap<String, String>();
if (entity.getConfig() != null) {
config.putAll(entity.getConfig());
}
mapping.setConfig(config);
}
return result;
}
@ -806,30 +811,31 @@ public class RealmAdapter extends AbstractMongoAdapter<MongoRealmEntity> 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<MongoRealmEntity> 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<MongoRealmEntity> 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<String, String> config = new HashMap<String, String>();
if (entity.getConfig() != null) config.putAll(config);
mapping.setConfig(config);
return mapping;
}

View file

@ -3,6 +3,8 @@ package org.keycloak.protocol;
import org.keycloak.provider.Provider;
import org.keycloak.provider.ProviderFactory;
import java.util.List;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
@ -10,4 +12,36 @@ import org.keycloak.provider.ProviderFactory;
public interface ProtocolMapper extends Provider, ProviderFactory<ProtocolMapper> {
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<ConfigProperty> getConfigProperties();
}

View file

@ -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 <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @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<String, Object> jsonObject = token.getOtherClaims();
for (int i = 0; i < split.length; i++) {
if (i == split.length - 1) {
jsonObject.put(split[i], attributeValue);
} else {
Map<String, Object> nested = (Map<String, Object>)jsonObject.get(split[i]);
if (nested == null) {
nested = new HashMap<String, Object>();
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;
}
}

View file

@ -20,6 +20,7 @@ public class OIDCLoginProtocolFactory implements LoginProtocolFactory {
@Override
public void init(Config.Scope config) {
System.out.println("here");
}

View file

@ -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 <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@ -27,7 +28,6 @@ public abstract class AbstractOIDCProtocolMapper implements ProtocolMapper {
@Override
public void init(Config.Scope config) {
}
}

View file

@ -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 <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public interface OIDCAccessTokenTransformer {
public interface OIDCAccessTokenMapper {
AccessToken transformToken(AccessToken token, ProtocolMapperModel mappingModel, KeycloakSession session,
UserSessionModel userSession, ClientSessionModel clientSession);

View file

@ -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 <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class OIDCClientSessionNoteMapper extends AbstractOIDCProtocolMapper implements OIDCAccessTokenMapper {
private static final List<ConfigProperty> configProperties = new ArrayList<ConfigProperty>();
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<ConfigProperty> 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;
}
}

View file

@ -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 <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class OIDCUserAttributeMapper extends AbstractOIDCProtocolMapper implements OIDCAccessTokenMapper {
public static final String TOKEN_CLAIM_NAME = "Token Claim Name";
private static final List<ConfigProperty> configProperties = new ArrayList<ConfigProperty>();
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<ConfigProperty> 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<String, Object> jsonObject = token.getOtherClaims();
for (int i = 0; i < split.length; i++) {
if (i == split.length - 1) {
jsonObject.put(split[i], attributeValue);
} else {
Map<String, Object> nested = (Map<String, Object>)jsonObject.get(split[i]);
if (nested == null) {
nested = new HashMap<String, Object>();
jsonObject.put(split[i], nested);
jsonObject = nested;
}
}
}
}
}

View file

@ -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 <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class OIDCUserModelMapper extends AbstractOIDCProtocolMapper implements OIDCAccessTokenMapper {
private static final List<ConfigProperty> configProperties = new ArrayList<ConfigProperty>();
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<ConfigProperty> 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;
}
}

View file

@ -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 <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class OIDCUserSessionNoteMapper extends AbstractOIDCProtocolMapper implements OIDCAccessTokenMapper {
private static final List<ConfigProperty> configProperties = new ArrayList<ConfigProperty>();
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<ConfigProperty> 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;
}
}

View file

@ -1 +1 @@
org.keycloak.protocol.oidc.OIDCAttributeToTokenMapper
org.keycloak.protocol.oidc.mappers.OIDCUserAttributeMapper