Abilities
view release on metacpan or search on metacpan
0.4 2013-05-02 21:06:25 Asia/Jerusalem
[ VARIOUS CHANGES ]
- Moved from Any::Moose to Moo as per the former's deprecation
[ BUGFIXES ]
- fixed _build_abilities() to skip non-blessed roles (submitted by cafe01)
- small documentation fixes
0.3 2012-12-28 17:15:01 Asia/Jerusalem
[ BACKWARDS COMPATIBILITY BROKEN ]
- Actions and features can now have constraints. When an action/features
doesn't have any, then it's really a yes/no (have/don't) option.
When an action/feature does have constraints, then it's no longer
a yes/no option. This allows more finer grained control over
abilities.
- The can_perform() method now can only take one action, not a list.
It now also takes an optional constraint. Same goes for has_feature().
- The required actions() method in Abilities.pm and features() method
in Abilities/Features.pm now expects a different return structure,
read the docs for more info
- Abilities::Scoped is removed since Abilities now provides the
same functionality with the new constraints paradigm
[ VARIOUS CHANGES ]
- Now using Any::Moose instead of Moose
- Changed the names of Abilities::assigned_role() and Abilities::belongs_to()
(they were the same method) to Abilities::assigned_role() and
added a deprecation warning for the previous two
- Created a test suite
0.2 2011-02-01 19:16:06 Asia/Jerusalem
- Added Abilites::Scoped - a special version of Abilites.pm that
supports scoping
0.1 2010-07-20 23:31:59 Asia/Jerusalem
- Initial release
- Base code extracted from my Catalyst::Plugin::Authorization::Abilities module
and turned into a Moose role
- Added the "Features" code to support customer-plan-features management
META.json
META.yml
Makefile.PL
README
SIGNATURE
dist.ini
lib/Abilities.pm
lib/Abilities/Features.pm
t/00-load.t
t/01-abilities.t
t/02-features.t
t/lib/TestCustomer.pm
t/lib/TestManager.pm
t/lib/TestPlan.pm
t/lib/TestRole.pm
t/lib/TestUser.pm
t/release-dist-manifest.t
t/release-pod-coverage.t
t/release-pod-syntax.t
DESCRIPTION
Abilities is a simple yet powerful mechanism for authorizing users of
web applications (or any applications) to perform certain actions in the
application. This is an extension of the familiar role-based access
control that is common in various systems and frameworks like Catalyst
(See Catalyst::Plugin::Authorization::Roles for the role-based
implementation and Catalyst::Plugin::Authorization::Abilities for the
ability-based implementation that inspired this module).
As opposed to role-based access control - where users are allowed access
to a certain feature (here called 'action') only through their
association to a certain role that is hard-coded into the program - in
ability-based acccess control, a list of actions is assigned to every
user, and they are only allowed to perform these actions. Actions are
not assigned by the developer during development, but rather by the
end-user during deployment. This allows for much more flexibility, and
also speeds up development, as you (the developer) do not need to think
about who should be allowed to perform a certain action, and can easily
grant access later-on after deployment (assuming you're also the
end-user).
and returns its object. This will probably means loading the role from a
database and blessing it into your role class that also consumes this
module.
I apologize for any inconvenience this might have caused.
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
SHA1 beff4a17e62d38f89f42f7d4bd8eea65bd3eed43 MANIFEST.SKIP
SHA1 79363be94119dee40e43d2b7b32962e9bd3efc25 META.json
SHA1 9674a80d81366885a992c06f608b0964d77923c6 META.yml
SHA1 548e1b4c55b9ebc755adeab12b43235409201209 Makefile.PL
SHA1 584c885eaa73ec7b92fd17db4891fd099569fd80 README
SHA1 92f96a5defd798a8643ad925dd3b86f10e2836c2 dist.ini
SHA1 ac56180c952cdc52935ea53ce590f5d384431447 lib/Abilities.pm
SHA1 6d3acce2f84f4360a4768d93c82663a884279145 lib/Abilities/Features.pm
SHA1 b968ed1c0d3c90f8bc8d8ad970aa3e6de5fbc150 t/00-load.t
SHA1 913156ea716a1551101e218ba6e663fd37c89f18 t/01-abilities.t
SHA1 52a99df8d7dcf5e2b599fbeba6c011a99f23f820 t/02-features.t
SHA1 6a7b6c44a92ac5b4c07cad3d1addab03d01e72d8 t/lib/TestCustomer.pm
SHA1 a735c6657aeb8677e6fc8adf8b982090d5685442 t/lib/TestManager.pm
SHA1 ec23fe5e511b42f09053e897fe7006c4a5772702 t/lib/TestPlan.pm
SHA1 168a46d7f5c5a4376f6c46e90ae926e69a343c79 t/lib/TestRole.pm
SHA1 b2b28919aa35e8ef5bdc061d5f8774e8b33e06d6 t/lib/TestUser.pm
SHA1 87768d177f8c2e2df9c591cde869e7c8a245555e t/release-dist-manifest.t
SHA1 9433c240fe590bc404ab68ff63984df763e347ed t/release-pod-coverage.t
SHA1 b30cbdfaf935017c4568c0c91b242438cb87786e t/release-pod-syntax.t
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v2.0.19 (GNU/Linux)
lib/Abilities.pm view on Meta::CPAN
=head1 DESCRIPTION
Abilities is a simple yet powerful mechanism for authorizing users of web
applications (or any applications) to perform certain actions in the application. This is an
extension of the familiar role-based access control that is common in
various systems and frameworks like L<Catalyst> (See L<Catalyst::Plugin::Authorization::Roles>
for the role-based implementation and L<Catalyst::Plugin::Authorization::Abilities>
for the ability-based implementation that inspired this module).
As opposed to role-based access control - where users are allowed access
to a certain feature (here called 'action') only through their association
to a certain role that is hard-coded into the program - in ability-based
acccess control, a list of actions is assigned to every user, and they are
only allowed to perform these actions. Actions are not assigned by the
developer during development, but rather by the end-user during deployment.
This allows for much more flexibility, and also speeds up development,
as you (the developer) do not need to think about who should be allowed
to perform a certain action, and can easily grant access later-on after
deployment (assuming you're also the end-user).
Abilities to perform certain actions can be given to a user specifically, or
lib/Abilities.pm view on Meta::CPAN
this module.
I apologize for any inconvenience this might have caused.
=head1 AUTHOR
Ido Perlmuter, C<< <ido at ido50 dot net> >>
=head1 BUGS
Please report any bugs or feature requests to C<bug-abilities at rt.cpan.org>, or through
the web interface at L<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.
=head1 SUPPORT
You can find documentation for this module with the perldoc command.
perldoc Abilities
You can also look for information at:
lib/Abilities/Features.pm view on Meta::CPAN
use Moose; # or Moo
with 'Abilities::Features';
# ... define required methods ...
# somewhere else in your code:
# get a customer object that consumed the Abilities::Features role
my $customer = MyApp->get_customer('some_company');
# check if the customer has a certain feature
if ($customer->has_feature('ssl_encryption')) {
&initiate_https_connection();
} else {
&initiate_http_connection();
}
=head1 DESCRIPTION
This L<Moo role|Moo::Role> extends the ability-based authorization
system defined by the L<Abilities> module with customer and plan management
for subscription-based web services. This includes paid services, where
customers subscribe to a plan from a list of available plans, each plan
with a different set of features. Examples of such a service are GitHub
(a Git revision control hosting service, where customers purchase a plan
that provides them with different amounts of storage, SSH support, etc.)
and MailChimp (email marketing service where customers purchase plans
that provide them with different amounts of monthly emails to send and
other features).
The L<Abilities> role defined three entities: users, roles and actions.
This role defines three more entities: customers, plans and features.
Customers are organizations, companies or individuals that subscribe to
your web service. They can subscribe to any number of plans, and thus be
provided with the features of these plans. The users from the Abilities
module will now be children of the customers. They still go on being members
of roles and performing actions they are granted with, but now possibly
only within the scope of their parent customer, and to the limits defined
in the customer's plan. Plans can inherit features from other plans, allowing
for defining plans faster and easier.
Customer and plan objects are meant to consume the Abilities::Features
role. L<Entities> is a reference implementation of both the L<Abilities> and
L<Abilities::Features> roles. It is meant to be used as-is by web applications,
or just as an example of how a user management and authorization system
that consumes these roles might look like. L<Entities::Customer> and
L<Entities::Plan> are customer and plan classes that consume this role.
Just like in L<Abilities>, features can be constrained. For more info,
see L<Abilities/"CONSTRAINTS">.
More information about how these roles work can be found in the L<Entities>
documentation.
=head1 REQUIRED METHODS
Customer and plan classes that consume this role are required to provide
the following methods:
lib/Abilities/Features.pm view on Meta::CPAN
( 'starter', 'diamond' )
NOTE: In previous versions, this method was required to return
an array of plan objects, not a list of plan names. This has been changed
in version 0.3.
=cut
requires 'plans';
=head2 features()
This method returns a list of all feature names that a customer has explicitely
been given, or that a plan has. If a certain feature is constrained, then
it should be added to the list as an array reference with two items, the first being
the name of the feature, the second being the name of the constraint.
Example return structure:
( 'ssh_access', [ 'multiple_users', 5 ] )
NOTE: In previous versions, this method was required to return
an array of feature objects, not a list of feature names. This has been changed
in version 0.3.
=cut
requires 'features';
=head2 get_plan( $name )
Returns the object of the plan named C<$plan>.
=cut
requires 'get_plan';
=head1 METHODS
Classes that consume this role will have the following methods provided
to them:
=head2 has_feature( $feature_name, [ $constraint ] )
Receives the name of a feature, and possibly a constraint, and returns a
true value if the customer/plan has that feature, false value otherwise.
=cut
sub has_feature {
my ($self, $feature, $constraint) = @_;
# return false if customer/plan does not have that feature
return unless $self->available_features->{$feature};
# customer/plan has feature, but is there a constraint?
if ($constraint) {
# return true if customer/plan's feature is not constrained
return 1 if !ref $self->available_features->{$feature};
# it is constrained (or at least it should be, let's make
# sure we have an array-ref of constraints)
if (ref $self->available_features->{$feature} eq 'ARRAY') {
foreach (@{$self->available_features->{$feature}}) {
return 1 if $_ eq $constraint;
}
return; # constraint not met
} else {
carp "Expected an array-ref of constraints for feature $feature, received ".ref($self->available_features->{$feature}).", returning false.";
return;
}
} else {
# no constraint, make sure customer/plan's feature is indeed
# not constrained
return if ref $self->available_features->{$feature}; # implied: ref == 'ARRAY', thus constrained
return 1; # not constrained
}
}
=head2 in_plan( $plan_name )
Receives the name of plan and returns a true value if the user/customer
is a direct member of the provided plan(s). Only direct association is
checked, so the user/customer must be specifically assigned to that plan,
and not to a plan that inherits from that plan (see L</"inherits_plan( $plan_name )">
lib/Abilities/Features.pm view on Meta::CPAN
foreach ($self->plans) {
return 1 if $_ eq $plan;
}
return;
}
=head2 inherits_plan( $plan_name )
Returns a true value if the customer/plan inherits the features of
the provided plan(s). If a customer belongs to the 'premium' plan, and
the 'premium' plan inherits from the 'basic' plan, then C<inherits_plan('basic')>
will be true for that customer, while C<in_plan('basic')> will be false.
=cut
sub inherits_plan {
my ($self, $plan) = @_;
return unless $plan;
foreach (map([$_, $self->get_plan($_)], $self->plans)) {
return 1 if $_->[0] eq $plan || $_->[1]->inherits_plan($plan);
}
return;
}
=head2 available_features
Returns a hash-ref of all features available to a customer/plan object, after
consolidating features from inherited plans (recursively) and directly granted.
Keys of this hash-ref will be the names of the features, values will either be
1 (for yes/no features), or a single-item array-ref with a name of a constraint
(for constrained features).
=cut
sub available_features {
my $self = shift;
my $features = {};
# load direct features granted to this customer/plan
foreach ($self->features) {
# is this features constrained?
unless (ref $_) {
$features->{$_} = 1;
} elsif (ref $_ eq 'ARRAY' && scalar @$_ == 2) {
$features->{$_->[0]} = [$_->[1]];
} else {
carp "Can't handle feature of reference ".ref($_);
}
}
# load features from plans this customer/plan has
my @hashes = map { $self->get_plan($_)->available_features } $self->plans;
# merge all features
while (scalar @hashes) {
$features = merge($features, shift @hashes);
}
return $features;
}
=head1 UPGRADING FROM v0.2
Up to version 0.2, C<Abilities::Features> required the C<plans> and C<features>
attributes to return objects. While this made it easier to calculate
available features, it made this system a bit less flexible.
In version 0.3, C<Abilities::Features> changed the requirement such that both these
attributes need to return strings (the names of the plans/features). If your implementation
has granted plans and features stored in a database by names, this made life a bit easier
for you. On other implementations, however, this has the potential of
requiring you to write a bit more code. If that is the case, I apologize,
but keep in mind that you can still store granted plans and features
any way you want in a database (either by names or by references), just
as long as you correctly provide C<plans> and C<features>.
Unfortunately, in both versions 0.3 and 0.4, I made a bit of a mess
that rendered both versions unusable. While I documented the C<plans>
attribute as requiring plan names instead of plan objects, the actual
implementation still required plan objects. This has now been fixed,
but it also meant I had to add a new requirement: consuming classes
now have to provide a method called C<get_plan()> that takes the name
of a plan and returns its object. This will probably means loading the
plan from a database and blessing it into your plan class that also consumes
this module.
I apologize for any inconvenience this might have caused.
=head1 AUTHOR
Ido Perlmuter, C<< <ido at ido50 dot net> >>
=head1 BUGS
Please report any bugs or feature requests to C<bug-abilities at rt.cpan.org>, or through
the web interface at L<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.
=head1 SUPPORT
You can find documentation for this module with the perldoc command.
perldoc Abilities::Features
You can also look for information at:
t/02-features.t view on Meta::CPAN
#!perl
use lib 't/lib';
use TestManager;
use TestPlan;
use TestCustomer;
use Test::More tests => 14;
my $mg = TestManager->new;
my $pa = TestPlan->new(name => 'PA', features => ['ssl', ['storage', '5GB']], mg => $mg);
my $pb = TestPlan->new(name => 'PB', features => ['backups', ['storage', '10GB']], plans => ['PA'], mg => $mg);
my $pc = TestPlan->new(name => 'PC', features => ['backups', ['phone_support', '24hrs']], mg => $mg);
my $ca = TestCustomer->new(name => 'CA', plans => ['PA'], mg => $mg);
my $cb = TestCustomer->new(name => 'CB', plans => ['PB'], features => [['phone_support', '12h']], mg => $mg);
my $cc = TestCustomer->new(name => 'CC', plans => ['PC'], mg => $mg);
my $cd = TestCustomer->new(name => 'CD', features => [['storage', '200GB']], mg => $mg);
$mg->add_objects($pa, $pb, $pc);
ok($pa, 'Got PA');
ok($pb, 'Got PB');
ok($pc, 'Got PC');
ok($ca, 'Got CA');
ok($cb, 'Got CB');
ok($cc, 'Got CC');
ok($cd, 'Got CD');
t/lib/TestCustomer.pm view on Meta::CPAN
package TestCustomer;
use Moo;
use namespace::autoclean;
has 'name' => (
is => 'ro',
required => 1
);
has 'features' => (
is => 'ro',
default => sub { [] }
);
has 'plans' => (
is => 'ro',
default => sub { [] }
);
has 'mg' => (
t/lib/TestCustomer.pm view on Meta::CPAN
);
with 'Abilities::Features';
sub get_plan {
my ($self, $plan) = @_;
return $self->mg->{$plan};
}
around qw/features plans/ => sub {
my ($orig, $self) = @_;
return @{$self->$orig || []};
};
1;
t/lib/TestPlan.pm view on Meta::CPAN
package TestPlan;
use Moo;
use namespace::autoclean;
has 'name' => (
is => 'ro',
required => 1
);
has 'features' => (
is => 'ro',
default => sub { [] }
);
has 'plans' => (
is => 'ro',
default => sub { [] }
);
has 'mg' => (
t/lib/TestPlan.pm view on Meta::CPAN
);
with 'Abilities::Features';
sub get_plan {
my ($self, $plan) = @_;
return $self->mg->{$plan};
}
around qw/features plans/ => sub {
my ($orig, $self) = @_;
return @{$self->$orig || []};
};
1;
( run in 0.311 second using v1.01-cache-2.11-cpan-4d50c553e7e )