Merge pull request #1029 from patriot1burke/master

saml role list mapper
This commit is contained in:
Bill Burke 2015-03-07 21:38:06 -05:00
commit 7bfd55c0de
14 changed files with 344 additions and 78 deletions

View file

@ -19,10 +19,8 @@ import org.picketlink.identity.federation.saml.v2.assertion.AuthnStatementType;
import org.picketlink.identity.federation.saml.v2.protocol.ResponseType; import org.picketlink.identity.federation.saml.v2.protocol.ResponseType;
import org.w3c.dom.Document; import org.w3c.dom.Document;
import java.util.HashMap;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Map;
import static org.picketlink.common.util.StringUtil.isNotNull; import static org.picketlink.common.util.StringUtil.isNotNull;
@ -36,7 +34,6 @@ import static org.picketlink.common.util.StringUtil.isNotNull;
public class SALM2LoginResponseBuilder { public class SALM2LoginResponseBuilder {
protected static final PicketLinkLogger logger = PicketLinkLoggerFactory.getLogger(); protected static final PicketLinkLogger logger = PicketLinkLoggerFactory.getLogger();
protected List<String> roles = new LinkedList<String>();
protected String destination; protected String destination;
protected String issuer; protected String issuer;
protected String nameId; protected String nameId;
@ -68,18 +65,6 @@ public class SALM2LoginResponseBuilder {
return this; return this;
} }
public SALM2LoginResponseBuilder roles(List<String> roles) {
this.roles = roles;
return this;
}
public SALM2LoginResponseBuilder roles(String... roles) {
for (String role : roles) {
this.roles.add(role);
}
return this;
}
public SALM2LoginResponseBuilder authMethod(String authMethod) { public SALM2LoginResponseBuilder authMethod(String authMethod) {
this.authMethod = authMethod; this.authMethod = authMethod;
return this; return this;
@ -155,12 +140,6 @@ public class SALM2LoginResponseBuilder {
assertion.addStatement(authnStatement); assertion.addStatement(authnStatement);
} }
if (roles != null && !roles.isEmpty()) {
AttributeStatementType attrStatement = StatementUtil.createAttributeStatementForRoles(roles, multiValuedRoles);
assertion.addStatement(attrStatement);
}
return responseType; return responseType;
} }

View file

@ -19,6 +19,7 @@ import org.keycloak.protocol.LoginProtocol;
import org.keycloak.protocol.ProtocolMapper; import org.keycloak.protocol.ProtocolMapper;
import org.keycloak.protocol.saml.mappers.SAMLAttributeStatementMapper; import org.keycloak.protocol.saml.mappers.SAMLAttributeStatementMapper;
import org.keycloak.protocol.saml.mappers.SAMLLoginResponseMapper; import org.keycloak.protocol.saml.mappers.SAMLLoginResponseMapper;
import org.keycloak.protocol.saml.mappers.SAMLRoleListMapper;
import org.keycloak.services.managers.ClientSessionCode; import org.keycloak.services.managers.ClientSessionCode;
import org.keycloak.services.managers.ResourceAdminManager; import org.keycloak.services.managers.ResourceAdminManager;
import org.keycloak.services.resources.RealmsResource; import org.keycloak.services.resources.RealmsResource;
@ -29,7 +30,6 @@ import org.picketlink.common.constants.JBossSAMLURIConstants;
import org.picketlink.common.exceptions.ConfigurationException; import org.picketlink.common.exceptions.ConfigurationException;
import org.picketlink.common.exceptions.ParsingException; import org.picketlink.common.exceptions.ParsingException;
import org.picketlink.common.exceptions.ProcessingException; 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.AssertionType;
import org.picketlink.identity.federation.saml.v2.assertion.AttributeStatementType; import org.picketlink.identity.federation.saml.v2.assertion.AttributeStatementType;
import org.picketlink.identity.federation.saml.v2.protocol.ResponseType; import org.picketlink.identity.federation.saml.v2.protocol.ResponseType;
@ -41,6 +41,8 @@ import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriInfo; import javax.ws.rs.core.UriInfo;
import java.io.IOException; import java.io.IOException;
import java.security.PublicKey; import java.security.PublicKey;
import java.util.LinkedList;
import java.util.List;
import java.util.Set; import java.util.Set;
import java.util.UUID; import java.util.UUID;
@ -258,26 +260,38 @@ public class SamlProtocol implements LoginProtocol {
.requestIssuer(clientSession.getClient().getClientId()) .requestIssuer(clientSession.getClient().getClientId())
.nameIdentifier(nameIdFormat, nameId) .nameIdentifier(nameIdFormat, nameId)
.authMethod(JBossSAMLURIConstants.AC_UNSPECIFIED.get()); .authMethod(JBossSAMLURIConstants.AC_UNSPECIFIED.get());
initClaims(builder, clientSession.getClient(), userSession.getUser());
if (clientSession.getRoles() != null) {
if (multivaluedRoles(client)) {
builder.multiValuedRoles(true);
}
for (String roleId : clientSession.getRoles()) {
// todo need a role mapping
RoleModel roleModel = clientSession.getRealm().getRoleById(roleId);
builder.roles(roleModel.getName());
}
}
if (!includeAuthnStatement(client)) { if (!includeAuthnStatement(client)) {
builder.disableAuthnStatement(true); builder.disableAuthnStatement(true);
} }
List<ProtocolMapperProcessor<SAMLAttributeStatementMapper>> attributeStatementMappers = new LinkedList<>();
List<ProtocolMapperProcessor<SAMLLoginResponseMapper>> loginResponseMappers = new LinkedList<>();
ProtocolMapperProcessor<SAMLRoleListMapper> roleListMapper = null;
Set<ProtocolMapperModel> mappings = client.getProtocolMappers();
for (ProtocolMapperModel mapping : mappings) {
if (!mapping.getProtocol().equals(SamlProtocol.LOGIN_PROTOCOL)) continue;
ProtocolMapper mapper = (ProtocolMapper)session.getKeycloakSessionFactory().getProviderFactory(ProtocolMapper.class, mapping.getProtocolMapper());
if (mapper == null) continue;
if (mapper instanceof SAMLAttributeStatementMapper) {
attributeStatementMappers.add(new ProtocolMapperProcessor<SAMLAttributeStatementMapper>((SAMLAttributeStatementMapper)mapper, mapping));
}
if (mapper instanceof SAMLLoginResponseMapper) {
loginResponseMappers.add(new ProtocolMapperProcessor<SAMLLoginResponseMapper>((SAMLLoginResponseMapper)mapper, mapping));
}
if (mapper instanceof SAMLRoleListMapper) {
roleListMapper = new ProtocolMapperProcessor<SAMLRoleListMapper>((SAMLRoleListMapper)mapper, mapping);
}
}
Document samlDocument = null; Document samlDocument = null;
try { try {
ResponseType samlModel = builder.buildModel(); ResponseType samlModel = builder.buildModel();
transformAttributeStatement(session, samlModel, client, userSession, clientSession); transformAttributeStatement(attributeStatementMappers, samlModel, session, userSession, clientSession);
samlModel = transformLoginResponse(session, samlModel, client, userSession, clientSession); populateRoles(roleListMapper, samlModel, session, userSession, clientSession);
samlModel = transformLoginResponse(loginResponseMappers, samlModel, session, userSession, clientSession);
samlDocument = builder.buildDocument(samlModel); samlDocument = builder.buildDocument(samlModel);
} catch (Exception e) { } catch (Exception e) {
logger.error("failed", e); logger.error("failed", e);
@ -348,54 +362,50 @@ public class SamlProtocol implements LoginProtocol {
return "true".equals(client.getAttribute(SAML_ENCRYPT)); return "true".equals(client.getAttribute(SAML_ENCRYPT));
} }
public void initClaims(SALM2LoginResponseBuilder builder, ClientModel model, UserModel user) { public static class ProtocolMapperProcessor<T> {
if (ClaimMask.hasEmail(model.getAllowedClaimsMask())) { final public T mapper;
//builder.attribute(X500SAMLProfileConstants.EMAIL_ADDRESS.getFriendlyName(), user.getEmail()); final public ProtocolMapperModel model;
}
if (ClaimMask.hasName(model.getAllowedClaimsMask())) { public ProtocolMapperProcessor(T mapper, ProtocolMapperModel model) {
//builder.attribute(X500SAMLProfileConstants.GIVEN_NAME.getFriendlyName(), user.getFirstName()); this.mapper = mapper;
//builder.attribute(X500SAMLProfileConstants.SURNAME.getFriendlyName(), user.getLastName()); this.model = model;
}
if (ClaimMask.hasUsername(model.getAllowedClaimsMask())) {
//builder.attribute(X500SAMLProfileConstants.USERID.getFriendlyName(), user.getUsername());
} }
} }
public ResponseType transformLoginResponse(KeycloakSession session, ResponseType response, ClientModel client, public void transformAttributeStatement(List<ProtocolMapperProcessor<SAMLAttributeStatementMapper>> attributeStatementMappers,
UserSessionModel userSession, ClientSessionModel clientSession) { ResponseType response,
Set<ProtocolMapperModel> mappings = client.getProtocolMappers(); KeycloakSession session,
KeycloakSessionFactory sessionFactory = session.getKeycloakSessionFactory(); UserSessionModel userSession, ClientSessionModel clientSession) {
for (ProtocolMapperModel mapping : mappings) { AssertionType assertion = response.getAssertions().get(0).getAssertion();
if (!mapping.getProtocol().equals(SamlProtocol.LOGIN_PROTOCOL)) continue; AttributeStatementType attributeStatement = new AttributeStatementType();
assertion.addStatement(attributeStatement);
ProtocolMapper mapper = (ProtocolMapper)sessionFactory.getProviderFactory(ProtocolMapper.class, mapping.getProtocolMapper()); for (ProtocolMapperProcessor<SAMLAttributeStatementMapper> processor : attributeStatementMappers) {
if (mapper == null || !(mapper instanceof SAMLLoginResponseMapper)) continue; processor.mapper.transformAttributeStatement(attributeStatement, processor.model, session, userSession, clientSession);
response = ((SAMLLoginResponseMapper)mapper).transformLoginResponse(response, mapping, session, userSession, clientSession); }
}
public ResponseType transformLoginResponse(List<ProtocolMapperProcessor<SAMLLoginResponseMapper>> mappers,
ResponseType response,
KeycloakSession session,
UserSessionModel userSession, ClientSessionModel clientSession) {
for (ProtocolMapperProcessor<SAMLLoginResponseMapper> processor : mappers) {
response = processor.mapper.transformLoginResponse(response, processor.model, session, userSession, clientSession);
} }
return response; return response;
} }
public void transformAttributeStatement(KeycloakSession session, ResponseType response, ClientModel client,
UserSessionModel userSession, ClientSessionModel clientSession) { public void populateRoles(ProtocolMapperProcessor<SAMLRoleListMapper> roleListMapper,
AttributeStatementType attributeStatement = new AttributeStatementType(); ResponseType response,
KeycloakSession session,
UserSessionModel userSession, ClientSessionModel clientSession) {
if (roleListMapper == null) return;
AssertionType assertion = response.getAssertions().get(0).getAssertion(); AssertionType assertion = response.getAssertions().get(0).getAssertion();
AttributeStatementType attributeStatement = new AttributeStatementType();
assertion.addStatement(attributeStatement); assertion.addStatement(attributeStatement);
Set<ProtocolMapperModel> mappings = client.getProtocolMappers(); roleListMapper.mapper.mapRoles(attributeStatement, roleListMapper.model, session, userSession, clientSession);
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);
}
} }
@Override @Override
public Response consentDenied(ClientSessionModel clientSession) { public Response consentDenied(ClientSessionModel clientSession) {
return getErrorResponse(clientSession, JBossSAMLURIConstants.STATUS_REQUEST_DENIED.get()); return getErrorResponse(clientSession, JBossSAMLURIConstants.STATUS_REQUEST_DENIED.get());

View file

@ -8,6 +8,8 @@ import org.keycloak.models.ProtocolMapperModel;
import org.keycloak.models.RealmModel; import org.keycloak.models.RealmModel;
import org.keycloak.protocol.AbstractLoginProtocolFactory; import org.keycloak.protocol.AbstractLoginProtocolFactory;
import org.keycloak.protocol.LoginProtocol; import org.keycloak.protocol.LoginProtocol;
import org.keycloak.protocol.saml.mappers.AttributeStatementHelper;
import org.keycloak.protocol.saml.mappers.SAMLBasicRoleListMapper;
import org.keycloak.protocol.saml.mappers.UserPropertyAttributeStatementMapper; import org.keycloak.protocol.saml.mappers.UserPropertyAttributeStatementMapper;
import org.keycloak.services.managers.AuthenticationManager; import org.keycloak.services.managers.AuthenticationManager;
import org.picketlink.common.constants.JBossSAMLURIConstants; import org.picketlink.common.constants.JBossSAMLURIConstants;
@ -75,6 +77,9 @@ public class SamlProtocolFactory extends AbstractLoginProtocolFactory {
X500SAMLProfileConstants.SURNAME.getFriendlyName(), X500SAMLProfileConstants.SURNAME.getFriendlyName(),
true, "family name"); true, "family name");
builtins.add(model); builtins.add(model);
model = SAMLBasicRoleListMapper.create("role list", "Role", AttributeStatementHelper.BASIC, null, false);
builtins.add(model);
defaultBuiltins.add(model);
} }

