Usermanagement

The usermanagement modul contains a backend for user administration and a generic business component - based on the GenericORMapper - that can be used for authentication purposes and to manage the users, groups, roles, permissions and permission sets. The built-in module is aimed to standardize user management. On the other hand, it is intended to be a reliable and reusable tool for this central issue, that eases daily work.

The following chapters describe the design of the module and present examples for usage of the business component and the Inclusion of the backend into existing applications.


1. Design of the module

The module consists of a business component, the UmgtManager, and a management backend. The manager uses the GenericORMapper as the data layer component. The backend is completely based on the business layer of the module. This means, that other applications or modules can use the UmgtManager as a fully qualified business component for authentication and user management purposes.


1.1. Datamodel

The data model contains fife different objects:
  • User
  • Grouos
  • Roles
  • Permission sets
  • Permissions
The separation in these five types makes a granular and generic usermanagement possible. The following UML diagram shows the objects mentioned and the relations between them:

Usermanagement UML Diagramm (APF)

Note: Groups are typically used to represent permissions on objects, roles are aimed to control permissions on functions. To be able to reuse permission definitions within different roles, the permission set was introduced. This container encapsulated one or more permissions, that can thus be assignet to a role.


1.2. UmgtManager

The business component features a set of methods, that provide abstract data access to the data model presented above. The list does not only contain CRUD functionality but also functions to create, read, update and delete relations between the objects. Futher, functions for user authentication and effective usage of the usermanagement have been implemented.

The following list gives you a overview of the API methods available. Details can be taken from the API documentation of the UmgtManager class:
  • loadUserByEMailAndPassword(): Loads a user object by email and password.
  • loadUserByUsernameAndPassword(): Loads a user object by username and password.
  • loadUserPermissions(): Loads the list of permissions a user has got.
  • saveUser($user): Saves a user object within the current application.
  • saveGroup($group): Saves a group object within the current application.
  • saveRole($role): Saves a role object within the current application.
  • savePermissionSet($permissionSet): Saves a permission set object within the current application.
  • savePermission($permission): Saves a permission object within the current application.
  • getPagedUserList(): Returns a list of users concerning the current page.
  • getPagedGroupList(): Returns a list of groups concerning the current page.
  • getPagedRoleList(): Returns a list of roles concerning the current page.
  • getPagedPermissionSetList(): Returns a list of permission sets concerning the current page.
  • getPagedPermissionList(): Returns a list of permissions concerning the current page.
  • loadUserByID($userID): Returns a user domain object.
  • loadGroupByID($groupID): Returns a group domain object.
  • loadRoleByID($roleID): Returns a role domain object.
  • loadPermissionSetByID($permissionSetID): Loads a permission set by it's id.
  • loadPermissionList(): Loads a list of permissions of the current application.
  • loadPermissionByID($permID): Loads a permission by it's id.
  • loadRolesNotWithPermissionSet($permissionSet): Loads a list of roles, that are not associated with the permission set.
  • loadRolesWithPermissionSet($permissionSet): Loads a list of roles, that are associated with the permission set.
  • assignPermissionSet2Roles($permissionSet,$roles): Associates a given permission set to a list of roles.
  • detachPermissionSetFromRoles($permissionSet,$roles): Removes a given permission set from a list of roles.
  • deleteUser($user): Deletes a user.
  • deleteGroup($group): Deletes a group.
  • deleteRole($role): Deletes a role.
  • deletePermissionSet($permissionSet): Deletes a PermissionSet.
  • deletePermission($permission): Deletes a Permission.
  • assignUser2Groups($user,$groups): Associates a user with a list of groups.
  • assignUsers2Group($users,$group): Associates users with a group.
  • assignRole2Users($role,$users): Associates a role with a list of users.
  • loadGroupsWithUser(&$user): Loads all groups, that are assigned to a given user.
  • loadGroupsNotWithUser(&$user): Loads all groups, that are not assigned to a given user.
  • loadUsersWithGroup(&$group): Loads all users, that are assigned to a given group.
  • loadUsersNotWithGroup(&$group): Loads all users, that are not assigned to a given group.
  • loadRolesWithUser(&$user): Loads all roles, that are assigned to a given user.
  • loadRolesNotWithUser(&$user): Loads all roles, that are not assigned to a given user.
  • loadUsersWithRole(&$role): Loads a list of users, that have a certail role.
  • loadUsersNotWithRole(&$role): Loads a list of users, that don't have the given role.
  • loadPermissionsOfPermissionSet(&$permissionSet): Loads the permissions associated with a permission set.
  • detachUserFromRole($user,$role): Detaches a user from a role.
  • detachUsersFromRole($users,$role): Detaches users from a role.
  • detachUserFromGroup($user,$group): Removes a user from the given groups.
  • detachUserFromGroups($user,$groups): Removes a user from the given groups.
  • detachUsersFromGroup($users,$group): Removes users from a given group.
  • loadUserByFirstName($firstName): Loads a user by it's first name.
  • loadUserByLastName($lastName): Loads a user by it's last name.
  • loadUserByEMail($email): Loads a user by it's email.
  • loadUserByFirstNameAndLastName($firstName,$lastName): Loads a user by it's first and last name.
  • loadUserByUserName($username): Loads a user by it's user name.

