Integrating External User Repositories

For many production scenarios the Infinity Process Engine will need to be integrated with previously existing user registries. This integration includes the following three aspects:

All these aspects can be customized by providing implementations of Infinity Process Platform -defined interfaces.

Authenticating Users against an External Registry

For most common integration scenarios Infinity Process Platform provides implementations for authentication against both, a Infinity Process Platform internal user registry and an external user registry, accessible using the default JAAS API.

However, sometimes it may be required to provide custom authentication using username, password and user's properties credentials. The properties map specify the user's realm, partition and domain. If these values are not specified, the map is passed empty. To support the authentication, Infinity Process Platform provides the interface:

org.eclipse.stardust.engine.core.spi.security.ExternalLoginProvider

declaring a single login(id, password, properties) method. Any implementation will be along the following simple pattern:

public class MyExternalLoginProvider implements ExternalLoginProvider
{
  public ExternalLoginResult login(String id, String password, Map properties)
  {
       // delegate authentication to external registry
    if (/* authentication succeeded */)
    {
      return ExternalLoginResult.testifySuccess();
    }
    else
    {
      if (/* invalid password */)
      {
        return ExternalLoginResult.testifyFailure(new LoginFailedException("The password is incorrect.", LoginFailedException.INVALID_PASSWORD));
      }
      else if (/* unknown realm */)
      {
        return ExternalLoginResult.testifyFailure(new LoginFailedException("The realm is unknown.", LoginFailedException.UNKNOWN_REALM));
      }
      else if (/* unknown domain */)
      {
        return ExternalLoginResult.testifyFailure(new LoginFailedException("The domain is unknown.", LoginFailedException.UNKNOWN_DOMAIN));
      }
      else if (/* unknown partition */)
      {
        return ExternalLoginResult.testifyFailure(new LoginFailedException("The partition is unknown.", LoginFailedException.UNKNOWN_PARTITION));
      }
      else
      {
        return ExternalLoginResult.testifyFailure(new LoginFailedException("Login failed", LoginFailedException.UNKNOWN_REASON));
} } } }

If authentication fails or was canceled, the provider may signal various common reasons, causing Infinity Process Platform to react in a proper way. For further details, please consult the included JavaDoc for the ExternalLoginProvider class.

To enable the use of your login provider, you need to configure the following properties:

Security.Authentication.Mode = internal
Security.Authentication.LoginService = <<fully.qualified.class.name>>

In case an exception is thrown in the class defined in the Security.Authentication.LoginService property, all exceptions should be caught and should return:

return org.eclipse.stardust.engine.core.spi.security.ExternalLoginResult.testifyFailure(new org.eclipse.stardust.common.error.LoginFailedException(...));

Synchronizing User, User Group and Department Attributes from an External Registry

When Infinity Process Platform operates against an external user registry, still some assorted attributes of users, user groups and departments need to be replicated to the audit trail. But as the details of obtaining such attributes are highly registry-dependent, the engine requires support from the integrator. The contract for providing user, user group and department attributes is defined by the following abstract class:

public abstract class DynamicParticipantSynchronizationProvider
{
   /**
    * Resolves a user in the external registry and provides its attributes.
    *
    * @param account The identity of the external user.
    * @return A (probably snapshot) of the users attributes.
    * 
    * @see #provideUserConfiguration(String realm, String account)
    */
   public abstract ExternalUserConfiguration provideUserConfiguration(String account);
   
   /**
    * Resolves a user in the external registry and provides its attributes.
    * 
    * The default implementation just calls {@link #provideUserConfiguration(String account)},
    * effectively ignoring the realm.
    *
    * @param realm The security realm of the external user.
    * @param account The identity of the external user.
    * @param properties The login properties like partition, domain, ...
    * @return A (probably snapshot) of the users attributes.
    * 
    * @see #provideUserConfiguration(String account)
    */
   public ExternalUserConfiguration provideUserConfiguration(String realm,
         String account, Map properties)
   {
      return provideUserConfiguration(account);
   }
   
   /**
    * Resolves a user group in the external registry for current partition and provides
    * its attributes.
    *
    * @param groupId The identity of the external user group.
    * @return A (probably snapshot) of the user groups attributes.
    */
   public ExternalUserGroupConfiguration provideUserGroupConfiguration(String groupId)
   {
      return null;
   }
   
