Chapter 11. Access Control And Security [DRAFT]

Table of Contents

11.1. This is a DRAFT! Give your opinion!
11.2. Document Security
11.3. ACE, ACL, ACP, and other acronyms
11.4. The repository and paths
11.4.1. Ordering of ACLs and ACEs
11.5. Seeing access control in action
11.6. For the truly interested (or dangerously curious)
11.7. Granting the social director special powers and other exercises

Objective: To understand and manipulate Nuxeo's model of controlling who can access, and what way, documents under the repository's control.

The user's going to pick dancing pigs over security every time. — Bruce Schneier (American Security Pundit, 1963 - )

11.1. This is a DRAFT! Give your opinion!

If you have any comments, questions, or general-purpose harassment you would like give us about this book, then please use the comment form at the bottom of each page! We promise that we will try to incorporate any feedback you give (minus the profanity, of course), will respond to your questions, and credit you appropriately.

11.2. Document Security

Doing computer security correctly and diligently every time is a bit like eating the correct number of number of fruit and vegetable servings each day while exercising regularly; everybody says that they do it but few people actually do. Fortunately, we are not going to be asking you to properly secure and manage your whole computer - although you should be that anyway, just like eating your broccoli! - but just the files you create and manage with Nuxeo.

In Nuxeo, each document has a collection of security entries that allow or disallow actions to be performed on the document. If you are using the Nuxeo EP very much at all, you will probably have noticed the Access Rights tab:

In the darker section of the screenshot above, you can see that the process of adding a permission for the members of the group "members" to be able write in the selected workspace.

11.3. ACE, ACL, ACP, and other acronyms

Creating a permission, as we show in the previous screen shot, adds a base element or an Access Control Element, or ACE, to the data structures Nuxeo maintains about each item in the repository. ACEs describe a single access control policy, such as "user Fred can write to this file" or "the group of users named 'students' are denied any access to folder 'grades'." Each ACE has three parts, a user or group, a type of permission like read or write, and one of 'grant' or 'deny.'

A collection of ACEs form an ACL, naturally, an Access Control List. For example, a typical ACL might include granting the ability to do anything to the 'Administrator' user and the ability to read anything to the group 'members.' It should come as no surprise that access control lists themselves are aggregated to form the ACP of a document. ACP stands for access control policy and when you have access to a document in Java code (usually in the form of a DocumentModel object) you can use getACP() to retrieve the ACP for the document. The question that remains is how, by creating ACE's (the unit that a developer typically works with), can one end up with the complex aggregate structure such as the ACP associated with a document? To do that, we need to explain a bit more about the repository.

11.4. The repository and paths

Every object in the repository has an associated "path" just like in file systems you are probably familiar with. When you use the web interface to Nuxeo EP server, you probably never see or notice a path. The web UI typically shows you a much more "logical" view of your documents. Under the covers, though, the repository has a heirarchical organization and each level of the heirarchy inherits the ACLs from its parents. In principle, for a document with a path like /joe/dimaggio/bats/for/average.txt, the document average.txt would have a collection of 4 ACLs that it inherited, one from each parent. To make things a bit simpler, Nuxeo actually computes one equivalent ACL for all the parents, so average.txt would actually inherit only one ACL from its parent - but the result is calculated to be equivalent to inheriting all four.

So, we can now explain one of the of the ACLs, inherited from parent, that make up an ACP ... what about the rest? Another ACL is the one created via the UI shown in the screen shot above. That user interface is usually manipulated by the system administrator, a member of the 'Administrator' group. If you look carefully, you will also notice a checkbox whose meaning is, roughly, 'don't use the that other ACL we just described!' These two ACLs are the only ACLs that most documents in a repository have; other ACLs are created by specific applications or subsystems and added to the list (the ACP) for specific purposes. For example, a document involved in a workflow may be an 'exception' to certain access control policies so that the workflow may proceed.

11.4.1. Ordering of ACLs and ACEs

We mentioned before that an ACP is a list of ACLs and an ACL is a list of ACEs. That was true, but not all the information you need to understand how they work; both of these lists are ordered and the order is signficant to the resulting access control that is applied by NUxeo. Let's start with a typical example of ACL. Suppose the ordered list of ACEs that make up this example are:

  1. GRANT, WRITE, User: Mary

  2. DENY, READ, User: Joe

  3. GRANT, READ, Group: Members

It should be fairly clear the goal of this ACL in English is, "Everybody in the Nuxeo system can read this document except Joe. In addition, Mary can write to it." When evaluating whether Joe can read the document, Nuxeo stops checking when it finds the first rule that applies to the user in question. Thus, starting from 1, Joe will be denied READ access but if the order were reversed - since Joe is a member of the group 'Members' - Joe would be granted read access. Processing of the ACE list would stop as soon as a match occurred, and that would be when the group rule was encountered.

The rules for the list of ACLs that make up an ACP work exactly the same way. Nuxeo is careful to insure that the inherited ACL that is computed from the parents in the heirarchy is placed last in the list of ACLs. Further, the UI shown in the screen shot manipulates an ACL that is typically placed first in the list. This combination yields a list of two ACLs that, semantically, work this way:

  1. Local overrides defined by the UI

  2. Inherited access control from parents in the heirarchy

For most people familiar with the way that access controls work in typical filesystems this will "do what they expect." As you proceed down the tree of documents you usually get more an more specialized access control, as more parents make their contribution to #2. Similarly, with the UI you can express local exceptions to the rules defined by the parent via manipulations of #1. Finally, if you were to do nothing via the UI, #1 would not exist and the access control would be exactly identical to the parent's settings. This is what "most people expect" so Nuxeo uses this as the default although by reading this article you have probably realized it can do much more exotic things!

Suppose that in your application you would need to allow a particular user write access to a document. In this scenario your application can create an ACL from one ACE, specifying the user and GRANT and WRITE, and introduce that ACL at the front of the list of ACLs in the document's ACP. That's it! Since your ACL will evaluated first, your special user will be allowed to write to the document. A more sticky problem is how to revoke that GRANT at some later time. You could introduce another ACL at the front with a single ACE that was DENY, WRITE for your special user. However, this is a bit messy as it leads to all applications adding evermore ACLs to documents. Better would be to simply remove the ACL you added before from the ACP, but how do you know which one is yours when other applications (or an administrator) might have added or changed various ACLs in the interim? Easy! ACLs are named. You can label your ACL with "myApplication" or "dontMessWithTexas" or whatever so that your application can be sure to find it later.

11.5. Seeing access control in action

To make it easier to understand, and give you some sample code to work with, we have constructed a test case that you can find in this lesson's skeleton (lesson-security, in the usual repository that you can access with svn). Here is an slightly edited for space version of that test code:

public class SecurityTest extends RepositoryOSGITestCase {
 
    public void testBasicSecurity() throws Exception {
        deployBundle("org.nuxeo.book.upcoming");
        openRepository();

        // path is /privateShow
        DocumentModel blankDocument = coreSession.createDocumentModel("/",
                "privateShow", "Upcoming");

        blankDocument.setProperty("upcoming", "admissionPrice", new Float(0.00));
        blankDocument.setProperty("upcoming", "occursOn", new Date()); // today
        blankDocument.setProperty("upcoming", "presenter", "Tori Amos");

        // create the document
        DocumentModel privateShow = coreSession.createDocument(blankDocument);

        // now we are going to set the owner of the document...
        // we heard "ismith" wants to see the private show with Tori Amos
        ACP acp = privateShow.getACP();
        acp.setOwners(new String[] { "ismith" });

        // only one ACL, we get it from the parent directory ('/' in this case)
        assertEquals("only one access control list?", 1, acp.getACLs().length);
        ACL acl = acp.getACLs()[0];
        assertEquals("we inherit this from parent?", ACL.INHERITED_ACL,
                acl.getName());

        // four "grants" are present by default?
        assertEquals("four ACEs?", 4, acl.getACEs().length);

        findGrantACE(acl, SecurityConstants.ADMINISTRATORS,
                SecurityConstants.EVERYTHING);
        findGrantACE(acl, SecurityConstants.ADMINISTRATOR,
                SecurityConstants.EVERYTHING);
        findGrantACE(acl, SecurityConstants.MEMBERS, SecurityConstants.READ);
        findGrantACE(acl, SecurityConstants.MEMBERS, SecurityConstants.VERSION);

        // try granting to a new group via our own ACL
        ACL myACL = privateShow.getACP().getOrCreateACL();
        assertEquals("it should be empty now", 0, myACL.size());

        // make the ACE
        ACE myACE = new ACE(SocialDirector.SOCIAL_DIRECTOR,
                SecurityConstants.READ_WRITE, true);
        myACL.add(myACE);

        assertEquals("should be two ACLs now", 2,
                privateShow.getACP().getACLs().length);

        // merging produces one ACL with all the values together
        ACL mergedACL = privateShow.getACP().getMergedACLs("mergedACL");
        assertNotNull("can we get them all put together?", mergedACL);

        assertEquals("updated to include our new entry?", 5, mergedACL.size());
        findGrantACE(mergedACL, SocialDirector.SOCIAL_DIRECTOR,
                SecurityConstants.READ_WRITE);
    }