2. Installation

The installation contains three major steps:
  • Configuration of the module itself
  • Configuration of the GenericORMappers
  • Installation of the database

2.1. Configuration of the module

The configuration file of the usermanagement module covers three params: the key of the database connection, the id of the application container and the service mode, that is used to create the OR mapper instance:
APF-Konfiguration
[InstanceName] ConnectionKey = "" ApplicationID = "" ServiceMode = ""
The adventure-configpack-* package contains an example configuration file with a detailed description of the params.

Due to the fact, that the data object model features an Application object, the usermanagement can be used for several applications at the same time and the same database. This again enables you to create a common user database for different applications or modules and reuse users within them. When using the UmgtManager, the ApplicationID must be defined, that the component knows, which application instance should be used.


2.2. Configuration of the O/R mapper

Because of the fact, that the module is completely based on the GenericORMapper a set of configuration files must be provided for the UML above. To ease setup, the adventure-configpack-* package contains the configuration files, that are necessary to setup the OR mapper. The files located under /modules/usermanagement/ must therefor be copied into the correct config namespace. Please be aware, that the configuration files must be located within the correct context folder. Details can be taken from the configuration chapter.

As you can read about in the GenericORMapper documentation, a pair of configuration files is used to define the objects and relations available within one onstance of the mapper.


2.3. Installation of the database

As a last step, the database must be prepared. For this reason, the /modules/usermanagement/data/scripts/setup.sql script must be executed against the desired database. After that, the module is ready for use.

It is recommended to use a seperate database for the module. Basically, there is no restriction installing the tables within an existing database. Because of the usage of the ConnectionManager, within one application multiple databases can be addressed.


3. Usage

3.1. Backend

In order to use the integrated backend, a seperate bootstrap file must be created or the application can be integrated within another using taglibs.Thanks to the generic implementation, the url creation recognizes external params (e.g. for navigation purposes).

