JIT Custom User Provisioning in OIF / SP cont’d

This article is a continuation of my previous entry about User Provisioning in OIF/SP, where I described how to use the built-in module in OIF/SP to create user records during a Federation SSO operation, if the user did not have a local account.

In this article, I will show how to build a custom User Provisioning module in OIF/SP. This will be based on the OAM/OIF 11.1.2.2.0 Developer’s Guide, chapter 16, which describes how to develop such a module.

I will focus here on how to:

  • Implement the plugin
  • Compile it
  • Package it
  • Upload the plugin to OAM
  • Configure OIF to use the newly uploaded plugin

For this example, I will use the sample code listed in the OAM/OIF 11.1.2.2.0 Developer’s Guide.

Enjoy the reading!

Custom User Provisioning Module


The User Provisioning framework allows the implementation of a custom plugin that implements the interfaces defined by the framework.

A custom User Provisioning plugin is made of the following:

  • One or more Java class implementing the custom User Provisioning plugin. One of the class will extend the oracle.security.fed.plugins.fed.provisioning.OIFUserProvisioningPlugin class
  • A MANIFEST.MF file describing the Java classes
  • An XML file describing the plugin

Those three elements will be bundled in a JAR file that is then uploaded to the OAM server via the OAM Administration Console. Once uploaded and activated, it can be used at runtime by OIF/SP.

Java Class

The class implementing the custom User Provisioning module must adhere to the following:

  • Extend the oracle.security.fed.plugins.fed.provisioning.OIFUserProvisioningPlugin class
  • Implement the following methods:
    • public ExecutionStatus process(UserContext context) throws UserProvisioningException
      • This method is invoked when a user record needs to be created
      • Must return a status (failure or success)
      • In our example, this method will
        • Check that the user data contained in the UserContext has the required information
        • Open a connection to the LDAP server
        • Create a user record
    • public String getPluginName()
      • Returns the name of the custom User Provisioning module
      • In our example it will return "CustomProvisioningPlugin"
    • public String getDescription()
      • Returns a description of the custom User Provisioning module
      • In our example it will return "Custom Provisioning Plugin"
    • public Map<String, MonitoringData> getMonitoringData()
      • Not used in a User Provisioning flow
      • In our example it will return null
    • public boolean getMonitoringStatus()
      • Not used in a User Provisioning flow
      • In our example it will return false
    • public int getRevision()
      • Must be the same value than the version specified in the manifest file
      • In our example it will return 10
    • public void setMonitoringStatus(boolean status)
      • Not used in a User Provisioning flow
      • In our example this method will be empty
  • The class will have to implement the org.osgi.framework.BundleActivator interface and the following methods:
    • public void start(BundleContext arg0) throws Exception
      • In our example this method will be empty
    • public void stop(BundleContext arg0) throws Exception
      • In our example this method will be empty
  • Optionally, if the plugin needs to read configuration data from the Plugin settings, the initialize() method can be implemented:
    • public ExecutionStatus initialize(PluginConfig config)
    • In our example, this method will read the user base DN from a setting entry in the plugin config: USER_BASE_DN

The following code is an example of the custom plugin called CustomerUserProvisioning.

package userprov;

import java.util.Collection;
import java.util.Hashtable;
import java.util.Map;

import javax.naming.Context;
import javax.naming.directory.BasicAttribute;
import javax.naming.directory.BasicAttributes;
import javax.naming.directory.InitialDirContext;

import oracle.security.am.plugin.ExecutionStatus;
import oracle.security.am.plugin.MonitoringData;
import oracle.security.am.plugin.PluginConfig;
import oracle.security.fed.plugins.fed.provisioning.OIFUserProvisioningPlugin;
import oracle.security.fed.plugins.fed.provisioning.UserContext;
import oracle.security.fed.plugins.fed.provisioning.UserProvisioningException;

public class CustomUserProvisioning extends OIFUserProvisioningPlugin implements org.osgi.framework.BundleActivator
{
    private String userBaseDN = null;

    public ExecutionStatus initialize(PluginConfig config)
    {
        ExecutionStatus status = super.initialize(config);
        if (status.getStatus() == ExecutionStatus.SUCCESS.getStatus())
            userBaseDN = (String)config.getParameter("USER_BASE_DN");
        return status;
    }

    public ExecutionStatus process(UserContext context) throws UserProvisioningException
    {
        try
        {
            // get user data
            // after OIF/SP processing of the attributes sent by the IdP, we expect
            // givenname, sn, mail as well as fed.nameidvalue which would contain
            //  the userid
            Map assertionAttributes = context.getAttributes();
            Collection collection = (Collection)assertionAttributes.get("givenname");
            String firstname = (String)(collection.size() > 0 ? collection.iterator().next() : null);
            collection = (Collection)assertionAttributes.get("sn");
            String lastname = (String)(collection.size() > 0 ? collection.iterator().next() : null);
            collection = (Collection)assertionAttributes.get("mail");
            String email = (String)(collection.size() > 0 ? collection.iterator().next() : null);
            collection = (Collection)assertionAttributes.get("fed.nameidvalue");
            String userid = (String)(collection.size() > 0 ? collection.iterator().next() : null);

            // check that the required attributes are present. If not, return an error
            if (firstname == null || firstname.length() == 0 ||
                lastname == null || lastname.length() == 0 ||
                email == null || email.length() == 0 ||
                userid == null || userid.length() == 0)
            {
                // failure
                return ExecutionStatus.FAILURE;
            }

            String ldap = "ldap://adc00pcc.us.oracle.com:11389";
            String username = "cn=orcladmin";
            String password = "welcome1";

            // connects to the LDAP directory
            Hashtable env = new Hashtable();
            env.put(Context.INITIAL_CONTEXT_FACTORY,
                "com.sun.jndi.ldap.LdapCtxFactory");
            env.put(Context.PROVIDER_URL, ldap);
            if (ldap.toLowerCase().startsWith("ldaps"))
                env.put(Context.SECURITY_PROTOCOL, "ssl");
            env.put(Context.SECURITY_AUTHENTICATION, "simple");
            env.put(Context.REFERRAL, "follow");
            env.put(Context.SECURITY_PRINCIPAL, username);
            env.put(Context.SECURITY_CREDENTIALS, password);     
            InitialDirContext ldapContext = new InitialDirContext(env);

            // create the user entry
            BasicAttributes attributes = new BasicAttributes();

            // object classes
            BasicAttribute objClassAttr = new BasicAttribute("objectClass");
            objClassAttr.add("person");
            objClassAttr.add("organizationalPerson");
            objClassAttr.add("inetOrgPerson");
            objClassAttr.add("top");
            attributes.put(objClassAttr);

            // uid attr
            attributes.put(new BasicAttribute("uid", userid));
            // first name
            attributes.put(new BasicAttribute("givenname", firstname));
            // last name
            attributes.put(new BasicAttribute("sn", lastname));
            // email
            attributes.put(new BasicAttribute("mail", email));
            // DN: cn will be set to userID
            String dn = "cn=" + userid + (userBaseDN != null &&
                 userBaseDN.length() > 0 ? "," + userBaseDN : "");

            // create the user record
            ldapContext.createSubcontext(dn, attributes);

            // return success
            return ExecutionStatus.SUCCESS;
        }
        catch (Exception ex)
        {
            ex.printStackTrace();
            return ExecutionStatus.FAILURE;
        }
    }

    public String getPluginName()
    {
        return "CustomProvisioningPlugin";
    }

    public String getDescription()
    {
        return "Custom Provisioning Plugin";
    }

    public Map<String, MonitoringData> getMonitoringData()
    {
        return null;
    }

    public boolean getMonitoringStatus()
    {
        return false;
    }

    public int getRevision()
    {
        return 10;
    }

    public void setMonitoringStatus(boolean status)
    {
    }

    public void start(BundleContext arg0) throws Exception
    {
    }

    public void stop(BundleContext arg0) throws Exception
    {
    }
}

Plugin Registration File

The custom User Provisioning plugin must be defined in a plugin XML file such as:

<Plugin type="User Provisioning">
<author>uid=admin</author>
<email>admin@example</email>
<creationDate>08:00:00,2014-01-15</creationDate>
<description>Custom Provisioning Plugin</description>
<configuration>
<AttributeValuePair>
  <Attribute type="string" length="100">USER_BASE_DN</Attribute>
  <mandatory>false</mandatory>
  <instanceOverride>false</instanceOverride>
  <globalUIOverride>false</globalUIOverride>
  <value> </value>
</AttributeValuePair>
</configuration>
</Plugin>

Important Note: the XML file must have the same name as the class implementing the plugin, in this case CustomUserProvisioning.xml

See the OAM/OIF 11.1.2.2.0 Developer’s Guide for more information

Manifest File

Before packaging the custom User Provisioning plugin in a JAR file, a MANIFEST.MF must be defined such as:   

Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: CustomUserProvisioning
Bundle-SymbolicName: CustomUserProvisioning
Bundle-Version: 10
Bundle-Activator: userprov.CustomUserProvisioning
Import-Package: org.osgi.framework;version="1.3.0",oracle.security.fed
.plugins.fed.provisioning,javax.naming,javax.naming.directory,oracle.
security.am.plugin
Bundle-RequiredExecutionEnvironment: JavaSE-1.6

See the OAM/OIF 11.1.2.2.0 Developer’s Guide for more information

Note: the manifest file must include the Import-Package property which lists all the packages that are used in the plugin

