By Lukas Smith
One of the key challenges in creating a web application is handling access rights to all of the various features that the application provides. Earlier, it was often sufficient to differentiate between guests, users, and administrators. Today, when more people inside a company are involved in administrating a web application, it has become necessary to control the access to different features in the web app. This is even more so the case in the rapidly growing market of intranet applications.Architecture
The idea behind this web application buzz is not to create web-based versions of existing legacy applications that are just as separated from the rest of the IT infrastructure as before. The driving force here is integration. Migrating user data from one project to another is tedious and often not feasible. New innovation is stalled. Infrastructure is reorganized not for efficiency reasons but simply because of the needs of certain applications. Money gets wasted, and projects get delayed. Enter LiveUser.
LiveUser offers a flexible permission system and allows integration of multiple authentication sources at once. However, instead of forcing all of its features onto each and every user, it follows a flexible container approach to make its functionality truly optional and extensible. LiveUser also provides a large set of features out of the box - enough to fulfil most of the most challenging requirements that clients can conjure up. It can be used to authenticate against a wide range of authentication sources like XML, database, LDAP, Radius, IMAP, and POP3. The permission backend supports user rights, user groups, roles, ownership, and many more features to help you organize your rights.
LiveUser itself is part of the PEAR repository and is actively maintained by over half a dozen developers. While the first stable release is only expected in the next few months, it is already being used "in production" on many web sites. Also, LiveUser has now reached the beta phase, which means it's the right time for potential users to embrace it and ensure that no time is wasted on custom solutions that simply reinvent the wheel. This article will introduce you to the architecture of LiveUser, and the 'out of the box ' functionality that LiveUser provides - both for authentication as well as permission.
One of our main architectural decisions, made during the early stages of LiveUser's development, was to separate the authentication and permission backend to enable the integration of several authentication sources in conjunction with LiveUser. This means that, by using LiveUser, every user inside each of the authentication data sources can be mapped to a unique ID inside the permission backend. At the same time, any type of backend can be integrated into LiveUser due to its container approach. Thus LiveUser can easily be extended to handle other data sources or integrate very specific functionality, as long as the given backend implements the provided interface.
We've also made several provisions to ensure the best runtime performance. For one, the code necessary to manage the content inside the data sources has been separated from the code necessary to include during the common runtime operations. Also, the different features in the provided permissions containers have been separated into several complexity levels: simple, medium, complex. Further, great care has been taken to ensure that none of the complexity levels adversely affect the permission of others, while at the same time maintaining the ability to move between them at any stage in the project.
Another part of LiveUser that has gotten a lot of attention is the API. The basic idea was to minimize the training effort behind integrating LiveUser into existing applications. All that is required is a decision on how to structure the rights and rights assignment, following which developers simply need to implement the rights check. It's quite easy to port the existing authentication schemes to LiveUser: existing authentication schemes can simply be wrapped into a container. For example, LiveUser provides an authentication container that wraps PEAR::Auth into LiveUser, thereby making migration as trivial as possible, while also exposing all the backend supported by PEAR::Auth to LiveUser.
Listing 1 shows the basic API calls that you need, to add LiveUser to your code. If the last method call has left you wondering, then read further into this article for more detail.
Listing 1: LiveUser - Basic API Calls // loading LiveUser
// create object
$LU = LiveUser::factory(..);
// user logged in?
// what happend?
// check a right
// check a right based on ownership
$LU->checkRightLevel($right, $user, $group);
Some sample usages of the Admin API are shown in Listing 2. For examples of more detailed usages, please refer to the numerous examples provided as part of the LiveUser package , which can also be found in CVS .
Listing 2: Example Usages of the Admin API // loading LiveUser Admin API
// create object
$admin = new LiveUser_Admin(..);
// add a user
$user_id = $admin->addUser(..);
// call a method in the authentication container
// call a method in the permission container
At this point it is also recommended that you take some time out to browse through the code and familiarize yourself with the architecture. Note that all Common.php files are actually interface definitions for the given containers. Authentication
LiveUser currently ships with five different containers for authentication. Three of them differentiate themselves by the database abstraction layer being used: PEAR::DB , PEAR::MDB , and PEAR::MDB2 . The other two are an XML based container and a wrapper container around PEAR::Auth. Missing functionality can be added by extending an existing container or by writing a new container (by extending the relevant interface file).
Since LiveUser can accept an existing instance of one of the three abstraction layers (mentioned earlier), including three different variants of the database container inside LiveUser can improve performance considerably. It is recommended to use the MDB based containers, for best compatibility with more exotic RDBMS, since MDB offers a more complete set of abstraction features. Another interesting feature of the database container is that both the name of the table and the table columns used are configurable - so, for most existing applications, the database containers can be used without having to modify the existing schema. Permission
The permission part of LiveUser needs a bit more explanation than the authentication section. As mentioned before, LiveUser splits the functionality for the permission part into three levels: simple, medium, and complex. At the time of writing, LiveUser provides an implementation of all of the complexity levels using a database backend, and the implementation of the simple container using an XML backend. The database backend is implemented using the three abstraction layers; PEAR::DB, PEAR::MDB, and PEAR::MDB2. All relevant comments also apply for the permission containers. However, the permission containers only allow setting an optional table prefix, instead of allowing configuration of all of the table and column names.
The implementation of the three different complexity levels serve as a good example of how one can go about expanding an existing container, both in the permission and the authentication part of LiveUser. Each complexity level inherits from the level below it - that is, the medium container inherits its functionality from the simple container, while the complex container inherits its functionally from the medium container.
Next, we will work through each of the containers starting from the simple to the complex container. It would be good to refer to the database schema while you read the forthcoming sections in the article.
Please note that the liveuser_right_scope table is currently not being used in LiveUser. One of its original purposes was to enable another level of organization of rights. For example, if the perm_type in the liveuser_perm_users table was below two, it would be possible to show only the rights for users in an admin GUI. However, this feature hasn't found its way into the code yet, nor is there any current plan to add it into the package. Simple Permissions
As the name implies this container only provides the simplest permission: assigning rights to users. However, since it's at the lowest point in the inheritance structure, it is important to be familiar with some of the basic tables LiveUser needs:
- The most important table is the liveuser_perm_users table. Users from all of the authentication containers are mapped to a single perm_user_id, in this table. Since this ID acts as a unique identifier for a given user, it should be used as a reference point for each user outside of LiveUser. The perm_type field defines the type of the user. For the simple container set the value to four (or higher) if you want the user to be a super admin who automatically has all the rights. The different levels are defined as constants inside the permission interface class file.
- All rights are defined in the liveuser_rights table and are grouped into so called areas using the liveuser_areas table, while areas themselves are organized into applications using the liveuser_applications table. Organizing rights in this manner has several advantages. First, this makes it possible to write a script that exports an array or a set of constants - this means that it is possible to use names for the rights inside application instead of integers representing the right ID. Second, this information can also be used to better structure the rights inside an admin GUI. Finally, it makes an additional feature possible - a sample usage of this is done in the medium container. Users are assigned their rights in the liveuser_userrights table.
- The liveuser_language and liveuser_translations tables are used to add a name and description to applications, areas, groups (which will be introduced in the medium container), languages, and rights. The section_id in the liveuser_translations table matches the ID of the entity (for example, the area_id) that is being described. section_type is an integer that is assigned for all the entity types that can be described in the table.
A set of constants representing the different types can be found inside the interface class for the permissions in the admin API. Medium Permissions
While the simple container's functionality may be sufficient for most Web sites, it becomes very limiting for real Web applications. For this reason, the medium container introduces two key concepts: groups and area admins.
In LiveUser, user groups and roles are actually stored and manipulated the same way. Note that the next major release of LiveUser will allow the two to be separated, while still retaining the facility to mix the two concepts as and when necessary. For this reason, the current liveuser_groups table will be expanded to optionally define a group_type. Users are assigned to a group using the liveuser_groupusers table, and the liveuser_grouprights table is used to assign the rights to a group.
The "area admins" feature leverages the rights organization we gained through the liveuser_area table. If a user has been set to the perm_type area admin (inside the liveuser_perm_users table) using the integer 'three', a look up to the liveuser_area_admin_areas table is done during the process of reading the rights. A user who has been assigned areas in the table is automatically granted all the rights of the given area, thus making it quite easy to grant a user the management rights to a certain section of your site without having to manually assign any newly introduced rights. Complex Permissions
Most Web applications should be well covered with the functionality provided by the medium container. Some need to go a step further, however. The complex container introduces the following new concepts: subgroups, right inheritance, and right levels.
The liveuser_subgroup table can be used to organize groups into infinitely deep structures. Note again that the "group" concept in LiveUser covers both "user groups" and "roles". A similar feature is also available for rights using the has_implied field in the liveuser_rights table, in combination with the liveuser_right_implied table. If the has_implied fields contain Y, LiveUser will do a lookup in the liveuser_right_implied table to determine what 'rights' the given right implies. This can be useful to ensure that all users that have a right to 'delete' a given object also have the right to 'read' on the same object. The "right inheritance" concept ensures that it is not necessary to explicitly assign the right 'read' to the user, if the inheritance is set up as described. (This also means that the user loses the right read immediately when he no longer has the right delete.)
The concept of "right levels" is another powerful LiveUser feature. This concept makes it possible to limit the application of a right to the concept of ownership. For example, it is possible to limit users to 'edit' only what they originally 'added' to the system. Note that storing of the ownership needs to be handled outside of LiveUser. The "right levels" concept also makes it possible to pass LiveUser multiple owners at once and handle both users and/or groups as owners. A sample API call using the ownership concept is shown in the last line of Listing 1.
LiveUser defines three levels, from one to three, the last being the highest level. All higher levels include the lower level. A right granted at 'level one' means that the right might only be applied if the perm_user_id of the authenticated user matches one of the IDs passed as the user owners. Granting a right at 'level two' also allows applying the right if one of the groups, the authenticated user is a member of, is listed in the group owners. With 'level three' the right can be applied without limitation. The level at which a given right is granted is determined by the right_level field in the liveuser_userrights and liveuser_grourights tables. Special admin types like area admin or super admin are always granted a 'level three' right.
Note that LiveUser tries to determine the highest level at which a user gained the right regardless of whether this was gained through direct assignment to the user, or through a group. The only exception to this rule is that the level in the liveuser_userrights table can optionally be a negative value. In case a 'negative level' is assigned, the level is subtracted from the level the user might have gained through membership in a group. Only if the resulting level is above zero will the user have the right at the level determined by the subtraction. Therefore, if a user has a right at 'level minus two', and she is a member in a group that has the same right at 'level three', she would actually end up having the right at 'level one'. This feature is probably not something that is used often, but it can prevent having to create a new role just to disable a specific right that a user gains through a role. Different Names, Same Concept
One of the challenges in integrating a system like LiveUser, based on customer requirements, is actually being able to map the terms used by the customer to the concepts and terms used in LiveUser. For example, in LiveUser, the word "permission" is used instead of "authorization" - this was done since authorization is often confused with authentication, two distinctly different things. Also, note that the word role doesn't appear in the current releases because roles are actually implemented using groups (in LiveUser). With the explanations in this article, you will be able to map the concepts properly.
One concept that you may often be faced with is RBAC. Essentially, RBAC provides the following concepts in order to handle rights management: users, roles, permissions, objects, and operations. For example, one question that you might want to ask in an RBAC system could go like this: Does a given user have a given role which grants him a given permission to execute a given operation on a given object. At first look it doesn't seem like LiveUser can actually be seen as an RBAC implementation. However, if you go through them step by step, you can see the corresponding concepts in LiveUser.
LiveUser does have a concept of users and roles. A right that has been granted to a user is simply a permission. The next two concepts might not be so obvious - essentially, in RBAC, finding the right to check for, is composed of two pieces of information - the object and the operation. An object is just a system resource that is subject to some sort of access control. Therefore we just need a way of determining the access control, which will be checked before an operation is allowed to be executed. For this same reason, an operation is just a right. Being able to determine the right to check for, when accessing an object, can either be done by having a clear mapping between the different available types of objects and a given area, or by storing the right along with the actual object.
There is a thread on SitePoint that serves as a good entry point to those who are unaware of RBAC. The Road Ahead
As stated in the introduction, a first stable release is expected sometime in the near future. The numbers of bugs found in recent months have been very low. However, there are a number of features that need to be worked on until then. Most of the work is being done on the API to make it easier to use, and more flexible and intuitive.
Some of the features being considered are the ability to optionally separate groups into user groups and roles, removing the dependency on PHP sessions, and the ability to authenticate against other data sources like NTLM and Kerberos. Another feature currently being discussed is being able to set and require a certain authentication quality. While weak authentication may be sufficient for certain parts of an application, others may require particularly strong authentication. It should be possible for the authentication to request a certain quality of authentication. If the user has already been authenticated using a lower quality, LiveUser should force the users to authenticate against a higher quality authentication source before being allowed to continue. The relevant quality level can be configured for each authentication data source. For example, authenticating against one database may result in a quality 'level of two' and against another it would result in a 'level of four'.
Another important improvement has been planned in the area of rights caching. LiveUser tries to optimize runtime performance by caching the rights a given user has. Both the complex container and the medium container (to a certain extent) require that a number of operations be done to determine the rights of a given user. At present, however, LiveUser only provides the possibility to cache the rights inside the user session. The structural limitations inside of LiveUser, that previously made this a requirement, have already been removed. So it's likely that LiveUser will soon gain the ability to cache outside of the session. This will also mean that user's changes in the user permissions will affect active user sessions, which currently is not the case.
Another area that will be explored in more depth in the coming months is the concept of Single Sign-on (SSO). Actually all that probably needs to be done is to provide a few sample containers implementing different variants of Single Sign-on - the infrastructure provided by LiveUser seems to be quite sufficient. Conclusion
LiveUser is a very flexible framework that can be used to handle authentication and permissions. It is meant to scale with the needs of the application. Easy integration into existing IT infrastructure, which is often full of legacy applications, is also one of the highlights of LiveUser. Due to the clear separation of the authentication and permission parts and the container approach of LiveUser, it may not be the fastest possible implementation of the provided feature set. However, a lot of work has already been done to make it sufficiently fast for most applications.
One of the areas where LiveUser currently lacks is documentation. This article is actually the most comprehensive introduction to LiveUser available anywhere at this moment. The LiveUser development team is however working to remedy this situation with the help of an increasingly large user base, and a Wiki has been created to ease collaboration. Once the first stable release rolls out, LiveUser will (hopefully) be fully documented and ready to be used in a great variety of applications. &lt;i&gt;About the Author&lt;/i&gt;
Lukas Smith is a member of the PEAR Group and lead developer of MDB, MDB2, and LiveUser. He is also one of the co-founders of BackendMedia, specializing in Web application development and Internet related services. Links and Literature
||Bugs are very low because there isn\'t user documentation! >:-(
||Looks as if someone's posted a bunch of porn and drug ads on your site.