Monday 21 October 2013

SAML2 Populate Subject NameID with Attribute Value

This is a post to cover my forum post here: https://forums.oracle.com/message/11238560#11238560

Basically we had an issue with SAML2 SSO for EPM. We do not manage the Federation Services servers and those servers return a specific attribute as the subject, like so:

<saml:Subject><saml:NameID Format="urn:oasis:names:tc:SAML:1.1:nameid-format:X509SubjectName">USERNAME</saml:NameID>

We can ask for more attributes to be returned in the SAML2 response and they will be added, like so:

<saml:Attribute Name="mail" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic"><saml:AttributeValue xsi:type="xs:string">my.name@example.com</saml:AttributeValue>


However we need to use the value of the "mail" attribute rather than the Subject NameID as the principal name to be passed back to the WebLogic application. The application is EPM Foundation Services and has been set up to use an LDAP authentication source that uses "mail" as the username. We cannot use the NameID. As such we see errors like the below in the WL logs for FoundationServices0, when we attempt SAML2 SSO:

<DefaultSAML2NameMapperImpl: mapName: Mapped name: qualifier: null, name: USERNAME>
<SAMLIACallbackHandler: SAMLIACallbackHandler(true, USERNAME, null)>
<SAMLIACallbackHandler: callback[0]: NameCallback: setName(USERNAME)>
<[Security:090304]Authentication Failed: User USERNAME javax.security.auth.login.FailedLoginException: [Security:090302]Authentication Failed: User USERNAME denied>


So I had to write the two classes below:


MyNameMapper.class:

package sample.providers.saml;

import java.util.HashSet;
import java.util.Set;
import java.util.Arrays;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import javax.security.auth.Subject;
import java.security.Principal;
import com.bea.security.saml2.providers.SAML2AttributeInfo;
import com.bea.security.saml2.providers.SAML2AttributeStatementInfo;
import com.bea.security.saml2.providers.SAML2CredentialNameMapper;
import com.bea.security.saml2.providers.SAML2IdentityAsserterNameMapper;
import com.bea.security.saml2.providers.SAML2IdentityAsserterAttributeMapper;
import com.bea.security.saml2.providers.SAML2NameMapperInfo;
import weblogic.security.service.ContextHandler;
import weblogic.security.SubjectUtils;
import weblogic.security.service.ContextHandler;
import weblogic.security.spi.WLSUser;
import weblogic.security.spi.WLSGroup;
import weblogic.security.principal.*;

public class MyNameMapper implements SAML2IdentityAsserterNameMapper, SAML2IdentityAsserterAttributeMapper {

   @Override
    public String mapNameInfo(SAML2NameMapperInfo saml2NameMapperInfo, ContextHandler arg1) {
        String user;
        System.out.println("--- MyNameMapper ---");
        // System.out.println("MyNameMapper mapNameInfo");
        // System.out.println("saml2NameMapperInfo: " + saml2NameMapperInfo.toString());
        // System.out.println("arg1: " + arg1.toString());
        // System.out.println("arg1 number of elements: " + arg1.size());
       
        // getNames gets a list of ContextElement names that can be requested.
        String[] arr = arg1.getNames();
       
        // For each possible element
        for (String element : arr) {
            System.out.println("ContextHandler elements: " + element);
            // If one of those possible elements has the AttributePrinciples
            if(element.equals("com.bea.contextelement.saml.AttributePrincipals")){
                // Put the AttributesPrincipals into an ArrayList of CustomPrincipals
                ArrayList <CustomPrincipal> arr2 = (ArrayList <CustomPrincipal>) arg1.getValue("com.bea.contextelement.saml.AttributePrincipals");
                int i = 0;
                String attr;
                if(arr2 != null){
                    // For each AttributePrincipal in the ArrayList
                    for (CustomPrincipal element2 : arr2) {
                        // Get the Attribute Name and the Attribute Value
                        attr = element2.toString();
                        System.out.println("Attribute " + i + " Name: " + attr);
                        System.out.println("Attribute " + i + " Value: " + element2.getCollectionAsString());
                        // If the Attribute is "loginAccount"
                        if(attr.equals("loginAccount")){
                            user = element2.getCollectionAsString();
                            // Remove the "@DNS.DOMAIN.COM" (case insensitive) and set the username to that string
                            if(!user.equals("null")){
                                System.out.println("Username (from loginAccount): " + user.replaceAll("(?i)\\@CLIENT\\.EXAMPLE\\.COM", ""));
                                return user.replaceAll("(?i)\\@CLIENT\\.EXAMPLE\\.COM", "");
                            }
                        }
                        i++;
                    }
                }
               
                // For some reason the ArrayList of CustomPrincipals was blank - just set the username to the Subject
               
                user = saml2NameMapperInfo.getName(); // Subject = BRID
                System.out.println("Username (from Subject): " + user);
                System.out.println("--- MyNameMapper ---");
                return user;
            }
        }
       
        // Just in case AttributePrincipals does not exist
        user = saml2NameMapperInfo.getName(); // Subject = BRID
        System.out.println("Username (from Subject): " + user);
        System.out.println("--- MyNameMapper ---");
        // Set the username to the Subject
        return user;
       
        // System.out.println("com.bea.contextelement.saml.AttributePrincipals: " + arg1.getValue("com.bea.contextelement.saml.AttributePrincipals"));
        // System.out.println("com.bea.contextelement.saml.AttributePrincipals CLASS: " + arg1.getValue("com.bea.contextelement.saml.AttributePrincipals").getClass().getName());
       
       
        // System.out.println("ArrayList toString: " + arr2.toString());
        // System.out.println("Initial size of arr2: " + arr2.size());
       
       
    }


    public Collection<Object> mapAttributeInfo0(Collection<SAML2AttributeStatementInfo> attrStmtInfos, ContextHandler contextHandler) {
        if (attrStmtInfos == null || attrStmtInfos.size() == 0) {
            System.out.println("CustomIAAttributeMapperImpl: attrStmtInfos has no elements");
            return null;
        }
       
        Collection<Object> customAttrs = new ArrayList<Object>();
       
        for (SAML2AttributeStatementInfo stmtInfo : attrStmtInfos) {
            Collection<SAML2AttributeInfo> attrs = stmtInfo.getAttributeInfo();
            if (attrs == null || attrs.size() == 0) {
                System.out.println("CustomIAAttributeMapperImpl: no attribute in statement: " + stmtInfo.toString());
            } else {
            for (SAML2AttributeInfo attr : attrs) {
                if (attr.getAttributeName().equals("AttributeWithSingleValue")){
                        CustomPrincipal customAttr1 = new CustomPrincipal(attr.getAttributeName(), attr.getAttributeNameFormat(),attr.getAttributeValues());
                        customAttrs.add(customAttr1);
                }else{
                    String customAttr = new StringBuffer().append(attr.getAttributeName()).append(",").append(attr.getAttributeValues()).toString();
                    customAttrs.add(customAttr);
                }
            }
            }
        }
        return customAttrs;
    }
   
    public Collection<Principal> mapAttributeInfo(Collection<SAML2AttributeStatementInfo> attrStmtInfos, ContextHandler contextHandler) {
        if (attrStmtInfos == null || attrStmtInfos.size() == 0) {
            // System.out.println("CustomIAAttributeMapperImpl: attrStmtInfos has no elements");
            return null;
        }
   
        Collection<Principal> pals = new ArrayList<Principal>();
       
        for (SAML2AttributeStatementInfo stmtInfo : attrStmtInfos) {
            Collection<SAML2AttributeInfo> attrs = stmtInfo.getAttributeInfo();
            if (attrs == null || attrs.size() == 0) {
                // System.out.println("CustomIAAttributeMapperImpl: no attribute in statement: " + stmtInfo.toString());
            } else {
                for (SAML2AttributeInfo attr : attrs) {
                    CustomPrincipal pal = new CustomPrincipal(attr.getAttributeName(), attr.getAttributeNameFormat(), attr.getAttributeValues());
                    pals.add(pal);
                }
            }
        }
        return pals;
    }
}


CustomPrincipal.class:

 package sample.providers.saml;

import java.util.HashSet;
import java.util.Set;
import java.util.Arrays;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import javax.security.auth.Subject;
import java.security.Principal;
import com.bea.security.saml2.providers.SAML2AttributeInfo;
import com.bea.security.saml2.providers.SAML2AttributeStatementInfo;
import com.bea.security.saml2.providers.SAML2CredentialNameMapper;
import com.bea.security.saml2.providers.SAML2IdentityAsserterNameMapper;
import com.bea.security.saml2.providers.SAML2IdentityAsserterAttributeMapper;
import com.bea.security.saml2.providers.SAML2NameMapperInfo;
import weblogic.security.service.ContextHandler;
import weblogic.security.SubjectUtils;
import weblogic.security.service.ContextHandler;
import weblogic.security.spi.WLSGroup;
import weblogic.security.spi.WLSUser;
import weblogic.security.principal.*;

public class CustomPrincipal extends WLSAbstractPrincipal implements WLSUser{
    private String commonName;
    private Collection collection;
    public CustomPrincipal(String name, String string, Collection<String> collection) {
        super();
        // Feed the WLSAbstractPrincipal.name. Mandatory
        System.out.println("--- CustomPrincipal ---");
        this.setName(name);
        this.setCommonName(name);
        this.setCollection(collection);
        System.out.println("--- CustomPrincipal ---");
    }
  
    public CustomPrincipal() {
        super();
    }
  
    public CustomPrincipal(String commonName) {
        super();
        this.setName(commonName);
        this.setCommonName(commonName);
    }
   
    public void setCommonName(String commonName) {
        // Feed the WLSAbstractPrincipal.name. Mandatory
        super.setName(commonName);
        this.commonName = commonName;
        System.out.println("Attribute: " + this.getName());
        // System.out.println("Custom Principle commonName is " + this.commonName);
    }
        
    public Collection getCollection() {
        return collection;
    }
  
    public String getCollectionAsString() {
        String collasstr;
        if(collection != null && collection.size()>0){
            for (Iterator iterator = collection.iterator(); iterator.hasNext();) {
                collasstr = (String) iterator.next();
                return collasstr;
            }
        }
        return "null";
    }
  
    public void setCollection(Collection collection) {
        this.collection = collection;
        // System.out.println("set collection in CustomPrinciple!");
        if(collection != null && collection.size()>0){
            for (Iterator iterator = collection.iterator(); iterator.hasNext();) {
                final String value = (String) iterator.next();
                System.out.println("Attribute Value: " + value);
            }
        }
    }
  
    @Override
    public int hashCode() {
        final int prime = 31;
        int result = super.hashCode();
        result = prime * result + ((collection == null) ? 0 : collection.hashCode());
        result = prime * result + ((commonName == null) ? 0 : commonName.hashCode());
        return result;
    }
  
    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (!super.equals(obj))
            return false;
        if (getClass() != obj.getClass())
            return false;
        CustomPrincipal other = (CustomPrincipal) obj;
        if (collection == null) {
            if (other.collection != null)
                return false;
        } else if (!collection.equals(other.collection))
                return false;
        if (commonName == null) {
            if (other.commonName != null)
                return false;
        } else if (!commonName.equals(other.commonName))
                return false;
        return true;
    }
   
}



This must be added as a Custom Name Mapper class in WebLogic:
sample.providers.saml.MyNameMapper



I have used these pages as a reference:

Totapally: Open AM, and WebLogic Application Server - Single Sign On

https://twiki.cern.ch/twiki/bin/view/DB/CernWlsPrincipalMapper

Use SAML assertions from an application server acting as an identity provider

And the API docs.

No comments:

Post a Comment