From 2da925505e420e3af50e75bf13eb5b0df3fbcfdc Mon Sep 17 00:00:00 2001 From: Bill Burke Date: Sat, 28 Feb 2015 11:17:06 -0500 Subject: [PATCH] saml attribute mappers --- .../saml/SALM2LoginResponseBuilder.java | 20 ------ .../keycloak/protocol/saml/SamlProtocol.java | 27 +++++-- .../mappers/AttributeStatementHelper.java | 72 +++++++++++++++++++ .../mappers/SAMLAttributeStatementMapper.java | 18 +++++ ...ttributeBasicAttributeStatementMapper.java | 69 ++++++++++++++++++ ...eUriReferenceAttributeStatementMapper.java | 69 ++++++++++++++++++ ...serModelBasicAttributeStatementMapper.java | 69 ++++++++++++++++++ ...lUriReferenceAttributeStatementMapper.java | 69 ++++++++++++++++++ .../org.keycloak.protocol.ProtocolMapper | 6 ++ .../protocol/ProtocolMapperUtils.java | 37 ++++++++++ .../oidc/OIDCLoginProtocolFactory.java | 17 ++--- ...er.java => OIDCAttributeMapperHelper.java} | 2 +- .../mappers/OIDCClientSessionNoteMapper.java | 71 ------------------ .../oidc/mappers/OIDCUserAttributeMapper.java | 16 ++--- .../oidc/mappers/OIDCUserModelMapper.java | 39 +++------- .../mappers/OIDCUserSessionNoteMapper.java | 72 ------------------- .../org.keycloak.protocol.ProtocolMapper | 2 - .../testsuite/account/AccountTest.java | 3 +- 18 files changed, 460 insertions(+), 218 deletions(-) create mode 100755 saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/mappers/AttributeStatementHelper.java create mode 100755 saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/mappers/SAMLAttributeStatementMapper.java create mode 100755 saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/mappers/UserAttributeBasicAttributeStatementMapper.java create mode 100755 saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/mappers/UserAttributeUriReferenceAttributeStatementMapper.java create mode 100755 saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/mappers/UserModelBasicAttributeStatementMapper.java create mode 100755 saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/mappers/UserModelUriReferenceAttributeStatementMapper.java create mode 100755 saml/saml-protocol/src/main/resources/META-INF/services/org.keycloak.protocol.ProtocolMapper create mode 100755 services/src/main/java/org/keycloak/protocol/ProtocolMapperUtils.java rename services/src/main/java/org/keycloak/protocol/oidc/mappers/{AttributeMapperHelper.java => OIDCAttributeMapperHelper.java} (96%) delete mode 100755 services/src/main/java/org/keycloak/protocol/oidc/mappers/OIDCClientSessionNoteMapper.java delete mode 100755 services/src/main/java/org/keycloak/protocol/oidc/mappers/OIDCUserSessionNoteMapper.java diff --git a/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SALM2LoginResponseBuilder.java b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SALM2LoginResponseBuilder.java index 782adbc8a7..d86bb31939 100755 --- a/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SALM2LoginResponseBuilder.java +++ b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SALM2LoginResponseBuilder.java @@ -46,14 +46,8 @@ public class SALM2LoginResponseBuilder { protected String requestID; protected String authMethod; protected String requestIssuer; - protected Map attributes = new HashMap(); - public SALM2LoginResponseBuilder attributes(Map attributes) { - this.attributes = attributes; - return this; - } - public SALM2LoginResponseBuilder destination(String destination) { this.destination = destination; return this; @@ -64,15 +58,6 @@ public class SALM2LoginResponseBuilder { return this; } - public SALM2LoginResponseBuilder attribute(String name, Object value) { - if (value == null) { - attributes.remove(name); - } else { - this.attributes.put(name, value); - } - return this; - } - public SALM2LoginResponseBuilder requestID(String requestID) { this.requestID =requestID; return this; @@ -176,11 +161,6 @@ public class SALM2LoginResponseBuilder { assertion.addStatement(attrStatement); } - // Add in the attributes information - if (attributes != null && attributes.size() > 0) { - AttributeStatementType attStatement = StatementUtil.createAttributeStatement(attributes); - assertion.addStatement(attStatement); - } return responseType; } diff --git a/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlProtocol.java b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlProtocol.java index dad37338fc..f57117eae8 100755 --- a/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlProtocol.java +++ b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlProtocol.java @@ -17,6 +17,7 @@ import org.keycloak.models.UserModel; import org.keycloak.models.UserSessionModel; import org.keycloak.protocol.LoginProtocol; import org.keycloak.protocol.ProtocolMapper; +import org.keycloak.protocol.saml.mappers.SAMLAttributeStatementMapper; import org.keycloak.protocol.saml.mappers.SAMLLoginResponseMapper; import org.keycloak.services.managers.ClientSessionCode; import org.keycloak.services.managers.ResourceAdminManager; @@ -29,6 +30,8 @@ import org.picketlink.common.exceptions.ConfigurationException; import org.picketlink.common.exceptions.ParsingException; import org.picketlink.common.exceptions.ProcessingException; import org.picketlink.identity.federation.core.saml.v2.constants.X500SAMLProfileConstants; +import org.picketlink.identity.federation.saml.v2.assertion.AssertionType; +import org.picketlink.identity.federation.saml.v2.assertion.AttributeStatementType; import org.picketlink.identity.federation.saml.v2.protocol.ResponseType; import org.picketlink.identity.federation.web.handlers.saml2.SAML2LogOutHandler; import org.w3c.dom.Document; @@ -273,6 +276,7 @@ public class SamlProtocol implements LoginProtocol { Document samlDocument = null; try { ResponseType samlModel = builder.buildModel(); + transformAttributeStatement(session, samlModel, client, userSession, clientSession); samlModel = transformLoginResponse(session, samlModel, client, userSession, clientSession); samlDocument = builder.buildDocument(samlModel); } catch (Exception e) { @@ -346,14 +350,14 @@ public class SamlProtocol implements LoginProtocol { public void initClaims(SALM2LoginResponseBuilder builder, ClientModel model, UserModel user) { if (ClaimMask.hasEmail(model.getAllowedClaimsMask())) { - builder.attribute(X500SAMLProfileConstants.EMAIL_ADDRESS.getFriendlyName(), user.getEmail()); + //builder.attribute(X500SAMLProfileConstants.EMAIL_ADDRESS.getFriendlyName(), user.getEmail()); } if (ClaimMask.hasName(model.getAllowedClaimsMask())) { - builder.attribute(X500SAMLProfileConstants.GIVEN_NAME.getFriendlyName(), user.getFirstName()); - builder.attribute(X500SAMLProfileConstants.SURNAME.getFriendlyName(), user.getLastName()); + //builder.attribute(X500SAMLProfileConstants.GIVEN_NAME.getFriendlyName(), user.getFirstName()); + //builder.attribute(X500SAMLProfileConstants.SURNAME.getFriendlyName(), user.getLastName()); } if (ClaimMask.hasUsername(model.getAllowedClaimsMask())) { - builder.attribute(X500SAMLProfileConstants.USERID.getFriendlyName(), user.getUsername()); + //builder.attribute(X500SAMLProfileConstants.USERID.getFriendlyName(), user.getUsername()); } } @@ -373,6 +377,21 @@ public class SamlProtocol implements LoginProtocol { } return response; } + public void transformAttributeStatement(KeycloakSession session, ResponseType response, ClientModel client, + UserSessionModel userSession, ClientSessionModel clientSession) { + AttributeStatementType attributeStatement = new AttributeStatementType(); + AssertionType assertion = response.getAssertions().get(0).getAssertion(); + assertion.addStatement(attributeStatement); + Set mappings = client.getProtocolMappers(); + KeycloakSessionFactory sessionFactory = session.getKeycloakSessionFactory(); + for (ProtocolMapperModel mapping : mappings) { + if (!mapping.getProtocol().equals(SamlProtocol.LOGIN_PROTOCOL)) continue; + + ProtocolMapper mapper = (ProtocolMapper)sessionFactory.getProviderFactory(ProtocolMapper.class, mapping.getProtocolMapper()); + if (mapper == null || !(mapper instanceof SAMLAttributeStatementMapper)) continue; + ((SAMLAttributeStatementMapper)mapper).transformAttributeStatement(attributeStatement, mapping, session, userSession, clientSession); + } + } diff --git a/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/mappers/AttributeStatementHelper.java b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/mappers/AttributeStatementHelper.java new file mode 100755 index 0000000000..cf9981f553 --- /dev/null +++ b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/mappers/AttributeStatementHelper.java @@ -0,0 +1,72 @@ +package org.keycloak.protocol.saml.mappers; + +import org.keycloak.models.ProtocolMapperModel; +import org.keycloak.protocol.ProtocolMapper; +import org.picketlink.common.constants.JBossSAMLURIConstants; +import org.picketlink.identity.federation.saml.v2.assertion.AttributeStatementType; +import org.picketlink.identity.federation.saml.v2.assertion.AttributeType; + +import java.util.List; + +/** + * @author Bill Burke + * @version $Revision: 1 $ + */ +public class AttributeStatementHelper { + public static final String ATTRIBUTE_STATEMENT_CATEGORY = "AttributeStatement Mapper"; + public static final String URI_REFERENCE = "URI Reference"; + public static final String URI_REFERENCE_HELP_TEXT = "Attribute name for the SAML URI Reference attribute name format"; + public static final String BASIC = "Basic name"; + public static final String BASIC_HELP_TEXT = "Attribute name for the SAML Basic attribute name format"; + public static final String FRIENDLY_NAME = "Friendly Name"; + public static final String FRIENDLY_NAME_HELP_TEXT = "Standard SAML attribute setting. An optional, more human-readable form of the attribute's name that can be provided if the actual attribute name is cryptic."; + + public static void addUriReferenceAttribute(AttributeStatementType attributeStatement, ProtocolMapperModel mappingModel, String attributeValue) { + String attributeName = mappingModel.getConfig().get(URI_REFERENCE); + AttributeType attribute = new AttributeType(attributeName); + attribute.setNameFormat(JBossSAMLURIConstants.ATTRIBUTE_FORMAT_URI.get()); + String friendlyName = mappingModel.getConfig().get(FRIENDLY_NAME); + if (friendlyName != null && friendlyName.trim().equals("")) friendlyName = null; + if (friendlyName != null) attribute.setFriendlyName(friendlyName); + attribute.addAttributeValue(attributeValue); + attributeStatement.addAttribute(new AttributeStatementType.ASTChoiceType(attribute)); + } + + public static void addBasicAttribute(AttributeStatementType attributeStatement, ProtocolMapperModel mappingModel, String attributeValue) { + String attributeName = mappingModel.getConfig().get(BASIC); + AttributeType attribute = new AttributeType(attributeName); + attribute.setNameFormat(JBossSAMLURIConstants.ATTRIBUTE_FORMAT_BASIC.get()); + String friendlyName = mappingModel.getConfig().get(FRIENDLY_NAME); + if (friendlyName != null && friendlyName.trim().equals("")) friendlyName = null; + if (friendlyName != null) attribute.setFriendlyName(friendlyName); + attribute.addAttributeValue(attributeValue); + attributeStatement.addAttribute(new AttributeStatementType.ASTChoiceType(attribute)); + } + + protected static void addUriReferenceProperties(List configProperties) { + ProtocolMapper.ConfigProperty property; + property = new ProtocolMapper.ConfigProperty(); + property.setName(FRIENDLY_NAME); + property.setLabel(FRIENDLY_NAME); + property.setHelpText(FRIENDLY_NAME_HELP_TEXT); + configProperties.add(property); + property = new ProtocolMapper.ConfigProperty(); + property.setName(URI_REFERENCE); + property.setLabel(URI_REFERENCE); + property.setHelpText(URI_REFERENCE_HELP_TEXT); + configProperties.add(property); + } + protected static void addBasicProperties(List configProperties) { + ProtocolMapper.ConfigProperty property; + property = new ProtocolMapper.ConfigProperty(); + property.setName(FRIENDLY_NAME); + property.setLabel(FRIENDLY_NAME); + property.setHelpText(FRIENDLY_NAME_HELP_TEXT); + configProperties.add(property); + property = new ProtocolMapper.ConfigProperty(); + property.setName(BASIC); + property.setLabel(BASIC); + property.setHelpText(BASIC_HELP_TEXT); + configProperties.add(property); + } +} diff --git a/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/mappers/SAMLAttributeStatementMapper.java b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/mappers/SAMLAttributeStatementMapper.java new file mode 100755 index 0000000000..8abe6811c2 --- /dev/null +++ b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/mappers/SAMLAttributeStatementMapper.java @@ -0,0 +1,18 @@ +package org.keycloak.protocol.saml.mappers; + +import org.keycloak.models.ClientSessionModel; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.ProtocolMapperModel; +import org.keycloak.models.UserSessionModel; +import org.picketlink.identity.federation.saml.v2.assertion.AttributeStatementType; +import org.picketlink.identity.federation.saml.v2.protocol.ResponseType; + +/** + * @author Bill Burke + * @version $Revision: 1 $ + */ +public interface SAMLAttributeStatementMapper { + + void transformAttributeStatement(AttributeStatementType attributeStatement, ProtocolMapperModel mappingModel, KeycloakSession session, + UserSessionModel userSession, ClientSessionModel clientSession); +} diff --git a/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/mappers/UserAttributeBasicAttributeStatementMapper.java b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/mappers/UserAttributeBasicAttributeStatementMapper.java new file mode 100755 index 0000000000..86cefe18cf --- /dev/null +++ b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/mappers/UserAttributeBasicAttributeStatementMapper.java @@ -0,0 +1,69 @@ +package org.keycloak.protocol.saml.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.protocol.ProtocolMapperUtils; +import org.picketlink.identity.federation.saml.v2.assertion.AttributeStatementType; + +import java.util.ArrayList; +import java.util.List; + +/** + * Mappings UserModel property (the property name of a getter method) to an AttributeStatement. + * + * @author Bill Burke + * @version $Revision: 1 $ + */ +public class UserAttributeBasicAttributeStatementMapper extends AbstractSAMLProtocolMapper implements SAMLAttributeStatementMapper { + private static final List configProperties = new ArrayList(); + + static { + ConfigProperty property; + property = new ConfigProperty(); + property.setName(ProtocolMapperUtils.USER_MODEL_ATTRIBUTE_NAME); + property.setLabel(ProtocolMapperUtils.USER_MODEL_ATTRIBUTE_NAME); + property.setHelpText(ProtocolMapperUtils.USER_MODEL_ATTRIBUTE_HELP_TEXT); + configProperties.add(property); + AttributeStatementHelper.addBasicProperties(configProperties); + + } + + public static final String PROVIDER_ID = "saml-user-attribute-basic-mapper"; + + + public List getConfigProperties() { + return configProperties; + } + @Override + public String getId() { + return PROVIDER_ID; + } + + @Override + public String getDisplayType() { + return "User Attribute Basic"; + } + + @Override + public String getDisplayCategory() { + return AttributeStatementHelper.ATTRIBUTE_STATEMENT_CATEGORY; + } + + @Override + public String getHelpText() { + return "Map a custom user attribute to a to a SAML Basic attribute type.."; + } + + @Override + public void transformAttributeStatement(AttributeStatementType attributeStatement, ProtocolMapperModel mappingModel, KeycloakSession session, UserSessionModel userSession, ClientSessionModel clientSession) { + UserModel user = userSession.getUser(); + String attributeName = mappingModel.getConfig().get(ProtocolMapperUtils.USER_MODEL_ATTRIBUTE_NAME); + String attributeValue = user.getAttribute(attributeName); + AttributeStatementHelper.addBasicAttribute(attributeStatement, mappingModel, attributeValue); + + } + +} diff --git a/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/mappers/UserAttributeUriReferenceAttributeStatementMapper.java b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/mappers/UserAttributeUriReferenceAttributeStatementMapper.java new file mode 100755 index 0000000000..28ce5c1870 --- /dev/null +++ b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/mappers/UserAttributeUriReferenceAttributeStatementMapper.java @@ -0,0 +1,69 @@ +package org.keycloak.protocol.saml.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.protocol.ProtocolMapperUtils; +import org.picketlink.identity.federation.saml.v2.assertion.AttributeStatementType; + +import java.util.ArrayList; +import java.util.List; + +/** + * Mappings UserModel property (the property name of a getter method) to an AttributeStatement. + * + * @author Bill Burke + * @version $Revision: 1 $ + */ +public class UserAttributeUriReferenceAttributeStatementMapper extends AbstractSAMLProtocolMapper implements SAMLAttributeStatementMapper { + private static final List configProperties = new ArrayList(); + + static { + ConfigProperty property; + property = new ConfigProperty(); + property.setName(ProtocolMapperUtils.USER_MODEL_ATTRIBUTE_NAME); + property.setLabel(ProtocolMapperUtils.USER_MODEL_ATTRIBUTE_NAME); + property.setHelpText(ProtocolMapperUtils.USER_MODEL_ATTRIBUTE_HELP_TEXT); + configProperties.add(property); + AttributeStatementHelper.addUriReferenceProperties(configProperties); + + } + + public static final String PROVIDER_ID = "saml-user-attribute-uri-mapper"; + + + public List getConfigProperties() { + return configProperties; + } + @Override + public String getId() { + return PROVIDER_ID; + } + + @Override + public String getDisplayType() { + return "User Attribute URI"; + } + + @Override + public String getDisplayCategory() { + return AttributeStatementHelper.ATTRIBUTE_STATEMENT_CATEGORY; + } + + @Override + public String getHelpText() { + return "Map a custom user attribute to a to a SAML URI reference attribute type.."; + } + + @Override + public void transformAttributeStatement(AttributeStatementType attributeStatement, ProtocolMapperModel mappingModel, KeycloakSession session, UserSessionModel userSession, ClientSessionModel clientSession) { + UserModel user = userSession.getUser(); + String attributeName = mappingModel.getConfig().get(ProtocolMapperUtils.USER_MODEL_ATTRIBUTE_NAME); + String attributeValue = user.getAttribute(attributeName); + AttributeStatementHelper.addUriReferenceAttribute(attributeStatement, mappingModel, attributeValue); + + } + +} diff --git a/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/mappers/UserModelBasicAttributeStatementMapper.java b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/mappers/UserModelBasicAttributeStatementMapper.java new file mode 100755 index 0000000000..a6f8c6fc2e --- /dev/null +++ b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/mappers/UserModelBasicAttributeStatementMapper.java @@ -0,0 +1,69 @@ +package org.keycloak.protocol.saml.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.protocol.ProtocolMapperUtils; +import org.picketlink.identity.federation.saml.v2.assertion.AttributeStatementType; + +import java.util.ArrayList; +import java.util.List; + +/** + * Mappings UserModel property (the property name of a getter method) to an AttributeStatement. + * + * @author Bill Burke + * @version $Revision: 1 $ + */ +public class UserModelBasicAttributeStatementMapper extends AbstractSAMLProtocolMapper implements SAMLAttributeStatementMapper { + private static final List configProperties = new ArrayList(); + + static { + ConfigProperty property; + property = new ConfigProperty(); + property.setName(ProtocolMapperUtils.USER_MODEL_PROPERTY); + property.setLabel(ProtocolMapperUtils.USER_MODEL_PROPERTY); + property.setHelpText(ProtocolMapperUtils.USER_MODEL_PROPERTY_HELP_TEXT); + configProperties.add(property); + AttributeStatementHelper.addBasicProperties(configProperties); + + } + + public static final String PROVIDER_ID = "saml-user-property-base-mapper"; + + + public List getConfigProperties() { + return configProperties; + } + @Override + public String getId() { + return PROVIDER_ID; + } + + @Override + public String getDisplayType() { + return "User Property Basic"; + } + + @Override + public String getDisplayCategory() { + return AttributeStatementHelper.ATTRIBUTE_STATEMENT_CATEGORY; + } + + @Override + public String getHelpText() { + return "Map a built in user property to a SAML Basic attribute type."; + } + + @Override + public void transformAttributeStatement(AttributeStatementType attributeStatement, ProtocolMapperModel mappingModel, KeycloakSession session, UserSessionModel userSession, ClientSessionModel clientSession) { + UserModel user = userSession.getUser(); + String propertyName = mappingModel.getConfig().get(ProtocolMapperUtils.USER_MODEL_PROPERTY); + String propertyValue = ProtocolMapperUtils.getUserModelValue(user, propertyName); + AttributeStatementHelper.addBasicAttribute(attributeStatement, mappingModel, propertyValue); + + } + +} diff --git a/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/mappers/UserModelUriReferenceAttributeStatementMapper.java b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/mappers/UserModelUriReferenceAttributeStatementMapper.java new file mode 100755 index 0000000000..c0a2d12507 --- /dev/null +++ b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/mappers/UserModelUriReferenceAttributeStatementMapper.java @@ -0,0 +1,69 @@ +package org.keycloak.protocol.saml.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.protocol.ProtocolMapperUtils; +import org.picketlink.identity.federation.saml.v2.assertion.AttributeStatementType; + +import java.util.ArrayList; +import java.util.List; + +/** + * Mappings UserModel property (the property name of a getter method) to an AttributeStatement. + * + * @author Bill Burke + * @version $Revision: 1 $ + */ +public class UserModelUriReferenceAttributeStatementMapper extends AbstractSAMLProtocolMapper implements SAMLAttributeStatementMapper { + private static final List configProperties = new ArrayList(); + + static { + ConfigProperty property; + property = new ConfigProperty(); + property.setName(ProtocolMapperUtils.USER_MODEL_PROPERTY); + property.setLabel(ProtocolMapperUtils.USER_MODEL_PROPERTY); + property.setHelpText(ProtocolMapperUtils.USER_MODEL_PROPERTY_HELP_TEXT); + configProperties.add(property); + AttributeStatementHelper.addUriReferenceProperties(configProperties); + + } + + public static final String PROVIDER_ID = "saml-user-property-uri-mapper"; + + + public List getConfigProperties() { + return configProperties; + } + @Override + public String getId() { + return PROVIDER_ID; + } + + @Override + public String getDisplayType() { + return "User Property URI"; + } + + @Override + public String getDisplayCategory() { + return AttributeStatementHelper.ATTRIBUTE_STATEMENT_CATEGORY; + } + + @Override + public String getHelpText() { + return "Map a built in user property to a SAML URI reference attribute type."; + } + + @Override + public void transformAttributeStatement(AttributeStatementType attributeStatement, ProtocolMapperModel mappingModel, KeycloakSession session, UserSessionModel userSession, ClientSessionModel clientSession) { + UserModel user = userSession.getUser(); + String propertyName = mappingModel.getConfig().get(ProtocolMapperUtils.USER_MODEL_PROPERTY); + String propertyValue = ProtocolMapperUtils.getUserModelValue(user, propertyName); + AttributeStatementHelper.addUriReferenceAttribute(attributeStatement, mappingModel, propertyValue); + + } + +} diff --git a/saml/saml-protocol/src/main/resources/META-INF/services/org.keycloak.protocol.ProtocolMapper b/saml/saml-protocol/src/main/resources/META-INF/services/org.keycloak.protocol.ProtocolMapper new file mode 100755 index 0000000000..719ddff57d --- /dev/null +++ b/saml/saml-protocol/src/main/resources/META-INF/services/org.keycloak.protocol.ProtocolMapper @@ -0,0 +1,6 @@ +org.keycloak.protocol.saml.mappers.UserAttributeBasicAttributeStatementMapper +org.keycloak.protocol.saml.mappers.UserModelBasicAttributeStatementMapper +org.keycloak.protocol.saml.mappers.UserAttributeUriReferenceAttributeStatementMapper +org.keycloak.protocol.saml.mappers.UserModelUriReferenceAttributeStatementMapper + + diff --git a/services/src/main/java/org/keycloak/protocol/ProtocolMapperUtils.java b/services/src/main/java/org/keycloak/protocol/ProtocolMapperUtils.java new file mode 100755 index 0000000000..8f7c2e9f4e --- /dev/null +++ b/services/src/main/java/org/keycloak/protocol/ProtocolMapperUtils.java @@ -0,0 +1,37 @@ +package org.keycloak.protocol; + +import org.keycloak.models.UserModel; + +import java.lang.reflect.Method; + +/** + * @author Bill Burke + * @version $Revision: 1 $ + */ +public class ProtocolMapperUtils { + public static final String USER_MODEL_PROPERTY = "User Property"; + public static final String USER_MODEL_PROPERTY_HELP_TEXT = "Name of the property method in the UserModel interface. For example, a value of 'email' would reference the UserModel.getEmail() method."; + public static final String USER_MODEL_ATTRIBUTE_NAME = "User Attribute"; + public static final String USER_MODEL_ATTRIBUTE_HELP_TEXT = "Name of stored user attribute which is the name of an attribute within the UserModel.attribute map."; + + public static String getUserModelValue(UserModel user, String propertyName) { + + String methodName = "get" + Character.toUpperCase(propertyName.charAt(0)) + propertyName.substring(1); + try { + Method method = UserModel.class.getMethod(methodName); + Object val = method.invoke(user); + if (val != null) return val.toString(); + } catch (Exception ignore) { + + } + 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/OIDCLoginProtocolFactory.java b/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocolFactory.java index 4b60b6a2e5..0a9c7c0620 100755 --- a/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocolFactory.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocolFactory.java @@ -8,7 +8,8 @@ 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.ProtocolMapperUtils; +import org.keycloak.protocol.oidc.mappers.OIDCAttributeMapperHelper; import org.keycloak.protocol.oidc.mappers.OIDCAddressMapper; import org.keycloak.protocol.oidc.mappers.OIDCFullNameMapper; import org.keycloak.protocol.oidc.mappers.OIDCUserModelMapper; @@ -65,27 +66,27 @@ public class OIDCLoginProtocolFactory implements LoginProtocolFactory { 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", + ProtocolMapperUtils.USER_MODEL_PROPERTY, "username", "preferred_username", "String", true, "username", true); addClaimMapper(realm, "email", OIDCUserModelMapper.PROVIDER_ID, - OIDCUserModelMapper.USER_MODEL_PROPERTY, "email", + ProtocolMapperUtils.USER_MODEL_PROPERTY, "email", "email", "String", true, "email", true); addClaimMapper(realm, "given name", OIDCUserModelMapper.PROVIDER_ID, - OIDCUserModelMapper.USER_MODEL_PROPERTY, "firstName", + ProtocolMapperUtils.USER_MODEL_PROPERTY, "firstName", "given_name", "String", true, "given name", true); addClaimMapper(realm, "family name", OIDCUserModelMapper.PROVIDER_ID, - OIDCUserModelMapper.USER_MODEL_PROPERTY, "lastName", + ProtocolMapperUtils.USER_MODEL_PROPERTY, "lastName", "family_name", "String", true, "family name", true); addClaimMapper(realm, "email verified", OIDCUserModelMapper.PROVIDER_ID, - OIDCUserModelMapper.USER_MODEL_PROPERTY, "emailVerified", + ProtocolMapperUtils.USER_MODEL_PROPERTY, "emailVerified", "email_verified", "boolean", false, null, false); @@ -131,8 +132,8 @@ public class OIDCLoginProtocolFactory implements LoginProtocolFactory { mapper.setAppliedByDefault(appliedByDefault); Map config = new HashMap(); config.put(propertyName, propertyNameValue); - config.put(AttributeMapperHelper.TOKEN_CLAIM_NAME, tokenClaimName); - config.put(AttributeMapperHelper.JSON_TYPE, claimType); + config.put(OIDCAttributeMapperHelper.TOKEN_CLAIM_NAME, tokenClaimName); + config.put(OIDCAttributeMapperHelper.JSON_TYPE, claimType); mapper.setConfig(config); realm.addProtocolMapper(mapper); } diff --git a/services/src/main/java/org/keycloak/protocol/oidc/mappers/AttributeMapperHelper.java b/services/src/main/java/org/keycloak/protocol/oidc/mappers/OIDCAttributeMapperHelper.java similarity index 96% rename from services/src/main/java/org/keycloak/protocol/oidc/mappers/AttributeMapperHelper.java rename to services/src/main/java/org/keycloak/protocol/oidc/mappers/OIDCAttributeMapperHelper.java index a4fe0fe0c8..a48d6ac94b 100755 --- a/services/src/main/java/org/keycloak/protocol/oidc/mappers/AttributeMapperHelper.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/mappers/OIDCAttributeMapperHelper.java @@ -10,7 +10,7 @@ import java.util.Map; * @author Bill Burke * @version $Revision: 1 $ */ -public class AttributeMapperHelper { +public class OIDCAttributeMapperHelper { public static final String TOKEN_CLAIM_NAME = "Token Claim Name"; public static final String JSON_TYPE = "Claim JSON Type"; 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 deleted file mode 100755 index 84d4a32b2d..0000000000 --- a/services/src/main/java/org/keycloak/protocol/oidc/mappers/OIDCClientSessionNoteMapper.java +++ /dev/null @@ -1,71 +0,0 @@ -package org.keycloak.protocol.oidc.mappers; - -import org.keycloak.models.ClientSessionModel; -import org.keycloak.models.KeycloakSession; -import org.keycloak.models.ProtocolMapperModel; -import org.keycloak.models.UserSessionModel; -import org.keycloak.representations.AccessToken; - -import java.util.ArrayList; -import java.util.List; - -/** - * Mappings UserSessionModel.note to an ID Token claim. Token claim name can be a full qualified nested object name, - * i.e. "address.country". This will create a nested - * json object within the toke claim. - * - * @author Bill Burke - * @version $Revision: 1 $ - */ -public class OIDCClientSessionNoteMapper extends AbstractOIDCProtocolMapper implements OIDCAccessTokenMapper { - private static final List configProperties = new ArrayList(); - public static final String CLIENT_SESSION_NOTE = "ClientSession Note"; - - static { - ConfigProperty property; - property = new ConfigProperty(); - property.setName(CLIENT_SESSION_NOTE); - property.setLabel(CLIENT_SESSION_NOTE); - property.setHelpText("Name of the note to map in the UserSessionModel"); - configProperties.add(property); - property = new ConfigProperty(); - 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 List getConfigProperties() { - return configProperties; - } - @Override - public String getId() { - return "oidc-client-session-note-mapper"; - } - - @Override - public String getDisplayType() { - return "ClientSession Note"; - } - - @Override - public String getDisplayCategory() { - return TOKEN_MAPPER_CATEGORY; - } - - @Override - public String getHelpText() { - return "Map a temporary note that is attached to the ClientSession to a token claim."; - } - - @Override - public AccessToken transformToken(AccessToken token, ProtocolMapperModel mappingModel, KeycloakSession session, - UserSessionModel userSession, ClientSessionModel clientSession) { - String note = mappingModel.getConfig().get(CLIENT_SESSION_NOTE); - String noteValue = clientSession.getNote(note); - AttributeMapperHelper.mapClaim(token, mappingModel, noteValue); - return token; - } - -} diff --git a/services/src/main/java/org/keycloak/protocol/oidc/mappers/OIDCUserAttributeMapper.java b/services/src/main/java/org/keycloak/protocol/oidc/mappers/OIDCUserAttributeMapper.java index ffc742db78..7aadbf321b 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 @@ -5,6 +5,7 @@ import org.keycloak.models.KeycloakSession; import org.keycloak.models.ProtocolMapperModel; import org.keycloak.models.UserModel; import org.keycloak.models.UserSessionModel; +import org.keycloak.protocol.ProtocolMapperUtils; import org.keycloak.representations.AccessToken; import java.util.ArrayList; @@ -21,18 +22,17 @@ import java.util.List; public class OIDCUserAttributeMapper extends AbstractOIDCProtocolMapper implements OIDCAccessTokenMapper { private static final List configProperties = new ArrayList(); - public static final String USER_MODEL_ATTRIBUTE_NAME = "UserModel Attribute Name"; static { ConfigProperty property; property = new ConfigProperty(); - property.setName(USER_MODEL_ATTRIBUTE_NAME); - property.setLabel(USER_MODEL_ATTRIBUTE_NAME); - property.setHelpText("Name of stored user attribute which is the name of an attribute within the UserModel.attribute map."); + property.setName(ProtocolMapperUtils.USER_MODEL_ATTRIBUTE_NAME); + property.setLabel(ProtocolMapperUtils.USER_MODEL_ATTRIBUTE_NAME); + property.setHelpText(ProtocolMapperUtils.USER_MODEL_ATTRIBUTE_HELP_TEXT); configProperties.add(property); property = new ConfigProperty(); - property.setName(AttributeMapperHelper.TOKEN_CLAIM_NAME); - property.setLabel(AttributeMapperHelper.TOKEN_CLAIM_NAME); + property.setName(OIDCAttributeMapperHelper.TOKEN_CLAIM_NAME); + property.setLabel(OIDCAttributeMapperHelper.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); @@ -69,10 +69,10 @@ public class OIDCUserAttributeMapper extends AbstractOIDCProtocolMapper implemen public AccessToken transformToken(AccessToken token, ProtocolMapperModel mappingModel, KeycloakSession session, UserSessionModel userSession, ClientSessionModel clientSession) { UserModel user = userSession.getUser(); - String attributeName = mappingModel.getConfig().get(USER_MODEL_ATTRIBUTE_NAME); + String attributeName = mappingModel.getConfig().get(ProtocolMapperUtils.USER_MODEL_ATTRIBUTE_NAME); String attributeValue = user.getAttribute(attributeName); if (attributeValue == null) return token; - AttributeMapperHelper.mapClaim(token, mappingModel, attributeValue); + OIDCAttributeMapperHelper.mapClaim(token, mappingModel, attributeValue); return token; } 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 d5da0fb0cb..693311f568 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 @@ -5,9 +5,9 @@ import org.keycloak.models.KeycloakSession; import org.keycloak.models.ProtocolMapperModel; import org.keycloak.models.UserModel; import org.keycloak.models.UserSessionModel; +import org.keycloak.protocol.ProtocolMapperUtils; import org.keycloak.representations.AccessToken; -import java.lang.reflect.Method; import java.util.ArrayList; import java.util.List; @@ -21,18 +21,17 @@ import java.util.List; */ public class OIDCUserModelMapper extends AbstractOIDCProtocolMapper implements OIDCAccessTokenMapper { private static final List configProperties = new ArrayList(); - public static final String USER_MODEL_PROPERTY = "User Property"; static { ConfigProperty property; property = new ConfigProperty(); - property.setName(USER_MODEL_PROPERTY); - property.setLabel(USER_MODEL_PROPERTY); - property.setHelpText("Name of the property method in the UserModel interface. For example, a value of 'email' would reference the UserModel.getEmail() method."); + property.setName(ProtocolMapperUtils.USER_MODEL_PROPERTY); + property.setLabel(ProtocolMapperUtils.USER_MODEL_PROPERTY); + property.setHelpText(ProtocolMapperUtils.USER_MODEL_PROPERTY_HELP_TEXT); configProperties.add(property); property = new ConfigProperty(); - property.setName(AttributeMapperHelper.TOKEN_CLAIM_NAME); - property.setLabel(AttributeMapperHelper.TOKEN_CLAIM_NAME); + property.setName(OIDCAttributeMapperHelper.TOKEN_CLAIM_NAME); + property.setLabel(OIDCAttributeMapperHelper.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); @@ -68,31 +67,11 @@ public class OIDCUserModelMapper extends AbstractOIDCProtocolMapper implements O public AccessToken transformToken(AccessToken token, ProtocolMapperModel mappingModel, KeycloakSession session, UserSessionModel userSession, ClientSessionModel clientSession) { UserModel user = userSession.getUser(); - String propertyName = mappingModel.getConfig().get(USER_MODEL_PROPERTY); - String propertyValue = getUserModelValue(user,propertyName); - AttributeMapperHelper.mapClaim(token, mappingModel, propertyValue); + String propertyName = mappingModel.getConfig().get(ProtocolMapperUtils.USER_MODEL_PROPERTY); + String propertyValue = ProtocolMapperUtils.getUserModelValue(user, propertyName); + OIDCAttributeMapperHelper.mapClaim(token, mappingModel, propertyValue); return token; } - protected String getUserModelValue(UserModel user, String propertyName) { - - String methodName = "get" + Character.toUpperCase(propertyName.charAt(0)) + propertyName.substring(1); - try { - Method method = UserModel.class.getMethod(methodName); - Object val = method.invoke(user); - if (val != null) return val.toString(); - } catch (Exception ignore) { - - } - 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 deleted file mode 100755 index b176340ed7..0000000000 --- a/services/src/main/java/org/keycloak/protocol/oidc/mappers/OIDCUserSessionNoteMapper.java +++ /dev/null @@ -1,72 +0,0 @@ -package org.keycloak.protocol.oidc.mappers; - -import org.keycloak.models.ClientSessionModel; -import org.keycloak.models.KeycloakSession; -import org.keycloak.models.ProtocolMapperModel; -import org.keycloak.models.UserSessionModel; -import org.keycloak.representations.AccessToken; - -import java.util.ArrayList; -import java.util.List; - -/** - * Mappings UserSessionModel.note to an ID Token claim. Token claim name can be a full qualified nested object name, - * i.e. "address.country". This will create a nested - * json object within the toke claim. - * - * @author Bill Burke - * @version $Revision: 1 $ - */ -public class OIDCUserSessionNoteMapper extends AbstractOIDCProtocolMapper implements OIDCAccessTokenMapper { - private static final List configProperties = new ArrayList(); - public static final String USER_SESSION_NOTE = "UserSession Note"; - - static { - ConfigProperty property; - property = new ConfigProperty(); - property.setName(USER_SESSION_NOTE); - property.setLabel("UserSession Note"); - property.setHelpText("Name of the note to map in the UserSessionModel"); - configProperties.add(property); - property = new ConfigProperty(); - 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 List getConfigProperties() { - return configProperties; - } - - @Override - public String getId() { - return "oidc-user-session-note-mapper"; - } - - @Override - public String getDisplayType() { - return "UserSession Note"; - } - - @Override - public String getDisplayCategory() { - return TOKEN_MAPPER_CATEGORY; - } - - @Override - public String getHelpText() { - return "Map a temporary note that is attached to the UserSession to a token claim."; - } - - @Override - public AccessToken transformToken(AccessToken token, ProtocolMapperModel mappingModel, KeycloakSession session, - UserSessionModel userSession, ClientSessionModel clientSession) { - String note = mappingModel.getConfig().get(USER_SESSION_NOTE); - String noteValue = userSession.getNote(note); - AttributeMapperHelper.mapClaim(token, mappingModel, noteValue); - return token; - } - -} diff --git a/services/src/main/resources/META-INF/services/org.keycloak.protocol.ProtocolMapper b/services/src/main/resources/META-INF/services/org.keycloak.protocol.ProtocolMapper index 1815b887d4..1615ecc763 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,8 +1,6 @@ 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/testsuite/integration/src/test/java/org/keycloak/testsuite/account/AccountTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/account/AccountTest.java index a69389d090..efe3fd4c50 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/account/AccountTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/account/AccountTest.java @@ -157,8 +157,7 @@ public class AccountTest { }); } - @Test - @Ignore + //@Test @Ignore public void runit() throws Exception { Thread.sleep(10000000); }