[security-dev] IDM Configuration API

Shane Bryzak sbryzak at redhat.com
Tue Nov 6 05:08:26 EST 2012


Hey guys,

For the past few days I've been wracking my brain trying to come up with 
a design for the IDM configuration API that doesn't suck, and allows 
easy configuration in both Java SE and EE environments while maintaining 
separation between the API and implementation.  I think I've come up 
with something that's quite flexible and extensible, so I'd like to run 
it past everyone for some feedback.

The starting point for configuring the Identity Management API is a 
concrete class called IdentityConfiguration:

public class IdentityConfiguration {
     private List<IdentityStoreConfiguration> configuredStores = new 
ArrayList<IdentityStoreConfiguration>();
     public List<IdentityStoreConfiguration> getConfiguredStores() {
         return configuredStores;
     }
     public void addStoreConfiguration(IdentityStoreConfiguration config) {
         configuredStores.add(config);
     }
}

This class simply provides a holder for one or more 
IdentityStoreConfiguration(s), an abstract class that provides the 
basics for configuring an IdentityStore:

public abstract class IdentityStoreConfiguration {
     private final Map<String,String> properties = new 
HashMap<String,String>();
     private final Set<Feature> supportedFeatures = new HashSet<Feature>();
     public Set<Feature> getSupportedFeatures() {
         return supportedFeatures;
     }
     public void addSupportedFeature(Feature feature) {
         supportedFeatures.add(feature);
     }
     public void removeSupportedFeature(Feature feature) {
         supportedFeatures.remove(feature);
     }
     public void setProperty(String name, String value) {
         properties.put(name, value);
     }
     public String getPropertyValue(String name) {
         return properties.get(name);
     }
}

Each IdentityStore implementation (such as JPAIdentityStore, 
LDAPIdentityStore, etc) should have a corresponding 
IdentityStoreConfiguration implementation that provides an API for 
setting specific property values for its IdentityStore.  For example, 
this is the one for JPAIdentityStore which allows a number of entity 
classes to be set:

public class JPAIdentityStoreConfiguration extends 
IdentityStoreConfiguration {
     private Class<?> identityClass;
     private Class<?> membershipClass;
     private Class<?> credentialClass;
     private Class<?> attributeClass;
     public Class<?> getIdentityClass() {
         return identityClass;
     }
     public void setIdentityClass(Class<?> identityClass) {
         this.identityClass = identityClass;
     }
     public Class<?> getCredentialClass() {
         return credentialClass;
     }
     public void setCredentialClass(Class<?> credentialClass) {
         this.credentialClass = credentialClass;
     }
     public Class<?> getMembershipClass() {
         return membershipClass;
     }
     public void setMembershipClass(Class<?> membershipClass) {
         this.membershipClass = membershipClass;
     }
     public Class<?> getAttributeClass() {
         return attributeClass;
     }
     public void setAttributeClass(Class<?> attributeClass) {
         this.attributeClass = attributeClass;
     }
}

The IdentityStore-specific configurations are intended to be part of the 
API, so they should be placed in the API module within the 
org.picketlink.idm.config package.

After creating an IdentityConfiguration, you can create an 
IdentityManager and provide the configuration via the bootstrap() method:

IdentityConfiguration identityConfig = new IdentityConfiguration();
JPAIdentityStoreConfiguration storeConfig = new 
JPAIdentityStoreConfiguration();
// snip storeConfig configuration

identityConfig.addStoreConfiguration(storeConfig);

IdentityManager identityManager = new DefaultIdentityManager();
identityManager.bootstrap(identityConfig, new 
DefaultIdentityStoreInvocationContextFactory(null));

The reason we use bootstrap() instead of performing configuration in the 
constructor is so the developer has a chance to override the 
IdentityStoreFactory.  This is an SPI interface that defines a couple of 
methods which are used to control which IdentityStoreConfigurations 
correspond to which IdentityStore.

