From ff499f2eb95574d200e0962e5e3cb160f39d3e6d Mon Sep 17 00:00:00 2001 From: David Cook Date: Wed, 12 May 2021 07:30:24 +0000 Subject: [PATCH] Bug 31380: Build core functionality for Mojolicious controllers This patch adds a new mount point in plack.psgi, which makes Koha able to run Mojolicious controllers. For example: http://localhost:8081/cgi-bin/koha/staff/plugins/run 0a) Apply patch 0b) cp debian/templates/plack.psgi /etc/koha/sites/kohadev/plack.psgi 0c) koha-plack --restart 1) Upload Koha Plugin koha-plugin-mojolicious.kpz 2) koha-plack --restart 3) Go to http://localhost:8081/cgi-bin/koha/staff/plugins/run?class=Koha%3A%3APlugin%3A%3AProsentient%3A%3AMojolicious&method=tool 4) Note that it looks perfect 5) The following URL will generate a 500 error, but technically the Koha Plugin could easily be crafted to support both CGI and Mojolicious::Controller objects: http://localhost:8081/cgi-bin/koha/plugins/run.pl?class=Koha%3A%3APlugin%3A%3AProsentient%3A%3AMojolicious&method=tool --- Koha/Auth.pm | 130 +++++++++++++++++++++++++++++++ Koha/Staff.pm | 88 +++++++++++++++++++++ Koha/Staff/Controller/Plugins.pm | 40 ++++++++++ Koha/Template.pm | 41 ++++++++++ debian/templates/plack.psgi | 32 ++++++++ staff | 21 +++++ 6 files changed, 352 insertions(+) create mode 100644 Koha/Auth.pm create mode 100644 Koha/Staff.pm create mode 100644 Koha/Staff/Controller/Plugins.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..84bc802922 --- /dev/null +++ b/Koha/Auth.pm @@ -0,0 +1,130 @@ +package Koha::Auth; + +use Modern::Perl; + +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 + +sub authenticate { + my ($class,$args) = @_; + my ($auth_user,$auth_session); + my $sessionID = $args->{sessionID}; + if ($sessionID){ + my $flags; + my ( $return, $session, $more_info) = C4::Auth::check_cookie_auth($sessionID,$flags, + { remote_addr => $ENV{REMOTE_ADDR}, skip_version_check => 1 } + ); + if ($return && $return eq 'ok' && $session){ + my $userid = $session->param('id'); + if ( $userid ){ + my $patron = Koha::Patrons->find({ userid => $userid }); + if ($patron){ + $auth_user = $patron; + $auth_session = $session; + } + } + } + } + return ($auth_user,$auth_session); +} + +=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: Replace using BZ 31389 + #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 @flagroots... + 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..2064368324 --- /dev/null +++ b/Koha/Staff.pm @@ -0,0 +1,88 @@ +package Koha::Staff; + +use Mojo::Base 'Mojolicious'; +use C4::Context; + +use Koha::Auth; +use Koha::Template; + +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 $plugins = $auth->under('plugins'); + #NOTE: Mojolicious 9 seems to require explicitly defining all routes (rather than getting :controller and :action from the path... + $plugins->any(['GET','POST'] => '/run')->to(controller => 'Plugins', action => 'run'); + #FIXME: handle 404 errors with catch all routes +} + +1; diff --git a/Koha/Staff/Controller/Plugins.pm b/Koha/Staff/Controller/Plugins.pm new file mode 100644 index 0000000000..8e1b02c8e1 --- /dev/null +++ b/Koha/Staff/Controller/Plugins.pm @@ -0,0 +1,40 @@ +package Koha::Staff::Controller::Plugins; + +# 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 Mojo::Base 'Mojolicious::Controller'; + +use C4::Context; +use Koha::Plugins::Handler; + +sub run { + my $c = shift; + my $class = $c->param('class'); + my $method = $c->param('method'); + my ($flags,$loggedinuser) = $c->staff_authorize({ flagsrequired => { 'plugins' => $method } }); + my $plugins_enabled = C4::Context->config("enable_plugins"); + if ( $plugins_enabled ) { + my $plugin = Koha::Plugins::Handler->run( { class => $class, method => $method, cgi => $c } ); + } else { + my $template = $c->prepare_template({ + template_filename => 'plugins/plugins-disabled.tt', + interface => 'intranet', + }); + $c->render( text => $template->output ); + } +} + +1; diff --git a/Koha/Template.pm b/Koha/Template.pm new file mode 100644 index 0000000000..5ae1dbdac6 --- /dev/null +++ b/Koha/Template.pm @@ -0,0 +1,41 @@ +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){ + #FIXME: See Bug 27293 about refactoring C4::Templates::gettemplate for providing the "language" param and "KohaOpacLanguage" cookie + $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 89cd6ef48f..94ca5f792c 100644 --- a/debian/templates/plack.psgi +++ b/debian/templates/plack.psgi @@ -66,6 +66,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 { @@ -114,6 +127,25 @@ builder { } $intranet; }; + mount '/intranet/staff' => builder { + #NOTE: it is important that these are relative links + enable 'ErrorDocument', + 400 => 'errors/400.pl', + 401 => 'errors/401.pl', + 402 => 'errors/402.pl', + 403 => 'errors/403.pl', + 404 => 'errors/404.pl', + 500 => 'errors/500.pl', + subrequest => 1; + #NOTE: Without this middleware to catch fatal errors, ErrorDocument won't be able to render a 500 document + #NOTE: This middleware must be closer to the PSGI app than ErrorDocument + enable "HTTPExceptions"; + if ( Log::Log4perl->get_logger('plack-intranet')->has_appenders ){ + enable 'Log4perl', category => 'plack-intranet'; + enable 'LogWarn'; + } + $staff_interface; + }; mount '/api/v1/app.pl' => builder { if ( Log::Log4perl->get_logger('plack-api')->has_appenders ){ enable 'Log4perl', category => 'plack-api'; 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.30.2