Identity Provider Custom Assertions
After a request by a customer to provide custom attributes for an Identity Provider initiated login, I looked into what it would take to provide the attributes that were requested (a brief introduction to SAML can be viewed here). There are several ways to create custom attributes for a SAML assertion. The property “saml.idp.metadata.attribute.names” can be set in the portal-ext.properties file to define what attributes the Identity Provider will submit to the Service Provider. They can be defined globally as well as for each Service Provider. The format allows for access to any attribute available from the User object. It also allows for access to Custom Fields assigned to the user by prefixing the Custom Field name with “expando:”. This provides a great deal of flexibility but it does not provide for complex access to items such as Organization. It also does not provide for mapping of Identity Provider Attributes to another name besides that of the name for the attribute of the User object.
Complex Custom Assertions
Once I discovered this, I looked at Mika Koivisto’s well written code and discovered that there is an interface available to create my own custom assertions. The interface is provided by com.liferay.saml.resolver.AttributeResolver; by implementing the resolve method in this interface you can return a list of OpenSAML Attribute objects. This list can be provided from any source. In the case of the example provided in this blog I extended the com.liferay.saml.resolver.DefaultAttributeResolver to make use of it’s provided helper methods. For a tomcat based installation, the jar containing the class can be placed in the <TOMCAT_HOME>/lib/ext directory, or in the <TOMCAT_HOME>/webapps/saml-portlet/WEB-INF/lib directory. Once this is done the property saml.idp.metadata.attribute.resolver can be set to the class name of the new attribute resolver. For example, I have created a class called com.xtivia.example.saml.resolver.ExampleAttributeResolver, the property would look like this “saml.idp.metadata.attribute.resolver=com.xtivia.example.saml.resolver.ExampleAttributeResolver”.
Example of the AttributeResolver implementation
/**
* Interface for adding attributes to SAML Assertion. Override
* DefaultAttributeResolver's resolve interface.
*
* @param user
* @param samlMessageContext
* @return attributes List of attributes for SAML Assertion
*/
@Override
public List resolve(User user,
SAMLMessageContext samlMessageContext) {
List attributes = new ArrayList();
String entityId = samlMessageContext.getPeerEntityId();
// duplicated functionality from DefaultAttributeResolver
for (String name : getAttributeNames(entityId)) {
String value = getValueForName(name, user);
Attribute attribute =
OpenSamlUtil.buildAttribute(name, value);
attributes.add(attribute);
}
// Add custom attributes from user object
try {
getGroupDisplayName(user, attributes);
getAddressAndPhoneNumber(user, attributes);
} catch (SystemException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (PortalException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return attributes;
}
Note: I left the auto-generated exception catch blocks here for this blog post though actual code would need to use our typical exception handling guidelines captured here.
Example of the AttributeResolver support functions
/**
* Get the first user group defined for the user and
* use the display name as a custom attribute.
*
* @param user
* @param attributes
* @throws PortalException
* @throws SystemException
*/
protected void getGroupDisplayName(User user, List attributes)
throws PortalException, SystemException {
List groups = user.getUserGroups();
if (groups != null && groups.size() > 0)
attributes.add(OpenSamlUtil.buildAttribute(
CustomSamlAssertionKeys.GROUP,
groups.get(0).getName()));
}
/**
* Get the primary address and phone number for the user, if they exist.
*
* @param user
* @param attributes
* @throws SystemException
*/
protected void getAddressAndPhoneNumber(User user,
List attributes) throws SystemException {
Address addr = null;
for (Address a : user.getAddresses()) {
if (a.isPrimary()) {
addr = a;
}
}
if (addr != null) {
attributes.add(OpenSamlUtil.buildAttribute(
CustomSamlAssertionKeys.ADDRESS,
addr.getStreet1()));
if (addr.getStreet2() != null)
attributes.add(OpenSamlUtil.buildAttribute(
CustomSamlAssertionKeys.ADDRESS2,
addr.getStreet2()));
attributes.add(OpenSamlUtil.buildAttribute(
CustomSamlAssertionKeys.CITY,
addr.getCity()));
attributes.add(OpenSamlUtil.buildAttribute(
CustomSamlAssertionKeys.STATE,
addr.getRegion().getName()));
attributes.add(OpenSamlUtil.buildAttribute(
CustomSamlAssertionKeys.ZIP,
addr.getZip()));
}
Phone phone = null;
for (Phone p : user.getPhones()) {
if (p.isPrimary()) {
phone = p;
}
}
if (phone != null)
attributes.add(OpenSamlUtil.buildAttribute(
CustomSamlAssertionKeys.PHONE,
phone.getNumber()));
}