claim mappings next phase

This commit is contained in:
Bill Burke 2015-02-24 19:37:07 -05:00
parent 6c5f35720a
commit c20ad93807
39 changed files with 666 additions and 250 deletions

View file

@ -5,14 +5,21 @@
<column name="ID" type="VARCHAR(36)">
<constraints nullable="false"/>
</column>
<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="NAME" type="VARCHAR(255)">
<constraints nullable="false"/>
</column>
<column name="PROTOCOL" type="VARCHAR(255)">
<constraints nullable="false"/>
</column>
<column name="PROTOCOL_MAPPER_NAME" type="VARCHAR(255)">
<constraints nullable="false"/>
</column>
<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)"/>
<column name="REALM_ID" type="VARCHAR(36)">
<constraints nullable="false"/>
</column>
</createTable>
<createTable tableName="PROTOCOL_MAPPER_CONFIG">
<column name="PROTOCOL_MAPPER_ID" type="VARCHAR(36)">

View file

@ -24,6 +24,75 @@ import org.codehaus.jackson.annotate.JsonProperty;
*/
public class UserClaimSet {
public static class AddressClaimSet {
@JsonProperty("formatted")
protected String formattedAddress;
@JsonProperty("street_address")
protected String streetAddress;
@JsonProperty("locality")
protected String locality;
@JsonProperty("region")
protected String region;
@JsonProperty("postal_code")
protected String postalCode;
@JsonProperty("country")
protected String country;
public String getFormattedAddress() {
return this.formattedAddress;
}
public void setFormattedAddress(String formattedAddress) {
this.formattedAddress = formattedAddress;
}
public String getStreetAddress() {
return this.streetAddress;
}
public void setStreetAddress(String streetAddress) {
this.streetAddress = streetAddress;
}
public String getLocality() {
return this.locality;
}
public void setLocality(String locality) {
this.locality = locality;
}
public String getRegion() {
return this.region;
}
public void setRegion(String region) {
this.region = region;
}
public String getPostalCode() {
return this.postalCode;
}
public void setPostalCode(String postalCode) {
this.postalCode = postalCode;
}
public String getCountry() {
return this.country;
}
public void setCountry(String country) {
this.country = country;
}
}
@JsonProperty("sub")
protected String sub;
@ -79,29 +148,11 @@ public class UserClaimSet {
protected Boolean phoneNumberVerified;
@JsonProperty("address")
protected String address;
protected AddressClaimSet address;
@JsonProperty("updated_at")
protected Long updatedAt;
@JsonProperty("formatted")
protected String formattedAddress;
@JsonProperty("street_address")
protected String streetAddress;
@JsonProperty("locality")
protected String locality;
@JsonProperty("region")
protected String region;
@JsonProperty("postal_code")
protected String postalCode;
@JsonProperty("country")
protected String country;
@JsonProperty("claims_locales")
protected String claimsLocales;
@ -249,11 +300,11 @@ public class UserClaimSet {
this.phoneNumberVerified = phoneNumberVerified;
}
public String getAddress() {
return this.address;
public AddressClaimSet getAddress() {
return address;
}
public void setAddress(String address) {
public void setAddress(AddressClaimSet address) {
this.address = address;
}
@ -273,54 +324,6 @@ public class UserClaimSet {
this.sub = sub;
}
public String getFormattedAddress() {
return this.formattedAddress;
}
public void setFormattedAddress(String formattedAddress) {
this.formattedAddress = formattedAddress;
}
public String getStreetAddress() {
return this.streetAddress;
}
public void setStreetAddress(String streetAddress) {
this.streetAddress = streetAddress;
}
public String getLocality() {
return this.locality;
}
public void setLocality(String locality) {
this.locality = locality;
}
public String getRegion() {
return this.region;
}
public void setRegion(String region) {
this.region = region;
}
public String getPostalCode() {
return this.postalCode;
}
public void setPostalCode(String postalCode) {
this.postalCode = postalCode;
}
public String getCountry() {
return this.country;
}
public void setCountry(String country) {
this.country = country;
}
public String getClaimsLocales() {
return this.claimsLocales;
}

View file

@ -30,7 +30,7 @@ public class ApplicationRepresentation {
protected Integer nodeReRegistrationTimeout;
protected Map<String, Integer> registeredNodes;
protected List<String> allowedIdentityProviders;
protected Set<String> protocolClaimMappings;
protected Set<String> protocolMappers;
public String getId() {
return id;
@ -200,11 +200,11 @@ public class ApplicationRepresentation {
this.allowedIdentityProviders = allowedIdentityProviders;
}
public Set<String> getProtocolClaimMappings() {
return protocolClaimMappings;
public Set<String> getProtocolMappers() {
return protocolMappers;
}
public void setProtocolClaimMappings(Set<String> protocolClaimMappings) {
this.protocolClaimMappings = protocolClaimMappings;
public void setProtocolMappers(Set<String> protocolMappers) {
this.protocolMappers = protocolMappers;
}
}

View file

@ -1,6 +1,7 @@
package org.keycloak.representations.idm;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
@ -64,7 +65,7 @@ public class RealmRepresentation {
protected List<String> eventsListeners;
private List<IdentityProviderRepresentation> identityProviders;
private List<ClaimTypeRepresentation> claimTypes;
private List<ProtocolMapperRepresentation> protocolClaimMappings;
private List<ProtocolMapperRepresentation> protocolMappers;
private Boolean identityFederationEnabled;
public String getId() {
@ -492,11 +493,16 @@ public class RealmRepresentation {
this.claimTypes = claimTypes;
}
public List<ProtocolMapperRepresentation> getProtocolClaimMappings() {
return protocolClaimMappings;
public List<ProtocolMapperRepresentation> getProtocolMappers() {
return protocolMappers;
}
public void setProtocolClaimMappings(List<ProtocolMapperRepresentation> protocolClaimMappings) {
this.protocolClaimMappings = protocolClaimMappings;
public void addProtocolMapper(ProtocolMapperRepresentation rep) {
if (protocolMappers == null) protocolMappers = new LinkedList<ProtocolMapperRepresentation>();
protocolMappers.add(rep);
}
public void setProtocolMappers(List<ProtocolMapperRepresentation> protocolMappers) {
this.protocolMappers = protocolMappers;
}
}

View file

@ -105,6 +105,7 @@ public interface ClientModel {
boolean hasIdentityProvider(String providerId);
Set<ProtocolMapperModel> getProtocolMappers();
void addProtocolMappers(Set<String> mapperIds);
void removeProtocolMappers(Set<String> mapperIds);
void addProtocolMappers(Set<String> mapperNames);
void removeProtocolMappers(Set<String> mapperNames);
void setProtocolMappers(Set<String> mapperNames);
}

View file

@ -1,6 +1,7 @@
package org.keycloak.models;
import org.keycloak.enums.SslRequired;
import org.keycloak.provider.ProviderEvent;
import java.security.Key;
import java.security.PrivateKey;
@ -15,6 +16,19 @@ import java.util.Set;
* @version $Revision: 1 $
*/
public interface RealmModel extends RoleContainerModel {
interface RealmCreationEvent extends ProviderEvent {
RealmModel getCreatedRealm();
}
interface ClientCreationEvent extends ProviderEvent {
RealmModel getCreatedRealm();
ClientModel getCreatedClient();
}
interface ApplicationCreationEvent extends ClientCreationEvent {
ApplicationModel getCreatedApplication();
}
interface OAuthClientCreationEvent extends ClientCreationEvent {
OAuthClientModel getCreatedOAuthClient();
}
String getId();
@ -235,6 +249,7 @@ public interface RealmModel extends RoleContainerModel {
void removeProtocolMapper(ProtocolMapperModel mapping);
void updateProtocolMapper(ProtocolMapperModel mapping);
public ProtocolMapperModel getProtocolMapperById(String id);
public ProtocolMapperModel getProtocolMapperByName(String name);
}

View file

@ -10,9 +10,6 @@ import java.util.List;
* @version $Revision: 1 $
*/
public interface RealmProvider extends Provider {
public interface RealmCreationEvent extends ProviderEvent {
RealmModel getCreatedRealm();
}
// Note: The reason there are so many query methods here is for layering a cache on top of an persistent KeycloakSession

View file

@ -30,7 +30,7 @@ public class ClientEntity extends AbstractIdentifiableEntity {
private List<String> redirectUris = new ArrayList<String>();
private List<String> scopeIds = new ArrayList<String>();
private List<String> allowedIdentityProviders = new ArrayList<String>();
private Set<String> protocolClaimMappings = new HashSet<String>();
private Set<String> protocolMappers = new HashSet<String>();
public String getName() {
return name;
@ -152,11 +152,11 @@ public class ClientEntity extends AbstractIdentifiableEntity {
this.allowedIdentityProviders = allowedIdentityProviders;
}
public Set<String> getProtocolClaimMappings() {
return protocolClaimMappings;
public Set<String> getProtocolMappers() {
return protocolMappers;
}
public void setProtocolClaimMappings(Set<String> protocolClaimMappings) {
this.protocolClaimMappings = protocolClaimMappings;
public void setProtocolMappers(Set<String> protocolMappers) {
this.protocolMappers = protocolMappers;
}
}

View file

@ -158,7 +158,7 @@ public class ModelToRepresentation {
}
for (ProtocolMapperModel mapping : realm.getProtocolMappers()) {
rep.getProtocolClaimMappings().add(toRepresentation(mapping));
rep.addProtocolMapper(toRepresentation(mapping));
}
return rep;
@ -267,8 +267,8 @@ public class ModelToRepresentation {
if (!applicationModel.getProtocolMappers().isEmpty()) {
Set<String> mappings = new HashSet<String>();
for (ProtocolMapperModel model : applicationModel.getProtocolMappers()) mappings.add(model.getId());
rep.setProtocolClaimMappings(mappings);
for (ProtocolMapperModel model : applicationModel.getProtocolMappers()) mappings.add(model.getName());
rep.setProtocolMappers(mappings);
}
return rep;
@ -302,7 +302,7 @@ public class ModelToRepresentation {
if (!model.getProtocolMappers().isEmpty()) {
Set<String> mappings = new HashSet<String>();
for (ProtocolMapperModel mappingMoel : model.getProtocolMappers()) mappings.add(mappingMoel.getId());
for (ProtocolMapperModel mappingModel : model.getProtocolMappers()) mappings.add(mappingModel.getName());
rep.setProtocolClaimMappings(mappings);
}
return rep;

View file

@ -460,8 +460,8 @@ public class RepresentationToModel {
applicationModel.setAllowedClaimsMask(ClaimMask.ALL);
}
if (resourceRep.getProtocolClaimMappings() != null) {
applicationModel.addProtocolMappers(resourceRep.getProtocolClaimMappings());
if (resourceRep.getProtocolMappers() != null) {
applicationModel.setProtocolMappers(resourceRep.getProtocolMappers());
}
return applicationModel;
@ -773,13 +773,17 @@ public class RepresentationToModel {
}
private static void importProtocolMappers(RealmRepresentation rep, RealmModel newRealm) {
if (rep.getProtocolClaimMappings() != null) {
if (rep.getProtocolMappers() != null) {
// we make sure we don't recreate mappers that are automatically created by the protocol providers.
for (ProtocolMapperRepresentation representation : rep.getProtocolClaimMappings()) {
if (representation.getId() == null || newRealm.getProtocolMapperById(representation.getId()) == null) {
Set<ProtocolMapperModel> mappers = newRealm.getProtocolMappers();
for (ProtocolMapperRepresentation representation : rep.getProtocolMappers()) {
ProtocolMapperModel existing = newRealm.getProtocolMapperByName(representation.getName());
if (existing == null) {
newRealm.addProtocolMapper(toModel(representation));
} else {
newRealm.updateProtocolMapper(toModel(representation));
ProtocolMapperModel mapping = toModel(representation);
mapping.setId(existing.getId());
newRealm.updateProtocolMapper(mapping);
}
}
}

View file

@ -286,16 +286,23 @@ public abstract class ClientAdapter implements ClientModel {
return cachedClient.getProtocolClaimMappings(); }
@Override
public void addProtocolMappers(Set<String> mappingIds) {
public void addProtocolMappers(Set<String> mapperNames) {
getDelegateForUpdate();
updatedClient.addProtocolMappers(mappingIds);
updatedClient.addProtocolMappers(mapperNames);
}
@Override
public void removeProtocolMappers(Set<String> mappingIds) {
public void removeProtocolMappers(Set<String> mapperNames) {
getDelegateForUpdate();
updatedClient.removeProtocolMappers(mappingIds);
updatedClient.removeProtocolMappers(mapperNames);
}
@Override
public void setProtocolMappers(Set<String> mapperNames) {
getDelegateForUpdate();
updatedClient.setProtocolMappers(mapperNames);
}
}

View file

@ -919,6 +919,14 @@ public class RealmAdapter implements RealmModel {
return null;
}
@Override
public ProtocolMapperModel getProtocolMapperByName(String name) {
for (ProtocolMapperModel mapping : cached.getClaimMappings()) {
if (mapping.getName().equals(name)) return mapping;
}
return null;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;

View file

@ -8,6 +8,7 @@ import org.keycloak.models.RoleModel;
import org.keycloak.models.jpa.entities.ClientEntity;
import org.keycloak.models.jpa.entities.IdentityProviderEntity;
import org.keycloak.models.jpa.entities.ProtocolMapperEntity;
import org.keycloak.models.jpa.entities.RealmEntity;
import org.keycloak.models.jpa.entities.RoleEntity;
import org.keycloak.models.jpa.entities.ScopeMappingEntity;
@ -17,6 +18,7 @@ import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
@ -364,6 +366,7 @@ public abstract class ClientAdapter implements ClientModel {
mapping.setId(entity.getId());
mapping.setName(entity.getName());
mapping.setProtocol(entity.getProtocol());
mapping.setProtocolMapper(entity.getProtocolMapper());
mapping.setAppliedByDefault(entity.isAppliedByDefault());
mapping.setConsentRequired(entity.isConsentRequired());
mapping.setConsentText(entity.getConsentText());
@ -377,16 +380,27 @@ public abstract class ClientAdapter implements ClientModel {
return mappings;
}
protected ProtocolMapperEntity findProtocolMapperByName(String name) {
TypedQuery<ProtocolMapperEntity> query = em.createNamedQuery("getProtocolMapperByName", ProtocolMapperEntity.class);
query.setParameter("name", name);
query.setParameter("realm", entity.getRealm());
List<ProtocolMapperEntity> entities = query.getResultList();
if (entities.size() == 0) return null;
if (entities.size() > 1) throw new IllegalStateException("Should not be more than one protocol mapper with same name");
return query.getResultList().get(0);
}
@Override
public void addProtocolMappers(Set<String> mappings) {
Collection<ProtocolMapperEntity> entities = entity.getProtocolMappers();
Set<String> already = new HashSet<String>();
for (ProtocolMapperEntity rel : entities) {
already.add(rel.getId());
already.add(rel.getName());
}
for (String providerId : mappings) {
if (!already.contains(providerId)) {
ProtocolMapperEntity mapping = em.find(ProtocolMapperEntity.class, providerId);
for (String name : mappings) {
if (!already.contains(name)) {
ProtocolMapperEntity mapping = findProtocolMapperByName(name);
if (mapping != null) {
entities.add(mapping);
}
@ -400,13 +414,36 @@ public abstract class ClientAdapter implements ClientModel {
Collection<ProtocolMapperEntity> entities = entity.getProtocolMappers();
List<ProtocolMapperEntity> remove = new LinkedList<ProtocolMapperEntity>();
for (ProtocolMapperEntity rel : entities) {
if (mappings.contains(rel.getId())) remove.add(rel);
if (mappings.contains(rel.getName())) remove.add(rel);
}
for (ProtocolMapperEntity entity : remove) {
entities.remove(entity);
}
em.flush();
}
@Override
public void setProtocolMappers(Set<String> mappings) {
Collection<ProtocolMapperEntity> entities = entity.getProtocolMappers();
Iterator<ProtocolMapperEntity> it = entities.iterator();
Set<String> already = new HashSet<String>();
while (it.hasNext()) {
ProtocolMapperEntity mapper = it.next();
if (mappings.contains(mapper.getName())) {
already.add(mapper.getName());
continue;
}
it.remove();
}
for (String name : mappings) {
if (!already.contains(name)) {
ProtocolMapperEntity mapping = findProtocolMapperByName(name);
if (mapping != null) {
entities.add(mapping);
}
}
}
em.flush();
}
}

View file

@ -45,7 +45,7 @@ public class JpaRealmProvider implements RealmProvider {
em.persist(realm);
em.flush();
final RealmModel model = new RealmAdapter(session, em, realm);
session.getKeycloakSessionFactory().publish(new RealmCreationEvent() {
session.getKeycloakSessionFactory().publish(new RealmModel.RealmCreationEvent() {
@Override
public RealmModel getCreatedRealm() {
return model;

View file

@ -628,6 +628,17 @@ public class RealmAdapter implements RealmModel {
return this.addApplication(KeycloakModelUtils.generateId(), name);
}
public void addDefaultClientProtocolMappers(ClientModel client) {
Set<String> adding = new HashSet<String>();
for (ProtocolMapperEntity mapper : realm.getProtocolMappers()) {
if (mapper.isAppliedByDefault()) {
adding.add(mapper.getName());
}
}
client.setProtocolMappers(adding);
}
@Override
public ApplicationModel addApplication(String id, String name) {
ApplicationEntity applicationData = new ApplicationEntity();
@ -639,6 +650,7 @@ public class RealmAdapter implements RealmModel {
em.persist(applicationData);
em.flush();
ApplicationModel resource = new ApplicationAdapter(this, em, session, applicationData);
addDefaultClientProtocolMappers(resource);
em.flush();
return resource;
}
@ -702,7 +714,10 @@ public class RealmAdapter implements RealmModel {
data.setRealm(realm);
em.persist(data);
em.flush();
return new OAuthClientAdapter(this, data, em);
OAuthClientModel model = new OAuthClientAdapter(this, data, em);
addDefaultClientProtocolMappers(model);
em.flush();
return model;
}
@Override
@ -1259,7 +1274,7 @@ public class RealmAdapter implements RealmModel {
@Override
public Set<ProtocolMapperModel> getProtocolMappers() {
Set<ProtocolMapperModel> mappings = new HashSet<ProtocolMapperModel>();
for (ProtocolMapperEntity entity : realm.getProtocolClaimMappings()) {
for (ProtocolMapperEntity entity : realm.getProtocolMappers()) {
ProtocolMapperModel mapping = new ProtocolMapperModel();
mapping.setId(entity.getId());
mapping.setName(entity.getName());
@ -1280,7 +1295,10 @@ public class RealmAdapter implements RealmModel {
@Override
public ProtocolMapperModel addProtocolMapper(ProtocolMapperModel model) {
String id = model.getId() == null ? KeycloakModelUtils.generateId() : model.getId();
if (getProtocolMapperByName(model.getName()) != null) {
throw new RuntimeException("Duplicate protocol mapper with name: " + model.getName());
}
String id = KeycloakModelUtils.generateId();
ProtocolMapperEntity entity = new ProtocolMapperEntity();
entity.setId(id);
entity.setName(model.getName());
@ -1293,20 +1311,12 @@ public class RealmAdapter implements RealmModel {
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.setAppliedByDefault(entity.isAppliedByDefault());
mapping.setConfig(model.getConfig());
mapping.setConsentRequired(entity.isConsentRequired());
mapping.setConsentText(entity.getConsentText());
return mapping;
realm.getProtocolMappers().add(entity);
return entityToModel(entity);
}
protected ProtocolMapperEntity getProtocolMapper(String id) {
for (ProtocolMapperEntity entity : realm.getProtocolClaimMappings()) {
protected ProtocolMapperEntity getProtocolMapperEntity(String id) {
for (ProtocolMapperEntity entity : realm.getProtocolMappers()) {
if (entity.getId().equals(id)) {
return entity;
}
@ -1315,11 +1325,21 @@ public class RealmAdapter implements RealmModel {
}
protected ProtocolMapperEntity getProtocolMapperEntityByName(String name) {
for (ProtocolMapperEntity entity : realm.getProtocolMappers()) {
if (entity.getName().equals(name)) {
return entity;
}
}
return null;
}
@Override
public void removeProtocolMapper(ProtocolMapperModel mapping) {
ProtocolMapperEntity toDelete = getProtocolMapper(mapping.getId());
ProtocolMapperEntity toDelete = getProtocolMapperEntity(mapping.getId());
if (toDelete != null) {
realm.getProtocolClaimMappings().remove(toDelete);
realm.getProtocolMappers().remove(toDelete);
em.remove(toDelete);
}
@ -1327,7 +1347,7 @@ public class RealmAdapter implements RealmModel {
@Override
public void updateProtocolMapper(ProtocolMapperModel mapping) {
ProtocolMapperEntity entity = getProtocolMapper(mapping.getId());
ProtocolMapperEntity entity = getProtocolMapperEntity(mapping.getId());
entity.setProtocolMapper(mapping.getProtocolMapper());
entity.setAppliedByDefault(mapping.isAppliedByDefault());
entity.setConsentRequired(mapping.isConsentRequired());
@ -1344,12 +1364,24 @@ public class RealmAdapter implements RealmModel {
@Override
public ProtocolMapperModel getProtocolMapperById(String id) {
ProtocolMapperEntity entity = getProtocolMapper(id);
ProtocolMapperEntity entity = getProtocolMapperEntity(id);
if (entity == null) return null;
return entityToModel(entity);
}
@Override
public ProtocolMapperModel getProtocolMapperByName(String name) {
ProtocolMapperEntity entity = getProtocolMapperEntityByName(name);
if (entity == null) return null;
return entityToModel(entity);
}
protected ProtocolMapperModel entityToModel(ProtocolMapperEntity entity) {
ProtocolMapperModel mapping = new ProtocolMapperModel();
mapping.setId(entity.getId());
mapping.setName(entity.getName());
mapping.setProtocol(entity.getProtocol());
mapping.setProtocolMapper(entity.getProtocolMapper());
mapping.setAppliedByDefault(entity.isAppliedByDefault());
mapping.setConsentRequired(entity.isConsentRequired());
mapping.setConsentText(entity.getConsentText());

View file

@ -77,7 +77,7 @@ public abstract class ClientEntity {
@JoinTable(name="CLIENT_ALLOWED_IDENTITY_PROVIDER", joinColumns = { @JoinColumn(name="CLIENT_ID")}, inverseJoinColumns = { @JoinColumn(name="INTERNAL_ID")})
Collection<IdentityProviderEntity> allowedIdentityProviders = new ArrayList<IdentityProviderEntity>();
@OneToMany(cascade ={CascadeType.REMOVE})
@OneToMany(fetch = FetchType.LAZY)
@JoinTable(name="CLIENT_PROTOCOL_MAPPER", joinColumns = { @JoinColumn(name="CLIENT_ID")}, inverseJoinColumns = { @JoinColumn(name="MAPPING_ID")})
Collection<ProtocolMapperEntity> protocolMappers = new ArrayList<ProtocolMapperEntity>();

View file

@ -20,7 +20,7 @@ import java.util.Map;
*/
@Entity
@NamedQueries({
@NamedQuery(name="deleteProtocolClaimMappersByRealm", query="delete from ProtocolMapperEntity attr where attr.realm = :realm")
@NamedQuery(name="getProtocolMapperByName", query="select mapper from ProtocolMapperEntity mapper where mapper.name = :name and mapper.realm = :realm")
})
@Table(name="PROTOCOL_MAPPER")
public class ProtocolMapperEntity {

View file

@ -96,7 +96,7 @@ public class RealmEntity {
Collection<ClaimTypeEntity> claimTypes = new ArrayList<ClaimTypeEntity>();
@OneToMany(cascade ={CascadeType.REMOVE}, orphanRemoval = true, mappedBy = "realm")
Collection<ProtocolMapperEntity> protocolClaimMappings = new ArrayList<ProtocolMapperEntity>();
Collection<ProtocolMapperEntity> protocolMappers = new ArrayList<ProtocolMapperEntity>();
@OneToMany(cascade ={CascadeType.REMOVE}, orphanRemoval = true, mappedBy = "realm")
Collection<RequiredCredentialEntity> requiredCredentials = new ArrayList<RequiredCredentialEntity>();
@ -447,12 +447,12 @@ public class RealmEntity {
this.claimTypes = claimTypes;
}
public Collection<ProtocolMapperEntity> getProtocolClaimMappings() {
return protocolClaimMappings;
public Collection<ProtocolMapperEntity> getProtocolMappers() {
return protocolMappers;
}
public void setProtocolClaimMappings(Collection<ProtocolMapperEntity> protocolClaimMappings) {
this.protocolClaimMappings = protocolClaimMappings;
public void setProtocolMappers(Collection<ProtocolMapperEntity> protocolMappers) {
this.protocolMappers = protocolMappers;
}
}

View file

@ -295,7 +295,7 @@ public abstract class ClientAdapter<T extends MongoIdentifiableEntity> extends A
@Override
public Set<ProtocolMapperModel> getProtocolMappers() {
Set<ProtocolMapperModel> result = new HashSet<ProtocolMapperModel>();
for (String id : getMongoEntityAsClient().getProtocolClaimMappings()) {
for (String id : getMongoEntityAsClient().getProtocolMappers()) {
ProtocolMapperModel model = getRealm().getProtocolMapperById(id);
if (model != null) result.add(model);
}
@ -303,15 +303,22 @@ public abstract class ClientAdapter<T extends MongoIdentifiableEntity> extends A
}
@Override
public void addProtocolMappers(Set<String> mappingIds) {
getMongoEntityAsClient().getProtocolClaimMappings().addAll(mappingIds);
public void addProtocolMappers(Set<String> mapperNames) {
getMongoEntityAsClient().getProtocolMappers().addAll(mapperNames);
updateMongoEntity();
}
@Override
public void removeProtocolMappers(Set<String> mappingIds) {
getMongoEntityAsClient().getProtocolClaimMappings().removeAll(mappingIds);
public void removeProtocolMappers(Set<String> mapperNames) {
getMongoEntityAsClient().getProtocolMappers().removeAll(mapperNames);
updateMongoEntity();
}
@Override
public void setProtocolMappers(Set<String> mapperNames) {
getMongoEntityAsClient().getProtocolMappers().clear();
getMongoEntityAsClient().getProtocolMappers().addAll(mapperNames);
updateMongoEntity();
}

View file

@ -52,7 +52,7 @@ public class MongoRealmProvider implements RealmProvider {
getMongoStore().insertEntity(newRealm, invocationContext);
final RealmModel model = new RealmAdapter(session, newRealm, invocationContext);
session.getKeycloakSessionFactory().publish(new RealmCreationEvent() {
session.getKeycloakSessionFactory().publish(new RealmModel.RealmCreationEvent() {
@Override
public RealmModel getCreatedRealm() {
return model;

View file

@ -616,6 +616,14 @@ public class RealmAdapter extends AbstractMongoAdapter<MongoRealmEntity> impleme
return result;
}
public void addDefaultClientProtocolMappers(ClientModel client) {
Set<String> adding = new HashSet<String>();
for (ProtocolMapperEntity mapper : realm.getProtocolMappers()) {
if (mapper.isAppliedByDefault()) adding.add(mapper.getName());
}
client.setProtocolMappers(adding);
}
@Override
public ApplicationModel addApplication(String name) {
return this.addApplication(null, name);
@ -630,7 +638,9 @@ public class RealmAdapter extends AbstractMongoAdapter<MongoRealmEntity> impleme
appData.setEnabled(true);
getMongoStore().insertEntity(appData, invocationContext);
return new ApplicationAdapter(session, this, appData, invocationContext);
ApplicationModel model = new ApplicationAdapter(session, this, appData, invocationContext);
addDefaultClientProtocolMappers(model);
return model;
}
@Override
@ -651,7 +661,9 @@ public class RealmAdapter extends AbstractMongoAdapter<MongoRealmEntity> impleme
oauthClient.setName(name);
getMongoStore().insertEntity(oauthClient, invocationContext);
return new OAuthClientAdapter(session, this, oauthClient, invocationContext);
OAuthClientAdapter model = new OAuthClientAdapter(session, this, oauthClient, invocationContext);
addDefaultClientProtocolMappers(model);
return model;
}
@Override
@ -809,8 +821,7 @@ public class RealmAdapter extends AbstractMongoAdapter<MongoRealmEntity> impleme
@Override
public ProtocolMapperModel addProtocolMapper(ProtocolMapperModel model) {
ProtocolMapperEntity entity = new ProtocolMapperEntity();
if (model.getId() != null) entity.setId(model.getId());
else entity.setId(KeycloakModelUtils.generateId());
entity.setId(KeycloakModelUtils.generateId());
entity.setProtocol(model.getProtocol());
entity.setName(model.getName());
entity.setAppliedByDefault(model.isAppliedByDefault());
@ -820,15 +831,7 @@ public class RealmAdapter extends AbstractMongoAdapter<MongoRealmEntity> impleme
entity.setConsentText(model.getConsentText());
realm.getProtocolMappers().add(entity);
updateRealm();
ProtocolMapperModel mapping = new ProtocolMapperModel();
mapping.setId(entity.getId());
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;
return entityToModel(entity);
}
@Override
@ -843,7 +846,7 @@ public class RealmAdapter extends AbstractMongoAdapter<MongoRealmEntity> impleme
}
protected ProtocolMapperEntity getProtocolMapper(String id) {
protected ProtocolMapperEntity getProtocolMapperyEntityById(String id) {
for (ProtocolMapperEntity entity : realm.getProtocolMappers()) {
if (entity.getId().equals(id)) {
return entity;
@ -852,11 +855,20 @@ public class RealmAdapter extends AbstractMongoAdapter<MongoRealmEntity> impleme
return null;
}
protected ProtocolMapperEntity getProtocolMapperyEntityByName(String name) {
for (ProtocolMapperEntity entity : realm.getProtocolMappers()) {
if (entity.getName().equals(name)) {
return entity;
}
}
return null;
}
@Override
public void updateProtocolMapper(ProtocolMapperModel mapping) {
ProtocolMapperEntity entity = getProtocolMapper(mapping.getId());
ProtocolMapperEntity entity = getProtocolMapperyEntityById(mapping.getId());
entity.setAppliedByDefault(mapping.isAppliedByDefault());
entity.setProtocolMapper(mapping.getProtocolMapper());
entity.setConsentRequired(mapping.isConsentRequired());
@ -873,8 +885,19 @@ public class RealmAdapter extends AbstractMongoAdapter<MongoRealmEntity> impleme
@Override
public ProtocolMapperModel getProtocolMapperById(String id) {
ProtocolMapperEntity entity = getProtocolMapper(id);
ProtocolMapperEntity entity = getProtocolMapperyEntityById(id);
if (entity == null) return null;
return entityToModel(entity);
}
@Override
public ProtocolMapperModel getProtocolMapperByName(String name) {
ProtocolMapperEntity entity = getProtocolMapperyEntityById(name);
if (entity == null) return null;
return entityToModel(entity);
}
protected ProtocolMapperModel entityToModel(ProtocolMapperEntity entity) {
ProtocolMapperModel mapping = new ProtocolMapperModel();
mapping.setId(entity.getId());
mapping.setName(entity.getName());

View file

@ -4,11 +4,22 @@ import org.keycloak.Config;
import org.keycloak.events.EventBuilder;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.models.ProtocolMapperModel;
import org.keycloak.models.RealmModel;
import org.keycloak.protocol.LoginProtocol;
import org.keycloak.protocol.LoginProtocolFactory;
import org.keycloak.protocol.oidc.mappers.AttributeMapperHelper;
import org.keycloak.protocol.oidc.mappers.OIDCAddressMapper;
import org.keycloak.protocol.oidc.mappers.OIDCFullNameMapper;
import org.keycloak.protocol.oidc.mappers.OIDCUserModelMapper;
import org.keycloak.provider.ProviderEvent;
import org.keycloak.provider.ProviderEventListener;
import org.keycloak.services.managers.AuthenticationManager;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
@ -25,8 +36,107 @@ public class OIDCLoginProtocolFactory implements LoginProtocolFactory {
@Override
public void postInit(KeycloakSessionFactory factory) {
KeycloakSession session = factory.create();
session.getTransaction().begin();
try {
List<RealmModel> realms = session.realms().getRealms();
for (RealmModel realm : realms) addMappers(realm);
session.getTransaction().commit();
} catch (Exception e) {
session.getTransaction().rollback();
} finally {
session.close();
}
factory.register(new ProviderEventListener() {
@Override
public void onEvent(ProviderEvent event) {
if (event instanceof RealmModel.RealmCreationEvent) {
RealmModel realm = ((RealmModel.RealmCreationEvent)event).getCreatedRealm();
addMappers(realm);
}
}
});
}
protected void addMappers(RealmModel realm) {
int counter = 0;
// the ids must never change!!!! So if you add more default mappers, then add to end with higher counter.
addClaimMapper(realm, "username", OIDCUserModelMapper.PROVIDER_ID,
OIDCUserModelMapper.USER_MODEL_PROPERTY, "username",
"preferred_username", "String",
true, "username",
true);
addClaimMapper(realm, "email", OIDCUserModelMapper.PROVIDER_ID,
OIDCUserModelMapper.USER_MODEL_PROPERTY, "email",
"email", "String",
true, "email",
true);
addClaimMapper(realm, "given name", OIDCUserModelMapper.PROVIDER_ID,
OIDCUserModelMapper.USER_MODEL_PROPERTY, "firstName",
"given_name", "String",
true, "given name",
true);
addClaimMapper(realm, "family name", OIDCUserModelMapper.PROVIDER_ID,
OIDCUserModelMapper.USER_MODEL_PROPERTY, "lastName",
"family_name", "String",
true, "family name",
true);
addClaimMapper(realm, "email verified", OIDCUserModelMapper.PROVIDER_ID,
OIDCUserModelMapper.USER_MODEL_PROPERTY, "emailVerified",
"email_verified", "boolean",
false, null,
false);
ProtocolMapperModel fullName = new ProtocolMapperModel();
if (realm.getProtocolMapperByName("full name") == null) {
fullName.setName("full name");
fullName.setProtocolMapper(OIDCFullNameMapper.PROVIDER_ID);
fullName.setProtocol(OIDCLoginProtocol.LOGIN_PROTOCOL);
fullName.setConsentRequired(true);
fullName.setConsentText("full name");
fullName.setAppliedByDefault(true);
realm.addProtocolMapper(fullName);
}
ProtocolMapperModel address = new ProtocolMapperModel();
if (realm.getProtocolMapperByName("address") == null) {
address.setName("address");
address.setProtocolMapper(OIDCAddressMapper.PROVIDER_ID);
address.setProtocol(OIDCLoginProtocol.LOGIN_PROTOCOL);
address.setConsentRequired(true);
address.setConsentText("address");
address.setAppliedByDefault(false);
realm.addProtocolMapper(address);
}
}
protected void addClaimMapper(RealmModel realm, String name, String mapperRef,
String propertyName, String propertyNameValue,
String tokenClaimName, String claimType,
boolean consentRequired, String consentText,
boolean appliedByDefault) {
ProtocolMapperModel mapper = realm.getProtocolMapperByName(name);
if (mapper != null) return;
mapper = new ProtocolMapperModel();
mapper.setName(name);
mapper.setProtocolMapper(mapperRef);
mapper.setProtocol(OIDCLoginProtocol.LOGIN_PROTOCOL);
mapper.setConsentRequired(consentRequired);
mapper.setConsentText(consentText);
mapper.setAppliedByDefault(appliedByDefault);
Map<String, String> config = new HashMap<String, String>();
config.put(propertyName, propertyNameValue);
config.put(AttributeMapperHelper.TOKEN_CLAIM_NAME, tokenClaimName);
config.put(AttributeMapperHelper.JSON_TYPE, claimType);
mapper.setConfig(config);
realm.addProtocolMapper(mapper);
}
@Override
public Object createProtocolEndpoint(RealmModel realm, EventBuilder event, AuthenticationManager authManager) {
return new OIDCLoginProtocolService(realm, event, authManager);

View file

@ -340,7 +340,7 @@ public class OIDCLoginProtocolService {
TokenManager.attachClientSession(userSession, clientSession);
AccessTokenResponse res = tokenManager.responseBuilder(realm, client, event)
.generateAccessToken(scope, client, user, userSession, clientSession)
.generateAccessToken(session, scope, client, user, userSession, clientSession)
.generateRefreshToken()
.generateIDToken()
.build();
@ -668,7 +668,7 @@ public class OIDCLoginProtocolService {
clientSession.setNote(AdapterConstants.APPLICATION_SESSION_HOST, adapterSessionHost);
}
AccessToken token = tokenManager.createClientAccessToken(accessCode.getRequestedRoles(), realm, client, user, userSession, clientSession);
AccessToken token = tokenManager.createClientAccessToken(session, accessCode.getRequestedRoles(), realm, client, user, userSession, clientSession);
try {
tokenManager.verifyAccess(token, realm, client, user);

View file

@ -13,6 +13,7 @@ import org.keycloak.models.ClaimMask;
import org.keycloak.models.ClientModel;
import org.keycloak.models.ClientSessionModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.models.ProtocolMapperModel;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleModel;
@ -20,6 +21,8 @@ import org.keycloak.models.UserModel;
import org.keycloak.models.UserSessionModel;
import org.keycloak.models.UserSessionProvider;
import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.protocol.ProtocolMapper;
import org.keycloak.protocol.oidc.mappers.OIDCAccessTokenMapper;
import org.keycloak.representations.AccessToken;
import org.keycloak.representations.AccessTokenResponse;
import org.keycloak.representations.UserClaimSet;
@ -104,7 +107,7 @@ public class TokenManager {
AccessToken accessToken = initToken(realm, client, user, userSession, clientSession);
accessToken.setRealmAccess(refreshToken.getRealmAccess());
accessToken.setResourceAccess(refreshToken.getResourceAccess());
accessToken = transformToken(accessToken, realm, client, user, userSession, clientSession);
accessToken = transformToken(session, accessToken, realm, client, user, userSession, clientSession);
userSession.setLastSessionRefresh(currentTime);
@ -132,12 +135,12 @@ public class TokenManager {
return refreshToken;
}
public AccessToken createClientAccessToken(Set<RoleModel> requestedRoles, RealmModel realm, ClientModel client, UserModel user, UserSessionModel session, ClientSessionModel clientSession) {
AccessToken token = initToken(realm, client, user, session, clientSession);
public AccessToken createClientAccessToken(KeycloakSession session, Set<RoleModel> requestedRoles, RealmModel realm, ClientModel client, UserModel user, UserSessionModel userSession, ClientSessionModel clientSession) {
AccessToken token = initToken(realm, client, user, userSession, clientSession);
for (RoleModel role : requestedRoles) {
addComposites(token, role);
}
token = transformToken(token, realm, client, user, session, clientSession);
token = transformToken(session, token, realm, client, user, userSession, clientSession);
return token;
}
@ -232,36 +235,20 @@ public class TokenManager {
}
}
public void initClaims(UserClaimSet claimSet, ClientModel model, UserModel user) {
claimSet.setSubject(user.getId());
if (ClaimMask.hasUsername(model.getAllowedClaimsMask())) {
claimSet.setPreferredUsername(user.getUsername());
}
if (ClaimMask.hasEmail(model.getAllowedClaimsMask())) {
claimSet.setEmail(user.getEmail());
claimSet.setEmailVerified(user.isEmailVerified());
}
if (ClaimMask.hasName(model.getAllowedClaimsMask())) {
claimSet.setFamilyName(user.getLastName());
claimSet.setGivenName(user.getFirstName());
StringBuilder fullName = new StringBuilder();
if (user.getFirstName() != null) fullName.append(user.getFirstName()).append(" ");
if (user.getLastName() != null) fullName.append(user.getLastName());
claimSet.setName(fullName.toString());
}
Set<ProtocolMapperModel> mappings = model.getProtocolMappers();
public AccessToken transformToken(KeycloakSession session, AccessToken token, RealmModel realm, ClientModel client, UserModel user,
UserSessionModel userSession, ClientSessionModel clientSession) {
Set<ProtocolMapperModel> mappings = client.getProtocolMappers();
KeycloakSessionFactory sessionFactory = session.getKeycloakSessionFactory();
for (ProtocolMapperModel mapping : mappings) {
if (!mapping.getProtocol().equals(OIDCLoginProtocol.LOGIN_PROTOCOL)) continue;
}
}
ProtocolMapper mapper = (ProtocolMapper)sessionFactory.getProviderFactory(ProtocolMapper.class, mapping.getProtocolMapper());
if (mapper == null || !(mapper instanceof OIDCAccessTokenMapper)) continue;
token = ((OIDCAccessTokenMapper)mapper).transformToken(token, mapping, session, userSession, clientSession);
protected AccessToken transformToken(AccessToken token, RealmModel realm, ClientModel client, UserModel user,
UserSessionModel session, ClientSessionModel clientSession) {
UserClaimSet claimSet = token.getUserClaimSet();
initClaims(claimSet, client, user);
}
return token;
}
@ -350,9 +337,9 @@ public class TokenManager {
return this;
}
public AccessTokenResponseBuilder generateAccessToken(String scopeParam, ClientModel client, UserModel user, UserSessionModel session, ClientSessionModel clientSession) {
public AccessTokenResponseBuilder generateAccessToken(KeycloakSession session, String scopeParam, ClientModel client, UserModel user, UserSessionModel userSession, ClientSessionModel clientSession) {
Set<RoleModel> requestedRoles = getAccess(scopeParam, client, user);
accessToken = createClientAccessToken(requestedRoles, realm, client, user, session, clientSession);
accessToken = createClientAccessToken(session, requestedRoles, realm, client, user, userSession, clientSession);
return this;
}
@ -395,16 +382,11 @@ public class TokenManager {
idToken.getUserClaimSet().setEmail(accessToken.getUserClaimSet().getEmail());
idToken.getUserClaimSet().setEmailVerified(accessToken.getUserClaimSet().getEmailVerified());
idToken.getUserClaimSet().setLocale(accessToken.getUserClaimSet().getLocale());
idToken.getUserClaimSet().setFormattedAddress(accessToken.getUserClaimSet().getFormattedAddress());
idToken.getUserClaimSet().setAddress(accessToken.getUserClaimSet().getAddress());
idToken.getUserClaimSet().setStreetAddress(accessToken.getUserClaimSet().getStreetAddress());
idToken.getUserClaimSet().setLocality(accessToken.getUserClaimSet().getLocality());
idToken.getUserClaimSet().setRegion(accessToken.getUserClaimSet().getRegion());
idToken.getUserClaimSet().setPostalCode(accessToken.getUserClaimSet().getPostalCode());
idToken.getUserClaimSet().setCountry(accessToken.getUserClaimSet().getCountry());
idToken.getUserClaimSet().setPhoneNumber(accessToken.getUserClaimSet().getPhoneNumber());
idToken.getUserClaimSet().setPhoneNumberVerified(accessToken.getUserClaimSet().getPhoneNumberVerified());
idToken.getUserClaimSet().setZoneinfo(accessToken.getUserClaimSet().getZoneinfo());
idToken.setOtherClaims(accessToken.getOtherClaims());
return this;
}

View file

@ -48,6 +48,8 @@ import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
import java.util.HashMap;
import java.util.Map;
/**
* @author pedroigor
@ -130,9 +132,8 @@ public class UserInfoService {
UserSessionModel userSession = session.sessions().getUserSession(realmModel, accessToken.getSessionState());
ClientModel clientModel = realmModel.findClient(accessToken.getIssuedFor());
UserModel userModel = userSession.getUser();
UserClaimSet userInfo = new UserClaimSet();
this.tokenManager.initClaims(userInfo, clientModel, userModel);
AccessToken userInfo = new AccessToken();
this.tokenManager.transformToken(session, userInfo, realmModel, clientModel, userModel, userSession, null);
event
.detail(Details.USERNAME, userModel.getUsername())
@ -141,7 +142,10 @@ public class UserInfoService {
.user(userModel)
.success();
return Cors.add(request, Response.ok(userInfo)).auth().allowedOrigins(accessToken).build();
Map<String, Object> claims = new HashMap<String, Object>();
claims.putAll(userInfo.getOtherClaims());
claims.put("sub", userModel.getId());
return Cors.add(request, Response.ok(claims)).auth().allowedOrigins(accessToken).build();
} catch (Exception e) {
throw new UnauthorizedException("Could not retrieve user info.", e);
}

View file

@ -0,0 +1,59 @@
package org.keycloak.protocol.oidc.mappers;
import org.keycloak.models.ProtocolMapperModel;
import org.keycloak.representations.AccessToken;
import java.util.HashMap;
import java.util.Map;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class AttributeMapperHelper {
public static final String TOKEN_CLAIM_NAME = "Token Claim Name";
public static final String JSON_TYPE = "Claim JSON Type";
public static Object mapAttributeValue(ProtocolMapperModel mappingModel, Object attributeValue) {
if (attributeValue == null) return null;
String type = mappingModel.getConfig().get(JSON_TYPE);
if (type == null) return attributeValue;
if (type.equals("boolean")) {
if (attributeValue instanceof Boolean) return attributeValue;
if (attributeValue instanceof String) return Boolean.valueOf((String)attributeValue);
throw new RuntimeException("cannot map type for token claim");
} else if (type.equals("String")) {
if (attributeValue instanceof String) return attributeValue;
return attributeValue.toString();
} else if (type.equals("long")) {
if (attributeValue instanceof Long) return attributeValue;
if (attributeValue instanceof String) return Long.valueOf((String)attributeValue);
throw new RuntimeException("cannot map type for token claim");
} else if (type.equals("int")) {
if (attributeValue instanceof Integer) return attributeValue;
if (attributeValue instanceof String) return Integer.valueOf((String)attributeValue);
throw new RuntimeException("cannot map type for token claim");
}
return attributeValue;
}
public static void mapClaim(AccessToken token, ProtocolMapperModel mappingModel, Object attributeValue) {
if (attributeValue == null) return;
attributeValue = mapAttributeValue(mappingModel, attributeValue);
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,59 @@
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 org.keycloak.representations.UserClaimSet;
import java.util.ArrayList;
import java.util.List;
/**
* Set the 'name' claim to be first + last name.
*
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class OIDCAddressMapper extends AbstractOIDCProtocolMapper implements OIDCAccessTokenMapper {
private static final List<ConfigProperty> configProperties = new ArrayList<ConfigProperty>();
static {
}
public static final String PROVIDER_ID = "oidc-address-mapper";
public List<ConfigProperty> getConfigProperties() {
return configProperties;
}
@Override
public String getId() {
return PROVIDER_ID;
}
@Override
public String getDisplayType() {
return "Address Mapper";
}
@Override
public AccessToken transformToken(AccessToken token, ProtocolMapperModel mappingModel, KeycloakSession session,
UserSessionModel userSession, ClientSessionModel clientSession) {
UserModel user = userSession.getUser();
UserClaimSet.AddressClaimSet addressSet = new UserClaimSet.AddressClaimSet();
addressSet.setStreetAddress(user.getAttribute("street"));
addressSet.setLocality(user.getAttribute("locality"));
addressSet.setRegion(user.getAttribute("region"));
addressSet.setPostalCode(user.getAttribute("postal_code"));
addressSet.setCountry(user.getAttribute("country"));
token.getOtherClaims().put("address", addressSet);
return token;
}
}

View file

@ -28,8 +28,8 @@ public class OIDCClientSessionNoteMapper extends AbstractOIDCProtocolMapper impl
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.setName(AttributeMapperHelper.TOKEN_CLAIM_NAME);
property.setLabel(AttributeMapperHelper.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);
@ -53,7 +53,7 @@ public class OIDCClientSessionNoteMapper extends AbstractOIDCProtocolMapper impl
UserSessionModel userSession, ClientSessionModel clientSession) {
String note = mappingModel.getConfig().get(CLIENT_SESSION_NOTE);
String noteValue = clientSession.getNote(note);
OIDCUserAttributeMapper.mapClaim(token, mappingModel, noteValue);
AttributeMapperHelper.mapClaim(token, mappingModel, noteValue);
return token;
}

View file

@ -0,0 +1,54 @@
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.util.ArrayList;
import java.util.List;
/**
* Set the 'name' claim to be first + last name.
*
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class OIDCFullNameMapper extends AbstractOIDCProtocolMapper implements OIDCAccessTokenMapper {
private static final List<ConfigProperty> configProperties = new ArrayList<ConfigProperty>();
static {
}
public static final String PROVIDER_ID = "oidc-full-name-mapper";
public List<ConfigProperty> getConfigProperties() {
return configProperties;
}
@Override
public String getId() {
return PROVIDER_ID;
}
@Override
public String getDisplayType() {
return "Full name Mapper";
}
@Override
public AccessToken transformToken(AccessToken token, ProtocolMapperModel mappingModel, KeycloakSession session,
UserSessionModel userSession, ClientSessionModel clientSession) {
UserModel user = userSession.getUser();
String first = user.getFirstName() == null ? "" : user.getFirstName() + " ";
String last = user.getLastName() == null ? "" : user.getLastName();
token.getOtherClaims().put("name", first + last);
return token;
}
}

View file

@ -7,11 +7,8 @@ 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,
@ -23,7 +20,6 @@ import java.util.Map;
*/
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";
@ -34,13 +30,15 @@ public class OIDCUserAttributeMapper extends AbstractOIDCProtocolMapper implemen
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.setName(AttributeMapperHelper.TOKEN_CLAIM_NAME);
property.setLabel(AttributeMapperHelper.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 static final String PROVIDER_ID = "oidc-usermodel-attribute-mapper";
public List<ConfigProperty> getConfigProperties() {
return configProperties;
@ -48,7 +46,7 @@ public class OIDCUserAttributeMapper extends AbstractOIDCProtocolMapper implemen
@Override
public String getId() {
return "oidc-usermodel-attribute-mapper";
return PROVIDER_ID;
}
@Override
@ -63,26 +61,8 @@ public class OIDCUserAttributeMapper extends AbstractOIDCProtocolMapper implemen
String attributeName = mappingModel.getConfig().get(USER_MODEL_ATTRIBUTE_NAME);
String attributeValue = user.getAttribute(attributeName);
if (attributeValue == null) return token;
mapClaim(token, mappingModel, attributeValue);
AttributeMapperHelper.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

@ -9,9 +9,7 @@ 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,
@ -32,20 +30,22 @@ public class OIDCUserModelMapper extends AbstractOIDCProtocolMapper implements O
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.setName(AttributeMapperHelper.TOKEN_CLAIM_NAME);
property.setLabel(AttributeMapperHelper.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 static final String PROVIDER_ID = "oidc-usermodel-property-mapper";
public List<ConfigProperty> getConfigProperties() {
return configProperties;
}
@Override
public String getId() {
return "oidc-usermodel-property-mapper";
return PROVIDER_ID;
}
@Override
@ -59,7 +59,7 @@ public class OIDCUserModelMapper extends AbstractOIDCProtocolMapper implements O
UserModel user = userSession.getUser();
String propertyName = mappingModel.getConfig().get(USER_MODEL_PROPERTY);
String propertyValue = getUserModelValue(user,propertyName);
OIDCUserAttributeMapper.mapClaim(token, mappingModel, propertyValue);
AttributeMapperHelper.mapClaim(token, mappingModel, propertyValue);
return token;
}
@ -73,6 +73,14 @@ public class OIDCUserModelMapper extends AbstractOIDCProtocolMapper implements O
if (val != null) return val.toString();
} catch (Exception ignore) {
}
methodName = "is" + 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

@ -28,8 +28,8 @@ public class OIDCUserSessionNoteMapper extends AbstractOIDCProtocolMapper implem
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.setName(AttributeMapperHelper.TOKEN_CLAIM_NAME);
property.setLabel(AttributeMapperHelper.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);
@ -54,7 +54,7 @@ public class OIDCUserSessionNoteMapper extends AbstractOIDCProtocolMapper implem
UserSessionModel userSession, ClientSessionModel clientSession) {
String note = mappingModel.getConfig().get(USER_SESSION_NOTE);
String noteValue = userSession.getNote(note);
OIDCUserAttributeMapper.mapClaim(token, mappingModel, noteValue);
AttributeMapperHelper.mapClaim(token, mappingModel, noteValue);
return token;
}

View file

@ -87,6 +87,11 @@ public class DefaultKeycloakSessionFactory implements KeycloakSessionFactory {
}
}
}
for ( Map<String, ProviderFactory> factories : factoriesMap.values()) {
for (ProviderFactory factory : factories.values()) {
factory.postInit(this);
}
}
}
public KeycloakSession create() {

View file

@ -1 +1,8 @@
org.keycloak.protocol.oidc.mappers.OIDCUserAttributeMapper
org.keycloak.protocol.oidc.mappers.OIDCClientSessionNoteMapper
org.keycloak.protocol.oidc.mappers.OIDCFullNameMapper
org.keycloak.protocol.oidc.mappers.OIDCUserModelMapper
org.keycloak.protocol.oidc.mappers.OIDCUserSessionNoteMapper
org.keycloak.protocol.oidc.mappers.OIDCAddressMapper

View file

@ -1,2 +1,3 @@
org.keycloak.protocol.LoginProtocolSpi
org.keycloak.protocol.ProtocolMapperSpi
org.keycloak.exportimport.ApplicationImportSpi

View file

@ -139,7 +139,7 @@ public class AdapterTestStrategy extends ExternalResource {
TokenManager tm = new TokenManager();
UserModel admin = session.users().getUserByUsername("admin", adminRealm);
UserSessionModel userSession = session.sessions().createUserSession(adminRealm, admin, "admin", null, "form", false);
AccessToken token = tm.createClientAccessToken(TokenManager.getAccess(null, adminConsole, admin), adminRealm, adminConsole, admin, userSession, null);
AccessToken token = tm.createClientAccessToken(session, TokenManager.getAccess(null, adminConsole, admin), adminRealm, adminConsole, admin, userSession, null);
return tm.encodeToken(adminRealm, token);
} finally {
keycloakRule.stopSession(session, true);

View file

@ -87,7 +87,7 @@ public class RelativeUriAdapterTest {
TokenManager tm = new TokenManager();
UserModel admin = session.users().getUserByUsername("admin", adminRealm);
UserSessionModel userSession = session.sessions().createUserSession(adminRealm, admin, "user", null, "form", false);
AccessToken token = tm.createClientAccessToken(tm.getAccess(null, adminConsole, admin), adminRealm, adminConsole, admin, userSession, null);
AccessToken token = tm.createClientAccessToken(session, tm.getAccess(null, adminConsole, admin), adminRealm, adminConsole, admin, userSession, null);
adminToken = tm.encodeToken(adminRealm, token);
}

View file

@ -79,7 +79,7 @@ public class AdminAPITest {
TokenManager tm = new TokenManager();
UserModel admin = session.users().getUserByUsername("admin", adminRealm);
UserSessionModel userSession = session.sessions().createUserSession(adminRealm, admin, "admin", null, "form", false);
AccessToken token = tm.createClientAccessToken(tm.getAccess(null, adminConsole, admin), adminRealm, adminConsole, admin, userSession, null);
AccessToken token = tm.createClientAccessToken(session, tm.getAccess(null, adminConsole, admin), adminRealm, adminConsole, admin, userSession, null);
return tm.encodeToken(adminRealm, token);
} finally {
keycloakRule.stopSession(session, true);

View file

@ -247,7 +247,7 @@ public class SamlBindingTest {
TokenManager tm = new TokenManager();
UserModel admin = session.users().getUserByUsername("admin", adminRealm);
UserSessionModel userSession = session.sessions().createUserSession(adminRealm, admin, "admin", null, "form", false);
AccessToken token = tm.createClientAccessToken(tm.getAccess(null, adminConsole, admin), adminRealm, adminConsole, admin, userSession, null);
AccessToken token = tm.createClientAccessToken(session, tm.getAccess(null, adminConsole, admin), adminRealm, adminConsole, admin, userSession, null);
return tm.encodeToken(adminRealm, token);
} finally {
keycloakRule.stopSession(session, true);