From 3ceb818ee558f733847751f54f5cf2f9a489adc7 Mon Sep 17 00:00:00 2001 From: Bill Burke Date: Mon, 20 Apr 2015 11:54:39 -0400 Subject: [PATCH 1/2] saml broker role mapper --- .../broker/saml/mappers/RoleMapper.java | 167 ++++++++++++++++++ ...oak.broker.provider.IdentityProviderMapper | 1 + .../realm-identity-provider-saml.html | 1 + .../testsuite/account/AccountTest.java | 2 +- 4 files changed, 170 insertions(+), 1 deletion(-) create mode 100755 broker/saml/src/main/java/org/keycloak/broker/saml/mappers/RoleMapper.java create mode 100755 broker/saml/src/main/resources/META-INF/services/org.keycloak.broker.provider.IdentityProviderMapper diff --git a/broker/saml/src/main/java/org/keycloak/broker/saml/mappers/RoleMapper.java b/broker/saml/src/main/java/org/keycloak/broker/saml/mappers/RoleMapper.java new file mode 100755 index 0000000000..3c6d655dc7 --- /dev/null +++ b/broker/saml/src/main/java/org/keycloak/broker/saml/mappers/RoleMapper.java @@ -0,0 +1,167 @@ +package org.keycloak.broker.saml.mappers; + +import org.keycloak.broker.provider.AbstractIdentityProviderMapper; +import org.keycloak.broker.provider.BrokeredIdentityContext; +import org.keycloak.broker.provider.IdentityBrokerException; +import org.keycloak.broker.saml.SAMLEndpoint; +import org.keycloak.broker.saml.SAMLIdentityProviderFactory; +import org.keycloak.dom.saml.v2.assertion.AssertionType; +import org.keycloak.dom.saml.v2.assertion.AttributeStatementType; +import org.keycloak.dom.saml.v2.assertion.AttributeType; +import org.keycloak.models.ClientModel; +import org.keycloak.models.IdentityProviderMapperModel; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.RealmModel; +import org.keycloak.models.RoleModel; +import org.keycloak.models.UserModel; +import org.keycloak.provider.ProviderConfigProperty; +import org.keycloak.representations.JsonWebToken; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +/** + * @author Bill Burke + * @version $Revision: 1 $ + */ +public class RoleMapper extends AbstractIdentityProviderMapper { + + public static final String[] COMPATIBLE_PROVIDERS = {SAMLIdentityProviderFactory.PROVIDER_ID}; + + private static final List configProperties = new ArrayList(); + + public static final String ROLE = "role"; + public static final String ATTRIBUTE_NAME = "attribute.name"; + public static final String ATTRIBUTE_FRIENDLY_NAME = "attribute.friendly.name"; + public static final String ATTRIBUTE_VALUE = "attribute.value"; + + static { + ProviderConfigProperty property; + property = new ProviderConfigProperty(); + property.setName(ATTRIBUTE_NAME); + property.setLabel("Attribute Name"); + property.setHelpText("Name of attribute to search for in assertion. You can leave this blank and specify a friendly name instead."); + property.setType(ProviderConfigProperty.STRING_TYPE); + configProperties.add(property); + property = new ProviderConfigProperty(); + property.setName(ATTRIBUTE_FRIENDLY_NAME); + property.setLabel("Attribute Name"); + property.setHelpText("Friendly name of attribute to search for in assertion. You can leave this blank and specify a name instead."); + property.setType(ProviderConfigProperty.STRING_TYPE); + configProperties.add(property); + property = new ProviderConfigProperty(); + property.setName(ATTRIBUTE_VALUE); + property.setLabel("Attribute Value"); + property.setHelpText("Value the attribute must have. If the attribute is a list, then the value must be contained in the list."); + property.setType(ProviderConfigProperty.STRING_TYPE); + configProperties.add(property); + property = new ProviderConfigProperty(); + property.setName(ROLE); + property.setLabel("Role"); + property.setHelpText("Role to grant to user. To reference an application role the syntax is appname.approle, i.e. myapp.myrole"); + property.setType(ProviderConfigProperty.STRING_TYPE); + configProperties.add(property); + } + + public static final String PROVIDER_ID = "saml-role-idp-mapper"; + + public static String[] parseRole(String role) { + int scopeIndex = role.indexOf('.'); + if (scopeIndex > -1) { + String appName = role.substring(0, scopeIndex); + role = role.substring(scopeIndex + 1); + String[] rtn = {appName, role}; + return rtn; + } else { + String[] rtn = {null, role}; + return rtn; + + } + } + + @Override + public List getConfigProperties() { + return configProperties; + } + + @Override + public String getId() { + return PROVIDER_ID; + } + + @Override + public String[] getCompatibleProviders() { + return COMPATIBLE_PROVIDERS; + } + + @Override + public String getDisplayCategory() { + return "Role Mapper"; + } + + @Override + public String getDisplayType() { + return "Role Mapper"; + } + + @Override + public void importNewUser(KeycloakSession session, RealmModel realm, UserModel user, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) { + String roleName = mapperModel.getConfig().get(ROLE); + if (isAttributePresent(mapperModel, context)) { + RoleModel role = getRoleFromString(realm, roleName); + if (role == null) throw new IdentityBrokerException("Unable to find role: " + roleName); + user.grantRole(role); + } + } + + protected RoleModel getRoleFromString(RealmModel realm, String roleName) { + String[] parsedRole = parseRole(roleName); + RoleModel role = null; + if (parsedRole[0] == null) { + role = realm.getRole(parsedRole[1]); + } else { + ClientModel client = realm.getClientByClientId(parsedRole[0]); + role = client.getRole(parsedRole[1]); + } + return role; + } + + protected boolean isAttributePresent(IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) { + String name = mapperModel.getConfig().get(ATTRIBUTE_NAME); + if (name != null && name.trim().equals("")) name = null; + String friendly = mapperModel.getConfig().get(ATTRIBUTE_FRIENDLY_NAME); + if (friendly != null && friendly.trim().equals("")) friendly = null; + String desiredValue = mapperModel.getConfig().get(ATTRIBUTE_VALUE); + AssertionType assertion = (AssertionType)context.getContextData().get(SAMLEndpoint.SAML_ASSERTION); + for (AttributeStatementType statement : assertion.getAttributeStatements()) { + for (AttributeStatementType.ASTChoiceType choice : statement.getAttributes()) { + AttributeType attr = choice.getAttribute(); + if (name != null && !name.equals(attr.getName())) continue; + if (friendly != null && !name.equals(attr.getFriendlyName())) continue; + for (Object val : attr.getAttributeValue()) { + if (val.equals(desiredValue)) return true; + } + } + } + return false; + } + + + @Override + public void updateBrokeredUser(KeycloakSession session, RealmModel realm, UserModel user, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) { + String roleName = mapperModel.getConfig().get(ROLE); + if (!isAttributePresent(mapperModel, context)) { + RoleModel role = getRoleFromString(realm, roleName); + if (role == null) throw new IdentityBrokerException("Unable to find role: " + roleName); + user.deleteRoleMapping(role); + } + + } + + @Override + public String getHelpText() { + return "If a claim exists, grant the user the specified realm or application role."; + } + +} diff --git a/broker/saml/src/main/resources/META-INF/services/org.keycloak.broker.provider.IdentityProviderMapper b/broker/saml/src/main/resources/META-INF/services/org.keycloak.broker.provider.IdentityProviderMapper new file mode 100755 index 0000000000..af14a9caf0 --- /dev/null +++ b/broker/saml/src/main/resources/META-INF/services/org.keycloak.broker.provider.IdentityProviderMapper @@ -0,0 +1 @@ +org.keycloak.broker.saml.mappers.RoleMapper \ No newline at end of file diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-saml.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-saml.html index 3e151fd75d..ef62c07612 100755 --- a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-saml.html +++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-saml.html @@ -163,6 +163,7 @@
+ Mappers Export 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 1d99e1f2fe..af202b35f8 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,7 +157,7 @@ public class AccountTest { }); } - //@Test + @Test public void ideTesting() throws Exception { Thread.sleep(100000000); } From d30b81144e666cc9f7d2603a3b9a1096f14d13ec Mon Sep 17 00:00:00 2001 From: Bill Burke Date: Mon, 20 Apr 2015 12:00:22 -0400 Subject: [PATCH 2/2] ignore test --- .../test/java/org/keycloak/testsuite/account/AccountTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 af202b35f8..1d99e1f2fe 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,7 +157,7 @@ public class AccountTest { }); } - @Test + //@Test public void ideTesting() throws Exception { Thread.sleep(100000000); }