15.4. Identity Management

Identity Management provides a standard API for managing a Seam application's users and roles, regardless of the identity store (database, LDAP, etc.) used in back-end operations. The identityManager component is at the core of the Identity Management API, and provides all methods for creating, modifying, and deleting users, granting and revoking roles, changing passwords, enabling and disabling user accounts, authenticating users, and listing users and roles.
Before use, the identityManager must be configured with at least one IdentityStore. These components interact with the back-end security provider.

15.4.1. Configuring IdentityManager

The identityManager component allows you to configure separate identity stores for authentication and authorization. This means that users can be authenticated against one identity store (for example, an LDAP directory), but have their roles loaded from another identity store (such as a relational database).
Seam provides two IdentityStore implementations out of the box. The default, JpaIdentityStore, uses a relational database to store user and role information. The other implementation is LdapIdentityStore, which uses an LDAP directory to store users and roles.
The identityManager component has two configurable properties: identityStore and roleIndentityStore. The value for these properties must be an EL expression that refers to a Seam component with the IdentityStore interface. If left unconfigured, the default (JpaIdentityStore) will be used. If only the identityStore property is configured, the same value will be used for roleIdentityStore. For example, the following entry in components.xml will configure identityManager to use an LdapIdentityStore for both user-related and role-related operations:
<security:identity-manager identity-store="#{ldapIdentityStore}"/>
The following example configures identityManager to use an LdapIdentityStore for user-related operations, and JpaIdentityStore for role-related operations:
<security:identity-manager identity-store="#{ldapIdentityStore}" 
          role-identity-store="#{jpaIdentityStore}"/>
The following sections explain each identity storage method in greater detail.

15.4.2. JpaIdentityStore

This method stores users and roles in a relational database. It is designed to allow flexible database design and table structure. A set of special annotations lets entity beans store user and role records.

15.4.2.1. Configuring JpaIdentityStore

Both user-class and role-class must be configured before JpaIdentityStore can be used. These properties refer to the entity classes used to store user and role records, respectively. The following example shows the components.xml file from the SeamSpace example:
 
<security:jpa-identity-store 
          user-class="org.jboss.seam.example.seamspace.MemberAccount" 
          role-class="org.jboss.seam.example.seamspace.MemberRole"/> 

15.4.2.2. Configuring the Entities

The following table describes the special annotations used to configure entity beans for user and role storage.

Table 15.1. User Entity Annotations

Annotation
Status
Description
@UserPrincipal
Required
This annotation marks the field or method containing the user's username.
@UserPassword
Required
This annotation marks the field or method containing the user's password. It allows a hash algorithm to be specified for password hashing. Possible values for hash are md5, sha and none. For example:
@UserPassword(hash = "md5") 
public String getPasswordHash() { 
  return passwordHash; 
}
It is possible to extend the PasswordHash component to implement other hashing algorithms, if required.
@UserFirstName
Optional
This annotation marks the field or method containing the user's first name.
@UserLastName
Optional
This annotation marks the field or method containing the user's last name.
@UserEnabled
Optional
This annotation marks the field or method containing the enabled user status. This should be a Boolean property. If not present, all user accounts are assumed to be enabled.
@UserRoles
Required
This annotation marks the field or method containing the roles of the user. This property will be described in more detail in a later section.

Table 15.2. Role Entity Annotations

Annotation
Status
Description
@RoleName
Required
This annotation marks the field or method containing the name of the role.
@RoleGroups
Optional
This annotation marks the field or method containing the group memberships of the role.
@RoleConditional
Optional
This annotation marks the field or method that indicates whether a role is conditional. Conditional roles are explained later in this chapter.

15.4.2.3. Entity Bean Examples

As mentioned previously, JpaIdentityStore is designed to be as flexible as possible when it comes to the database schema design of your user and role tables. This section looks at a number of possible database schemas that can be used to store user and role records.
15.4.2.3.1. Minimal schema example
Here, a simple user and role table are linked via a many-to-many relationship using a cross-reference table named UserRoles.
@Entity
public class User {
  private Integer userId;
  private String username;
  private String passwordHash;
  private Set<Role> roles;
  
  @Id @GeneratedValue
  public Integer getUserId() { return userId; }
  public void setUserId(Integer userId) { this.userId = userId; }
  
  @UserPrincipal
  public String getUsername() { return username; }
  public void setUsername(String username) { this.username = username; }
  
  @UserPassword(hash = "md5")
  public String getPasswordHash() { return passwordHash; }
  public void setPasswordHash(String passwordHash) { 
    this.passwordHash = passwordHash; 
  }
  
  @UserRoles
  @ManyToMany(targetEntity = Role.class)
  @JoinTable(name = "UserRoles", 
       joinColumns = @JoinColumn(name = "UserId"),
       inverseJoinColumns = @JoinColumn(name = "RoleId"))
  public Set<Role> getRoles() { return roles; }
  public void setRoles(Set<Role> roles) { this.roles = roles; }
}
@Entity
public class Role {
  private Integer roleId;
  private String rolename;
  
  @Id @Generated
  public Integer getRoleId() { return roleId; }
  public void setRoleId(Integer roleId) { this.roleId = roleId; }
  
  @RoleName
  public String getRolename() { return rolename; }
  public void setRolename(String rolename) { this.rolename = rolename; }
}
15.4.2.3.2. Complex Schema Example
This example builds on the previous minimal example by including all optional fields, and allowing group memberships for roles.
@Entity
public class User {
  private Integer userId;
  private String username;
  private String passwordHash;
  private Set<Role> roles;
  private String firstname;
  private String lastname;
  private boolean enabled;
  
  @Id @GeneratedValue
  public Integer getUserId() { return userId; }
  public void setUserId(Integer userId) { this.userId = userId; }
  
  @UserPrincipal
  public String getUsername() { return username; }
  public void setUsername(String username) { this.username = username; }
  
  @UserPassword(hash = "md5")
  public String getPasswordHash() { return passwordHash; }
  public void setPasswordHash(String passwordHash) { 
    this.passwordHash = passwordHash; 
  }
  
  @UserFirstName
  public String getFirstname() { return firstname; }
  public void setFirstname(String firstname) { 
    this.firstname = firstname; 
  }
  
  @UserLastName
  public String getLastname() { return lastname; }
  public void setLastname(String lastname) { this.lastname = lastname; }
  
  @UserEnabled
  public boolean isEnabled() { return enabled; }
  public void setEnabled(boolean enabled) { this.enabled = enabled; }
  
  @UserRoles
  @ManyToMany(targetEntity = Role.class)
  @JoinTable(name = "UserRoles", 
    joinColumns = @JoinColumn(name = "UserId"),
    inverseJoinColumns = @JoinColumn(name = "RoleId"))
  public Set<Role> getRoles() { return roles; }
  public void setRoles(Set<Role> roles) { this.roles = roles; }
}
@Entity
public class Role {
  private Integer roleId;
  private String rolename;
  private boolean conditional;
  
  @Id @Generated
  public Integer getRoleId() { return roleId; }
  public void setRoleId(Integer roleId) { this.roleId = roleId; }
  
  @RoleName
  public String getRolename() { return rolename; }
  public void setRolename(String rolename) { this.rolename = rolename; }
  
  @RoleConditional
  public boolean isConditional() { return conditional; }
  public void setConditional(boolean conditional) { 
    this.conditional = conditional; 
  }
  
  @RoleGroups
  @ManyToMany(targetEntity = Role.class)
  @JoinTable(name = "RoleGroups", 
             joinColumns = @JoinColumn(name = "RoleId"),
             inverseJoinColumns = @JoinColumn(name = "GroupId"))
  public Set<Role> getGroups() { return groups; }
  public void setGroups(Set<Role> groups) { this.groups = groups; }  
  
}

15.4.2.4. JpaIdentityStore Events

When using JpaIdentityStore with IdentityManager, several events are raised when certain IdentityManager methods are invoked.
15.4.2.4.1. JpaIdentityStore.EVENT_PRE_PERSIST_USER
This event is raised in response to calling IdentityManager.createUser(). Just before the user entity is persisted to the database, this event is raised to pass the entity instance as an event parameter. The entity will be an instance of the user-class configured for JpaIdentityStore.
An observer can be useful, here, for setting entity field values that are not part of standard createUser() functionality.
15.4.2.4.2. JpaIdentityStore.EVENT_USER_CREATED
This event is also raised in response to calling IdentityManager.createUser(). However, it is raised after the user entity has already been persisted to the database. Like the EVENT_PRE_PERSIST_USER event, it also passes the entity instance as an event parameter. It may be useful to observe this event if you need to persist other entities that reference the user entity, such as contact detail records or other user-specific data.
15.4.2.4.3. JpaIdentityStore.EVENT_USER_AUTHENTICATED
This event is raised when calling IdentityManager.authenticate(). It passes the user entity instance as the event parameter, and is useful for reading additional properties from the user entity being authenticated.

15.4.3. LdapIdentityStore

This identity storage method is designed to work with user records stored in an LDAP directory. It is highly configurable, and allows very flexible directory storage of both users and roles. The following sections describe the configuration options for this identity store, and provide some configuration examples.

15.4.3.1. Configuring LdapIdentityStore

The following table describes the properties that can be configured in components.xml for LdapIdentityStore.

Table 15.3. LdapIdentityStore Configuration Properties

Property
Default Value
Description
server-address
localhost
The address of the LDAP server.
server-port
389
The port number that the LDAP server listens on.
user-context-DN
ou=Person,dc=acme,dc=com
The Distinguished Name (DN) of the context containing user records.
user-DN-prefix
uid=
This value is prefixed to the front of the username to locate the user's record.
user-DN-suffix
,ou=Person,dc=acme,dc=com
This value is appended to the end of the username to locate the user's record.
role-context-DN
ou=Role,dc=acme,dc=com
The DN of the context containing role records.
role-DN-prefix
cn=
This value is prefixed to the front of the role name to form the DN that locates the role record.
role-DN-suffix
,ou=Roles,dc=acme,dc=com
This value is appended to the role name to form the DN that locates the role record.
bind-DN
cn=Manager,dc=acme,dc=com
This is the context used to bind to the LDAP server.
bind-credentials
secret
These are the credentials (the password) used to bind to the LDAP server.
user-role-attribute
roles
The attribute name of the user record containing the list of roles that the user is a member of.
role-attribute-is-DN
true
This Boolean property indicates whether the role attribute of the user record is itself a distinguished name.
user-name-attribute
uid
Indicates the user record attribute containing the username.
user-password-attribute
userPassword
Indicates the user record attribute containing the user's password.
first-name-attribute
null
Indicates the user record attribute containing the user's first name.
last-name-attribute
sn
Indicates the user record attribute containing the user's last name.
full-name-attribute
cn
Indicates the user record attribute containing the user's full (common) name.
enabled-attribute
null
Indicates the user record attribute that determines whether the user is enabled.
role-name-attribute
cn
Indicates the role record attribute containing the name of the role.
object-class-attribute
objectClass
Indicates the attribute that determines the class of an object in the directory.
role-object-classes
organizationalRole
An array of the object classes that new role records should be created as.
user-object-classes
person,uidObject
An array of the object classes that new user records should be created as.

15.4.3.2. LdapIdentityStore Configuration Example

The following configuration example shows how LdapIdentityStore can be configured for an LDAP directory running on fictional host directory.mycompany.com. The users are stored within this directory under the ou=Person,dc=mycompany,dc=com context, and are identified by the uid attribute (which corresponds to their username). Roles are stored in their own context, ou=Roles,dc=mycompany,dc=com, and are referenced from the user's entry via the roles attribute. Role entries are identified by their common name (the cn attribute), which corresponds to the role name. In this example, users can be disabled by setting the value of their enabled attribute to false.
 
<security:ldap-identity-store
  server-address="directory.mycompany.com"
  bind-DN="cn=Manager,dc=mycompany,dc=com"
  bind-credentials="secret"
  user-DN-prefix="uid="
  user-DN-suffix=",ou=Person,dc=mycompany,dc=com"
  role-DN-prefix="cn="
  role-DN-suffix=",ou=Roles,dc=mycompany,dc=com"
  user-context-DN="ou=Person,dc=mycompany,dc=com"
  role-context-DN="ou=Roles,dc=mycompany,dc=com"
  user-role-attribute="roles"
  role-name-attribute="cn"
  user-object-classes="person,uidObject"
  enabled-attribute="enabled"
/>

15.4.4. Writing your own IdentityStore

Writing your own identity store implementation allows you to authenticate and perform identity management operations against security providers that are not supported out of the box by Seam. You only need a single class that implements the org.jboss.seam.security.management.IdentityStore interface to achieve this.
Refer to the JavaDoc about IdentityStore for a description of the methods that must be implemented.

15.4.5. Authentication with Identity Management