   /**
    * Resolves a user group in the external registry for given partition and provides
    * its attributes.
    *
    * @param groupId The identity of the external user group.
    * @param properties The login properties like partition, domain, ... 
    * @return A (probably snapshot) of the user groups attributes.
    */
   public ExternalUserGroupConfiguration provideUserGroupConfiguration(String groupId,
         Map properties)
   {
      return provideUserGroupConfiguration(groupId);
   }
   
   /**
    * Resolves a department in the external registry for given partition and provides
    * its attributes.
    *
    * @param departmentKey The identity of the external department.
    * @return A (probably) snapshot of the department attributes.
    */
   public ExternalDepartmentConfiguration provideDepartmentConfiguration(String participantId,
         List<String> departmentKey)
   {
      return null;
   }
   
   /**
    * Resolves a department in the external registry for given partition and provides
    * its attributes.
    *
    * @param departmentKey The identity of the external department.
    * @param properties The login properties like partition, domain, ...
    * @return A (probably) snapshot of the department attributes.
    */
   public ExternalDepartmentConfiguration provideDepartmentConfiguration(String participantId,
         List<String> departmentKey, Map<String, ?> properties)
   {
      return provideDepartmentConfiguration(participantId, departmentKey);
   }
}

The methods provideUserConfiguration, provideUserGroupConfiguration and provideDepartmentConfiguration resolve a user, user group or department respectively in the external registry and provide its attributes.

Any implementation is eventually asked to provide a reference to an attribute provider for users, user groups or departments. As support for user groups is somewhat an advanced scenario, there is a default implementation doing nothing.

The types ExternalUserConfiguration, ExternalUserGroupConfiguration and ExternalDepartmentConfiguration themselves are also abstract classes, allowing to provide both, an adapter to the external registry's query interfaces or a snapshot of previously obtained attributes.

If the user, user groups or departments queried for are not existing in the registry, or the registry is not available for some reason, return null.

To set your synchronization provider, you need to configure the following property:

Security.Authorization.SynchronizationProvider = <<fully.qualified.class.name>>

In case an exception is thrown in the DynamicParticipantSynchronizationProvider class, which is an instance of the property Security.Authorization.SynchronizationProvider, all exceptions should be caught. For further details, please consult the included JavaDoc for the DynamicParticipantSynchronizationProvider class.

Notes

Realms

Note, that synchronization takes only place on certain events like logging in or creating user details objects. During a user synchronization, a realm for the user is specified. If the user realm already exists, an update in the database is performed automatically, otherwise a new realm is created.

Synchronization when working on archive audit trail

Note, that it is not possible to perform a synchronization if you work on the archive audit trail.

Providing Department Configuration

The method provideDepartmentConfiguration(...) is only invoked if the ExternalUserConfiguration object obtained via one of the provideUserConfiguration(...) methods returns a valid Set<GrantInfo> when invoking its getModelParticipantsGrants() method. Thus, the key is to first provide a valid department hierarchy set (Set<GrantInfo>) in the ExternalUserConfiguraton, which is subsequently used to retrieve department details via provideDepartmentConfiguration(...)..

Examples

Java Property File Synchronization Example

For an example implementation of both, a login provider and participant synchronization, please refer to the following example, which you can download from here:

LDAP Synchronization Example

For an example implementation to retrieve authorization information from an LDAP repository, please refer to the following example, which you can download from here:

Authorization for Users from an External Registry

DynamicParticipantSynchronizationStrategy

To determine if a given user, user group or department needs to be synchronized with an external registry, Infinity Process Platform provides the interface:

org.eclipse.stardust.engine.core.spi.security.DynamicParticipantSynchronizationStrategy

This interface provides the following implementation:

public abstract class DynamicParticipantSynchronizationStrategy
{
   /**
    * Checks if this user needs to be synchronized.
    * 
    * @param user the user to be checked.
    * @return 
    */
   public abstract boolean isDirty(IUser user);

   /**
    * Callback method to inform the SynchronizationStrategy that the user have been
    * successfully synchronized.
    * 	
    * @param user the user that has been synchronized.
    */
   public abstract void setSynchronized(IUser user);

