NAME
Abilities::Scoped - Scoped version of the Abilities user authorization system.
VERSION
version 0.2
SYNOPSIS
package User;
use Moose;
with 'Abilities::Scoped';
# ... define required methods ...
# somewhere else in your code:
# get a user object that consumed the Abilities role
my $user = MyApp->get_user('username'); # $user is a User object
# check if the user is able to do something in the scope of the app
if ($user->can_perform('app', 'something')) {
do_something();
} else {
die "Hey you can't do that in the app, you can only do ", join(', ', $user->abilities('app'));
}
DESCRIPTION
This Moose role defines a scoped version of the Abilities role for hierarchial user authorization. In this version, users are granted abilities to perform actions within certain scopes. This allows building a much more comprehensive user authorization system for your app.
For example, say you're creating a content-rich website that has different sections such as 'news', 'video archive', 'picture galleries', etc. You might want to assign responsibilities on these sections for different users. Each section is, therefore, a scope. A user can be granted the ability to 'do_something' in the 'news' scope, or the 'video archive' scope, or both. But the 'do_something' action is different in each scope. Global actions, such as creating users and maintenance actions, can be thought of as belonging to a 'global', or 'app' scope.
This can be taken much further. Say you're building a hosted blogging platform. Users should be able to create and edit posts only in their own blogs, otherwise chaos will erupt. Checking that a user has the ability to 'edit_post' isn't sufficient, because their ability to do so should be limited within their own scope, i.e. their own blogs. So, if there's a blog called 'my_blog' hosted on your platform, then in order to be able to edit a post in that blog, a user must have the 'edit_post' ability in the 'my_blog' scope.
Abilities::Scoped requires implementing your user-base a little differently than Abilities. The same three methods are required - actions()
, roles()
and is_super()> - but now they require a scope argument, which turns them into actions( $scope )
, roles( $scope )
and is_super( $super )
. Notice that is_super()
is now scoped as well. This is interesting. In Abilities, a super-user (or super-role) are allowed to perform whatever they want. Here, their "super-powers" can also be limited to some scope(s). So, a certain user can be a super-user in the 'my_blog' scope, allowing them to perform any defined action in that scope. Again, you can make someone a super user in a global scope (e.g. 'global' or 'app'), this is purely semantics.
ADVANCED USAGE
By default, Abilities::Scoped treats scopes as names (i.e. scalars), and while this may be sufficient for a lot of cases, it isn't flexible or comfortable enough. Say a blog hosted on your blogging platform has many users who were granted abilities on it (and some of them even on other blogs). Storing this information in a database (or whatever backend you use, see Entities for a reference implementation) can be difficult, inconvenient or even impossible. Should you store a list of actions/roles for each user in each scope? That can be a major pain in the ass.
Sometimes, therefore, it would make much more sense to _calculate_ a user's ability to perform an action within a certain scope instead of looking for it in a store. For example, consider a case in which Abilities::Scoped is paired with Abilities::Features, again in our blogging platform. A customer entity has a blog, and we want to limit the child users of that customer to perform actions only within that blog. In the store, we can grant these users the ability to perform 'edit_post' in a 'customer_blog' scope instead of using a very-specific scope name (i.e. the specific blog name). The, in our app's code, when some user requests authorization to perform the 'edit_post' action on a certain blog, we first make sure this blog belongs to the user's parent customer entity, and then check their ability to perform 'edit_post':
if ($user->can_perform({ blog => $blog_id, post => $post_id }, 'edit_post')) {
# ... edit the post ... #
} else {
# ... you can't do that ... #
}
Here, we're not passing the can_perform()
method a scope name, but a hash-ref. You can pass anything you want, actually, but you'd have to override can_perform()
with your own method. For example, you can do this in your user class:
package MyApp::User;
use Moose;
with 'Abilities::Scoped';
override 'can_perform' => sub {
my ($self, $scope, $action) = @_; # $self is the user object, obviously
# find the blog
my $blog = MyApp->load_blog($scope->{blog});
# does this blog belong to the user's parent customer?
return unless $blog->customer_id == $self->customer_id;
# it does belong, now let's make sure the user do the
# requested action in this scope
return super('customer_blog', $action);
};
Of course, you can use your own imagination and implement whatever kind of checking you want. Since the whole Abilities system is just Moose roles, you can take advantage of Moose's flexibility and really do some neat stuff.
REQUIRED METHODS
Classes that consume this role are required to implement the following methods:
roles( $scope )
Returns a list of all roles that a user object belongs to, or a role object inherits from, in a certain scope. The list must contain references to the role objects, not just their names.
actions( $scope )
Returns a list of all actions that a user object has been explicitely granted, or that a role object has been granted, in a certain scope. The list must contain references to the action objects, not just their names.
is_super( $scope )
This is a boolean attribute that both user and role objects should have. If a user/role object has a true value for this attribute in a certain scope, then they will be able to perform any action in that scope, even if it wasn't granted to them.
PROVIDED METHODS
Classes that consume this role will have the following methods available for them:
can_perform( $scope, $action_name | @action_names )
Receives a scope and the name of an action (or names of actions), and returns a true value if the user/role can perform the provided action(s) in that scope. If more than one actions are passed, a true value will be returned only if the user/role can perform ALL of these actions.
belongs_to( $scope, $role_name | @role_names )
takes_from( $scope, $role_name | @role_names )
The above two methods are actually the same. The names are meant to differentiate between user objects (first case) and role objects (second case).
These methods receive a scope and a role name (or names). In case of a user object, the method will return a true value if the user is a direct member of the provided role in the required scope. In case multiple role names were provided, a true value will be returned only if the user is a member of ALL of these roles. Only direct association is checked, so the user must be specifically assigned to the provided role, and not to a role that inherits from that role (see inherits_from_role()
instead.
In case of a role object, this method will return a true value if the role directly consumes the abilities of the provided role in the required scope. In case multiple role names were provided, a true value will be returned only if the role directly consumes ALL of these roles. Like in case of a user, only direct association is checked, so inheritance doesn't count.
inherits_from_role( $scope, $role_name | @role_names )
Receives a scope and the name of a role (or names of roles), and returns a true value if the user/role inherits the abilities of the provided role in the required scope. If more than one roles are passed, a true value will be returned only if the user/role inherits from ALL of these roles.
This method takes inheritance into account, so if a user was directly assigned to the 'admins' role in the required scope, and the 'admins' role inherits from the 'devs' role in the same scope, then inherits_from_role($scope, 'devs') will return true for that user.
all_abilities( $scope )
Returns a list of all actions that a user/role can perform in a certain scope, either due to direct association or due to inheritance.
INTERNAL METHODS
These methods are only to be used internally.
_all_abilities()
AUTHOR
Ido Perlmuter, <ido at ido50 dot net>
BUGS
Please report any bugs or feature requests to bug-abilities at rt.cpan.org
, or through the web interface at http://rt.cpan.org/NoAuth/ReportBug.html?Queue=Abilities. I will be notified, and then you'll automatically be notified of progress on your bug as I make changes.
SUPPORT
You can find documentation for this module with the perldoc command.
perldoc Abilities::Scoped
You can also look for information at:
RT: CPAN's request tracker
AnnoCPAN: Annotated CPAN documentation
CPAN Ratings
Search CPAN
LICENSE AND COPYRIGHT
Copyright 2010 Ido Perlmuter.
This program is free software; you can redistribute it and/or modify it under the terms of either: the GNU General Public License as published by the Free Software Foundation; or the Artistic License.
See http://dev.perl.org/licenses/ for more information.