In this how to, you will learn how to let managers of a workspace determine who is part of locally defined groups (local to the workspace). It is like implementing a "role" notion.
The Nuxeo security system gives you all the tools you need to define security from giving a simple right to a specific user on a document to defining complex use cases. You can basically play with ACLs, granting and denying permissions to users and groups. Groups in Nuxeo are defined by users part of the "powerusers" or "administrators" groups, in the Admin Center. But it is also possible to define another category of groups, whose content definition is not "manual": the computed groups.
Computed groups let you define a list of groups to which users will be affected using Java code. There are multiple use cases where you will need this feature. Implementation will require Java development knowledge and if you are familiar with Nuxeo Core Development (CoreSession, DocumentModel, UnrestrictedRunner, ...), it's better.
Development environment requirements:
- a Nuxeo Studio project (for the Workspace modification and User action definition),
- a Nuxeo SDK instance ready for test,
- Nuxeo IDE (for bundle creation and computed group definition).
Examples of uses cases for which you will need computed groups:
- delegation management
- local groups on a workspace,
- group resolved by complex logic more generally.
In the coming example we will implement the notion of "local groups" thanks to computed groups. Users with management permission on the workspace will be able to decide who is part of the "validators" group of the workspace by editing one of the metadata of this workspace. Some more complex examples can be thought of. Here is the global strategy to implement this use case:
- I want to create a virtual group named
$idWorkspace_validator
for each workspace, where$idWorkspace
is the id of the workspace. Virtual means that the group is not referenced in the group directory. Affectation of users to the group will be resolved by a piece of Java code, just after the user gets authenticated to the system. - Users affected to the
$idWorkspace_validator
group are the ones listed in thewks:validator
property of the Workspace$idWorkspace
.wks:validators
must be added as a property containing the list of user names, on the Workspace type. - I want to create a button on each document whose state is
inProgress
. Clicking on this button starts a simple validation workflow with one validation task assigned to the$idWorkspace__validator
group where$idWorkspace
is the id of the closest parent workspace.
This use case is simple but from this example you can easily implement the delegation feature.
Preparatory Step: Updating the Workspace Document Type
Here, we will just add the wks:validators
field to the Workspace document type definition and improve the form to let the manager of the workspace set this value.
- In Studio, create a schema with the name
workspace
and prefixwks
. - In this schema, define a field
validators
as a list of string. - In Studio, create a
Workspace
document type (to override the default Workspace document type set by Nuxeo). - In the Workspace definition, add the schema
workspace
as an extra schema. - In the Creation, View and Edition layouts, replace the Warning... widget by the
wks:validators
field and set the widgetMultiple Users/Groups suggestion
and force only users suggestions.
Now, you can specify a list of validators into each workspace. Let's make users of this list members of the $idWorkspace_validator
group.
Java-based membership definition
This part is the most interesting part of this presentation, where we see how Computed Group Service is leveraged to have dynamical group definitions.
We can resume the Computed Group Service like that:
- You can register a class that implements a method that will be called just after each user connection.
- The list of strings returned by your method will be considered as the list of virtual groups the user belongs to.
Preparing the Project
This part assumes you have IDEDOC configured with the Nuxeo SDK associated and Nuxeo Connect account referenced. Please look at the page Getting Started with Nuxeo IDE if you don't.
- Create a new Nuxeo Plugin Project.
Add the following component:
src/main/resources/OSGI-INF/test-computed-group-contrib.xml<?xml version="1.0"?> <component name="org.nuxeo.mail.management.security.computer.group.contribution" version="1.0"> <extension point="computer" target="org.nuxeo.ecm.platform.computedgroups.ComputedGroupsServiceImpl"> <groupComputer name="ValidatorsGroupComputer"> <computer>org.nuxeo.project.computed.group.ValidatorsGroupComputer </computer> </groupComputer> </extension> <extension point="computerChain" target="org.nuxeo.ecm.platform.computedgroups.ComputedGroupsServiceImpl"> <groupComputerChain append="true"> <computers> <computer>ValidatorsGroupComputer</computer> </computers> </groupComputerChain> </extension> </component>
- The first contribution
computer
defines the class that will implement the logic that will return the list of virtual groups the user belongs to. - The second contribution
computerChain
enables to contribute and chain multiple resolution logics.
- The first contribution
Don't forget to reference the XML contribution in the
src/main/resources/META-INF/MANIFEST.MF
. The file must be like that:MANIFEST.MFBundle-ActivationPolicy: lazy Bundle-ClassPath: . Manifest-Version: 1.0 Bundle-Name: test-computed-group Bundle-RequiredExecutionEnvironment: JavaSE-1.6 Nuxeo-Component: OSGI-INF/test-computed-group-contrib.xml Bundle-Version: 5.7.1 Bundle-ManifestVersion: 2 Bundle-SymbolicName: test-computed-group Bundle-Vendor: Nuxeo
Don't forget to let the last line empty, without any character.
Now, let's see how to implement the memberhsip logic based on the
wks:validators
property value.
Coding your Computer Group
In the previous section we asked Nuxeo Runtime to register our new computer group. We named the class org.nuxeo.project.computed.group.ValidatorsGroupComputer_._
- So we must first create a class in
src/main/java
, defined in the packageorg.nuxeo.project.computed.group
and namedValidatorsGroupComputer
. - This class must implement the
GroupComputer
interface. The Nuxeo Platform delivers an abstraction of this class with main class implemented namedAbstractGroupComputer
. We suggest to extend this class. - The main method to implement is the
getGroupsForUser
that returns the list of virtual groups to which the user belongs given as parameter. - The difficulty is that when
getGroupsForUser
is called the user is not yet connected. So you must play with the Unrestricted Runner object.
ValidatorsGroupComputer
Class Creation
- As usual, simply create a class in the
src/main/java
. Mark it as extending the
AbstractGroupComputer
class. You must have something like that:Simple Static Computer Grouppackage org.nuxeo.project.computed.group; import java.util.ArrayList; import java.util.List; import org.nuxeo.ecm.platform.computedgroups.AbstractGroupComputer; import org.nuxeo.ecm.platform.usermanager.NuxeoPrincipalImpl; /** * @since 5.7.2 * */ public class ValidatorsGroupComputer extends AbstractGroupComputer { @Override public List<String> getGroupsForUser(NuxeoPrincipalImpl nuxeoPrincipal) throws Exception { List<String> result = new ArrayList<String>(); result.add("myTestGroup"); return result; } @Override public List<String> getAllGroupIds() throws Exception { // TODO Auto-generated method stub return null; } @Override public List<String> getGroupMembers(String groupName) throws Exception { // TODO Auto-generated method stub return null; } @Override public List<String> getParentsGroupNames(String groupName) throws Exception { // TODO Auto-generated method stub return null; } @Override public List<String> getSubGroupsNames(String groupName) throws Exception { // TODO Auto-generated method stub return null; } }
TEST
As you can see in this example, the computer group statically returns myTestGroup
. Let's test your test environment:
- Start your SDK instance from the Nuxeo IDE interface. See the page Getting Started with Nuxeo IDE for details.
- Add your project into the deployment configuration.
- Refresh the deployment server.
- Connect as Administrator into your Nuxeo instance.
- Go to Home > Profile.
- You must see a section virtual user into the main view with the
myTestGroup_
referenced.
If you don't have this please look errors message into your Java project and into the server console.
If you refresh several times your project in the SDK server, you will see myTestGroup
as many times as you did refresh. This is because the extension point registering your Computer Group adds your contribution with each refresh. But if you stop and restart the server, your contribution will be deployed once, and myTestGroup
will be displayed once.
Now, we need to replace this static result by a dynamic one that will be the list of $idWorkspace_validator
where the user is referenced. But when the getGroupsForUser
method is called, no Session on the Core Repository is available as the user is not yet connected. Here comes the UnrestrictedRunner
object.
Here you can find the project ready to use.
UnrestrictedRunner Object
Extending the UnrestrictedRunner
object helps you executing code with a session without security constraint, even if you don't have session available.
How it works:
- Define a constructor where you will initialize the parameters needed for your code unrestricted execution.
- Implement a run method where a
CoreSession
will be available without restriction. - Execute the
runUnrestricted
method that will execute your run implementation without restriction.
Why do we need of this? Because in our example, we would like to fetch all workspaces where the user about to connect is referenced into the wks:validators
field.
In other words, we would like to make the following query SELECT * FROM Workspace WHERE wks:validators = 'theUsername'
, to get the id of each workspace to create the dynamic virtual groups list. Here is the code result:
protected class GetWorkspaceIds extends UnrestrictedSessionRunner {
private static final String QUERY_GET_WORKSPACE_IDS = "SELECT ecm:uuid "
+ "FROM WORKSPACE WHERE wks:validators = '%s'";
public IterableQueryResult ids = null;
private String username;
protected GetWorkspaceIds(String repositoryName, String username)
throws Exception {
super(repositoryName);
this.username = username;
}
@Override
public void run() throws ClientException {
String query = String.format(QUERY_GET_WORKSPACE_IDS, username);
ids = session.queryAndFetch(query, "NXQL");
}
}
You can create this class as a public class, but we suggest to create it directly into the Computer Group.
You will need this pattern several times to implement you Computer Group. So lets move to the next section and first replace the getGroupsForUser
method with the dynamic resolution one.
ValidatorsGroupComputer Class with Dynamic Group List Resolution
In this section we will just merge information from the two previous ones and test it.
Here is the final version of the ValidatorsGroupComputer
class:
package org.nuxeo.project.computed.group;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.nuxeo.ecm.core.api.ClientException;
import org.nuxeo.ecm.core.api.IterableQueryResult;
import org.nuxeo.ecm.core.api.UnrestrictedSessionRunner;
import org.nuxeo.ecm.core.api.repository.RepositoryManager;
import org.nuxeo.ecm.platform.computedgroups.AbstractGroupComputer;
import org.nuxeo.ecm.platform.usermanager.NuxeoPrincipalImpl;
import org.nuxeo.runtime.api.Framework;
/**
* @since 5.7.2
*
*/
public class ValidatorsGroupComputer extends AbstractGroupComputer {
private static final Log log = LogFactory.getLog(ValidatorsGroupComputer.class);
@Override
public List<String> getGroupsForUser(NuxeoPrincipalImpl nuxeoPrincipal)
throws Exception {
String username = nuxeoPrincipal.getName();
GetWorkspaceIds runner = new GetWorkspaceIds(getRepository(), username);
runner.runUnrestricted();
List<String> groupIds = new ArrayList<String>();
String groupId = null;
for (Map<String, Serializable> id : runner.ids) {
groupId = ((String) id.get("ecm:uuid")) + "_validator";
log.debug("Virtual Group Id found: " + groupId);
groupIds.add(groupId);
}
return groupIds;
}
@Override
public List<String> getAllGroupIds() throws Exception {
// TODO Auto-generated method stub
return null;
}
@Override
public List<String> getGroupMembers(String groupName) throws Exception {
// TODO Auto-generated method stub
return null;
}
@Override
public List<String> getParentsGroupNames(String groupName) throws Exception {
// TODO Auto-generated method stub
return null;
}
@Override
public List<String> getSubGroupsNames(String groupName) throws Exception {
// TODO Auto-generated method stub
return null;
}
protected class GetWorkspaceIds extends UnrestrictedSessionRunner {
private static final String QUERY_GET_WORKSPACE_IDS = "SELECT ecm:uuid "
+ "FROM WORKSPACE WHERE wks:validators = '%s'";
public IterableQueryResult ids = null;
private String username;
protected GetWorkspaceIds(String repositoryName, String username)
throws Exception {
super(repositoryName);
this.username = username;
}
@Override
public void run() throws ClientException {
String query = String.format(QUERY_GET_WORKSPACE_IDS, username);
ids = session.queryAndFetch(query, "NXQL");
}
}
private String getRepository() {
return Framework.getLocalService(RepositoryManager.class).getDefaultRepository().getName();
}
}
TEST 1
As you can see in this example, the computer group statically returns myTestGroup
. Let's test your test environment:
- Stop your server from the Nuxeo IDE interface (if you didn't do it).
- Start it again from the Nuxeo IDE interface.
- Add your project into the deployment configuration (if you removed it).
- Refresh the deployment server.
- Connect as Administrator into your Nuxeo instance.
- Go to Home > Profile.
- You must see a section virtual user into the main view with no group referenced.
TEST 2
- Right-click on your Java project into the Nuxeo IDE.
- Go to Nuxeo > Nuxeo Studio.
- Check the Nuxeo Studio Project where you defined the Workspace with the workspace schema and validate.
- Refresh.
- Connect as Administrator into your Nuxeo instance.
- Create a workspace and add Administrator as validator.
- Log out.
- Log in as Administrator.
- Go to Home > Profile.
- You must see a section virtual user into the main view with one group referenced.
Why does Administrator log out? Because the resolution of groups are only during the connection.
Button Creation
And now we have to create the button that starts the workflow and assign the validation task to the virtual group.
This part is a pure Studio demonstration, just a way of making sure our newly defined group do work.
Create a user action:
- Choose the Contextual Tool category.
- Add filter to limit to users that have Edit permission.
Create an automation chain and attach it to this action. In the Automation Chain definition:
- Fetch > Context Document(s).
- Execution Context > Set Context Variable From Input:
name = "documentToValidate"
. - Document > Get Parent :
type = Workspace
. - Execution Context > Set Context Variable:
name = "validatorGroup", value = "group:@{Document.id}_validator"
. - Execution Context > Restore Document Input :
name = "documentToValidate"
. - Service > Create Task : task name = "Vallidation", directive = "Please Validate the document", variable name for actors prefixed = "validatorGroup", create one task per actor = unchecked
TEST
- Stop your server from the Nuxeo IDE interface (if you didn't do it).
- Start it again from the Nuxeo IDE interface.
- Refresh the Nuxeo Studio Panel in Nuxeo IDE interface.
- Add your project into the deployment configuration (if you removed it).
- Refresh the deployment server.
- Connect as Administrator into your Nuxeo instance.
- Create two users: user1 and user2.
- Create a workspace and set user1 as validator.
- Create a File and click on your button.
- Connect as user1. On his Home Dashboard, you will see a task assigned on the File document.
- Log out and connect as Administrator.
- Modify the Workspace and add user2 into the validators list.
- Log out and connect as user1. On his Home Dashboard, you will see a task assigned on the File document.
Conclusion
Next steps could be:
- Add a listener on the Workspace to add an ACE on it to grant read or edit permission on the workspace for the validator group.
- Implement a delegation document type that stores a missing user and a list of delegated users and add a computer group that resolves indirect group assignment through this object.