Authorization-AccessControl
view release on metacpan or search on metacpan
lib/Authorization/AccessControl.pm view on Meta::CPAN
# ABSTRACT: Hybrid RBAC/ABAC access control
use Authorization::AccessControl::ACL;
use experimental qw(signatures);
use Exporter 'import';
our @EXPORT_OK = qw(acl);
sub acl() {
state $acl = Authorization::AccessControl::ACL->new();
$acl;
}
=head1 NAME
Authorization::AccessControl - hybrid RBAC/ABAC access control
=head1 SYNOPSIS
lib/Authorization/AccessControl/ACL.pm view on Meta::CPAN
# ABSTRACT: Access Control List of granted privileges
use Authorization::AccessControl::Grant;
use Authorization::AccessControl::Request;
use List::Util qw(any);
use Readonly;
use experimental qw(signatures);
sub new($class, %params) {
my $base = delete($params{base});
my $role = delete($params{role});
die("Unsupported params: ", join(', ', keys(%params))) if (keys(%params));
Readonly::Hash1 my %hooks => (
on_permit => [],
on_deny => []
);
Readonly::Hash1 my %data => (
_base => $base,
_role => $role,
_grants => ($base ? undef : []), # prevent privs from being saved in non-base instances
_hooks => ($base ? undef : \%hooks),
);
bless(\%data, $class);
}
sub hook($self, $type, $sub) {
push($self->_base_instance->{_hooks}->{$type}->@*, $sub);
}
sub clone($self) {
my $clone = __PACKAGE__->new();
push($clone->{_grants}->@*, $self->{_grants}->@*);
push($clone->{_hooks}->{$_}->@*, $self->{_hooks}->{$_}->@*) foreach (keys($self->{_hooks}->%*));
return $clone;
}
sub _base_instance($self) {
$self->{_base} // $self;
}
sub role($self, $role = undef) {
return __PACKAGE__->new(base => $self->_base_instance, role => $role);
}
sub grant($self, $resource, $action, $restrictions = undef) {
my $p = Authorization::AccessControl::Grant->new(
role => $self->{_role},
resource => $resource,
action => $action,
restrictions => $restrictions,
);
if (any {$p->is_equal($_)} $self->_base_instance->{_grants}->@*) {
warn("skipping duplicate grant: $p\n");
} else {
push($self->_base_instance->{_grants}->@*, $p);
}
return $self;
}
sub __contains($arr, $v) {
return 0 unless (defined($v));
any {$_ eq $v} $arr->@*;
}
sub get_grants($self, %filters) {
my @grants = $self->_base_instance->{_grants}->@*;
@grants = grep {$_->resource eq $filters{resource}} @grants if (exists($filters{resource}));
@grants = grep {$_->action eq $filters{action}} @grants if (exists($filters{action}));
@grants = grep {__contains($filters{roles}, $_->role) || !defined($_->role)} @grants if (exists($filters{roles}));
return @grants;
}
sub request($self) {
warn("Warning: Calling `roles` on the result of `role` or `grant` calls may not yield expected results\n") if ($self->{_base});
return Authorization::AccessControl::Request->new(acl => $self->_base_instance);
}
sub _event($self, $type, $ctx) {
$_->($ctx) foreach ($self->_base_instance->{_hooks}->{$type}->@*);
}
=head1 NAME
Authorization::AccessControl::ACL - Access Control List of granted privileges
=head1 SYNOPSIS
use Authorization::AccessControl::ACL;
lib/Authorization/AccessControl/Dispatch.pm view on Meta::CPAN
package Authorization::AccessControl::Dispatch 0.04;
use v5.26;
use warnings;
# ABSTRACT: Dispatch result/status appropriately following ACL request yield
use Readonly;
use experimental qw(signatures);
sub new($class, %params) {
my $granted = delete($params{granted});
$granted = !!$granted if (defined($granted)); #force into boolean/undef
my $entity = delete($params{entity});
undef($entity) unless ($granted); # ensure we don't hold the protected value if access is not granted
die("Unsupported params: ", join(', ', keys(%params))) if (keys(%params));
Readonly::Hash1 my %data => (
_granted => $granted,
_entity => $entity,
);
bless(\%data, $class);
}
sub granted($self, $sub) {
$sub->($self->{_entity}) if ($self->{_granted});
return $self;
}
sub denied($self, $sub) {
$sub->() if (defined($self->{_granted}) && !$self->{_granted});
return $self;
}
sub null($self, $sub) {
$sub->() if (!defined($self->{_granted}));
return $self;
}
sub is_granted($self) {
return ($self->{_granted} // 0) != 0;
}
=head1 NAME
Authorization::AccessControl::Dispatch - Dispatch result/status appropriately
following ACL request yield
=head1 SYNOPSIS
lib/Authorization/AccessControl/Grant.pm view on Meta::CPAN
use Data::Compare;
use Readonly;
use Scalar::Util qw(looks_like_number);
use experimental qw(signatures);
use overload
'""' => 'to_string';
sub new($class, %params) {
my $role = delete($params{role});
my $resource = delete($params{resource});
my $action = delete($params{action});
my $restrictions = delete($params{restrictions});
$restrictions = {} unless (defined($restrictions));
die("Unsupported params: ", join(', ', keys(%params))) if (keys(%params));
die("Role must be a non-empty string") if (defined($role) && (ref($role) || $role eq ''));
die("Resource is required") unless ($resource && !ref($resource));
die("Action is required") unless ($action && !ref($action));
lib/Authorization/AccessControl/Grant.pm view on Meta::CPAN
Readonly::Scalar my $data => {
_role => $role,
_resource => $resource,
_action => $action,
_restrictions => $restrictions
};
bless($data, $class);
}
sub to_string($self, @params) {
my $role = $self->{_role} ? '[' . $self->{_role} . '] ' : '';
my $restrictions = '';
foreach (keys($self->{_restrictions}->%*)) {
my $v;
if ($self->{_restrictions}->{$_}) {$v = $self->{_restrictions}->{$_}}
elsif (looks_like_number($self->{_restrictions}->{$_})) {$v = 0}
else {$v = 'false'}
$restrictions .= "$_=$v,";
}
chop($restrictions);
$role . $self->{_resource} . ' => ' . $self->{_action} . '(' . $restrictions . ')';
}
sub role($self) {
$self->{_role};
}
sub resource($self) {
$self->{_resource};
}
sub action($self) {
$self->{_action};
}
sub restrictions($self) {
$self->{_restrictions};
}
sub _satisfies_role($self, @roles) {
return 1 unless ($self->{_role});
return (grep {$_ eq $self->{_role}} @roles) > 0;
}
sub _satisfies_resource($self, $resource) {
return 0 unless (defined($resource));
return $self->{_resource} eq $resource;
}
sub _satisfies_action($self, $action) {
return 0 unless (defined($action));
return $self->{_action} eq $action;
}
sub _satisfies_restrictions($self, $attributes) {
my %attrs = $attributes->%*;
delete($attrs{$_}) foreach (grep {!exists($self->{_restrictions}->{$_})} keys(%attrs));
my $v = Compare($self->{_restrictions}, \%attrs);
return $v;
}
sub is_equal($self, $priv) {
return 0 unless (($self->role // '') eq ($priv->role // ''));
return 0 unless ($self->resource eq $priv->resource);
return 0 unless ($self->action eq $priv->action);
return 0 unless (Compare($self->restrictions, $priv->restrictions));
return 1;
}
sub accepts($self, %params) {
my ($roles, $resource, $action, $attributes) = @params{qw(roles resource action attributes)};
return 0 unless ($self->_satisfies_resource($resource));
return 0 unless ($self->_satisfies_action($action));
return 0 unless ($self->_satisfies_role(($roles // [])->@*));
return 0 unless ($self->_satisfies_restrictions($attributes // {}));
return 1;
}
=head1 NAME
lib/Authorization/AccessControl/Request.pm view on Meta::CPAN
use Scalar::Util qw(looks_like_number);
use constant true => !0;
use constant false => !1;
use experimental qw(signatures);
use overload
'""' => \&to_string;
sub new($class, %params) {
my $acl = delete($params{acl});
my $roles = delete($params{roles});
my $resource = delete($params{resource});
my $action = delete($params{action});
my $attributes = delete($params{attributes}) // {};
my $get_attrs = delete($params{get_attrs}) // undef;
die("Unsupported params: ", join(', ', keys(%params))) if (keys(%params));
die("acl is a required property") unless (defined($acl) && ref($acl) && $acl->isa('Authorization::AccessControl::ACL'));
lib/Authorization/AccessControl/Request.pm view on Meta::CPAN
_acl => $acl,
_roles => $roles,
_resource => $resource,
_action => $action,
_attributes => $attributes,
_get_attrs => $get_attrs,
};
bless($data, $class);
}
sub to_string($self, @params) {
my $roles = ($self->{_roles} // [])->@* ? '[' . join(',', ($self->{_roles} // [])->@*) . ']' : '';
my $attributes = '';
my $resource = $self->{_resource} // '{NO_RESOURCE}';
my $action = $self->{_action} // '{NO_ACTION}';
foreach (keys($self->{_attributes}->%*)) {
my $v;
if ($self->{_attributes}->{$_}) {$v = $self->{_attributes}->{$_}}
elsif (looks_like_number($self->{_attributes}->{$_})) {$v = 0}
else {$v = 'false'}
$attributes .= "$_=$v,";
}
chop($attributes);
$roles . $resource . ' => ' . $action . '(' . $attributes . ')';
}
sub __properties($self) {
(
acl => $self->{_acl},
roles => $self->{_roles},
resource => $self->{_resource},
action => $self->{_action},
attributes => $self->{_attributes},
get_attrs => $self->{_get_attrs},
)
}
sub with_roles($self, @roles) {
return __PACKAGE__->new($self->__properties, roles => [@roles],);
}
sub with_action($self, $action) {
return __PACKAGE__->new($self->__properties, action => $action,);
}
sub with_resource($self, $resource) {
return __PACKAGE__->new($self->__properties, resource => $resource,);
}
sub with_attributes($self, $attrs) {
return __PACKAGE__->new($self->__properties, attributes => {$self->{_attributes}->%*, $attrs->%*},);
}
sub with_get_attrs($self, $sub) {
return __PACKAGE__->new($self->__properties, get_attrs => $sub,);
}
sub _applicable_grants($self) {
return undef unless (defined($self->{_resource}));
return undef unless (defined($self->{_action}));
my @grants =
grep {
$_->accepts(
roles => $self->{_roles},
resource => $self->{_resource},
action => $self->{_action},
attributes => $self->{_attributes},
)
} $self->{_acl}->get_grants;
return \@grants;
}
sub precheck($self) {
my $grants = $self->_applicable_grants;
return false unless (defined($grants));
return $grants->@* > 0;
}
sub permitted($self) {
my $grants = $self->_applicable_grants;
return false unless (defined($grants));
if ($grants->@*) {
$self->{_acl}->_event(on_permit => $grants->[0]);
return true;
}
$self->{_acl}->_event(on_deny => $self);
return false;
}
sub yield($self, $get_obj) {
unless (defined($self->{_get_attrs})) {
return Authorization::AccessControl::Dispatch->new(granted => false) unless ($self->permitted);
my $obj = $get_obj->();
return Authorization::AccessControl::Dispatch->new(granted => undef) unless (defined($obj));
return Authorization::AccessControl::Dispatch->new(granted => true, entity => $obj);
}
my $obj = $get_obj->();
return Authorization::AccessControl::Dispatch->new(granted => undef) unless (defined($obj));
my $attrs = $self->{_get_attrs}->($obj);
( run in 0.296 second using v1.01-cache-2.11-cpan-65fba6d93b7 )