Because the backend is completely based on the front controller (e.g. displaying of the icons is realized by the <*:mediastream /> tags), the index.php must contain the following code:
PHP-Code
include('./apps/core/pagecontroller/pagecontroller.php'); import('core::frontcontroller','Frontcontroller'); $fC = &Singleton::getInstance('Frontcontroller'); $fC->set('Context',{CONTEXT}); $fC->start('modules::usermanagement::pres::templates','main');
If the backend should be included as a view, the following tag definition can be used:
APF-Template
<core:importdesign namespace="modules::usermanagement::pres::templates" template="main" />
In case, the application the usermanagement is integrated in is based on a front controller action that is used for navigation, the module must be included using the <generic:importdesign /> tag. Please note, that the navigation action class must then be defined with $__KeepInURL = true to enable the Link creation method to include these params. If not, navigation probably is not working.
APF-Template
<core::addtaglib namespace="tools::html::taglib" prefix="generic" class="importdesign" /> <generic:importdesign modelnamespace="namespace::to::application::model" modelfile="ModelName" modelclass="ModelName" modelmode="SINGLETON" namespaceparam="..." templateparam="..." dependentactionnamespace="namespace::to::dependent::action" dependentactionname="DependentActionName" dependentactionparams="param1:value1|param2:value2" />
Details on the tag can be taken from the special taglibs chapter.

As mentioned above, the image and icon delivery of the module is done by <*:mediastream /> tags. These tags generate an image source url, that contains a front controller action directive. As described in the front controller chapter, every action must be defined within a configuration file. To enable the image display, please create the configuration file
Code
{ENVIRONMENT}_actionconfig.ini
within the namespace
Code
/config/tools/media/actions/{CONTEXT}/
The content of the file is as follows:
APF-Konfiguration
[streamMedia] FC.ActionNamespace = "tools::media::actions" FC.ActionFile = "StreamMediaAction" FC.ActionClass = "StreamMediaAction" FC.InputFile = "StreamMediaInput" FC.InputClass = "StreamMediaInput" FC.InputParams = ""
Details on the naming of configuration files can be taken from the corresponding configuration chapter.


3.2. Business component

The API of the UmgtManager class basically works with objects. This means, that arguments applied to the class' methods must be instances of the GenericDomainObject class. Return values are also instances or lists of GenericDomainObject objects or null.


3.2.1. Login via username and password
The following example shows, how easily authentication can be done by URL values:
PHP-Code
// include business component prior to use import('modules::usermanagement::biz','umgtManager'); // get the business object $uM = &$this->__getAndInitServiceObject('modules::usermanagement::biz','umgtManager','Default'); // retrieve the username and password from the request $username = RequestHandler::getValue('user'); $password = RequestHandler::getValue('pass'); // try to get the user object. if null ist returned, the credentials are not correct $user = $uM->loadUserByUsernameAndPassword($username,$password); if($user !== null){ echo 'user "'.$user->getProperty('DisplayName').'" is logged in'; } else{ echo 'user could not be logged in with the given credentials'; }

3.2.2. Login via email and password
In order to authenticate by email and password, the
PHP-Code
$user = $uM->loadUserByUsernameAndPassword($username,$password);
call must be replaced by
PHP-Code
$user = $uM->loadUserByEMailAndPassword($email,$password);

3.2.3. Loading of groups and roles
If a user was authenticated succesfully by the methods described in 3.2.1 and 3.2.2, the user's groups and roes can be loaded as follows:
PHP-Code
// load groups $groups = $uM->loadGroupsWithUser($user); // load roles $roles = $uM->loadRolesWithUser($user);

3.2.4. Loading of user permissions
If the developer needs to access the user's function permissions, these can be accesed via the associated roles or the API method intended for this task:
PHP-Code
// load permissions via the relevant relations $roles = $uM->loadRolesWithUser($user); $permissions = array(); for($i = 0; $i < count($roles); $i++){ // load permission sets $permSets = $roles[$i]->loadRelatedObjects('Role2PermissionSet'); for($j = 0; $j < count($permSets); $j++){ $permissions = array_merge($permissions,$permSets[$j]->loadRelatedObjects('PermissionSet2Permission')); } } // call the api method $permissions = $uM->loadUserPermissions($user);
The disatvantage of the first variant is, that permissions may apear twice ore more with in the list of permissions, because one or more roles can have the same permissions associated.


