package SweetPea::Application::Rbac; use warnings; use strict; =head1 NAME SweetPea::Application::Rbac - Role-Based Access Control for SweetPea-Application. =cut our $VERSION = '0.001'; =head1 SYNOPSIS # Based on a common example permissions.yml --- roles: administrator: permissions: manage accounts: operations: create account update account delete account manager: permissions: manager accounts: operations: create account guests: permissions: ... from inside SweetPea::Application or a Controller; # verify user access $s->rbac->authorize($login, $password); # change user $s->rbac->subject($user_id); # verify target user has permission to perform "create account" operation $s->rbac->subject($user_id)->can('/manage accounts/create account'); # change user to default set by authenticate method $s->rbac->subject; # verify has the following role $s->rbac->role('administrator'); $s->rbac->role('guests'); $s->rbac->can('/manage accounts/delete account'); =head1 METHODS =head2 new The new method instantiates a new SweetPea::Application::Rbac object which uses Yaml (via SweetPea::Application::Config) to provide methods for retrieving authenticating and verifying system access permissions. $s->plug( 'rbac', sub { return SweetPea::Application::Rbac->new($s); }); =cut sub new { my ($class, $s) = @_; my $self = {}; bless $self, $class; $self->{base} = $s; $self->{subject}= '0'; return $self; } =head2 authorize The authorize method check whether the login and password passed to it belong to an active system user, if not report the error. $s->rbac->authorize($login, $password); =cut sub authorize { my ($self, $login, $password) = @_; my $s = $self->{base}; if ($login && $password) { my $user = $s->data->select( 'users', ['id','login','password','status'], { login => $login, password => $password } )->hash; if ($user) { if ($user->{status}) { $s->session->param('authenticated', 'true'); $s->session->param('user_id', $user->{id}); $s->session->param('user_login', $user->{login}); $s->session->param('original_user_id', $user->{id}); $s->session->param('original_user_login', $user->{login}); $s->session->flush; $self->{subject} = $user->{id}; return $self; } else { $s->error->message($s->locale->text('account_disabled')); } } else { $s->error->message($s->locale->text('no_account')); } } return 0; } =head2 authorized The authorized method check whether a user has been authenticated. if $s->rbac->authorized; =cut sub authorized { my ($self, $login, $password) = @_; my $s = $self->{base}; return $s->session->param('authenticated'); } =head2 unauthorize The unauthorize method revokes the currently authenticated users authentication status. (Kinda like a logout function) =cut sub unauthorize { my ($self, $login, $password) = @_; my $s = $self->{base}; $s->session->param('authenticated', ''); return $self; } =head2 override The override method re-authenticates as another system user while retaining the originally logged in user's credentials. Thie method is useful for applications that need to provide a means to temporarily switch accounts. $s->rbac->override($login, $password); # change back to the original user $s->rbac->override; =cut sub override { my ($self, $login, $password) = @_; my $s = $self->{base}; if ($login && $password) { my $user = $s->data->select( 'users', ['id','login','password','status'], { login => $login, password => $password } )->hash; if ($user) { if ($user->{status}) { $s->session->param('authenticated', 'true'); $s->session->param('user_id', $user->{id}); $s->session->param('user_login', $user->{login}); $s->session->flush; $self->{subject} = $user->{id}; return $self; } else { $s->error->message($s->locale->text('account_disabled')); } } else { $s->error->message($s->locale->text('no_account')); } } else { $s->session->param('authenticated', 'true'); $s->session->param('user_id', $s->session->param('original_user_id')); $s->session->param('user_login', $s->session->param('original_user_login')); $s->session->flush; $self->{subject} = $s->session->param('original_user_id'); return $self; } return 0; } =head2 subject The subject method specifies the user account permissions will be validated against using the user id pass to it, if called with no parameters the authenticated user's account will be used. $s->rbac->subject($user_id); $s->rbac->subject; =cut sub subject { my ($self, $id) = @_; my $s = $self->{base}; if ($id =~ /^\d+$/) { $self->{subject} = $id; return $self; } else { if ($s->session->param('user_id')) { $self->{subject} = $s->session->param('user_id'); return $self; } } return 0; } =head2 role The role method verifies whether the subject (target user) has the role specified. if $s->rbac->role('administrator'); =cut sub role { my ($self, $role) = @_; my $s = $self->{base}; if ($role) { my $has_role = $s->data->select( 'permissions', ['id','user','role','permission','operation'], { user => $self->{subject}, role => $role } )->rows; return $has_role; } return 0; } =head2 can The "can" method verifies whether the subject (target user) has a specific permission or has permission to perform a specific action. # check if subject (target user) has permission generally if $s->rbac->can('/manage accounts'); # check if subject (target user) has permission to perform a specific operation if $s->rbac->can('/manage accounts/create account'); =cut sub can { my ($self, $request) = @_; my $s = $self->{base}; if ($request) { $request =~ s/^\///; my ($permission, $operation) = split /\//, $request; return 0 if !$permission; my $where = {}; $where->{user} = $self->{subject}; $where->{permission} = $permission if $permission; $where->{operation} = $operation if $operation; # check that permission and operation is defined my $permissions = $s->config->get('/permissions'); my ($permission_check, $operation_check); foreach my $r (keys %{$permissions->{roles}}) { foreach my $p (keys %{$permissions->{roles}->{$r}->{permissions}}) { $permission_check++ if lc($permission) eq lc($p); if ($operation) { foreach my $o ( @{$permissions->{roles}->{$r}->{permissions} ->{$p}->{operations}} ) { $operation_check++ if lc($operation) eq lc($o); } } } } die "Permission ($permission) has not been defined." unless $permission_check; if ($operation) { die "Operation ($operation) has not been defined." unless $operation_check; } my $can = $s->data->select( 'permissions', ['id','user','role','permission','operation'], $where )->rows; return $can; } return 0; } =head1 AUTHOR Al Newkirk, C<< >> =cut 1; # End of SweetPea::Application::Rbac