In this post we'll look at a requested customization for Alfresco Share and how it can be implemented by overriding the controller of an existing Alfresco Web Script.
First let's look at the following functionality in Alfresco Share.
By default, any user in Alfresco can start a workflow on a document which they have access to. If a user chooses to create a New Task (ad hoc task) workflow on a document, the workflow creation form is displayed, and on that form, the user is able to select a user to assign the new task to.
In the "Select..." dialog that pops up, the user is able to search across all registered Alfresco users.
It looks like this:
Customization Requirement
The requirement for the customization is, for all users other than admin users, to limit the search result to include only users that belong to the same groups which they are members of.
Implementation
How can we achieve that?After a little investigation, we find that the user assignment popup is a kind of picker object, and that the client JavaScript code for the picker searches for users by making a call to a repository Web Script. The controller for that Web Script is the following file:
alfresco/templates/webscripts/org/alfresco/repository/forms/pickerchildren.get.js.
[Note that this file can be found bundled in the repo jar file: alfresco-remote-api-5.x.x.jar]
Within the Web Script, the function that gets called to search for users is findUsers(). We'll change this method to limit the search results from the Picker to include only those users that belong to same groups that the user belongs to.
function findUsers(searchTerm, maxResults, results) { var personRefs = people.getPeople(searchTerm, maxResults, "lastName", true); // create person object for each result for each(var personRef in personRefs) { if (logger.isLoggingEnabled()) logger.log("found personRefs = " + personRefs); //filter out the disabled users var daname = (search.findNode(personRef)).properties.userName; if(people.isAccountEnabled(daname)){ results.push( { item: createPersonResult(search.findNode(personRef)), selectable: true }); } else{ results.push( { item: createPersonResult(search.findNode(personRef)), selectable: false }); if (logger.isLoggingEnabled()) logger.log("User not added to results not enabled" + daname); } } }
We see here that searches are currently being made using the Alfresco JavaScript API using root object 'people':
var personRefs = people.getPeople(searchTerm, maxResults, "lastName", true);
The requirement for the customization is to change the search by limiting the results to only those users that belong to the same group as the current user. We can again use the 'people' root-scoped object to find which groups the user belongs to:
var userGroups = people.getContainerGroups(person);
But the problem with the method getContainerGroups() is that it does not include system groups with the results. It would be great if this method took another parameter as a flag to specify that system groups should be included in the result, but it doesn't. System groups include the Share groups created for each site for managing the four default Site roles: site manager, collaborator, contributor, and consumer.
If a user belongs to a Share site, we also want to include in the results any users which belong to any of the four Share site groups for that site. To find the additional site groups corresponding to the sites that a user belongs to, we will write a new method. That is shown in the following code which takes a list and adds the Share site groups to the list.
function addSiteGroups(g) { var s = siteService.findSites("", null, 0); for(var j=0; j<s.length; j++) { var perms = s[j].getSitePermissionGroups(); for(var k=0;k<perms.length;k++) { g.push( groups.getGroupForFullAuthorityName(perms[k]).getGroupNode() ); } } }
Here we query the site service to find all sites that the current user is able to access. Then we get the names of the roles for the site -- there typically will be just the four we mentioned earlier. Then we get the group corresponding to each of those permissions and push the group nodes onto the list of groups:
groups.getGroupForFullAuthorityName(perms[k]).getGroupNode()
Once we have the list of groups that the user belongs to, we can make a query to find only those people that match the search criteria and also are members of those groups.
First we a build a string which contains part of the condition for the query that lists the groups that the user belongs:
grpQuery = " AND ("; // make concatenated list of groups for(var i=0; i<userGroups.length; i++) { if(i>0) { grpQuery = grpQuery + " OR "; } grpQuery = grpQuery + "PARENT:\"" + userGroups[i].nodeRef.toString() + "\" "; } grpQuery = grpQuery + ")";
The query is as follows. Note that 'filterTerm' corresponds to the text entered by the user within the UI. 'grpQuery' corresponds to the criteria for the group restriction. Note that we search for objects that are type cm:person and which do not have the cm:personDisabled flag applied.
var def = { query: "+TYPE:\"cm:person\" AND (-ASPECT:\"cm:personDisabled\") AND(cm:lastName:\"*" + filterTerm + "*\" OR cm:userName:\"*" + filterTerm + "*\")" + grpQuery, store: "workspace://SpacesStore", language: "fts-alfresco", page: paging };
Putting it all together, the combined changes to the Web Script are as follows:
function addSiteGroups(g) { var s = siteService.findSites("", null, 0); for(var j=0; j<s.length; j++) { var perms = s[j].getSitePermissionGroups(); for(var k=0;k<perms.length;k++) { g.push( groups.getGroupForFullAuthorityName(perms[k]).getGroupNode() ); } } } // Modified by Formtek to find only group to which the user belongs function findUsers(filterTerm, maxResults, results) { var paging = { maxItems: maxResults, skipCount: 0 }; var grpQuery = ""; // If the user is not an admin, restrict the search if(!people.isAdmin(people.getPerson(person.properties["cm:userName"]))) { // Find all the groups that the user is in var userGroups = people.getContainerGroups(person); addSiteGroups(userGroups); grpQuery = " AND ("; // make concatenated list of groups for(var i=0; i<userGroups.length; i++) { if(i>0) { grpQuery = grpQuery + " OR "; } grpQuery = grpQuery + "PARENT:\"" + userGroups[i].nodeRef.toString() + "\" "; } grpQuery = grpQuery + ")"; } var def = { query: "+TYPE:\"cm:person\" AND -ASPECT:\"cm:personDisabled\" AND (cm:firstName:\"*" + filterTerm + "*\" OR cm:lastName:\"*" + filterTerm + "*\" OR cm:userName:\"*" + filterTerm + "*\")" + grpQuery, store: "workspace://SpacesStore", language: "fts-alfresco", page: paging }; var personRefs = search.query(def); // create person object for each result for each(var personRef in personRefs) { // add to results results.push( { item: createPersonResult(search.findNode(personRef.nodeRef)), selectable: true }); } }
To override the existing Web Script file, copy the original file into the extensions area and make the change to the findUsers() method as shown above here:
tomcat/shared/WEB-INF/classes/alfresco/extension/templates/webscripts/org/alfresco/repository/forms/pickerchildren.get.js.
One additional thing to notice is that filtering by group is only applied when the user is not an admin:
people.isAdmin(people.getPerson(person.properties["cm:userName"]))
Workflow assignments of tasks to users will now use the override method defined here and users will be filtered by group.