ScriptedAttributeDefinition

Namespace: urn:mace:shibboleth:2.0:resolver
Schema: http://shibboleth.net/schema/idp/shibboleth-attribute-resolver.xsd

Overview

The ScriptedAttribute AttributeDefinition constructs an output attribute via the execution of a JSR-223 script. Scripts are somewhat easier to write and maintain than native Java code, though they are slower. They can also be changed dynamically since the resolver is a ReloadableService.

Scripting

The script "context" defines the execution environment for the script and provides the following variables:

  • resolutionContext

    • AttributeResolutionContext for the current resolution step, which exists within the tree of state information that tracks the current request

  • id

    • Variable which implements ScriptedIdPAttribute, whose name is the ID of the AttributeDefinition, and which will be the output of the script. Note: If an attribute of this same name occurs as a dependency (see below), then the attribute will be pre-populated with all the values of the dependent attribute.

  • profileContext

  • custom

    • Contains whatever bean was referenced by the customObjectRef XML Attribute

  • subjects

    • Array of Subject objects associated with this request. Note that these will only be present if the attribute resolution is associated with a completed authentication step (so is not present for back channel requests or certain other cases).

In addition, each defined dependency of the connector, it exists, will be present via an object which implements ScriptedIdPAttribute.

For an AttributeDefinition dependency, that IdPAttribute is supplied. For a DataConnector dependency, each IdPAttribute produced by that connector is supplied.

The variable's name will be the attribute ID of the attribute from the dependency. In the event that more than one dependency produces attributes with the same ID, the values of all of those attributes are merged and made available to the script.

Note that any changes made to these dependency objects within the script will not be reflected in the result of the resolution process. In contrast, changes made to other objects accessed by means of the other variables in most cases will cause side effects, and should usually be avoided.

Wrapper Class

The attribute variables (both input and output) available to the scripting environment are exposed using a wrapper class, ScriptedIdPAttribute, which has the following methods:

  • Collection<Object> getValues()

    • This returns the current values of the attribute (which may be empty).

    • As a convenience, values of type StringAttributeValue are available as simple Strings, but all other IdPAttributeValue subtypes are only available as their native type since their underlying data can only be accessed natively.

    • Values can be added and removed from this collection, but added values must be of type String or implement the IdPAttributeValue interface. This method cannot be called if getNativeAttribute() has been called on the same attribute in the same script.

  • IdPAttribute getNativeAttribute()

    • Provided for advanced use only, this returns a native IdPAttribute object instead of the wrapper class. This method cannot be called if getValues() has been called.

  • addValue(value)

    • This adds a value to the attribute. The parameter must either be a String or an implementation of IdPAttributeValue. See below for examples.

Adding Values

Values are added by calling the addValue() method. This must take either a String value or an object which implements IdPAttributeValue. The former is the most common case. A useful example of the latter is ScopedStringAttributeValue which has two constructor parameters - the value and scope, respectively. There are also other subclasses available.

Thus:

Adding a String Attribute Value (Rhino) 
1 eduPersonEntitlement.addValue("urn:mace:dir:entitlement:common-lib-terms");
Adding a Scoped Attribute Value (Nashorn)
1 2 scopedValueType = Java.type("net.shibboleth.idp.attribute.ScopedStringAttributeValue"); eduPersonPrincipalName.addValue(new scopedValueType("user", "example.org"));

The standard way of locating other context types in the tree of state information is via context navigation. The following example shows how to locate a peer context and a child context (the actual context types shown are examples only):

Locating other contexts
1 2 3 child = resolutionContext.getSubcontext("net.shibboleth.idp.attribute.resolver.ChildContextType");  parent = resolutionContext.getParent(); peer = parent.getSubcontext("net.shibboleth.idp.attribute.resolver.PeerContextType");

The same logging framework used throughout the IdP (SLF4J) may be used for logging within a script. First import the package org.slf4j and then obtain an org.slf4j.Logger object from an org.slf4j.LoggerFactory. The logging category name used is arbitrary, but you may need to adjust the IdP's logging configuration to see particular results. Logging levels available are: error, warn, info, debug, trace.

The string passed to the LoggerFactory.getLogger method should be the name of an existing logger element, defined in logging.xml.

For more information on configuring logging within the IdP, see the LoggingConfiguration topic.

Logging (Rhino)
1 2 3 4 5 6 7 importPackage(Packages.org.slf4j);   logger = LoggerFactory.getLogger("net.shibboleth.idp.attribute"); Scripted.addValue("foo"); Scripted.addValue("bar"); logger.info("Values of scriptTest were: {} ", Scripted.getValues());
Logging (Nashorn)
1 2 3 4 5 6 logger = Java.type("org.slf4j.LoggerFactory").getLogger("net.shibboleth.idp.attribute"); Scripted.addValue("foo"); Scripted.addValue("bar"); logger.info("Values of scriptTest were: {} ", Scripted.getValues());

Reference

Name

Type

Default

Description

Name

Type

Default

Description

language

string

JavaScript

The default is ECMA script using either the Rhino (Java 7) or Nashorn (Java 8+) engines.

