On Nov 6, 2012, at 11:10 PM, Shane Bryzak <sbryzak(a)redhat.com> wrote:
On 11/07/2012 07:35 AM, Boleslaw Dawidowicz wrote:
> Also +1. It looks really good.
>
> I assume the strategy to handle operations when two stores are configured is
IdentityManager implementation area. I wonder if we should make this part also more
flexible in some way. Not really thinking about IdentityStoreRepository kind of
design I had in 1.x as it is probably a bit too much. However it should be at least easy
to extend DefaultIdentityManager to add some customizations to how specific operations are
handled. Or we should have something like GenericIdentityManager for such purpose.
This feature (which I've been referring to as partitioning) is supported by requiring
each configured IdentityStore to provide metadata as to which features are supported, via
the getFeatureSet() method:
Set<Feature> getFeatureSet();
Feature is an enum defining all currently supported identity management operations:
public enum Feature { createUser, readUser, updateUser, deleteUser,
createGroup, readGroup, updateGroup, deleteGroup,
createRole, readRole, updateRole, deleteRole,
createMembership, readMembership, updateMembership,
deleteMembership,
validateCredential, updateCredential,
all }
When an IdentityManager method is invoked, the correct IdentityStore for the required
operation is selected based on its supported feature set. Here's an example of this,
in the createUser() method:
@Override
public User createUser(String name) {
User user = new SimpleUser(name);
IdentityStore store = getStoreForFeature(Feature.createUser);
store.createUser(getContextFactory().getContext(store), user);
return user;
}
The getStoreForFeature() method iterates through the configured stores and returns the
one that supports the specified Feature, in this example Feature.createUser. This way we
can configure multiple stores with one of them providing user-related operations, one
providing group and role operations, and so forth.
This will still not cover all use cases.
You have few stores that support credential validation and implement some kind of strategy
like in pam. I'm not saying that spi should be more complex - no I think it is rather
perfect for the balance between complexity and features. Just pointing out that people
will need to extend IdentityManager to introduce some more complex multi store behaviors.
Implementation should be done with it in mind. Avoid private methods and so on.
>
> Looking forward to see your ideas around design of event handling part. I think it
will be critical to truly pluggable and extendable.
This feature was quite simple. I've opted to go with the "class as an
event" model, where each event type is represented by a POJO containing the relevant
event state. For example, the following event is raised when a new user is created:
public class UserCreatedEvent extends AbstractBaseEvent {
private User user;
public UserCreatedEvent(User user) {
this.user = user;
}
public User getUser() {
return user;
}
}
In this example, the event class contains a reference to the User that was created. In
addition to this, each event class should extend AbstractBaseEvent which also provides an
event context:
public abstract class AbstractBaseEvent {
private EventContext context = new EventContext();
public EventContext getContext() {
return context;
}
}
The EventContext provides a general purpose mechanism for passing arbitrary state:
public class EventContext {
private Map<String,Object> context;
public Object getValue(String name) {
return context != null ? context.get(name) : null;
}
public void setValue(String name, Object value) {
if (context == null) {
context = new HashMap<String,Object>();
}
context.put(name, value);
}
public boolean contains(String name) {
return context != null && context.containsKey(name);
}
public boolean isEmpty() {
return context == null || context.isEmpty();
}
}
It is via the EventContext that we can pass IdentityStore implementation-specific state
(for example the actual entity bean instance that was persisted to the database in the
case of a JPA backed IdentityStore) or any additional state that might be relevant to
the event.
As for bridging the event itself, the IdentityStoreInvocationContext provides access to
the EventBridge via the getEventBridge() method:
EventBridge getEventBridge();
This is an extremely simple interface declaring just a single method:
public interface EventBridge {
void raiseEvent(Object event);
}
The idea here is that you can provide an EventBridge implementation tailored for the
environment that you're running in. In an EE6 environment, this is a piece of cake as
we just pass the event straight on through to the CDI event bus.
That pretty much sums up event handling.
Looks pretty simple indeed. What I had in mind was something with post and per operation
events but I guess that if needed those would fit this design right? It more seems about
defining valid event types and raising points.
I would like to be able to have access to full context - not just the single store. Use
case is for example to synchronize or propagate objects from one identity store into other
one in case of specific operation.
>
> On Nov 6, 2012, at 5:35 PM, Jason Porter <lightguard.jp(a)gmail.com> wrote:
>
>> +1 at all sounds good to me.
>>
>>
>> On Tue, Nov 6, 2012 at 3:08 AM, Shane Bryzak <sbryzak(a)redhat.com> wrote:
>>> 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
>>> _______________________________________________
>>> security-dev mailing list
>>> security-dev(a)lists.jboss.org
>>>
https://lists.jboss.org/mailman/listinfo/security-dev
>>
>>
>>
>> --
>> Jason Porter
>>
http://lightguard-jp.blogspot.com
>>
http://twitter.com/lightguardjp
>>
>> Software Engineer
>> Open Source Advocate
>>
>> PGP key id: 926CCFF5
>> PGP key available at:
keyserver.net,
pgp.mit.edu
>> _______________________________________________
>> security-dev mailing list
>> security-dev(a)lists.jboss.org
>>
https://lists.jboss.org/mailman/listinfo/security-dev