@@ -, +, @@ better manage permissions for testing. => a module which consists of specific Permissions. Typically covers one Module in Koha. => a specific Permission of a Module. Granting a Borrower the permission to do something. => A specific Permission a specific Borrower has, eg. the permission to edit bibliographic records. -2. Note the permissions a bunch of borrowers has. -1. run updatedatabase.pl which didn't have any subpermissions, a generic subpermission is added and the borrowers should have it if they had the existing module permission. and login persists on other pages. Save. which doesn't have any subpermissions. --- C4/Auth.pm | 59 ++- C4/Members.pm | 11 +- Koha/Auth/BorrowerPermission.pm | 221 +++++++++ Koha/Auth/BorrowerPermissions.pm | 61 +++ Koha/Auth/Permission.pm | 60 +++ Koha/Auth/PermissionManager.pm | 524 +++++++++++++++++++++ Koha/Auth/PermissionModule.pm | 78 +++ Koha/Auth/PermissionModules.pm | 64 +++ Koha/Auth/Permissions.pm | 67 +++ Koha/Schema/Result/Borrower.pm | 19 +- installer/data/mysql/en/mandatory/userflags.sql | 40 +- .../data/mysql/en/mandatory/userpermissions.sql | 159 ++++--- installer/data/mysql/kohastructure.sql | 92 ++-- installer/data/mysql/updatedatabase.pl | 146 ++++++ .../prog/en/modules/members/member-flags.tt | 14 +- members/member-flags.pl | 198 ++++---- misc/devel/interactiveWebDriverShell.pl | 6 +- t/db_dependent/Koha/Auth/BorrowerPermission.t | 102 ++++ t/db_dependent/Koha/Auth/PermissionManager.t | 221 +++++++++ t/db_dependent/Members/member-flags.t | 100 ++++ t/lib/Page/Members/MemberFlags.pm | 125 +++++ 21 files changed, 2087 insertions(+), 280 deletions(-) create mode 100644 Koha/Auth/BorrowerPermission.pm create mode 100644 Koha/Auth/BorrowerPermissions.pm create mode 100644 Koha/Auth/Permission.pm create mode 100644 Koha/Auth/PermissionManager.pm create mode 100644 Koha/Auth/PermissionModule.pm create mode 100644 Koha/Auth/PermissionModules.pm create mode 100644 Koha/Auth/Permissions.pm create mode 100644 t/db_dependent/Koha/Auth/BorrowerPermission.t create mode 100644 t/db_dependent/Koha/Auth/PermissionManager.t create mode 100644 t/db_dependent/Members/member-flags.t create mode 100644 t/lib/Page/Members/MemberFlags.pm --- a/C4/Auth.pm +++ a/C4/Auth.pm @@ -23,6 +23,8 @@ use Digest::MD5 qw(md5_base64); use JSON qw/encode_json/; use URI::Escape; use CGI::Session; +use Scalar::Util qw(blessed); +use Try::Tiny; require Exporter; use C4::Context; @@ -226,7 +228,7 @@ sub get_template_and_user { # We are going to use the $flags returned by checkauth # to create the template's parameters that will indicate # which menus the user can access. - if ( $flags && $flags->{superlibrarian} == 1 ) { + if ( $flags && $flags->{superlibrarian} ) { $template->param( CAN_user_circulate => 1 ); $template->param( CAN_user_catalogue => 1 ); $template->param( CAN_user_parameters => 1 ); @@ -1801,6 +1803,7 @@ sub checkpw_hash { } =head2 getuserflags +@DEPRECATED, USE THE Koha::Auth::PermissionManager my $authflags = getuserflags($flags, $userid, [$dbh]); @@ -1813,6 +1816,7 @@ C<$authflags> is a hashref of permissions =cut sub getuserflags { + #@DEPRECATED, USE THE Koha::Auth::PermissionManager my $flags = shift; my $userid = shift; my $dbh = @_ ? shift : C4::Context->dbh; @@ -1824,6 +1828,9 @@ sub getuserflags { no warnings 'numeric'; $flags += 0; } + return get_user_subpermissions($userid); + + #@DEPRECATED, USE THE Koha::Auth::PermissionManager my $sth = $dbh->prepare("SELECT bit, flag, defaulton FROM userflags"); $sth->execute; @@ -1847,7 +1854,7 @@ sub getuserflags { } =head2 get_user_subpermissions - +@DEPRECATED, USE THE Koha::Auth::PermissionManager $user_perm_hashref = get_user_subpermissions($userid); Given the userid (note, not the borrowernumber) of a staff user, @@ -1872,25 +1879,22 @@ necessary to check borrowers.flags. =cut sub get_user_subpermissions { + #@DEPRECATED, USE THE Koha::Auth::PermissionManager my $userid = shift; - my $dbh = C4::Context->dbh; - my $sth = $dbh->prepare( "SELECT flag, user_permissions.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); - + use Koha::Auth::PermissionManager; + my $permissionManager = Koha::Auth::PermissionManager->new(); + my $borrowerPermissions = $permissionManager->getBorrowerPermissions($userid); #Prefetch all related tables. my $user_perms = {}; - while ( my $perm = $sth->fetchrow_hashref ) { - $user_perms->{ $perm->{'flag'} }->{ $perm->{'code'} } = 1; + foreach my $perm ( @$borrowerPermissions ) { + $user_perms->{ $perm->getPermissionModule->module }->{ $perm->getPermission->code } = 1; } + return $user_perms; } =head2 get_all_subpermissions +@DEPRECATED, USE THE Koha::Auth::PermissionManager my $perm_hashref = get_all_subpermissions(); @@ -1903,6 +1907,20 @@ of the subpermission. =cut sub get_all_subpermissions { + #@DEPRECATED, USE THE Koha::Auth::PermissionManager + use Koha::Auth::PermissionManager; + my $permissionManager = Koha::Auth::PermissionManager->new(); + my $all_permissions = $permissionManager->listKohaPermissionsAsHASH(); + foreach my $module ( keys %$all_permissions ) { + my $permissionModule = $all_permissions->{$module}; + foreach my $code (keys %{$permissionModule->{permissions}}) { + my $permission = $permissionModule->{permissions}->{$code}; + $all_permissions->{$module}->{$code} = $permission->{'description'}; + } + } + return $all_permissions; + + #@DEPRECATED, USE THE Koha::Auth::PermissionManager my $dbh = C4::Context->dbh; my $sth = $dbh->prepare( "SELECT flag, code, description FROM permissions @@ -1917,6 +1935,7 @@ sub get_all_subpermissions { } =head2 haspermission +@DEPRECATED, USE THE Koha::Auth::PermissionManager $flags = ($userid, $flagsrequired); @@ -1928,11 +1947,15 @@ Returns member's flags or 0 if a permission is not met. =cut sub haspermission { + #@DEPRECATED, USE THE Koha::Auth::PermissionManager my ( $userid, $flagsrequired ) = @_; - my $sth = C4::Context->dbh->prepare("SELECT flags FROM borrowers WHERE userid=?"); - $sth->execute($userid); - my $row = $sth->fetchrow(); - my $flags = getuserflags( $row, $userid ); + + my $flags = getuserflags( undef, $userid ); + #Sanitate 1 to * because we no longer have 1's from the koha.borrowers.flags. + foreach my $module (%$flagsrequired) { + $flagsrequired->{$module} = '*' if $flagsrequired->{$module} && $flagsrequired->{$module} eq '1'; + } + if ( $userid eq C4::Context->config('user') ) { # Super User Account from /etc/koha.conf @@ -1949,7 +1972,7 @@ sub haspermission { foreach my $module ( keys %$flagsrequired ) { my $subperm = $flagsrequired->{$module}; if ( $subperm eq '*' ) { - return 0 unless ( $flags->{$module} == 1 or ref( $flags->{$module} ) ); + return 0 unless ( ref( $flags->{$module} ) ); } else { return 0 unless ( ( defined $flags->{$module} and --- a/C4/Members.pm +++ a/C4/Members.pm @@ -230,17 +230,8 @@ sub GetMemberDetails { $borrower->{'amountoutstanding'} = $amount; # FIXME - patronflags calls GetMemberAccountRecords... just have patronflags return $amount my $flags = patronflags( $borrower); - my $accessflagshash; - $sth = $dbh->prepare("select bit,flag from userflags"); - $sth->execute; - while ( my ( $bit, $flag ) = $sth->fetchrow ) { - if ( $borrower->{'flags'} && $borrower->{'flags'} & 2**$bit ) { - $accessflagshash->{$flag} = 1; - } - } - $borrower->{'flags'} = $flags; - $borrower->{'authflags'} = $accessflagshash; + $borrower->{'flags'} = $flags; #Is this the flags-column? @DEPRECATED! # For the purposes of making templates easier, we'll define a # 'showname' which is the alternate form the user's first name if --- a/Koha/Auth/BorrowerPermission.pm +++ a/Koha/Auth/BorrowerPermission.pm @@ -0,0 +1,221 @@ +package Koha::Auth::BorrowerPermission; + +# Copyright 2015 Vaara-kirjastot +# +# This file is part of Koha. +# +# Koha is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# Koha is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Koha; if not, see . + +use Modern::Perl; +use Scalar::Util qw(blessed); + +use Koha::Auth::BorrowerPermissions; +use Koha::Auth::PermissionModules; +use Koha::Auth::Permissions; +use Koha::Borrowers; + +use Koha::Exception::BadParameter; + +use base qw(Koha::Object); + +sub type { + return 'BorrowerPermission'; +} +sub object_class { + return 'Koha::Auth::BorrowerPermission'; +} + +=head NAME + +Koha::Auth::BorrowerPermission + +=head SYNOPSIS + +Object representation of a Permission given to a Borrower. + +=head new + + my $borrowerPermission = Koha::Auth::BorrowerPermission->new({ + borrowernumber => 12, + permission_module_id => 2, + permission => $Koha::Auth::Permission, + }); + my $borrowerPermission = Koha::Auth::BorrowerPermission->new({ + borrower => $Koha::Borrower, + permissionModule => $Koha::Auth::PermissionModule, + permission_id => 22, + }); + +Remember to ->store() the returned object to persist it in the DB. +@PARAM1 HASHRef of constructor parameters: + MANDATORY keys: + borrower or borrowernumber + permissionModule or permission_module_id + permission or permission_id + Values can be either Koha::Object derivatives or their respective DB primary keys +@RETURNS Koha::Auth::BorrowerPermission +=cut + +sub new { + my ($class, $params) = @_; + + _validateParams($params); + + #Check for duplicates, and update existing permission if available. + my $self = Koha::Auth::BorrowerPermissions->find({borrowernumber => $params->{borrower}->borrowernumber, + permission_module_id => $params->{permissionModule}->permission_module_id, + permission_id => $params->{permission}->permission_id, + }); + $self = $class->SUPER::new() unless $self; + $self->{params} = $params; + $self->set({borrowernumber => $self->getBorrower()->borrowernumber, + permission_id => $self->getPermission()->permission_id, + permission_module_id => $self->getPermissionModule()->permission_module_id + }); + return $self; +} + +=head getBorrower + + my $borrower = $borrowerPermission->getBorrower(); + +@RETURNS Koha::Borrower +=cut + +sub getBorrower { + my ($self) = @_; + + unless ($self->{params}->{borrower}) { + my $dbix_borrower = $self->_result()->borrower; + my $borrower = Koha::Borrower->_new_from_dbic($dbix_borrower); + $self->{params}->{borrower} = $borrower; + } + return $self->{params}->{borrower}; +} + +=head setBorrower + + my $borrowerPermission = $borrowerPermission->setBorrower( $borrower ); + +Set the Borrower. +When setting the DB is automatically updated as well. +@PARAM1 Koha::Borrower, set the given Borrower to this BorrowerPermission. +@RETURNS Koha::Auth::BorrowerPermission, +=cut + +sub setBorrower { + my ($self, $borrower) = @_; + + unless (blessed($borrower) && $borrower->isa('Koha::Borrower')) { + Koha::Exception::BadParameter->throw(error => __PACKAGE__."->setPermissionModule():> Given parameter '\\$borrower' is not a Koha::Borrower-object!"); + } + $self->{params}->{borrower} = $borrower; + $self->set({borrowernumber => $borrower->borrowernumber()}); + $self->store(); +} + +=head getPermissionModule + + my $permissionModule = $borrowerPermission->getPermissionModule(); + +@RETURNS Koha::Auth::PermissionModule +=cut + +sub getPermissionModule { + my ($self) = @_; + + unless ($self->{params}->{permissionModule}) { + my $dbix_object = $self->_result()->permission_module; + my $object = Koha::Auth::PermissionModule->_new_from_dbic($dbix_object); + $self->{params}->{permissionModule} = $object; + } + return $self->{params}->{permissionModule}; +} + +=head setPermissionModule + + my $borrowerPermission = $borrowerPermission->setPermissionModule( $permissionModule ); + +Set the PermissionModule. +When setting the DB is automatically updated as well. +@PARAM1 Koha::Auth::PermissionModule, set the given PermissionModule as + the PermissionModule of this BorrowePermission. +@RETURNS Koha::Auth::BorrowerPermission, +=cut + +sub setPermissionModule { + my ($self, $permissionModule) = @_; + + unless (blessed($permissionModule) && $permissionModule->isa('Koha::Auth::PermissionModule')) { + Koha::Exception::BadParameter->throw(error => __PACKAGE__."->setPermissionModule():> Given parameter '\$permissionModule' is not a Koha::Auth::PermissionModule-object!"); + } + $self->{params}->{permissionModule} = $permissionModule; + $self->set({permission_module_id => $permissionModule->permission_module_id()}); + $self->store(); +} + +=head getPermission + + my $permission = $borrowerPermission->getPermission(); + +@RETURNS Koha::Auth::Permission +=cut + +sub getPermission { + my ($self) = @_; + + unless ($self->{params}->{permission}) { + my $dbix_object = $self->_result()->permission; + my $object = Koha::Auth::Permission->_new_from_dbic($dbix_object); + $self->{params}->{permission} = $object; + } + return $self->{params}->{permission}; +} + +=head setPermission + + my $borrowerPermission = $borrowerPermission->setPermission( $permission ); + +Set the Permission. +When setting the DB is automatically updated as well. +@PARAM1 Koha::Auth::Permission, set the given Permission to this BorrowerPermission. +@RETURNS Koha::Auth::BorrowerPermission, +=cut + +sub setPermission { + my ($self, $permission) = @_; + + unless (blessed($permission) && $permission->isa('Koha::Auth::Permission')) { + Koha::Exception::BadParameter->throw(error => __PACKAGE__."->setPermission():> Given parameter '\$permission' is not a Koha::Auth::Permission-object!"); + } + $self->{params}->{permission} = $permission; + $self->set({permission_id => $permission->permission_id()}); + $self->store(); +} + +=head _validateParams + +Validates the given constructor parameters and fetches the Koha::Objects when needed. + +=cut + +sub _validateParams { + my ($params) = @_; + + $params->{permissionModule} = Koha::Auth::PermissionModules::castToPermissionModule( $params->{permission_module_id} || $params->{permissionModule} ); + $params->{permission} = Koha::Auth::Permissions::castToPermission( $params->{permission_id} || $params->{permission} ); + $params->{borrower} = Koha::Borrowers::castToBorrower( $params->{borrowernumber} || $params->{borrower} ); +} + +1; --- a/Koha/Auth/BorrowerPermissions.pm +++ a/Koha/Auth/BorrowerPermissions.pm @@ -0,0 +1,61 @@ +package Koha::Auth::BorrowerPermissions; + +# Copyright 2015 Vaara-kirjastot +# +# This file is part of Koha. +# +# Koha is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# Koha is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Koha; if not, see . + +use Modern::Perl; +use Scalar::Util qw(blessed); + +use Koha::Auth::BorrowerPermission; + +use base qw(Koha::Objects); + +sub type { + return 'BorrowerPermission'; +} +sub object_class { + return 'Koha::Auth::BorrowerPermission'; +} + +sub castToBorrowerPermission { + my ($input) = @_; + + unless ($input) { + Koha::Exception::BadParameter->throw(error => __PACKAGE__."::castToBorrowerPermission():> No parameter given!"); + } + if (blessed($input) && $input->isa('Koha::Auth::BorrowerPermission')) { + return $input; + } + if (blessed($input) && $input->isa('Koha::Schema::Result::BorrowerPermission')) { + return Koha::Auth::BorrowerPermission->_new_from_dbic($input); + } + + my $permission; + if (not(ref($input))) { #We have a scalar + $permission = Koha::Auth::BorrowerPermissions->find({borrower_permission_id => $input}); + unless ($permission) { + Koha::Exception::UnknownObject->throw(error => __PACKAGE__."::castToBorrowerPermission():> Cannot find an existing BorrowerPermission with borrower_permission_id '$input'."); + } + } + unless ($permission) { + Koha::Exception::BadParameter->throw(error => __PACKAGE__."::castToBorrowerPermission():> Cannot cast \$input '$input' to a BorrowerPermission-object."); + } + + return $permission; +} + +1; --- a/Koha/Auth/Permission.pm +++ a/Koha/Auth/Permission.pm @@ -0,0 +1,60 @@ +package Koha::Auth::Permission; + +# Copyright 2015 Vaara-kirjastot +# +# This file is part of Koha. +# +# Koha is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# Koha is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Koha; if not, see . + +use Modern::Perl; + +use Koha::Auth::Permissions; + +use Koha::Exception::BadParameter; + +use base qw(Koha::Object); + +sub type { + return 'Permissions'; +} +sub object_class { + return 'Koha::Auth::Permission'; +} + +sub new { + my ($class, $params) = @_; + + _validateParams($params); + + my $self = Koha::Auth::Permissions->find({code => $params->{code}, module => $params->{module}}); + $self = $class->SUPER::new() unless $self; + $self->set($params); + return $self; +} + +sub _validateParams { + my ($params) = @_; + + unless ($params->{description} && length $params->{description} > 0) { + Koha::Exception::BadParameter->throw(error => "Koha::Auth::Permission->new():> Parameter 'description' isn't defined or is empty."); + } + unless ($params->{module} && length $params->{module} > 0) { + Koha::Exception::BadParameter->throw(error => "Koha::Auth::Permission->new():> Parameter 'module' isn't defined or is empty."); + } + unless ($params->{code} && length $params->{code} > 0) { + Koha::Exception::BadParameter->throw(error => "Koha::Auth::Permission->new():> Parameter 'code' isn't defined or is empty."); + } +} + +1; --- a/Koha/Auth/PermissionManager.pm +++ a/Koha/Auth/PermissionManager.pm @@ -0,0 +1,524 @@ +package Koha::Auth::PermissionManager; + +# Copyright 2015 Vaara-kirjastot +# +# This file is part of Koha. +# +# Koha is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# Koha is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Koha; if not, see . + +use Modern::Perl; +use Scalar::Util qw(blessed); +use Try::Tiny; + +use Koha::Database; +use Koha::Auth::Permission; +use Koha::Auth::PermissionModule; +use Koha::Auth::BorrowerPermission; + +use Koha::Exception::BadParameter; +use Koha::Exception::NoPermission; +use Koha::Exception::UnknownProgramState; + +=head NAME Koha::Auth::PermissionManager + +=head SYNOPSIS + +PermissionManager is a gateway to all Koha's permission operations. You shouldn't +need to touch individual Koha::Auth::Permission* -objects. + +=head USAGE + +See t::db_dependent::Koha::Auth::PermissionManager.t + +=head new + + my $permissionManager = Koha::Auth::PermissionManager->new(); + +Instantiates a new PemissionManager. + +In the future this Manager can easily be improved with Koha::Cache. + +=cut + +sub new { + my ($class, $self) = @_; + $self = {} unless $self; + bless($self, $class); + return $self; +} + +=head addPermission + + $permissionManager->addPermission({ code => "end_remaining_hostilities", + description => "All your base are belong to us", + }); + +INSERTs or UPDATEs a Koha::Auth::Permission to the Koha DB. +Very handy when introducing new features that need new permissions. + +@PARAM1 Koha::Auth::Permission + or + HASHRef of all the koha.permissions-table columns set. +@THROWS Koha::Exception::BadParameter +=cut + +sub addPermission { + my ($self, $permission) = @_; + if (blessed($permission) && not($permission->isa('Koha::Auth::Permission'))) { + Koha::Exception::BadParameter->throw(error => __PACKAGE__."::addPermission():> Given permission is not a Koha::Auth::Permission-object."); + } + elsif (ref($permission) eq 'HASH') { + $permission = Koha::Auth::Permission->new($permission); + } + unless (blessed($permission)) { + Koha::Exception::BadParameter->throw(error => __PACKAGE__."::addPermission():> Given permission '$permission' is not of a recognized format."); + } + + $permission->store(); +} + +=head getPermission + + my $permission = $permissionManager->getPermission('edit_items'); #koha.permissions.code + my $permission = $permissionManager->getPermission(12); #koha.permissions.permission_id + my $permission = $permissionManager->getPermission($dbix_Permission); #Koha::Schema::Result::Permission + +@RETURNS Koha::Auth::Permission-object +@THROWS Koha::Exception::BadParameter +=cut + +sub getPermission { + my ($self, $permissionId) = @_; + + try { + return Koha::Auth::Permissions::castToPermission($permissionId); + } catch { + if (blessed($_) && $_->isa('Koha::Exception::UnknownObject')) { + #We catch this type of exception, and simply return nothing, since there was no such Permission + } + else { + die $_; + } + }; +} + +=head delPermission + + $permissionManager->delPermission('edit_items'); #koha.permissions.code + $permissionManager->delPermission(12); #koha.permissions.permission_id + $permissionManager->delPermission($dbix_Permission); #Koha::Schema::Result::Permission + $permissionManager->delPermission($permission); #Koha::Auth::Permission + +@THROWS Koha::Exception::UnknownObject if no given object in DB to delete. +=cut + +sub delPermission { + my ($self, $permissionId) = @_; + + my $permission = Koha::Auth::Permissions::castToPermission($permissionId); + $permission->delete(); +} + +=head addPermissionModule + + $permissionManager->addPermissionModule({ module => "scotland", + description => "William Wallace is my hero!", + }); + +INSERTs or UPDATEs a Koha::Auth::PermissionModule to the Koha DB. +Very handy when introducing new features that need new permissions. + +@PARAM1 Koha::Auth::PermissionModule + or + HASHRef of all the koha.permission_modules-table columns set. +@THROWS Koha::Exception::BadParameter +=cut + +sub addPermissionModule { + my ($self, $permissionModule) = @_; + if (blessed($permissionModule) && not($permissionModule->isa('Koha::Auth::PermissionModule'))) { + Koha::Exception::BadParameter->throw(error => __PACKAGE__."::addPermission():> Given permissionModule is not a Koha::Auth::PermissionModule-object."); + } + elsif (ref($permissionModule) eq 'HASH') { + $permissionModule = Koha::Auth::PermissionModule->new($permissionModule); + } + unless (blessed($permissionModule)) { + Koha::Exception::BadParameter->throw(error => __PACKAGE__."::addPermission():> Given permissionModule '$permissionModule' is not of a recognized format."); + } + + $permissionModule->store(); +} + +=head getPermissionModule + + my $permission = $permissionManager->getPermissionModule('cataloguing'); #koha.permission_modules.module + my $permission = $permissionManager->getPermission(12); #koha.permission_modules.permission_module_id + my $permission = $permissionManager->getPermission($dbix_Permission); #Koha::Schema::Result::PermissionModule + +@RETURNS Koha::Auth::PermissionModule-object +@THROWS Koha::Exception::BadParameter +=cut + +sub getPermissionModule { + my ($self, $permissionModuleId) = @_; + + try { + return Koha::Auth::PermissionModules::castToPermissionModule($permissionModuleId); + } catch { + if (blessed($_) && $_->isa('Koha::Exception::UnknownObject')) { + #We catch this type of exception, and simply return nothing, since there was no such PermissionModule + } + else { + die $_; + } + }; +} + +=head delPermissionModule + + $permissionManager->delPermissionModule('cataloguing'); #koha.permission_modules.module + $permissionManager->delPermissionModule(12); #koha.permission_modules.permission_module_id + $permissionManager->delPermissionModule($dbix_Permission); #Koha::Schema::Result::PermissionModule + $permissionManager->delPermissionModule($permissionModule); #Koha::Auth::PermissionModule + +@THROWS Koha::Exception::UnknownObject if no given object in DB to delete. +@THROWS Koha::Exception::BadParameter +=cut + +sub delPermissionModule { + my ($self, $permissionModuleId) = @_; + + my $permissionModule = Koha::Auth::PermissionModules::castToPermissionModule($permissionModuleId); + $permissionModule->delete(); +} + +=head getKohaPermissions + + my $kohaPermissions = $permissionManager->getKohaPermissions(); + +Gets all the PermissionModules and their related Permissions in one huge DB query. +@RETURNS ARRAYRef of Koha::Auth::PermissionModule-objects with related objects prefetched. +=cut + +sub getKohaPermissions { + my ($self) = @_; + + my $schema = Koha::Database->new()->schema(); + my @permissionModules = $schema->resultset('PermissionModule')->search( + {}, + { join => ['permissions'], + prefetch => ['permissions'], + order_by => ['me.module', 'permissions.code'], + } + ); + #Cast DBIx to Koha::Object. + for (my $i=0 ; $i { + description => "Yada yada", + module => 'acquisitions', + permission_module_id => 21, + permissions => { + budget_add_del => { + description => "More yada yada", + code => 'budget_add_del', + permission_id => 12, + } + budget_manage => { + description => "Yaawn yadayawn", + ... + } + ... + } + }, + borrowers => { + ... + }, + ... + } +=cut + +sub listKohaPermissionsAsHASH { + my ($self) = @_; + my $permissionModules = $self->getKohaPermissions(); + my $hash = {}; + + foreach my $permissionModule (sort {$a->module cmp $b->module} @$permissionModules) { + my $module = $permissionModule->module; + + $hash->{$module} = $permissionModule->_result->{'_column_data'}; + $hash->{$module}->{permissions} = {}; + + my $permissions = $permissionModule->getPermissions; + foreach my $permission (sort {$a->code cmp $b->code} @$permissions) { + my $code = $permission->code; + + $hash->{$module}->{permissions}->{$code} = $permission->_result->{'_column_data'}; + } + } + return $hash; +} + +=head getBorrowerPermissions + + my $borrowerPermissions = $permissionManager->getBorrowerPermissions($borrower); #Koha::Borrower + my $borrowerPermissions = $permissionManager->getBorrowerPermissions($dbix_borrower);#Koha::Schema::Resultset::Borrower + my $borrowerPermissions = $permissionManager->getBorrowerPermissions(1012); #koha.borrowers.borrowernumber + my $borrowerPermissions = $permissionManager->getBorrowerPermissions('167A0012311'); #koha.borrowers.cardnumber + my $borrowerPermissions = $permissionManager->getBorrowerPermissions('bill69'); #koha.borrowers.userid + +@RETURNS ARRAYRef of Koha::Auth::BorrowerPermission-objects +@THROWS Koha::Exception::UnknownObject, if the given $borrower cannot be casted to Koha::Borrower +@THROWS Koha::Exception::BadParameter +=cut + +sub getBorrowerPermissions { + my ($self, $borrower) = @_; + $borrower = Koha::Borrowers::castToBorrower($borrower); + + my $schema = Koha::Database->new()->schema(); + + my @borrowerPermissions = $schema->resultset('BorrowerPermission')->search({borrowernumber => $borrower->borrowernumber}, + {join => ['permission','permission_module'], + prefetch => ['permission','permission_module'], + order_by => ['permission_module.module', 'permission.code']}); + for (my $i=0 ; $igrantPermissions($borrower, {borrowers => 'view_borrowers', + reserveforothers => ['place_holds'], + tools => ['edit_news', 'edit_notices'], + acquisition => { + budger_add_del => 1, + budget_modify => 1, + }, + } + ); + +Adds a group of permissions to one user. +@THROWS Koha::Exception::UnknownObject, if the given $borrower cannot be casted to Koha::Borrower +@THROWS Koha::Exception::BadParameter +=cut + +sub grantPermissions { + my ($self, $borrower, $permissionsGroup) = @_; + + while (my ($module, $permissions) = each(%$permissionsGroup)) { + if (ref($permissions) eq 'ARRAY') { + foreach my $permission (@$permissions) { + $self->grantPermission($borrower, $module, $permission); + } + } + elsif (ref($permissions) eq 'HASH') { + foreach my $permission (keys(%$permissions)) { + $self->grantPermission($borrower, $module, $permission); + } + } + else { + $self->grantPermission($borrower, $module, $permissions); + } + } +} + +=head grantPermission + + my $borrowerPermission = $permissionManager->grantPermission($borrower, $permissionModule, $permission); + +@PARAM1 Koha::Borrower or + Scalar koha.borrowers.borrowernumber or + Scalar koha.borrowers.cardnumber or + Scalar koha.borrowers.userid or +@PARAM2 Koha::Auth::PermissionModule-object + Scalar koha.permission_modules.module or + Scalar koha.permission_modules.permission_module_id +@PARAM3 Koha::Auth::Permission-object or + Scalar koha.permissions.code or + Scalar koha.permissions.permission_id +@RETURNS Koha::Auth::BorrowerPermissions +@THROWS Koha::Exception::UnknownObject, if the given parameters cannot be casted to Koha::Object-subclasses +@THROWS Koha::Exception::BadParameter +=cut + +sub grantPermission { + my ($self, $borrower, $permissionModule, $permission) = @_; + + my $borrowerPermission = Koha::Auth::BorrowerPermission->new({borrower => $borrower, permissionModule => $permissionModule, permission => $permission}); + $borrowerPermission->store(); + return $borrowerPermission; +} + +=head + + $permissionManager->revokePermission($borrower, $permissionModule, $permission); + +Revokes a Permission from a Borrower +same parameters as grantPermission() + +@THROWS Koha::Exception::UnknownObject, if the given parameters cannot be casted to Koha::Object-subclasses +@THROWS Koha::Exception::BadParameter +=cut + +sub revokePermission { + my ($self, $borrower, $permissionModule, $permission) = @_; + + my $borrowerPermission = Koha::Auth::BorrowerPermission->new({borrower => $borrower, permissionModule => $permissionModule, permission => $permission}); + $borrowerPermission->delete(); + return $borrowerPermission; +} + +=head revokeAllPermissions + + $permissionManager->revokeAllPermissions($borrower); + +@THROWS Koha::Exception::UnknownObject, if the given $borrower cannot be casted to Koha::Borrower +@THROWS Koha::Exception::BadParameter +=cut + +sub revokeAllPermissions { + my ($self, $borrower) = @_; + $borrower = Koha::Borrowers::castToBorrower($borrower); + + my $schema = Koha::Database->new()->schema(); + $schema->resultset('BorrowerPermission')->search({borrowernumber => $borrower->borrowernumber})->delete_all(); +} + +=head hasPermissions + +See if the given Borrower has all of the given permissions +@PARAM1 Koha::Borrower, or any of the koha.borrowers-table's unique identifiers. +@PARAM2 HASHRef of needed permissions, + { + borrowers => 'view_borrowers', + reserveforothers => ['place_holds'], + tools => ['edit_news', 'edit_notices'], + acquisition => { + budger_add_del => 1, + budget_modify => 1, + }, + coursereserves => '*', #Means any Permission under this PermissionModule + } +@RETURNS see hasPermission() +@THROWS Koha::Exception::NoPermission, from hasPermission() if permission is missing. +=cut + +sub hasPermissions { + my ($self, $borrower, $requiredPermissions) = @_; + + foreach my $module (keys(%$requiredPermissions)) { + my $permissions = $requiredPermissions->{$module}; + if (ref($permissions) eq 'ARRAY') { + foreach my $permission (@$permissions) { + $self->hasPermission($borrower, $module, $permission); + } + } + elsif (ref($permissions) eq 'HASH') { + foreach my $permission (keys(%$permissions)) { + $self->hasPermission($borrower, $module, $permission); + } + } + else { + $self->hasPermission($borrower, $module, $permissions); + } + } + return 1; +} + +=head hasPermission + +See if the given Borrower has the given permission +@PARAM1 Koha::Borrower, or any of the koha.borrowers-table's unique identifiers. +@PARAM2 Koha::Auth::PermissionModule or koha.permission_modules.module or koha.permission_modules.permission_module_id +@PARAM3 Koha::Auth::Permission or koha.permissions.code or koha.permissions.permission_id or + '*' if we just need any permission for the given PermissionModule. +@RETURNS Integer, 1 if permission check succeeded. + 2 if user is a superlibrarian. + Catch Exceptions if permission check fails. +@THROWS Koha::Exception::NoPermission, if Borrower is missing the permission. + Exception tells which permission is missing. +@THROWS Koha::Exception::UnknownObject, if the given parameters cannot be casted to Koha::Object-subclasses +@THROWS Koha::Exception::BadParameter +=cut + +sub hasPermission { + my ($self, $borrower, $permissionModule, $permission) = @_; + + $borrower = Koha::Borrowers::castToBorrower($borrower); + $permissionModule = Koha::Auth::PermissionModules::castToPermissionModule($permissionModule); + $permission = Koha::Auth::Permissions::castToPermission($permission) unless $permission eq '*'; + + my $error; + if ($permission eq '*') { + my $borrowerPermission = Koha::Auth::BorrowerPermissions->search({borrowernumber => $borrower->borrowernumber, + permission_module_id => $permissionModule->permission_module_id, + })->next(); + return 1 if ($borrowerPermission); + $error = "Borrower '".$borrower->borrowernumber."' lacks any permission under permission module '".$permissionModule->module."'."; + } + else { + my $borrowerPermission = Koha::Auth::BorrowerPermissions->search({borrowernumber => $borrower->borrowernumber, + permission_module_id => $permissionModule->permission_module_id, + permission_id => $permission->permission_id, + })->next(); + return 1 if ($borrowerPermission); + $error = "Borrower '".$borrower->borrowernumber."' lacks permission module '".$permissionModule->module."' and permission '".$permission->code."'."; + } + + return 2 if not($permissionModule->module eq 'superlibrarian') && $self->_isSuperuser($borrower); + return 2 if not($permissionModule->module eq 'superlibrarian') && $self->_isSuperlibrarian($borrower); + Koha::Exception::NoPermission->throw(error => $error); +} + +sub _isSuperuser { + my ($self, $borrower) = @_; + $borrower = Koha::Borrowers::castToBorrower($borrower); + + if ( $borrower->userid eq C4::Context->config('user') ) { + return 1; + } + elsif ( $borrower->userid eq 'demo' && C4::Context->config('demo') ) { + return 1; + } + return 0; +} + +sub _isSuperlibrarian { + my ($self, $borrower) = @_; + + try { + return $self->hasPermission($borrower, 'superlibrarian', 'superlibrarian'); + } catch { + if (blessed($_) && $_->isa('Koha::Exception::NoPermission')) { + return 0; + } + else { + die $_; + } + }; +} + +1; --- a/Koha/Auth/PermissionModule.pm +++ a/Koha/Auth/PermissionModule.pm @@ -0,0 +1,78 @@ +package Koha::Auth::PermissionModule; + +# Copyright 2015 Vaara-kirjastot +# +# This file is part of Koha. +# +# Koha is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# Koha is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Koha; if not, see . + +use Modern::Perl; + +use Koha::Auth::PermissionModules; + +use Koha::Exception::BadParameter; + +use base qw(Koha::Object); + +sub type { + return 'PermissionModule'; +} +sub object_class { + return 'Koha::Auth::PermissionModule'; +} + +sub new { + my ($class, $params) = @_; + + _validateParams($params); + + my $self = Koha::Auth::PermissionModules->find({module => $params->{module}}); + $self = $class->SUPER::new() unless $self; + $self->set($params); + return $self; +} + +sub _validateParams { + my ($params) = @_; + + unless ($params->{description} && length $params->{description} > 0) { + Koha::Exception::BadParameter->throw(error => "Koha::Auth::Permission->new():> Parameter 'description' isn't defined or is empty."); + } + unless ($params->{module} && length $params->{module} > 0) { + Koha::Exception::BadParameter->throw(error => "Koha::Auth::Permission->new():> Parameter 'module' isn't defined or is empty."); + } +} + +=head getPermissions + + my $permissions = $permissionModule->getPermissions(); + +@RETURNS List of Koha::Auth::Permission-objects +=cut + +sub getPermissions { + my ($self) = @_; + + unless ($self->{params}->{permissions}) { + $self->{params}->{permissions} = []; + my @dbix_objects = $self->_result()->permissions; + foreach my $dbix_object (@dbix_objects) { + my $object = Koha::Auth::Permission->_new_from_dbic($dbix_object); + push @{$self->{params}->{permissions}}, $object; + } + } + return $self->{params}->{permissions}; +} + +1; --- a/Koha/Auth/PermissionModules.pm +++ a/Koha/Auth/PermissionModules.pm @@ -0,0 +1,64 @@ +package Koha::Auth::PermissionModules; + +# Copyright 2015 Vaara-kirjastot +# +# This file is part of Koha. +# +# Koha is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# Koha is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Koha; if not, see . + +use Modern::Perl; +use Scalar::Util qw(blessed); + +use Koha::Auth::PermissionModule; + +use base qw(Koha::Objects); + +sub type { + return 'PermissionModule'; +} +sub object_class { + return 'Koha::Auth::PermissionModule'; +} + +sub castToPermissionModule { + my ($input) = @_; + + unless ($input) { + Koha::Exception::BadParameter->throw(error => __PACKAGE__."::castToPermissionModule():> No parameter given!"); + } + if (blessed($input) && $input->isa('Koha::Auth::PermissionModule')) { + return $input; + } + if (blessed($input) && $input->isa('Koha::Schema::Result::PermissionModule')) { + return Koha::Auth::PermissionModule->_new_from_dbic($input); + } + + my $permissionModule; + if (not(ref($input))) { #We have a scalar + $permissionModule = Koha::Auth::PermissionModules->search({'-or' => [{permission_module_id => $input}, + {module => $input}, + ] + })->next(); + unless ($permissionModule) { + Koha::Exception::UnknownObject->throw(error => __PACKAGE__."::castToPermissionModule():> Cannot find an existing permissionModule with permission_module_id|module '$input'."); + } + } + unless ($permissionModule) { + Koha::Exception::BadParameter->throw(error => __PACKAGE__."::castToPermissionModule():> Cannot cast \$input '$input' to a PermissionModule-object."); + } + + return $permissionModule; +} + +1; --- a/Koha/Auth/Permissions.pm +++ a/Koha/Auth/Permissions.pm @@ -0,0 +1,67 @@ +package Koha::Auth::Permissions; + +# Copyright 2015 Vaara-kirjastot +# +# This file is part of Koha. +# +# Koha is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# Koha is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Koha; if not, see . + +use Modern::Perl; +use Scalar::Util qw(blessed); + +use Koha::Auth::Permission; + +use Koha::Exception::BadParameter; +use Koha::Exception::UnknownObject; + +use base qw(Koha::Objects); + +sub type { + return 'Permissions'; +} +sub object_class { + return 'Koha::Auth::Permission'; +} + +sub castToPermission { + my ($input) = @_; + + unless ($input) { + Koha::Exception::BadParameter->throw(error => __PACKAGE__."::castToPermission():> No parameter given!"); + } + if (blessed($input) && $input->isa('Koha::Auth::Permission')) { + return $input; + } + if (blessed($input) && $input->isa('Koha::Schema::Result::Permission')) { + return Koha::Auth::Permission->_new_from_dbic($input); + } + + my $permission; + if (not(ref($input))) { #We have a scalar + $permission = Koha::Auth::Permissions->search({'-or' => [{permission_id => $input}, + {code => $input}, + ] + })->next(); + unless ($permission) { + Koha::Exception::UnknownObject->throw(error => __PACKAGE__."::castToPermission():> Cannot find an existing permission with permission_id|code '$input'."); + } + } + unless ($permission) { + Koha::Exception::BadParameter->throw(error => __PACKAGE__."::castToPermission():> Cannot cast \$input '$input' to a Permission-object."); + } + + return $permission; +} + +1; --- a/Koha/Schema/Result/Borrower.pm +++ a/Koha/Schema/Result/Borrower.pm @@ -753,6 +753,21 @@ __PACKAGE__->has_many( { cascade_copy => 0, cascade_delete => 0 }, ); +=head2 borrower_permissions + +Type: has_many + +Related object: L + +=cut + +__PACKAGE__->has_many( + "borrower_permissions", + "Koha::Schema::Result::BorrowerPermission", + { "foreign.borrowernumber" => "self.borrowernumber" }, + { cascade_copy => 0, cascade_delete => 0 }, +); + =head2 borrower_syncs Type: has_many @@ -1154,8 +1169,8 @@ Composing rels: L -> ordernumber __PACKAGE__->many_to_many("ordernumbers", "aqorder_users", "ordernumber"); -# Created by DBIx::Class::Schema::Loader v0.07039 @ 2015-04-27 16:08:40 -# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:Z50zYBD3Hqlv5/EnoLnyZw +# Created by DBIx::Class::Schema::Loader v0.07039 @ 2015-07-11 12:59:56 +# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:ZRxBjZb0KKxabonVI/2vjg # You can replace this text with custom content, and it will be preserved on regeneration --- a/installer/data/mysql/en/mandatory/userflags.sql +++ a/installer/data/mysql/en/mandatory/userflags.sql @@ -1,21 +1,21 @@ -INSERT INTO userflags (bit, flag, flagdesc, defaulton) VALUES -(0,'superlibrarian','Access to all librarian functions',0), -(1,'circulate','Check out and check in items',0), -(2,'catalogue','Required for staff login. Staff access, allows viewing of catalogue in staff client.',0), -(3,'parameters','Manage Koha system settings (Administration panel)',0), -(4,'borrowers','Add or modify patrons',0), -(5,'permissions','Set user permissions',0), -(6,'reserveforothers','Place and modify holds for patrons',0), -(9,'editcatalogue','Edit catalog (Modify bibliographic/holdings data)',0), -(10,'updatecharges','Manage patrons fines and fees',0), -(11,'acquisition','Acquisition and/or suggestion management',0), -(12,'management','Set library management parameters (deprecated)',0), -(13,'tools','Use all tools (expand for granular tools permissions)',0), -(14,'editauthorities','Edit authorities',0), -(15,'serials','Manage serial subscriptions',0), -(16,'reports','Allow access to the reports module',0), -(17,'staffaccess','Allow staff members to modify permissions for other staff members',0), -(18,'coursereserves','Course reserves',0), -(19, 'plugins', 'Koha plugins', '0'), -(20, 'lists', 'Lists', 0) +INSERT INTO permission_modules (module, description) VALUES +('superlibrarian','Access to all librarian functions'), +('circulate','Check out and check in items'), +('catalogue','Required for staff login. Staff access, allows viewing of catalogue in staff client.'), +('parameters','Manage Koha system settings (Administration panel)'), +('borrowers','Add or modify patrons'), +('permissions','Set user permissions'), +('reserveforothers','Place and modify holds for patrons'), +('editcatalogue','Edit catalog (Modify bibliographic/holdings data)'), +('updatecharges','Manage patrons fines and fees'), +('acquisition','Acquisition and/or suggestion management'), +('management','Set library management parameters (deprecated)'), +('tools','Use all tools (expand for granular tools permissions)'), +('editauthorities','Edit authorities'), +('serials','Manage serial subscriptions'), +('reports','Allow access to the reports module'), +('staffaccess','Allow staff members to modify permissions for other staff members'), +('coursereserves','Course reserves'), +('plugins', 'Koha plugins'), +('lists', 'Lists') ; --- a/installer/data/mysql/en/mandatory/userpermissions.sql +++ a/installer/data/mysql/en/mandatory/userpermissions.sql @@ -1,77 +1,84 @@ -INSERT INTO permissions (module_bit, code, description) VALUES - ( 1, 'circulate_remaining_permissions', 'Remaining circulation permissions'), - ( 1, 'override_renewals', 'Override blocked renewals'), - ( 1, 'overdues_report', 'Execute overdue items report'), - ( 1, 'force_checkout', 'Force checkout if a limitation exists'), - ( 1, 'manage_restrictions', 'Manage restrictions for accounts'), - ( 3, 'parameters_remaining_permissions', 'Remaining system parameters permissions'), - ( 3, 'manage_circ_rules', 'manage circulation rules'), - ( 6, 'place_holds', 'Place holds for patrons'), - ( 6, 'modify_holds_priority', 'Modify holds priority'), - ( 9, 'edit_catalogue', 'Edit catalog (Modify bibliographic/holdings data)'), - ( 9, 'fast_cataloging', 'Fast cataloging'), - ( 9, 'edit_items', 'Edit items'), - ( 9, 'edit_items_restricted', 'Limit item modification to subfields defined in the SubfieldsToAllowForRestrictedEditing preference (please note that edit_item is still required)'), - ( 9, 'delete_all_items', 'Delete all items at once'), - (10, 'writeoff', 'Write off fines and fees'), - (10, 'remaining_permissions', 'Remaining permissions for managing fines and fees'), - (11, 'vendors_manage', 'Manage vendors'), - (11, 'contracts_manage', 'Manage contracts'), - (11, 'period_manage', 'Manage periods'), - (11, 'budget_manage', 'Manage budgets'), - (11, 'budget_modify', 'Modify budget (can''t create lines, but can modify existing ones)'), - (11, 'planning_manage', 'Manage budget plannings'), - (11, 'order_manage', 'Manage orders & basket'), - (11, 'order_manage_all', 'Manage all orders and baskets, regardless of restrictions on them'), - (11, 'group_manage', 'Manage orders & basketgroups'), - (11, 'order_receive', 'Manage orders & basket'), - (11, 'budget_add_del', 'Add and delete budgets (but can''t modify budgets)'), - (11, 'budget_manage_all', 'Manage all budgets'), - (13, 'edit_news', 'Write news for the OPAC and staff interfaces'), - (13, 'label_creator', 'Create printable labels and barcodes from catalog and patron data'), - (13, 'edit_calendar', 'Define days when the library is closed'), - (13, 'moderate_comments', 'Moderate patron comments'), - (13, 'edit_notices', 'Define notices'), - (13, 'edit_notice_status_triggers', 'Set notice/status triggers for overdue items'), - (13, 'edit_quotes', 'Edit quotes for quote-of-the-day feature'), - (13, 'view_system_logs', 'Browse the system logs'), - (13, 'inventory', 'Perform inventory (stocktaking) of your catalog'), - (13, 'stage_marc_import', 'Stage MARC records into the reservoir'), - (13, 'manage_staged_marc', 'Managed staged MARC records, including completing and reversing imports'), - (13, 'export_catalog', 'Export bibliographic and holdings data'), - (13, 'import_patrons', 'Import patron data'), - (13, 'edit_patrons', 'Perform batch modification of patrons'), - (13, 'delete_anonymize_patrons', 'Delete old borrowers and anonymize circulation history (deletes borrower reading history)'), - (13, 'batch_upload_patron_images', 'Upload patron images in a batch or one at a time'), - (13, 'schedule_tasks', 'Schedule tasks to run'), - (13, 'items_batchmod', 'Perform batch modification of items'), - (13, 'items_batchmod_restricted', 'Limit batch item modification to subfields defined in the SubfieldsToAllowForRestrictedBatchmod preference (please note that items_batchmod is still required)'), - (13, 'items_batchdel', 'Perform batch deletion of items'), - (13, 'manage_csv_profiles', 'Manage CSV export profiles'), - (13, 'moderate_tags', 'Moderate patron tags'), - (13, 'rotating_collections', 'Manage rotating collections'), - (13, 'upload_local_cover_images', 'Upload local cover images'), - (13, 'manage_patron_lists', 'Add, edit and delete patron lists and their contents'), - (13, 'records_batchmod', 'Perform batch modification of records (biblios or authorities)'), - (13, 'marc_modification_templates', 'Manage marc modification templates'), - (13, 'records_batchdel', 'Perform batch deletion of records (bibliographic or authority)'), - (15, 'check_expiration', 'Check the expiration of a serial'), - (15, 'claim_serials', 'Claim missing serials'), - (15, 'create_subscription', 'Create a new subscription'), - (15, 'delete_subscription', 'Delete an existing subscription'), - (15, 'edit_subscription', 'Edit an existing subscription'), - (15, 'receive_serials', 'Serials receiving'), - (15, 'renew_subscription', 'Renew a subscription'), - (15, 'routing', 'Routing'), - (15, 'superserials', 'Manage subscriptions from any branch (only applies when IndependentBranches is used)'), - (16, 'execute_reports', 'Execute SQL reports'), - (16, 'create_reports', 'Create SQL reports'), - (18, 'manage_courses', 'Add, edit and delete courses'), - (18, 'add_reserves', 'Add course reserves'), - (18, 'delete_reserves', 'Remove course reserves'), - (19, 'manage', 'Manage plugins ( install / uninstall )'), - (19, 'tool', 'Use tool plugins'), - (19, 'report', 'Use report plugins'), - (19, 'configure', 'Configure plugins'), - (20, 'delete_public_lists', 'Delete public lists') +INSERT INTO permissions (module, code, description) VALUES + ( 'superlibrarian', 'superlibrarian', 'Access to all librarian functions'), + ( 'circulate', 'circulate_remaining_permissions', 'Remaining circulation permissions'), + ( 'circulate', 'override_renewals', 'Override blocked renewals'), + ( 'circulate', 'overdues_report', 'Execute overdue items report'), + ( 'circulate', 'force_checkout', 'Force checkout if a limitation exists'), + ( 'circulate', 'manage_restrictions', 'Manage restrictions for accounts'), + ( 'catalogue', 'staff_login', 'Allow staff login.'), + ( 'parameters', 'parameters_remaining_permissions', 'Remaining system parameters permissions'), + ( 'parameters', 'manage_circ_rules', 'manage circulation rules'), + ( 'borrowers', 'view_borrowers', 'Show borrower details and search for borrowers.'), + ( 'permissions', 'set_permissions', 'Set user permissions'), + ( 'reserveforothers','place_holds', 'Place holds for patrons'), + ( 'reserveforothers','modify_holds_priority', 'Modify holds priority'), + ( 'editcatalogue', 'edit_catalogue', 'Edit catalog (Modify bibliographic/holdings data)'), + ( 'editcatalogue', 'fast_cataloging', 'Fast cataloging'), + ( 'editcatalogue', 'edit_items', 'Edit items'), + ( 'editcatalogue', 'edit_items_restricted', 'Limit item modification to subfields defined in the SubfieldsToAllowForRestrictedEditing preference (please note that edit_item is still required)'), + ( 'editcatalogue', 'delete_all_items', 'Delete all items at once'), + ( 'updatecharges', 'writeoff', 'Write off fines and fees'), + ( 'updatecharges', 'remaining_permissions', 'Remaining permissions for managing fines and fees'), + ( 'acquisition', 'vendors_manage', 'Manage vendors'), + ( 'acquisition', 'contracts_manage', 'Manage contracts'), + ( 'acquisition', 'period_manage', 'Manage periods'), + ( 'acquisition', 'budget_manage', 'Manage budgets'), + ( 'acquisition', 'budget_modify', 'Modify budget (can''t create lines, but can modify existing ones)'), + ( 'acquisition', 'planning_manage', 'Manage budget plannings'), + ( 'acquisition', 'order_manage', 'Manage orders & basket'), + ( 'acquisition', 'order_manage_all', 'Manage all orders and baskets, regardless of restrictions on them'), + ( 'acquisition', 'group_manage', 'Manage orders & basketgroups'), + ( 'acquisition', 'order_receive', 'Manage orders & basket'), + ( 'acquisition', 'budget_add_del', 'Add and delete budgets (but can''t modify budgets)'), + ( 'acquisition', 'budget_manage_all', 'Manage all budgets'), + ( 'management', 'management', 'Set library management parameters (deprecated)'), + ( 'tools', 'edit_news', 'Write news for the OPAC and staff interfaces'), + ( 'tools', 'label_creator', 'Create printable labels and barcodes from catalog and patron data'), + ( 'tools', 'edit_calendar', 'Define days when the library is closed'), + ( 'tools', 'moderate_comments', 'Moderate patron comments'), + ( 'tools', 'edit_notices', 'Define notices'), + ( 'tools', 'edit_notice_status_triggers', 'Set notice/status triggers for overdue items'), + ( 'tools', 'edit_quotes', 'Edit quotes for quote-of-the-day feature'), + ( 'tools', 'view_system_logs', 'Browse the system logs'), + ( 'tools', 'inventory', 'Perform inventory (stocktaking) of your catalog'), + ( 'tools', 'stage_marc_import', 'Stage MARC records into the reservoir'), + ( 'tools', 'manage_staged_marc', 'Managed staged MARC records, including completing and reversing imports'), + ( 'tools', 'export_catalog', 'Export bibliographic and holdings data'), + ( 'tools', 'import_patrons', 'Import patron data'), + ( 'tools', 'edit_patrons', 'Perform batch modification of patrons'), + ( 'tools', 'delete_anonymize_patrons', 'Delete old borrowers and anonymize circulation history (deletes borrower reading history)'), + ( 'tools', 'batch_upload_patron_images', 'Upload patron images in a batch or one at a time'), + ( 'tools', 'schedule_tasks', 'Schedule tasks to run'), + ( 'tools', 'items_batchmod', 'Perform batch modification of items'), + ( 'tools', 'items_batchmod_restricted', 'Limit batch item modification to subfields defined in the SubfieldsToAllowForRestrictedBatchmod preference (please note that items_batchmod is still required)'), + ( 'tools', 'items_batchdel', 'Perform batch deletion of items'), + ( 'tools', 'manage_csv_profiles', 'Manage CSV export profiles'), + ( 'tools', 'moderate_tags', 'Moderate patron tags'), + ( 'tools', 'rotating_collections', 'Manage rotating collections'), + ( 'tools', 'upload_local_cover_images', 'Upload local cover images'), + ( 'tools', 'manage_patron_lists', 'Add, edit and delete patron lists and their contents'), + ( 'tools', 'records_batchmod', 'Perform batch modification of records (biblios or authorities)'), + ( 'tools', 'marc_modification_templates', 'Manage marc modification templates'), + ( 'tools', 'records_batchdel', 'Perform batch deletion of records (bibliographic or authority)'), + ( 'editauthorities', 'edit_authorities', 'Edit authorities'), + ( 'serials', 'check_expiration', 'Check the expiration of a serial'), + ( 'serials', 'claim_serials', 'Claim missing serials'), + ( 'serials', 'create_subscription', 'Create a new subscription'), + ( 'serials', 'delete_subscription', 'Delete an existing subscription'), + ( 'serials', 'edit_subscription', 'Edit an existing subscription'), + ( 'serials', 'receive_serials', 'Serials receiving'), + ( 'serials', 'renew_subscription', 'Renew a subscription'), + ( 'serials', 'routing', 'Routing'), + ( 'serials', 'superserials', 'Manage subscriptions from any branch (only applies when IndependentBranches is used)'), + ( 'reports', 'execute_reports', 'Execute SQL reports'), + ( 'reports', 'create_reports', 'Create SQL reports'), + ( 'staffaccess', 'staff_access_permissions', 'Allow staff members to modify permissions for other staff members'), + ( 'coursereserves', 'manage_courses', 'Add, edit and delete courses'), + ( 'coursereserves', 'add_reserves', 'Add course reserves'), + ( 'coursereserves', 'delete_reserves', 'Remove course reserves'), + ( 'plugins', 'manage', 'Manage plugins ( install / uninstall )'), + ( 'plugins', 'tool', 'Use tool plugins'), + ( 'plugins', 'report', 'Use report plugins'), + ( 'plugins', 'configure', 'Configure plugins'), + ( 'lists', 'delete_public_lists', 'Delete public lists') ; --- a/installer/data/mysql/kohastructure.sql +++ a/installer/data/mysql/kohastructure.sql @@ -249,7 +249,6 @@ CREATE TABLE `borrowers` ( -- this table includes information about your patrons `ethnotes` varchar(255) default NULL, -- unused in Koha `sex` varchar(1) default NULL, -- patron/borrower's gender `password` varchar(60) default NULL, -- patron/borrower's encrypted password - `flags` int(11) default NULL, -- will include a number associated with the staff member's permissions `userid` varchar(75) default NULL, -- patron/borrower's opac and/or staff client log in `opacnote` mediumtext, -- a note on the patron/borrower's account that is visible in the OPAC and staff client `contactnote` varchar(255) default NULL, -- a note related to the patron/borrower's alternate address @@ -2292,19 +2291,6 @@ CREATE TABLE `tags_index` ( -- a weighted list of all tags and where they are us ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; -- --- Table structure for table `userflags` --- - -DROP TABLE IF EXISTS `userflags`; -CREATE TABLE `userflags` ( - `bit` int(11) NOT NULL default 0, - `flag` varchar(30) default NULL, - `flagdesc` varchar(255) default NULL, - `defaulton` int(11) default NULL, - PRIMARY KEY (`bit`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; - --- -- Table structure for table `virtualshelves` -- @@ -2485,20 +2471,6 @@ CREATE TABLE language_script_mapping ( ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; -- --- Table structure for table `permissions` --- - -DROP TABLE IF EXISTS `permissions`; -CREATE TABLE `permissions` ( - `module_bit` int(11) NOT NULL DEFAULT 0, - `code` varchar(64) DEFAULT NULL, - `description` varchar(255) DEFAULT NULL, - PRIMARY KEY (`module_bit`, `code`), - CONSTRAINT `permissions_ibfk_1` FOREIGN KEY (`module_bit`) REFERENCES `userflags` (`bit`) - ON DELETE CASCADE ON UPDATE CASCADE -) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; - --- -- Table structure for table `serialitems` -- @@ -2513,21 +2485,6 @@ CREATE TABLE `serialitems` ( ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; -- --- Table structure for table `user_permissions` --- - -DROP TABLE IF EXISTS `user_permissions`; -CREATE TABLE `user_permissions` ( - `borrowernumber` int(11) NOT NULL DEFAULT 0, - `module_bit` int(11) NOT NULL DEFAULT 0, - `code` varchar(64) DEFAULT NULL, - CONSTRAINT `user_permissions_ibfk_1` FOREIGN KEY (`borrowernumber`) REFERENCES `borrowers` (`borrowernumber`) - ON DELETE CASCADE ON UPDATE CASCADE, - CONSTRAINT `user_permissions_ibfk_2` FOREIGN KEY (`module_bit`, `code`) REFERENCES `permissions` (`module_bit`, `code`) - ON DELETE CASCADE ON UPDATE CASCADE -) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; - --- -- Table structure for table `tmp_holdsqueue` -- @@ -3362,6 +3319,55 @@ CREATE TABLE IF NOT EXISTS `borrower_modifications` ( ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; -- +-- Table structure for table permissions +-- + +DROP TABLE IF EXISTS permissions; +CREATE TABLE permissions ( + permission_id int(11) NOT NULL auto_increment, + module varchar(32) NOT NULL, + code varchar(64) NOT NULL, + description varchar(255) DEFAULT NULL, + PRIMARY KEY (permission_id), + UNIQUE KEY (code), + CONSTRAINT permissions_to_modules_ibfk1 FOREIGN KEY (module) REFERENCES permission_modules (module) + ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; + +-- +-- Table structure for table permission_modules +-- + +DROP TABLE IF EXISTS permission_modules; +CREATE TABLE permission_modules ( + permission_module_id int(11) NOT NULL auto_increment, + module varchar(32) NOT NULL, + description varchar(255) DEFAULT NULL, + PRIMARY KEY (permission_module_id), + UNIQUE KEY (module) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; + +-- +-- Table structure for table borrower_permissions +-- + +DROP TABLE IF EXISTS borrower_permissions; +CREATE TABLE borrower_permissions ( + borrower_permission_id int(11) NOT NULL auto_increment, + borrowernumber int(11) NOT NULL, + permission_module_id int(11) NOT NULL, + permission_id int(11) NOT NULL, + PRIMARY KEY (borrower_permission_id), + UNIQUE KEY (borrowernumber, permission_module_id, permission_id), + CONSTRAINT borrower_permissions_ibfk_1 FOREIGN KEY (borrowernumber) REFERENCES borrowers (borrowernumber) + ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT borrower_permissions_ibfk_2 FOREIGN KEY (permission_id) REFERENCES permissions (permission_id) + ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT borrower_permissions_ibfk_3 FOREIGN KEY (permission_module_id) REFERENCES permission_modules (permission_module_id) + ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; + +-- -- Table structure for table linktracker -- This stores clicks to external links -- --- a/installer/data/mysql/updatedatabase.pl +++ a/installer/data/mysql/updatedatabase.pl @@ -7823,6 +7823,109 @@ if ( CheckVersion($DBversion) ) { SetVersion($DBversion); } +$DBversion = "3.15.00.XXX"; +if ( CheckVersion($DBversion) ) { + my @borrowerPermissions = $schema->resultset('BorrowerPermissions')->search({limit => 1}); + if (scalar(@borrowerPermissions)) { + print "Upgrade to $DBversion ALREADY DONE?!? (Bug XXX: Permissions rewrite)\n"; + } + else { + ##CREATE new TABLEs + ##CREATing instead of ALTERing existing tables because this way the changes are more easy to understand. + $dbh->do("CREATE TABLE permission_modules ( + permission_module_id int(11) NOT NULL auto_increment, + module varchar(32) NOT NULL, + description varchar(255) DEFAULT NULL, + PRIMARY KEY (permission_module_id), + UNIQUE KEY (module) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;"); + $dbh->do("INSERT INTO permission_modules (permission_module_id, module, description) SELECT bit, flag, flagdesc FROM userflags WHERE bit != 0;"); #superlibrarian causes primary key conflict + $dbh->do("INSERT INTO permission_modules (permission_module_id, module, description) SELECT 21, flag, flagdesc FROM userflags WHERE bit = 0;"); #So add him by himself. + + $dbh->do("CREATE TABLE permissions2 ( + permission_id int(11) NOT NULL auto_increment, + module varchar(32) NOT NULL, + code varchar(64) NOT NULL, + description varchar(255) DEFAULT NULL, + PRIMARY KEY (permission_id), + UNIQUE KEY (code), + CONSTRAINT permissions_to_modules_ibfk1 FOREIGN KEY (module) REFERENCES permission_modules (module) + ON DELETE CASCADE ON UPDATE CASCADE + ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;"); + $dbh->do("INSERT INTO permissions2 (module, code, description) + SELECT userflags.flag, code, description FROM permissions + LEFT JOIN userflags ON permissions.module_bit = userflags.bit;"); + + $dbh->do("CREATE TABLE borrower_permissions ( + borrower_permission_id int(11) NOT NULL auto_increment, + borrowernumber int(11) NOT NULL, + permission_module_id int(11) NOT NULL, + permission_id int(11) NOT NULL, + PRIMARY KEY (borrower_permission_id), + UNIQUE KEY (borrowernumber, permission_module_id, permission_id), + CONSTRAINT borrower_permissions_ibfk_1 FOREIGN KEY (borrowernumber) REFERENCES borrowers (borrowernumber) + ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT borrower_permissions_ibfk_2 FOREIGN KEY (permission_id) REFERENCES permissions2 (permission_id) + ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT borrower_permissions_ibfk_3 FOREIGN KEY (permission_module_id) REFERENCES permission_modules (permission_module_id) + ON DELETE CASCADE ON UPDATE CASCADE + ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;"); + $dbh->do("INSERT INTO borrower_permissions (borrowernumber, permission_module_id, permission_id) + SELECT borrowernumber, user_permissions.module_bit, permissions2.permission_id FROM user_permissions + LEFT JOIN permissions2 ON user_permissions.code = permissions2.code;"); + + ##Add subpermissions to the stand-alone modules (modules with no subpermissions) + use Koha::Auth::PermissionManager; + my $permissionManager = Koha::Auth::PermissionManager->new(); + $permissionManager->addPermission({module => 'superlibrarian', code => 'superlibrarian', description => 'Access to all librarian functions.'}); + $permissionManager->addPermission({module => 'catalogue', code => 'staff_login', description => 'Allow staff login.'}); + $permissionManager->addPermission({module => 'borrowers', code => 'view_borrowers', description => 'Show borrower details and search for borrowers.'}); + $permissionManager->addPermission({module => 'permissions', code => 'set_permissions', description => 'Set user permissions.'}); + $permissionManager->addPermission({module => 'management', code => 'management', description => 'Set library management parameters (deprecated).'}); + $permissionManager->addPermission({module => 'editauthorities', code => 'edit_authorities', description => 'Edit authorities.'}); + $permissionManager->addPermission({module => 'staffaccess', code => 'staff_access_permissions', description => 'Allow staff members to modify permissions for other staff members.'}); + + ##Create borrower_permissions to replace singular userflags from borrowers.flags. + use Koha::Borrowers; + my @borrowers = Koha::Borrowers->search({}); + foreach my $b (@borrowers) { + next unless $b->flags; + if ( ( $b->flags & ( 2**0 ) ) ) { + $permissionManager->grantPermission($b, 'superlibrarian', 'superlibrarian'); + } + if ( ( $b->flags & ( 2**2 ) ) ) { + $permissionManager->grantPermission($b, 'catalogue', 'staff_login'); + } + if ( ( $b->flags & ( 2**4 ) ) ) { + $permissionManager->grantPermission($b, 'borrowers', 'view_borrowers'); + } + if ( ( $b->flags & ( 2**5 ) ) ) { + $permissionManager->grantPermission($b, 'permissions', 'set_permissions'); + } + if ( ( $b->flags & ( 2**12 ) ) ) { + $permissionManager->grantPermission($b, 'management', 'management'); + } + if ( ( $b->flags & ( 2**14 ) ) ) { + $permissionManager->grantPermission($b, 'editauthorities', 'edit_authorities'); + } + if ( ( $b->flags & ( 2**17 ) ) ) { + $permissionManager->grantPermission($b, 'staffaccess', 'staff_access_permissions'); + } + } + + ##Cleanup redundant tables. + $dbh->do("DELETE FROM userflags"); #Cascades to other tables. + $dbh->do("DROP TABLE user_permissions"); + $dbh->do("DROP TABLE permissions"); + $dbh->do("DROP TABLE userflags"); + $dbh->do("ALTER TABLE borrowers DROP COLUMN flags"); + $dbh->do("ALTER TABLE permissions2 RENAME TO permissions"); + + print "Upgrade to $DBversion done (Bug XXX: Permissions rewrite)\n"; + SetVersion($DBversion); + } +} + $DBversion = "3.15.00.002"; if(CheckVersion($DBversion)) { $dbh->do("ALTER TABLE deleteditems MODIFY materials text;"); @@ -10585,6 +10688,49 @@ if ( CheckVersion($DBversion) ) { SetVersion ($DBversion); } +$DBversion = "XXX"; +if(CheckVersion($DBversion)) { + $dbh->do(q{ + DROP TABLE IF EXISTS api_keys; + }); + $dbh->do(q{ + CREATE TABLE api_keys ( + borrowernumber int(11) NOT NULL, + api_key VARCHAR(255) NOT NULL, + active int(1) DEFAULT 1, + PRIMARY KEY (borrowernumber, api_key), + CONSTRAINT api_keys_fk_borrowernumber + FOREIGN KEY (borrowernumber) + REFERENCES borrowers (borrowernumber) + ON DELETE CASCADE ON UPDATE CASCADE + ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; + }); + + print "Upgrade to $DBversion done (Bug 13920: Add API keys table)\n"; + SetVersion($DBversion); +} + +$DBversion = "XXX"; +if(CheckVersion($DBversion)) { + $dbh->do(q{ + DROP TABLE IF EXISTS api_timestamps; + }); + $dbh->do(q{ + CREATE TABLE api_timestamps ( + borrowernumber int(11) NOT NULL, + timestamp bigint, + PRIMARY KEY (borrowernumber), + CONSTRAINT api_timestamps_fk_borrowernumber + FOREIGN KEY (borrowernumber) + REFERENCES borrowers (borrowernumber) + ON DELETE CASCADE ON UPDATE CASCADE + ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; + }); + + print "Upgrade to $DBversion done (Bug 13920: Add API timestamps table)\n"; + SetVersion($DBversion); +} + $DBversion = "3.21.00.008"; if ( CheckVersion($DBversion) ) { $dbh->do(q{ --- a/koha-tmpl/intranet-tmpl/prog/en/modules/members/member-flags.tt +++ a/koha-tmpl/intranet-tmpl/prog/en/modules/members/member-flags.tt @@ -9,23 +9,23 @@ $("#permissionstree").treeview({animated: "fast", collapsed: true}); // Enforce Superlibrarian Privilege Mutual Exclusivity - if($('input[id="flag-0"]:checked').length){ + if($('input[id="flag-superlibrarian"]:checked').length){ if ($('input[name="flag"]:checked').length > 1){ alert('Inconsistency Detected!\n\nThe superlibrarian privilege is mutually exclusive of other privileges, as it includes them all.\n\nThis patron\'s privileges will now be reset to include only superlibrarian.'); } $('input[name="flag"]').each(function() { - if($(this).attr('id') != "flag-0"){ + if($(this).attr('id') != "flag-superlibrarian"){ $(this).attr('disabled', 'disabled'); $(this).removeAttr('checked', 'checked'); } }); } - $('input#flag-0').click(function() { - if($('input[id="flag-0"]:checked').length){ + $('input#flag-superlibrarian').click(function() { + if($('input[id="flag-superlibrarian"]:checked').length){ $('input[name="flag"]').each(function() { - if($(this).attr('id') != "flag-0"){ + if($(this).attr('id') != "flag-superlibrarian"){ $(this).attr('disabled', 'disabled'); $(this).removeAttr('checked', 'checked'); } @@ -121,9 +121,9 @@ [% FOREACH loo IN loop %] [% IF ( loo.expand ) %] -
  • +
  • [% ELSE %] -
  • +
  • [% END %] [% IF ( loo.checked ) %] --- a/members/member-flags.pl +++ a/members/member-flags.pl @@ -14,7 +14,9 @@ use C4::Context; use C4::Members; use C4::Branch; use C4::Members::Attributes qw(GetBorrowerAttributes); -#use C4::Acquisitions; +use Koha::Auth::PermissionManager; + +use Koha::Exception::BadParameter; use C4::Output; @@ -35,121 +37,43 @@ my ($template, $loggedinuser, $cookie) = get_template_and_user({ debug => 1, }); - +my $permissionManager = Koha::Auth::PermissionManager->new(); my %member2; $member2{'borrowernumber'}=$member; if ($input->param('newflags')) { my $dbh=C4::Context->dbh(); + #Cast CGI-params into a permissions HASH. my @perms = $input->param('flag'); - my %all_module_perms = (); my %sub_perms = (); foreach my $perm (@perms) { - if ($perm !~ /:/) { - $all_module_perms{$perm} = 1; + if ($perm eq 'superlibrarian') { + $sub_perms{superlibrarian}->{superlibrarian} = 1; + } + elsif ($perm !~ /:/) { + #DEPRECATED, GUI still sends the module flags here even though they have been removed from the DB. } else { my ($module, $sub_perm) = split /:/, $perm, 2; - push @{ $sub_perms{$module} }, $sub_perm; + $sub_perms{$module}->{$sub_perm} = 1; } } - # construct flags - my $module_flags = 0; - my $sth=$dbh->prepare("SELECT bit,flag FROM userflags ORDER BY bit"); - $sth->execute(); - while (my ($bit, $flag) = $sth->fetchrow_array) { - if (exists $all_module_perms{$flag}) { - $module_flags += 2**$bit; - } - } - - $sth = $dbh->prepare("UPDATE borrowers SET flags=? WHERE borrowernumber=?"); - $sth->execute($module_flags, $member); - - # deal with subpermissions - $sth = $dbh->prepare("DELETE FROM user_permissions WHERE borrowernumber = ?"); - $sth->execute($member); - $sth = $dbh->prepare("INSERT INTO user_permissions (borrowernumber, module_bit, code) - SELECT ?, bit, ? - FROM userflags - WHERE flag = ?"); - foreach my $module (keys %sub_perms) { - next if exists $all_module_perms{$module}; - foreach my $sub_perm (@{ $sub_perms{$module} }) { - $sth->execute($member, $sub_perm, $module); - } - } - + $permissionManager->revokeAllPermissions($member); + $permissionManager->grantPermissions($member, \%sub_perms); + print $input->redirect("/cgi-bin/koha/members/moremember.pl?borrowernumber=$member"); } else { -# my ($bor,$flags,$accessflags)=GetMemberDetails($member,''); - my $flags = $bor->{'flags'}; - my $accessflags = $bor->{'authflags'}; - my $dbh=C4::Context->dbh(); - my $all_perms = get_all_subpermissions(); - my $user_perms = get_user_subpermissions($bor->{'userid'}); - my $sth=$dbh->prepare("SELECT bit,flag,flagdesc FROM userflags ORDER BY bit"); - $sth->execute; + my $all_perms = $permissionManager->listKohaPermissionsAsHASH(); + my $user_perms = $permissionManager->getBorrowerPermissions($member); + + $all_perms = markBorrowerGrantedPermissions($all_perms, $user_perms); + my @loop; - while (my ($bit, $flag, $flagdesc) = $sth->fetchrow) { - my $checked=''; - if ($accessflags->{$flag}) { - $checked= 1; - } - - my %row = ( bit => $bit, - flag => $flag, - checked => $checked, - flagdesc => $flagdesc ); - - my @sub_perm_loop = (); - my $expand_parent = 0; - if ($checked) { - if (exists $all_perms->{$flag}) { - $expand_parent = 1; - foreach my $sub_perm (sort keys %{ $all_perms->{$flag} }) { - push @sub_perm_loop, { - id => "${flag}_$sub_perm", - perm => "$flag:$sub_perm", - code => $sub_perm, - description => $all_perms->{$flag}->{$sub_perm}, - checked => 1 - }; - } - } - } else { - if (exists $user_perms->{$flag}) { - $expand_parent = 1; - # put selected ones first - foreach my $sub_perm (sort keys %{ $user_perms->{$flag} }) { - push @sub_perm_loop, { - id => "${flag}_$sub_perm", - perm => "$flag:$sub_perm", - code => $sub_perm, - description => $all_perms->{$flag}->{$sub_perm}, - checked => 1 - }; - } - } - # then ones not selected - if (exists $all_perms->{$flag}) { - foreach my $sub_perm (sort keys %{ $all_perms->{$flag} }) { - push @sub_perm_loop, { - id => "${flag}_$sub_perm", - perm => "$flag:$sub_perm", - code => $sub_perm, - description => $all_perms->{$flag}->{$sub_perm}, - checked => 0 - } unless exists $user_perms->{$flag} and exists $user_perms->{$flag}->{$sub_perm}; - } - } - } - $row{expand} = $expand_parent; - if ($#sub_perm_loop > -1) { - $row{sub_perm_loop} = \@sub_perm_loop; - } - push @loop, \%row; + #Make sure the superlibrarian module is always on top. + push @loop, preparePermissionModuleForDisplay($all_perms, 'superlibrarian'); + foreach my $module (sort(keys(%$all_perms))) { + push @loop, preparePermissionModuleForDisplay($all_perms, $module) unless $module eq 'superlibrarian'; } if ( $bor->{'category_type'} eq 'C') { @@ -172,8 +96,8 @@ if (C4::Context->preference('ExtendedPatronAttributes')) { } # Computes full borrower address -my $roadtype = C4::Koha::GetAuthorisedValueByCode( 'ROADTYPE', $bor->{streettype} ); -my $address = $bor->{'streetnumber'} . " $roadtype " . $bor->{'address'}; +my $roadtype = C4::Koha::GetAuthorisedValueByCode( 'ROADTYPE', $bor->{streettype} ) || ''; +my $address = ($bor->{'streetnumber'} || '') . " $roadtype " . ($bor->{'address'} || ''); $template->param( borrowernumber => $bor->{'borrowernumber'}, @@ -206,3 +130,75 @@ $template->param( output_html_with_http_headers $input, $cookie, $template->output; } + +=head markBorrowerGrantedPermissions + +Adds a 'checked'-value for all subpermissions in the all-Koha-Permissions-list +that the current borrower has been granted. +@PARAM1 HASHRef of all Koha permissions and modules. +@PARAM1 ARRAYRef of all the granted Koha::Auth::BorrowerPermission-objects. +@RETURNS @PARAM1, slightly checked. +=cut + +sub markBorrowerGrantedPermissions { + my ($all_perms, $user_perms) = @_; + + foreach my $borrowerPermission (@$user_perms) { + my $module = $borrowerPermission->getPermissionModule->module; + my $code = $borrowerPermission->getPermission->code; + $all_perms->{$module}->{permissions}->{$code}->{checked} = 1; + } + return $all_perms; +} + +=head checkIfAllModulePermissionsGranted + +@RETURNS Boolean, 1 if all permissions granted. +=cut + +sub checkIfAllModulePermissionsGranted { + my ($moduleHash) = @_; + foreach my $code (keys(%{$moduleHash->{permissions}})) { + unless ($moduleHash->{permissions}->{$code}->{checked}) { + return 0; + } + } + return 1; +} + +sub preparePermissionModuleForDisplay { + my ($all_perms, $module) = @_; + + my $moduleHash = $all_perms->{$module}; + my $checked = checkIfAllModulePermissionsGranted($moduleHash); + + my %row = ( + bit => $module, + flag => $module, + checked => $checked, + flagdesc => $moduleHash->{description} ); + + my @sub_perm_loop = (); + my $expand_parent = 0; + + if ($module ne 'superlibrarian') { + foreach my $sub_perm (sort keys %{ $all_perms->{$module}->{permissions} }) { + my $sub_perm_checked = $all_perms->{$module}->{permissions}->{$sub_perm}->{checked}; + $expand_parent = 1 if $sub_perm_checked; + + push @sub_perm_loop, { + id => "${module}_$sub_perm", + perm => "$module:$sub_perm", + code => $sub_perm, + description => $all_perms->{$module}->{permissions}->{$sub_perm}->{description}, + checked => $sub_perm_checked || 0, + }; + } + + $row{expand} = $expand_parent; + if ($#sub_perm_loop > -1) { + $row{sub_perm_loop} = \@sub_perm_loop; + } + } + return \%row; +} --- a/misc/devel/interactiveWebDriverShell.pl +++ a/misc/devel/interactiveWebDriverShell.pl @@ -112,13 +112,13 @@ my $supportedPageObjects = { "members/moremember.pl" => { package => "t::lib::Page::Members::Moremember", urlEndpoint => "members/moremember.pl", - status => "not implemented", + status => "OK", params => ["borrowernumber"], }, "members/member-flags.pl" => { package => "t::lib::Page::Members::MemberFlags", urlEndpoint => "members/member-flags.pl", - status => "not implemented", + status => "OK", params => ["borrowernumber"], }, }; @@ -173,4 +173,4 @@ sub deployPageObject { } return ($po, $po->getDriver()); -} +} --- a/t/db_dependent/Koha/Auth/BorrowerPermission.t +++ a/t/db_dependent/Koha/Auth/BorrowerPermission.t @@ -0,0 +1,102 @@ +#!/usr/bin/perl + +# Copyright 2015 Vaara-kirjastot +# +# This file is part of Koha. +# +# Koha is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# Koha is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Koha; if not, see . + +use Modern::Perl; +use Test::More; + +use Koha::Auth::BorrowerPermission; +use Koha::Auth::BorrowerPermissions; + +use t::db_dependent::TestObjects::ObjectFactory; +use t::db_dependent::TestObjects::Borrowers::BorrowerFactory; + +##Setting up the test context +my $testContext = {}; + +my $borrowerFactory = t::db_dependent::TestObjects::Borrowers::BorrowerFactory->new(); +my $borrowers = $borrowerFactory->createTestGroup([ + {firstname => 'Olli-Antti', + surname => 'Kivi', + cardnumber => '1A01', + branchcode => 'CPL', + }, + {firstname => 'Alli-Ontti', + surname => 'Ivik', + cardnumber => '1A02', + branchcode => 'CPL', + }, + ], undef, $testContext); + +##Test context set, starting testing: +eval { #run in a eval-block so we don't die without tearing down the test context + ##Basic id-based creation. + my $borrowerPermissionById = Koha::Auth::BorrowerPermission->new({borrowernumber => $borrowers->{'1A01'}->{borrowernumber}, permission_module_id => 1, permission_id => 1}); + $borrowerPermissionById->store(); + my @borrowerPermissionById = Koha::Auth::BorrowerPermissions->search({borrowernumber => $borrowers->{'1A01'}->{borrowernumber}}); + is(scalar(@borrowerPermissionById), 1, "BorrowerPermissions, id-based creation:> Borrower has only one permission"); + is($borrowerPermissionById[0]->permission_module_id, 1, "BorrowerPermissions, id-based creation:> Same permission_module_id"); + is($borrowerPermissionById[0]->permission_id, 1, "BorrowerPermissions, id-based creation:> Same permission_id"); + + ##Basic name-based creation. + my $borrowerPermissionByName = Koha::Auth::BorrowerPermission->new({borrowernumber => $borrowers->{'1A02'}->{borrowernumber}, permissionModule => 'circulate', permission => 'manage_restrictions'}); + $borrowerPermissionByName->store(); + my @borrowerPermissionByName = Koha::Auth::BorrowerPermissions->search({borrowernumber => $borrowers->{'1A02'}->{borrowernumber}}); + is(scalar(@borrowerPermissionByName), 1, "BorrowerPermissions, name-based creation:> Borrower has only one permission"); + is($borrowerPermissionByName[0]->getPermissionModule->module, 'circulate', "BorrowerPermissions, name-based creation:> Same permission_module"); + is($borrowerPermissionByName[0]->getPermission->code, 'manage_restrictions', "BorrowerPermissions, name-based creation:> Same permission"); + + ##Testing setter/getter for Borrower + my $borrower1A01 = $borrowerPermissionById->getBorrower(); + is($borrower1A01->cardnumber, "1A01", "BorrowerPermissions, setter/getter:> getBorrower() 1A01"); + my $borrower1A02 = $borrowerPermissionByName->getBorrower(); + is($borrower1A02->cardnumber, "1A02", "BorrowerPermissions, setter/getter:> getBorrower() 1A02"); + + $borrowerPermissionById->setBorrower($borrower1A02); + is($borrowerPermissionById->getBorrower()->cardnumber, "1A02", "BorrowerPermissions, setter/getter:> setBorrower() 1A02"); + $borrowerPermissionByName->setBorrower($borrower1A01); + is($borrowerPermissionByName->getBorrower()->cardnumber, "1A01", "BorrowerPermissions, setter/getter:> setBorrower() 1A01"); + + ##Testing getter for PermissionModule + my $permissionModule1 = $borrowerPermissionById->getPermissionModule(); + is($permissionModule1->permission_module_id, 1, "BorrowerPermissions, setter/getter:> getPermissionModule() 1"); + my $permissionModuleCirculate = $borrowerPermissionByName->getPermissionModule(); + is($permissionModuleCirculate->module, "circulate", "BorrowerPermissions, setter/getter:> getPermissionModule() circulate"); + + #Not testing setters because changing the module might not make any sense. + #Then we would need to make sure we dont end up with bad permissionModule->permission combinations. + + ##Testing getter for Permission + my $permission1 = $borrowerPermissionById->getPermission(); + is($permission1->permission_id, 1, "BorrowerPermissions, setter/getter:> getPermission() 1"); + my $permissionManage_restrictions = $borrowerPermissionByName->getPermission(); + is($permissionManage_restrictions->code, "manage_restrictions", "BorrowerPermissions, setter/getter:> getPermission() manage_restrictions"); +}; +if ($@) { #Catch all leaking errors and gracefully terminate. + warn $@; + tearDown(); + exit 1; +} + +##All tests done, tear down test context +$borrowerFactory->tearDownTestContext($testContext); +done_testing; + +sub tearDown { + t::db_dependent::TestObjects::ObjectFactory->tearDownTestContext($testContext); +} --- a/t/db_dependent/Koha/Auth/PermissionManager.t +++ a/t/db_dependent/Koha/Auth/PermissionManager.t @@ -0,0 +1,221 @@ +#!/usr/bin/perl + +# Copyright 2015 Vaara-kirjastot +# +# This file is part of Koha. +# +# Koha is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# Koha is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Koha; if not, see . + +use Modern::Perl; +use Test::More; +use Try::Tiny; +use Scalar::Util qw(blessed); + +use Koha::Auth::PermissionManager; + +use t::db_dependent::TestObjects::ObjectFactory; +use t::db_dependent::TestObjects::Borrowers::BorrowerFactory; + + +##Setting up the test context +my $testContext = {}; + +my $borrowerFactory = t::db_dependent::TestObjects::Borrowers::BorrowerFactory->new(); +my $borrowers = $borrowerFactory->createTestGroup([ + {firstname => 'Olli-Antti', + surname => 'Kivi', + cardnumber => '1A01', + branchcode => 'CPL', + }, + {firstname => 'Alli-Ontti', + surname => 'Ivik', + cardnumber => '1A02', + branchcode => 'CPL', + }, + ], undef, $testContext); + +##Test context set, starting testing: +eval { #run in a eval-block so we don't die without tearing down the test context + my $permissionManager = Koha::Auth::PermissionManager->new(); + my ($permissionModule, $permission, $failureCaughtFlag, $permissionsList); + + ##Test getBorrowerPermissions + $permissionManager->grantPermission($borrowers->{'1A01'}, 'circulate', 'force_checkout'); + $permissionManager->grantPermission($borrowers->{'1A01'}, 'circulate', 'manage_restrictions'); + $permissionsList = $permissionManager->getBorrowerPermissions($borrowers->{'1A01'}); + is($permissionsList->[0]->getPermission->code, 'force_checkout', "PermissionManager, getBorrowerPermissions:> Check 1."); + is($permissionsList->[1]->getPermission->code, 'manage_restrictions', "PermissionManager, getBorrowerPermissions:> Check 2."); + $permissionManager->revokePermission($borrowers->{'1A01'}, 'circulate', 'force_checkout'); + $permissionManager->revokePermission($borrowers->{'1A01'}, 'circulate', 'manage_restrictions'); + + ##Test grantPermissions && revokeAllPermissions + $permissionManager->grantPermissions($borrowers->{'1A01'}, + { borrowers => 'view_borrowers', + reserveforothers => ['place_holds'], + tools => ['edit_news', 'edit_notices'], + acquisition => { + budget_add_del => 1, + budget_modify => 1, + }, + }); + $permissionManager->hasPermission($borrowers->{'1A01'}, 'borrowers', 'view_borrowers'); + $permissionManager->hasPermission($borrowers->{'1A01'}, 'tools', 'edit_notices'); + $permissionManager->hasPermission($borrowers->{'1A01'}, 'acquisition', 'budget_modify'); + $permissionsList = $permissionManager->getBorrowerPermissions($borrowers->{'1A01'}); + is(scalar(@$permissionsList), 6, "PermissionManager, grantPermissions:> Permissions as HASH, ARRAY and Scalar."); + + $permissionManager->revokeAllPermissions($borrowers->{'1A01'}); + $permissionsList = $permissionManager->getBorrowerPermissions($borrowers->{'1A01'}); + is(scalar(@$permissionsList), 0, "PermissionManager, revokeAllPermissions:> No permissions left."); + + ##Test listKohaPermissionsAsHASH + my $listedPermissions = $permissionManager->listKohaPermissionsAsHASH(); + ok(ref($listedPermissions->{circulate}->{permissions}->{force_checkout}) eq 'HASH', "PermissionManager, listKohaPermissionsAsHASH:> Check 1."); + ok(ref($listedPermissions->{editcatalogue}->{permissions}->{edit_catalogue}) eq 'HASH', "PermissionManager, listKohaPermissionsAsHASH:> Check 2."); + ok(defined($listedPermissions->{reports}->{permissions}->{create_reports}->{description}), "PermissionManager, listKohaPermissionsAsHASH:> Check 3."); + ok(defined($listedPermissions->{permissions}->{description}), "PermissionManager, listKohaPermissionsAsHASH:> Check 4."); + + + + ### TESTING WITH unique keys, instead of the recommended Koha::Objects. ### + #Arguably this makes for more clear tests cases :) + ##Add/get PermissionModule + $permissionModule = $permissionManager->addPermissionModule({module => 'test', description => 'Just testing this module.'}); + is($permissionModule->module, "test", "PermissionManager from names, add/getPermissionModule:> Module added."); + $permissionModule = $permissionManager->getPermissionModule('test'); + is($permissionModule->module, "test", "PermissionManager from names, add/getPermissionModule:> Module got."); + + ##Add/get Permission + $permission = $permissionManager->addPermission({module => 'test', code => 'testperm', description => 'Just testing this permission.'}); + is($permission->code, "testperm", "PermissionManager from names, add/getPermission:> Permission added."); + $permission = $permissionManager->getPermission('testperm'); + is($permission->code, "testperm", "PermissionManager from names, add/getPermission:> Permission got."); + + ##Grant permission + $permissionManager->grantPermission($borrowers->{'1A01'}, 'test', 'testperm'); + ok($permissionManager->hasPermission($borrowers->{'1A01'}, 'test', 'testperm'), "PermissionManager from names, grant/hasPermission:> Borrower granted permission."); + + ##hasPermission with wildcard + ok($permissionManager->hasPermission($borrowers->{'1A01'}, 'test', '*'), "PermissionManager from names, hasPermission:> Wildcard permission."); + + ##hasPermissions with wildcard + ok($permissionManager->hasPermissions($borrowers->{'1A01'}, {test => ['*']}), "PermissionManager from names, hasPermission:> Wildcard permissions from array."); + + ##Revoke permission + $permissionManager->revokePermission($borrowers->{'1A01'}, 'test', 'testperm'); + $failureCaughtFlag = 0; + try { + $permissionManager->hasPermission($borrowers->{'1A01'}, 'test', 'testperm'); + } catch { + if (blessed($_) && $_->isa('Koha::Exception::NoPermission')) { + $failureCaughtFlag = 1; + } + else { + die $_; #Somekind of another problem arised and rethrow it. + } + }; + ok($failureCaughtFlag, "PermissionManager from names, revoke/hasPermission:> Borrower revoked permission."); + + ##Delete permissions and modules we just made. When we delete the module first, the permissions is ON CASCADE DELETEd + $permissionManager->delPermissionModule('test'); + $permissionModule = $permissionManager->getPermissionModule('test'); + ok(not(defined($permissionModule)), "PermissionManager from names, delPermissionModule:> Module deleted."); + + $failureCaughtFlag = 0; + try { + #This subpermission is now deleted due to cascading delete of the parent permissionModule + #We catch the exception gracefully and report test success + $permissionManager->delPermission('testperm'); + } catch { + if (blessed($_) && $_->isa('Koha::Exception::UnknownObject')) { + $failureCaughtFlag = 1; + } + else { + die $_; #Somekind of another problem arised and rethrow it. + } + }; + ok($failureCaughtFlag, "PermissionManager from names, delPermission:> Permission already deleted, exception caught."); + $permission = $permissionManager->getPermission('testperm'); + ok(not(defined($permission)), "PermissionManager from names, delPermission:> Permission deleted."); + + + + ### TESTING WITH Koha::Object parameters instead. ### + ##Add/get PermissionModule + $permissionModule = $permissionManager->addPermissionModule({module => 'test', description => 'Just testing this module.'}); + is($permissionModule->module, "test", "PermissionManager from objects, add/getPermissionModule:> Module added."); + $permissionModule = $permissionManager->getPermissionModule($permissionModule); + is($permissionModule->module, "test", "PermissionManager from objects, add/getPermissionModule:> Module got."); + + ##Add/get Permission + $permission = $permissionManager->addPermission({module => 'test', code => 'testperm', description => 'Just testing this permission.'}); + is($permission->code, "testperm", "PermissionManager from objects, add/getPermission:> Permission added."); + $permission = $permissionManager->getPermission($permission); + is($permission->code, "testperm", "PermissionManager from objects, add/getPermission:> Permission got."); + + ##Grant permission + $permissionManager->grantPermission($borrowers->{'1A01'}, $permissionModule, $permission); + ok($permissionManager->hasPermission($borrowers->{'1A01'}, $permissionModule, $permission), "PermissionManager from objects, grant/hasPermission:> Borrower granted permission."); + + ##hasPermission with wildcard + ok($permissionManager->hasPermission($borrowers->{'1A01'}, $permissionModule, '*'), "PermissionManager from objects, hasPermission:> Wildcard permission."); + + ##hasPermissions with wildcard, we quite cannot use a blessed Object as a HASH key + #ok($permissionManager->hasPermissions($borrowers->{'1A01'}, {$permissionModule->module() => ['*']}), "PermissionManager from objects, hasPermission:> Wildcard permissions from array."); + + ##Revoke permission + $permissionManager->revokePermission($borrowers->{'1A01'}, $permissionModule, $permission); + $failureCaughtFlag = 0; + try { + $permissionManager->hasPermission($borrowers->{'1A01'}, $permissionModule, $permission); + } catch { + if (blessed($_) && $_->isa('Koha::Exception::NoPermission')) { + $failureCaughtFlag = 1; + } + else { + die $_; #Somekind of another problem arised and rethrow it. + } + }; + ok($failureCaughtFlag, "PermissionManager from objects, revoke/hasPermission:> Borrower revoked permission."); + + ##Delete permissions and modules we just made + $permissionManager->delPermission($permission); + $permission = $permissionManager->getPermission('testperm'); + ok(not(defined($permission)), "PermissionManager from objects, delPermission:> Permission deleted."); + + $permissionManager->delPermissionModule($permissionModule); + $permissionModule = $permissionManager->getPermissionModule('test'); + ok(not(defined($permissionModule)), "PermissionManager from objects, delPermissionModule:> Module deleted."); + + + + ##Testing superlibrarian permission + $permissionManager->revokeAllPermissions($borrowers->{'1A01'}); + $permissionManager->grantPermission($borrowers->{'1A01'}, 'superlibrarian', 'superlibrarian'); + ok($permissionManager->hasPermission($borrowers->{'1A01'}, 'staffaccess', 'staff_access_permissions'), "PermissionManager, superuser permission:> Superuser has all permissions 1."); + ok($permissionManager->hasPermission($borrowers->{'1A01'}, 'tools', 'batch_upload_patron_images'), "PermissionManager, superuser permission:> Superuser has all permissions 2."); +}; +if ($@) { #Catch all leaking errors and gracefully terminate. + warn $@; + tearDown(); + exit 1; +} + +##All tests done, tear down test context +tearDown(); +done_testing; + +sub tearDown { + t::db_dependent::TestObjects::ObjectFactory->tearDownTestContext($testContext); +} --- a/t/db_dependent/Members/member-flags.t +++ a/t/db_dependent/Members/member-flags.t @@ -0,0 +1,100 @@ +#!/usr/bin/env perl + +# Copyright 2015 Open Source Freedom Fighters +# +# This file is part of Koha. +# +# Koha is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# Koha is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Koha; if not, see . + +use Modern::Perl; + +use Test::More; +use Try::Tiny; #Even Selenium::Remote::Driver uses Try::Tiny :) +use Scalar::Util qw(blessed); + +use t::lib::Page::Members::MemberFlags; +use t::db_dependent::TestObjects::Borrowers::BorrowerFactory; +use Koha::Auth::PermissionManager; + + +##Setting up the test context +my $testContext = {}; + +my $password = '1234'; +my $borrowerFactory = t::db_dependent::TestObjects::Borrowers::BorrowerFactory->new(); +my $borrowers = $borrowerFactory->createTestGroup([ + {firstname => 'Olli-Antti', + surname => 'Kivi', + cardnumber => '1A01', + branchcode => 'CPL', + userid => 'mini_admin', + password => $password, + }, + ], undef, $testContext); + +##Test context set, starting testing: +eval { #run in a eval-block so we don't die without tearing down the test context + + testGrantRevokePermissions(); + +}; +if ($@) { #Catch all leaking errors and gracefully terminate. + warn $@; + tearDown(); + exit 1; +} + +##All tests done, tear down test context +tearDown(); +done_testing; + +sub tearDown { + t::db_dependent::TestObjects::ObjectFactory->tearDownTestContext($testContext); +} + +sub testGrantRevokePermissions { + my $permissionManager = Koha::Auth::PermissionManager->new(); + $permissionManager->grantPermissions($borrowers->{'1A01'}, {permissions => 'set_permissions', + catalogue => 'staff_login', + staffaccess => 'staff_access_permissions', + circulate => 'override_renewals', + borrowers => 'view_borrowers', + }); + + my $memberflags = t::lib::Page::Members::MemberFlags->new({borrowernumber => $borrowers->{'1A01'}->{borrowernumber}}); + + $memberflags->isPasswordLoginAvailable()->doPasswordLogin($borrowers->{'1A01'}->{userid}, $password) + ->togglePermission('editcatalogue', 'delete_all_items') #Add this + ->togglePermission('editcatalogue', 'edit_items') #Add this + ->togglePermission('circulate', 'override_renewals') #Remove this permission + ->submitPermissionTree(); + + ok($permissionManager->hasPermissions($borrowers->{'1A01'},{editcatalogue => ['delete_all_items', 'edit_items']}), + "member-flags.pl:> Granting new permissions succeeded."); + + my $failureCaughtFlag = 0; + try { + $permissionManager->hasPermission($borrowers->{'1A01'}, 'circulate', 'override_renewals'); + } catch { + if (blessed($_) && $_->isa('Koha::Exception::NoPermission')) { + $failureCaughtFlag = 1; + } + else { + die $_; #Somekind of another problem arised and rethrow it. + } + }; + ok($failureCaughtFlag, "member-flags.pl:> Revoking permissions succeeded."); + + $permissionManager->revokeAllPermissions($borrowers->{'1A01'}); +} --- a/t/lib/Page/Members/MemberFlags.pm +++ a/t/lib/Page/Members/MemberFlags.pm @@ -0,0 +1,125 @@ +package t::lib::Page::Members::MemberFlags; + +# Copyright 2015 Open Source Freedom Fighters +# +# This file is part of Koha. +# +# Koha is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# Koha is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Koha; if not, see . + +use Modern::Perl; +use Test::More; + +use t::lib::Page::Members::Moremember; + +use base qw(t::lib::Page); + +=head NAME t::lib::Page::Members::MemberFlags + +=head SYNOPSIS + +member-flags.pl PageObject providing page functionality as a service! + +=cut + +=head new + + my $memberflags = t::lib::Page::Members::MemberFlags->new({borrowernumber => "1"}); + +Instantiates a WebDriver and loads the members/member-flags.pl. +@PARAM1 HASHRef of optional and MANDATORY parameters +MANDATORY extra parameters: + borrowernumber => loads the page to display Borrower matching the given borrowernumber + +@RETURNS t::lib::Page::Members::MemberFlags, ready for user actions! +=cut + +sub new { + my ($class, $params) = @_; + unless (ref($params) eq 'HASH') { + $params = {}; + } + $params->{resource} = '/cgi-bin/koha/members/member-flags.pl'; + $params->{type} = 'staff'; + + $params->{getParams} = []; + #Handle MANDATORY parameters + if ($params->{borrowernumber}) { + push @{$params->{getParams}}, "member=".$params->{borrowernumber}; + } + else { + Koha::Exception::BadParameter->throw(error => __PACKAGE__."->new():> Parameter 'borrowernumber' is missing."); + } + + my $self = $class->SUPER::new($params); + + return $self; +} + +sub togglePermission { + my ($self, $permissionModule, $permissionCode) = @_; + my $d = $self->getDriver(); + $self->debugTakeSessionSnapshot(); + + my ($moduleTreeExpansionButton, $moduleCheckbox, $permissionCheckbox) = _getPermissionTreePermissionElements($d, $permissionModule, $permissionCode); + if ($moduleTreeExpansionButton->get_attribute("class") =~ /expandable-hitarea/) { #Permission checkboxes are hidden and need to be shown. + $moduleTreeExpansionButton->click(); + $d->pause( $self->{userInteractionDelay} ); + } + + + #$moduleCheckbox->click(); #Clicking this will toggle all module permissions. + my $checked = $permissionCheckbox->get_attribute("checked") || ''; #Returns undef if not checked + $permissionCheckbox->click(); + ok($checked ne ($permissionCheckbox->get_attribute("checked") || ''), + "Module '$permissionModule', permission '$permissionCode', checkbox toggled"); + $self->debugTakeSessionSnapshot(); + + return $self; +} + +sub submitPermissionTree { + my $self = shift; + my $d = $self->getDriver(); + + my ($submitButton, $cancelButton) = _getPermissionTreeControlElements($d); + $submitButton->click(); + $self->debugTakeSessionSnapshot(); + + ok(($d->get_title() =~ /Patron details for/), "Permissions set"); + + return t::lib::Page::Members::Moremember->rebrandFromPageObject($self); +} + +sub _getPermissionTreeControlElements { + my $d = shift; + my $saveButton = $d->find_element('input[value="Save"]'); + my $cancelButton = $d->find_element('a.cancel'); + return ($saveButton, $cancelButton); +} + +=head _getPermissionTreePermissionElements + +@PARAM1 Selenium::Remote::Driver implementation +@PARAM2 Scalar, Koha::Auth::PermissionModule's module +@PARAM3 Scalar, Koha::Auth::Permission's code +=cut + +sub _getPermissionTreePermissionElements { + my ($d, $module, $code) = @_; + my $moduleTreeExpansionButton = $d->find_element("div.$module-hitarea"); + my $moduleCheckbox = $d->find_element("input#flag-$module"); + my $permissionCheckbox = $d->find_element('input#'.$module.'_'.$code); + return ($moduleTreeExpansionButton, $moduleCheckbox, $permissionCheckbox); +} +1; #Make the compiler happy! --