Multi-application support for IDM
by Shane Bryzak
I've been thinking about Bill's request for multi-application support,
and I think I've come up with a solution that's going to be minimally
disruptive to the existing API. For starters, we need to add a few
methods to IdentityManager to support application management:
void createApplication(Application application);
void removeApplication(Application application);
Application getApplication(String applicationId);
Collection<Application> getAllApplications();
(The getAllApplications() method is necessary as the Query API only
deals with IdentityTypes, of which Application isn't one).
The next step is to allow the Application to be set somehow for any
given identity management operation. I think the easiest way to do this
is by providing a new method called forApplication():
IdentityManager forApplication(Application application);
The forApplication() method returns an instance of IdentityManager for
which any operations performed will be within the context of the
specified Application. Let's take a look at this in more practical
terms - for example, pretend we want to grant the "moderator" role to
user "bill" for the application "JBossForums". The code would look like
this:
Application jbossForums = identityManager.getApplication("jbossForums");
IdentityManager im = identityManager.forApplication(jbossForums);
User bill = im.getUser("bill");
Role moderator = im.getRole("moderator");
im.grantApplicationRole(bill, moderator);
The selected Application is passed to the underlying IdentityStores via
the IdentityStoreInvocationContext, to which we will add a
getApplication() method. We can also support multi-application
configuration, where one application might use an LDAP-based identity
store, while another might use a File-based identity store.
By providing multi-application support in this way, we can maintain the
existing API (we don't need to refactor every single method to add an
Application parameter) and for the consumers who don't care about
multi-application support the feature won't get in their way. We can
then very easily expose the IDM API as a set of RESTful web services to
achieve a standalone identity management service.
What do you guys think?
12 years, 3 months
IDM Configuration API
by Shane Bryzak
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
12 years, 3 months
IDM API Stabilization Status
by Anil Saldhana
Today I was discussing with Shane over IM. Basically, we are close to
stabilizing the api. Maybe another 7-10 days to checkpoint IDM for API
stabilization at about 90% fixed state.
12 years, 3 months
Entitlement versus Enforcement Model
by Anil Saldhana
Hi All,
this is an issue I see more at a client (in the classic client/server
paradigm) that the computing industry is moving toward.
With the increasing push towards mobility, cloud and REST
architectures, I think access control decisions may have to be made
where a decision is needed. So instead of making 100 authorization
calls to the server, we need a model where one call is made to the
server (given user, context etc) and we get back a set of entitlements
(or permissions) that need to be applied at the client side.
Examples include a mobile client (such as banking) that needs to figure
out what aspects of the mobile screen the user is entitled to see and
what operations he is capable of performing.
The industry has put too much emphasis on the enforcement model
(meaning, make 100 authorization calls to the glorified server). There
has been almost no models for the entitlement approach.
I have prototyped something here:
https://docs.jboss.org/author/display/SECURITY/EntitlementsManager
The entitlements should be sent in a JSON response.
Also, trying to get this standardized in the industry via the OASIS
Cloud Authorization TC.
https://lists.oasis-open.org/archives/oasis-charter-discuss/201210/msg000...
I have a hunch that projects such as Aerogear, Drools, Errai and
Infinispan may need this model.
Thoughts?
Regards,
Anil
12 years, 3 months
IdentityManager review - queries
by Shane Bryzak
I've started reviewing the IdentityManager interface to see where we can
improve the API. The first area I'd like to visit is the Query API, of
which I've come to the conclusion that we need to do some serious
redesign - the current API is non-intuitive, too verbose and not future
proof.
What I'd like to do is throw it all out and start again, replacing it
with a new cleaner API that looks something like this:
public interface IdentityManager {
// <snip other methods>
<T extends IdentityType> IdentityQuery<T> createQuery();
}
public interface IdentityQuery<T extends IdentityType> {
public enum Param {id, key, created, expired, enabled, firstName,
lastName, email, name, parent, memberOf};
public enum Operator { equals, notEquals, greaterThan, lessThan };
IdentityQuery<T> reset();
IdentityQuery<T> setParameter(Param param, Object value);
IdentityQuery<T> setParameter(Param param, Operator operator,
Object value);
IdentityQuery<T> setAttributeParameter(String attributeName, Object
value);
IdentityQuery<T> setAttributeParameter(String attributeName,
Operator operator, Object value);
IdentityQuery<T> setRange(Range range);
List<T> getResultList();
}
This unified API basically replaces the 4 separate existing interfaces
we currently have; UserQuery, RoleQuery, GroupQuery and
MembershipQuery. I've put together a few usage scenarios to show how it
might work:
1) Find users with first name 'John':
List<User> users = identityManager.<User>createQuery()
.setParameter(Param.firstName, "John")
.getResultList();
2) Find all expired users:
List<User> users = identityManager.<User>createQuery()
.setParameter(Param.expired, Operator.lessThan, new Date())
.getResultList();
3) Find all users that are a member of the "Superuser" group
List<User> users = identityManager.<User>createQuery()
.setParameter(Param.memberOf, identityManager.getGroup("Superuser"))
.getResultList();
4) Find all sub-groups of the "Employees" group:
List<Group> groups = identityManager.<Group>createQuery()
.setParameter(Param.memberOf, identityManager.getGroup("Employees"))
.getResultList();
5) Find all disabled roles:
List<Role> roles = identityManager.<Role>createQuery()
.setParameter(Param.enabled, false)
.getResultList();
6) Find all Users, Groups and Roles that have been granted the "Payroll
Officer" role in the "Human Resources" group:
List<IdentityType> identities = identityManager.<IdentityType>createQuery()
.setParameter(Param.memberOf, identityManager.getGroup("Human
Resources"))
.setParameter(Param.memberOf, identityManager.getRole("Payroll
Officer"))
.getResultList();
7) Find all Users that have an attribute named "Citizenship" with a
value of "Greenland":
List<User> users = identityManager.<User>createQuery()
.setAttributeParameter("Citizenship", "Greenland")
.getResultList();
I'm *pretty* certain that this API is at least as capable as what we
currently have, if not more so, and IMHO provides a far simpler and more
versatile design (being able to select different IdentityTypes in a
single query I think is a big plus). I'd love to hear any feedback on
whether you like it, hate it or can think of any improvements to the
design to make it better for our developers. Also, please think
especially about additional usage scenarios and whether or not there are
any particular use cases which might be problematic for this API.
Thanks!
Shane
12 years, 3 months