3.2.5. Manipulation of objects
For the manipulation of objects a couple of methods are included in the API. The following code box depicts, how user, groups and roles can be created, updated and deleted. Because of the fact, that all business objects are instances of the GenericDomainObject class, the developer must take care, that the name of the desired object is set correctly dusing instanciation. The name must be equal to the object's name within the configuration. The same is true for the attributes. Details can be taken from the object definition chapter on the OR mapper documantation page.
PHP-Code
// get the business object $uM = &$this->__getAndInitServiceObject('modules::usermanagement::biz','umgtManager','Default'); // create new user $user = new GenericDomainObject('User'); $user->setProperty('FirstName','John'); $user->setProperty('LastName','Doe'); $user->setProperty('Username','jdoe'); $uM->saveUser($user); // create new group $group = new GenericDomainObject('Group'); $group->setProperty('DisplayName','Users'); $uM->saveGroup($group); // load a user and change username $user = $uM->loadUserByID(1); $user->setProperty('Username','johndoe'); $uM->saveUser($user); // load a group and change display name $group = $uM->loadGroupByID(2); $group->setProperty('DisplayName','Forum users'); $uM->saveGroup($group); // delete a user $users = $uM->getPagedUserList(); for($i = 0; $i < count($users); $i++){ if($users[$i]->getProperty('Username') === 'johndoe'){ $user = $users[$i]; } } $uM->deleteUser($user); // delete a group $groups = $uM->getPagedGroupList(); for($i = 0; $i < count($groups); $i++){ if($groups[$i]->getProperty('DisplayName') === 'Forum users'){ $group = $groups[$i]; } } $uM->deleteGroup($group);

3.2.6. Manipulation of relations
In order to create, modify and delete relations between the objects managed by the OR mapper (see UML for details), the developer is provided several functions. These are focused on the dedicated application use case and expect instances of the GenericDomainObject as arguments. The follwing code boy describes several use cases:
PHP-Code
// get the business object $uM = &$this->__getAndInitServiceObject('modules::usermanagement::biz','umgtManager','Default'); // add user to group $user = $uM->loadUserByID(1); $group = $uM->loadGroupByID(2); $uM->assignUser2Groups($user,array($group)); // assign a dedicated role to a user $role = $uM->loadRoleByID(3); $uM->assignRole2Users($role,array($user)); // remove user from group $uM->detachUserFromGroups($user,array($group)); // detatch role from user $uM->detachUserFromRole($user,$role);

3.2. Direct access

If the API of the UmgtManager does not support a special case, the GenericORMapper can be used to directly access the database. Please be aware to use the same configuration files, you took for the usermanagement setup to ensure data consistency. The next code example shows you how to create the OR mapper instance:
PHP-Code
$oRMFact = &$this->__getServiceObject( 'modules::genericormapper::data', 'GenericORMapperFactory' ); $oRM = &$oRMFact->getGenericORMapper( 'modules::usermanagement', 'umgt', $connectionKey, $serviceMode );
The params $connectionKey and $serviceMode must be filled with the values of the configuration file of the usermanagement module. The ServiceMode directive is optional.

After creating the mapper it can be used concerning the configuration files. The following source code example counts the users within on group. The $oRM contains a mapper instance as defined above.
PHP-Code
$group = $oRM->loadObjectByID('Group',1); $count = $oRM->loadRelationMultiplicity($group,'Group2User');
If you want to test, if a user is included in a dedicated group or if it has associated a dedicated role, the following code can be used:
PHP-Code
// test, if user is within group $group = $oRM->loadObjectByID('Group',1); $user = $oRM->loadObjectByID('User',2); if($oRM->isAssociated('Group2User',$group,$user)){ echo 'user is in group'; } else{ echo 'user is *not* in group'; } // test, if user is associated with the desired role $role = $oRM->loadObjectByID('Role',3); if($oRM->isAssociated('Role2User',$role,$user)){ echo 'user is associated the desired role'; } else{ echo 'user is *not* associated the desired role'; }
In order to freely define the selection criterions, the GenericORMapper features the methods
  • loadObjectByStatement()
  • loadObjectByTextStatement()
  • loadObjectByCriterion()
  • loadObjectListByStatement()
  • loadObjectListByTextStatement()
  • loadObjectListByCriterion()
