UserFrosting implements an extended version of role-based access control, which allows for very fine-grained control over user permissions. Every user can have zero or more roles, and every role can have zero or more permissions. Users' effective permissions are determined through their roles.
For example, consider the following roles, and their associated permissions:
If Alice has the Member role, she will be able to update her account info, post new messages, and delete her own messages. If she had the Site Administrator role as well, she would gain the ability to update other users' accounts and delete their messages, in addition to the permissions that she has from her Member role.
A permission is a rule that associates an action with a set of conditions under which that action can be performed. These are defined in the permissions
database table. For example:
id | slug | name | conditions | description |
---|---|---|---|---|
1 | uri_user |
View user | always() |
View the user page of any user. |
uri_user
, then in your code you can call $authorizer->checkAccess($currentUser, 'uri_user')
to determine if the current user has this permission. You can define multiple permissions on the same slug. As long as a user passes at least one permission on that slug, they will be granted access.uri_user
, but only for users in a particular group. A boolean expression consisting of access condition callbacks can be used to construct your condition.Roles can be created and modified through the administrative interface, but permissions cannot. This is because permissions are intimately tied to your code and should not be modified during the course of daily site operation. You should think of permissions as hardcoded parts of your application that just happen to be stored in the database. When you need to add, remove, or modify permissions, this should be done by a developer or sysadmin using a database migration.
In your code, access is controlled through the use of access checks on permission slugs. Often times, you will want to perform these checks in your controller methods, and throw a ForbiddenException
if the current user fails the check.
This can be done by calling the checkAccess
method of the authorizer
service. For example:
/** @var UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager */
$authorizer = $this->ci->authorizer;
/** @var UserFrosting\Sprinkle\Account\Model\User $currentUser */
$currentUser = $this->ci->currentUser;
if (!$authorizer->checkAccess($currentUser, 'uri_users')) {
throw new ForbiddenException();
}
If the current user does not have any permissions for the slug uri_users
, then the controller method will be aborted and a ForbiddenException
will be thrown. By default, the ForbiddenExceptionHandler
will catch this exception and generate a "404 Not Found" response.
You can, of course, use checkAccess
to control the behavior of your controller methods in other ways. For example, you might build a data API that is available to the public, but that returns more specialized information to authorized users:
if ($authorizer->checkAccess($currentUser, 'uri_owls')) {
return $response->withJson($secretOwls);
} else {
return $response->withJson($publicOwls);
}
You can also perform permission checks in your Twig templates using the checkAccess
helper function. This is useful when you want to render a portion of a page's content conditioned on whether or not a user has a certain permission. For example, this can be used to hide a navigation menu item for pages that the current user does not have access to:
{% if checkAccess('uri_users') %}
<li>
<a href="{{site.uri.public}}/admin/users"><i class="fa fa-user fa-fw"></i> {{ translate("USER", 2) }}</a>
</li>
{% endif %}
Access conditions are PHP expressions composed of callbacks and boolean operators. These expressions must return a boolean value when evaluated. When UserFrosting checks a permission for a given user, it will evaluate the condition expression, passing in any additional data from the final argument of checkAccess
, and grant the permission only if the expression evaluates to true
. For example, suppose the current user has the permission:
id | slug | name | conditions | description |
---|---|---|---|---|
1 | uri_activity |
View activity | equals_num(self.id,activity.user_id) |
View one of your own activities. |
In your code, if you call:
$requestedActivity = Activity::find($requestedActivityId);
if (!$authorizer->checkAccess($currentUser, 'uri_activity', [
'activity' => $requestedActivity
])) {
throw new ForbiddenException();
}
Then, the equals_num
condition will be used to compare the current user's id
with the user_id
associated with the requested activity (passed in as the activity
key). If they match, then the condition evaluates to true
and the user is granted access. You can use boolean operators to built arbitrarily complex conditions:
!has_role(user.id,2) && !is_master(user.id)
In access conditions, the special keyword
self
is used to refer to the current user. This avoids the need to explicitly pass in the current user's object.
UserFrosting ships with a number of predefined access condition callbacks, which are defined in sprinkles/account/src/ServicesProvider/AccountServicesProvider.php
:
Callback | Description |
---|---|
always() |
Unconditionally grant permission - use carefully! |
equals($val1, $val2) |
Check if the specified values are identical to one another (strict comparison). |
equals_num($val1, $val2) |
Check if the specified values are numeric, and if so, if they are equal to each other. |
has_role($user_id, $role_id) |
Check if the specified user (by $user_id ) has a particular role. |
in($needle, $haystack) |
Check if the specified value $needle is in the values of $haystack . |
in_group($user_id, $group_id) |
Check if the specified user (by $user_id ) is in a particular group. |
is_master($user_id) |
Check if the specified user (by $user_id ) is the master user. |
subset($needle, $haystack) |
Check if all values in the array $needle are present in the values of $haystack . |
subset_keys($needle, $haystack) |
Check if all keys of the array $needle are present in the values of $haystack . |
To add your own access condition callbacks, simply extend the authorizer
service in your Sprinkle.