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
:
- 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 isAnonymousUser::USER_GROUP_NAME
.
The configuration of the authentication process in the default application looks like the following:
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:
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
and is equivalent to
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.
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:.
- 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:
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:
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 theAuthorization
configuration section like shown in the following code:
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:
ChainedPermissionManager
is used to combine differentPermissionManager
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:
NullPermissionManager
acts like there is no permission manager and is merely used for testing.
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:
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:
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 anX-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:
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:
This creates the csrf_token
response variable. The variable is used to add the token as a hidden field to the form:
After form submission the controller validates the token and acts appropriately: