SAML SSO SDK Extensions

A plug-in is delivered with Datameer that provides the basic infrastructure which allows Datameer to participate in an SAML SSO environment as a service provider. The plug-in introduces a few extension point to allow further customization by customer plug-ins in order to provide the flexibility to operate in different environments. When configuring the SSO Authenticator, an administration can choose which of these extension points to use and provide configuration for them at that time.

Requirements

  • A functioning identity provider (in this case a simpleSAMLphp instance), capable of providing its metadata as an XML file which describes the services it provides.
  • A public/private key pair for the Datameer server stored in a Java KeyStore on the local file system of the Datameer server.
  • A public/private key pair for the Identity Provider server stored in PEM format in <SIMPLE_SAML>/certs and configured in config.php.
  • A naming scheme for the Datameer service provider: entity ID, base URL, and alias.
  • An implementation of the UserProvider extension point.
  • An implementation of the SamlAuthenticatorRepository extension point.

Plug-in API

Extension points of plug-in SAML SSO

ConfigurableExtension

All interfaces below are extensions of ConfigurableExtension which contains the following methods:

/**
 * Add properties to the UI for this source
 *
 * @param group the PropertyGroupDefinition 
 */
public void configure(PropertyGroupDefinition group);

/**
 * @return the display name for this source
 */
public String getLabel();

/**
 * Initialize the Extension based on the current configuration
 *
 * @param configuration the configuration resulting from configure()
 * @param context the current SAML authentication context
 */
public void initialize(GenericConfiguration configuration, Context context);

User Provider (Required)

This is the extension which provides the actual Datameer User instance based off a validated SAML assertion. Once the assertion has been validated and accepted, the plug-in defers to this extension to provide the fully populated user object (username, email, groups, roles) based on the metadata contained within the original assertion.

public interface UserProvider extends ConfigurableExtension {
 
    public static final String EXTENSION_POINT_ID = "datameer.plugin.samlsso.authenticator.UserProvider";
 
    /**
     * Provides a User based on the validated SAML Assertion
     *
     * N.B., the returned User object's Role Set will be validated against the stored set of Roles,
     * implementations should use the provided Context to access the current Set
     *
     * @param name the NameId value for the principal
     * @param credential contains details of the assertion used to authenticate the user
     * @return a User populated with metadata and Role's from existingRoles
     */
    public User createUser(String name, SAMLCredential credential);
}

SAMLCredential

The SAMLCredential parameter is an object containing information about the SAML authentication using opensaml's API classes:

  • NameId : NameId of the authenticated user.

  • Assertion : Assertion used to validate the user.

  • List<Attribute>: Metadata attributes contained in the validated assertion.

SamlAuthenticatorRepository (Required)

Is an extension that provides functions to query users and groups for the Authenticator. A user that has valid SAMLCredential must be resolvable via the configured SAML Authenticator Repository, otherwise the user is rejected.

/**
 * Provides information about users and groups that are available remotely (e.g. via LDAP).
 */
public interface SamlAuthenticatorRepository extends AuthenticatorRepository, ConfigurableExtension {
    String EXTENSION_POINT_ID = "datameer.das.plugin.samlsso.directory.Directory";
}

The interface datameer.plugin.samlsso.directory.SamlAuthenticatorRepository extends datameer.dap.sdk.usermanagement.auth.AuthenticatorRepository which is available from the Datameer SDK.

package datameer.dap.sdk.usermanagement.auth;

import datameer.dap.sdk.usermanagement.Group;
import datameer.dap.sdk.usermanagement.NoSuchGroupException;
import datameer.dap.sdk.usermanagement.NoSuchUserException;

/**
 * Defines operations to query groups and users in a repository.
 * <p>
 * Provides only accessor methods, because an authenticator should never modify groups and users in
 * a repository.
 * <p>
 * An repository can only be used when option {@link AuthenticatorFeatures#hasSearchableUsers()} or
 * {@link AuthenticatorFeatures#hasSearchableGroups()} is turned on (returns <code>true</code>).
 */
public interface AuthenticatorRepository {
    /**
     * Counts users that are assigned to one of the given groups.
     * 
     * @param groups
     *            groups where the users must be a member of
     * @return number of users
     */
    long countUsersByGroups(Iterable<? extends Group> groups);
    /**
     * Checks that a group with a specific name exists.
     * 
     * @param groupName
     *            name of the group to check
     * @return <code>true</code> if the group with name exists, otherwise <code>false</code>
     */
    boolean existsGroupByName(String groupName);
    /**
     * Checks that a user with a specific name exists.
     * 
     * @param username
     *            name of user to check
     * @return <code>true</code> if exists, otherwise <code>false</code>
     */
    boolean existsUserByName(String username);
    /**
     * Finds available groups whether they are allowed to authenticate or not.
     * 
     * @return a list of groups
     */
    Iterable<AuthenticatorGroup> findAllGroups();
    /**
     * Finds available users whether they are allowed to authenticate or not.
     * 
     * @return a list of users
     */
    Iterable<AuthenticatorUser> findAllUsers();
    /**
     * Finds a group by the given name.
     * <p>
     * Check the existence of a group first by calling {@link #existsGroupByName(String)}.
     * 
     * @param groupName
     *            name of the group to search
     * @return the found group and never <code>null</code>
     * 
     * @throws NoSuchGroupException
     *             if the group with given name not exists
     */
    AuthenticatorGroup findGroupByName(String groupName);
    /**
     * Finds a user by the given name.
     * <p>
     * Check the existence of an user first by calling {@link #existsUserByName(String)}.
     * 
     * @param username
     *            name of the user to search
     * @return the found user and never <code>null</code>
     * 
     * @throws NoSuchUserException
     *             if the user with given name not exists
     */
    AuthenticatorUser findUserByName(String username);
    /**
     * Finds users that are assigned to one of the given groups.
     * 
     * @param groups
     *            groups where the users must be a member of
     * @return a list of users
     */
    Iterable<AuthenticatorUser> findUsersByGroups(Iterable<? extends Group> groups);
    /**
     * Finds users that contains a specified text in username or email.
     * <p>
     * The search should be performed case-insensitive.
     * 
     * @param text
     *            character sequence that must be present in username or email
     * @return a list of users
     */
    Iterable<AuthenticatorUser> findUsersThatContainsInNameOrEmail(String text);
}

KeyManagerFactory (Optional)

An optional extension point used to create a KeyManager instance. KeyManager is an extension to the OpenSAML CredentialResolver interface. This is used for loading credentials for use by the plug-in when signing assertions. A default implementation based on a local file system based Java KeyStore is provided by the plug-in, but Datameer users can implement their own in order load keys from another source.

/**
 * @return the KeyManager created based on configuration
 */
public KeyManager createKeyManager();

MetadataSource (Optional)

An abstraction that allows clients to customize how IDP metadata is provided to Datameer. It creates an OpenSAML MetadataProvider based on configuration driven by the UI.  

/**
 * provide the MetadataProvider
 *
 * @return the OpenSAML MetadataProvider
 */
public MetadataProvider getProvider();

/**
 * Validate the configuration of the MetadataSource, will be
 * called when changing configuration
 * @throws Exception if the validation fails
 */
public void validate() throws Exception;

The SAML plug-in ships with three metadata sources which should cover most usage: 

  • Server file system  Provide a path to the XML file on the Datameer server.
  • File upload: Upload the metadata file to the Datameer server from your desktop.
  • HTTP URL: Provide a URL from which to load the XML metadata.
     
Note all three implementations are refreshing, i.e., the source metadata is periodically refreshed to pick up any changes.

SAML Integration

SAML integration requires Datameer users to build two components to snap into the system:

  • A mechanism to look up who the user is based on the SAML return token. This can be done by taking the account ID returned from the SAML token and looking up that user in an LDAP or AD and then creating a Datameer shadow user at time of authentication. 
  • A mechanism to determine what groups are available to Datameer for permissions. This group directory is also often connected into an LDAP.

Integration flow

  1. SAML starts and interrogates what groups it can put users in and then caches those groups. 
  2. The user going to Datameer is missing a cookie and is redirected to the SSO login page. They then log in, get a token, and are returned to the Datameer server. 
  3. On a back channel, Datameer asks SAML for the token and about the user. The SAML response looks like: <samlauth><id>123</id><domain>mydomain</domain><username>bob</username></samlauth>
  4. The user's custom UserProvider code parses the XML response. In this case, because the SAML response lacks the group memberships and email, the domain and username are used to look up the user in an LDAP to finish assembling the user and their roles and group memberships. 
  5. That shadow user is logged into Datameer and is destroyed when the user logs out.

Logging Out

Sessions are terminated depending on how the custom plug-in provided to initiate that session is coded.

To validate the logout action, log out of Datameer after having signed in with SAML. Once logged out, try to validate the generated SAML token by refreshing. This functionality is working as intended if now it asks to re-authenticate.

SSO Redirect

The SSO login URL is owned by the SAML identity provider. Datameer only accepts and validates the credentials and token generated by the IDP. Users being redirected to the Datameer local URL after entering credentials on the SSO login page should investigate their SAML code.