From 590b7f2f2b5e48f3df0161b11e9cb443fc538cde Mon Sep 17 00:00:00 2001 From: David Cook Date: Mon, 16 Nov 2020 09:59:59 +0000 Subject: [PATCH] Bug 26791: Replace export.pl with export Mojolicious controller --- Koha/Auth.pm | 151 ++++++++++++++++++++ Koha/Staff.pm | 85 +++++++++++ Koha/Staff/Controller/Tools/Export.pm | 155 +++++++++++++++++++++ Koha/Template.pm | 40 ++++++ debian/templates/plack.psgi | 20 +++ .../intranet-tmpl/prog/en/includes/tools-menu.inc | 2 +- .../intranet-tmpl/prog/en/modules/tools/export.tt | 8 +- .../prog/en/modules/tools/tools-home.tt | 2 +- staff | 21 +++ 9 files changed, 478 insertions(+), 6 deletions(-) create mode 100644 Koha/Auth.pm create mode 100644 Koha/Staff.pm create mode 100644 Koha/Staff/Controller/Tools/Export.pm create mode 100644 Koha/Template.pm create mode 100755 staff diff --git a/Koha/Auth.pm b/Koha/Auth.pm new file mode 100644 index 0000000000..38e86d150c --- /dev/null +++ b/Koha/Auth.pm @@ -0,0 +1,151 @@ +package Koha::Auth; + +use strict; +use warnings; + +use C4::Auth qw//; +use C4::Context qw//; + +=head2 authenticate + + my $user = Koha::Auth->authenticate({ + sessionID => $sessionID, + }); + +Verifies that this user has an authenticated Koha session + +=cut + +#NOTE: This method requires that a Koha session exists +sub authenticate { + my ($class,$args) = @_; + my ($auth_user,$auth_session,$auth_cookie); + my $sessionID = $args->{sessionID}; + if ($sessionID){ + my $session = C4::Auth::get_session($sessionID); + if ($session){ + my $id = $session->param('id'); + if ( $id ){ + my $patrons = Koha::Patrons->search({ userid => $id }); + if ($patrons->count){ + $auth_user = $patrons->next; + $auth_session = $session; + _set_userenv_compat({ session => $session }); + } + } + } + } + if ($auth_user){ + #FIXME: Check for timeout + } + #FIXME: Ideally, it would be nice to calculate patron authorizations at login time, + #rather than on each page load (like we do currently)... + return ($auth_user,$auth_session,$auth_cookie); +} + +#NOTE: Needed since C4::Context->userenv is so ubiquitous +sub _set_userenv_compat { + my ($args) = @_; + my $session = $args->{session}; + if ($session){ + C4::Context->_new_userenv($session->param('id')); + C4::Context->set_userenv( + $session->param('number'), $session->param('id'), + $session->param('cardnumber'), $session->param('firstname'), + $session->param('surname'), $session->param('branch'), + $session->param('branchname'), $session->param('flags'), + $session->param('emailaddress'), $session->param('shibboleth'), + $session->param('desk_id'), $session->param('desk_name') + ); + } +} + +=head2 authorize + + my $flags = Koha::Auth->authorize({ + session => $session, + flagsrequired => { self_check => 'self_checkout_module' }, + }); + +=cut + +sub authorize { + my ($class,$args) = @_; + my $flags; + my $session = $args->{session}; + my $flagsrequired = $args->{flagsrequired}; + if ($session && $flagsrequired){ + my $userid = $session->param('id'); + if ($userid){ + $flags = C4::Auth::haspermission($userid,$flagsrequired); + } + } + return $flags; +} + +sub get_authz_from_flags { + my ($args) = @_; + my $flags = $args->{flags}; + my $authz; + + #FIXME: This contains a lot of copy/paste from C4::Auth + my $all_perms = C4::Auth::get_all_subpermissions(); + + if ($flags){ + if ( $flags->{superlibrarian} == 1 ){ + #NOTE: These are similar but slightly different to @flatroots... + my @perms = qw/ + circulate + catalogue + parameters + borrowers + permissions + reserveforothers + editcatalogue + updatecharges + acquisition + tools + editauthorities + serials + reports + staffaccess + plugins + coursereserves + clubs + ill + stockrotation + problem_reports + /; + foreach my $perm (@perms){ + $authz->{ "CAN_user_${perm}" } = 1; + } + foreach my $module ( keys %$all_perms ) { + foreach my $subperm ( keys %{ $all_perms->{$module} } ) { + $authz->{ "CAN_user_${module}_${subperm}" } = 1; + } + } + } + else { + foreach my $module ( keys %$all_perms ) { + if ( defined($flags->{$module}) && $flags->{$module} == 1 ) { + foreach my $subperm ( keys %{ $all_perms->{$module} } ) { + $authz->{ "CAN_user_${module}_${subperm}" } = 1; + } + } elsif ( ref( $flags->{$module} ) ) { + foreach my $subperm ( keys %{ $flags->{$module} } ) { + $authz->{ "CAN_user_${module}_${subperm}" } = 1; + } + } + } + foreach my $module ( keys %$flags ) { + if ( $flags->{$module} == 1 or ref( $flags->{$module} ) ) { + $authz->{ "CAN_user_$module" } = 1; + } + } + } + } + + return $authz; +} + +1; diff --git a/Koha/Staff.pm b/Koha/Staff.pm new file mode 100644 index 0000000000..8654752292 --- /dev/null +++ b/Koha/Staff.pm @@ -0,0 +1,85 @@ +package Koha::Staff; + +use Mojo::Base 'Mojolicious'; +use Koha::Auth; +use Koha::Template; +use C4::Context; + +sub startup { + my $self = shift; + + #FIXME: Move into a plugin? + $self->helper( + staff_authorize => sub { + my ($c,$args) = @_; + my $session = $c->stash->{__koha_session__}; + my $flags = Koha::Auth->authorize({ + session => $session, + flagsrequired => $args->{flagsrequired}, + }); + if($flags){ + $c->stash->{__koha_flags__} = $flags; + $c->stash->{__koha_authz__} = Koha::Auth::get_authz_from_flags({ flags => $flags }); + return ($flags,$c->stash->{__koha__user__}); + } + else { + $c->render( text => 'Forbidden', status => 403 ); + return; + } + } + ); + #FIXME: Move into a plugin? + $self->helper( + prepare_template => sub { + my ($c,$args) = @_; + my $template_filename = $args->{template_filename}; + my $interface = $args->{interface}; + my $template = Koha::Template::prepare_template({ + template_filename => $template_filename, + interface => $interface, + koha_session => $c->stash->{__koha_session__}, + koha_user => $c->stash->{__koha_user__}, + koha_authz => $c->stash->{__koha_authz__}, + }); + return $template; + } + ); + + # Router + my $r = $self->routes; + + #NOTE: The /login route is public and does not require authentication + #FIXME: Implement a Mojolicious login route + #$r->get('/login')->to( controller => 'login', action => 'index' ); + + #NOTE: All other routes require authentication + my $auth = $r->under('/' => sub { + my $c = shift; + my $sessionID = $c->cookie('CGISESSID'); + my ($user,$session) = Koha::Auth->authenticate({ + sessionID => $sessionID, + }); + if ($user && $session){ + $c->stash->{__koha_user__} = $user; + $c->stash->{__koha_session__} = $session; + $c->cookie( + 'CGISESSID' => $session->id, + { + httponly => 1, + secure => ( C4::Context->https_enabled() ? 1 : 0 ), + path => '/', + } + ); + return 1; + } + else { + #FIXME: In future, redirect to a /login route + $c->redirect_to('/index.html'); + return; + } + }); + my $tools = $auth->under('tools'); + $tools->any(['GET','POST'] => '/:controller/:action' => { action => 'index' })->to(namespace => 'Koha::Staff::Controller::Tools'); +} + +1; diff --git a/Koha/Staff/Controller/Tools/Export.pm b/Koha/Staff/Controller/Tools/Export.pm new file mode 100644 index 0000000000..1440652094 --- /dev/null +++ b/Koha/Staff/Controller/Tools/Export.pm @@ -0,0 +1,155 @@ +package Koha::Staff::Controller::Tools::Export; + +use Mojo::Base 'Mojolicious::Controller'; + +use Koha::Exporter::Record; +use C4::Biblio qw//; +use Encode; +use Carp; + +use Koha::ItemTypes; +use Koha::Authority::Types; +use Koha::Libraries; + +use Koha::Auth; + +sub index { + my $c = shift; + my ($flags,$loggedinuser) = $c->staff_authorize({ flagsrequired => { 'tools' => 'export_catalog' } }); + my $template = $c->prepare_template({ + template_filename => 'tools/export.tt', + interface => 'intranet', + }); + + #FIXME: This is for showing messages about the download... + my @messages = (); + $template->{VARS}->{messages} = \@messages; + + my $itemtypes = Koha::ItemTypes->search_with_localization; + $template->{VARS}->{itemtypes} = $itemtypes; + + my $authority_types = Koha::Authority::Types->search( {}, { order_by => ['authtypecode'] } ); + $template->{VARS}->{authority_types} = $authority_types; + + my $branch = $c->every_param('branch'); + my $libraries = Koha::Libraries->search_filtered({}, { order_by => ['branchname'] })->unblessed; + #NOTE: This will only work if we return to this action... + for my $library ( @$libraries ) { + $library->{selected} = 1 if grep { $library->{branchcode} eq $_ } @$branch; + } + $template->{VARS}->{libraries} = $libraries; + + my $backupdir = C4::Context->config('backupdir'); + if ( $flags->{superlibrarian} + && C4::Context->config('backup_db_via_tools') + && $backupdir + && -d $backupdir ) + { + $template->{VARS}->{'allow_db_export'} = 1; + $template->{VARS}->{'dbfiles'} = _getbackupfilelist( + { directory => "$backupdir", extension => 'sql' } ); + } + + if ( $flags->{superlibrarian} + && C4::Context->config('backup_conf_via_tools') + && $backupdir + && -d $backupdir ) + { + $template->{VARS}->{'allow_conf_export'} = 1; + $template->{VARS}->{'conffiles'} = _getbackupfilelist( + { directory => "$backupdir", extension => 'tar' } ); + } + + $c->render( text => $template->output ); +} + +sub _getbackupfilelist { + my $args = shift; + my $directory = $args->{directory}; + my $extension = $args->{extension}; + my @files; + + if ( opendir( my $dir, $directory ) ) { + while ( my $file = readdir($dir) ) { + next unless ( $file =~ m/\.$extension(\.(gz|bz2|xz))?/ ); + push @files, $file + if ( -f "$directory/$file" && -r "$directory/$file" ); + } + closedir($dir); + } + use Data::Dumper; + warn Dumper(\@files); + return \@files; +} + + +sub download { + my $c = shift; + my ($flags,$loggedinuser) = $c->staff_authorize({ flagsrequired => { 'tools' => 'export_catalog' } }); + + my $record_type = $c->param('record_type') || 'bibs'; #FIXME: Remove bibs? + my $backupdir = C4::Context->config('backupdir'); + + if ( $record_type eq 'bibs' or $record_type eq 'auths' ){ + #TODO: Refactor code from export.pl + } + elsif ( $record_type eq 'db' or $record_type eq 'conf' ){ + #TODO: Most of this code should go in a model module + if ( $flags->{superlibrarian} + and ( + $record_type eq 'db' and C4::Context->config('backup_db_via_tools') + or + $record_type eq 'conf' and C4::Context->config('backup_conf_via_tools') + ) + ) { + my $extension = $record_type eq 'db' ? 'sql' : 'tar'; + my $filename = $c->param('filename'); + $filename =~ s/(\r|\n)//; + my $fh = _get_backup_filehandle({ + directory => $backupdir, + extension => $extension, + filename => $filename, + }); + my $content_type = 'application/octet-stream'; + if ( $filename =~ m/\.gz$/ ){ + $content_type = 'application/x-gzip'; + } + elsif ( $filename =~ m/\.bz2$/ ){ + $content_type = 'application/x-bzip2'; + } + #FIXME: Add error handling + $c->res->headers->content_type($content_type); + $c->res->headers->content_disposition("attachment; filename=$filename;"); + my $drain; + $drain = sub { + my $c = shift; + read($fh,my $chunk, 64 * 1024); + if ($chunk){ + $c->write($chunk,$drain); + } + else { + $c->write(''); + } + }; + $c->$drain; + } + } +} + +#FIXME: Move this into a Koha::Exporter module +sub _get_backup_filehandle { + my ($args) = @_; + my $directory = $args->{directory}; + my $extension = $args->{extension}; + my $filename = $args->{filename}; + + return unless ( $directory && -d $directory ); + return unless ( $filename =~ m/\.$extension(\.(gz|bz2|xz))?$/ ); + return if ( $filename =~ m#/# ); + $filename = "$directory/$filename"; + return unless ( -f $filename && -r $filename ); + return unless ( open( my $fh, '<', $filename ) ); + return $fh; +} + +1; diff --git a/Koha/Template.pm b/Koha/Template.pm new file mode 100644 index 0000000000..fa0b629369 --- /dev/null +++ b/Koha/Template.pm @@ -0,0 +1,40 @@ +package Koha::Template; + +use strict; +use warnings; + +use C4::Templates; + +sub prepare_template { + my ($args) = @_; + my $template; + my $session = $args->{koha_session}; + my $user = $args->{koha_user}; + my $authz = $args->{koha_authz}; + my $template_filename = $args->{template_filename}; + my $interface = $args->{interface}; + if ($template_filename && $interface){ + $template = C4::Templates::gettemplate($template_filename,$interface); + if ($template){ + if ($session){ + $template->{VARS}->{ sessionID } = $session->param('id'); + } + if ($user){ + $template->{VARS}->{ logged_in_user } = $user; + $template->{VARS}->{ loggedinusernumber } = $user->borrowernumber; + $template->{VARS}->{ loggedinusername } = $user->userid; + } + if ($authz){ + foreach my $key ( keys %$authz ){ + $template->{VARS}->{ $key } = $authz->{$key}; + } + } + + #NOTE: You could include all the syspref stuff here that exists in C4::Auth, + #but I rather avoid it, and have templates update to use the Koha.Preference mechanism instead. + } + } + return $template; +} + +1; diff --git a/debian/templates/plack.psgi b/debian/templates/plack.psgi index 99438c595a..fc20ed1f40 100644 --- a/debian/templates/plack.psgi +++ b/debian/templates/plack.psgi @@ -67,6 +67,19 @@ my $apiv1 = builder { $server->to_psgi_app; }; +my $staff_interface = builder { + my $server = Mojo::Server::PSGI->new; + $server->build_app('Koha::Staff'); + my $app = $server->app; + $app->hook( before_dispatch => sub { + my $c = shift; + #NOTE: Rewrite the base path to strip off the mount prefix + my $path = $c->req->url->base->path(Mojo::Path->new); + + }); + $server->to_psgi_app; +}; + Koha::Logger->_init; builder { @@ -98,4 +111,11 @@ builder { } $apiv1; }; + mount '/intranet/staff' => builder { + if ( Log::Log4perl->get_logger('plack-intranet')->has_appenders ){ + enable 'Log4perl', category => 'plack-intranet'; + enable 'LogWarn'; + } + $staff_interface; + }; }; diff --git a/koha-tmpl/intranet-tmpl/prog/en/includes/tools-menu.inc b/koha-tmpl/intranet-tmpl/prog/en/includes/tools-menu.inc index ba3f54a281..b0d7deddaa 100644 --- a/koha-tmpl/intranet-tmpl/prog/en/includes/tools-menu.inc +++ b/koha-tmpl/intranet-tmpl/prog/en/includes/tools-menu.inc @@ -72,7 +72,7 @@
  • Automatic item modifications by age
  • [% END %] [% IF ( CAN_user_tools_export_catalog ) %] -
  • Export data
  • +
  • Export data
  • [% END %] [% IF ( CAN_user_tools_inventory ) %]
  • Inventory
  • diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/tools/export.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/tools/export.tt index 4e9a992c04..f65cafce39 100644 --- a/koha-tmpl/intranet-tmpl/prog/en/modules/tools/export.tt +++ b/koha-tmpl/intranet-tmpl/prog/en/modules/tools/export.tt @@ -48,7 +48,7 @@ Note : The items are exported by this tool unless specified.

    -
    +
    Select records to export
      @@ -167,7 +167,7 @@
      - +
      Select records to export
      1. @@ -229,7 +229,7 @@ [% IF ( allow_db_export ) %]
        - +

        Note : This export file will be very large, and is generated nightly.

        Choose a file @@ -255,7 +255,7 @@ [% IF ( allow_conf_export ) %]
        - +

        Note : This export file will be very large, and is generated nightly.

        Choose a file diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/tools/tools-home.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/tools/tools-home.tt index c8d7f7d567..61a42c3e26 100644 --- a/koha-tmpl/intranet-tmpl/prog/en/modules/tools/tools-home.tt +++ b/koha-tmpl/intranet-tmpl/prog/en/modules/tools/tools-home.tt @@ -195,7 +195,7 @@ [% END %] [% IF ( CAN_user_tools_export_catalog ) %] -
        Export data
        +
        Export data
        Export bibliographic, holdings, and authority records
        [% END %] diff --git a/staff b/staff new file mode 100755 index 0000000000..f5b4a97808 --- /dev/null +++ b/staff @@ -0,0 +1,21 @@ +#!/usr/bin/perl + +# 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; + +require Mojolicious::Commands; +Mojolicious::Commands->start_app('Koha::Staff'); -- 2.11.0