Table of Contents
Objective: To learn how Nuxeo handles managing user accounts and groups. To gain a further understanding of utilizing the system "services" provided by Nuxeo EP.
The software isn't finished until the last user is dead. --Anonymous
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.
The role of "social director" is an informal one in most organizations; the social director is the person who has the energy and interest to devise, plan, and usually coordinate social events for a group. An example event might be, the company's annual Holiday party. Since the social director is heavily involved in most upcoming activities, it seems fair that this role have a special status with respect to our Upcoming documents. Using Eclipse and Maven as before and with this lesson's skeleton, found in the usual place on the svn server http://svn.nuxeo.org/nuxeo/sandbox/iansmith/book/lesson-users, create a Java class called SocialDirector in the package org.nuxeo.upcoming:
package org.nuxeo.upcoming;
import java.util.ArrayList;
import java.util.List;
import org.nuxeo.ecm.core.api.DocumentModel;
import org.nuxeo.ecm.platform.usermanager.UserManager;
import org.nuxeo.runtime.api.Framework;
public class SocialDirector {
public static final String SOCIAL_DIRECTOR = "socialButterflies";
public boolean groupAlreadyExists() throws Exception{
UserManager mgr = getUserManager();
DocumentModel model = mgr.getGroupModel(SOCIAL_DIRECTOR);
return model!=null;
}
public UserManager getUserManager() throws Exception {
return Framework.getService(UserManager.class);
}
public void createGroup() throws Exception {
UserManager mgr = getUserManager();
//get the basic version of a group
DocumentModel model = mgr.getBareGroupModel();
//set some properties as if there is a document schema called "group"
model.setProperty("group", "groupname", SOCIAL_DIRECTOR);
model.setProperty("group", "description", "people who coordinate social events");
model.setProperty("group", "members", new ArrayList<String>());
//this property is a list of group members, but we want to start empty
List<String> parentGroups = new ArrayList<String>();
parentGroups.add("members");
model.setProperty("group", "parentGroups", parentGroups);
//use model to actually create the group
mgr.createGroup(model);
}
}
As you can see from the text above, the SocialDirector class has
two main methods, groupAlreadyExists
and createGroup. As one would expect,
these check to see if a group exists called "socialButterflies" and
create that same group, respectively. Both of these methods use the
UserManager to do their work, and the
UserManager will be discussed in an upcoming section.
The design we are using in this lesson is to create a group denoted by the constant SOCIAL_DIRECTOR that we can add users into. This group, by the time the next few lessons are finished, will have some special rights with respect to Upcoming documents. In this sense, the "group" is really "role" but Nuxeo does not have a strong distinction between these two designations, so we will continue to use them interchangably. An example of a special privilege for those in the group SOCIAL_DIRECTOR is that they can, perhaps, modify the properties of an upcoming schema an any document in the system.
If you have forgotten how to manipulate users and groups, you may want to revisit section 3.3 or just boot up the Nuxeo server and click the "User Management" link that appears at the top of each page if you are logged in as Administrator.
As you saw in the previous lesson's tests and are now seeing in
this lesson's implementation, services are a key ingredient to
programming with Nuxeo. There are a multitude of services that are
available: so far we have used the EventService, EventProducer, and
now the UserService. The complete set of services visible to your
program varies somewhat based on which OSGi bundles you have
installed/deployed into your Nuxeo system. If you have not deployed
the bundle LeftHandedMonkeyWrench, then it is likely that the call
Framework.getService(LeftHandedMonkeyWrench.class)
will fail - by returning null - at run-time. It is important that
you think about the classes you have visible to you in Eclipse at
compile time - such as LeftHandedMonkeyWrench - and their
relationship to deployed bundle. As a general rule, if your code
fails at run-time to find a Service you expected to be present, it
is likely that your MANIFEST.MF is
missing a dependency. Why? The fact that Nuxeo's Framework could not find a service means that a
bundle was not present you needed, but this should never happen if
you have written the appropriate Nuxeo-Require lines in your manifest! The
case that, unfortunately, causes this situation somewhat often is
when you forgot the Nuxeo-Require line in your manifest, but got
"lucky" (unlucky?) and your code worked on a particular Nuxeo
installation because that bundle was already deployed, maybe be
someone else's code. Since test code tends to run in a version of
Nuxeo that has few bundles deployed (to speed test execution) it is
a good idea to see what bundles you need to run your test code -
denoted by deployBundle() in the
JUnit test case - and make sure that your manifest declares at
least those bundles as required.
Returning to the "big picture" theme that was discussed earlier,
the Nuxeo UI and default server functionality have "no magic." In
other words, they are implemented the same way that your code is;
even the Nuxeo server developers have to be concerned about which
services they use and which bundles are deployed! Going the other
direction, in a future lesson you will develop your own service and
allow it to be found and utilized by components outside your own.
This process, naturally, would mean that other users could write a
dependency on your bundle (!), then access your service with
Framework.getService(YourCode.class).
The only part of the Nuxeo system that is implemented "with magic"
is the Nuxeo runtime system since it is used to provide the
OSGi-like infrastructure that both your code and the Nuxeo server
depend on. Nuxeo is just software, it cannot be "turtles all the
way down" (with apologies to Bertrand Russel or someone else
http://en.wikipedia.org/wiki/Turtles_all_the_way_down).
To motivate the use of the UserManager service we are going to partially explain how users and groups work in Nuxeo. This explanation is not really intended to be exactly accurate, but rather to give you a flavor of what complexity the UserManager is hiding from you!
Directories are a Nuxeo 5 concept that, sadly, is not particularly related to the more common notion of a "directory" in a filesystem although both contain structured content. In Nuxeo, a directory is a collection records that describe a user or a group. The particular fields that describe the directory can vary quite dramatically between Nuxeo installations. Some nuxeo installations will want to be "stand alone" and simply have someone who maintains a text file. Other installations may want to piggyback on their existing user account configuration, say for their database like Postgres or MySQL. Finally, you may have organizations with highly-developed user management regimes that use Open LDAP or microsoft domains (which are also based on LDAP). In all of these cases, the data kept about a user will vary quite a bit although all are likely to include a user name of some kind - some Microsoft installations use Last Name, First Name rather than a shortened form as most other systems do - and some way of authenticating the user. Some organizations maintain building maps such that a user account includes an office name or perhaps a phone number while other user directories allow free form text in some fields so the location of a user may be "out to lunch."
Groups are even more problematic because these are often maintained by administrators of other systems - databases or LDAP servers - that are loathe to change just because a new Nuxeo server has been installed. Again, a nuxeo server must be able to handle groups defined again in text files, in databases (with varying configurations in terms of security, database design, table design, etc), or in LDAP services that are difficult to change. There may be an arbitrary number of both groups and users, and in some situations, Nuxeo must even take the "users database" from two different sources and merge these together to form the set of valid Nuxeo users! This capability is particularly critical in situations where the administration of the Nuxeo server must be done by someone who is not listed as an "Administrator" from the standpoint of the organization that maintains the "normal" set of user accounts.
The Nuxeo directory mechanism is used in a number of situations in the system in addition to managing Users and Groups. For more information on this powerful tool, please see chapter 18 of the Nuxeo Book.
We hope that the "scary" description in the previous subsection
convinced you that there is a need for a higher-level API for
manipulating Users and Groups. The User Manager provides access to
functionality with calls such as areUsersReadOnly(), getUsersInGroup(String), and checkUsernamePassword(String, String), the last
of which allows you validate a users credentials. If you have an
instance of the type UserManager in a Java file and type
control-space after typing the dot, you can see all the methods the
UserManager exposes, as is shown here:

As we explained in the previous section about directories, there are many possible fields that might be available to you with respect to a particular User or Group. To ease this burden, the UserManager presents this variability in a form you have already seen - using a Schema. Since schemas may have arbitrarily complex structures, it is convenient to re-use this notion when describing users or groups. Although it may seem strange to describe a user with a document, this is the way it is exposed via the UserManager.
The methods getBareUser and
getBareGroup will hand you back a
DocumentModel that represents an simple
abstraction of a schema for users or groups. This version prevents
you needing to walk through all the fields exposed by the
particular installation in favor of a small set of well-understood
properties. You can fill in the fields just as you did when you
created a new document in the last lesson's tests, and then ask the
UserManager to create the User or Group
that you have defined (as we did in the listing above).
In the case of the "bare" version of users that you can use if you want to do simple operations, here is the XML schema definition:
<?xml version="1.0"?> <xs:schema targetNamespace="http://www.nuxeo.org/ecm/schemas/user" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:nxs="http://www.nuxeo.org/ecm/schemas/user"> <xs:include schemaLocation="base.xsd" /> <xs:element name="username" type="xs:string" /> <xs:element name="password" type="xs:string" /> <xs:element name="firstName" type="xs:string" /> <xs:element name="lastName" type="xs:string" /> <xs:element name="company" type="xs:string" /> <xs:element name="email" type="xs:string" /> <!-- inverse reference --> <xs:element name="groups" type="nxs:stringList" /> </xs:schema>
there is a similar simplification for the schema that describes a group:
<?xml version="1.0"?> <xs:schema targetNamespace="http://www.nuxeo.org/ecm/schemas/group" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:nxs="http://www.nuxeo.org/ecm/schemas/group"> <xs:include schemaLocation="base.xsd" /> <xs:element name="groupname" type="xs:string" /> <xs:element name="description" type="xs:string" /> <!-- references --> <xs:element name="members" type="nxs:stringList" /> <xs:element name="subGroups" type="nxs:stringList" /> <!-- inverse reference --> <xs:element name="parentGroups" type="nxs:stringList" /> </xs:schema>
Both of the schemas above use a type that is defined by Nuxeo,
the nxs:stringlist which is (suprise!) a
list of strings. In the case of the members field of the group schema, the list of strings represents a list
of usernames of the members of that group.
If you want to manipulate all the fields of a user -- perhaps to
change their room assignment in organizations that have this field
-- you will need to use the API of the DocumentModel returned from the UserManager's getUserModel function. With this you can inquire
about all the fields available, but it is up to you to interpret
their semantics.
Testing the SocialDirector is a straightforward process. In the next listing, we have provided you with all the code necessary to run the test. However, some of the comments are labelled "SKETCH" and these suggest how to construct the test yourself. If we provided you with all the details of that part of the test, what we would do about exercise number 1?
package org.nuxeo.upcoming.test;
import org.nuxeo.ecm.core.repository.jcr.testing.RepositoryOSGITestCase;
import org.nuxeo.upcoming.SocialDirector;
public class EventTest ... {
...
// this test assures us that the side effect of first document creation
// is to use the social director to create a group and that the second
// has no effect
public void testSocialDirector() throws Exception {
// SKETCH: Setup test infrastructure
// SKETCH: Create SocialDirector object
// SKETCH: Verify that the group is not present at start
// SKETCH: Create a document
// SKETCH: Verify the group is now there
// SKETCH: Create another document or two and verify the group doesn't disappear
}
In the previous lesson, we have insured that our document type, Upcoming, was created before any pesky users could actually manipulate the Nuxeo server and create a document. We would dearly love to use this same mechanism to insure that if our bundle is installed, the group "socialButterflies" is present on the server. Oh, if only life were that simple.
Without boring the reader with a great many details about what
the problems exactly, we will simply say that calling the Social
Director during the initialization activities of the event handler
(during the activation() function)
can introduce some nasty run-time dependencies. These can occur if
the UserManager, or the directories that underly it in SQL
databases in our example, are fully ready to operate when we begin
accessing them. There are mechanisms provided by Nuxeo - and used
by Nuxeo itself - to solve this type of dependency when you really
need to be sure that everything is initialized before you begin
execution. However, it would overcomplicate our simple example.
Simplicity, sometimes, is its own reward. Thus, we are going to implement the creation of the group, via the SocialDirector class, when the first document gets created of document type Upcoming. This is somewhat poor in that when you first boot up the system you will have create a document before the "special" group will be created. So, were an administrator try to add users to the "socialButterfly" group immediately, he or she might be surprised to find that the group doesn't exist yet. Put another way, we are using the fact that documents cannot be created until everything is initialized properly as "poor man's event" to indicate that the UserManager is fully ready.
Here is the snippet of code we would like to run after the creation of the first Upcoming document. This is the code that "does the work" although as we just explained it can be a bit tricky to decide when this should be executed! We have supplied you with tests that test the "document creation trick" we detailed functions as advertised.
// make sure we have a social director group
try {
SocialDirector director = new SocialDirector();
if (!director.groupAlreadyExists()) {
director.createGroup();
}
} catch (Exception e) {
throw new ClientException(e);
}
The careful reader will notice that this now introduces a
dependency on SocialDirector from the
event handler object and transitively a dependency on the
UserManager. (Did you catch that?) . The
reader, careful or not, will notice that we have included a new
base class that you should use for setting up the test case,
UserManagerTestBase. This base class
understands the details the dependencies of the UserManager. It calls the deployBundle function that is available to Nuxeo
tests, plus a few others, to make sure the necessary OSGi
dependencies are available. This new class has been included
because the UserManager cannot function without something that
describes the particular directory (in the sense of section 4.1)
structure being used. We have supplied you with a some sample
configurations that will allow the tests to run with the user
manager. In effect, we must define a simple "world" for users and
groups so the UserManager can provide access to it. These files can
be found src/test/resources/testconf
and may be of interest to readers that are interested in how to
construct more complex test setups.
At this point, if you go through the usual deployment process, you should be able create a document as the Administrator and then see your class in the list of groups that users can be added to. The "Manage Users" link is highlighted in the screen shot below; click that link to see the page shown in the darker part of the image where you can add users to the socialButterflies group.

Complete the test code to check that the SocialDirector implementation works properly.
If you have a copy of the Nuxeo source or can download it,
examine the directory src/main/resources in the package nuxeo-platform-directory-sql. This is the source
code of the bundle org.nuxeo.ecm.directory.sql referred to by the
file default-sql-directories-bundle.xml that is in
your server's config subdirectory
below the nuxeo.ear deployment. There
are a number of files in that directory show how Nuxeo's default
groups get created. Figure out how to change the default
Administrator password and deploy that change.
Refactor the code that accesses the social director so that it is not duplicated in the implementation of InitStructuresNeededForUpcoming.
Can you use an xml file with a contribution to an extension point to create a listener for the Framework.STARTED event? Why or why not?