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 modul contains a backend for user administration and a generic
business component - based on the Generic OR 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.
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.
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:
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.
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.
The installation contains three major steps:
- Configuration of the module itself
- Configuration of the GenericORMappers
- Installation of the database
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-Konfiguration
[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-Konfiguration
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.
Because of the fact, that the module is completely based on the
Generic OR 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 OR mapper
documentation, a pair of configuration files is used to define the objects and relations available
within one onstance of the mapper.
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.
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->setContext({CONTEXT});
echo $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"
class="GenericImportTemplateTag"
prefix="generic"
name="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.ActionClass = "StreamMediaAction"
FC.InputClass = "StreamMediaInput"
FC.InputParams = ""
Details on the naming of configuration files can be taken from the corresponding
configuration scheme.
Here is a sample for the backend installation:
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.
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';
}
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);
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);
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.
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->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);
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->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));
// detach role from user
$uM->detachUserFromRole($user,$role);
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.
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
Generic OR mapper chapter.
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->getAndInitServiceObject('my::namespace','MyUmgtManager','Default');
$user = $umgt->loadUserByID(5);
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
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.
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-Konfiguration
PasswordHashProvider.Default.Namespace = "modules::usermanagement::biz::provider::crypt"
PasswordHashProvider.Default.Class = "CryptHardcodedSaltPasswordHashProvider"
PasswordHashProvider.Fallback.Namespace = "modules::usermanagement::biz::provider::md5"
PasswordHashProvider.Fallback.Class = "OldMD5PasswordHashProvider"
It doesn't matter how you call the subsections (here Default and Fallback), the order of definition is decisive.
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');
}
}
In case you are using release 1.13 or earlier, __getDisplayName() must be overwritten instead of
getDisplayName().
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.
1
Priya
19.04.2013, 08:50:15
Nice one..but why don't you add some download option to get the full code...