ModeShape delegates all authentication and authorization to the providers with which a repository is configured. ModeShape includes a few providers out-of-the-box, but it is also possible to create custom authentication and/or authorization providers.
One exception is the Access Control feature, which provides a way to use the standard JCR API to define node-level access control lists (ACLs) that ModeShape will use to augment the normal authorization mechanism. This fine-grained access controls are handled entirely within ModeShape, stored within the normal repository content, and built on top of the existing authentication and authorization providers.
This section describes how ModeShape's authentication and authorization features work.
Authentication and authorization
In order to create a Session, a client application must authenticate their identity by logging in and providing a javax.jcr.Credential. ModeShape passes this credential to a series of AuthenticationProvider components. The first provider to accept the credential will result in ModeShape authenticating the caller and returning a valid Session.
The authorizing provider, as part of the authentication step, returns an internal SecurityContext that is associated with that session. This SecurityContext is then used to determine whether the session is authorized to read, write, or administer the repository. These are coarse-grained roles that apply to all content; for example, if a session only has the read role, then it can read all repository content but can write or administer no content.
The names of the three roles are "readonly", "readwrite", and "admin".
Anonymous sessions
ModeShape does make it possible for clients to create anonymous sessions. These are never authenticated, and they generally are given only the "readonly" role. Of course, you can choose to configure anonymous sessions to use any of the three roles, though be careful granting more than "readonly".
When a client attempts to authenticate normally by supplying credentials, should that authentication fail the repository can do one of two things:
This is often useful in applications that want to always provide at least some read-only functionality for all users.
Built-in providers
ModeShape includes a few authentication providers:
JAAS
The "org.modeshape.jcr.security.JaasProvider" class is configured to use a specific JAAS policy to perform all authentication and role-based authorization. This is the easiest to use, since most application servers will come with JAAS support and even Java SE applications can pretty easily set up one of the available JAAS implementations.
If no providers are explicitly configured, the JAAS provider is automatically enabled with the "modeshape-jcr" policy.
Configuration
Each JAAS implementation will be configured differently. In the case of the PicketBox implementation, configuration is done via a "jaas.conf.xml" file on the classpath. There are quite a few modules to choose from, including LDAP, database, XACML, and even a simple file-based option. Here is an example of a "jaas.conf.xml" file that uses the users and roles defined in local files:
<?xml version='1.0'?>
<policy xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="urn:jboss:security-config:5.0" xmlns="urn:jboss:security-config:5.0">
<application-policy name="modeshape-jcr">
<authentication>
<login-module code="org.jboss.security.auth.spi.UsersRolesLoginModule" flag="required">
<module-option name="usersProperties">security/users.properties</module-option>
<module-option name="rolesProperties">security/roles.properties</module-option>
</login-module>
</authentication>
</application-policy>
</policy>
This file sets up a JAAS policy named "modeshape-jcr" that uses the User-Roles Login Module, and defines the users and passwords in the "security/users.properties" file and the roles in the "security/roles.properties" file.
The users file contains a line for each user, of the form "username=password". The roles file also contains a line for each user, but this format is a little more complicated:
<username>=<role>\[,<role>,...\]
where:
-
<username> is the name of the user,
-
<role> is an expression describing a role for the user and which adheres to the format "<role>=<roleName>[.<workspaceName]", where:
-
<roleName> is one of "admin", "readonly", "readwrite", or (for WebDAV and RESTful access) "connect"
-
<workspaceName> is the name of the repository workspace to which the role is granted; if absent, the role will be granted for all workspaces in the repository
For example, the following line provides all roles to user 'jsmith' for all workspaces in the configured repository:
jsmith=admin,connect,readonly,readwrite
while
jsmith=connect,readonly,readwrite.ws1
provides connect and read access to all workspaces, but only write access to the "ws1" workspace.
When using the JBoss AS kit, the security mechanism is configured as part of AS, though some of the example configuration files in the kit set up the file-based authentication system via "modeshape-users.properties" and "modeshape-roles.properties" files.
Servlet authentication
Simply configure a repository to this provider, and then have your applications create a "org.modeshape.jcr.api.ServletCredentials" instance with the servlet's HttpServletRequest. ModeShape will then delegate all authentication and role-based authorization to the servlet container. Again, the roles are expected to be "readonly", "readwrite" and "admin".
If no providers are explicitly configured, the Servlet provider is automatically enabled if the servlet API is on the classpath.
Custom providers
If you would like to have ModeShape integrate with a different security system, then you will need to create a custom authorization provider. For more information about this, see the Custom authentication providers page.
Access controls
Recall that the aforementioned role-based authorizations apply to a whole repository or workspace, and thus are referred to as coarse-grained authorization. This simple approach is perfectly acceptable for many applications. However, it is possible to use fine-grained authorization to determine what operations are allowed on specific nodes or subtrees. The API to set up and manage these fine-grained permissions and access control lists is actually part of the standard JCR 2.0 API.
Note that an authenticated user must have already be granted the coarse-grained roles for a repository before any fine-grained access controls are even evaluated. This means that, for example, even if an authenticated user is granted a privilege to modify the properties of a node, that means nothing unless the user has one of the roles that allows writing or changing content. In other words, when using fine-grained access controls, ModeShape will require that both the coarse-grained and fine-grained authorizations allow the requested action.
Privileges
The JCR 2.0 API defines the following privileges:
Privilege
|
Description
|
jcr:read
|
The privilege to retrieve a node and get its properties and their values.
|
jcr:modifyProperties
|
The privilege to create, remove and modify the values of the properties of a node.
|
jcr:addChildNodes
|
The privilege to create child nodes of a node.
|
jcr:removeNode
|
The privilege to remove a node.
|
jcr:removeChildNodes
|
The privilege to remove child nodes of a node. In order to actually remove a node requires jcr:removeNode on that node and jcr:removeChildNodes on the parent node.
|
jcr:write
|
An aggregate privilege that contains: jcr:modifyProperties, jcr:addChildNodes, jcr:removeNode, and jcr:removeChildNodes.
|
jcr:readAccessControl
|
The privilege to read the access control settings of a node.
|
jcr:modifyAccessControl
|
The privilege to modify the access control settings of a node.
|
jcr:lockManagement
|
The privilege to lock and unlock a node.
|
jcr:versionManagement
|
The privilege to perform versioning operations on a node.
|
jcr:nodeTypeManagement
|
The privilege to add and remove mixin node types and change the primary node type of a node.
|
jcr:retentionManagement
|
The privilege to perform retention management operations on a node.
|
jcr:lifecycleManagement
|
The privilege to perform lifecycle operations on a node.
|
jcr:all
|
An aggregate privilege that contains: jcr:read, jcr:write, jcr:readAccessControl, jcr:modifyAccessControl, jcr:lockManagement, jcr:versionManagement, jcr:nodeTypeManagement, jcr:retentionManagement, and jcr:lifecycleManagement
|
See the javax.jcr.security.AccessControlManager API for methods to determine the privileges supported by the repository on any given node and for manually determining whether the session has particular privileges on any given node.
Principals
Privileges are assigned to specific principals, which can either represent usernames or the names of groups. A principal is represented in the API via the javax.security.Prinicpal, which can be any implementation. (ModeShape primarily just uses the principal's name.)
An authenticated user is considered a member of a group if the AuthorizationProvider or AdvancedAuthorizationProvider implementations return true for hasRole(groupName).
Access Control Policies (ACLs)
The privileges granted to a user can be controlled by assigning an access control policy to nodes. Before the access to a node can be controlled, however, it must have the "mode:accessControllable" mixin. Each such node has one or more access control policies to which additional access control entries (e.g., a principal-permissions pair) can be added.
For example, the following code fragment shows how to define an access control policy on a specific node (and its descendants):
String path = "/Cars/Luxury";
String[] privileges = new String[]{Privilege.JCR_READ, Privilege.JCR_WRITE, Privilege.JCR_MODIFY_ACCESS_CONTROL};
Principal principal = ... /* any implementation, referring to a username or group name */
Session session = ...
AccessControlManager acm = session.getAccessControlManager();
// Convert the privilege strings to Privilege instances ...
Privilege[] permissions = new Privilege[privileges.length];
for (int i = 0; i < privileges.length; i++) {
permissions[i] = acm.privilegeFromName(privileges[i]);
}
AccessControlList acl = null;
AccessControlPolicyIterator it = acm.getApplicablePolicies(path);
if (it.hasNext()) {
acl = (AccessControlList)it.nextAccessControlPolicy();
} else {
acl = (AccessControlList)acm.getPolicies(path)[0];
}
acl.addAccessControlEntry(principal, permissions);
acm.setPolicy(path, acl);
session.save();
From this point on, when a session is created by authenticating as a user with the supplied principal (e.g., username or group membership), then that session will be allowed to read, write and modify access controls on the "/Cars/Luxury" node or its descendants (unless otherwise restricted with access controls). Again, this presume that the authentication session already has the coarse-grained roles for reading and writing content in this particular workspace.
Creating an access control entry for a principal that does not exist is not useful, but it is not dangerous, either. Evaluation of access controls requires that the entry match the current session's username or roles (for groups); other principals are never considered.
See the javax.jcr.security.AccessControlManager API and the JSR-283 for more information about defining and using access control policies.