It can be useful to have a point-in-time record of specific attributes, particularly generated attributes, for audit purposes. The Shibboleth Identity Provider (IdP) audit log provides an appropriate location and is probably already being centrally monitored for other purposes.
This example shows how to add the eduPersonTargetedId
attribute as an extra field to the Shibboleth Identity Provider's audit log (idp-audit.xml
). This could also be used for other attributes such as pairwise-id
.
Your audit log configuration is unchanged from a v4.0 installation and your attribute resolver generates an attribute named eduPersonTargetedId
such as:
<AttributeDefinition id="eduPersonTargetedId" xsi:type="SAML2NameID" nameIdFormat="urn:oasis:names:tc:SAML:2.0:nameid-format:persistent"> <InputDataConnector attributeNames="computedId" ref="computed"/> <AttributeEncoder xsi:type="SAML1XMLObject" name="urn:oid:1.3.6.1.4.1.5923.1.1.1.10"/> <AttributeEncoder xsi:type="SAML2XMLObject" name="urn:oid:1.3.6.1.4.1.5923.1.1.1.10" friendlyName="eduPersonTargetedId"/> </AttributeDefinition> |
In conf/audit.xml
, add the following <beans>
(at the end, before the closing </beans>
). This script, based on the examples in AuditLoggingConfiguration, takes a single or multi-valued attribute generated by the resolver and returns it to a calling bean. If the attribute is multi-valued then it delimits the values with a comma (,
) in the same way as Perl's join(',', @list)
.
<bean id="AttributeValueExtraction" parent="shibboleth.ContextFunctions.Scripted" factory-method="inlineScript" abstract="true"> <constructor-arg> <value> <![CDATA[ var getinfo = function() { var rpContext = input.getSubcontext("net.shibboleth.idp.profile.context.RelyingPartyContext"); if (null === rpContext) { return null; } var attrContext = rpContext.getSubcontext("net.shibboleth.idp.attribute.context.AttributeContext"); if (null === attrContext) { return null; } var attributes = null attributes = attrContext.getUnfilteredIdPAttributes(); if (null === attributes) { return null; } attribute = attributes.get(custom); if (null === attribute || attribute.getValues().isEmpty()) { return null; } var iter = attribute.getValues().iterator(); var result = ""; while (iter.hasNext()) { if(!result.equals("")) result = result + ","; result = result + iter.next().getDisplayValue(); } return result; } getinfo(); ]]> </value> </constructor-arg> </bean> |
After the AttributeValueExtraction
bean, add:
<bean id="audit-eduPersonTargetedId" parent="AttributeValueExtraction" p:customObject="eduPersonTargetedId" /> |
You can create multiple audit-...
beans if you require multiple attributes to be made available in the audit log.
After the audit-eduPersonTargetedID
bean, add:
<bean id="shibboleth.PostResponseAuditExtractors" parent="shibboleth.DefaultPostResponseAuditExtractors"> <property name="sourceMap"> <map merge="true"> <entry key="eduPersonTargetedId" value-ref="audit-eduPersonTargetedId" /> </map> </property> </bean> |
This creates a new substitution value %eduPersonTargetedID
based on the value returned from audit-eduPersonTargetedID
.
In conf/audit.xml
, find <util:map id="shibboleth.AuditFormattingMap">
which may look like:
<util:map id="shibboleth.AuditFormattingMap"> <entry key="Shibboleth-Audit" value="%a|%ST|%T|%u|%SP|%i|%ac|%t|%attr|%n|%f|%SSO|%XX|%XA|%b|%bb|%e|%S|%SS|%s|%UA" /> </util:map> |
Add the new attribute (%eduPersonTargetedID
)to the end of the template:
<util:map id="shibboleth.AuditFormattingMap"> <entry key="Shibboleth-Audit" value="%a|%ST|%T|%u|%SP|%i|%ac|%t|%attr|%n|%f|%SSO|%XX|%XA|%b|%bb|%e|%S|%SS|%s|%UA|%eduPersonTargetedId" /> </util:map> |
and restart the IdP.