    private void findGrantACE(ACL acl, String who, String what) {
        for (ACE ace : acl.getACEs()) {
            String username = ace.getUsername();
            String permission = ace.getPermission();

            if (username.equals(who)) {
                if (permission.equals(what)) {
                    assertTrue("is it a grant?", ace.isGranted());
                    return;
                }
            }
        }
        fail("unable to find the access control entry:" + what + "," + who);
    }

A key interface used in the code above is SecurityConstants. This is useful for accessing the two "usual" groups on all Nuxeo instances, MEMBERS and ADMINISTRATORS, as well as all the capabilities that can be granted or denied, such as READ, WRITE, or DELETE.

11.6. For the truly interested (or dangerously curious)

The not very well hidden secret about all the access control features in this lesson is that this expresses only a single "policy" in terms of how the Nuxeo system can behave. In other words, we have said "ACEs are evaluated in order," but who made that a rule? Why can't they be evaluated randomly, or in alphabetical order based on the username? (The latter would be great for users with the username 'adam.') The default policy that we have explained herein as "fact" is simply one that a great many people find acceptable for getting many things done. This policy is built-in but others are possible..

You can read about Security Policy extensions in chapter 14 of the Nuxeo book. In short, there is an extension point exposed by a Nuxeo service, the SecurityService, that allows you to supply a Java class name that is going to implement your new policy. You can even have your new security policy have higher priority than (get evaluated before) the default one supplied by Nuxeo!

Why would you need this? You need to use this somewhat more exotic mechanism when you would like access control to interact more directly with document properties. For example, a contribution one could imagine for the Upcoming document type might be that free events (events whose admissionPrice is 0.00) can be modified by anyone but not deleted. This is a bit more difficult to specify using the strategy we have suggested in exercise 1 below (!), because the document creation handler does not know the usernames or any group to use in an ACE. Further, the user in question may not even exist when the document is created! Along this same line, the price of admission might change at a later time so really this type of check needs to be done inside your own policy contribution.

Use these contributions to the security service, one can implement specific Java interfaces that allow you to perform any type of check you would like about the user accessing the document. Perhaps you would like Upcoming documents to always allow deletion by users whose username begins with a vowel on nights of the full moon? These interfaces allow you perform the any permission check of interest or simply return a special value that means "a typical Nuxeo security check is ok" for this document.

11.7. Granting the social director special powers and other exercises

  1. Add the necessary code to the DocumentCreationListener so that any member of "socialButterflies" group can change the properties or remove documents of type Upcoming. This makes all the work we have done on groups, listeners, and document types come together!

  2. Prove that your code from #1 works by creating two users, Alice and Bob. Using the nuxeo web UI, add Alice to the socialButterflies group, then login as Bob and create a new Upcoming document. After logging out as bob, log back in as Alice and change the event to be a "Beatles Reunion" event presented by Michael Jackson.

  3. Explain why Joe, in section 4.1's first example, will be denied write access.