These functions expect sql statements or a GenericCriterionObject. To load all permissions associated with one role, you can use this code snippet:
PHP-Code
// define statement $select = 'SELECT `ent_permission`.* FROM `ent_permission` INNER JOIN ass_permissionset2permission ON ent_permission.PermissionID = ass_permissionset2permission.PermissionID INNER JOIN ent_permissionset ON ass_permissionset2permission.PermissionSetID = ent_permissionset.PermissionSetID INNER JOIN ass_role2permissionset ON ent_permissionset.PermissionSetID = ass_role2permissionset.PermissionSetID INNER JOIN ent_role ON ass_role2permissionset.RoleID = ent_role.RoleID WHERE ent_role.RoleID = '1' GROUP BY ent_permission.PermissionID;'; // load permission list $permissions = $oRM->loadObjectListByTextStatement('Permission',$select);
To get a list of available application containers, the following code can be used:
PHP-Code
$apps = $oRM->loadObjectListByCriterion('Application');
Details on the objects, attributes and relations of the module can be taken from the configuration file or the UML diagram. The usage of the OR mapper can be read about in the GenericORMapper chapter.


4. Extension

4.1. Password encryption

4.1.1. Enhancement-of-the-manager
This section describes the implementation up to version 1.10. This is true for greater versions, but there, the prefered method is to implement a custom password hash provider as describes in the next chapter.

The business component uses the MD5 algorithm to create a password hash. If you intend to change this behaviour (e.g. to SHA1), this goal can be achieved by extending the umgtManager class and overwrite the __createPasswordHash() method. Please be aware, that the new class does not overwrite the init() function.

The new class can be used exactly as the old one. The following example shows, how you can change the hashing algo:
PHP-Code
class MyUmgtManager extends umgtManager { function __createPasswordHash($password){ return sha1($password); } } $umgt = &$this->__getAndInitServiceObject('my::namespace','MyUmgtManager','Default'); $user = $umgt->loadUserByID(5);
4.1.2. Implementation of the PasswordHashProvider
Since version Version 1.11, you can define your own PasswordHashProvider within the configuration file of the usermanagement module. This component is responsible for creating a password hash for the umgtManager. In default case, md5 is used so far.

In order to provide a custom password hash component, a new PHP class must be created implementing the PasswordHashProvider interface. It defines a method, that calculates a hash key out of a given clear text password. Going along with the above example, the class is as follows:
PHP-Code
import('modules::usermanagement::biz','PasswordHashProvider'); class Sha1PasswordHashProvider implements PasswordHashProvider { public function createPasswordHash($password){ return sha1($password); } }
To activate this provider, the configuration must be added the two directies, that specify the namespace and (class) name of the component:
APF-Template
PasswordHashProvider.Namespace = "my::namespace" PasswordHashProvider.Class = "Sha1PasswordHashProvider"
Complete usermanagement sample configuration files can be found in the apf-configpack-* release packages.


4.2. Display name manipulation

The display name of a user is generated as
APF-Template
{last name}, {first name}
by default. In order to influence this, the umgtManager must be extended as described earlier in this chapter. For this reason, the __getDisplayName($user) must be overridden. The type of the argument is GenericDomainObject and the user management manager expects it to return the desired display name or the given user as string.

To take the email address as a display name, the following code snippet can be used:
PHP-Code
class MyUmgtManager extends umgtManager { function __getDisplayName($user){ return $user->getProperty('EMail'); } }

Comments

Do you want to add a comment to the article above, or do you want to post additional hints? So please click here. Comments already posted can be found below.
There are no comments belonging to this article.