package org.picketlink.identity.federation.bindings.jboss.auth.mapping; import java.util.List; import java.util.Map; import java.util.Set; import org.jboss.security.identity.RoleGroup; import org.jboss.security.identity.plugins.SimpleRole; import org.jboss.security.identity.plugins.SimpleRoleGroup; import org.jboss.security.mapping.MappingProvider; import org.jboss.security.mapping.MappingResult; import org.picketlink.identity.federation.PicketLinkLogger; import org.picketlink.identity.federation.PicketLinkLoggerFactory; import org.picketlink.identity.federation.bindings.jboss.auth.SAML20CommonTokenRoleAttributeProvider; import org.picketlink.identity.federation.core.wstrust.auth.AbstractSTSLoginModule; import org.picketlink.identity.federation.core.wstrust.plugins.saml.SAMLUtil; 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.ASTChoiceType; import org.picketlink.identity.federation.saml.v2.assertion.AttributeType; import org.picketlink.identity.federation.saml.v2.assertion.StatementAbstractType; import org.w3c.dom.Element; /** * <p> * This mapping provider looks at the role attributes in the Assertion and returns corresponding JBoss RoleGroup objects for * insertion into the Subject. * </p> * * <h3>Configuration</h3> * * <pre> * {@code * <application-policy name="saml-issue-token"> * <authentication> * <login-module code="org.picketlink.identity.federation.core.wstrust.auth.STSIssuingLoginModule" flag="required"> * <module-option name="configFile">/sts-client.properties</module-option> * <module-option name="password-stacking">useFirstPass</module-option> * </login-module> * </authentication> * <mapping> * <mapping-module code="org.picketlink.identity.federation.bindings.jboss.auth.mapping.STSPrincipalMappingProvider" type="principal"/> * <mapping-module code="org.picketlink.identity.federation.bindings.jboss.auth.mapping.STSGroupMappingProvider" type="role"> * <module-option name="token-role-attribute-name">role</module-option> * </mapping-module> * </mapping> * </application-policy> * } * </pre> * * As demonstrated above, this mapping provider is typically configured for an STS Login Module to extract user roles from the * STS token and supply them for insertion into the JAAS Subject. * * This mapping provider looks for a multi-valued Attribute in the Assertion, where each value is a user role. The name of this * attribute defaults to {@code SAML20TokenRoleAttributeProvider.DEFAULT_TOKEN_ROLE_ATTRIBUTE_NAME} but may be set to any value * through the "token-role-attribute-name" module option. * <p/> * * * @author <a href="mailto:Babak@redhat.com">Babak Mozaffari</a> */ public class STSGroupMappingProvider implements MappingProvider<RoleGroup> { private static final PicketLinkLogger logger = PicketLinkLoggerFactory.getLogger(); private MappingResult<RoleGroup> result; private String tokenRoleAttributeName; public void init(Map<String, Object> contextMap) { Object tokenRoleAttributeObject = contextMap.get("token-role-attribute-name"); if (tokenRoleAttributeObject != null) { tokenRoleAttributeName = (String) tokenRoleAttributeObject; } else { tokenRoleAttributeName = SAML20CommonTokenRoleAttributeProvider.DEFAULT_TOKEN_ROLE_ATTRIBUTE_NAME; } // No initialization needed logger.trace("Initialized with " + contextMap); } public void performMapping(Map<String, Object> contextMap, RoleGroup Group) { logger.debug("performMapping with map as " + contextMap); if (contextMap == null) { logger.mappingContextNull(); } Object tokenObject = contextMap.get(AbstractSTSLoginModule.SHARED_TOKEN); if (!(tokenObject instanceof Element)) { // With Tomcat SSO Valves, mapping providers DO get called automatically, so there may be no tokens and errors // should be expected and handled logger.debug("Did not find a token " + Element.class.getName() + " under " + AbstractSTSLoginModule.SHARED_TOKEN + " in the map"); } try { Element tokenElement = (Element) tokenObject; AssertionType assertion = SAMLUtil.fromElement(tokenElement); // check the assertion statements and look for role attributes. AttributeStatementType attributeStatement = this.getAttributeStatement(assertion); if (attributeStatement != null) { RoleGroup rolesGroup = new SimpleRoleGroup(SAML20CommonTokenRoleAttributeProvider.JBOSS_ROLE_PRINCIPAL_NAME); List<ASTChoiceType> attributeList = attributeStatement.getAttributes(); for (ASTChoiceType obj : attributeList) { AttributeType attribute = obj.getAttribute(); if (attribute != null) { // if this is a role attribute, get its values and add them to the role set. if (tokenRoleAttributeName.equals(attribute.getName())) { for (Object value : attribute.getAttributeValue()) { rolesGroup.addRole(new SimpleRole((String) value)); } } } } result.setMappedObject(rolesGroup); logger.trace("Mapped roles to " + rolesGroup); } } catch (Exception e) { logger.authFailedToParseSAMLAssertion(e); } } public void setMappingResult(MappingResult<RoleGroup> mappingResult) { this.result = mappingResult; } /** * @see MappingProvider#supports(Class) */ public boolean supports(Class<?> p) { if (RoleGroup.class.isAssignableFrom(p)) return true; return false; } /** * <p> * Checks if the specified SAML assertion contains a {@code AttributeStatementType} and returns this type when it is * available. * </p> * * @param assertion a reference to the {@code AssertionType} that may contain an {@code AttributeStatementType}. * @return the assertion's {@code AttributeStatementType}, or {@code null} if no such type can be found in the SAML * assertion. */ private AttributeStatementType getAttributeStatement(AssertionType assertion) { Set<StatementAbstractType> statementList = assertion.getStatements(); if (statementList.size() != 0) { for (StatementAbstractType statement : statementList) { if (statement instanceof AttributeStatementType) return (AttributeStatementType) statement; } } return null; } }