public interface IdentityStoreFactory {
     /**
      * Creates an instance of an IdentityStore using the provided 
configuration
      *
      * @param config
      * @return
      */
     IdentityStore createIdentityStore(IdentityStoreConfiguration config);

     /**
      * Maps specific implementations of IdentityStoreConfiguration to a 
corresponding
      * IdentityStore implementation.
      *
      * @param configClass
      * @param storeClass
      */
     void mapConfiguration(Class<? extends IdentityStoreConfiguration> 
configClass,
             Class<? extends IdentityStore> storeClass);
}

By default, the DefaultIdentityManager will create and use an instance 
of DefaultIdentityStoreFactory - this factory knows about all the 
built-in IdentityStore implementations and can create IdentityStore 
instances based on their corresponding IdentityStoreConfiguration.  If a 
developer wished to provide their own IdentityStore though (or override 
the behaviour of one of the built-in ones) then we need to provide a 
hook to allow them to override the default factory with their own.  This 
is provided by the IdentityManager.setIdentityStoreFactory() method - by 
invoking this method with a new IdentityStoreFactory the developer can 
provide an alternative factory for creating IdentityStore instances 
based on non built-in configurations before bootstrap() is called.

Here's an example where the built-in identity stores are supplemented 
with support for a native Hibernate-based identity store:

IdentityManager identityManager = new DefaultIdentityManager();

DefaultIdentityStoreFactory factory = new DefaultIdentityStoreFactory();
factory.mapConfiguration(HibernateIdentityStoreConfiguration.class, 
HibernateIdentityStore.class);
identityManager.setIdentityStoreFactory(factory);

identityManager.bootstrap(identityConfig, new 
DefaultIdentityStoreInvocationContextFactory(null));

The last piece of the puzzle is the second parameter to the bootstrap() 
method.  This parameter should be an instance of 
IdentityStoreInvocationContextFactory (I know, it's a mouthful), an SPI 
interface that declares a single method:

public interface IdentityStoreInvocationContextFactory {
     IdentityStoreInvocationContext getContext(IdentityStore store);
}

The implementation of this interface is responsible for creating 
IdentityStoreInvocationContext instances, which are passed as a 
parameter value to pretty much all of the IdentityStore methods and 
allow the IdentityStore implementation to be shared between multiple 
threads (i.e. a stateless design).  The IdentityStoreInvocationContext 
is responsible for providing the IdentityStore with any state (besides 
the IdentityStore configuration) required to execute its requested 
operation.  It also provides a gateway to the event bridge, which allows 
events to be raised during an IdentityStore operation and propagated to 
any environment, such as the CDI event bus.  The 
IdentityStoreInvocationContext implementation/s are currently still a 
work in progress, however the basic API should not change.

This essentially wraps up the description of the configuration API. What 
I haven't touched upon yet are the builders (classes that parse a 
configuration source such as a file to create an 
IdentityStoreConfiguration) however I'm hoping Anil that you might want 
to have a go at this.

With these changes comes a small TO-DO list:

1) IdentityStoreConfiguration implementations should be created in the 
API module within the org.picketlink.idm.config package for each of the 
IdentityStore implementations (we already have one for LDAP, but it 
needs to be moved and possibly renamed to LDAPIdentityStoreConfiguration 
for consistency).

2) During the course of the refactor I had to provide workarounds for 
many of the tests, and I also managed to totally break quite a few 
others.  The test suite needs a quick review to see why the tests are 
broken, and once configurations are provided for the other identity 
stores the workarounds can be removed.

3) The getFeatureSet() method should be correctly implemented for all 
IdentityStores.  With the change to the configuration API we now have 
proper support for partitioning, and it's important that this method 
accurately reflect the underlying capabilities of the IdentityStore 
implementation for this feature to work correctly.

Thanks for listening, and I'm looking forward to getting some feedback 
on this!

Shane


More information about the security-dev mailing list