granular permissions - C4::Auth

Enhanced the permission-checking functions in C4::Auth
(e.g., get_template_and_user, checkauth, check_api_auth, etc.).

If the CheckSpecificUserPermissions syspref is OFF, the behavior
of those APIs is unchanged.

If CheckSpecificUserPermissions is ON, then the value of each
key in the flagsrequired hash is considered during
the permissions check:

  1. if the value is 1, the staff user must have
     all privileges for the userflags flag (or module)
     referred to by the hash key, e.g.,

     { flaqsrequired => { tools => 1 } }

     In terms of the database, this means that the
     corresponding bit must be set in borrowers.flags.

  2. If the value is '*', the staff user must
     have at least one of the permissions for the
     userflag/module, but it doesn't matter which one.

     In terms of the database, this means ether that the
     corresponding bit must be set in borrowers.flags or
     that there at least is one row in user_permissions
     for the staff user and bit/module combination.

  3. If the value is any other string, it must be
     a permissions code defined in the permissions table.
     The staff user must have that specific permission
     or have access to all functions of the module

     In terms of the database, this means ether that the
     corresponding bit must be set in borrowers.flags or
     that there is a matching row in user_permissions
     for the staff user, bit/module, subpermission
     code combination.

In addition, get_template_and_user is modified so that the
CAN_user_XXX variables that it sets also includes the
subpermissions available (CAN_user_XXX_YYY, e.g.,
CAN_user_tools_import_patrons).  The template variables
for the specific permissions are set regardless of whether
CheckSpecificUserPermissions is ON or OFF so that the templates
don't have to test for that syspref explicitly.

In addition, the meaning of CAN_user_XXX has changed slightly -
CAN_user_tools, for example, is set to 1 in the template if the
user has access to *any* of the tools functions.  This was done
to simply the logic for deciding whether to display a menu
item in the staff interface are not.  This does mean that
when specific subpermissions are added to (say) the circulate
module, each use of CAN_user_circulate will need to be examined
to see if the intent is to allow the user to get at a circ
menu or page or if the user really should be required to have
all circulate functions.

Signed-off-by: Joshua Ferraro <jmf@liblime.com>
This commit is contained in:
Galen Charlton 2008-04-10 11:37:43 -05:00 committed by Joshua Ferraro
parent cafaa26b45
commit 70d33a82bb
1 changed files with 162 additions and 64 deletions

View File

