Page tree
Skip to end of metadata
Go to start of metadata

The ScriptedAttribute attribute definition constructs an output attribute via the execution of a JSR-223 script.

Prior to V3.3, the xsi:type used was Script, declared in the urn:mace:shibboleth:2.0:resolver:ad namespace.

Java 8 uses an updated language engine for ECMA scripting that has incompatibilities with ECMA scripts written for the older Rhino engine included with Java 7 and older IdP versions. Some of the differences are noted in the examples below.

Schema Name and Location

This xsi:type is defined by the urn:mace:shibboleth:2.0:resolver namespace 3.3, the schema for which can be located at http://shibboleth.net/schema/idp/shibboleth-attribute-resolver.xsd

Prior to V3.3 supplied plugins were defined by a schema type in the urn:mace:shibboleth:2.0:resolver:ad namespace, the schema for which is located at http://shibboleth.net/schema/idp/shibboleth-attribute-resolver-ad.xsd. This is still supported, but every element or type in the old namespace has an equivalently named (but not necessarily identical) version in the urn:mace:shibboleth:2.0:resolver namespace. The use of the urn:mace:shibboleth:2.0:resolver namespace also allows a relaxation of the ordering requirements of child elements to reduce strictness.

Attributes

Any of the common attributes can be specified. In addition, the following attributes may be provided:

Name
Type
Default
Description
language          
stringJavaScriptDefines the JSR-233 language to use. The default is ECMA script using either the Rhino (Java 7) or Nashorn (Java 8) engines.

customObjectRef 3.2

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

Child Elements

Any of the common child elements can be specified. In addition, one of the following two elements must be defined

NameCardinalityDescription
<Script>


Exactly One

The content (typically inside an XML CDATA block) defines the actual Script to be executed
<ScriptFile>
The content specifies a file which contains the script to be executed

Notes On the Semantics of the Script

Data Available to the script

The script has the following available:

  • A variable which implements ScriptedIdPAttribute whose name is the ID of the attribute definition.
    Note, if an attribute of this name occurs as a dependency (see below), then the attribute will be pre-populated with all the values of the dependent attribute.
  • A variable named resolutionContext containing the AttributeResolutionContext for the current resolution request.
  • A variable named profileContext containing the ProfileRequestContext for the current resolution request.
  • A variable named custom 3.2which contains whatever was provided by the customObjectRef attribute (see above)
  • A variable named subjects 3.3an array of the java javax.security.auth.Subject objects associated with this authorization.  Note that these will only be present if the attribute resolution has been associated with an act of authentication (and so will not work for back channel requests).
  • Other contexts can be located by navigating the context tree, whose root is the profileContext variable.
  • A variable which implements ScriptedIdPAttribute for each attribute produced by the defined dependencies of this definition. If a dependency is an attribute definition, that attribute is supplied. If a dependency is a data connector, every attribute resolved by that connector is supplied. The variable's name will be that of the 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 will be merged and made available to the script.
    Note that any changes made to these attributes within the script will not be reflected in the result of the attribute resolution.
  • A single attribute, whose name is the ID of the attribute definition, is made available at the end of the attribute resolution process.

The ScriptedIdPAttribute

The attribute variables (both input and output) available to the scripting environment have the following methods. Note that more methods may be added to this interface between minor software revisions, but methods will not be removed except between major revisions, if at all.

  • Collection getValues()
    • This returns the current values of the attribute (which may be empty).  As a convenience, StringAttributeValues are available as simple Strings, but all other AttributeValue types 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 V3 native IdPAttribute. Note that only the output attribute will be available after the script has run.  This method cannot be called if getValues() has been called.
  • addValue(value)
    • This adds a value to the attribute. The parameter either be a String or an implementation of IdPAttributeValue. See below for examples.

Adding Values

Values are added to the attributed 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/Java7)
eduPersonEntitlement.addValue("urn:mace:dir:entitlement:common-lib-terms");
Adding a Scoped Attribute Value (Nashorn/Java8)
scopedValueType =  Java.type("net.shibboleth.idp.attribute.ScopedStringAttributeValue");
eduPersonPrincipalName.addValue(new scopedValueType("user", "example.org"));

Locating Other Contexts

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
child = resolutionContext.getSubcontext("net.shibboleth.idp.attribute.resolver.ChildContextType"); 
parent = resolutionContext.getParent();
peer = parent.getSubcontext("net.shibboleth.idp.attribute.resolver.PeerContextType");

Logging Within a Script

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 Java 1.7/Rhino
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 Java 1.8/Nashorn
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());

Accessing other information

The use of the customObjectRef is a very powerful paradigm, widening the scope of information available to a script beyond the request's immediate state (via the profile request context).

In particular, the beans named "shibboleth.HttpServletRequest" and "shibboleth.HttpServletResponse" allow access to the HTTP information associated with the request and response.

V2 Compatibility

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.

Java 1.8 and Nashorn

In Java 1.8, a new scripting engine is provided for the default "JavaScript" language. This has some syntactic (explained here) and semantic differences.  To convert a working V2, pre-Java 1.8 script to IdPV3 running Java 1.8:

  • Make the syntactic changes required.
  • Remove the now-redundant creation of the output attribute (In V3 the output attribute is precreated).  If the script needs to run under Java 1.7 and Java 1.8, the creation can be conditionalized:

    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 V3 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, plus it depends on the "ScriptedAttribute" one 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.
(Note that this variant will generate WARN-level entries in idp-process.log, due to the use of 2 Dependency elements while the specified sourceAttributeID only exists in one of them. That's a known issue with the resolver schema. To avoid the warning from getting logged you can add an entry to your logback.xml for the appropriate class ("net.shibboleth.idp.attribute.resolver.PluginDependencySupport"), setting the level to ERROR.)

Minimal scripting, using Dependencies (Nashorn/Java8)
 <AttributeDefinition id="eduPersonPrincipalName" xsi:type="Prescoped" sourceAttributeID="eduPersonPrincipalName">
    <Dependency ref="myLDAP" />
    <Dependency ref="eppnFromUid" />
    <AttributeEncoder xsi:type="SAML1ScopedString" name="urn:mace:dir:attribute-def:eduPersonPrincipalName" encodeType="false" />
    <AttributeEncoder xsi:type="SAML2ScopedString" name="urn:oid:1.3.6.1.4.1.5923.1.1.1.6" friendlyName="eduPersonPrincipalName" encodeType="false" />
</AttributeDefinition>

<AttributeDefinition id="eppnFromUid" xsi:type="ScriptedAttribute" dependencyOnly="true">
    <Dependency ref="myLDAP" />
    <Script><![CDATA[if (typeof eduPersonPrincipalName == "undefined") eppnFromUid.addValue(uid.getValues().get(0) + "@%{idp.scope}");]]></Script>
</AttributeDefinition>

Variant 2: Doing everything in one ScriptedAttribute type AttributeDefinition. Since the eduPersonPrincipalName values from LDAP will be a prescoped, but to the IDP "flat", string we'll have to empty out the Collection of values before adding the properly scoped one based on ScopedStringAttributeValue (described above).

All in one Script (Nashorn/Java8)
<AttributeDefinition id="eduPersonPrincipalName" xsi:type="ScriptedAttribute">
    <Dependency ref="myLDAP" />

    <Script><![CDATA[
logger = Java.type("org.slf4j.LoggerFactory").getLogger("net.shibboleth.idp.attribute.resolver.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>

    <AttributeEncoder xsi:type="SAML1ScopedString" name="urn:mace:dir:attribute-def:eduPersonPrincipalName" encodeType="false" />
    <AttributeEncoder xsi:type="SAML2ScopedString" name="urn:oid:1.3.6.1.4.1.5923.1.1.1.6" friendlyName="eduPersonPrincipalName" encodeType="false" />

</AttributeDefinition>
  • No labels