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.
For instance you may want to work with "members" of a workspace, as well as "administrators", and require that users with the administrator role can define who is a member and who is an administrator. This documentation will show you how to implement this kind of behavior, but for any "role" you want, and with even more complex use cases.
<extension point="configuration" target="org.nuxeo.runtime.ConfigurationService">
<require>org.nuxeo.ecm.core.automation.core.properties</require>
<documentation>
Allow setting permissions to virtual groups
</documentation>
<property name="nuxeo.automation.allowVirtualUser">true</property>
</extension>
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 Administration menu. 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, ...), it's better.
Development environment requirements:
- A Nuxeo Studio project (for the Workspace modification and User action definition),
- A Nuxeo Platform instance ready for test,
- Nuxeo CLI (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 prefix&wks
. - 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 summarize the Computed Group Service as follows:
- 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 Nuxeo CLI, and a Nuxeo Server associated to a Nuxeo Connect account.
- 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 membership 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 execute code as a privileged user.
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:`
- Bootstrap an empty project with Nuxeo CLI
- Make sure the project is correctly configured to be hot reloaded:
nuxeo hotreload configure
- Hot reload your project.
- 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.
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. You will need to explicitly switch to a privileged user.
Here you can find the project ready to use.
CoreInstance.doPrivileged
The CoreInstance.doPrivileged
method helps you execute code with a session without security constraint, even if you don't have session available.
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:
List<String> groupIds = new ArrayList<>();
TransactionHelper.runInTransaction(() -> {
CoreInstance.doPrivileged(repositoryName, session -> {
try (IterableQueryResult results = session.queryAndFetch(query, "NXQL")) {
for (Map<String, Serializable> result : results) {
String groupId = (String) result.get("ecm:uuid");
groupIds.add(groupId);
}
}
});
});
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.nuxeo.ecm.core.api.CoreInstance;
import org.nuxeo.ecm.core.api.IterableQueryResult;
import org.nuxeo.ecm.core.api.repository.RepositoryManager;
import org.nuxeo.ecm.platform.usermanager.NuxeoPrincipalImpl;
import org.nuxeo.runtime.api.Framework;
public class ValidatorsGroupComputer extends AbstractGroupComputer {
private static final String QUERY_GET_WORKSPACE_IDS = "SELECT ecm:uuid "
+ "FROM WORKSPACE WHERE wks:validators = '%s'";
@Override
public List<String> getGroupsForUser(NuxeoPrincipalImpl nuxeoPrincipal) {
String username = nuxeoPrincipal.getName();
String query = String.format(QUERY_GET_WORKSPACE_IDS, username);
String repositoryName = Framework.getService(RepositoryManager.class).getDefaultRepositoryName();
List<String> groupIds = new ArrayList<String>();
CoreInstance.doPrivileged(repositoryName, session -> {
try (IterableQueryResult it = session.queryAndFetch(query, "NXQL")) {
for (Map<String, Serializable> map : it) {
String groupId = (String) map.get("ecm:uuid");
groupIds.add(groupId);
}
}
});
return groupIds;
}
@Override
public List<String> getAllGroupIds() {
// TODO Auto-generated method stub
return null;
}
@Override
public List<String> getGroupMembers(String groupName) {
// TODO Auto-generated method stub
return null;
}
@Override
public List<String> getParentsGroupNames(String groupName) {
// TODO Auto-generated method stub
return null;
}
@Override
public List<String> getSubGroupsNames(String groupName) {
// TODO Auto-generated method stub
return null;
}
}
TEST 1
As you can see in this example, the computer group statically returns myTestGroup
. Let's test your test environment:
- Restart your server.
- Hot reload your project.
- 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
- Open 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.
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
- Deploy your project.
- 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.