From f986c2b8b2502d1d67aea08cdf19f73ed0e76d34 Mon Sep 17 00:00:00 2001 From: Paul Derscheid Date: Tue, 13 Jan 2026 10:54:25 +0100 Subject: [PATCH] Bug 37025: (QA follow-up) Use standard koha- script invocation pattern - Add Koha::Perl::Instance module for shared environment setup logic - Script now takes instance name as last argument - Re-exec with proper environment setup (PERL5LIB, KOHA_CONF) - Pass arguments directly via exec() to avoid shell quoting issues - Support both package and git installs - Use Koha::Script to set CLI interface for proper audit logging - Add APIKEY module to log viewer - Add translation for GENERATE action - Usage: sudo koha-api-keys [options] instancename - Update POD and DocBook XML documentation The new Koha::Perl::Instance module provides reusable functions for other perl-based debian scripts that need to run in a Koha environment: - in_koha_env(): Check if already running in Koha environment - get_instance_perl5lib(): Get PERL5LIB for an instance - reexec_in_koha_env(): Re-execute script with proper environment Test plan: NOTE: For testing, copy files first: cp debian/scripts/koha-api-keys /usr/sbin/ mkdir -p /usr/share/koha/bin/Koha/Perl cp debian/scripts/Koha/Perl/Instance.pm /usr/share/koha/bin/Koha/Perl/ 1. Test help output: sudo koha-api-keys --help 2. Test API key generation (with spaces in description): sudo koha-api-keys --generate --description 'Test key' --borrowernumber 51 instancename => Should output Client ID and Client Secret 3. Test interactive deletion: sudo koha-api-keys --delete instancename => Should prompt for Client ID, then delete 4. Test scripted deletion: sudo koha-api-keys --delete --client-id CLIENT_ID instancename => Should delete without prompting 5. Test error handling: sudo koha-api-keys --generate --description test instancename => Should show error about missing --borrowernumber 6. Test invalid instance: sudo koha-api-keys --generate --description test --borrowernumber 51 nonexistent => Should show error about instance not existing 7. Verify audit logging in log viewer: - Go to Tools > Log viewer - Select "API keys" module - Should see GENERATE and DELETE actions with interface "Command-line" 8. Verify DocBook XML validation: prove -v xt/verify-debian-docbook.t --- debian/docs/koha-api-keys.xml | 26 +-- debian/koha-common.install | 1 + debian/koha-core.install | 1 + debian/scripts/Koha/Perl/Instance.pm | 197 ++++++++++++++++++ debian/scripts/koha-api-keys | 45 ++-- .../prog/en/includes/action-logs.inc | 4 + .../prog/en/modules/tools/viewlog.tt | 2 +- 7 files changed, 249 insertions(+), 27 deletions(-) create mode 100644 debian/scripts/Koha/Perl/Instance.pm diff --git a/debian/docs/koha-api-keys.xml b/debian/docs/koha-api-keys.xml index 62bb2458f2c..75da7328d7c 100644 --- a/debian/docs/koha-api-keys.xml +++ b/debian/docs/koha-api-keys.xml @@ -29,19 +29,21 @@ - sudo koha-shell - "koha-api-keys description borrowernumber" + sudo koha-api-keys + + description + borrowernumber instancename - sudo koha-shell - "koha-api-keys [ client_id]" + sudo koha-api-keys + + client_id instancename - sudo koha-shell - "koha-api-keys " - instancename + sudo koha-api-keys + @@ -51,7 +53,7 @@ The koha-api-keys script allows administrators to generate or delete API keys associated with specific Koha patrons. These API keys can be used for authorized programmatic access to the Koha system. - As this tool requires administrative privileges and access to the Koha environment, it must be run using sudo and the koha-shell wrapper as shown in the synopsis. + The instance name must be provided as the last argument. The script will automatically set up the Koha environment and run as the instance user. @@ -100,16 +102,16 @@ Examples Generate a new API key for patron '123' with the description 'Test API access': - $ sudo koha-shell -c "koha-api-keys --generate --description 'Test API access' --borrowernumber 123" instancename + $ sudo koha-api-keys --generate --description 'Test API access' --borrowernumber 123 instancename Delete an existing API key (interactive): - $ echo "CLIENT_ID" | sudo koha-shell -c "koha-api-keys --delete" instancename + $ sudo koha-api-keys --delete instancename Delete an existing API key (scripted): - $ sudo koha-shell -c "koha-api-keys --delete --client-id a1b2c3d4-e5f6-7890-abcd-ef1234567890" instancename + $ sudo koha-api-keys --delete --client-id a1b2c3d4-e5f6-7890-abcd-ef1234567890 instancename Display help information: - $ sudo koha-shell -c "koha-api-keys --help" instancename + $ sudo koha-api-keys --help diff --git a/debian/koha-common.install b/debian/koha-common.install index 23c9b719f1e..344db89cc4f 100644 --- a/debian/koha-common.install +++ b/debian/koha-common.install @@ -7,6 +7,7 @@ debian/unavailable.html usr/share/koha/intranet/htdocs debian/unavailable.html usr/share/koha/opac/htdocs debian/templates/* etc/koha debian/scripts/koha-functions.sh usr/share/koha/bin +debian/scripts/Koha usr/share/koha/bin debian/scripts/koha-api-keys usr/sbin debian/scripts/koha-create usr/sbin debian/scripts/koha-create-dirs usr/sbin diff --git a/debian/koha-core.install b/debian/koha-core.install index 2b8c0f4646b..fdd36ec7db7 100644 --- a/debian/koha-core.install +++ b/debian/koha-core.install @@ -7,6 +7,7 @@ debian/unavailable.html usr/share/koha/intranet/htdocs debian/unavailable.html usr/share/koha/opac/htdocs debian/templates/* etc/koha debian/scripts/koha-functions.sh usr/share/koha/bin +debian/scripts/Koha usr/share/koha/bin debian/scripts/koha-create usr/sbin debian/scripts/koha-create-dirs usr/sbin debian/scripts/koha-disable usr/sbin diff --git a/debian/scripts/Koha/Perl/Instance.pm b/debian/scripts/Koha/Perl/Instance.pm new file mode 100644 index 00000000000..57aeb2fce9a --- /dev/null +++ b/debian/scripts/Koha/Perl/Instance.pm @@ -0,0 +1,197 @@ +package Koha::Perl::Instance; + +# Koha::Perl::Instance - Shared Perl utilities for koha-* debian scripts +# Copyright 2026 LMSCloud GmbH +# +# This program 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. +# +# This program 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 this program. If not, see . + +use Modern::Perl; + +use Exporter qw( import ); +our @EXPORT_OK = qw( in_koha_env get_instance_perl5lib reexec_in_koha_env ); + +=head1 NAME + +Koha::Perl::Instance - Shared utilities for Koha debian scripts + +=head1 SYNOPSIS + + use lib '/usr/share/koha/bin'; + use Koha::Perl::Instance qw( in_koha_env reexec_in_koha_env ); + + # Re-exec with Koha environment if not already set up + if ( !in_koha_env() ) { + my $instance = pop @ARGV; + reexec_in_koha_env({ instance => $instance, argv => \@ARGV }); + } + +=head1 DESCRIPTION + +This module provides shared utilities for Perl-based koha-* debian scripts +that need to run within a Koha instance's environment. + +=head1 FUNCTIONS + +=head2 in_koha_env + + if ( in_koha_env() ) { ... } + +Returns true if the script is running within a Koha environment +(i.e., KOHA_CONF is set). + +=cut + +sub in_koha_env { + return defined $ENV{KOHA_CONF} && $ENV{KOHA_CONF} ne q{}; +} + +=head2 get_instance_perl5lib + + my $perl5lib = get_instance_perl5lib($instance); + +Returns the appropriate PERL5LIB path for the given Koha instance. +Handles both package installs and git/dev installs. + +=cut + +sub get_instance_perl5lib { + my ($instance) = @_; + + my $koha_conf = "/etc/koha/sites/$instance/koha-conf.xml"; + my $perl5lib; + + # Check if this is a git install + my $is_git = `bash -c '. /usr/share/koha/bin/koha-functions.sh; if is_git_install $instance; then echo 1; fi'`; + chomp $is_git; + + if ($is_git) { + + # For git installs, get path from koha-conf.xml + $perl5lib = `xmlstarlet sel -t -v 'yazgfs/config/intranetdir' $koha_conf 2>/dev/null`; + chomp $perl5lib; + $perl5lib = "$perl5lib:$perl5lib/lib" if $perl5lib; + } + + if ( !$perl5lib ) { + + # Fall back to system default + $perl5lib = `grep "^PERL5LIB=" /etc/default/koha-common 2>/dev/null`; + chomp $perl5lib; + $perl5lib =~ s/^PERL5LIB=\s*//; + $perl5lib =~ s/^"//; + $perl5lib =~ s/"$//; + } + + return $perl5lib; +} + +=head2 reexec_in_koha_env + + reexec_in_koha_env({ + instance => $instance, + argv => \@ARGV, + script => $0, + }); + +Re-executes the current script with the proper Koha environment set up. +This function does not return on success. + +Arguments are passed directly to exec() without shell interpolation, +avoiding quoting issues. + +Parameters: + +=over 4 + +=item instance + +The Koha instance name (required). + +=item argv + +Array ref of arguments to pass to the re-executed script (default: current @ARGV). + +=item script + +Path to the script to execute (default: $0). + +=back + +Dies with an error message if: + +=over 4 + +=item * instance is not provided + +=item * instance does not exist + +=item * exec fails + +=back + +=cut + +sub reexec_in_koha_env { + my ($args) = @_; + + my $instance = $args->{instance}; + my $argv = $args->{argv} // \@ARGV; + my $script = $args->{script} // $0; + + die "Usage: sudo $0 [options] instancename\n" unless $instance; + die "Usage: sudo $0 [options] instancename\n" if $instance =~ /^-/; + die "Error: Koha instance '$instance' does not exist.\n" + unless -e "/etc/koha/sites/$instance"; + + my $perl5lib = get_instance_perl5lib($instance); + my $koha_conf = "/etc/koha/sites/$instance/koha-conf.xml"; + + exec( + '/usr/bin/sudo', '-u', "$instance-koha", '-H', + 'env', "PERL5LIB=$perl5lib", "KOHA_CONF=$koha_conf", + '/usr/bin/perl', $script, @{$argv} + ); + die "Failed to exec: $!\n"; +} + +1; + +__END__ + +=head1 AUTHOR + +Paul Derscheid + +=head1 COPYRIGHT + +Copyright LMSCloud GmbH + +=head1 LICENSE + +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 . + +=cut diff --git a/debian/scripts/koha-api-keys b/debian/scripts/koha-api-keys index cbae068a13d..7bb8652c51e 100755 --- a/debian/scripts/koha-api-keys +++ b/debian/scripts/koha-api-keys @@ -2,7 +2,7 @@ # koha-api-keys: generate or delete API keys for a given patron and # Koha instance. -# Copyright 2024 LMSCloud GmbH +# Copyright 2026 LMSCloud GmbH # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -21,10 +21,27 @@ use Modern::Perl; use Getopt::Long qw( GetOptions ); use Pod::Usage qw( pod2usage ); -use Koha::Script; -use Koha::ApiKeys; -use Koha::Patrons; -use C4::Log qw( logaction ); +use lib '/usr/share/koha/bin'; +use Koha::Perl::Instance qw( in_koha_env reexec_in_koha_env ); + +# Handle --help before re-exec (doesn't need Koha environment) +if ( grep { $_ eq '--help' || $_ eq '-h' } @ARGV ) { + pod2usage(1); +} + +# If not in Koha environment, re-exec with proper environment +if ( !in_koha_env() ) { + my $instance = pop @ARGV; + reexec_in_koha_env( { instance => $instance, argv => \@ARGV } ); +} + +# Now in Koha environment - load modules at runtime +require Koha::Script; +Koha::Script->import(); # Set CLI interface for proper audit logging +require Koha::ApiKeys; +require Koha::Patrons; +require C4::Log; +C4::Log->import('logaction'); my $generate; my $delete; @@ -143,11 +160,11 @@ koha-api-keys - Generate or delete API keys for specified patrons =head1 SYNOPSIS -sudo koha-shell -c "koha-api-keys --generate --description DESCRIPTION --borrowernumber BORROWERNUMBER" instancename +sudo koha-api-keys --generate --description DESCRIPTION --borrowernumber BORROWERNUMBER instancename -sudo koha-shell -c "koha-api-keys --delete [--client-id CLIENT_ID]" instancename +sudo koha-api-keys --delete [--client-id CLIENT_ID] instancename -sudo koha-shell -c "koha-api-keys --help" instancename +sudo koha-api-keys --help =head1 OPTIONS @@ -183,8 +200,8 @@ Display this help message. This script lets you generate or delete API keys for specified patrons in Koha. -As this tool requires administrative privileges and access to the Koha environment, -it must be run using sudo and koha-shell wrapper as shown in the synopsis. +The instance name must be provided as the last argument. The script will +automatically set up the Koha environment and run as the instance user. For generating keys, you must provide both a description and a borrower number. For deleting keys, you can provide the client ID via --client-id or be prompted interactively. @@ -192,17 +209,17 @@ For deleting keys, you can provide the client ID via --client-id or be prompted =head1 EXAMPLES Generate an API key: - sudo koha-shell -c "koha-api-keys --generate --description 'Test API access' --borrowernumber 123" instancename + sudo koha-api-keys --generate --description 'Test API access' --borrowernumber 123 instancename Delete an API key (interactive): - echo "CLIENT_ID" | sudo koha-shell -c "koha-api-keys --delete" instancename + sudo koha-api-keys --delete instancename Delete an API key (scripted): - sudo koha-shell -c "koha-api-keys --delete --client-id a1b2c3d4-e5f6-7890-abcd-ef1234567890" instancename + sudo koha-api-keys --delete --client-id a1b2c3d4-e5f6-7890-abcd-ef1234567890 instancename =head1 AUTHOR -Koha Development Team +Paul Derscheid =head1 COPYRIGHT diff --git a/koha-tmpl/intranet-tmpl/prog/en/includes/action-logs.inc b/koha-tmpl/intranet-tmpl/prog/en/includes/action-logs.inc index 9df6b43226f..7b71a6d1db7 100644 --- a/koha-tmpl/intranet-tmpl/prog/en/includes/action-logs.inc +++ b/koha-tmpl/intranet-tmpl/prog/en/includes/action-logs.inc @@ -1,5 +1,7 @@ [% BLOCK translate_log_module %] [% SWITCH module %] + [% CASE 'APIKEY' %] + API keys [% CASE 'AUTH' %] Authentication[% UNLESS Koha.Preference('AuthFailureLog') && Koha.Preference('AuthSuccessLog') %] @@ -61,6 +63,8 @@ Return [% CASE 'CREATE' %] Create + [% CASE 'GENERATE' %] + Generate [% CASE 'CANCEL' %] Cancel [% CASE 'FILL' %] diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/tools/viewlog.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/tools/viewlog.tt index 6e434a5c67f..28f0977e402 100644 --- a/koha-tmpl/intranet-tmpl/prog/en/modules/tools/viewlog.tt +++ b/koha-tmpl/intranet-tmpl/prog/en/modules/tools/viewlog.tt @@ -123,7 +123,7 @@ [% ELSE %] [% END %] - [% FOREACH modx IN [ 'AUTH' 'CATALOGUING' 'AUTHORITIES' 'MEMBERS' 'ACQUISITIONS' 'SERIAL' 'HOLDS' 'ILL' 'CIRCULATION' 'CLAIMS' 'FINES' 'SYSTEMPREFERENCE' 'CRONJOBS', 'REPORTS', 'SEARCHENGINE', 'NOTICES', 'NEWS', 'RECALLS', 'SUGGESTION', 'TRANSFERS' ] %] + [% FOREACH modx IN [ 'APIKEY', 'AUTH' 'CATALOGUING' 'AUTHORITIES' 'MEMBERS' 'ACQUISITIONS' 'SERIAL' 'HOLDS' 'ILL' 'CIRCULATION' 'CLAIMS' 'FINES' 'SYSTEMPREFERENCE' 'CRONJOBS', 'REPORTS', 'SEARCHENGINE', 'NOTICES', 'NEWS', 'RECALLS', 'SUGGESTION', 'TRANSFERS' ] %] [% IF modules.grep(modx).size %]