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.
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
- 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:
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.
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.
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 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.
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.
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->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.
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 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);
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);
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.
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);
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.
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.