From db2c01caa98034993474db779895b67ae9d0195f Mon Sep 17 00:00:00 2001 From: Bill Burke Date: Thu, 5 Mar 2015 19:55:53 -0500 Subject: [PATCH 1/2] more mappers --- .../keycloak/representations/AccessToken.java | 8 +- .../saml/mappers/SAMLRoleListMapper.java | 17 +++ .../oidc/mappers/OIDCAddClaimMapper.java | 127 ++++++++++++++++++ .../oidc/mappers/OIDCAddRoleMapper.java | 81 +++++++++++ .../mappers/OIDCAttributeMapperHelper.java | 3 +- .../protocol/oidc/mappers/OIDCRoleMapper.java | 100 ++++++++++++++ .../oidc/mappers/OIDCUserAttributeMapper.java | 2 +- .../oidc/mappers/OIDCUserModelMapper.java | 2 +- .../org.keycloak.protocol.ProtocolMapper | 3 + 9 files changed, 337 insertions(+), 6 deletions(-) create mode 100755 saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/mappers/SAMLRoleListMapper.java create mode 100755 services/src/main/java/org/keycloak/protocol/oidc/mappers/OIDCAddClaimMapper.java create mode 100755 services/src/main/java/org/keycloak/protocol/oidc/mappers/OIDCAddRoleMapper.java create mode 100755 services/src/main/java/org/keycloak/protocol/oidc/mappers/OIDCRoleMapper.java diff --git a/core/src/main/java/org/keycloak/representations/AccessToken.java b/core/src/main/java/org/keycloak/representations/AccessToken.java index 4d099ca2c0..81d3615bb7 100755 --- a/core/src/main/java/org/keycloak/representations/AccessToken.java +++ b/core/src/main/java/org/keycloak/representations/AccessToken.java @@ -125,9 +125,11 @@ public class AccessToken extends IDToken { } public Access addAccess(String service) { - Access token = new Access(); - resourceAccess.put(service, token); - return token; + Access access = resourceAccess.get(service); + if (access != null) return access; + access = new Access(); + resourceAccess.put(service, access); + return access; } public AccessToken clientSession(String session) { diff --git a/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/mappers/SAMLRoleListMapper.java b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/mappers/SAMLRoleListMapper.java new file mode 100755 index 0000000000..b2d500fa67 --- /dev/null +++ b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/mappers/SAMLRoleListMapper.java @@ -0,0 +1,17 @@ +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; + +/** + * @author Bill Burke + * @version $Revision: 1 $ + */ +public interface SAMLRoleListMapper { + + void mapRoles(AttributeStatementType roleAttributeStatement, ProtocolMapperModel mappingModel, KeycloakSession session, + UserSessionModel userSession, ClientSessionModel clientSession); +} diff --git a/services/src/main/java/org/keycloak/protocol/oidc/mappers/OIDCAddClaimMapper.java b/services/src/main/java/org/keycloak/protocol/oidc/mappers/OIDCAddClaimMapper.java new file mode 100755 index 0000000000..fad7bd6795 --- /dev/null +++ b/services/src/main/java/org/keycloak/protocol/oidc/mappers/OIDCAddClaimMapper.java @@ -0,0 +1,127 @@ +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.protocol.ProtocolMapperUtils; +import org.keycloak.representations.AccessToken; +import org.keycloak.representations.IDToken; + +import java.util.ArrayList; +import java.util.List; + +/** + * + * + * @author Bill Burke + * @version $Revision: 1 $ + */ +public class OIDCAddClaimMapper extends AbstractOIDCProtocolMapper implements OIDCAccessTokenMapper, OIDCIDTokenMapper { + + private static final List configProperties = new ArrayList(); + + public static final String CLAIM_VALUE = "claim.value"; + + static { + ConfigProperty property; + property = new ConfigProperty(); + property.setName(OIDCAttributeMapperHelper.TOKEN_CLAIM_NAME); + property.setLabel(OIDCAttributeMapperHelper.TOKEN_CLAIM_NAME_LABEL); + property.setType(ConfigProperty.STRING_TYPE); + property.setHelpText("Claim name you want to hard code 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); + property = new ConfigProperty(); + property.setName(CLAIM_VALUE); + property.setLabel("Claim value"); + property.setType(ConfigProperty.STRING_TYPE); + property.setHelpText("Value of the claim you want to hard code. 'true' and 'false can be used for boolean values."); + configProperties.add(property); + property = new ConfigProperty(); + property.setName(OIDCAttributeMapperHelper.JSON_TYPE); + property.setLabel(OIDCAttributeMapperHelper.JSON_TYPE); + property.setType(ConfigProperty.STRING_TYPE); + property.setDefaultValue(ConfigProperty.STRING_TYPE); + property.setHelpText("JSON type that should be used for the value of the claim. long, int, boolean, and String are valid values."); + configProperties.add(property); + property = new ConfigProperty(); + property.setName(OIDCAttributeMapperHelper.INCLUDE_IN_ID_TOKEN); + property.setLabel(OIDCAttributeMapperHelper.INCLUDE_IN_ID_TOKEN_LABEL); + property.setType(ConfigProperty.BOOLEAN_TYPE); + property.setDefaultValue("true"); + property.setHelpText(OIDCAttributeMapperHelper.INCLUDE_IN_ID_TOKEN_HELP_TEXT); + configProperties.add(property); + property = new ConfigProperty(); + property.setName(OIDCAttributeMapperHelper.INCLUDE_IN_ACCESS_TOKEN); + property.setLabel(OIDCAttributeMapperHelper.INCLUDE_IN_ACCESS_TOKEN_LABEL); + property.setType(ConfigProperty.BOOLEAN_TYPE); + property.setDefaultValue("true"); + property.setHelpText(OIDCAttributeMapperHelper.INCLUDE_IN_ACCESS_TOKEN_HELP_TEXT); + configProperties.add(property); + + } + + public static final String PROVIDER_ID = "oidc-add-claim-mapper"; + + + public List getConfigProperties() { + return configProperties; + } + + @Override + public String getId() { + return PROVIDER_ID; + } + + @Override + public String getDisplayType() { + return "Hard coded claim"; + } + + @Override + public String getDisplayCategory() { + return TOKEN_MAPPER_CATEGORY; + } + + @Override + public String getHelpText() { + return "Hardcode a claim into the token."; + } + + @Override + public AccessToken transformAccessToken(AccessToken token, ProtocolMapperModel mappingModel, KeycloakSession session, + UserSessionModel userSession, ClientSessionModel clientSession) { + if (!OIDCAttributeMapperHelper.includeInAccessToken(mappingModel)) return token; + + setClaim(token, mappingModel, userSession); + return token; + } + + protected void setClaim(IDToken token, ProtocolMapperModel mappingModel, UserSessionModel userSession) { + String attributeValue = mappingModel.getConfig().get(CLAIM_VALUE); + if (attributeValue == null) return; + OIDCAttributeMapperHelper.mapClaim(token, mappingModel, attributeValue); + } + + @Override + public IDToken transformIDToken(IDToken token, ProtocolMapperModel mappingModel, KeycloakSession session, UserSessionModel userSession, ClientSessionModel clientSession) { + if (!OIDCAttributeMapperHelper.includeInIDToken(mappingModel)) return token; + setClaim(token, mappingModel, userSession); + return token; + } + + public static ProtocolMapperModel createClaimMapper(String name, + String userAttribute, + String tokenClaimName, String claimType, + boolean consentRequired, String consentText, + boolean accessToken, boolean idToken) { + return OIDCAttributeMapperHelper.createClaimMapper(name, userAttribute, + tokenClaimName, claimType, + consentRequired, consentText, + accessToken, idToken, + PROVIDER_ID); + } + + +} diff --git a/services/src/main/java/org/keycloak/protocol/oidc/mappers/OIDCAddRoleMapper.java b/services/src/main/java/org/keycloak/protocol/oidc/mappers/OIDCAddRoleMapper.java new file mode 100755 index 0000000000..c46b9d48ee --- /dev/null +++ b/services/src/main/java/org/keycloak/protocol/oidc/mappers/OIDCAddRoleMapper.java @@ -0,0 +1,81 @@ +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; + +/** + * Add a role to a token + * + * @author Bill Burke + * @version $Revision: 1 $ + */ +public class OIDCAddRoleMapper extends AbstractOIDCProtocolMapper implements OIDCAccessTokenMapper { + + private static final List configProperties = new ArrayList(); + + public static final String ROLE_CONFIG = "role"; + + static { + ConfigProperty property; + property = new ConfigProperty(); + property.setName(ROLE_CONFIG); + property.setLabel("Role"); + property.setHelpText("Role you want added to the token. To specify an application role the syntax is appname.approle, i.e. myapp.myrole"); + property.setType(ConfigProperty.STRING_TYPE); + configProperties.add(property); + } + + public static final String PROVIDER_ID = "oidc-role-mapper"; + + + public List getConfigProperties() { + return configProperties; + } + + @Override + public String getId() { + return PROVIDER_ID; + } + + @Override + public String getDisplayType() { + return "Add Role"; + } + + @Override + public String getDisplayCategory() { + return TOKEN_MAPPER_CATEGORY; + } + + @Override + public String getHelpText() { + return "Hardcode any role specify into the token."; + } + + @Override + public AccessToken transformAccessToken(AccessToken token, ProtocolMapperModel mappingModel, KeycloakSession session, + UserSessionModel userSession, ClientSessionModel clientSession) { + String role = mappingModel.getConfig().get(ROLE_CONFIG); + String appName = null; + int scopeIndex = role.indexOf('.'); + if (scopeIndex > -1) { + appName = role.substring(0, scopeIndex); + role = role.substring(scopeIndex + 1); + token.addAccess(appName).addRole(role); + } else { + AccessToken.Access access = token.getRealmAccess(); + if (access == null) { + access = new AccessToken.Access(); + token.setRealmAccess(access); + } + access.addRole(role); + } + return token; + } +} diff --git a/services/src/main/java/org/keycloak/protocol/oidc/mappers/OIDCAttributeMapperHelper.java b/services/src/main/java/org/keycloak/protocol/oidc/mappers/OIDCAttributeMapperHelper.java index 881971305e..b91c233172 100755 --- a/services/src/main/java/org/keycloak/protocol/oidc/mappers/OIDCAttributeMapperHelper.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/mappers/OIDCAttributeMapperHelper.java @@ -15,7 +15,8 @@ import java.util.Map; * @version $Revision: 1 $ */ public class OIDCAttributeMapperHelper { - public static final String TOKEN_CLAIM_NAME = "Token Claim Name"; + public static final String TOKEN_CLAIM_NAME = "claim.name"; + public static final String TOKEN_CLAIM_NAME_LABEL = "Token Claim Name"; public static final String JSON_TYPE = "Claim JSON Type"; public static final String INCLUDE_IN_ACCESS_TOKEN = "access.token.claim"; public static final String INCLUDE_IN_ACCESS_TOKEN_LABEL = "Add to access token"; diff --git a/services/src/main/java/org/keycloak/protocol/oidc/mappers/OIDCRoleMapper.java b/services/src/main/java/org/keycloak/protocol/oidc/mappers/OIDCRoleMapper.java new file mode 100755 index 0000000000..10724ef18f --- /dev/null +++ b/services/src/main/java/org/keycloak/protocol/oidc/mappers/OIDCRoleMapper.java @@ -0,0 +1,100 @@ +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.protocol.ProtocolMapperUtils; +import org.keycloak.representations.AccessToken; +import org.keycloak.representations.IDToken; + +import java.util.ArrayList; +import java.util.List; + +/** + * Map an assigned role to a different position and name in the token + * + * @author Bill Burke + * @version $Revision: 1 $ + */ +public class OIDCRoleMapper extends AbstractOIDCProtocolMapper implements OIDCAccessTokenMapper { + + private static final List configProperties = new ArrayList(); + + public static final String ROLE_CONFIG = "role"; + public static String NEW_ROLE_NAME = "new.role.name"; + + static { + ConfigProperty property; + property = new ConfigProperty(); + property.setName(ROLE_CONFIG); + property.setLabel("Role"); + property.setHelpText("Role name you want changed. To reference an application role the syntax is appname.approle, i.e. myapp.myrole"); + property.setType(ConfigProperty.STRING_TYPE); + configProperties.add(property); + property.setName(NEW_ROLE_NAME); + property.setLabel("New Role Name"); + property.setHelpText("The new role name. The new name format corresponds to where in the access token the role will be mapped to. So, a new name of 'myapp.newname' will map the role to that position in the access token. A new name of 'newname' will map the role to the realm roles in the token."); + property.setType(ConfigProperty.STRING_TYPE); + configProperties.add(property); + } + + public static final String PROVIDER_ID = "oidc-role-mapper"; + + + public List getConfigProperties() { + return configProperties; + } + + @Override + public String getId() { + return PROVIDER_ID; + } + + @Override + public String getDisplayType() { + return "Role Mapper"; + } + + @Override + public String getDisplayCategory() { + return TOKEN_MAPPER_CATEGORY; + } + + @Override + public String getHelpText() { + return "Map an assigned role to a new name or position in the token."; + } + + @Override + public AccessToken transformAccessToken(AccessToken token, ProtocolMapperModel mappingModel, KeycloakSession session, + UserSessionModel userSession, ClientSessionModel clientSession) { + String role = mappingModel.getConfig().get(ROLE_CONFIG); + String newName = mappingModel.getConfig().get(NEW_ROLE_NAME); + String appName = null; + int scopeIndex = role.indexOf('.'); + if (scopeIndex > -1) { + appName = role.substring(0, scopeIndex); + AccessToken.Access access = token.getResourceAccess(appName); + if (access == null) return token; + + role = role.substring(scopeIndex + 1); + if (!access.getRoles().contains(role)) return token; + access.getRoles().remove(role); + } else { + AccessToken.Access access = token.getRealmAccess(); + if (access == null) return token; + access.getRoles().remove(role); + } + + String newAppName = null; + scopeIndex = newName.indexOf('.'); + if (scopeIndex > -1) { + newAppName = role.substring(0, scopeIndex); + newName = role.substring(scopeIndex + 1); + token.addAccess(newAppName).addRole(newName); + } + 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 f0f565782e..b2ea92dc9e 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 @@ -35,7 +35,7 @@ public class OIDCUserAttributeMapper extends AbstractOIDCProtocolMapper implemen configProperties.add(property); property = new ConfigProperty(); property.setName(OIDCAttributeMapperHelper.TOKEN_CLAIM_NAME); - property.setLabel(OIDCAttributeMapperHelper.TOKEN_CLAIM_NAME); + property.setLabel(OIDCAttributeMapperHelper.TOKEN_CLAIM_NAME_LABEL); property.setType(ConfigProperty.STRING_TYPE); 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); 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 cd0cd03d68..3b559e26ac 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 @@ -34,7 +34,7 @@ public class OIDCUserModelMapper extends AbstractOIDCProtocolMapper implements O configProperties.add(property); property = new ConfigProperty(); property.setName(OIDCAttributeMapperHelper.TOKEN_CLAIM_NAME); - property.setLabel(OIDCAttributeMapperHelper.TOKEN_CLAIM_NAME); + property.setLabel(OIDCAttributeMapperHelper.TOKEN_CLAIM_NAME_LABEL); property.setType(ConfigProperty.STRING_TYPE); 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); 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 1615ecc763..d89c1bf2a1 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 @@ -2,5 +2,8 @@ org.keycloak.protocol.oidc.mappers.OIDCUserAttributeMapper org.keycloak.protocol.oidc.mappers.OIDCFullNameMapper org.keycloak.protocol.oidc.mappers.OIDCUserModelMapper org.keycloak.protocol.oidc.mappers.OIDCAddressMapper +org.keycloak.protocol.oidc.mappers.OIDCAddClaimMapper +org.keycloak.protocol.oidc.mappers.OIDCAddRoleMapper +org.keycloak.protocol.oidc.mappers.OIDCRoleMapper From 4a4158a4e47d69c8e97f8ecd83e072c8da6e4e03 Mon Sep 17 00:00:00 2001 From: Bill Burke Date: Fri, 6 Mar 2015 18:29:36 -0500 Subject: [PATCH 2/2] test saml attributes --- .../idm/ProtocolMapperTypeRepresentation.java | 6 +- .../oauth-client-protocol-mapper-detail.html | 7 +- .../partials/protocol-mapper-detail.html | 9 +- .../protocol/saml/SamlProtocolFactory.java | 30 +- .../mappers/AttributeStatementHelper.java | 81 +++-- ...java => UserAttributeStatementMapper.java} | 24 +- ...eUriReferenceAttributeStatementMapper.java | 79 ----- ...serModelBasicAttributeStatementMapper.java | 79 ----- ...UserPropertyAttributeStatementMapper.java} | 23 +- .../org.keycloak.protocol.ProtocolMapper | 6 +- .../org/keycloak/protocol/ProtocolMapper.java | 7 +- .../protocol/oidc/mappers/OIDCRoleMapper.java | 1 + .../testsuite/adapter/AdapterTest.java | 316 +++++++++--------- .../testsuite/rule/AbstractKeycloakRule.java | 2 +- .../testsuite/saml/SamlBindingTest.java | 87 +++++ .../saml/simple-get/WEB-INF/picketlink.xml | 2 - .../src/test/resources/saml/testsaml.json | 48 ++- 17 files changed, 391 insertions(+), 416 deletions(-) rename saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/mappers/{UserAttributeBasicAttributeStatementMapper.java => UserAttributeStatementMapper.java} (70%) delete mode 100755 saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/mappers/UserAttributeUriReferenceAttributeStatementMapper.java delete mode 100755 saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/mappers/UserModelBasicAttributeStatementMapper.java rename saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/mappers/{UserModelUriReferenceAttributeStatementMapper.java => UserPropertyAttributeStatementMapper.java} (72%) diff --git a/core/src/main/java/org/keycloak/representations/idm/ProtocolMapperTypeRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/ProtocolMapperTypeRepresentation.java index 1e510f8bfa..779f3be519 100755 --- a/core/src/main/java/org/keycloak/representations/idm/ProtocolMapperTypeRepresentation.java +++ b/core/src/main/java/org/keycloak/representations/idm/ProtocolMapperTypeRepresentation.java @@ -17,7 +17,7 @@ public class ProtocolMapperTypeRepresentation { protected String label; protected String helpText; protected String type; - protected String defaultValue; + protected Object defaultValue; public String getName() { return name; @@ -43,11 +43,11 @@ public class ProtocolMapperTypeRepresentation { this.type = type; } - public String getDefaultValue() { + public Object getDefaultValue() { return defaultValue; } - public void setDefaultValue(String defaultValue) { + public void setDefaultValue(Object defaultValue) { this.defaultValue = defaultValue; } diff --git a/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/oauth-client-protocol-mapper-detail.html b/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/oauth-client-protocol-mapper-detail.html index a98bbe7cf6..36738b65b6 100755 --- a/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/oauth-client-protocol-mapper-detail.html +++ b/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/oauth-client-protocol-mapper-detail.html @@ -78,12 +78,17 @@
-
+
+
+ +
diff --git a/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/protocol-mapper-detail.html b/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/protocol-mapper-detail.html index 25be1f11d0..aed7f506c6 100755 --- a/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/protocol-mapper-detail.html +++ b/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/protocol-mapper-detail.html @@ -76,14 +76,19 @@
- + -
+
+
+ +
diff --git a/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlProtocolFactory.java b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlProtocolFactory.java index 934abe90f1..5f673fbdb9 100755 --- a/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlProtocolFactory.java +++ b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlProtocolFactory.java @@ -4,26 +4,18 @@ import org.keycloak.Config; import org.keycloak.events.EventBuilder; import org.keycloak.models.ClientModel; import org.keycloak.models.KeycloakSession; -import org.keycloak.models.KeycloakSessionFactory; import org.keycloak.models.ProtocolMapperModel; import org.keycloak.models.RealmModel; import org.keycloak.protocol.AbstractLoginProtocolFactory; import org.keycloak.protocol.LoginProtocol; -import org.keycloak.protocol.LoginProtocolFactory; -import org.keycloak.protocol.oidc.OIDCLoginProtocol; -import org.keycloak.protocol.oidc.mappers.OIDCAttributeMapperHelper; -import org.keycloak.protocol.oidc.mappers.OIDCUserModelMapper; -import org.keycloak.protocol.saml.mappers.AttributeStatementHelper; -import org.keycloak.protocol.saml.mappers.UserAttributeBasicAttributeStatementMapper; -import org.keycloak.protocol.saml.mappers.UserModelUriReferenceAttributeStatementMapper; +import org.keycloak.protocol.saml.mappers.UserPropertyAttributeStatementMapper; import org.keycloak.services.managers.AuthenticationManager; +import org.picketlink.common.constants.JBossSAMLURIConstants; import org.picketlink.identity.federation.core.saml.v2.constants.X500SAMLProfileConstants; import org.picketlink.identity.federation.core.sts.PicketLinkCoreSTS; import java.util.ArrayList; -import java.util.HashMap; import java.util.List; -import java.util.Map; /** * @author Bill Burke @@ -62,19 +54,25 @@ public class SamlProtocolFactory extends AbstractLoginProtocolFactory { static { ProtocolMapperModel model; - model = UserModelUriReferenceAttributeStatementMapper.createAttributeMapper("X500 email", + model = UserPropertyAttributeStatementMapper.createAttributeMapper("X500 email", "email", - X500SAMLProfileConstants.EMAIL.get(), X500SAMLProfileConstants.EMAIL.getFriendlyName(), + X500SAMLProfileConstants.EMAIL.get(), + JBossSAMLURIConstants.ATTRIBUTE_FORMAT_URI.get(), + X500SAMLProfileConstants.EMAIL.getFriendlyName(), true, "email"); builtins.add(model); - model = UserModelUriReferenceAttributeStatementMapper.createAttributeMapper("X500 givenName", + model = UserPropertyAttributeStatementMapper.createAttributeMapper("X500 givenName", "firstName", - X500SAMLProfileConstants.GIVEN_NAME.get(), X500SAMLProfileConstants.GIVEN_NAME.getFriendlyName(), + X500SAMLProfileConstants.GIVEN_NAME.get(), + JBossSAMLURIConstants.ATTRIBUTE_FORMAT_URI.get(), + X500SAMLProfileConstants.GIVEN_NAME.getFriendlyName(), true, "given name"); builtins.add(model); - model = UserModelUriReferenceAttributeStatementMapper.createAttributeMapper("X500 surname", + model = UserPropertyAttributeStatementMapper.createAttributeMapper("X500 surname", "lastName", - X500SAMLProfileConstants.SURNAME.get(), X500SAMLProfileConstants.SURNAME.getFriendlyName(), + X500SAMLProfileConstants.SURNAME.get(), + JBossSAMLURIConstants.ATTRIBUTE_FORMAT_URI.get(), + X500SAMLProfileConstants.SURNAME.getFriendlyName(), true, "family name"); builtins.add(model); 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 index 3bbb7fcb75..378a0f613d 100755 --- 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 @@ -9,6 +9,7 @@ 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.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -18,19 +19,24 @@ import java.util.Map; * @version $Revision: 1 $ */ public class AttributeStatementHelper { - public static final String SAML_ATTRIBUTE_NAME = "SAML Attribute Name"; + public static final String SAML_ATTRIBUTE_NAME = "attribute.name"; public static final String ATTRIBUTE_STATEMENT_CATEGORY = "AttributeStatement Mapper"; - public static final String URI_REFERENCE_LABEL = "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_LABEL = "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 = "friendly.name"; + public static final String FRIENDLY_NAME_LABEL = "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 final String SAML_ATTRIBUTE_NAMEFORMAT = "attribute.nameformat"; + public static final String BASIC = "Basic"; + public static final String URI_REFERENCE = "URI Reference"; + public static final String UNSPECIFIED = "Unspecified"; public static void addAttribute(AttributeStatementType attributeStatement, ProtocolMapperModel mappingModel, - String attributeNameFormat, String attributeValue) { + String attributeValue) { String attributeName = mappingModel.getConfig().get(SAML_ATTRIBUTE_NAME); AttributeType attribute = new AttributeType(attributeName); + String attributeType = mappingModel.getConfig().get(SAML_ATTRIBUTE_NAMEFORMAT); + String attributeNameFormat = JBossSAMLURIConstants.ATTRIBUTE_FORMAT_BASIC.get(); + if ("URI Reference".equals(attributeType)) attributeNameFormat = JBossSAMLURIConstants.ATTRIBUTE_FORMAT_URI.get(); + else if ("Unspecified".equals(attributeType)) attributeNameFormat = "urn:oasis:names:tc:SAML2.0:attrname-format:unspecified"; attribute.setNameFormat(attributeNameFormat); String friendlyName = mappingModel.getConfig().get(FRIENDLY_NAME); if (friendlyName != null && !friendlyName.trim().equals("")) attribute.setFriendlyName(friendlyName); @@ -38,43 +44,31 @@ public class AttributeStatementHelper { attributeStatement.addAttribute(new AttributeStatementType.ASTChoiceType(attribute)); } - public static void addUriReferenceAttribute(AttributeStatementType attributeStatement, ProtocolMapperModel mappingModel, String attributeValue) { - String attributeNameFormat = JBossSAMLURIConstants.ATTRIBUTE_FORMAT_URI.get(); - addAttribute(attributeStatement, mappingModel, attributeNameFormat, attributeValue); - } + public static void setConfigProperties(List configProperties) { + ProtocolMapper.ConfigProperty property = new ProtocolMapper.ConfigProperty(); + property.setName(AttributeStatementHelper.FRIENDLY_NAME); + property.setLabel(AttributeStatementHelper.FRIENDLY_NAME_LABEL); + property.setHelpText(AttributeStatementHelper.FRIENDLY_NAME_HELP_TEXT); + configProperties.add(property); + property = new ProtocolMapper.ConfigProperty(); + property.setName(AttributeStatementHelper.SAML_ATTRIBUTE_NAME); + property.setLabel("SAML Attribute Name"); + property.setHelpText("SAML Attribute Name"); + configProperties.add(property); + property = new ProtocolMapper.ConfigProperty(); + property.setName(AttributeStatementHelper.SAML_ATTRIBUTE_NAMEFORMAT); + property.setLabel("SAML Attribute NameFormat"); + property.setHelpText("SAML Attribute NameFormat. Can be basic, URI reference, or unspecified."); + List types = new ArrayList(3); + types.add(AttributeStatementHelper.BASIC); + types.add(AttributeStatementHelper.URI_REFERENCE); + types.add(AttributeStatementHelper.UNSPECIFIED); + property.setType(ProtocolMapper.ConfigProperty.LIST_TYPE); + property.setDefaultValue(types); + configProperties.add(property); - public static void addBasicAttribute(AttributeStatementType attributeStatement, ProtocolMapperModel mappingModel, String attributeValue) { - addAttribute(attributeStatement, mappingModel, JBossSAMLURIConstants.ATTRIBUTE_FORMAT_BASIC.get(), attributeValue); } - - 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(SAML_ATTRIBUTE_NAME); - property.setLabel(URI_REFERENCE_LABEL); - 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(SAML_ATTRIBUTE_NAME); - property.setLabel(BASIC_LABEL); - property.setHelpText(BASIC_HELP_TEXT); - configProperties.add(property); - } - - public static ProtocolMapperModel createAttributeMapper(String name, String userAttribute, String samlAttributeName, String friendlyName, boolean consentRequired, String consentText, String mapperId) { + public static ProtocolMapperModel createAttributeMapper(String name, String userAttribute, String samlAttributeName, String nameFormat, String friendlyName, boolean consentRequired, String consentText, String mapperId) { ProtocolMapperModel mapper = mapper = new ProtocolMapperModel(); mapper.setName(name); mapper.setProtocolMapper(mapperId); @@ -87,6 +81,9 @@ public class AttributeStatementHelper { if (friendlyName != null) { config.put(FRIENDLY_NAME, friendlyName); } + if (nameFormat != null) { + config.put(SAML_ATTRIBUTE_NAMEFORMAT, nameFormat); + } mapper.setConfig(config); return mapper; } 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/UserAttributeStatementMapper.java similarity index 70% rename from saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/mappers/UserAttributeBasicAttributeStatementMapper.java rename to saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/mappers/UserAttributeStatementMapper.java index 16374dd991..fd83512334 100755 --- 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/UserAttributeStatementMapper.java @@ -3,7 +3,6 @@ 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.RealmModel; import org.keycloak.models.UserModel; import org.keycloak.models.UserSessionModel; import org.keycloak.protocol.ProtocolMapperUtils; @@ -18,7 +17,7 @@ import java.util.List; * @author Bill Burke * @version $Revision: 1 $ */ -public class UserAttributeBasicAttributeStatementMapper extends AbstractSAMLProtocolMapper implements SAMLAttributeStatementMapper { +public class UserAttributeStatementMapper extends AbstractSAMLProtocolMapper implements SAMLAttributeStatementMapper { private static final List configProperties = new ArrayList(); static { @@ -28,11 +27,11 @@ public class UserAttributeBasicAttributeStatementMapper extends AbstractSAMLProt property.setLabel(ProtocolMapperUtils.USER_MODEL_ATTRIBUTE_LABEL); property.setHelpText(ProtocolMapperUtils.USER_MODEL_ATTRIBUTE_HELP_TEXT); configProperties.add(property); - AttributeStatementHelper.addBasicProperties(configProperties); + AttributeStatementHelper.setConfigProperties(configProperties); } - public static final String PROVIDER_ID = "saml-user-attribute-basic-mapper"; + public static final String PROVIDER_ID = "saml-user-attribute-mapper"; public List getConfigProperties() { @@ -45,7 +44,7 @@ public class UserAttributeBasicAttributeStatementMapper extends AbstractSAMLProt @Override public String getDisplayType() { - return "User Attribute Basic"; + return "User Attribute"; } @Override @@ -55,7 +54,7 @@ public class UserAttributeBasicAttributeStatementMapper extends AbstractSAMLProt @Override public String getHelpText() { - return "Map a custom user attribute to a to a SAML Basic attribute type.."; + return "Map a custom user attribute to a to a SAML attribute."; } @Override @@ -63,17 +62,16 @@ public class UserAttributeBasicAttributeStatementMapper extends AbstractSAMLProt UserModel user = userSession.getUser(); String attributeName = mappingModel.getConfig().get(ProtocolMapperUtils.USER_ATTRIBUTE); String attributeValue = user.getAttribute(attributeName); - AttributeStatementHelper.addBasicAttribute(attributeStatement, mappingModel, attributeValue); + AttributeStatementHelper.addAttribute(attributeStatement, mappingModel, attributeValue); } - public static ProtocolMapperModel createAttributeMapper(String name, - String userAttribute, - String samlAttributeName, - String friendlyName, - boolean consentRequired, String consentText) { + public static ProtocolMapperModel createAttributeMapper(String name, String userAttribute, + String samlAttributeName, String nameFormat, String friendlyName, + boolean consentRequired, String consentText) { String mapperId = PROVIDER_ID; - return AttributeStatementHelper.createAttributeMapper(name, userAttribute, samlAttributeName, friendlyName, consentRequired, consentText, mapperId); + return AttributeStatementHelper.createAttributeMapper(name, userAttribute, samlAttributeName, nameFormat, friendlyName, + consentRequired, consentText, mapperId); } 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 deleted file mode 100755 index 232474dbc0..0000000000 --- a/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/mappers/UserAttributeUriReferenceAttributeStatementMapper.java +++ /dev/null @@ -1,79 +0,0 @@ -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.RealmModel; -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_ATTRIBUTE); - property.setLabel(ProtocolMapperUtils.USER_MODEL_ATTRIBUTE_LABEL); - 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_LABEL); - String attributeValue = user.getAttribute(attributeName); - AttributeStatementHelper.addUriReferenceAttribute(attributeStatement, mappingModel, attributeValue); - - } - public static ProtocolMapperModel createAttributeMapper(String name, - String userAttribute, - String samlAttributeName, - String friendlyName, - boolean consentRequired, String consentText) { - String mapperId = PROVIDER_ID; - return AttributeStatementHelper.createAttributeMapper(name, userAttribute, samlAttributeName, friendlyName, consentRequired, consentText, mapperId); - - } - -} 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 deleted file mode 100755 index 35b3354ba9..0000000000 --- a/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/mappers/UserModelBasicAttributeStatementMapper.java +++ /dev/null @@ -1,79 +0,0 @@ -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.RealmModel; -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_ATTRIBUTE); - property.setLabel(ProtocolMapperUtils.USER_MODEL_PROPERTY_LABEL); - 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_LABEL); - String propertyValue = ProtocolMapperUtils.getUserModelValue(user, propertyName); - AttributeStatementHelper.addBasicAttribute(attributeStatement, mappingModel, propertyValue); - - } - - public static ProtocolMapperModel createAttributeMapper(String name, - String userAttribute, - String samlAttributeName, - String friendlyName, - boolean consentRequired, String consentText) { - String mapperId = PROVIDER_ID; - return AttributeStatementHelper.createAttributeMapper(name, userAttribute, samlAttributeName, friendlyName, consentRequired, consentText, mapperId); - - } -} 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/UserPropertyAttributeStatementMapper.java similarity index 72% rename from saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/mappers/UserModelUriReferenceAttributeStatementMapper.java rename to saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/mappers/UserPropertyAttributeStatementMapper.java index 8037638e46..6294b68000 100755 --- 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/UserPropertyAttributeStatementMapper.java @@ -17,7 +17,7 @@ import java.util.List; * @author Bill Burke * @version $Revision: 1 $ */ -public class UserModelUriReferenceAttributeStatementMapper extends AbstractSAMLProtocolMapper implements SAMLAttributeStatementMapper { +public class UserPropertyAttributeStatementMapper extends AbstractSAMLProtocolMapper implements SAMLAttributeStatementMapper { private static final List configProperties = new ArrayList(); static { @@ -27,11 +27,11 @@ public class UserModelUriReferenceAttributeStatementMapper extends AbstractSAMLP property.setLabel(ProtocolMapperUtils.USER_MODEL_PROPERTY_LABEL); property.setHelpText(ProtocolMapperUtils.USER_MODEL_PROPERTY_HELP_TEXT); configProperties.add(property); - AttributeStatementHelper.addUriReferenceProperties(configProperties); + AttributeStatementHelper.setConfigProperties(configProperties); } - public static final String PROVIDER_ID = "saml-user-property-uri-mapper"; + public static final String PROVIDER_ID = "saml-user-property-mapper"; public List getConfigProperties() { @@ -44,7 +44,7 @@ public class UserModelUriReferenceAttributeStatementMapper extends AbstractSAMLP @Override public String getDisplayType() { - return "User Property URI"; + return "User Property"; } @Override @@ -54,25 +54,24 @@ public class UserModelUriReferenceAttributeStatementMapper extends AbstractSAMLP @Override public String getHelpText() { - return "Map a built in user property to a SAML URI reference attribute type."; + return "Map a built in user property to a SAML 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_LABEL); + String propertyName = mappingModel.getConfig().get(ProtocolMapperUtils.USER_ATTRIBUTE); String propertyValue = ProtocolMapperUtils.getUserModelValue(user, propertyName); - AttributeStatementHelper.addUriReferenceAttribute(attributeStatement, mappingModel, propertyValue); + AttributeStatementHelper.addAttribute(attributeStatement, mappingModel, propertyValue); } - public static ProtocolMapperModel createAttributeMapper(String name, - String userAttribute, - String samlAttributeName, - String friendlyName, + public static ProtocolMapperModel createAttributeMapper(String name, String userAttribute, + String samlAttributeName, String nameFormat, String friendlyName, boolean consentRequired, String consentText) { String mapperId = PROVIDER_ID; - return AttributeStatementHelper.createAttributeMapper(name, userAttribute, samlAttributeName, friendlyName, consentRequired, consentText, mapperId); + return AttributeStatementHelper.createAttributeMapper(name, userAttribute, samlAttributeName, nameFormat, friendlyName, + consentRequired, consentText, mapperId); } } 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 index 719ddff57d..7d75839140 100755 --- 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 @@ -1,6 +1,4 @@ -org.keycloak.protocol.saml.mappers.UserAttributeBasicAttributeStatementMapper -org.keycloak.protocol.saml.mappers.UserModelBasicAttributeStatementMapper -org.keycloak.protocol.saml.mappers.UserAttributeUriReferenceAttributeStatementMapper -org.keycloak.protocol.saml.mappers.UserModelUriReferenceAttributeStatementMapper +org.keycloak.protocol.saml.mappers.UserAttributeStatementMapper +org.keycloak.protocol.saml.mappers.UserPropertyAttributeStatementMapper diff --git a/services/src/main/java/org/keycloak/protocol/ProtocolMapper.java b/services/src/main/java/org/keycloak/protocol/ProtocolMapper.java index ff6e10591c..d986decd59 100755 --- a/services/src/main/java/org/keycloak/protocol/ProtocolMapper.java +++ b/services/src/main/java/org/keycloak/protocol/ProtocolMapper.java @@ -18,12 +18,13 @@ public interface ProtocolMapper extends Provider, ProviderFactoryBill Burke - */ -public class AdapterTest { - - public static PublicKey realmPublicKey; - @ClassRule - public static AbstractKeycloakRule keycloakRule = new AbstractKeycloakRule() { - @Override - protected void configure(KeycloakSession session, RealmManager manager, RealmModel adminRealm) { - RealmModel realm = AdapterTestStrategy.baseAdapterTestInitialization(session, manager, adminRealm, getClass()); - realmPublicKey = realm.getPublicKey(); - - URL url = getClass().getResource("/adapter-test/cust-app-keycloak.json"); - deployApplication("customer-portal", "/customer-portal", CustomerServlet.class, url.getPath(), "user"); - url = getClass().getResource("/adapter-test/secure-portal-keycloak.json"); - deployApplication("secure-portal", "/secure-portal", CallAuthenticatedServlet.class, url.getPath(), "user", false); - url = getClass().getResource("/adapter-test/customer-db-keycloak.json"); - deployApplication("customer-db", "/customer-db", CustomerDatabaseServlet.class, url.getPath(), "user"); - url = getClass().getResource("/adapter-test/product-keycloak.json"); - deployApplication("product-portal", "/product-portal", ProductServlet.class, url.getPath(), "user"); - - // Test that replacing system properties works for adapters - System.setProperty("app.server.base.url", "http://localhost:8081"); - System.setProperty("my.host.name", "localhost"); - url = getClass().getResource("/adapter-test/session-keycloak.json"); - deployApplication("session-portal", "/session-portal", SessionServlet.class, url.getPath(), "user"); - url = getClass().getResource("/adapter-test/input-keycloak.json"); - deployApplication("input-portal", "/input-portal", InputServlet.class, url.getPath(), "user", true, null, "/secured/*"); - } - }; - - @Rule - public AdapterTestStrategy testStrategy = new AdapterTestStrategy("http://localhost:8081/auth", "http://localhost:8081", keycloakRule); - - @Test - public void testLoginSSOAndLogout() throws Exception { - testStrategy.testLoginSSOAndLogout(); - } - - @Test - public void testSavedPostRequest() throws Exception { - testStrategy.testSavedPostRequest(); - } - - @Test - public void testServletRequestLogout() throws Exception { - testStrategy.testServletRequestLogout(); - } - - @Test - public void testLoginSSOIdle() throws Exception { - testStrategy.testLoginSSOIdle(); - - } - - @Test - public void testLoginSSOIdleRemoveExpiredUserSessions() throws Exception { - testStrategy.testLoginSSOIdleRemoveExpiredUserSessions(); - } - - @Test - public void testLoginSSOMax() throws Exception { - testStrategy.testLoginSSOMax(); - } - - /** - * KEYCLOAK-518 - * @throws Exception - */ - @Test - public void testNullBearerToken() throws Exception { - testStrategy.testNullBearerToken(); - } - - /** - * KEYCLOAK-518 - * @throws Exception - */ - @Test - public void testBadUser() throws Exception { - testStrategy.testBadUser(); - } - - @Test - public void testVersion() throws Exception { - testStrategy.testVersion(); - } - - @Test - public void testAuthenticated() throws Exception { - testStrategy.testAuthenticated(); - } - - /** - * KEYCLOAK-732 - * - * @throws Throwable - */ - @Test - public void testSingleSessionInvalidated() throws Throwable { - testStrategy.testSingleSessionInvalidated(); - } - - /** - * KEYCLOAK-741 - */ - @Test - public void testSessionInvalidatedAfterFailedRefresh() throws Throwable { - testStrategy.testSessionInvalidatedAfterFailedRefresh(); - - } - - /** - * KEYCLOAK-942 - */ - @Test - public void testAdminApplicationLogout() throws Throwable { - testStrategy.testAdminApplicationLogout(); - } - -} +/* + * JBoss, Home of Professional Open Source. + * Copyright 2012, Red Hat, Inc., and individual contributors + * as indicated by the @author tags. See the copyright.txt file in the + * distribution for a full listing of individual contributors. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ +package org.keycloak.testsuite.adapter; + +import org.junit.ClassRule; +import org.junit.Rule; +import org.junit.Test; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.RealmModel; +import org.keycloak.services.managers.RealmManager; +import org.keycloak.testsuite.rule.AbstractKeycloakRule; + +import java.net.URL; +import java.security.PublicKey; + +/** + * Tests Undertow Adapter + * + * @author Bill Burke + */ +public class AdapterTest { + + public static PublicKey realmPublicKey; + @ClassRule + public static AbstractKeycloakRule keycloakRule = new AbstractKeycloakRule() { + @Override + protected void configure(KeycloakSession session, RealmManager manager, RealmModel adminRealm) { + RealmModel realm = AdapterTestStrategy.baseAdapterTestInitialization(session, manager, adminRealm, getClass()); + realmPublicKey = realm.getPublicKey(); + + URL url = getClass().getResource("/adapter-test/cust-app-keycloak.json"); + deployApplication("customer-portal", "/customer-portal", CustomerServlet.class, url.getPath(), "user"); + url = getClass().getResource("/adapter-test/secure-portal-keycloak.json"); + deployApplication("secure-portal", "/secure-portal", CallAuthenticatedServlet.class, url.getPath(), "user", false); + url = getClass().getResource("/adapter-test/customer-db-keycloak.json"); + deployApplication("customer-db", "/customer-db", CustomerDatabaseServlet.class, url.getPath(), "user"); + url = getClass().getResource("/adapter-test/product-keycloak.json"); + deployApplication("product-portal", "/product-portal", ProductServlet.class, url.getPath(), "user"); + + // Test that replacing system properties works for adapters + System.setProperty("app.server.base.url", "http://localhost:8081"); + System.setProperty("my.host.name", "localhost"); + url = getClass().getResource("/adapter-test/session-keycloak.json"); + deployApplication("session-portal", "/session-portal", SessionServlet.class, url.getPath(), "user"); + url = getClass().getResource("/adapter-test/input-keycloak.json"); + deployApplication("input-portal", "/input-portal", InputServlet.class, url.getPath(), "user", true, null, "/secured/*"); + } + }; + + @Rule + public AdapterTestStrategy testStrategy = new AdapterTestStrategy("http://localhost:8081/auth", "http://localhost:8081", keycloakRule); + + @Test + public void testLoginSSOAndLogout() throws Exception { + testStrategy.testLoginSSOAndLogout(); + } + + @Test + public void testSavedPostRequest() throws Exception { + testStrategy.testSavedPostRequest(); + } + + @Test + public void testServletRequestLogout() throws Exception { + testStrategy.testServletRequestLogout(); + } + + @Test + public void testLoginSSOIdle() throws Exception { + testStrategy.testLoginSSOIdle(); + + } + + @Test + public void testLoginSSOIdleRemoveExpiredUserSessions() throws Exception { + testStrategy.testLoginSSOIdleRemoveExpiredUserSessions(); + } + + @Test + public void testLoginSSOMax() throws Exception { + testStrategy.testLoginSSOMax(); + } + + /** + * KEYCLOAK-518 + * @throws Exception + */ + @Test + public void testNullBearerToken() throws Exception { + testStrategy.testNullBearerToken(); + } + + /** + * KEYCLOAK-518 + * @throws Exception + */ + @Test + public void testBadUser() throws Exception { + testStrategy.testBadUser(); + } + + @Test + public void testVersion() throws Exception { + testStrategy.testVersion(); + } + + @Test + public void testAuthenticated() throws Exception { + testStrategy.testAuthenticated(); + } + + /** + * KEYCLOAK-732 + * + * @throws Throwable + */ + @Test + public void testSingleSessionInvalidated() throws Throwable { + testStrategy.testSingleSessionInvalidated(); + } + + /** + * KEYCLOAK-741 + */ + @Test + public void testSessionInvalidatedAfterFailedRefresh() throws Throwable { + testStrategy.testSessionInvalidatedAfterFailedRefresh(); + + } + + /** + * KEYCLOAK-942 + */ + @Test + public void testAdminApplicationLogout() throws Throwable { + testStrategy.testAdminApplicationLogout(); + } + +} diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/rule/AbstractKeycloakRule.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/rule/AbstractKeycloakRule.java index 169ff3a88e..d1e37b3e17 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/rule/AbstractKeycloakRule.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/rule/AbstractKeycloakRule.java @@ -128,7 +128,7 @@ public abstract class AbstractKeycloakRule extends ExternalResource { } - private DeploymentInfo createDeploymentInfo(String name, String contextPath, Class servletClass) { + public DeploymentInfo createDeploymentInfo(String name, String contextPath, Class servletClass) { DeploymentInfo deploymentInfo = new DeploymentInfo(); deploymentInfo.setClassLoader(getClass().getClassLoader()); deploymentInfo.setDeploymentName(name); 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 db33eb1340..8f8f2b492f 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 @@ -21,7 +21,19 @@ import org.keycloak.testsuite.pages.LoginPage; import org.keycloak.testsuite.rule.WebResource; import org.keycloak.testsuite.rule.WebRule; import org.openqa.selenium.WebDriver; +import org.picketlink.common.constants.JBossSAMLURIConstants; +import org.picketlink.identity.federation.api.saml.v2.response.SAML2Response; +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.assertion.AttributeType; +import org.picketlink.identity.federation.saml.v2.protocol.ResponseType; +import org.picketlink.identity.federation.web.util.PostBindingUtil; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; import javax.ws.rs.client.Client; import javax.ws.rs.client.ClientBuilder; import javax.ws.rs.client.ClientRequestContext; @@ -32,6 +44,7 @@ import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import javax.ws.rs.core.UriBuilder; +import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; @@ -54,11 +67,15 @@ public class SamlBindingTest { initializeSamlSecuredWar("/saml/signed-post-persistent", "/sales-post-sig-persistent", "post-sig-persistent.war", classLoader); initializeSamlSecuredWar("/saml/signed-metadata", "/sales-metadata", "post-metadata.war", classLoader); initializeSamlSecuredWar("/saml/signed-get", "/employee-sig", "employee-sig.war", classLoader); + //initializeSamlSecuredWar("/saml/simple-get", "/employee", "employee.war", classLoader); initializeSamlSecuredWar("/saml/signed-front-get", "/employee-sig-front", "employee-sig-front.war", classLoader); initializeSamlSecuredWar("/saml/bad-client-signed-post", "/bad-client-sales-post-sig", "bad-client-post-sig.war", classLoader); initializeSamlSecuredWar("/saml/bad-realm-signed-post", "/bad-realm-sales-post-sig", "bad-realm-post-sig.war", classLoader); initializeSamlSecuredWar("/saml/encrypted-post", "/sales-post-enc", "post-enc.war", classLoader); uploadSP(); + server.getServer().deploy(createDeploymentInfo("employee.war", "/employee", SamlSPFacade.class)); + + } @@ -68,6 +85,30 @@ public class SamlBindingTest { } }; + public static class SamlSPFacade extends HttpServlet { + public static String samlResponse; + + @Override + protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { + handler(req, resp); + } + + @Override + protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { + handler(req, resp); + } + + private void handler(HttpServletRequest req, HttpServletResponse resp) { + System.out.println("********* HERE ******"); + if (req.getParameterMap().isEmpty()) { + resp.setStatus(302); + resp.setHeader("Location", "http://localhost:8081/auth/realms/demo/protocol/saml?SAMLRequest=jVJbT8IwFP4rS99HuwluNIwEIUYSLwugD76Y2h2kSdfOng7l31uGRn0ATfrQ9HznfJfTEYpaN3zS%2Bo1ZwGsL6KP3WhvkXaEgrTPcClTIjagBuZd8Obm55mmP8cZZb6XV5NByGiwQwXllDYkmX9epNdjW4JbgtkrC%2FeK6IBvvG06ptlLojUXPc5YnFOpG2x0AJdEsaFRG7PuPoUWwQx0IXSOtoLb0SynduyLRpXUSOs8FWQuNQKL5rCDz2VO%2FymEgIY2zlJ3H%2FSx9jkU%2BzOK0ys8yNmSSsUEAYxnsqC18tyO2MDfohfEFSVkyiNlZzM5XacrDSbJePug%2Fkqj8FHKhTKXMy%2BnIng8g5FerVRmXd8sViR7AYec8AMh4tPfDO3L3Y2%2F%2F3cT4j7BH9Mf8A1nDb8PA%2Bay0WsldNNHavk1D1D5k4V0LXbi18MclJL2ke1FVvO6gvDXYgFRrBRWh4wPp7z85%2FgA%3D"); + return; + } + samlResponse = req.getParameter("SAMLResponse"); + } + } + @Rule public WebRule webRule = new WebRule(this); @WebResource @@ -151,6 +192,52 @@ public class SamlBindingTest { checkLoggedOut("http://localhost:8081/sales-post-sig-email/"); } + + + @Test + public void testAttributes() throws Exception { + // this test has a hardcoded SAMLRequest and we hack a SP face servlet to get the SAMLResponse so we can look + // at the assertions sent. This is because Picketlink, AFAICT, does not give you any way to get access to + // the assertion. + SamlSPFacade.samlResponse = null; + driver.navigate().to("http://localhost:8081/employee/"); + Assert.assertTrue(driver.getCurrentUrl().startsWith("http://localhost:8081/auth/realms/demo/protocol/saml")); + System.out.println(driver.getCurrentUrl()); + loginPage.login("bburke", "password"); + Assert.assertEquals(driver.getCurrentUrl(), "http://localhost:8081/employee/"); + Assert.assertNotNull(SamlSPFacade.samlResponse); + SAML2Response saml2Response = new SAML2Response(); + byte[] samlResponse = PostBindingUtil.base64Decode(SamlSPFacade.samlResponse); + ResponseType rt = saml2Response.getResponseType(new ByteArrayInputStream(samlResponse)); + Assert.assertTrue(rt.getAssertions().size() == 1); + AssertionType assertion = rt.getAssertions().get(0).getAssertion(); + + // test attributes + + boolean email = false; + boolean phone = false; + for (AttributeStatementType statement : assertion.getAttributeStatements()) { + for (AttributeStatementType.ASTChoiceType choice : statement.getAttributes()) { + AttributeType attr = choice.getAttribute(); + if (X500SAMLProfileConstants.EMAIL.getFriendlyName().equals(attr.getFriendlyName())) { + Assert.assertEquals(X500SAMLProfileConstants.EMAIL.get(), attr.getName()); + Assert.assertEquals(JBossSAMLURIConstants.ATTRIBUTE_FORMAT_URI.get(), attr.getNameFormat()); + Assert.assertEquals(attr.getAttributeValue().get(0), "bburke@redhat.com"); + email = true; + } else if (attr.getName().equals("phone")) { + Assert.assertEquals(JBossSAMLURIConstants.ATTRIBUTE_FORMAT_BASIC.get(), attr.getNameFormat()); + Assert.assertEquals(attr.getAttributeValue().get(0), "617"); + phone = true; + } + } + + } + + Assert.assertTrue(email); + Assert.assertTrue(phone); + + } + @Test public void testRedirectSignedLoginLogout() { driver.navigate().to("http://localhost:8081/employee-sig/"); diff --git a/testsuite/integration/src/test/resources/saml/simple-get/WEB-INF/picketlink.xml b/testsuite/integration/src/test/resources/saml/simple-get/WEB-INF/picketlink.xml index 1d170ee3a1..7636260689 100755 --- a/testsuite/integration/src/test/resources/saml/simple-get/WEB-INF/picketlink.xml +++ b/testsuite/integration/src/test/resources/saml/simple-get/WEB-INF/picketlink.xml @@ -12,8 +12,6 @@ class="org.picketlink.identity.federation.web.handlers.saml2.SAML2AuthenticationHandler">