   /**
    * Checks if this user group needs to be synchronized.
    *
    * The default implementation is to never flag the user group as dirty.
    * 
    * @param userGroup
    *           the user group to be checked.
    * @return
    */
   public boolean isDirty(IUserGroup userGroup)
   {
      return false;
   }

   /**
    * Callback method to inform the SynchronizationStrategy that the user group has been
    * successfully synchronized.
    * 
    * The default implementation is to do nothing.
    *
    * @param userGroup
    *           the user group that has been synchronized.
    */
   public void setSynchronized(IUserGroup userGroup)
   {
   }
   
   /**
    * Checks if this department needs to be synchronized.
    *
    * The default implementation is to never flag the department as dirty.
    *
    * @param department
    *           the department to be checked.
    * @return
    */
   public boolean isDirty(IDepartment department)
   {
      return false;
   }

   /**
    * Callback method to inform the SynchronizationStrategy that the department has been
    * successfully synchronized.
    *
    * The default implementation is to do nothing.
    *
    * @param department
    *           the department that has been synchronized.
    */
   public void setSynchronized(IDepartment department)
   {
   }
}

The isDirty method checks if the given user, user group or department needs to be synchronized.

Use the setSynchronized method to inform the SynchronizationStrategy that the user, user group or department has been successfully synchronized.

For further details, please consult the included JavaDoc for the DynamicParticipantSynchronizationStrategy class.

ExternalUserConfiguration

The interface ExternalUserConfiguration provides authorization information about user, user groups and departments the participant is assigned to.

public abstract class ExternalUserConfiguration
{
   public abstract String getFirstName();

   public abstract String getLastName();

   public abstract String getEMail();

   public abstract String getDescription();

   public abstract Map getProperties();

   public abstract Collection getGrantedModelParticipants();

   public Collection getUserGroupMemberships()
   {
      return Collections.EMPTY_LIST;
   }
   
   public Set<GrantInfo> getModelParticipantsGrants()
   {
      return Collections.emptySet();
   }
   
	  
   public static class GrantInfo
   {
      private final String participantId;
      private final List<String> departmentKey;

      public GrantInfo(String participantId, List<String> departmentKey)
      {
         if (participantId == null)
         {
            throw new NullPointerException("participantId must not be null.");
         }
         if (StringUtils.isEmpty(participantId))
         {
            throw new IllegalArgumentException("participantId must not be empty.");
         }
         
         this.participantId = participantId;
         this.departmentKey = getAdjustedList(departmentKey);
      }
      
      public GrantInfo(String participantId, String... departmentKey)
      {
         this(participantId, Arrays.asList(departmentKey));
      }

      public String getParticipantId()
      {
         return participantId;
      }

      public List<String> getDepartmentKey()
      {
         return departmentKey;
      }

      @Override
      public int hashCode()
      {
         final int prime = 31;
         int result = 1;
         result = prime * result + departmentKey.hashCode();
         result = prime * result + participantId.hashCode();
         return result;
      }

      @Override
      public boolean equals(Object obj)
      {
         if (this == obj)
         {
            return true;
         }
         if (obj == null)
         {
            return false;
         }
         if (getClass() != obj.getClass())
         {
            return false;
         }
         
         final GrantInfo other = (GrantInfo) obj;
         if ( !departmentKey.equals(other.departmentKey))
         {
            return false;
         }
         if ( !participantId.equals(other.participantId))
         {
            return false;
         }

         return true;
      }
      
      private List<String> getAdjustedList(final List<String> departmentKeys)
      {
         if (departmentKeys == null)
         {
            return newArrayList();
         }
         
         final int firstIndexOfNull = departmentKeys.indexOf(null);
         if (firstIndexOfNull != -1)
         {
            return copyList(departmentKeys.subList(0, firstIndexOfNull));
         }
         else
         {
            return copyList(departmentKeys);
         }
      }
   }
}

The method getModelParticipantsGrants gets a set of participants the external user has the grants for. A set is used as return value to reflect the department hierarchy in case of scoped participants. External departments are identified by a list of strings, where:

Note
Please note that the method getGrantedModelParticipants is obsolete and will be deprecated in later releases. This method is only valid for unscoped participants. It is recommended to use the method getModelParticipantsGrants instead, which is described above.

For further details, please consult the included JavaDoc for the ExternalUserConfiguration class.