View file

@ -1,6 +1,5 @@
package org.keycloak.protocol.saml; package org.keycloak.protocol.saml;
import java.security.NoSuchAlgorithmException;
import java.security.Signature; import java.security.Signature;
/** /**

View file

@ -4,7 +4,6 @@ import org.keycloak.Config;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory; import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.protocol.ProtocolMapper; import org.keycloak.protocol.ProtocolMapper;
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
import org.keycloak.protocol.saml.SamlProtocol; import org.keycloak.protocol.saml.SamlProtocol;
/** /**

View file

@ -1,7 +1,6 @@
package org.keycloak.protocol.saml.mappers; package org.keycloak.protocol.saml.mappers;
import org.keycloak.models.ProtocolMapperModel; import org.keycloak.models.ProtocolMapperModel;
import org.keycloak.models.RealmModel;
import org.keycloak.protocol.ProtocolMapper; import org.keycloak.protocol.ProtocolMapper;
import org.keycloak.protocol.ProtocolMapperUtils; import org.keycloak.protocol.ProtocolMapperUtils;
import org.keycloak.protocol.saml.SamlProtocol; import org.keycloak.protocol.saml.SamlProtocol;
@ -31,6 +30,12 @@ public class AttributeStatementHelper {
public static void addAttribute(AttributeStatementType attributeStatement, ProtocolMapperModel mappingModel, public static void addAttribute(AttributeStatementType attributeStatement, ProtocolMapperModel mappingModel,
String attributeValue) { String attributeValue) {
AttributeType attribute = createAttributeType(mappingModel);
attribute.addAttributeValue(attributeValue);
attributeStatement.addAttribute(new AttributeStatementType.ASTChoiceType(attribute));
}
public static AttributeType createAttributeType(ProtocolMapperModel mappingModel) {
String attributeName = mappingModel.getConfig().get(SAML_ATTRIBUTE_NAME); String attributeName = mappingModel.getConfig().get(SAML_ATTRIBUTE_NAME);
AttributeType attribute = new AttributeType(attributeName); AttributeType attribute = new AttributeType(attributeName);
String attributeType = mappingModel.getConfig().get(SAML_ATTRIBUTE_NAMEFORMAT); String attributeType = mappingModel.getConfig().get(SAML_ATTRIBUTE_NAMEFORMAT);
@ -40,8 +45,7 @@ public class AttributeStatementHelper {
attribute.setNameFormat(attributeNameFormat); attribute.setNameFormat(attributeNameFormat);
String friendlyName = mappingModel.getConfig().get(FRIENDLY_NAME); String friendlyName = mappingModel.getConfig().get(FRIENDLY_NAME);
if (friendlyName != null && !friendlyName.trim().equals("")) attribute.setFriendlyName(friendlyName); if (friendlyName != null && !friendlyName.trim().equals("")) attribute.setFriendlyName(friendlyName);
attribute.addAttributeValue(attributeValue); return attribute;
attributeStatement.addAttribute(new AttributeStatementType.ASTChoiceType(attribute));
} }
public static void setConfigProperties(List<ProtocolMapper.ConfigProperty> configProperties) { public static void setConfigProperties(List<ProtocolMapper.ConfigProperty> configProperties) {

View file

@ -5,7 +5,6 @@ import org.keycloak.models.KeycloakSession;
import org.keycloak.models.ProtocolMapperModel; import org.keycloak.models.ProtocolMapperModel;
import org.keycloak.models.UserSessionModel; import org.keycloak.models.UserSessionModel;
import org.picketlink.identity.federation.saml.v2.assertion.AttributeStatementType; import org.picketlink.identity.federation.saml.v2.assertion.AttributeStatementType;
import org.picketlink.identity.federation.saml.v2.protocol.ResponseType;
/** /**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a> * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>

View file

@ -0,0 +1,153 @@
package org.keycloak.protocol.saml.mappers;
import org.keycloak.models.ClientSessionModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.models.ProtocolMapperModel;
import org.keycloak.models.RoleModel;
import org.keycloak.models.UserSessionModel;
import org.keycloak.protocol.ProtocolMapper;
import org.keycloak.protocol.ProtocolMapperUtils;
import org.keycloak.protocol.saml.SamlProtocol;
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;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class SAMLBasicRoleListMapper extends AbstractSAMLProtocolMapper implements SAMLRoleListMapper {
public static final String PROVIDER_ID = "saml-role-list-mapper";
public static final String SINGLE_ROLE_ATTRIBUTE = "single";
private static final List<ConfigProperty> configProperties = new ArrayList<ConfigProperty>();
static {
ConfigProperty property;
property = new ConfigProperty();
property.setName(AttributeStatementHelper.SAML_ATTRIBUTE_NAME);
property.setLabel("Role attribute name");
property.setDefaultValue("Role");
property.setHelpText("Name of the SAML attribute you want to put your roles into. i.e. 'Role', 'memberOf'.");
configProperties.add(property);
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_NAMEFORMAT);
property.setLabel("SAML Attribute NameFormat");
property.setHelpText("SAML Attribute NameFormat. Can be basic, URI reference, or unspecified.");
List<String> 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);
property = new ConfigProperty();
property.setName(SINGLE_ROLE_ATTRIBUTE);
property.setLabel("Single Role Attribute");
property.setType(ConfigProperty.BOOLEAN_TYPE);
property.setDefaultValue("true");
property.setHelpText("If true, all roles will be stored under one attribute with multiple attribute values.");
configProperties.add(property);
}
@Override
public String getDisplayCategory() {
return "Role Mapper";
}
@Override
public String getDisplayType() {
return "Role list";
}
@Override
public String getHelpText() {
return "Role names are stored in an attribute value. There is either one attribute with multiple attribute values, or an attribute per role name depending on how you configure it. You can also specify the attribute name i.e. 'Role' or 'memberOf' being examples.";
}
@Override
public List<ConfigProperty> getConfigProperties() {
return configProperties;
}
@Override
public String getId() {
return PROVIDER_ID;
}
@Override
public void mapRoles(AttributeStatementType roleAttributeStatement, ProtocolMapperModel mappingModel, KeycloakSession session, UserSessionModel userSession, ClientSessionModel clientSession) {
String single = mappingModel.getConfig().get(SINGLE_ROLE_ATTRIBUTE);
boolean singleAttribute = Boolean.parseBoolean(single);
Map<ProtocolMapperModel, SAMLRoleNameMapper> roleNameMappers = new HashMap<>();
KeycloakSessionFactory sessionFactory = session.getKeycloakSessionFactory();
for (ProtocolMapperModel mapping : clientSession.getClient().getProtocolMappers()) {
if (!mapping.getProtocol().equals(SamlProtocol.LOGIN_PROTOCOL)) continue;
ProtocolMapper mapper = (ProtocolMapper)sessionFactory.getProviderFactory(ProtocolMapper.class, mapping.getProtocolMapper());
if (mapper == null || !(mapper instanceof SAMLRoleNameMapper)) continue;
roleNameMappers.put(mapping, (SAMLRoleNameMapper)mapper);
}
AttributeType singleAttributeType = null;
for (String roleId : clientSession.getRoles()) {
// todo need a role mapping
RoleModel roleModel = clientSession.getRealm().getRoleById(roleId);
AttributeType attributeType = null;
if (singleAttribute) {
if (singleAttributeType == null) {
singleAttributeType = AttributeStatementHelper.createAttributeType(mappingModel);
roleAttributeStatement.addAttribute(new AttributeStatementType.ASTChoiceType(singleAttributeType));
}
attributeType = singleAttributeType;
} else {
attributeType = AttributeStatementHelper.createAttributeType(mappingModel);
roleAttributeStatement.addAttribute(new AttributeStatementType.ASTChoiceType(attributeType));
}
String roleName = roleModel.getName();
for (Map.Entry<ProtocolMapperModel, SAMLRoleNameMapper> entry : roleNameMappers.entrySet()) {
String newName = entry.getValue().mapName(entry.getKey(), roleModel);
if (newName != null) {
roleName = newName;
break;
}
}
attributeType.addAttributeValue(roleName);
}
}
public static ProtocolMapperModel create(String name, String samlAttributeName, String nameFormat, String friendlyName, boolean singleAttribute) {
ProtocolMapperModel mapper = new ProtocolMapperModel();
mapper.setName(name);
mapper.setProtocolMapper(PROVIDER_ID);
mapper.setProtocol(SamlProtocol.LOGIN_PROTOCOL);
mapper.setConsentRequired(false);
Map<String, String> config = new HashMap<String, String>();
config.put(AttributeStatementHelper.SAML_ATTRIBUTE_NAME, samlAttributeName);
if (friendlyName != null) {
config.put(AttributeStatementHelper.FRIENDLY_NAME, friendlyName);
}
if (nameFormat != null) {
config.put(AttributeStatementHelper.SAML_ATTRIBUTE_NAMEFORMAT, nameFormat);
}
config.put(SINGLE_ROLE_ATTRIBUTE, Boolean.toString(singleAttribute));
mapper.setConfig(config);
return mapper;
}
}

View file

@ -0,0 +1,91 @@
package org.keycloak.protocol.saml.mappers;
import org.keycloak.models.ApplicationModel;
import org.keycloak.models.ProtocolMapperModel;
import org.keycloak.models.RoleContainerModel;
import org.keycloak.models.RoleModel;
import org.keycloak.protocol.oidc.mappers.AbstractOIDCProtocolMapper;
import java.util.ArrayList;
import java.util.List;
/**
* Map an assigned role to a different position and name in the token
*
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class SAMLBasicRoleNameMapper extends AbstractOIDCProtocolMapper implements SAMLRoleNameMapper {
private static final List<ConfigProperty> configProperties = new ArrayList<ConfigProperty>();
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 = new ConfigProperty();
property.setName(NEW_ROLE_NAME);
property.setLabel("New Role Name");
property.setHelpText("The new role name.");
property.setType(ConfigProperty.STRING_TYPE);
configProperties.add(property);
}
public static final String PROVIDER_ID = "saml-role-name-mapper";
public List<ConfigProperty> getConfigProperties() {
return configProperties;
}
@Override
public String getId() {
return PROVIDER_ID;
}
@Override
public String getDisplayType() {
return "Role Name Mapper";
}
@Override
public String getDisplayCategory() {
return "Role Mapper";
}
@Override
public String getHelpText() {
return "Map an assigned role to a new name";
}
@Override
public String mapName(ProtocolMapperModel model, RoleModel roleModel) {
RoleContainerModel container = roleModel.getContainer();
ApplicationModel app = null;
if (container instanceof ApplicationModel) {
app = ((ApplicationModel) container);
}
String role = model.getConfig().get(ROLE_CONFIG);
String newName = model.getConfig().get(NEW_ROLE_NAME);
String appName = null;
int scopeIndex = role.indexOf('.');
if (scopeIndex > -1) {
if (app == null) return null;
appName = role.substring(0, scopeIndex);
if (!app.getName().equals(appName)) return null;
role = role.substring(scopeIndex + 1);
} else {
if (app != null) return null;
}
if (roleModel.getName().equals(role)) return newName;
return null;
}
}

View file

@ -4,7 +4,6 @@ import org.keycloak.models.ClientSessionModel;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
import org.keycloak.models.ProtocolMapperModel; import org.keycloak.models.ProtocolMapperModel;
import org.keycloak.models.UserSessionModel; import org.keycloak.models.UserSessionModel;
import org.keycloak.representations.AccessToken;
import org.picketlink.identity.federation.saml.v2.protocol.ResponseType; import org.picketlink.identity.federation.saml.v2.protocol.ResponseType;
/** /**

View file

@ -0,0 +1,12 @@
package org.keycloak.protocol.saml.mappers;
import org.keycloak.models.ProtocolMapperModel;
import org.keycloak.models.RoleModel;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public interface SAMLRoleNameMapper {
public String mapName(ProtocolMapperModel model, RoleModel role);
}

View file

@ -1,3 +1,5 @@
org.keycloak.protocol.saml.mappers.SAMLBasicRoleListMapper
org.keycloak.protocol.saml.mappers.SAMLBasicRoleNameMapper
org.keycloak.protocol.saml.mappers.UserAttributeStatementMapper org.keycloak.protocol.saml.mappers.UserAttributeStatementMapper
org.keycloak.protocol.saml.mappers.UserPropertyAttributeStatementMapper org.keycloak.protocol.saml.mappers.UserPropertyAttributeStatementMapper

View file

@ -1,8 +1,11 @@
package org.keycloak.protocol; package org.keycloak.protocol;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.models.ProtocolMapperModel;
import org.keycloak.models.UserModel; import org.keycloak.models.UserModel;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.util.List;
/** /**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a> * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>

View file

@ -256,6 +256,17 @@
"attribute.name": "phone", "attribute.name": "phone",
"attribute.nameformat": "Basic" "attribute.nameformat": "Basic"
} }
},
{
"name": "role-list",
"protocol": "saml",
"protocolMapper": "saml-role-list-mapper",
"consentRequired": false,
"config": {
"attribute.name": "Role",
"attribute.nameformat": "Basic",
"single": "false"
}
} }
] ]
}, },