Building the Plugin


Compiling

The following JAR files from the OAM deployment need to be used for compilation:

  • felix.jar

  • oam-plugin.jar

  • fed.jar

These files are found in the following locations:

  • felix.jar: $IAM_HOME/oam/server/lib/plugin/felix.jar
  • oam-plugin.jar: $IAM_HOME/oam/server/lib/plugin/oam-plugin.jar
  • fed.jar: in the $DOMAIN_HOME/servers/MANAGED_INSTANCE_NAME/tmp/_WL_user/oam_server/RANDOM_STRING/APP-INF/lib, with RANDOM_STRING being a directory name with random characters, for example 88g74i in my test installation

In my example, I put the CustomerUserProvisioning.java file in a src/userprov folder:

bash-4.1$ ls -l src/userprov/
total 8
-rw-r--r-- 1 root root 4717 Mar 1 11:42 CustomUserProvisioning.java

To compile, execute the following command:

$JDK_HOME/bin/javac -cp $IAM_HOME/oam/server/lib/plugin/felix.jar:$IAM_HOME/oam/server/lib/plugin/oam-plugin.jar:/tmp/oam-server/APP-INF/lib/fed.jar src/userprov/*.java

Packaging the Custom Plugin

I created the MANIFEST.MF in the current directory based on the content listed in the previous section, and the CustomUserProvisioning.xmlin the src directory, which contains the plugin definition listed in the previous section.

find
.
./MANIFEST.MF
./src
./src/userprov
./src/userprov/CustomUserProvisioning.class
./src/userprov/CustomUserProvisioning.java
./src/CustomUserProvisioning.xml

To create the CustomUserProvisioning.jar JAR file that will contain the plugin and the required files, execute the following command:

jar cfvm CustomUserProvisioning.jar MANIFEST.MF -C src/ .
added manifest
adding: userprov/(in = 0) (out= 0)(stored 0%)
adding: userprov/CustomUserProvisioning.class(in = 3991) (out= 1954)(deflated 51%)
adding: userprov/CustomUserProvisioning.java(in = 4717) (out= 1401)(deflated 70%)
adding: CustomUserProvisioning.xml(in = 486) (out= 266)(deflated 45%)

This will create the CustomUserProvisioning.jar. To view the contents of the file:

unzip -l CustomUserProvisioning.jar
Archive:  CustomUserProvisioning.jar
  Length      Date    Time    Name
---------  ---------- -----   ----
        0  03-01-2014 14:32   META-INF/
      404  03-01-2014 14:32   META-INF/MANIFEST.MF
        0  03-01-2014 12:10   userprov/
     3991  03-01-2014 12:10   userprov/CustomUserProvisioning.class
     4717  03-01-2014 11:42   userprov/CustomUserProvisioning.java
      486  03-01-2014 14:04   CustomUserProvisioning.xml
---------                     -------
     9598                     6 files

Important Note: the JAR file must have the same name as the class implementing the plugin, in this case CustomUserProvisioning.jar

Deploying the Custom Provisioning Module


Perform the following steps to deploy the custom User Provisioning plugin in OAM:

  • Go to the OAM Administration Console: http(s)://oam-admin-host:oam-admin-port/oamconsole
  • Navigate to Access Manager -> Plugins
  • Click Import Plug-In
  • Select the plugin JAR file (CustomUserProvisioning.jar in this example)

The plugin will be in an uploaded state:

You will need to distribute the plugin to the runtime OAM servers and activate it:

  • Select the plugin
  • Click Distribute Selected
  • The Activation Status tab of the plugin will show the state of the plugin

You will need to activate the plugin:

  • Select the plugin
  • Click Activate Selected
  • The Activation Status tab of the plugin will show the state of the plugin

Finally, the plugin will need to be configured for user base DN. Perform the following steps:

  • Select the plugin
  • Click on the Configuration Parameters tab of the plugin
  • Enter the User Base DN (for example for the directory I am using: ou=users,dc=us,dc=oracle,dc=com)
  • Save

Using the Custom User Provisioning Module


Enabling the User Provisioning in OIF

To enable/disable User Provisioning in OIF/SP, execute the following steps:

  • Enter the WLST environment by executing:
    $IAM_ORACLE_HOME/common/bin/wlst.sh
  • Connect to the WLS Admin server:
    connect()
  • Navigate to the Domain Runtime branch:
    domainRuntime()
  • Update the userprovisioningenabled property to:
    • Enable User Provisioning in OIF/SP:
      putBooleanProperty("/fedserverconfig/userprovisioningenabled", "true")
    • Disable User Provisioning in OIF/SP:
      putBooleanProperty("/fedserverconfig/userprovisioningenabled", "false")
  • Exit the WLST environment:
    exit()

Configuring OIF to use the Custom Plugin

To configure OIF/SP to use the custom plugin, execute the following steps:

  • Enter the WLST environment by executing:
    $IAM_ORACLE_HOME/common/bin/wlst.sh
  • Connect to the WLS Admin server:
    connect()
  • Navigate to the Domain Runtime branch:
    domainRuntime()
  • Update the userprovisioningplugin property to:
    • Configure OIF to use the custom plugin, set the property to the plugin name (in our example CustomUserProvisioning):
      putStringProperty("/fedserverconfig/userprovisioningplugin", "CustomUserProvisioning")
    • Configure OIF to use the built-in User Provisioning module:
      putStringProperty("/fedserverconfig/userprovisioningplugin", "FedUserProvisioningPlugin")
  • Exit the WLST environment:
    exit()

Testing Setup


I will use the same SAML 2.0 Federation setup that was configured in the previous articles, where:

  • OIF acts as a Service Provider
  • The IdP (AcmeIdP) will send a SAML Assertion with
    • NameID set to userID
    • Attributes sent:
      • email set to user’s email address
      • fname set to user’s first name
      • surname set to user’s last name
      • title set to user’s last job title
  • OIF/SP configured with an IdP Attribute Profile to
    • Map fname to givenname
    • Map surname to sn
    • Map email to mail
  • User alice will be used at the IdP, while no user account for alice exists at OIF/SP:
    • userID: alice
    • email: alice@oracle.com
    • first name: Alice
    • last name: Appleton
    • title: manager

During a SAML 2.0 Federation SSO with the remote IdP partner, the XML SAML Response with the Assertion sent back by the IdP would be:

<samlp:Response ..>
    <saml:Issuer ...>http://acme.com/idp</saml:Issuer>
    <samlp:Status>
        <samlp:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Success"/>
    </samlp:Status>
    <saml:Assertion ...>
        <saml:Issuer ...>http://acme.com/idp</saml:Issuer>
        <dsig:Signature ...>
        ...
        </dsig:Signature>
        <saml:Subject>
            <saml:NameID ...>alice</saml:NameID>
            ...
        </saml:Subject>
        <saml:Conditions ...>
         ...
        </saml:Conditions>
        <saml:AuthnStatement ...>
        ...
        </saml:AuthnStatement>
        <saml:AttributeStatement ...>
            <saml:Attribute Name="email" ...>
                <saml:AttributeValue ...>alice@oracle.com</saml:AttributeValue>
            </saml:Attribute>
            <saml:Attribute Name="title" ...>
                <saml:AttributeValue ...>manager</saml:AttributeValue>
            </saml:Attribute>
            <saml:Attribute Name="surname" ...>
                <saml:AttributeValue ...>Appleton</saml:AttributeValue>
            </saml:Attribute>
            <saml:Attribute Name="fname" ...>
                <saml:AttributeValue ...>Alice</saml:AttributeValue>
            </saml:Attribute>
        </saml:AttributeStatement>
    </saml:Assertion>
</samlp:Response>

The result of the processing of the SAML 2.0 Assertion by OIF/SP will show the transformed attributes as well as the NameID

Test


After Federation SSO, the user record for alice was created:

dn: uid=alice,ou=users,dc=us,dc=oracle,dc=com
mail: alice@oracle.com
givenName: Alice
objectClass: person
objectClass: inetOrgPerson
objectClass: organizationalPerson
objectClass: top
uid: alice
cn: alice
sn: alice
sn: Appleton


In my next article, I will describe how to integrate OIF (11.1.2.2.0 or later) as an IdP with Office365 for Federation SSO using the SAML 2.0 protocol.
Cheers,
Damien Carru

Comments:

Hi Damien,

Thx for the explanantion. I was wondering if it was possible to use the OAM identity API to create users in the LDAP. What is the best way to fully use features from OAM like: configuration settings via the console and LDAP connection pooling ?

Regards,

Bert

Posted by Bert on February 12, 2015 at 08:38 AM EST #

Hi,

Unfortunately, the OAM Identity API is not a public API as far as I know and thus cannot be used to interact with the LDAP directory.

The plugin framework allows for some properties to be defined, so that they can be:
- set from the OAM Administration console
- saved in the OAM configuration service

But regarding LDAP connection pooling, in the current implementation of the server, the plugin would need to manage it.

Posted by Damien on April 16, 2015 at 11:33 AM EDT #

Post a Comment:
  • HTML Syntax: NOT allowed
About

Damien Carru is a member of the Oracle Identity Management organization, focusing on Federation and SSO. This blog will cover Federation use cases involving Oracle Access Manager, Oracle Identity Federation and Oracle Security Token Service

Search

Categories
Archives
« April 2015
SunMonTueWedThuFriSat
   
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
17
18
19
20
21
22
23
24
25
26
27
28
29
30
  
       
Today