Friday, June 04, 2010

Alfresco: Users, Permissions and Workflow

As you probably know, I am building a proof-of-concept with Alfresco to host blog posts by a group of bloggers, each with their own blog. This models a corporate blogging environment, so there is an editor who reviews the stuff the bloggers write, before it is published on to the website.

Since setting up users is likely to be an infrequent occurrence, I decided to do most of the work using the Alfresco web client. Basically, you create users and groups, then assign users to groups, then assign groups to roles. Roles are basically a set of permissions. Alfresco comes with a built-in set of roles (which can be extended if needed). While all this is relatively straightforward once you've been through it once, it probably helps if you have the sequence of steps. I relied heavily on Munwar Sharif's book for this. Here is my sequence of steps.

  1. From the Admin Console as admin, I chose Manage System Users. Then I created users happy, grumpy, bashful and doc with their own home directories under User Homes. Users happy, grumpy and bashful are the bloggers, and doc is the editor.
  2. From the Admin Console as admin, I chose Manage System Groups. Then I created two groups GROUP_BLOGGER and GROUP_EDITOR.
  3. In the same Manage System Group form, I then added happy, grumpy and bashful to GROUP_BLOGGER and doc to GROUP_EDITOR.

Now we need a shared area where these users can collaborate. For this stuff to make sense, we also need to understand the workflow, so here it is. Individual bloggers write their posts in their home directories. Once they are happy with it, they move this to a common Review directory. Bloggers should only be able to create posts in the Review directory, once its there, they cannot edit it anymore. The Editor has full rights on the Review directory. Once the Editor is happy with the post, he publishes it by moving it to the Published directory. If the Editor decides that it should be scheduled for publish in the future, he specifies a publish date and moves it to the Pending directory (and lets cron/quartz take it from there). If he is not happy with the post, he can return it by moving it back to the Blogger's home directory. Once the document is back in the Blogger's home directory, the Editor should not be able to modify it. The state diagram below illustrates the complete workflow.

For this, we create a shared folder called Public under Company Home, under which are sub-folders Review, Pending, Published and Archived. You can see all these folders on the left nav in the screenshot below.

Since the Public folder and its sub-folders are owned by admin, we need to give our users appropriate rights on these folders to allow them to do their job. This is done by navigating to the folder in question and "inviting" groups into the folder (click More Actions/Manage Space Users/Invite).

For the Reviews folder, we add GROUP_BLOGGER as Contributor and GROUP_EDITOR as Collaborator. For the other sub-folders under Public, we add GROUP_EDITOR as Collaborator. For the home directories, we add GROUP_EDITOR as Contributor. The table below summarizes this.

User (Group) Blogger Homes Review Published Pending Archived
bashful (GROUP_BLOGGER) All Contributor None None None
grumpy (GROUP_BLOGGER) All Contributor None None None
happy (GROUP_BLOGGER) All Contributor None None None
doc (GROUP_EDITOR) Contributor Collaborator Collaborator Collaborator Collaborator

If you look at the permissions that the Collaborator and Contributor have, you will see that Collaborator has powers equal to the owner of the folder, and a Contributor can create content but not modify it (as long as he does not own the target content). So obviously, this is way more power than they need and ideally, I should have built a bunch of custom roles for these groups and assigned them on a per-folder basis.

I did consider doing this, and Jeff Pott's book has a fairly easy to follow guide. However, midway through this, I realized that this is only needed if I expose the Alfresco Web Client to my bloggers and editors (which I am not planning on doing - instead I have in mind a simple Drupal like interface that communicates with Alfresco via web scripts). So I finally opted to just go with the roles that Alfresco has.

I've also been playing a bit with the Alfresco Foundation API, and I wanted to see if I could use that to generate the permissions table above, so here is the code - it just finds the Node references of interest for the 8 folders we have and reports on the users/groups and their permissions on each of them. Not very useful, but I thought you may find it interesting.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
// Source: src/java/com/mycompany/alfresco/extension/reports/UserReport.java
package com.mycompany.alfresco.extension.reports;

import java.util.HashMap;
import java.util.Map;
import java.util.Set;

import javax.transaction.UserTransaction;

import org.alfresco.service.ServiceRegistry;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.StoreRef;
import org.alfresco.service.cmr.search.ResultSet;
import org.alfresco.service.cmr.search.SearchService;
import org.alfresco.service.cmr.security.AccessPermission;
import org.alfresco.service.cmr.security.AuthenticationService;
import org.alfresco.service.cmr.security.PermissionService;
import org.alfresco.service.transaction.TransactionService;
import org.alfresco.util.ApplicationContextHelper;
import org.apache.commons.lang.ArrayUtils;
import org.junit.Test;
import org.springframework.context.ApplicationContext;

/**
 * Generates a report of the users currently in Alfresco.
 */
public class UserReport {

  private static ApplicationContext ctx = 
    ApplicationContextHelper.getApplicationContext();

  // Find all the spaces we are interested in.
  // We found these directories using the node browser
  @SuppressWarnings("unchecked")
  private static final Map<String,String> INTERESTING_SPACES = 
    ArrayUtils.toMap(new Object[][] {
    new String[] {"Happy Home", "/app:company_home/app:user_homes/sys:Happy"},
    new String[] {"Grumpy Home", "/app:company_home/app:user_homes/sys:Grumpy"},
    new String[] {"Bashful Home", "/app:company_home/app:user_homes/sys:Bashful"},
    new String[] {"Doc Home", "/app:company_home/app:user_homes/sys:Doctor"},
    new String[] {"Review", "/app:company_home/cm:Public/cm:Review"},
    new String[] {"Published", "/app:company_home/cm:Public/cm:Published"},
    new String[] {"Pending", "/app:company_home/cm:Public/cm:Pending"},
    new String[] {"Archived", "/app:company_home/cm:Public/cm:Archived"}
  });

  public void generate() throws Exception {
    ServiceRegistry serviceRegistry = 
      (ServiceRegistry) ctx.getBean(ServiceRegistry.SERVICE_REGISTRY);
    // log in
    AuthenticationService authenticationService = 
      serviceRegistry.getAuthenticationService();
    authenticationService.authenticate("admin", "admin".toCharArray());
    TransactionService transactionService = 
     serviceRegistry.getTransactionService();
    UserTransaction tx = transactionService.getUserTransaction();
    tx.begin();
    // find the corresponding NodeRefs for our interesting spaces
    SearchService searchService = serviceRegistry.getSearchService();
    Map<String,NodeRef> nodeRefs = new HashMap<String,NodeRef>();
    for (String spaceName : INTERESTING_SPACES.keySet()) {
      ResultSet results = searchService.query(
        StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, 
        SearchService.LANGUAGE_XPATH, INTERESTING_SPACES.get(spaceName));
      nodeRefs.put(spaceName, results.getNodeRef(0));
    }
    // find the permissions and users/groups for each space
    PermissionService permissionService = 
      serviceRegistry.getPermissionService();
    for (String spaceName : nodeRefs.keySet()) {
      Set<AccessPermission> permissions = 
        permissionService.getAllSetPermissions(nodeRefs.get(spaceName));
      for (AccessPermission permission : permissions) {
        System.out.println(spaceName + " => " + 
          permission.getPermission() + " - " + permission.getAuthority());
      }
    }
    tx.commit();
  }
  
  @Test
  public void testGenerate() throws Exception {
    UserReport report = new UserReport();
    report.generate();
  }
}

I have noted earlier that I liked the "everything is a node" paradigm in Alfresco. Yet another thing to like about Alfresco is its concept of grouping documents into spaces, each space with its own permissions. As you can perhaps guess from the correlation between the sub-folders under Public and the nodes in my workflow state diagram, I plan to use the folder concept to model my workflow. If my workflow were linear, then I could have set it up using the Web client itself - the alternative is to use the bundled jBPM workflow engine. However, I think just using the properties of the node and its position to determine its position in the worflow will be just as effective. But more on that later.

7 comments (moderated to prevent spam):

Anonymous said...

Hi,

I run this code but I am getting
Exception in thread "main" java.lang.ExceptionInInitializerError
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'indexerComponent' defined in class path resource [alfresco/core-services-context.xml]: Cannot resolve reference to bean '
.......

How u are solving this issue??

Sujit Pal said...

Not sure, I never saw this error myself. From the error it looks like it cannot open/create the indexer bean. Your stack trace will probably tell you more (look for "Caused by"). One thing I suspect is that the user running Tomcat has no permission to create an index on the alf_data directory, but not 100% sure of this.

Anonymous said...

Hi,

I have an AD connected to Alfresco. My task is to assign roles to all the users using xml config file. Would you please tell me if I have to code this task from the scratch or just need to change some files? Do you have any idea which files should be used for this task.? I have been struggling with this issue since last week:(.

Thanks in advance.

Sujit Pal said...

Hi, sorry about the delay, hopefully you have solved your problem by now. I am not sure what an AD is? If the xml configuration file is in the Alfresco repository, there should be no coding required, you should be able to assign rights for this file to your users. But its probably not what you were asking?

kavitha said...

Hi sujitpal
this is kavitha.last one year onwards i am using yor code .i am very thank ful to u for that .now i have one problem in alfresco using java api.
my problem is i want source code for scanning documents and stored in repository .Document will be external or internal .
i want complete source code .i tried so many ways atill i am not getting exact result. its very urgent .please send me code . my id is software.kavitha@gmail.com.

Having spent some time on my studies, I would like to extend my formal qualifications and work experience gained to develop these skills in an IT/Engineering environment. said...

Hi Alfresco Friends,

I'm currently using [b][i]Alfresco Community 3.4.d on Ubuntu 10.04 server[/i][/b].

User-1 tried to invite other users to User-1's space as follows. There is no problem up to the invitation process and successfully and mail also delivered to assigned users. However my issue is how User-2 and User-3 access this space. Where is notification or link appear ? [i](Other similar Eg: When we initiate [b]"Advanced Work-flow"[/b], relevant assigned users can view job in [b]"My Tasks To Do"[/b] area.)[/i].

[b]User Homes[/b]
...........|
...........|
...........|------[b]User-1[/b] [i](Private Space, Only User-1 can access this. User-2 & User-3 no permission to even read this space.)[/i]
...........|...........|------[b][i]Space-1-1[/i][/b] [i](Private Space)[/i]
...........|...........|------[b][i]Space-1-2 [/i][/b][i](User-2 & User-3 are Consumers)[/i]
...........|...........|------[b][i]Space-1-3[/i][/b] [i](User-2 is Editor)[/i]
...........|
...........|------[b]User-2[/b]
...........|
...........|
...........|------[b]User-2[/b]

Please help me to solve this problem.

Thank you very much...................

Sujit Pal said...

@kavitha: sorry for the delay, your comment went into my spam bucket, was looking for another comment which I remembered, found yours in there too. If you use Alfresco as a document repository (ie the default approach, you store binary files as files with metadata attached to it), then you can use the appropriate third-party tool/API to scan the paper docs into electronic files and save them to Alfresco. On the output side, use the API provided by your third-party tool/API to display the document. It seems too easy though, perhaps I am missing something?

@Having...: Once again, blogger thought your comment was spam, almost certainly because of the very long handle, so sorry about the delay in responding. Regarding your question, my preference is to create a separate area (with a separate user and rights) which can be accessed by multiple people - I have described this setup in one of my Alfresco related posts here.