User management module

Please note that this documentation is partially deprecated. Please use the documentation presented under Benutzer-Verwaltung. Unfortunately, the new page is available in German only until release 1.16. In case it does not contain the desired information please consult this page again or create a thread in the Forum.

The usermanagement module contains a backend for user administration and a generic business component - based on the Generic o/r mapper - that can be used for authentication purposes and to manage the users, groups, roles, permissions and permission sets. In release 1.12 visibility definitions are introduced to manager object visibility permissions generically for every application integrated with the module.

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
  • Groups
  • Roles
  • Permission sets
  • Permissions
  • Visibility definitions
  • Visibility definitions types

The separation in these 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)

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. Further, 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.
Further methods and their signature can be found in the API documentation.

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 at least 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 configuration
[InstanceName] ConnectionKey = "" ApplicationID = "" ServiceMode = ""
Since version 1.14 there is an other, optional value, which will be used as static salt for the default PasswordHashProvider. If no salt is specified, an default salt from the APF will be used. For increasing security it is highly recommended to define your own salt. It should be long and make use of special characters.
APF configuration
Salt = ""
ATTENTION! Loosing or changing the salt will lead to unusable hashes in the database! Your users then won't be able to log in anymore! You really need to backup you salt therefore!
The apf-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 Generic o/r mapper a set of configuration files must be provided for the UML above. To ease setup, the apf-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 scheme chapter.

As you can read about in the Generic o/r mapper 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 separate bootstrap file must be created or the application can be integrated within another using tags.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 <html:mediastream /> tag), the index.php must contain the following code:

PHP code
include('./APF/core/bootstrap.php'); use APF\core\singleton\Singleton; use APF\core\frontcontroller\FrontController; $fC = Singleton::getInstance(FrontController::class); $fC->setContext({CONTEXT}); echo $fC->start('APF\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="APF\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 class="APF\tools\html\taglib\GenericImportTemplateTag" prefix="generic" class="importdesign" /> <generic:importdesign model-class="VENDOR\..\ModelName" model-mode="SINGLETON" namespace-param="..." template-param="..." dependent-action-namespace="VENDOR\namespace\to\dependent\action" dependent-action-name="DependentActionName" dependent-action-params="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 the <html:mediastream /> tag. 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
within the namespace
The content of the file is as follows:
APF configuration
[streamMedia] ActionClass = "APF\tools\media\actions\StreamMediaAction"

Details on the naming of configuration files can be taken from the corresponding configuration scheme.

Here is a sample for the backend installation:

User management backend installation

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
use APF\modules\usermanagement\biz\UmgtManager; // get the business object $uM = $this->getDIServiceObject('APF\modules\usermanagement\biz', 'UmgtManager'); // retrieve the username and password from the request $username = $this->getRequest()->getParameter('user'); $password = $this->getRequest()->getParameter('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 o/r mapper documantation page.
PHP code
// get the business object $uM = $this->getDIServiceObject('APF\modules\usermanagement\biz', 'UmgtManager'); // 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 following code boy describes several use cases:
PHP code
// get the business object $uM = $this->getDIServiceObject('APF\modules\usermanagement\biz', 'UmgtManager'); // 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)); // detach role from user $uM->detachUserFromRole($user,$role);
3.2.7. Visibility permissions

The new concept added in release 1.12 enables the developer to integrate the user management module with any application that requires such functionality. Besides the functional permissions that are aimed to manage permissions on actions that can be executed on an application's object (permissions) now visibility definitions are included (visibility definition).

The combination of these two permission types is the basis for multi-purpose user management. You can now distinguish between what the user might see (visibility definition) and what a user may do with what he sees (permissions). The UmgtManager thus has several new methods that let you deal with the visibility definitions.

Wiki page Anwendung Sichtbarkeits-Berechtigungen (German) describes a real life application sample.

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. The following code box shows how to obtain the mapper instance from the user management business component:

PHP code
$umgt = $this->getDIServiceObject('APF\modules\usermanagement\biz', 'UmgtManager'); $orm = $umgt->getORMapper();

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 Generic o/r mapper 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 preferred method is to implement a custom password hash provider as describes in the next chapter.

The business component uses the crypt 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 { protected function createPasswordHash($password){ return sha1($password); } } $umgt = $this->getDIServiceObject('APF\modules\usermanagement\biz', 'UmgtManager'); $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. Up to Version 1.13 md5 has been used for this. Since Version 1.14 we changed this, outdated algorithm to a more complex and more secure one, based on crypt using a static and dynamic salt. It is highly recommended to avoid using the old algorithm. How to upgrade an existing application to the new alghorithm is being described in 4.1.3. PasswordHashProvider fallback.
For the new static salt, which came in Version 1.14, a new configuration entry should be made, you can find information about this in 2.1. Configuration of the module. Furthermore a new column in the user table will be needed, which will contain a DynamicSalt, which gets generated automatically and different for each user.

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
use APF\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.Class = "VENDOR\..\namespace\Sha1PasswordHashProvider"
Complete usermanagement sample configuration files can be found in the apf-configpack-* release packages.

4.1.3. PasswordHashProvider fallback
Since Version 1.14 it is possible to use more than one PasswordHashProvider at the same time, in order to be able to upgrade from one provider to another on-the-fly, because otherwise the users wouldn't be able to log in anymore. For this feature you just define multiple PasswordHashProvider in the configuration. The first configured provider will be the new provider which should be used normally. All following providers will be seen as fallback. When loggin in, the new provider will be tried first. If this doesn't find a match between the hash and the given password, all fallback providers will be tested, one after the other. If a matching hash was found with a fallback provider, the old hash in database will be automatically replaced with the hash of the new, first defined provider in the background, and the user will be logged in.
Using this way, after some time, when all users have logged in since the upgrade, every hash in database should be in the new format, and therefore be more secured against brute force and rainbow table attacks.

The configuration needs to be changed like the following, this is the example how to upgrade an application which used the default APF algorithm from before 1.14, to the new algorithm introduced in version 1.14:
APF configuration
PasswordHashProvider.Default.Class = "APF\modules\usermanagement\biz\provider\crypt\CryptHardcodedSaltPasswordHashProvider" PasswordHashProvider.Fallback.Class = "APF\modules\usermanagement\biz\provider\md5\OldMD5PasswordHashProvider"

It doesn't matter how you call the subsections (here Default and Fallback), the order of definition is decisive.

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 { protected function getDisplayName($user){ return $user->getProperty('EMail'); } }

4.3. Forgot password

The UMGT has now the function to reset the password of a user in case it was lost. Two simple templates are delivered

  • modules/usermanagement/pres/templates/login/forgot_pw.html
  • and
  • modules/usermanagement/pres/templates/login/reset_pw.html

forgot_pw.html provides a form to to enter the mail address and by using the reset_pw.html the password can by reset by the user.

For changing the password the user has to click a link in a mail it was sent to his address. This link is by default 24 hours valid. You can change the validity via a configuration-file. The next step is to reset the password. Subsequent there will be sent a second mail to inform the user about the successful password change.

To send the e-mails APF's Message class will be used. To enable e-mail distribution, the following configuration is required:

APF configuration
[UmgtForgotPassword] Mail.SenderName = "Example Sender - Forgot password" Mail.SenderEMail = "mail@address.tld" Mail.ReturnPath = "return@address.tld" Mail.ContentType = "text/html; charset=UTF-8"

To change the validity of the link you can use the configuration file: {ENVIRONMENT}_forgotpw.ini in namespace /APF/config/modules/usermanagement/pres/{CONTEXT}/. By using the hash.lifetime parameter you can set the validity in seconds. Please make sure that your validity is indicated in whole hours.

APF configuration
[Default] hash.lifetime = "86400"

All texts in the templates and the mails can be configured by using the *.labels-config file for the UMGT.


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.

In order to provide a state-of-the-art web experience and to continuously improve our services we are using cookies. By using this web page you agree to the use of cookies. For more information, please refer to our Privacy policy.