If you use Identity Management features in your Seam application, then you do not need to provide an authenticator component (see previous Authentication section) to enable authentication. Simply omit the authenticator-method from the identity configuration in components.xml, and the SeamLoginModule will use IdentityManager to authenticate your application's users without any special configuration.

15.4.6. Using IdentityManager

Access the IdentityManager either by injecting it into your Seam component, like so:

@In IdentityManager identityManager;
or, through its static instance() method:

IdentityManager identityManager = IdentityManager.instance();
The following table describes IdentityManager's API methods:

Table 15.4. Identity Management API

Method
Returns
Description
createUser(String name, String password)
boolean
Creates a new user account, with the specified name and password. Returns true if successful; otherwise, returns false.
deleteUser(String name)
boolean
Deletes the user account with the specified name. Returns true if successful; otherwise, returns false.
createRole(String role)
boolean
Creates a new role, with the specified name. Returns true if successful; otherwise, returns false.
deleteRole(String name)
boolean
Deletes the role with the specified name. Returns true if successful; otherwise, returns false.
enableUser(String name)
boolean
Enables the user account with the specified name. Accounts that are not enabled cannot authenticate. Returns true if successful; otherwise, returns false.
disableUser(String name)
boolean
Disables the user account with the specified name. Returns true if successful; otherwise, returns false.
changePassword(String name, String password)
boolean
Changes the password for the user account with the specified name. Returns true if successful; otherwise, returns false.
isUserEnabled(String name)
boolean
Returns true if the specified user account is enabled; otherwise, returns false.
grantRole(String name, String role)
boolean
Grants the specified role to the specified user or role. The role must already exist for it to be granted. Returns true if the role is successfully granted, or false if the user has already been granted the role.
revokeRole(String name, String role)
boolean
Revokes the specified role from the specified user or role. Returns true if the specified user is a member of the role and it is successfully revoked, or false if the user is not a member of the role.
userExists(String name)
boolean
Returns true if the specified user exists, or false if it does not.
listUsers()
List
Returns a list of all user names, sorted in alpha-numeric order.
listUsers(String filter)
List
Returns a list of all user names filtered by the specified filter parameter, sorted in alpha-numeric order.
listRoles()
List
Returns a list of all role names.
getGrantedRoles(String name)
List
Returns a list of all roles explicitly granted to the specified user name.
getImpliedRoles(String name)
List
Returns a list of all roles implicitly granted to the specified user name. Implicitly granted roles include those that are granted to the roles that the user is a member of, rather than granted directly to the user. For example, if the admin role is a member of the user role, and a user is a member of the admin role, then the implied roles for the user are both the admin, and user roles.
authenticate(String name, String password)
boolean
Authenticates the specified username and password using the configured Identity Store. Returns true if successful or false if authentication failed. Successful authentication implies nothing beyond the return value of the method. It does not change the state of the Identity component - to perform a proper Seam log in the Identity.login() must be used instead.
addRoleToGroup(String role, String group)
boolean
Adds the specified role as a member of the specified group. Returns true if the operation is successful.
removeRoleFromGroup(String role, String group)
boolean
Removes the specified role from the specified group. Returns true if the operation is successful.
listRoles()
List
Lists the names of all roles.
A calling user must have appropriate authorization to invoke methods on the Identity Management API. The following table describes the permission requirements for each of the methods in IdentityManager. The permission targets listed below are literal String values.

Table 15.5. Identity Management Security Permissions

Method
Permission Target
Permission Action
createUser()
seam.user
create
deleteUser()
seam.user
delete
createRole()
seam.role
create
deleteRole()
seam.role
delete
enableUser()
seam.user
update
disableUser()
seam.user
update
changePassword()
seam.user
update
isUserEnabled()
seam.user
read
grantRole()
seam.user
update
revokeRole()
seam.user
update
userExists()
seam.user
read
listUsers()
seam.user
read
listRoles()
seam.role
read
addRoleToGroup()
seam.role
update
removeRoleFromGroup()
seam.role
update
The following code listing provides an example set of security rules that grants all admin role members access to all Identity Management-related methods:
rule ManageUsers
  no-loop
  activation-group "permissions"
when
  check: PermissionCheck(name == "seam.user", granted == false)
  Role(name == "admin")
then
  check.grant();
end

rule ManageRoles
  no-loop
  activation-group "permissions"
when
  check: PermissionCheck(name == "seam.role", granted == false)
  Role(name == "admin")
then
  check.grant();
end