[Koha-patches] [PATCH] granular permissions - C4::Auth

Galen Charlton galen.charlton at liblime.com
Thu Apr 10 18:37:43 CEST 2008


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.
---
 C4/Auth.pm |  226 +++++++++++++++++++++++++++++++++++++++++++-----------------
 1 files changed, 162 insertions(+), 64 deletions(-)

diff --git a/C4/Auth.pm b/C4/Auth.pm
index ba7f7dd..ec0fe59 100755
--- a/C4/Auth.pm
+++ b/C4/Auth.pm
@@ -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 ( $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 (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->{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.
-- 
1.5.5.rc0.16.g02b00




More information about the Koha-patches mailing list