This chapter addresses identity management in JBoss Portal 2.6
Since JBoss Portal 2.6 there are 4 identity services and 2 identity related interfaces. The goal of having such a fine grained API is to enable flexible implementations based on different identity storage like relational databases or LDAP servers. The Membership service takes care of managing the relationship between user objects and role objects. The User Profile service is responsible for managing the profile of a user, it has database and LDAP implementations as well as a mode that combines data from both.
The org.jboss.portal.identity.User interface represents a user and exposes the following operations:
/** The user identifier. */ public Object getId(); /** The user name. */ public String getUserName(); /** Set the password using proper encoding. */ public void updatePassword(String password); /** Return true if the password is valid. */ public boolean validatePassword(String password);
// Always use it like this: user.getId().toString(); // Do not use it like this: // We would get a Long object if we are using the database implementation (Long)user.getId(); // We would get a String with an LDAP server (String)user.getId();This is because the ID value depends on the User implementation. It'll probably be String object with the LDAP implementation and a Long object with the database implementation but it could be something else if one has chosen to make its own implementation.
The org.jboss.portal.identity.Role interface represents a Role and exposes the following operations:
/** The role identifier. */ public Object getId(); /** The role name used in security rules. This name can not be modified */ public String getName(); /** The role display name used on screens. This name can be modified */ public String getDisplayName(); /** */ public void setDisplayName(String name);
The org.jboss.portal.identity.UserModule interface exposes operations for users management:
/**Retrieve a user by its name.*/ User findUserByUserName(String userName) throws IdentityException, IllegalArgumentException, NoSuchUserException; /**Retrieve a user by its id.*/ User findUserById(Object id) throws IdentityException, IllegalArgumentException, NoSuchUserException; /**Retrieve a user by its id.*/ User findUserById(String id) throws IdentityException, IllegalArgumentException, NoSuchUserException; /** Creates a new user with the specified name.*/ User createUser(String userName, String password) throws IdentityException, IllegalArgumentException; /** Remove a user.*/ void removeUser(Object id) throws IdentityException, IllegalArgumentException; /** Get a range of users.*/ Set findUsers(int offset, int limit) throws IdentityException, IllegalArgumentException; /** Get a range of users.*/ Set findUsersFilteredByUserName(String filter, int offset, int limit) throws IdentityException, IllegalArgumentException; /**Returns the number of users.*/ int getUserCount() throws IdentityException, IllegalArgumentException;
The org.jboss.portal.identity.RoleModule interface exposes operations for roles management:
/** Retrieves a role by its name*/ Role findRoleByName(String name) throws IdentityException, IllegalArgumentException; /**Retrieve a collection of role from the role names.*/ Set findRolesByNames(String[] names) throws IdentityException, IllegalArgumentException; /** Retrieves a role by its id.*/ Role findRoleById(Object id) throws IdentityException, IllegalArgumentException; /** Retrieves a role by its id.*/ Role findRoleById(String id) throws IdentityException, IllegalArgumentException; /** Create a new role with the specified name.*/ Role createRole(String name, String displayName) throws IdentityException, IllegalArgumentException; /** Remove a role.*/ void removeRole(Object id) throws IdentityException, IllegalArgumentException; /** Returns the number of roles. */ int getRolesCount() throws IdentityException; /** Get all the roles */ Set findRoles() throws IdentityException;/** Retrieves a role by its name*/ Role findRoleByName(String name) throws IdentityException, IllegalArgumentException; /**Retrieve a collection of role from the role names.*/ Set findRolesByNames(String[] names) throws IdentityException, IllegalArgumentException; /** Retrieves a role by its id.*/ Role findRoleById(Object id) throws IdentityException, IllegalArgumentException; /** Retrieves a role by its id.*/ Role findRoleById(String id) throws IdentityException, IllegalArgumentException; /** Create a new role with the specified name.*/ Role createRole(String name, String displayName) throws IdentityException, IllegalArgumentException; /** Remove a role.*/ void removeRole(Object id) throws IdentityException, IllegalArgumentException; /** Returns the number of roles. */ int getRolesCount() throws IdentityException; /** Get all the roles */ Set findRoles() throws IdentityException;
The MembershipModule interface exposes operations for obtaining or managing relationships beetween users and roles. The role of this service is to decouple relationship information from user and roles. Indeed while user role relationship is pretty straightforward with a relational database (using a many to many relationship with an intermediary table), with an LDAP server there a different ways to define relationships between users and roles.
/** Return the set of role objects that a given user has.*/ Set getRoles(User user) throws IdentityException, IllegalArgumentException; Set getUsers(Role role) throws IdentityException, IllegalArgumentException; /** Creates a relationship beetween a role and set of users. Other roles that have assotiontions with those users remain unaffected.*/ void assignUsers(Role role, Set users) throws IdentityException, IllegalArgumentException; /** Creates a relationship beetween a user and set of roles. This operation will erase any other assotientions beetween the user and roles not specified in the provided set.*/ void assignRoles(User user, Set roles) throws IdentityException, IllegalArgumentException; /** Returns role members based on rolename - depreciated method ethod here only for compatibility with old RoleModule interface */ Set findRoleMembers(String roleName, int offset, int limit, String userNameFilter) throws IdentityException, IllegalArgumentException;
The UserProfileModule interface exposes operations to access and manage informations stored in User profile:
public Object getProperty(User user, String propertyName) throws IdentityException, IllegalArgumentException; public void setProperty(User user, String name, Object property) throws IdentityException, IllegalArgumentException; public Map getProperties(User user) throws IdentityException, IllegalArgumentException; public ProfileInfo getProfileInfo() throws IdentityException;
The ProfileInfo interface can be obtained using the UserProfileModule and exposes meta information of a profile:
/** Returns a Map o PropertyInfo objects describing profile properties */ public Map getPropertiesInfo(); public PropertyInfo getPropertyInfo(String name);
PropertyInfo interface expose methods to obtain information about accessible property in User profile
public static final String ACCESS_MODE_READ_ONLY = "read-only"; public static final String ACCESS_MODE_READ_WRITE = "read-write"; public static final String USAGE_MANDATORY = "mandatory"; public static final String USAGE_OPTIONAL = "optional"; public static final String MAPPING_DB_TYPE_COLUMN = "column"; public static final String MAPPING_DB_TYPE_DYNAMIC = "dynamic"; public String getName(); public String getType(); public String getAccessMode(); public String getUsage(); public LocalizedString getDisplayName(); public LocalizedString getDescription(); public String getMappingDBType(); public String getMappingLDAPValue(); public String getMappingDBValue(); public boolean isMappedDB(); public boolean isMappedLDAP();
The advocated way to get a reference to the identity modules is by using JNDI:
import org.jboss.portal.identity.UserModule; import org.jboss.portal.identity.RoleModule; import org.jboss.portal.identity.MembershipModule; import org.jboss.portal.identity.UserProfileModule; [...] (UserModule)new InitialContext().lookup("java:portal/UserModule"); (RoleModule)new InitialContext().lookup("java:portal/RoleModule"); (MembershipModule)new InitialContext().lookup("java:portal/MembershipModule"); (UserProfileModule)new InitialContext().lookup("java:portal/UserProfileModule");
Another way to do this is, if you are fimiliar with JBoss Microkernel architecture is to get the IdentityServiceController mbean. You may want to inject it into your services like this:
<depends optional-attribute-name="IdentityServiceController" proxy-type="attribute"> portal:service=Module,type=IdentityServiceController </depends>
or simply obtain in your code by doing a lookup using the portal:service=Module,type=IdentityServiceController name. Please refer to the JBoss Application Server documentation if you want to learn more about service MBeans. Once you obtained the object you can use it:
(UserModule)identityServiceController.getIdentityContext() .getObject(IdentityContext.TYPE_USER_MODULE); (RoleModule)identityServiceController.getIdentityContext() .getObject(IdentityContext.TYPE_ROLE_MODULE); (MembershipModule)identityServiceController.getIdentityContext() .getObject(IdentityContext.TYPE_MEMBERSHIP_MODULE); (UserProfileModule)identityServiceController.getIdentityContext() .getObject(IdentityContext.TYPE_USER_PROFILE_MODULE);
Because in JBoss Portal 2.4 there were only UserModule , RoleModule , User and Role interfaces some API usages changed. Here are the most important changes you will need to aply to your code while migrating your aplication to 2.6:
For the User interface:
// Instead of: user.getEnabled() userProfileModule.getProperty(user, User.INFO_USER_ENABLED); // Instead of: user.setEnabled(value) userProfileModule.setProperty(user, User.INFO_USER_ENABLED, value); // In a similar way you should change rest of methods that are missing in User interface // in 2.6 by the call to the UserProfileModule // Instead of: user.getProperties() userProfileModule.getProperties(user); // Instead of: user.getGivenName() userProfileModule.getProperty(user, User.INFO_USER_NAME_GIVEN); // Instead of: user.getFamilyName() userProfileModule.getProperty(user, User.INFO_USER_NAME_FAMILY); // Instead of: user.getRealEmail() userProfileModule.getProperty(user, User.INFO_USER_EMAIL_REAL); // Instead of: user.getFakeEmail() userProfileModule.getProperty(user, User.INFO_USER_EMAIL_FAKE); // Instead of: user.getRegistrationDate() userProfileModule.getProperty(user, User.INFO_USER_REGISTRATION_DATE); // Instead of: user.getViewRealEmail() userProfileModule.getProperty(user, User.INFO_USER_VIEW_EMAIL_VIEW_REAL); // Instead of: user.getPreferredLocale() userProfileModule.getProperty(user, User.INFO_USER_LOCALE); // Instead of: user.getSignature() userProfileModule.getProperty(user, User.INFO_USER_SIGNATURE); // Instead of: user.getLastVisitDate() userProfileModule.getProperty(user, User.INFO_USER_LAST_LOGIN_DATE);
The RoleModule interface:
// Instead of // RoleModule.findRoleMembers(String roleName, int offset, int limit, String userNameFilter) // throws IdentityException; membershipModule.findRoleMembers(String roleName, int offset, int limit, String userNameFilter) // Instead of // RoleModule.setRoles(User user, Set roles) throws IdentityException; membershipModule.assignRoles(User user, Set roles) // Instead of // RoleModule.getRoles(User user) throws IdentityException; membershipModule.getRoles(User user)
In order to understand identity configuration you need to understand its architecture. Different identity services like UserModule, RoleModule and etc are just plain java classes that are instantiated and exposed by the portal. So an *example* of UserModule service could be a plain java bean object that would be:
As you see from this point of view, configuration just specifies what java class will be used and how it should be used by portal as a service.
In JBoss Portal we provide a very flexible configuration. It is very easy to rearrange and customize services, provide alternative implementations, extend the existing ones or provide a custom identity model.
To grasp the full picture of the configuration of identity services let's start from its root component. Whole configuration and setup of identity components is done by the IdentityServiceController service. It brings to life and registers all other services such as UserModule, RoleModule, MembershipModule and UserProfileModule. IdentityServiceController is defined in jboss-portal.sar/META-INF/jboss-service.xml
<mbean code="org.jboss.portal.identity.IdentityServiceControllerImpl" name="portal:service=Module,type=IdentityServiceController" xmbean-dd="" xmbean-code="org.jboss.portal.jems.as.system.JBossServiceModelMBean"> <xmbean/> <depends>portal:service=Hibernate</depends> <attribute name="JndiName">java:/portal/IdentityServiceController</attribute> <attribute name="RegisterMBeans">true</attribute> <attribute name="ConfigFile">conf/identity/identity-config.xml</attribute> <attribute name="DefaultConfigFile">conf/identity/standardidentity-config.xml</attribute> </mbean>
We can specify a few options here:
RegisterMBeans - defines if IdentityServiceController should register components which are instantiated as mbeans
ConfigFile - defines the location of the main identity services configuration file. It describes and configures all the components like UserModule, RoleModule... that need to be instantiated
DefaultConfigFile - defines the location of the configuration file containing the default values. For each component defined in ConfigFile, the IdentityServiceController will obtain a set of default options from this file. That helps to keep the main main configuration file simple, short and easy to read. Potentially it provides more powerful customizations.
The file describing portal identity services contains three sections:
<identity-configuration> <datasources> <!-- Datasources section --> <datasource> ... </datasource> <datasource> ... </datasource> ... </datasources> <modules> <!-- Modules section --> <module> ... </module> <module> ... </module> ... </modules> <options> <!-- Options section --> <option-group> ... </option-group> <option-group> ... </option-group> ... </options> </identity-configuration>
By default you can find it in jboss-portal.sar/conf/identity/identity-config.xml
This section defines datasource components. They will be processed and instantiated before components in Module section, so they will be ready to serve them.
<datasource> <name>LDAP</name> <service-name>portal:service=Module,type=LDAPConnectionContext</service-name> <class>org.jboss.portal.identity.ldap.LDAPConnectionContext</class> <config> <option> <name>host</name> <value>jboss.com</value> </option> <option> <name>port</name> <value>10389</value> </option> <option> <name>adminDN</name> <value>cn=Directory Manager</value> </option> <option> <name>adminPassword</name> <value>xxxxx</value> </option> <!-- Other options here.... --> </config> </datasource>
Modules are core service components like UserModule, RoleModule and etc.
<module> <!--type used to correctly map in IdentityContext registry--> <type>User</type> <implementation>DB</implementation> <!--name of service and class for creating mbean--> <service-name>portal:service=Module,type=User</service-name> <class>org.jboss.portal.identity.db.HibernateUserModuleImpl</class> <!--set of options that are in the instantiated object--> <config> <option> <name>sessionFactoryJNDIName</name> <value>java:/portal/IdentitySessionFactory</value> </option> <option> <name>jNDIName</name> <value>java:/portal/UserModule</value> </option> </config> </module>
implementation - defines the scope under which the configuration for different implementations of modules types resides. It enables to define the default options of the configuration of the different implementations of same module types in one configuration file.
type - must be unique name across all modules defined in the main configuration file. This is important as module will be stored with such name within IdentityContext registry at runtime. Standard names are used (User, Role, Membership, UserProfile). Together with implementation will create unique pair within file with default configuration values.
service-name - will be used for the name when registered as an MBean.
class - java class that will be use to instantiate the module.
config - contains options related to this module
<module> <!--type used to correctly map in IdentityContext registry--> <type>User</type> <implementation>DB</implementation> <config/> </module>As you can see we specify only the type and the implementation - all the other values (service-name, class and set of options) are read from default configuration. But remember that you can still overwrite any of those values in the main config simply by overriding them.
This section provides common options that are accessible by identity modules. We set options here that may need to be shared. They are grouped, and can have many values:
<options> <!--Common options section--> <option-group> <group-name>common</group-name> <option> <name>userCtxDN</name> <value>ou=People,dc=example,dc=com</value> </option> <option> <name>uidAttributeID</name> <value>uid</value> </option> <option> <name>passwordAttributeID</name> <value>userPassword</value> </option> <option> <name>roleCtxDN</name> <value>ou=Roles,dc=example,dc=com</value> </option> <option> <name>ridAttributeId</name> <value>cn</value> </option> <option> <name>roleDisplayNameAttributeID</name> <value>cn</value> </option> <option> <name>membershipAttributeID</name> <value>member</value> </option> <option> <name>membershipAttributeIsDN</name> <value>true</value> </option> </option-group> <option-group> <group-name>userCreateAttibutes</group-name> <option> <name>objectClass</name> <value>top</value> <value>uidObject</value> <value>person</value> <value>inetUser</value> </option> <!--Schema requires those to have initial value--> <option> <name>cn</name> <value>none</value> </option> <option> <name>sn</name> <value>none</value> </option> </option-group>
UserProfileModule has additional configuration file that defines user properties. It is specified in configuration in:
<module> <type>UserProfile</type> <implementation>DELEGATING</implementation> (...) <config> (...) <option> <name>profileConfigFile</name> <value>conf/identity/profile-config.xml</value> </option> </config> </module>
This means that you can configure user profile in jboss-portal.sar/conf/identity/profile-config.xml
<profile> <property> <name>user.name.nickName</name> <type>java.lang.String</type> <access-mode>read-only</access-mode> <usage>mandatory</usage> <display-name xml:lang="en">Name</display-name> <description xml:lang="en">The user name</description> <mapping> <database> <type>column</type> <value>jbp_uname</value> </database> </mapping> </property> <property> <name>user.business-info.online.email</name> <type>java.lang.String</type> <access-mode>read-write</access-mode> <usage>mandatory</usage> <display-name xml:lang="en">Email</display-name> <description xml:lang="en">The user real email</description> <mapping> <database> <type>column</type> <value>jbp_realemail</value> </database> <ldap> <value>mail</value> </ldap> </mapping> </property> <property> <name>portal.user.location</name> <type>java.lang.String</type> <access-mode>read-write</access-mode> <usage>optional</usage> <display-name xml:lang="en">Location</display-name> <description xml:lang="en">The user location</description> <mapping> <database> <type>dynamic</type> <value>portal.user.location</value> </database> </mapping> </property> (...) </properties>
Configuration file contains properties definition that can be retrieved using the PropertyInfo interface. Each property used in portal has to be defined here.
JBoss portal comes with a set of database related identity modules implementations done using Hibernate - those are configured by default. Those are not very configurable in identity-config.xml file. The reason is that to keep backwards compatibility of database schema with previous portal version, we reused most of hibernate implementation. If you want to tweak the hibernate mappings you should look into files in jboss-portal.sar/conf/hibernate. Also those modules rely on hibernate SessionFactory components that are created in SessionFactoryBinder mbeans defined in jboss-portal.sar/META-INF/jboss-service.xml
Classes implementing identity modules:
For each of those modules you can alter two config options:
Delegating UserProfileModule implementation has very specific role. When we use storage mechanism like LDAP we may not be able to map all user properties into LDAP attributes because of schema limitations. To solve this problem we still can use the database to store user properties that do not exist in the LDAP schema. Delegating user profile module will recognize if a property is mapped as ldap or database and delegate setProperty()/getProperty() method invocation to proper module implementation. This is implemented in org.jboss.portal.identity.DelegatingUserProfileModuleImpl. If property is mapped either as ldap and database the ldap mapping will have higher priority.
<module> <!--type used to correctly map in IdentityContext registry--> <type>UserProfile</type> <implementation>DELEGATING</implementation> <!--name of service and class for creating mbean--> <service-name>portal:service=Module,type=UserProfile</service-name> <class>org.jboss.portal.identity.DelegatingUserProfileModuleImpl</class> <!--set of options that are set in instantiated object--> <config> <option> <name>jNDIName</name> <value>java:/portal/UserProfileModule</value> </option> <option> <name>dbModuleJNDIName</name> <value>java:/portal/DBUserProfileModule</value> </option> <option> <name>profileConfigFile</name> <value>conf/identity/profile-config.xml</value> </option> </config> </module>
Module options are:
Because of the behaviour described in the previous section, database UserProfileModule requires some special features. If a user is present in LDAP server but a writable property isn't mapped as an LDAP attribute, such property requires to be stored in the database. In order to achieve such result the user need to be synchronized from ldap into the database first.
Class org.jboss.portal.identity.db.HibernateUserProfileModuleImpl has additional synchronization features. Here are the options: