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

Creating Custom IdP Extensions - General Overview

STOP!

The Shibboleth Identity Provider uses Spring and XML and XML Schema extensively. Before you begin you must have a basic understanding of these technologies.

This document is meant to give developers a basic overview in creating Shibboleth extensions. The general steps are the same for every plugin, only things like the particular classes and schema types that are extended change.

Be aware that the IdP uses the same plugin mechanism as custom extensions do in order to provide default implementations of the extension points. The only difference is that the files for those extensions are included in the IdP's jars instead of a separate one for the custom extension.

Appendix A provides a complete list of the extension points within the IdP. Following the link to the extension-specific description will provide the various namespaces, schema file locations, and classes needed when creating a custom extension.

The SVN repository shib-extension contains some example extensions.

What can be Extended?

The easiest way to tell what can be extended is to look in your IdP's configuration file. Every element with the attribute xsi:type represents a plugin point.

As mentioned before the IdP uses the same plugin mechanisms for its own implementations. Therefore looking at the existing IdP implementations of the extension point should help provide already working code examples.

What does a Plugin Look Like?

The final result of a plugin is a JAR with the following structure:

/META-INF/spring.handlers
/META-INF/spring.schemas
/schema/... your schema files ...
... Java class files for extension...

While the schema file(s) for the extension do not have to go in a schema directory it is recommended for organizational purposes.

Step 1: Set up the Project

The final result of an extension project is a JAR in the form outlined in the previous section. Any build environment may be used to create this JAR but these instructions will use Maven, the build system used by the IdP.

  1. Download this project template setup and extract it
  2. Edit the included pom.xml
    1. Fill in the <groupId>, <artifactId>, <version>, <name> elements (found as children of <project>)
    2. Fill in the <version> element of the shibboleth-identityprovider dependency with the appropriate Shibboleth IdP version, e.g. "2.4.0" (found as a descendant of <project>/<dependencies>)
  3. Optionally, within pom.xml uncomment some or all of the project metadata section at the bottom of the file and fill it in with the appropriate information

This guide assumes the usage of this project layout. It will refer to the root of the project folder as $PROJ_HOME.

Step 2: Write the Extension

Create a Java class that implements/extends the appropriate interface/class for the extension point being implemented.

Spring works by injecting properties into the plugin by either passing them as constructor arguments or by means of setter methods. That is, it assumes the plugin is a JavaBean. The example in this guide uses both constructor and property based injection.

Example Login Handler Class
package org.example.shibboleth.authn;

... imports ...

public class IPAddressLoginHandler extends AbstractLoginHandler {

    public IPAddressLoginHandler(String username){
        // method logic
    }

    /** {@inheritDoc} */
    public void login(HttpServletRequest httpRequest, HttpServletResponse httpResponse) {
        // method logic
    }

    public List<String> getAllowedIPAddressRanges(){
        // method logic
    }

    public void setAllowedIPAddressRanges(List<String> ranges){
        // method logic
    }
}

Step 3: Define a Plugin Schema

The next step is to describe the properties needed by the plugin with an XML schema. This Schema enumerates all the possible configuration options for your extension and allows the IdP to perform basic validation of a user's configuration file.

The contents of the schema file will be one or more <complexType> elements that extend a type defined either in the standard Shibboleth XML Schema files or another developer's extension.

Here are the basic steps for setting up the schema file:

  1. Pick a namespace for the plugin. Do NOT use the urn:mace:shibboleth namespace.
  2. Create the schema file in $PROJ_HOME/resources/schema
    1. Set the targetNamespace to the chosen namespace
    2. Declare any Shibboleth namespaces that will be used via the customary xmlns:PREFIX attributes. At a minimum the namespace associated with the extension point must be declared.
    3. Set the elementFormDefault attribute value to "qualified"
  3. Import the schema file that contains the plugin point being implemented. Shibboleth defines a special URL scheme, classpath, which ensures that the schema files are resolved from the classpath. No other resolution mechanism is allowed.
  4. Define the plugin type(s) and ensure they extend the appropriate Shibboleth type.
Example Schema File for an IP-based Login Handler Type
<schema targetNamespace="urn:mace:example.org:shibboleth:authn"
        xmlns="http://www.w3.org/2001/XMLSchema"
        xmlns:ph="urn:mace:shibboleth:2.0:idp:profile-handler"
        elementFormDefault="qualified">

    <import namespace="urn:mace:shibboleth:2.0:idp:profile-handler"
		schemaLocation="classpath:/schema/shibboleth-2.0-idp-profile-handler.xsd" />

    <complexType name="IPAddress">
        <complexContent>
            <extension base="ph:LoginHandlerType">
                <sequence>
                    <element name="IPEntry" type="string" maxOccurs="unbounded" />
                </sequence>
                <attribute name="username" type="string" />
            </extension>
        </complexContent>
    </complexType>

</schema>

If you are using Eclipse, you might want it to load an URL Handler for the "classpath:" URL scheme, so that Eclipse can find imported schemas when editing a .xsd file. You can download this JAR file and load it in Eclipse like this (assuming you downloaded the file in $HOME/shibboleth):

eclipse -vmargs -Djava.protocol.handler.pkgs=org.my.protocols -Xbootclasspath/p:$HOME/shibboleth/classpath-uri-stream-handler-0.0.1-SNAPSHOT.jar

Step 4: Write the Spring BeanDefinitionParser

The Spring BeanDefinitionParser reads the XML configuration for the extension and sets the appropriate bean properties and/or constructor arguments on the plugin class. In many cases the IdP uses a slightly more complex method for creating its plugins. It uses a BeanDefinitionParser to populate an AbstractFactoryBean. The factory bean provides the JavaBean interface needed by Spring but allows those properties to be mangled or added to a plugin by means of some method other than setters. Within the IdP this is almost always done to avoid having to have setter methods that would replace some Collection field within the plugin (which can lead to various NullPointerExceptions, synchronization issues, etc.).

  1. Create a class that extends the Bean Parser class for the extension point.
  2. Define a public static final QName SCHEMA_TYPE class field that contains the QName for the extension type. The example uses a static variable from the class to be defined next. This QName isn't required but putting it here helps keep related information together.
  3. Implement the getBeanClass(Element) method and return a Class object for the extension written in step 1.
  4. Implement the doParse(Element, ParserContext, BeanDefinitionBuilder) method and populate the BeanDefinitionBuilder with information from the given Element. This Element object is the DOM Element, matching the defined schema, for a particular configuration instance of the extension.
Bean Definition Parser for Example Login Handler
package org.example.shibboleth.authn.config;

... imports ...

public class IPAddressLoginHandlerBeanDefinitionParser extends AbstractSimpleBeanDefinitionParser{

    /** Schema type. */
    public static final QName SCHEMA_TYPE = new QName(AuthnExtensionNamespaceHandler.NAMESPACE, "IPAddress");

    /** {@inheritDoc} */
    protected Class getBeanClass(Element arg0) {
        return IPAddressLoginHandler.class;
    }

    /** {@inheritDoc} */
    protected void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) {
        // Create a list from the <IPEntry> elements
        List ipEntries;  // code for populating the list omitted

        builder.addPropertyValue("allowedIPAddressRanges", ipEntries);

        String username = element.getAttributeNS(null, "username");
        builder.addConstructorArg(username);
    }
}

The classes org.opensaml.xml.util.XMLHelper and org.opensaml.xml.util.DatatypeHelper provide a lot of helper methods for dealing with XML and various data types. For example, XMLHelper contains methods for getting attribute values as booleans, Lists, and other things.

Step 5: Define a NamespaceHandler

The last class to implement for Spring is the NamespaceHandler. This is the class that instructs Spring to execute the BeanDefinitionParser associated with a given extension type in a configuration file.

  1. Create a class that extends edu.internet2.middleware.shibboleth.common.config.BaseSpringNamespaceHandler
  2. Add a public static final String NAMESPACE field whose value is the Schema namespace for your extension
  3. Implement the init() method and, for each plugin within this namespace, call registerBeanDefinitionParser(QName, BeanDefinitionParser) where the first argument is the SCHEMA_TYPE field on a BeanDefinitionParser implementation and the second argument is a new'ed instance of that BeanDefinitionParser.
Namespace Handler for Example Login Handler
package org.example.shibboleth.authn.config;

... imports ...

public class AuthnNamespaceHandler extends BaseSpringNamespaceHandler {

    public static final String NAMESPACE = "urn:mace:example.org:shibboleth:authn";

    public void init() {
        registerBeanDefinitionParser(IPAddressLoginHandlerBeanDefinitionParser.SCHEMA_TYPE, new IPAddressLoginHandlerBeanDefinitionParser());
        // other calls to registerBeanDefinitionParser for other extensions in this namespace would go here
    }
}

Step 6: Create Spring Configuration Files

Finally two files; spring.schemas and spring.handlers. Both files go in to the META-INF directory of the JAR and the $PROJ_HOME/main/resources/META-INF directory within the project. Both files may have multiple, line-terminated, entries.

The spring.schemas file tells Spring where to look for the schema file for a particular namespace. The format of the file is simply the XML namespace URI followed by an "=" followed by the location of the schema file within the JAR (i.e schema/FILE.xsd). Do not include the a preceding "/", the files are not at the root of the filesystem.

spring.schemas file for Example Login Handler
urn\:mace\:example.org\:shibboleth\:authn = schema/authn.xsd

The spring.handlers file tells Spring which NamespaceHandler to use for a particular namespace. The format of the file is the XML namespace URI followed by an "=" followed by the fully qualified class name of the NamespaceHandler implementation.

spring.handlers file for Example Login Handler
urn\:mace\:example.org\:shibboleth\:authn = org.example.shibboleth.authn.config.AuthnNamespaceHandler

Note that because Spring uses the java.util.Properties class to load these files the ':' character must be escaped with a backslash. This character has a special meaning for property files loaded in this manner.

Step 7: Write Installation Instructions

The expected procedure for installing an extension in to the IdP is to have the deployer copy the necessary material in to the expanded IdP distribution archive and then re-run the IdP installation script. This ensures the libraries get bundled in to the WAR file and placed where command line tools may use them. However the exact installation steps will depend on the nature of the extension. For example, an extension that includes not only custom plugins that use the Spring architecture but also new Servlets/JSP pages will require the deployer to edit the web.xml document and/or copy JSP files before rebuilding the war.

Appendix: Shibboleth IdP Extension Points

Attribute Filter Policy Rules

Plugin point for creating a new match function used by the Attribute Filter Policy policy requirement, permit value, and deny value rules.

Attribute Definition

Plugin point for creating new attribute resolver attribute definitions.

Attribute Encoder

Plugin point for creating a new attribute resolver attribute encoder.

Data Connector

Plugin point for creating a new attribute resolver data connector.

Principal Connector

Plugin point for creating a new attribute resolver principal connector.

Profile Handler

Plugin point for creating a new profile handler.

Login Handler

Plugin point for creating a new login handler.

Profile Configuration

Plugin point for creating a new relying party profile configuration.

Metadata Provider

Plugin point for creating a new relying party metadata provider.

Metadata Provider Filter

Plugin point for creating a new metadata provider filter.

Credential

Plugin point for creating a new plugin for reading credentials.

Trust Engine

Plugin point for creating a new trust engine.

Security Policy

Plugin point for creating a new security policy.

Security Policy Rule

Plugin point for creating a new security policy rule.

Resource

Plugin point for creating a new resource.

Resource Filter

Plugin point for creating a new resource filter.

Service

Plugin point for creating a new service.

  • No labels