Security

Security

Two key aspects of securing an application are authentication and authorization. While authentication is the process of verifying the identity of a user, authorization means determining if the user is allowed to do what he or she is about to do. That implies that authentication is a precondition for authorization. Input validation and filtering is another aspect, which is especially important in web applications.

Users and Roles

Users are essential for an authentication system. In wCMF users are represented as instances of classes implementing the User interface. Since wCMF's authorization system is role based, users are organized in roles. Role classes implement the Role interface. A user could have multiple roles, while multiple users could have the same role.

The concrete implementations of these interfaces are configured in PrincipalFactory.

Authentication

In wCMF authentication is handled by implementations of AuthenticationManager.

By default DefaultAuthenticationManager is used. It implements a login/password based authentication procedure by matching the given user credentials against existing User instances. These instances are provided by implementations of PrincipalFactory.

The following code demonstrates the authentication process as implemented in LoginController:

// get the user credentials from the request
$login = $request->getValue('user');
$password = $request->getValue('password');
try {
// try to login using the credentials
$authUser = $this->_authenticationManager->login([
'login' => $login,
'password' => $password
]);
}
catch (Exception $ex) {
Log::error("Could not log in: ".$ex, __CLASS__);
}
// set the authenticated user in the session
if ($authUser) {
// login succeeded
$session->setAuthUser($authUser->getLogin());
}
Note
The login of the user, that is associated with the current session - the current user - is obtained using the Session::getAuthUser method. Before successful authentication, this login is AnonymousUser::USER_GROUP_NAME.

The configuration of the authentication process in the default application looks like the following:

[AuthenticationManager]
__class = wcmf\lib\security\impl\DefaultAuthenticationManager
principalFactory = $principalFactory
[PrincipalFactory]
__class = wcmf\lib\security\principal\impl\DefaultPrincipalFactory
userType = app.src.model.wcmf.User
roleType = app.src.model.wcmf.Role

Since DefaultPrincipalFactory retrieves user instances from the storage, it needs to configured with the appropriate entity types. If required, the default user type can be replaced by custom implementations of User.

Authorization

The purpose of authorization is controlling access to application resources, which could be controllers or entity instances. To establish access control, rules have to be defined in the first place and enforced afterwards.

Permissions

Access control rules are expressed as permissions. Permissions are either granted or denied to a role (see Users and Roles).

A permission definition in wCMF consists of two parts:

  • The permission subject is a combination of a resource, a context and an action and the notation is the same as for action keys (see Action Key), except that the controller value is an arbitrary resource.
  • The involved roles are listed space-separated and each one is prepended with a modifier (+ for granting and - for denying).

The following code illustrates the format:

resource?context?action = +allowedRole -deniedRole ...

Implicit denial

Roles that are not listed in the permission are denied per default. The wildcard character (*) is used to define the permission for all roles that are not explicitly listed.

The following code grants the permission only to allowedRole

resource?context?action = +allowedRole -*

and is equivalent to

resource?context?action = +allowedRole

Default policy

If no permissions are defined on a resource, a default policy will be applied by default. AbstractPermissionManager implements the default policy as follows:

  • If the current user is not authorized, the default policy denies all actions
  • If the current user is authorized, the default policy allows all actions

The PermissionManager::authorize method allows to ignore the default policy by passing false as the $applyDefaultPolicy parameter, in which case the method returns null, if no permissions are defined for the resource.

Operator precedence

Granted roles are evaluated before denied ones. That means that the following code grants the permission to a user who has both roles (allowedRole and deniedRole) although the permission is denied for one of the roles.

resource?context?action = +allowedRole -deniedRole

Built-in resources

The resource value could be set to any string to implement custom application specific permissions.

Besides this, wCMF uses the following built-in resources:

  • Controller to restrict access on execution of a controller (e.g. wcmf\application\controller\SaveController)
  • Entity type to restrict access on all instances of an entity type (e.g. app.src.model.wcmf.User)
  • Entity property to restrict access on a certain property of an entity type (e.g. app.src.model.wcmf.User.login)
  • Entity instance to restrict access on one entity instance (e.g. app.src.model.wcmf.User:123)
  • Entity instance propery to restrict access on a certain property of one entity instance (e.g. app.src.model.wcmf.User:123.login)
  • Media to restrict access to file resources (e.g. media:/folderA??hidden)

The actions for the persistency related resources are properties of PersistenceAction, e.g. PersistenceAction::READ. While all actions can be applied to instances, for properties only read and update are taken into account.

Note
Entity relations are treated like entity properties, which means that read and update access can be restricted.

The actions for the file related resources are read, write, locked (read only) and hidden.

Dynamic roles

Besides assigning static roles to users, it's sometimes useful to have roles, that evaluate dynamically in the current context (requested resource, user and action). With these so called dynamic roles it is for example possible to assign permissions to the creator of an instance or realize date/time based permissions.

Dynamic roles implement the DynamicRole interface and are defined in the DynamicRoles configuration section.

The following example shows the configuration of the CreatorRole in the default application:.

[DynamicRoles]
creator = $creatorRole
[CreatorRole]
__class = wcmf\lib\security\principal\impl\CreatorRole
Note
Dynamic roles are checked before static role assignments. That means you could define a dynamic role with the same name as a static role and add custom matching code.

Permission inheritance

Permissions on entity instances are passed to child entities in a composite relation (see Associations). The following image illustrates this:

Permission inheritance

If access to the Book instance Book A is restricted for one role, the same restriction also applies for the Chapter instances belonging to it (Chapter A1, Chapter A2). An inherited access restriction can be removed by explicitly granting the permission on the object in question.

Examples

The following code shows some permission examples:

# tester role is not allowed to update any authors except for the specified one
# tester role is not allowed to update any authors stage attribute
app.src.model.Author??update = -tester
app.src.model.Author:111??update = +tester
app.src.model.Author.stage??update = +administrators
# tester role is not allowed to update any publishers name except for the specified one
# tester role is not allowed to update any authors stage attribute
app.src.model.Publisher.name??update = -tester
app.src.model.Publisher:111.name??update = +tester
# tester role is not allowed to read any book name except for the specified one
app.src.model.Book??read = -tester
app.src.model.Book:111??read = +tester
# tester is not allowed to read chapter 111 and due to inheritance also not sub chapter 222,
# and sub sub chapter 333, sub chapter 555 explicitly allowed and due to inheritance
# also sub sub chapter 666
app.src.model.Chapter:111??read = -tester +administrators
app.src.model.Chapter:555??read = +tester +administrators
# tester role is not allowed to execute SaveController
wcmf\application\controller\SaveController?? = -tester
# the tmp directory is hidden for everyone
# only administrators can rename or delete the uploads directory
media:/tmp??hidden = +*
media:/uploads??locked = -administrators +*
# custom permissions
customPermission??start = +tester
customPermission??stop = -tester

Permission management

Permission management includes creation, modification and deletion of permissions as well as handling authorization requests. In an wCMF application an instance of PermissionManager is used for these tasks. It is configured in the PermissionManager configuration section.

Generally there are two kinds of permissions to be defined in an application.

  • Static permissions are already known when the application is designed, e.g. restriction to entity types or controllers. Since these permissions are not likely to be changed by application users, they can be stored in the application configuration.
  • Dynamic permissions are defined on the application data. These permissions will be set when the appropriate data is created. To allow application users to change these permissions they are typically stored in the database.

Different permission types require different ways of permission management, particularly regarding storing permissions. To support this wCMF provides several implementations of the PermissionManager interface:

  • StaticPermissionManager is used to retrieve permissions from the application configuration. The permissions are stored in the Authorization configuration section like shown in the following code:
[Authorization]
??login = +*
??logout = +*
??checkPermissions = +*
??checkPermissionsOfUser = +administrators
app.src.model.wcmf.User??read = +administrators
app.src.model.wcmf.User??update = +administrators
app.src.model.wcmf.User??delete = +administrators
app.src.model.wcmf.User??create = +administrators
  • DefaultPermissionManager handles permissions that are stored in the database. The entity type that actually stores the permissions is required to have a resource, context, action and roles attribute and is defined in the application configuration. The following lines are taken from the configuration of the default application:
[DefaultPermissionManager]
__class = wcmf\lib\security\impl\DefaultPermissionManager
permissionType = app.src.model.wcmf.Permission
  • ChainedPermissionManager is used to combine different PermissionManager instances. When asked for authorization, it delegates the request to all it's managers, while creation, modification and deletion of permissions is always handled by the first contained manager. An example configuration of this manager is shown in the code below:
[PermissionManager]
__class = wcmf\lib\security\impl\ChainedPermissionManager
managers = {$defaultPermissionManager, $staticPermissionManager}
[DefaultPermissionManager]
__class = wcmf\lib\security\impl\DefaultPermissionManager
permissionType = app.src.model.wcmf.Permission
[StaticPermissionManager]
__class = wcmf\lib\security\impl\StaticPermissionManager

All implementations inherit from AbstractPermissionManager, which already provides most of parts for handling authorization requests.

Temporary permissions

There are situations, where a piece of code requires a certain permission in the context of the current user, which is not possible to grant throughout the whole application. For example, if a user wants to sign in to the application, PrincipalFactory needs to provide a user instance although the anonymous user is not allowed to read user instances. To accomplish this, PermissionManager allows to set a transient, temporary permission like in the following example:

$permissionManager = ObjectFactory::getInstance('permissionManager');
$persistenceFacade = ObjectFactory::getInstance('persistenceFacade');
$userType = 'app.src.model.wcmf.User';
// set up a temporary permission to read the user instance for the given login
$permissionManager->addTempPermission($userType, '', PersistenceAction::READ);
$user = $persistenceFacade->loadFirstObject($userType, BuildDepth::SINGLE, [
new Criteria($userType, 'login', '=', $login)
], null);
// remove the temporary permission
$permissionManager->removeTempPermission($userType, '', PersistenceAction::READ);

Checking permissions

To test, if a user has the permission to access a resource in the requested way the method PermissionManager::authorize is used. It returns a boolean value indicating whether the user is authorized or not.

The following code shows how to determine, if the current user is allowed to read the given object:

$canRead = $permissionManager->authorize($object->getOID(), '', PersistenceAction::READ);

Default policies

Default policies answer the question, what happens if no permission is defined on a requested resource. These rules depend on the authentication status of the current user and are defined in AbstractPermissionManager as follows:

  • Not authenticated: Resource is not accessible
  • Authenticated: Resource is accessible

Sessions

wCMF provides the following Session implementations:

  • DefaultSession is a session that uses PHP's default server side session implementation in combination with a session cookie.
  • AuthTokenSession additionally requires clients to send an X-Auth-Token request header.
  • ClientSideSession has no server state as it stores the data in cookies.

Input

Generally all external input sent to the application should be considered harmful.

In PHP this input is stored in so called Superglobals, e.g. $_GET, $_POST, $_FILES. To avoid using these global variables directly in code, wCMF encapsulates them in the Request instance and makes them available through the Request::getValue method. Besides the variable name this method defines the following optional parameters:

  • $default default value if the value is not contained in the request
  • $validateDesc description of the validation to be applied to the value

The $validateDesc parameter is passed to the Validator::validate method and null is returned if validation fails.

The following code demonstrates the usage:

// validate an email value
$email = $request->getValue('email', '', 'filter:{"type":"validate_email"}');
// validate a date using a regular expression and return the current date on failure
$today = date('Y-m-d');
$date = $request->getValue('date', $today, 'regex:{"pattern":"/^[0-9]{4}-[0-9]{2}-[0-9]{2}$/"}');

For more information about validation see Validation.

CSRF protection

To avoid CSRF vulnerabilities in a web application, forms are usually protected by using CSRF tokens. The Controller::generateCsrfToken method is used to generate a unique token value for each form display. The token is validated by using the Controller::validateCsrfToken method when the form is submitted. For proper identification, tokens are referenced by a name (e.g. the form's name) which is passed to these methods.

The following example demonstrates the usage of these methods.

A new token value is generated each time the form is displayed:

class UserController {
public function render() {
$this->generateCsrfToken('login');
}
}

This creates the csrf_token response variable. The variable is used to add the token as a hidden field to the form:

<input type="hidden" name="csrf_token" value="{$csrf_token}">

After form submission the controller validates the token and acts appropriately:

class UserController {
public function login() {
if (!$this->validateCsrfToken('login')) {
// error
}
else {
// do login
}
}
}