diff --git a/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.2.0.Beta1.xml b/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.2.0.Beta1.xml index 71fc2c4be4..6139c3d931 100755 --- a/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.2.0.Beta1.xml +++ b/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.2.0.Beta1.xml @@ -5,14 +5,21 @@ - - - - + + + + + + + + + - + + + diff --git a/core/src/main/java/org/keycloak/representations/UserClaimSet.java b/core/src/main/java/org/keycloak/representations/UserClaimSet.java old mode 100644 new mode 100755 index c6472691a1..4e8f1f9f57 --- a/core/src/main/java/org/keycloak/representations/UserClaimSet.java +++ b/core/src/main/java/org/keycloak/representations/UserClaimSet.java @@ -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; } diff --git a/core/src/main/java/org/keycloak/representations/idm/ApplicationRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/ApplicationRepresentation.java index 0e42a40ba7..9ca74f07d0 100755 --- a/core/src/main/java/org/keycloak/representations/idm/ApplicationRepresentation.java +++ b/core/src/main/java/org/keycloak/representations/idm/ApplicationRepresentation.java @@ -30,7 +30,7 @@ public class ApplicationRepresentation { protected Integer nodeReRegistrationTimeout; protected Map registeredNodes; protected List allowedIdentityProviders; - protected Set protocolClaimMappings; + protected List protocolMappers; public String getId() { return id; @@ -200,11 +200,11 @@ public class ApplicationRepresentation { this.allowedIdentityProviders = allowedIdentityProviders; } - public Set getProtocolClaimMappings() { - return protocolClaimMappings; + public List getProtocolMappers() { + return protocolMappers; } - public void setProtocolClaimMappings(Set protocolClaimMappings) { - this.protocolClaimMappings = protocolClaimMappings; + public void setProtocolMappers(List protocolMappers) { + this.protocolMappers = protocolMappers; } } diff --git a/core/src/main/java/org/keycloak/representations/idm/ClientProtocolMappingRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/ClientProtocolMappingRepresentation.java new file mode 100755 index 0000000000..e5252b1c16 --- /dev/null +++ b/core/src/main/java/org/keycloak/representations/idm/ClientProtocolMappingRepresentation.java @@ -0,0 +1,26 @@ +package org.keycloak.representations.idm; + +/** + * @author Bill Burke + * @version $Revision: 1 $ + */ +public class ClientProtocolMappingRepresentation { + protected String protocol; + protected String name; + + public String getProtocol() { + return protocol; + } + + public void setProtocol(String protocol) { + this.protocol = protocol; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } +} diff --git a/core/src/main/java/org/keycloak/representations/idm/OAuthClientRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/OAuthClientRepresentation.java index dcb6ba805e..aa095a7000 100755 --- a/core/src/main/java/org/keycloak/representations/idm/OAuthClientRepresentation.java +++ b/core/src/main/java/org/keycloak/representations/idm/OAuthClientRepresentation.java @@ -24,7 +24,7 @@ public class OAuthClientRepresentation { protected Boolean fullScopeAllowed; protected Boolean frontchannelLogout; protected List allowedIdentityProviders; - protected Set protocolClaimMappings; + protected List protocolMappers; public String getId() { @@ -147,11 +147,11 @@ public class OAuthClientRepresentation { this.allowedIdentityProviders = allowedIdentityProviders; } - public Set getProtocolClaimMappings() { - return protocolClaimMappings; + public List getProtocolMappers() { + return protocolMappers; } - public void setProtocolClaimMappings(Set protocolClaimMappings) { - this.protocolClaimMappings = protocolClaimMappings; + public void setProtocolMappers(List protocolMappers) { + this.protocolMappers = protocolMappers; } } diff --git a/core/src/main/java/org/keycloak/representations/idm/RealmRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/RealmRepresentation.java index 9b655dc15a..dc5ada6a6d 100755 --- a/core/src/main/java/org/keycloak/representations/idm/RealmRepresentation.java +++ b/core/src/main/java/org/keycloak/representations/idm/RealmRepresentation.java @@ -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 eventsListeners; private List identityProviders; private List claimTypes; - private List protocolClaimMappings; + private List protocolMappers; private Boolean identityFederationEnabled; public String getId() { @@ -492,11 +493,16 @@ public class RealmRepresentation { this.claimTypes = claimTypes; } - public List getProtocolClaimMappings() { - return protocolClaimMappings; + public List getProtocolMappers() { + return protocolMappers; } - public void setProtocolClaimMappings(List protocolClaimMappings) { - this.protocolClaimMappings = protocolClaimMappings; + public void addProtocolMapper(ProtocolMapperRepresentation rep) { + if (protocolMappers == null) protocolMappers = new LinkedList(); + protocolMappers.add(rep); + } + + public void setProtocolMappers(List protocolMappers) { + this.protocolMappers = protocolMappers; } } diff --git a/model/api/src/main/java/org/keycloak/models/ClientModel.java b/model/api/src/main/java/org/keycloak/models/ClientModel.java index 243de4dc43..5354d04225 100755 --- a/model/api/src/main/java/org/keycloak/models/ClientModel.java +++ b/model/api/src/main/java/org/keycloak/models/ClientModel.java @@ -107,4 +107,5 @@ public interface ClientModel { Set getProtocolMappers(); void addProtocolMappers(Set mapperIds); void removeProtocolMappers(Set mapperIds); + void setProtocolMappers(Set mapperIds); } diff --git a/model/api/src/main/java/org/keycloak/models/RealmModel.java b/model/api/src/main/java/org/keycloak/models/RealmModel.java index badc6bdbb9..f70f8255a5 100755 --- a/model/api/src/main/java/org/keycloak/models/RealmModel.java +++ b/model/api/src/main/java/org/keycloak/models/RealmModel.java @@ -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 protocol, String name); } diff --git a/model/api/src/main/java/org/keycloak/models/RealmProvider.java b/model/api/src/main/java/org/keycloak/models/RealmProvider.java index f2454d41bd..58328b2cb5 100755 --- a/model/api/src/main/java/org/keycloak/models/RealmProvider.java +++ b/model/api/src/main/java/org/keycloak/models/RealmProvider.java @@ -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 diff --git a/model/api/src/main/java/org/keycloak/models/entities/ClientEntity.java b/model/api/src/main/java/org/keycloak/models/entities/ClientEntity.java index 1cfbcfe32d..3d43feeb3d 100755 --- a/model/api/src/main/java/org/keycloak/models/entities/ClientEntity.java +++ b/model/api/src/main/java/org/keycloak/models/entities/ClientEntity.java @@ -30,7 +30,7 @@ public class ClientEntity extends AbstractIdentifiableEntity { private List redirectUris = new ArrayList(); private List scopeIds = new ArrayList(); private List allowedIdentityProviders = new ArrayList(); - private Set protocolClaimMappings = new HashSet(); + private Set protocolMappers = new HashSet(); public String getName() { return name; @@ -152,11 +152,11 @@ public class ClientEntity extends AbstractIdentifiableEntity { this.allowedIdentityProviders = allowedIdentityProviders; } - public Set getProtocolClaimMappings() { - return protocolClaimMappings; + public Set getProtocolMappers() { + return protocolMappers; } - public void setProtocolClaimMappings(Set protocolClaimMappings) { - this.protocolClaimMappings = protocolClaimMappings; + public void setProtocolMappers(Set protocolMappers) { + this.protocolMappers = protocolMappers; } } diff --git a/model/api/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java b/model/api/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java index 69d5477f07..522c96572b 100755 --- a/model/api/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java +++ b/model/api/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java @@ -19,6 +19,7 @@ import org.keycloak.models.UserSessionModel; import org.keycloak.representations.idm.ApplicationRepresentation; import org.keycloak.representations.idm.ClaimRepresentation; import org.keycloak.representations.idm.ClaimTypeRepresentation; +import org.keycloak.representations.idm.ClientProtocolMappingRepresentation; import org.keycloak.representations.idm.CredentialRepresentation; import org.keycloak.representations.idm.FederatedIdentityRepresentation; import org.keycloak.representations.idm.IdentityProviderRepresentation; @@ -158,7 +159,7 @@ public class ModelToRepresentation { } for (ProtocolMapperModel mapping : realm.getProtocolMappers()) { - rep.getProtocolClaimMappings().add(toRepresentation(mapping)); + rep.addProtocolMapper(toRepresentation(mapping)); } return rep; @@ -266,9 +267,13 @@ public class ModelToRepresentation { } if (!applicationModel.getProtocolMappers().isEmpty()) { - Set mappings = new HashSet(); - for (ProtocolMapperModel model : applicationModel.getProtocolMappers()) mappings.add(model.getId()); - rep.setProtocolClaimMappings(mappings); + List mappings = new LinkedList(); + for (ProtocolMapperModel model : applicationModel.getProtocolMappers()) { + ClientProtocolMappingRepresentation map = new ClientProtocolMappingRepresentation(); + map.setProtocol(model.getProtocol()); + map.setName(model.getName()); + } + rep.setProtocolMappers(mappings); } return rep; @@ -301,10 +306,15 @@ public class ModelToRepresentation { } if (!model.getProtocolMappers().isEmpty()) { - Set mappings = new HashSet(); - for (ProtocolMapperModel mappingMoel : model.getProtocolMappers()) mappings.add(mappingMoel.getId()); - rep.setProtocolClaimMappings(mappings); + List mappings = new LinkedList(); + for (ProtocolMapperModel mapping : model.getProtocolMappers()) { + ClientProtocolMappingRepresentation map = new ClientProtocolMappingRepresentation(); + map.setProtocol(mapping.getProtocol()); + map.setName(mapping.getName()); + } + rep.setProtocolMappers(mappings); } + return rep; } diff --git a/model/api/src/main/java/org/keycloak/models/utils/RepresentationToModel.java b/model/api/src/main/java/org/keycloak/models/utils/RepresentationToModel.java index 2fb49af311..9e152b8137 100755 --- a/model/api/src/main/java/org/keycloak/models/utils/RepresentationToModel.java +++ b/model/api/src/main/java/org/keycloak/models/utils/RepresentationToModel.java @@ -23,6 +23,7 @@ import org.keycloak.models.UserModel; import org.keycloak.representations.idm.ApplicationRepresentation; import org.keycloak.representations.idm.ClaimRepresentation; import org.keycloak.representations.idm.ClaimTypeRepresentation; +import org.keycloak.representations.idm.ClientProtocolMappingRepresentation; import org.keycloak.representations.idm.CredentialRepresentation; import org.keycloak.representations.idm.FederatedIdentityRepresentation; import org.keycloak.representations.idm.IdentityProviderRepresentation; @@ -460,10 +461,19 @@ public class RepresentationToModel { applicationModel.setAllowedClaimsMask(ClaimMask.ALL); } - if (resourceRep.getProtocolClaimMappings() != null) { - applicationModel.addProtocolMappers(resourceRep.getProtocolClaimMappings()); + if (resourceRep.getProtocolMappers() != null) { + Set ids = new HashSet(); + for (ClientProtocolMappingRepresentation map : resourceRep.getProtocolMappers()) { + ProtocolMapperModel mapperModel = applicationModel.getRealm().getProtocolMapperByName(map.getProtocol(), map.getName()); + if (mapperModel != null) { + ids.add(mapperModel.getId()); + } + + } + applicationModel.setProtocolMappers(ids); } + return applicationModel; } @@ -637,8 +647,16 @@ public class RepresentationToModel { model.updateAllowedIdentityProviders(rep.getAllowedIdentityProviders()); } - if (rep.getProtocolClaimMappings() != null) { - model.addProtocolMappers(rep.getProtocolClaimMappings()); + if (rep.getProtocolMappers() != null) { + Set ids = new HashSet(); + for (ClientProtocolMappingRepresentation map : rep.getProtocolMappers()) { + ProtocolMapperModel mapperModel = model.getRealm().getProtocolMapperByName(map.getProtocol(), map.getName()); + if (mapperModel != null) { + ids.add(mapperModel.getId()); + } + + } + model.setProtocolMappers(ids); } } @@ -773,13 +791,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 mappers = newRealm.getProtocolMappers(); + for (ProtocolMapperRepresentation representation : rep.getProtocolMappers()) { + ProtocolMapperModel existing = newRealm.getProtocolMapperByName(representation.getProtocol(), 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); } } } diff --git a/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/ClientAdapter.java b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/ClientAdapter.java index 83f5490089..0fc38bcc9d 100755 --- a/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/ClientAdapter.java +++ b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/ClientAdapter.java @@ -286,16 +286,23 @@ public abstract class ClientAdapter implements ClientModel { return cachedClient.getProtocolClaimMappings(); } @Override - public void addProtocolMappers(Set mappingIds) { + public void addProtocolMappers(Set mapperNames) { getDelegateForUpdate(); - updatedClient.addProtocolMappers(mappingIds); + updatedClient.addProtocolMappers(mapperNames); } @Override - public void removeProtocolMappers(Set mappingIds) { + public void removeProtocolMappers(Set mapperNames) { getDelegateForUpdate(); - updatedClient.removeProtocolMappers(mappingIds); + updatedClient.removeProtocolMappers(mapperNames); + + } + + @Override + public void setProtocolMappers(Set mapperNames) { + getDelegateForUpdate(); + updatedClient.setProtocolMappers(mapperNames); } } diff --git a/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/RealmAdapter.java b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/RealmAdapter.java index 21d32cc037..debd784a91 100755 --- a/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/RealmAdapter.java +++ b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/RealmAdapter.java @@ -919,6 +919,14 @@ public class RealmAdapter implements RealmModel { return null; } + @Override + public ProtocolMapperModel getProtocolMapperByName(String protocol, String name) { + for (ProtocolMapperModel mapping : cached.getClaimMappings()) { + if (mapping.getProtocol().equals(protocol) && mapping.getName().equals(name)) return mapping; + } + return null; + } + @Override public boolean equals(Object o) { if (this == o) return true; diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/ClientAdapter.java b/model/jpa/src/main/java/org/keycloak/models/jpa/ClientAdapter.java index c06cdecf57..d4616fd526 100755 --- a/model/jpa/src/main/java/org/keycloak/models/jpa/ClientAdapter.java +++ b/model/jpa/src/main/java/org/keycloak/models/jpa/ClientAdapter.java @@ -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,6 +380,18 @@ public abstract class ClientAdapter implements ClientModel { return mappings; } + protected ProtocolMapperEntity findProtocolMapperByName(String protocol, String name) { + TypedQuery query = em.createNamedQuery("getProtocolMapperByNameProtocol", ProtocolMapperEntity.class); + query.setParameter("name", name); + query.setParameter("protocol", protocol); + query.setParameter("realm", entity.getRealm()); + List 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 mappings) { Collection entities = entity.getProtocolMappers(); @@ -384,9 +399,9 @@ public abstract class ClientAdapter implements ClientModel { for (ProtocolMapperEntity rel : entities) { already.add(rel.getId()); } - for (String providerId : mappings) { - if (!already.contains(providerId)) { - ProtocolMapperEntity mapping = em.find(ProtocolMapperEntity.class, providerId); + for (String id : mappings) { + if (!already.contains(id)) { + ProtocolMapperEntity mapping = em.find(ProtocolMapperEntity.class, id); if (mapping != null) { entities.add(mapping); } @@ -407,6 +422,29 @@ public abstract class ClientAdapter implements ClientModel { } em.flush(); } + @Override + public void setProtocolMappers(Set mappings) { + Collection entities = entity.getProtocolMappers(); + Iterator it = entities.iterator(); + Set already = new HashSet(); + while (it.hasNext()) { + ProtocolMapperEntity mapper = it.next(); + if (mappings.contains(mapper.getId())) { + already.add(mapper.getId()); + continue; + } + it.remove(); + } + for (String id : mappings) { + if (!already.contains(id)) { + ProtocolMapperEntity mapping = em.find(ProtocolMapperEntity.class, id); + if (mapping != null) { + entities.add(mapping); + } + } + } + em.flush(); + } } diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/JpaRealmProvider.java b/model/jpa/src/main/java/org/keycloak/models/jpa/JpaRealmProvider.java index 371d0488b4..3091cc9213 100755 --- a/model/jpa/src/main/java/org/keycloak/models/jpa/JpaRealmProvider.java +++ b/model/jpa/src/main/java/org/keycloak/models/jpa/JpaRealmProvider.java @@ -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; diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java b/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java index 7578fd0ae0..00a3fbb79f 100755 --- a/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java +++ b/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java @@ -628,6 +628,17 @@ public class RealmAdapter implements RealmModel { return this.addApplication(KeycloakModelUtils.generateId(), name); } + public void addDefaultClientProtocolMappers(ClientModel client) { + Set adding = new HashSet(); + for (ProtocolMapperEntity mapper : realm.getProtocolMappers()) { + if (mapper.isAppliedByDefault()) { + adding.add(mapper.getId()); + } + } + 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 getProtocolMappers() { Set mappings = new HashSet(); - 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.getProtocol(), model.getName()) != null) { + throw new RuntimeException("protocol mapper name must be unique per protocol"); + } + 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 protocol, String name) { + for (ProtocolMapperEntity entity : realm.getProtocolMappers()) { + if (entity.getProtocol().equals(protocol) && 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 protocol, String name) { + ProtocolMapperEntity entity = getProtocolMapperEntityByName(protocol, 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()); diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/ClientEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/ClientEntity.java index ec8929e2a8..817fd8085c 100755 --- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/ClientEntity.java +++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/ClientEntity.java @@ -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 allowedIdentityProviders = new ArrayList(); - @OneToMany(cascade ={CascadeType.REMOVE}) + @OneToMany(fetch = FetchType.LAZY) @JoinTable(name="CLIENT_PROTOCOL_MAPPER", joinColumns = { @JoinColumn(name="CLIENT_ID")}, inverseJoinColumns = { @JoinColumn(name="MAPPING_ID")}) Collection protocolMappers = new ArrayList(); diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/ProtocolMapperEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/ProtocolMapperEntity.java index bd8bbe2a0d..cfb79390e1 100755 --- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/ProtocolMapperEntity.java +++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/ProtocolMapperEntity.java @@ -20,7 +20,7 @@ import java.util.Map; */ @Entity @NamedQueries({ - @NamedQuery(name="deleteProtocolClaimMappersByRealm", query="delete from ProtocolMapperEntity attr where attr.realm = :realm") + @NamedQuery(name="getProtocolMapperByNameProtocol", query="select mapper from ProtocolMapperEntity mapper where mapper.protocol = :protocol and mapper.name = :name and mapper.realm = :realm") }) @Table(name="PROTOCOL_MAPPER") public class ProtocolMapperEntity { diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RealmEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RealmEntity.java index e9cdda1e78..2f24d06bf7 100755 --- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RealmEntity.java +++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RealmEntity.java @@ -96,7 +96,7 @@ public class RealmEntity { Collection claimTypes = new ArrayList(); @OneToMany(cascade ={CascadeType.REMOVE}, orphanRemoval = true, mappedBy = "realm") - Collection protocolClaimMappings = new ArrayList(); + Collection protocolMappers = new ArrayList(); @OneToMany(cascade ={CascadeType.REMOVE}, orphanRemoval = true, mappedBy = "realm") Collection requiredCredentials = new ArrayList(); @@ -447,12 +447,12 @@ public class RealmEntity { this.claimTypes = claimTypes; } - public Collection getProtocolClaimMappings() { - return protocolClaimMappings; + public Collection getProtocolMappers() { + return protocolMappers; } - public void setProtocolClaimMappings(Collection protocolClaimMappings) { - this.protocolClaimMappings = protocolClaimMappings; + public void setProtocolMappers(Collection protocolMappers) { + this.protocolMappers = protocolMappers; } } diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/ClientAdapter.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/ClientAdapter.java index 1a6990dea0..8d87dc0b33 100755 --- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/ClientAdapter.java +++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/ClientAdapter.java @@ -295,7 +295,7 @@ public abstract class ClientAdapter extends A @Override public Set getProtocolMappers() { Set result = new HashSet(); - 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 extends A } @Override - public void addProtocolMappers(Set mappingIds) { - getMongoEntityAsClient().getProtocolClaimMappings().addAll(mappingIds); + public void addProtocolMappers(Set mapperIds) { + getMongoEntityAsClient().getProtocolMappers().addAll(mapperIds); updateMongoEntity(); } @Override - public void removeProtocolMappers(Set mappingIds) { - getMongoEntityAsClient().getProtocolClaimMappings().removeAll(mappingIds); + public void removeProtocolMappers(Set mapperIds) { + getMongoEntityAsClient().getProtocolMappers().removeAll(mapperIds); + updateMongoEntity(); + } + + @Override + public void setProtocolMappers(Set mapperIds) { + getMongoEntityAsClient().getProtocolMappers().clear(); + getMongoEntityAsClient().getProtocolMappers().addAll(mapperIds); updateMongoEntity(); } diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoRealmProvider.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoRealmProvider.java index 997025e888..61865d6e6e 100755 --- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoRealmProvider.java +++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoRealmProvider.java @@ -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; diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java index 9b20f2a869..d48ae7d898 100755 --- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java +++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java @@ -616,6 +616,14 @@ public class RealmAdapter extends AbstractMongoAdapter impleme return result; } + public void addDefaultClientProtocolMappers(ClientModel client) { + Set adding = new HashSet(); + for (ProtocolMapperEntity mapper : realm.getProtocolMappers()) { + if (mapper.isAppliedByDefault()) adding.add(mapper.getId()); + } + client.setProtocolMappers(adding); + + } @Override public ApplicationModel addApplication(String name) { return this.addApplication(null, name); @@ -630,7 +638,9 @@ public class RealmAdapter extends AbstractMongoAdapter 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 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 @@ -808,9 +820,11 @@ public class RealmAdapter extends AbstractMongoAdapter impleme @Override public ProtocolMapperModel addProtocolMapper(ProtocolMapperModel model) { + if (getProtocolMapperByName(model.getProtocol(), model.getName()) != null) { + throw new RuntimeException("protocol mapper name must be unique per protocol"); + } 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 +834,7 @@ public class RealmAdapter extends AbstractMongoAdapter 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 +849,7 @@ public class RealmAdapter extends AbstractMongoAdapter 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 +858,20 @@ public class RealmAdapter extends AbstractMongoAdapter impleme return null; } + protected ProtocolMapperEntity getProtocolMapperEntityByName(String protocol, String name) { + for (ProtocolMapperEntity entity : realm.getProtocolMappers()) { + if (entity.getProtocol().equals(protocol) && 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 +888,19 @@ public class RealmAdapter extends AbstractMongoAdapter 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 protocol, String name) { + ProtocolMapperEntity entity = getProtocolMapperEntityByName(protocol, 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()); diff --git a/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocolFactory.java b/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocolFactory.java index d8ff2e4b38..4b60b6a2e5 100755 --- a/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocolFactory.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocolFactory.java @@ -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 Bill Burke * @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 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(OIDCLoginProtocol.LOGIN_PROTOCOL, "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(OIDCLoginProtocol.LOGIN_PROTOCOL, "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(OIDCLoginProtocol.LOGIN_PROTOCOL, 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 config = new HashMap(); + 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); diff --git a/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocolService.java b/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocolService.java index ae0ba0a457..5e4080976f 100755 --- a/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocolService.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocolService.java @@ -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); diff --git a/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java b/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java index 4c56df7df7..8e48aeb640 100755 --- a/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java @@ -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 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 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 mappings = model.getProtocolMappers(); + public AccessToken transformToken(KeycloakSession session, AccessToken token, RealmModel realm, ClientModel client, UserModel user, + UserSessionModel userSession, ClientSessionModel clientSession) { + Set 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 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; } diff --git a/services/src/main/java/org/keycloak/protocol/oidc/UserInfoService.java b/services/src/main/java/org/keycloak/protocol/oidc/UserInfoService.java index 7a0e8a8951..0829d67f8b 100755 --- a/services/src/main/java/org/keycloak/protocol/oidc/UserInfoService.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/UserInfoService.java @@ -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 claims = new HashMap(); + 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); } diff --git a/services/src/main/java/org/keycloak/protocol/oidc/mappers/AttributeMapperHelper.java b/services/src/main/java/org/keycloak/protocol/oidc/mappers/AttributeMapperHelper.java new file mode 100755 index 0000000000..a4fe0fe0c8 --- /dev/null +++ b/services/src/main/java/org/keycloak/protocol/oidc/mappers/AttributeMapperHelper.java @@ -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 Bill Burke + * @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 jsonObject = token.getOtherClaims(); + for (int i = 0; i < split.length; i++) { + if (i == split.length - 1) { + jsonObject.put(split[i], attributeValue); + } else { + Map nested = (Map)jsonObject.get(split[i]); + if (nested == null) { + nested = new HashMap(); + jsonObject.put(split[i], nested); + jsonObject = nested; + } + } + } + } +} diff --git a/services/src/main/java/org/keycloak/protocol/oidc/mappers/OIDCAddressMapper.java b/services/src/main/java/org/keycloak/protocol/oidc/mappers/OIDCAddressMapper.java new file mode 100755 index 0000000000..ee7e8150e3 --- /dev/null +++ b/services/src/main/java/org/keycloak/protocol/oidc/mappers/OIDCAddressMapper.java @@ -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 Bill Burke + * @version $Revision: 1 $ + */ +public class OIDCAddressMapper extends AbstractOIDCProtocolMapper implements OIDCAccessTokenMapper { + + private static final List configProperties = new ArrayList(); + + static { + + } + + public static final String PROVIDER_ID = "oidc-address-mapper"; + + + public List 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; + } + +} diff --git a/services/src/main/java/org/keycloak/protocol/oidc/mappers/OIDCClientSessionNoteMapper.java b/services/src/main/java/org/keycloak/protocol/oidc/mappers/OIDCClientSessionNoteMapper.java index 3e38ed29e4..216c0fe972 100755 --- a/services/src/main/java/org/keycloak/protocol/oidc/mappers/OIDCClientSessionNoteMapper.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/mappers/OIDCClientSessionNoteMapper.java @@ -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; } diff --git a/services/src/main/java/org/keycloak/protocol/oidc/mappers/OIDCFullNameMapper.java b/services/src/main/java/org/keycloak/protocol/oidc/mappers/OIDCFullNameMapper.java new file mode 100755 index 0000000000..b77783a647 --- /dev/null +++ b/services/src/main/java/org/keycloak/protocol/oidc/mappers/OIDCFullNameMapper.java @@ -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 Bill Burke + * @version $Revision: 1 $ + */ +public class OIDCFullNameMapper extends AbstractOIDCProtocolMapper implements OIDCAccessTokenMapper { + + private static final List configProperties = new ArrayList(); + + static { + + } + + public static final String PROVIDER_ID = "oidc-full-name-mapper"; + + + public List 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; + } + +} diff --git a/services/src/main/java/org/keycloak/protocol/oidc/mappers/OIDCUserAttributeMapper.java b/services/src/main/java/org/keycloak/protocol/oidc/mappers/OIDCUserAttributeMapper.java index 1c687be3b5..c5435588b9 100755 --- a/services/src/main/java/org/keycloak/protocol/oidc/mappers/OIDCUserAttributeMapper.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/mappers/OIDCUserAttributeMapper.java @@ -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 configProperties = new ArrayList(); 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 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 jsonObject = token.getOtherClaims(); - for (int i = 0; i < split.length; i++) { - if (i == split.length - 1) { - jsonObject.put(split[i], attributeValue); - } else { - Map nested = (Map)jsonObject.get(split[i]); - if (nested == null) { - nested = new HashMap(); - jsonObject.put(split[i], nested); - jsonObject = nested; - } - } - } - } } diff --git a/services/src/main/java/org/keycloak/protocol/oidc/mappers/OIDCUserModelMapper.java b/services/src/main/java/org/keycloak/protocol/oidc/mappers/OIDCUserModelMapper.java index 4af3178680..6c30080321 100755 --- a/services/src/main/java/org/keycloak/protocol/oidc/mappers/OIDCUserModelMapper.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/mappers/OIDCUserModelMapper.java @@ -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 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; } diff --git a/services/src/main/java/org/keycloak/protocol/oidc/mappers/OIDCUserSessionNoteMapper.java b/services/src/main/java/org/keycloak/protocol/oidc/mappers/OIDCUserSessionNoteMapper.java index 0069d5a25d..e430f4d528 100755 --- a/services/src/main/java/org/keycloak/protocol/oidc/mappers/OIDCUserSessionNoteMapper.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/mappers/OIDCUserSessionNoteMapper.java @@ -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; } diff --git a/services/src/main/java/org/keycloak/services/DefaultKeycloakSessionFactory.java b/services/src/main/java/org/keycloak/services/DefaultKeycloakSessionFactory.java index 361eb87aec..dc03c490b4 100755 --- a/services/src/main/java/org/keycloak/services/DefaultKeycloakSessionFactory.java +++ b/services/src/main/java/org/keycloak/services/DefaultKeycloakSessionFactory.java @@ -87,6 +87,11 @@ public class DefaultKeycloakSessionFactory implements KeycloakSessionFactory { } } } + for ( Map factories : factoriesMap.values()) { + for (ProviderFactory factory : factories.values()) { + factory.postInit(this); + } + } } public KeycloakSession create() { diff --git a/services/src/main/java/org/keycloak/services/resources/admin/ApplicationResource.java b/services/src/main/java/org/keycloak/services/resources/admin/ApplicationResource.java index 5e5a24211c..583d886633 100755 --- a/services/src/main/java/org/keycloak/services/resources/admin/ApplicationResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/ApplicationResource.java @@ -4,6 +4,7 @@ import org.jboss.logging.Logger; import org.jboss.resteasy.annotations.cache.NoCache; import org.jboss.resteasy.spi.BadRequestException; import org.jboss.resteasy.spi.NotFoundException; +import org.jboss.resteasy.spi.ResteasyProviderFactory; import org.keycloak.models.ApplicationModel; import org.keycloak.models.KeycloakSession; import org.keycloak.models.ModelDuplicateException; @@ -87,6 +88,14 @@ public class ApplicationResource { return new ClaimResource(application, auth); } + @Path("protocol-mappers") + public ClientProtocolMappersResource getProtocolMappers() { + ClientProtocolMappersResource mappers = new ClientProtocolMappersResource(realm, auth, application); + ResteasyProviderFactory.getInstance().injectProperties(mappers); + //resourceContext.initResource(mappers); + return mappers; + } + /** * Update the application. * @param rep diff --git a/services/src/main/java/org/keycloak/services/resources/admin/ClientProtocolMappersResource.java b/services/src/main/java/org/keycloak/services/resources/admin/ClientProtocolMappersResource.java new file mode 100755 index 0000000000..b4d9456945 --- /dev/null +++ b/services/src/main/java/org/keycloak/services/resources/admin/ClientProtocolMappersResource.java @@ -0,0 +1,134 @@ +package org.keycloak.services.resources.admin; + +import org.jboss.logging.Logger; +import org.jboss.resteasy.annotations.cache.NoCache; +import org.jboss.resteasy.spi.NotFoundException; +import org.keycloak.models.ClientModel; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.ProtocolMapperModel; +import org.keycloak.models.RealmModel; +import org.keycloak.models.utils.ModelToRepresentation; +import org.keycloak.models.utils.RepresentationToModel; +import org.keycloak.representations.idm.ProtocolMapperRepresentation; + +import javax.ws.rs.Consumes; +import javax.ws.rs.DELETE; +import javax.ws.rs.GET; +import javax.ws.rs.POST; +import javax.ws.rs.PUT; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; +import javax.ws.rs.core.Context; +import javax.ws.rs.core.Response; +import javax.ws.rs.core.UriInfo; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * Base resource for managing users + * + * @author Bill Burke + * @version $Revision: 1 $ + */ +public class ClientProtocolMappersResource { + protected static final Logger logger = Logger.getLogger(ClientProtocolMappersResource.class); + protected ClientModel client; + protected RealmModel realm; + protected RealmAuth auth; + + @Context + protected UriInfo uriInfo; + + @Context + protected KeycloakSession session; + + public ClientProtocolMappersResource(RealmModel realm, RealmAuth auth, ClientModel client) { + this.auth = auth; + this.realm = realm; + this.client = client; + + auth.init(RealmAuth.Resource.USER); + } + + /** + * Map of mappers by name for a specific protocol attached to the client + * + * @param protocol + * @return + */ + @GET + @NoCache + @Path("protocol/{protocol}") + @Produces("application/json") + public Map getMappersPerProtocol(@PathParam("protocol") String protocol) { + auth.requireView(); + Map mappers = new HashMap(); + for (ProtocolMapperModel mapper : client.getProtocolMappers()) { + mappers.put(mapper.getName(), ModelToRepresentation.toRepresentation(mapper)); + } + return mappers; + } + + /** + * Add mappers to client. + * + * @param mapperIds List of mapper ids + */ + @Path("models/add") + @PUT + @NoCache + @Consumes("application/json") + public void addMappers(Set mapperIds) { + auth.requireManage(); + client.addProtocolMappers(mapperIds); + } + + /** + * replace sets of client mappers. + * + * @param mapperIds List of mapper ids + */ + @Path("models/set") + @PUT + @NoCache + @Consumes("application/json") + public void setMappers(Set mapperIds) { + auth.requireManage(); + client.setProtocolMappers(mapperIds); + } + + /** + * remove client mappers. + * + * @param mapperIds List of mapper ids + */ + @Path("models/remove") + @PUT + @NoCache + @Consumes("application/json") + public void removeMappers(Set mapperIds) { + auth.requireManage(); + client.removeProtocolMappers(mapperIds); + } + + @GET + @NoCache + @Path("models") + @Produces("application/json") + public List getMappersPerProtocol() { + auth.requireView(); + List mappers = new LinkedList(); + for (ProtocolMapperModel mapper : realm.getProtocolMappers()) { + mappers.add(ModelToRepresentation.toRepresentation(mapper)); + } + return mappers; + } + + + + +} diff --git a/services/src/main/java/org/keycloak/services/resources/admin/OAuthClientResource.java b/services/src/main/java/org/keycloak/services/resources/admin/OAuthClientResource.java index 1bdf4109f2..e5768e6f0b 100755 --- a/services/src/main/java/org/keycloak/services/resources/admin/OAuthClientResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/OAuthClientResource.java @@ -3,6 +3,7 @@ package org.keycloak.services.resources.admin; import org.jboss.logging.Logger; import org.jboss.resteasy.annotations.cache.NoCache; import org.jboss.resteasy.spi.NotFoundException; +import org.jboss.resteasy.spi.ResteasyProviderFactory; import org.keycloak.models.KeycloakSession; import org.keycloak.models.ModelDuplicateException; import org.keycloak.models.OAuthClientModel; @@ -73,6 +74,19 @@ public class OAuthClientResource { return new ClaimResource(oauthClient, auth); } + /** + * interface for updating attached ProtocolMappers + * + * @return + */ + @Path("protocol-mappers") + public ClientProtocolMappersResource getProtocolMappers() { + ClientProtocolMappersResource mappers = new ClientProtocolMappersResource(realm, auth, oauthClient); + ResteasyProviderFactory.getInstance().injectProperties(mappers); + //resourceContext.initResource(mappers); + return mappers; + } + /** * * @param attributePrefix diff --git a/services/src/main/java/org/keycloak/services/resources/admin/ProtocolMappersResource.java b/services/src/main/java/org/keycloak/services/resources/admin/ProtocolMappersResource.java new file mode 100755 index 0000000000..2a7c82a6a1 --- /dev/null +++ b/services/src/main/java/org/keycloak/services/resources/admin/ProtocolMappersResource.java @@ -0,0 +1,150 @@ +package org.keycloak.services.resources.admin; + +import org.jboss.logging.Logger; +import org.jboss.resteasy.annotations.cache.NoCache; +import org.jboss.resteasy.spi.NotFoundException; +import org.keycloak.models.KerberosConstants; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.ProtocolMapperModel; +import org.keycloak.models.RealmModel; +import org.keycloak.models.RequiredCredentialModel; +import org.keycloak.models.UserCredentialModel; +import org.keycloak.models.UserFederationProvider; +import org.keycloak.models.UserFederationProviderFactory; +import org.keycloak.models.UserFederationProviderModel; +import org.keycloak.models.utils.ModelToRepresentation; +import org.keycloak.models.utils.RepresentationToModel; +import org.keycloak.provider.ProviderFactory; +import org.keycloak.representations.idm.ProtocolMapperRepresentation; +import org.keycloak.representations.idm.UserFederationProviderFactoryRepresentation; +import org.keycloak.representations.idm.UserFederationProviderRepresentation; +import org.keycloak.services.managers.UsersSyncManager; +import org.keycloak.timer.TimerProvider; + +import javax.ws.rs.Consumes; +import javax.ws.rs.DELETE; +import javax.ws.rs.GET; +import javax.ws.rs.POST; +import javax.ws.rs.PUT; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; +import javax.ws.rs.QueryParam; +import javax.ws.rs.core.Context; +import javax.ws.rs.core.Response; +import javax.ws.rs.core.UriInfo; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +/** + * Base resource for managing users + * + * @author Bill Burke + * @version $Revision: 1 $ + */ +public class ProtocolMappersResource { + protected static final Logger logger = Logger.getLogger(ProtocolMappersResource.class); + + protected RealmModel realm; + + protected RealmAuth auth; + + @Context + protected UriInfo uriInfo; + + @Context + protected KeycloakSession session; + + public ProtocolMappersResource(RealmModel realm, RealmAuth auth) { + this.auth = auth; + this.realm = realm; + + auth.init(RealmAuth.Resource.USER); + } + + /** + * Map of mappers by name for a specific protocol + * + * @param protocol + * @return + */ + @GET + @NoCache + @Path("protocol/{protocol}") + @Produces("application/json") + public Map getMappersPerProtocol(@PathParam("protocol") String protocol) { + auth.requireView(); + Map mappers = new HashMap(); + for (ProtocolMapperModel mapper : realm.getProtocolMappers()) { + mappers.put(mapper.getName(), ModelToRepresentation.toRepresentation(mapper)); + } + return mappers; + } + + /** + * createa mapper + * + * @param rep + */ + @Path("models") + @POST + @NoCache + @Consumes("application/json") + public Response createMapper(ProtocolMapperRepresentation rep) { + auth.requireManage(); + ProtocolMapperModel model = RepresentationToModel.toModel(rep); + realm.addProtocolMapper(model); + return Response.created(uriInfo.getAbsolutePathBuilder().path(model.getId()).build()).build(); + } + + @GET + @NoCache + @Path("models") + @Produces("application/json") + public List getMappersPerProtocol() { + auth.requireView(); + List mappers = new LinkedList(); + for (ProtocolMapperModel mapper : realm.getProtocolMappers()) { + mappers.add(ModelToRepresentation.toRepresentation(mapper)); + } + return mappers; + } + + @GET + @NoCache + @Path("models/{id}") + @Produces("application/json") + public ProtocolMapperRepresentation getMapperById(@PathParam("id") String id) { + auth.requireView(); + ProtocolMapperModel model = realm.getProtocolMapperById(id); + if (model == null) throw new NotFoundException("Model not found"); + return ModelToRepresentation.toRepresentation(model); + } + + @PUT + @NoCache + @Path("models/{id}") + @Consumes("application/json") + public void update(@PathParam("id") String id, ProtocolMapperRepresentation rep) { + auth.requireManage(); + ProtocolMapperModel model = realm.getProtocolMapperById(id); + if (model == null) throw new NotFoundException("Model not found"); + model = RepresentationToModel.toModel(rep); + realm.updateProtocolMapper(model); + } + + @DELETE + @NoCache + @Path("models/{id}") + public void delete(@PathParam("id") String id) { + auth.requireManage(); + ProtocolMapperModel model = realm.getProtocolMapperById(id); + if (model == null) throw new NotFoundException("Model not found"); + realm.removeProtocolMapper(model); + } + + + +} diff --git a/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java b/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java index 3e5c2b0bd8..592fa6f098 100755 --- a/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java @@ -273,6 +273,19 @@ public class RealmAdminResource { return new ResourceAdminManager().pushRealmRevocationPolicy(uriInfo.getRequestUri(), realm); } + /** + * Protocol mappers + * + */ + @Path("protocol-mappers") + @POST + public ProtocolMappersResource protocolMappers() { + ProtocolMappersResource mappers = new ProtocolMappersResource(realm, auth); + ResteasyProviderFactory.getInstance().injectProperties(mappers); + //resourceContext.initResource(mappers); + return mappers; + } + /** * Removes all user sessions. Any application that has an admin url will also be told to invalidate any sessions * they have. @@ -281,7 +294,6 @@ public class RealmAdminResource { @Path("logout-all") @POST public GlobalRequestResult logoutAll() { - auth.requireManage(); session.sessions().removeUserSessions(realm); return new ResourceAdminManager().logoutAll(uriInfo.getRequestUri(), realm); } diff --git a/services/src/main/java/org/keycloak/services/resources/admin/ServerInfoAdminResource.java b/services/src/main/java/org/keycloak/services/resources/admin/ServerInfoAdminResource.java index 6952e88472..f1d3e355e0 100755 --- a/services/src/main/java/org/keycloak/services/resources/admin/ServerInfoAdminResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/ServerInfoAdminResource.java @@ -120,7 +120,6 @@ public class ServerInfoAdminResource { } } - private void setProtocols(ServerInfoRepresentation info) { info.protocols = new LinkedList(); for (ProviderFactory p : session.getKeycloakSessionFactory().getProviderFactories(LoginProtocol.class)) { diff --git a/services/src/main/resources/META-INF/services/org.keycloak.protocol.ProtocolMapper b/services/src/main/resources/META-INF/services/org.keycloak.protocol.ProtocolMapper index 0f9da65a17..1815b887d4 100755 --- a/services/src/main/resources/META-INF/services/org.keycloak.protocol.ProtocolMapper +++ b/services/src/main/resources/META-INF/services/org.keycloak.protocol.ProtocolMapper @@ -1 +1,8 @@ -org.keycloak.protocol.oidc.mappers.OIDCUserAttributeMapper \ No newline at end of file +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 + + diff --git a/services/src/main/resources/META-INF/services/org.keycloak.provider.Spi b/services/src/main/resources/META-INF/services/org.keycloak.provider.Spi index 7cea20de62..30eeb3e59b 100755 --- a/services/src/main/resources/META-INF/services/org.keycloak.provider.Spi +++ b/services/src/main/resources/META-INF/services/org.keycloak.provider.Spi @@ -1,2 +1,3 @@ org.keycloak.protocol.LoginProtocolSpi +org.keycloak.protocol.ProtocolMapperSpi org.keycloak.exportimport.ApplicationImportSpi \ No newline at end of file diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/AdapterTestStrategy.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/AdapterTestStrategy.java index 08f57757af..cfe16a8e76 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/AdapterTestStrategy.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/AdapterTestStrategy.java @@ -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); diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/RelativeUriAdapterTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/RelativeUriAdapterTest.java index 0ebb8fc201..cce65bd49e 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/RelativeUriAdapterTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/RelativeUriAdapterTest.java @@ -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); } diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/AdminAPITest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/AdminAPITest.java index 0d6c30fd3f..68e3f13540 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/AdminAPITest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/AdminAPITest.java @@ -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); diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/saml/SamlBindingTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/saml/SamlBindingTest.java index 82c0ec9289..db33eb1340 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/saml/SamlBindingTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/saml/SamlBindingTest.java @@ -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);