This situation is in flux due to the removal of Nashorn from future Java versions, and there are plans to provide a V4.1+ plugin that supplies one of these options in the future at the deployer's discretion.

customObjectRef

string

 

The name of a Spring Bean defined elsewhere. This bean will be made available to the script in a variable named "custom".

The following XML Elements are specific to this connector, and one of them must be supplied:

Name

Description

Name

Description

<Script>

The contents define the script to execute, usually wrapped in an XML CDATA block to avoid escaping

<ScriptFile>

The contents define a file which contains the script to execute

V2 Compatibility

This support is deprecated and should have been removed in V4, however the change was not warned about sufficiently. This will be removed in V5.

In order to support the majority of scripts written for V2, the runtime environment is extended in two ways:

  • A package named edu.internet2.middleware.shibboleth.common.attribute.provider is available, and specifically within it, the BasicAttribute class. This provides a facsimile of the V2-equivalent class. Specifically:

    • A constructor which takes a String, being the name of the attribute being created.

    • The getValues() method which returns the current values.

  • A variable named requestContext  which implements all the methods implemented by the V2 class "edu.internet2.middleware.shibboleth.common.profile.provider.BaseSAMLProfileRequestContext". However, all but three of these methods do nothing but log an error and (where a return value is required) return a null pointer. The three methods which are implemented are:

    • getPrincipalName to provide identification of the subject 

    • getPeerEntityId to get the entityId of the attribute recipient

    • getLocalEntityId to get the entityId of the attribute issuer

    In V3, all of this information is also available in the AttributeResolutionContext to eliminate the dependency on the legacy interface.

It is expected that the addition of this emulation code will allow the majority of V2 scripts to run unchanged. All of the examples in the V2 wiki topic run unchanged (subject to the constraints introduced by Java 1.8). Nonetheless, users should consider rewriting their scripts after upgrading, as this capability will likely be removed in V4.

Nashorn

In Java 1.8 through Java 14, the Nashorn scripting engine provides the default "JavaScript" language. This has some syntactic (explained here) and semantic differences from the older Rhino engine.  To convert a working V2, pre-Java 1.8 script to IdPV4 running Java 11:

  • Make the syntactic changes required.

  • Remove the now-redundant creation of the output attribute (In V4 the output attribute is pre-created).  If the script needs to run under both versions:

    1 2 3 4 if (null == scriptedAttribute) { // will only occurr under IdP V2, running pre-Java 1.8 scriptedAttribute = new BasicAttribute("scriptedAttribute"); }

Of course, for new scripts created for V4 alone, this isn't necessary.

Examples

Get eduPersonPrincipalName from LDAP or build one from uid

Variant 1: A "Prescoped" AttributeDefinition resolves existing eduPersonPrincipalName values from LDAP, then depends on a "ScriptedAttribute" definition to generate missing values. The Script also needs a dependency on the myLDAP DataConnector in order to have access to existing eduPersonPrincipalName and uid attribute values.

Minimal scripting, using Dependencies (Nashorn)
1 2 3 4 5 6 7 8 9 10 <AttributeDefinition id="eduPersonPrincipalName" xsi:type="Prescoped"> <InputAttributeDefinition ref="eppnFromUid" /> </AttributeDefinition> <AttributeDefinition id="eppnFromUid" xsi:type="ScriptedAttribute" dependencyOnly="true"> <InputDataConnector ref="myLDAP" attributeNames="eduPersonPrincipalName uid" /> <Script><![CDATA[ if (typeof eduPersonPrincipalName == "undefined") eppnFromUid.addValue(uid.getValues().get(0) + "@%{idp.scope}"); ]]></Script> </AttributeDefinition>

Variant 2: Doing everything in one "ScriptedAttribute" definition. Since the eduPersonPrincipalName values from LDAP will contain the scope but are simple strings at this point, we'll have to empty out the collection of values before adding the properly scope-aware values based on ScopedStringAttributeValue (described above).

All in one Script (Nashorn)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 <AttributeDefinition id="eduPersonPrincipalName" xsi:type="ScriptedAttribute"> <InputDataConnector ref="myLDAP" attributeNames="eduPersonPrincipalName uid" /> <Script><![CDATA[ logger = Java.type("org.slf4j.LoggerFactory").getLogger("org.example.eppnbuilder"); scopedValueType = Java.type("net.shibboleth.idp.attribute.ScopedStringAttributeValue"); var localpart = ""; if (typeof eduPersonPrincipalName == "undefined" || eduPersonPrincipalName.getValues().size() < 1) { logger.debug("No ePPN in LDAP found, creating one"); localpart = uid.getValues().get(0); } else { logger.debug("ePPN had value: " + eduPersonPrincipalName.getValues().get(0)); localpart = eduPersonPrincipalName.getValues().get(0).split("@")[0]; eduPersonPrincipalName.getValues().retainAll([]); } eduPersonPrincipalName.addValue(new scopedValueType(localpart, "%{idp.scope}")); logger.debug("ePPN final value: " + eduPersonPrincipalName.getValues().get(0)); ]]></Script> </AttributeDefinition>