From e40ced0503496730a708e8600e639eef3f02e59d Mon Sep 17 00:00:00 2001 From: Olli-Antti Kivilahti Date: Mon, 13 Jul 2015 09:15:12 +0000 Subject: [PATCH] Bug 14540 - Move member-flags.pl to PermissionsManager to better manage permissions for testing. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently there is no nice way to grant/revoke permissions for Borrowers in Koha. This is a big problem when doing integration testing and when working with permissions using the future REST API. Currently we have one web-script, member-flags.pl, which deals with all permission-related things. Basically it always DELETEs all your permissions and then adds them again with some modifications. There is no internal API for creating permissions and this makes automated testing significantly harder. To create permissions for a test borrower, we must decrypt the arcane way of creating koha.borrowers.flags and add user_permissions with brittle SQL-queries. This makes for very messy test scripts, and quite frankly this is hardly ever done in Koha. Also the koha.borrowers.flags-column is pretty horrible. It is 19-bit binary array where each bit tells of the borrower has all permissions for the respective permission module. This is very difficult to calculate and the mechanism is hard to understand. This feature is also unnecessary, since we are always interested in having the specific permissions. We don't need to know if one Borrower has all the permissions for one module. Also what happens if more subpermissions are added to the module? Existing all-permission-holders lose their all-permissions-status? Instead we could have a nice internal API to work with permissions. My patch introduces a new database structure for permissions (not necessarily better), Koha::Object implementation of the DB structure, and most importantly, a Koha::Auth::PermissionManager which is a gateway to all permissions-related actions. This feature is well tested with unit tests and PageObject integration tests. See the tests for usage examples. Especially the t/db_dependent/Members/member-flags.t I hope you like it :) SELENIUM TESTS: Now that the new permission manager enables setting permissions easily in test environments. This patch introduces more complex PageObject regressions tests. -Logged in branch regression test for Intra. -OPAC password login/logout -OPAC anonymous search history preservation. VOCABULARY: Module (permission_modules) => a module which consists of specific Permissions. Typically covers one Module in Koha. Permission (permission) => a specific Permission of a Module. Granting a Borrower the permission to do something. Borrower permission (borrower_permissions) => A specific Permission a specific Borrower has, eg. the permission to edit bibliographic records. TEST PLAN: -2. Note the permissions a bunch of borrowers has. -1. run updatedatabase.pl 0. Observe that those borrowers still have the same permissions. For modules which didn't have any subpermissions, a generic subpermission is added and the borrowers should have it if they had the existing module permission. Basic login: 1. Go to Koha staff client 2. Make a password login. 3. Browse to any other staff client pages to confirm that the session is active and login persists on other pages. Modify permissions: 1. Go to member-flags.pl (Home › Patrons › Set permissions) 2. Change your permissions. Save. 3. Go back to member-flags.pl 4. Observe that you see the same permissions you just set. Modify superlibrarian permission: 1. Go to member-flags.pl (Home › Patrons › Set permissions) 2. Give the superlibrarian-permission. Observe all other permissions vanishing. Save. 3. Go back to member-flags.pl 4. Observe that you see only the superlibrarian-permission active. 5. Observe that the superlibrarian permission is the only top-level permission which doesn't have any subpermissions. --- C4/Auth.pm | 77 +-- C4/Members.pm | 11 +- Koha/Auth/BorrowerPermission.pm | 221 +++++++++ Koha/Auth/BorrowerPermissions.pm | 38 ++ Koha/Auth/Permission.pm | 60 +++ Koha/Auth/PermissionManager.pm | 529 +++++++++++++++++++++ Koha/Auth/PermissionModule.pm | 78 +++ Koha/Auth/PermissionModules.pm | 38 ++ Koha/Auth/Permissions.pm | 42 ++ Koha/AuthUtils.pm | 1 - Koha/Schema/Result/Borrower.pm | 71 ++- Koha/Schema/Result/Permission.pm | 79 +-- installer/data/mysql/en/mandatory/userflags.sql | 40 +- .../data/mysql/en/mandatory/userpermissions.sql | 159 ++++--- installer/data/mysql/kohastructure.sql | 94 ++-- installer/data/mysql/updatedatabase.pl | 113 +++++ .../prog/en/modules/members/member-flags.tt | 14 +- members/member-flags.pl | 198 ++++---- misc/devel/interactiveWebDriverShell.pl | 9 +- opac/opac-user.pl | 2 +- t/db_dependent/Koha/Auth.t | 57 ++- 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/db_dependent/Opac/opac-search.t | 79 +++ 25 files changed, 2077 insertions(+), 356 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/db_dependent/Opac/opac-search.t diff --git a/C4/Auth.pm b/C4/Auth.pm index ef0c585..2561961 100644 --- a/C4/Auth.pm +++ b/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 ); @@ -254,11 +256,7 @@ sub get_template_and_user { if ($flags) { foreach my $module ( keys %$all_perms ) { - if ( defined($flags->{$module}) && $flags->{$module} == 1 ) { - foreach my $subperm ( keys %{ $all_perms->{$module} } ) { - $template->param( "CAN_user_${module}_${subperm}" => 1 ); - } - } elsif ( ref( $flags->{$module} ) ) { + if ( ref( $flags->{$module} ) ) { foreach my $subperm ( keys %{ $flags->{$module} } ) { $template->param( "CAN_user_${module}_${subperm}" => 1 ); } @@ -990,7 +988,7 @@ sub checkauth { if ( $return == 1 ) { my $select = " - SELECT borrowernumber, firstname, surname, flags, borrowers.branchcode, + SELECT borrowernumber, firstname, surname, borrowers.branchcode, branches.branchname as branchname, branches.branchprinter as branchprinter, email @@ -1013,7 +1011,7 @@ sub checkauth { } } if ( $sth->rows ) { - ( $borrowernumber, $firstname, $surname, $userflags, + ( $borrowernumber, $firstname, $surname, $branchcode, $branchname, $branchprinter, $emailaddress ) = $sth->fetchrow; $debug and print STDERR "AUTH_3 results: " . "$cardnumber,$borrowernumber,$userid,$firstname,$surname,$userflags,$branchcode,$emailaddress\n"; @@ -1739,7 +1737,7 @@ sub checkpw_internal { my $sth = $dbh->prepare( - "select password,cardnumber,borrowernumber,userid,firstname,surname,borrowers.branchcode,branches.branchname,flags from borrowers join branches on borrowers.branchcode=branches.branchcode where userid=?" + "select password,cardnumber,borrowernumber,userid,firstname,surname,borrowers.branchcode,branches.branchname from borrowers join branches on borrowers.branchcode=branches.branchcode where userid=?" ); $sth->execute($userid); if ( $sth->rows ) { @@ -1756,18 +1754,18 @@ sub checkpw_internal { } $sth = $dbh->prepare( - "select password,cardnumber,borrowernumber,userid,firstname,surname,borrowers.branchcode,branches.branchname,flags from borrowers join branches on borrowers.branchcode=branches.branchcode where cardnumber=?" + "select password,cardnumber,borrowernumber,userid,firstname,surname,borrowers.branchcode,branches.branchname from borrowers join branches on borrowers.branchcode=branches.branchcode where cardnumber=?" ); $sth->execute($userid); if ( $sth->rows ) { my ( $stored_hash, $cardnumber, $borrowernumber, $userid, $firstname, - $surname, $branchcode, $branchname, $flags ) + $surname, $branchcode, $branchname ) = $sth->fetchrow; if ( checkpw_hash( $password, $stored_hash ) ) { C4::Context->set_userenv( $borrowernumber, $userid, $cardnumber, - $firstname, $surname, $branchcode, $branchname, $flags ); + $firstname, $surname, $branchcode, $branchname ); return 1, $cardnumber, $userid; } } @@ -1799,6 +1797,7 @@ sub checkpw_hash { } =head2 getuserflags +@DEPRECATED, USE THE Koha::Auth::PermissionManager my $authflags = getuserflags($flags, $userid, [$dbh]); @@ -1811,6 +1810,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; @@ -1822,6 +1822,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; @@ -1845,7 +1848,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, @@ -1870,25 +1873,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(); @@ -1901,6 +1901,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 @@ -1915,6 +1929,7 @@ sub get_all_subpermissions { } =head2 haspermission +@DEPRECATED, USE THE Koha::Auth::PermissionManager $flags = ($userid, $flagsrequired); @@ -1926,11 +1941,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 @@ -1947,7 +1966,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 diff --git a/C4/Members.pm b/C4/Members.pm index 95fb4e7..5c51973 100644 --- a/C4/Members.pm +++ b/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 diff --git a/Koha/Auth/BorrowerPermission.pm b/Koha/Auth/BorrowerPermission.pm new file mode 100644 index 0000000..945abaa --- /dev/null +++ b/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->cast( $params->{permission_module_id} || $params->{permissionModule} ); + $params->{permission} = Koha::Auth::Permissions->cast( $params->{permission_id} || $params->{permission} ); + $params->{borrower} = Koha::Borrowers->cast( $params->{borrowernumber} || $params->{borrower} ); +} + +1; \ No newline at end of file diff --git a/Koha/Auth/BorrowerPermissions.pm b/Koha/Auth/BorrowerPermissions.pm new file mode 100644 index 0000000..706fb23 --- /dev/null +++ b/Koha/Auth/BorrowerPermissions.pm @@ -0,0 +1,38 @@ +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 _get_castable_unique_columns { + return ['borrower_permission_id']; +} + +1; diff --git a/Koha/Auth/Permission.pm b/Koha/Auth/Permission.pm new file mode 100644 index 0000000..32771ee --- /dev/null +++ b/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 'Permission'; +} +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; \ No newline at end of file diff --git a/Koha/Auth/PermissionManager.pm b/Koha/Auth/PermissionManager.pm new file mode 100644 index 0000000..50ca3de --- /dev/null +++ b/Koha/Auth/PermissionManager.pm @@ -0,0 +1,529 @@ +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::Auth::BorrowerPermissions; + +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->cast($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->cast($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->cast($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->cast($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 ; $icast( $permissionModules[$i] ); + } + return \@permissionModules; +} + +=head listKohaPermissionsAsHASH + +@RETURNS HASHRef, a HASH-representation of all the permissions and permission modules +in Koha. Eg: + { + acquisitions => { + 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->cast($borrower); + + if ($borrower->isSuperuser()) { + return [Koha::Auth::BorrowerPermission->new({borrower => $borrower, permission => 'superlibrarian', permissionModule => 'superlibrarian'})]; + } + + 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 ; $icast($borrowerPermissions[$i]); + } + return \@borrowerPermissions; +} + +=head grantPermissions + + $permissionManager->grantPermissions($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->cast($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->cast($borrower); + $permissionModule = Koha::Auth::PermissionModules->cast($permissionModule); + $permission = Koha::Auth::Permissions->cast($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->cast($borrower); + + if ( $borrower->userid && $borrower->userid eq C4::Context->config('user') ) { + return 1; + } + elsif ( $borrower->userid && $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; \ No newline at end of file diff --git a/Koha/Auth/PermissionModule.pm b/Koha/Auth/PermissionModule.pm new file mode 100644 index 0000000..8e72e31 --- /dev/null +++ b/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; \ No newline at end of file diff --git a/Koha/Auth/PermissionModules.pm b/Koha/Auth/PermissionModules.pm new file mode 100644 index 0000000..a7bc088 --- /dev/null +++ b/Koha/Auth/PermissionModules.pm @@ -0,0 +1,38 @@ +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 _get_castable_unique_columns { + return ['permission_module_id', 'module']; +} + +1; \ No newline at end of file diff --git a/Koha/Auth/Permissions.pm b/Koha/Auth/Permissions.pm new file mode 100644 index 0000000..76f4605 --- /dev/null +++ b/Koha/Auth/Permissions.pm @@ -0,0 +1,42 @@ +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 'Permission'; +} + +sub object_class { + return 'Koha::Auth::Permission'; +} + +sub _get_castable_unique_columns { + return ['permission_id', 'code']; +} + +1; diff --git a/Koha/AuthUtils.pm b/Koha/AuthUtils.pm index 5fa0afb..7d01e28 100644 --- a/Koha/AuthUtils.pm +++ b/Koha/AuthUtils.pm @@ -166,7 +166,6 @@ sub _createTemporarySuperuser { firstname => $superuserName, surname => $superuserName, branchcode => 'NO_LIBRARY_SET', - flags => 1, email => C4::Context->preference('KohaAdminEmailAddress') }); return $borrower; diff --git a/Koha/Schema/Result/Borrower.pm b/Koha/Schema/Result/Borrower.pm index e9f8838..5356dfd 100644 --- a/Koha/Schema/Result/Borrower.pm +++ b/Koha/Schema/Result/Borrower.pm @@ -309,11 +309,6 @@ __PACKAGE__->table("borrowers"); is_nullable: 1 size: 60 -=head2 flags - - data_type: 'integer' - is_nullable: 1 - =head2 userid data_type: 'varchar' @@ -542,8 +537,6 @@ __PACKAGE__->add_columns( { data_type => "varchar", is_nullable => 1, size => 1 }, "password", { data_type => "varchar", is_nullable => 1, size => 60 }, - "flags", - { data_type => "integer", is_nullable => 1 }, "userid", { data_type => "varchar", is_nullable => 1, size => 75 }, "opacnote", @@ -648,6 +641,36 @@ __PACKAGE__->has_many( { cascade_copy => 0, cascade_delete => 0 }, ); +=head2 api_keys + +Type: has_many + +Related object: L + +=cut + +__PACKAGE__->has_many( + "api_keys", + "Koha::Schema::Result::ApiKey", + { "foreign.borrowernumber" => "self.borrowernumber" }, + { cascade_copy => 0, cascade_delete => 0 }, +); + +=head2 api_timestamp + +Type: might_have + +Related object: L + +=cut + +__PACKAGE__->might_have( + "api_timestamp", + "Koha::Schema::Result::ApiTimestamp", + { "foreign.borrowernumber" => "self.borrowernumber" }, + { cascade_copy => 0, cascade_delete => 0 }, +); + =head2 aqbasketusers Type: has_many @@ -753,6 +776,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 @@ -1053,21 +1091,6 @@ __PACKAGE__->has_many( { cascade_copy => 0, cascade_delete => 0 }, ); -=head2 user_permissions - -Type: has_many - -Related object: L - -=cut - -__PACKAGE__->has_many( - "user_permissions", - "Koha::Schema::Result::UserPermission", - { "foreign.borrowernumber" => "self.borrowernumber" }, - { cascade_copy => 0, cascade_delete => 0 }, -); - =head2 virtualshelfcontents Type: has_many @@ -1154,8 +1177,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 diff --git a/Koha/Schema/Result/Permission.pm b/Koha/Schema/Result/Permission.pm index e2444ee..47d8b83 100644 --- a/Koha/Schema/Result/Permission.pm +++ b/Koha/Schema/Result/Permission.pm @@ -23,17 +23,22 @@ __PACKAGE__->table("permissions"); =head1 ACCESSORS -=head2 module_bit +=head2 permission_id data_type: 'integer' - default_value: 0 + is_auto_increment: 1 + is_nullable: 0 + +=head2 module + + data_type: 'varchar' is_foreign_key: 1 is_nullable: 0 + size: 32 =head2 code data_type: 'varchar' - default_value: (empty string) is_nullable: 0 size: 64 @@ -46,15 +51,12 @@ __PACKAGE__->table("permissions"); =cut __PACKAGE__->add_columns( - "module_bit", - { - data_type => "integer", - default_value => 0, - is_foreign_key => 1, - is_nullable => 0, - }, + "permission_id", + { data_type => "integer", is_auto_increment => 1, is_nullable => 0 }, + "module", + { data_type => "varchar", is_foreign_key => 1, is_nullable => 0, size => 32 }, "code", - { data_type => "varchar", default_value => "", is_nullable => 0, size => 64 }, + { data_type => "varchar", is_nullable => 0, size => 64 }, "description", { data_type => "varchar", is_nullable => 1, size => 255 }, ); @@ -63,7 +65,19 @@ __PACKAGE__->add_columns( =over 4 -=item * L +=item * L + +=back + +=cut + +__PACKAGE__->set_primary_key("permission_id"); + +=head1 UNIQUE CONSTRAINTS + +=head2 C + +=over 4 =item * L @@ -71,46 +85,43 @@ __PACKAGE__->add_columns( =cut -__PACKAGE__->set_primary_key("module_bit", "code"); +__PACKAGE__->add_unique_constraint("code", ["code"]); =head1 RELATIONS -=head2 module_bit +=head2 borrower_permissions -Type: belongs_to +Type: has_many -Related object: L +Related object: L =cut -__PACKAGE__->belongs_to( - "module_bit", - "Koha::Schema::Result::Userflag", - { bit => "module_bit" }, - { is_deferrable => 1, on_delete => "CASCADE", on_update => "CASCADE" }, +__PACKAGE__->has_many( + "borrower_permissions", + "Koha::Schema::Result::BorrowerPermission", + { "foreign.permission_id" => "self.permission_id" }, + { cascade_copy => 0, cascade_delete => 0 }, ); -=head2 user_permissions +=head2 module -Type: has_many +Type: belongs_to -Related object: L +Related object: L =cut -__PACKAGE__->has_many( - "user_permissions", - "Koha::Schema::Result::UserPermission", - { - "foreign.code" => "self.code", - "foreign.module_bit" => "self.module_bit", - }, - { cascade_copy => 0, cascade_delete => 0 }, +__PACKAGE__->belongs_to( + "module", + "Koha::Schema::Result::PermissionModule", + { module => "module" }, + { is_deferrable => 1, on_delete => "CASCADE", on_update => "CASCADE" }, ); -# Created by DBIx::Class::Schema::Loader v0.07025 @ 2013-10-14 20:56:21 -# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:Ut3lzlxoPPoIIwmhJViV1Q +# Created by DBIx::Class::Schema::Loader v0.07039 @ 2015-07-28 17:08:28 +# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:ugaurNx48ISDyRgoAlBk2A # You can replace this text with custom content, and it will be preserved on regeneration diff --git a/installer/data/mysql/en/mandatory/userflags.sql b/installer/data/mysql/en/mandatory/userflags.sql index d8e3626..7148682 100644 --- a/installer/data/mysql/en/mandatory/userflags.sql +++ b/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') ; diff --git a/installer/data/mysql/en/mandatory/userpermissions.sql b/installer/data/mysql/en/mandatory/userpermissions.sql index 7b50d51..c8cae26 100644 --- a/installer/data/mysql/en/mandatory/userpermissions.sql +++ b/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') ; diff --git a/installer/data/mysql/kohastructure.sql b/installer/data/mysql/kohastructure.sql index 13b6a6a..aadf087 100644 --- a/installer/data/mysql/kohastructure.sql +++ b/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 @@ -879,7 +878,6 @@ CREATE TABLE `deletedborrowers` ( -- stores data related to the patrons/borrower `ethnotes` varchar(255) default NULL, -- unused in Koha `sex` varchar(1) default NULL, -- patron/borrower's gender `password` varchar(30) 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(30) 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 @@ -2291,19 +2289,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` -- @@ -2484,20 +2469,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` -- @@ -2512,21 +2483,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` -- @@ -3338,7 +3294,6 @@ CREATE TABLE IF NOT EXISTS `borrower_modifications` ( `ethnotes` varchar(255) DEFAULT NULL, `sex` varchar(1) DEFAULT NULL, `password` varchar(30) DEFAULT NULL, - `flags` int(11) DEFAULT NULL, `userid` varchar(75) DEFAULT NULL, `opacnote` mediumtext, `contactnote` varchar(255) DEFAULT NULL, @@ -3378,6 +3333,55 @@ CREATE TABLE uploaded_files ( ) 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 -- diff --git a/installer/data/mysql/updatedatabase.pl b/installer/data/mysql/updatedatabase.pl index 10809a1..a35c334 100755 --- a/installer/data/mysql/updatedatabase.pl +++ b/installer/data/mysql/updatedatabase.pl @@ -7822,6 +7822,119 @@ if ( CheckVersion($DBversion) ) { SetVersion($DBversion); } +$DBversion = "3.21.00.XXX"; +if ( CheckVersion($DBversion) ) { + my $upgradeDone; + my $permissionModule = $dbh->selectrow_hashref("SELECT * FROM permission_modules LIMIT 1"); + if ($permissionModule) { + print "Upgrade to $DBversion ALREADY DONE?!? (Bug 14540 - Move member-flags.pl to PermissionsManager to better manage permissions for testing.)\n"; + $upgradeDone = 1; + } + unless ($upgradeDone) { + ##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;"); + $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("ALTER TABLE permissions RENAME TO permissions_old"); + $dbh->do("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;"); + $dbh->do("INSERT INTO permissions (module, code, description) + SELECT userflags.flag, code, description FROM permissions_old + LEFT JOIN userflags ON permissions_old.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 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;"); + $dbh->do("INSERT INTO borrower_permissions (borrowernumber, permission_module_id, permission_id) + SELECT user_permissions.borrowernumber, user_permissions.module_bit, permissions.permission_id + FROM user_permissions + LEFT JOIN permissions ON user_permissions.code = permissions.code + LEFT JOIN borrowers ON borrowers.borrowernumber = user_permissions.borrowernumber + WHERE borrowers.borrowernumber IS NOT NULL;"); #It is possible to have user_permissions dangling around not pointing to anything, so make sure we dont try to move nonexisting borrower permissions. + + ##Add subpermissions to the stand-alone modules (modules with no subpermissions) + $dbh->do("INSERT INTO permissions (module, code, description) VALUES ('superlibrarian', 'superlibrarian', 'Access to all librarian functions.');"); + $dbh->do("INSERT INTO permissions (module, code, description) VALUES ('catalogue', 'staff_login', 'Allow staff login.');"); + $dbh->do("INSERT INTO permissions (module, code, description) VALUES ('borrowers', 'view_borrowers', 'Show borrower details and search for borrowers.');"); + $dbh->do("INSERT INTO permissions (module, code, description) VALUES ('permissions', 'set_permissions', 'Set user permissions.');"); + $dbh->do("INSERT INTO permissions (module, code, description) VALUES ('management', 'management', 'Set library management parameters (deprecated).');"); + $dbh->do("INSERT INTO permissions (module, code, description) VALUES ('editauthorities', 'edit_authorities', 'Edit authorities.');"); + $dbh->do("INSERT INTO permissions (module, code, description) VALUES ('staffaccess', 'staff_access_permissions', 'Allow staff members to modify permissions for other staff members.');"); + + ##Create borrower_permissions to replace singular userflags from borrowers.flags. + use Koha::Borrowers; + my $sth = $dbh->prepare(" + INSERT INTO borrower_permissions (borrowernumber, permission_module_id, permission_id) + VALUES (?, + (SELECT permission_module_id FROM permission_modules WHERE module = ?), + (SELECT permission_id FROM permissions WHERE code = ?) + ); + "); + my @borrowers = Koha::Borrowers->search({}); + foreach my $b (@borrowers) { + next unless $b->flags; + if ( ( $b->flags & ( 2**0 ) ) ) { + $sth->execute($b->borrowernumber, 'superlibrarian', 'superlibrarian'); + } + if ( ( $b->flags & ( 2**2 ) ) ) { + $sth->execute($b->borrowernumber, 'catalogue', 'staff_login'); + } + if ( ( $b->flags & ( 2**4 ) ) ) { + $sth->execute($b->borrowernumber, 'borrowers', 'view_borrowers'); + } + if ( ( $b->flags & ( 2**5 ) ) ) { + $sth->execute($b->borrowernumber, 'permissions', 'set_permissions'); + } + if ( ( $b->flags & ( 2**12 ) ) ) { + $sth->execute($b->borrowernumber, 'management', 'management'); + } + if ( ( $b->flags & ( 2**14 ) ) ) { + $sth->execute($b->borrowernumber, 'editauthorities', 'edit_authorities'); + } + if ( ( $b->flags & ( 2**17 ) ) ) { + $sth->execute($b->borrowernumber, '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_old"); + $dbh->do("DROP TABLE userflags"); + $dbh->do("ALTER TABLE borrowers DROP COLUMN flags"); + + print "Upgrade to $DBversion done (Bug 14540 - Move member-flags.pl to PermissionsManager to better manage permissions for testing.)\n"; + SetVersion($DBversion); + } +} + $DBversion = "3.15.00.002"; if(CheckVersion($DBversion)) { $dbh->do("ALTER TABLE deleteditems MODIFY materials text;"); diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/members/member-flags.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/members/member-flags.tt index c1e751e..6427e05 100644 --- a/koha-tmpl/intranet-tmpl/prog/en/modules/members/member-flags.tt +++ b/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 ) %] diff --git a/members/member-flags.pl b/members/member-flags.pl index 68c52aa..7a4dc8b 100755 --- a/members/member-flags.pl +++ b/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; +} \ No newline at end of file diff --git a/misc/devel/interactiveWebDriverShell.pl b/misc/devel/interactiveWebDriverShell.pl index 72d4df9..0af4a2b 100755 --- a/misc/devel/interactiveWebDriverShell.pl +++ b/misc/devel/interactiveWebDriverShell.pl @@ -115,13 +115,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"], }, "catalogue/detail.pl" => @@ -138,6 +138,11 @@ my $supportedPageObjects = { urlEndpoint => "opac/opac-main.pl", status => "OK", }, + "opac/opac-search.pl" => + { package => "t::lib::Page::Opac::OpacSearch", + urlEndpoint => "opac/opac-search.pl", + status => "OK", + }, }; ################################################################################ ########## END OF PAGE CONFIGURATIONS ########## diff --git a/opac/opac-user.pl b/opac/opac-user.pl index d4fbbe3..a653705 100755 --- a/opac/opac-user.pl +++ b/opac/opac-user.pl @@ -349,7 +349,7 @@ foreach my $res (@reserves) { $template->param( WAITING => \@waiting ); # current alert subscriptions -my $alerts = getalert($borrowernumber); +my $alerts = getalert($borrowernumber) if $borrowernumber; foreach ( @$alerts ) { $_->{ $_->{type} } = 1; $_->{relatedto} = findrelatedto( $_->{type}, $_->{externalid} ); diff --git a/t/db_dependent/Koha/Auth.t b/t/db_dependent/Koha/Auth.t index 441070d..51d3af2 100644 --- a/t/db_dependent/Koha/Auth.t +++ b/t/db_dependent/Koha/Auth.t @@ -22,7 +22,10 @@ use Modern::Perl; use Test::More; use Try::Tiny; #Even Selenium::Remote::Driver uses Try::Tiny :) +use Koha::Auth::PermissionManager; + use t::lib::Page::Mainpage; +use t::lib::Page::Opac::OpacMain; use t::lib::TestObjects::BorrowerFactory; @@ -36,17 +39,36 @@ my $borrowers = $borrowerFactory->createTestGroup([ surname => 'Kivi', cardnumber => '1A01', branchcode => 'CPL', - flags => '1', #superlibrarian, not exactly a very good way of doing permission testing? userid => 'mini_admin', password => $password, }, + {firstname => 'Admin', + surname => 'Administrative', + cardnumber => 'maxi_admin', + branchcode => 'FPL', + userid => 'maxi_admin', + password => $password, + }, ], undef, $testContext); +my $permissionManager = Koha::Auth::PermissionManager->new(); +$permissionManager->grantPermission($borrowers->{'1A01'}, 'catalogue', 'staff_login'); +$permissionManager->grantPermission($borrowers->{'maxi_admin'}, 'superlibrarian', 'superlibrarian'); + ##Test context set, starting testing: eval { #run in a eval-block so we don't die without tearing down the test context - testPasswordLogin(); + my $mainpage = t::lib::Page::Mainpage->new(); + testPasswordLoginLogout($mainpage); + testSuperuserPasswordLoginLogout($mainpage); + testSuperlibrarianPasswordLoginLogout($mainpage); + $mainpage->quit(); + my $opacmain = t::lib::Page::Opac::OpacMain->new(); + testOpacPasswordLoginLogout($opacmain); + testSuperuserPasswordLoginLogout($opacmain); + testOpacSuperlibrarianPasswordLoginLogout($opacmain); + $opacmain->quit(); }; if ($@) { #Catch all leaking errors and gracefully terminate. warn $@; @@ -66,7 +88,30 @@ sub tearDown { ### STARTING TEST IMPLEMENTATIONS ### ###################################################### -sub testPasswordLogin { - my $mainpage = t::lib::Page::Mainpage->new(); - $mainpage->isPasswordLoginAvailable()->doPasswordLogin($borrowers->{'1A01'}->userid(), $password)->quit(); -} \ No newline at end of file +sub testPasswordLoginLogout { + my ($mainpage) = @_; + $mainpage->isPasswordLoginAvailable()->doPasswordLogin($borrowers->{'1A01'}->userid(), $password) + ->isLoggedInBranchCode($borrowers->{'1A01'}->branchcode()) + ->doPasswordLogout(); +} +sub testOpacPasswordLoginLogout { + my ($mainpage) = @_; + $mainpage->isPasswordLoginAvailable()->doPasswordLogin($borrowers->{'1A01'}->userid(), $password) + ->doPasswordLogout(); +} +sub testSuperuserPasswordLoginLogout { + my ($mainpage) = @_; + $mainpage->isPasswordLoginAvailable()->doPasswordLogin(C4::Context->config('user'), C4::Context->config('pass')) + ->doPasswordLogout(); +} +sub testSuperlibrarianPasswordLoginLogout { + my ($mainpage) = @_; + $mainpage->isPasswordLoginAvailable()->doPasswordLogin($borrowers->{'maxi_admin'}->userid(), $password) + ->isLoggedInBranchCode($borrowers->{'maxi_admin'}->branchcode()) + ->doPasswordLogout(); +} +sub testOpacSuperlibrarianPasswordLoginLogout { + my ($mainpage) = @_; + $mainpage->isPasswordLoginAvailable()->doPasswordLogin($borrowers->{'maxi_admin'}->userid(), $password) + ->doPasswordLogout(); +} diff --git a/t/db_dependent/Koha/Auth/BorrowerPermission.t b/t/db_dependent/Koha/Auth/BorrowerPermission.t new file mode 100644 index 0000000..02dd0e7 --- /dev/null +++ b/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::lib::TestObjects::ObjectFactory; +use t::lib::TestObjects::BorrowerFactory; + +##Setting up the test context +my $testContext = {}; + +my $borrowerFactory = t::lib::TestObjects::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::lib::TestObjects::ObjectFactory->tearDownTestContext($testContext); +} \ No newline at end of file diff --git a/t/db_dependent/Koha/Auth/PermissionManager.t b/t/db_dependent/Koha/Auth/PermissionManager.t new file mode 100644 index 0000000..513bb08 --- /dev/null +++ b/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::lib::TestObjects::ObjectFactory; +use t::lib::TestObjects::BorrowerFactory; + + +##Setting up the test context +my $testContext = {}; + +my $borrowerFactory = t::lib::TestObjects::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::lib::TestObjects::ObjectFactory->tearDownTestContext($testContext); +} \ No newline at end of file diff --git a/t/db_dependent/Members/member-flags.t b/t/db_dependent/Members/member-flags.t new file mode 100644 index 0000000..e93579e --- /dev/null +++ b/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::lib::TestObjects::BorrowerFactory; +use Koha::Auth::PermissionManager; + + +##Setting up the test context +my $testContext = {}; + +my $password = '1234'; +my $borrowerFactory = t::lib::TestObjects::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::lib::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'}); +} \ No newline at end of file diff --git a/t/db_dependent/Opac/opac-search.t b/t/db_dependent/Opac/opac-search.t new file mode 100644 index 0000000..fae1ce2 --- /dev/null +++ b/t/db_dependent/Opac/opac-search.t @@ -0,0 +1,79 @@ +#!/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 t::lib::Page::Opac::OpacSearch; +use t::lib::TestObjects::BorrowerFactory; + + +##Setting up the test context +my $testContext = {}; + +my $password = '1234'; +my $borrowerFactory = t::lib::TestObjects::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 + + my $opacsearch = t::lib::Page::Opac::OpacSearch->new(); + testAnonymousSearchHistory($opacsearch); + +}; +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::lib::TestObjects::ObjectFactory->tearDownTestContext($testContext); +} + +sub testAnonymousSearchHistory { + my $opacsearch = shift; + + $opacsearch->doSetSearchFieldTerm(1, 'Author', 'nengard')->doSearchSubmit()->navigateAdvancedSearch() + ->doSetSearchFieldTerm(1, 'Author', 'joubu')->doSearchSubmit()->navigateAdvancedSearch() + ->doSetSearchFieldTerm(1, 'Author', 'khall')->doSearchSubmit()->navigateHome() + ->doPasswordLogin($borrowers->{'1A01'}->userid, $password)->navigateAdvancedSearch() + ->doSetSearchFieldTerm(1, 'Author', 'magnuse')->doSearchSubmit()->navigateAdvancedSearch() + ->doSetSearchFieldTerm(1, 'Author', 'cait')->doSearchSubmit()->navigateSearchHistory() + ->testDoSearchHistoriesExist(['nengard', + 'joubu', + 'khall', + 'magnuse', + 'cait']) + ->quit(); +} \ No newline at end of file -- 1.9.1