@ -41,7 +41,8 @@ BEGIN {
$debug = $ENV{DEBUG} || 0 ;
@ISA = qw(Exporter);
@EXPORT = qw(&checkauth &get_template_and_user);
@EXPORT_OK = qw(&check_api_auth &get_session &check_cookie_auth &checkpw);
@EXPORT_OK = qw(&check_api_auth &get_session &check_cookie_auth &checkpw &get_all_subpermissions &get_user_subpermissions);
%EXPORT_TAGS = (EditPermissions => [qw(get_all_subpermissions get_user_subpermissions)]);
$ldap = C4::Context->config('useldapserver') || 0;
if ($ldap) {
require C4::Auth_with_ldap; # no import
@ -67,7 +68,7 @@ C4::Auth - Authenticates Koha users
query => $query,
type => "opac",
authnotrequired => 1,
flagsrequired => {borrow => 1},
flagsrequired => {borrow => 1, catalogue => '*', tools => 'import_patrons' },
}
);
@ -97,7 +98,7 @@ C4::Auth - Authenticates Koha users
query => $query,
type => "opac",
authnotrequired => 1,
flagsrequired => {borrow => 1},
flagsrequired => {borrow => 1, catalogue => '*', tools => 'import_patrons' },
}
);
@ -158,6 +159,8 @@ sub get_template_and_user {
my @bordat;
$bordat[0] = $borr;
$template->param( "USER_INFO" => \@bordat );
my $all_perms = get_all_subpermissions();
my @flagroots = qw(circulate catalogue parameters borrowers permissions reserveforothers borrow
editcatalogue updatecharges management tools editauthorities serials reports);
@ -169,7 +172,7 @@ sub get_template_and_user {
$template->param( CAN_user_catalogue => 1 );
$template->param( CAN_user_parameters => 1 );
$template->param( CAN_user_borrowers => 1 );
$template->param( CAN_user_permission => 1 );
$template->param( CAN_user_permissions => 1 );
$template->param( CAN_user_reserveforothers => 1 );
$template->param( CAN_user_borrow => 1 );
$template->param( CAN_user_editcatalogue => 1 );
@ -181,68 +184,47 @@ sub get_template_and_user {
$template->param( CAN_user_serials => 1 );
$template->param( CAN_user_reports => 1 );
$template->param( CAN_user_staffaccess => 1 );
foreach my $module (keys %$all_perms) {
foreach my $subperm (keys %{ $all_perms->{$module} }) {
$template->param( "CAN_user_${module}_${subperm}" => 1 );
}
}
}
if ( $flags && $flags->{circulate} == 1 ) {
$template->param( CAN_user_circulate => 1 );
if (C4::Context->preference('CheckSpecificUserPermissions')) {
if ( $flags ) {
foreach my $module (keys %$all_perms) {
if ( $flags->{$module} == 1) {
foreach my $subperm (keys %{ $all_perms->{$module} }) {
$template->param( "CAN_user_${module}_${subperm}" => 1 );
}
} elsif ( ref($flags->{$module}) ) {
foreach my $subperm (keys %{ $flags->{$module} } ) {
$template->param( "CAN_user_${module}_${subperm}" => 1 );
}
}
}
}
} else {
foreach my $module (keys %$all_perms) {
foreach my $subperm (keys %{ $all_perms->{$module} }) {
$template->param( "CAN_user_${module}_${subperm}" => 1 );
}
}
}
if ( $flags && $flags->{catalogue} == 1 ) {
$template->param( CAN_user_catalogue => 1 );
}
if ( $flags && $flags->{parameters} == 1 ) {
$template->param( CAN_user_parameters => 1 );
$template->param( CAN_user_management => 1 );
}
if ( $flags && $flags->{borrowers} == 1 ) {
$template->param( CAN_user_borrowers => 1 );
}
if ( $flags && $flags->{permissions} == 1 ) {
$template->param( CAN_user_permission => 1 );
}
if ( $flags && $flags->{reserveforothers} == 1 ) {
$template->param( CAN_user_reserveforothers => 1 );
}
if ( $flags && $flags->{borrow} == 1 ) {
$template->param( CAN_user_borrow => 1 );
}
if ( $flags && $flags->{editcatalogue} == 1 ) {
$template->param( CAN_user_editcatalogue => 1 );
}
if ( $flags && $flags->{updatecharges} == 1 ) {
$template->param( CAN_user_updatecharges => 1 );
}
if ( $flags && $flags->{acquisition} == 1 ) {
$template->param( CAN_user_acquisition => 1 );
}
if ( $flags && $flags->{tools} == 1 ) {
$template->param( CAN_user_tools => 1 );
}
if ( $flags && $flags->{editauthorities} == 1 ) {
$template->param( CAN_user_editauthorities => 1 );
}
if ( $flags && $flags->{serials} == 1 ) {
$template->param( CAN_user_serials => 1 );
}
if ( $flags && $flags->{reports} == 1 ) {
$template->param( CAN_user_reports => 1 );
}
if ( $flags && $flags->{staffaccess} == 1 ) {
$template->param( CAN_user_staffaccess => 1 );
if ($flags) {
foreach my $module (keys %$flags) {
if ( $flags->{$module} == 1 or ref($flags->{$module}) ) {
$template->param( "CAN_user_$module" => 1 );
if ($module eq "parameters") {
$template->param( CAN_user_management => 1 );
}
}
}
}
}
if ( $in->{'type'} eq "intranet" ) {
$template->param(
intranetcolorstylesheet => C4::Context->preference("intranetcolorstylesheet"),
@ -375,6 +357,25 @@ that the user must have the "circulate" privilege in order to
proceed. To make sure that access control is correct, the
C<$flagsrequired> parameter must be specified correctly.
If the CheckSpecificUserPermissions system preference is ON, the
value of each key in the C<flagsrequired> hash takes on an additional
meaning, e.g.,
=item 1
The user must have access to all subfunctions of the module
specified by the hash key.
=item *
The user must have access to at least one subfunction of the module
specified by the hash key.
=item specific permission, e.g., 'export_catalog'
The user must have access to the specific subfunction list, which
must correspond to a row in the permissions table.
The C<$type> argument specifies whether the template should be
retrieved from the opac or intranet directory tree. "opac" is
assumed if it is not specified; however, if C<$type> is specified,
@ -1197,6 +1198,7 @@ C<$authflags> is a hashref of permissions
sub getuserflags {
my $flags = shift;
my $userid = shift;
my $dbh = shift;
my $userflags;
$flags = 0 unless $flags;
@ -1211,15 +1213,101 @@ sub getuserflags {
$userflags->{$flag} = 0;
}
}
# get subpermissions and merge with top-level permissions
my $user_subperms = get_user_subpermissions($userid);
foreach my $module (keys %$user_subperms) {
next if $userflags->{$module} == 1; # user already has permission for everything in this module
$userflags->{$module} = $user_subperms->{$module};
}
return $userflags;
}
=item get_user_subpermissions
=over 4
my $user_perm_hashref = get_user_subpermissions($userid);
=back
Given the userid (note, not the borrowernumber) of a staff user,
return a hashref of hashrefs of the specific subpermissions
accorded to the user. An example return is
{
tools => {
export_catalog => 1,
import_patrons => 1,
}
}
The top-level hash-key is a module or function code from
userflags.flag, while the second-level key is a code
from permissions.
The results of this function do not give a complete picture
of the functions that a staff user can access; it is also
necessary to check borrowers.flags.
=cut
sub get_user_subpermissions {
my $userid = shift;
my $dbh = C4::Context->dbh;
my $sth = $dbh->prepare("SELECT flag, code
FROM user_permissions
JOIN permissions USING (module_bit, code)
JOIN userflags ON (module_bit = bit)
JOIN borrowers USING (borrowernumber)
WHERE userid = ?");
$sth->execute($userid);
my $user_perms = {};
while (my $perm = $sth->fetchrow_hashref) {
$user_perms->{$perm->{'flag'}}->{$perm->{'code'}} = 1;
}
return $user_perms;
}
=item get_all_subpermissions
=over 4
my $perm_hashref = get_all_subpermissions();
=back
Returns a hashref of hashrefs defining all specific
permissions currently defined. The return value
has the same structure as that of C<get_user_subpermissions>,
except that the innermost hash value is the description
of the subpermission.
=cut
sub get_all_subpermissions {
my $dbh = C4::Context->dbh;
my $sth = $dbh->prepare("SELECT flag, code, description
FROM permissions
JOIN userflags ON (module_bit = bit)");
$sth->execute();
my $all_perms = {};
while (my $perm = $sth->fetchrow_hashref) {
$all_perms->{$perm->{'flag'}}->{$perm->{'code'}} = $perm->{'description'};
}
return $all_perms;
}
=item haspermission
$flags = ($dbh,$member,$flagsrequired);
C<$member> may be either userid or overloaded with $borrower hashref from GetMemberDetails.
C<$flags> is a hashref of required flags lik C<$borrower-&lt;{authflags}>
C<$flags> is a hashref of required flags like C<$borrower-&lt;{authflags}>
Returns member's flags or 0 if a permission is not met.
@ -1235,7 +1323,7 @@ sub haspermission {
my $sth = $dbh->prepare("SELECT flags FROM borrowers WHERE userid=?");
$sth->execute($userid);
my ($intflags) = $sth->fetchrow;
$flags = getuserflags( $intflags, $dbh );
$flags = getuserflags( $intflags, $userid, $dbh );
}
if ( $userid eq C4::Context->config('user') ) {
# Super User Account from /etc/koha.conf
@ -1246,8 +1334,18 @@ sub haspermission {
$flags->{'superlibrarian'} = 1;
}
return $flags if $flags->{superlibrarian};
foreach ( keys %$flagsrequired ) {
return 0 unless( $flags->{$_} );
foreach my $module ( keys %$flagsrequired ) {
if (C4::Context->preference('CheckSpecificUserPermissions')) {
my $subperm = $flagsrequired->{$module};
if ($subperm eq '*') {
return 0 unless ( $flags->{$module} == 1 or ref($flags->{$module}) );
} else {
return 0 unless ( $flags->{$module} == 1 or
( exists $flags->{$module}->{$subperm} and $flags->{$module}->{$subperm} == 1 ) );
}
} else {
return 0 unless ( $flags->{$module} );
}
}
return $flags;
#FIXME - This fcn should return the failed permission so a suitable error msg can be delivered.