Bugzilla – Attachment 174937 Details for
Bug 35761
Add an administration editor for FTP and SFTP servers
Home
|
New
|
Browse
|
Search
|
[?]
|
Reports
|
Help
|
New Account
|
Log In
[x]
|
Forgot Password
Login:
[x]
[patch]
Bug 35761: Introduce central FTP/SFTP management
Bug-35761-Introduce-central-FTPSFTP-management.patch (text/plain), 103.99 KB, created by
Jake Deery
on 2024-11-22 16:50:52 UTC
(
hide
)
Description:
Bug 35761: Introduce central FTP/SFTP management
Filename:
MIME Type:
Creator:
Jake Deery
Created:
2024-11-22 16:50:52 UTC
Size:
103.99 KB
patch
obsolete
>From a21af5d6b622da1d142827e070dd45cbc1f46bd6 Mon Sep 17 00:00:00 2001 >From: Jake Deery <jake.deery@ptfs-europe.com> >Date: Tue, 8 Oct 2024 17:14:52 +0000 >Subject: [PATCH] Bug 35761: Introduce central FTP/SFTP management > >This patch adds a central FTP/SFTP management UI, with an associated >database object and class, plus the beginnings of an API. We can use >this code as a building block for adding FTP/SFTP connections to >other parts of the system, as well as to improve the usability of the >EDI transports. It is also now possible to test these connections >from within the Koha UI. > >To test:- >You will need access to an FTP or SFTP server >Contact me if you need a disposable FTP/SFTP server > >a) Go to Koha Administration >b) Notice no FTP/SFTP referenced on this page >c) APPLY PATCH >d) Create new user, let's say sftpuser >e) Set permissions for new user to include: > 1) catalogue > 2) parameters -> manage_sftp_servers >f) Log in with this new user, go back to Koha Administration > 1) Notice the FTP/SFTP servers option >g) Undo step e2) for the sftpuser via the koha admin user > 1) Refresh the Koha Administration page as the sftpuser > 2) Notice how FTP/SFTP servers has disappeared >h) Back to koha admin, go to Koha Administration -> FTP/SFTP Servers -> New >i) Complete form depending on the FTP or SFTP server you have access to, Submit > 1) Notice you are prompted to confirm before saving >j) Select Edit, observe your details are the same, and Submit again >k) Repeat step J one additional time >l) Select Test, observe all tests passing >m) Select Delete, confirm deletion, and observe server disappear >n) Optionally, run prove on new tests > 1) t/Koha/Auth/Permissions.t > 2) t/db_dependent/Koha/SFTP/Server.t > 3) t/db_dependent/api/v1/sftp_servers.t >o) SIGN-OFF >--- > Koha/REST/V1/Config/SFTP/Servers.pm | 169 ++++ > Koha/SFTP/Server.pm | 315 +++++++ > Koha/SFTP/Servers.pm | 57 ++ > Koha/Schema/Result/SftpServer.pm | 183 ++++ > admin/sftp_servers.pl | 223 +++++ > api/v1/swagger/definitions/sftp_server.yaml | 54 ++ > api/v1/swagger/paths/config_sftp_servers.yaml | 236 ++++++ > api/v1/swagger/swagger.yaml | 15 + > .../bug_35761-add_SFTP_tables_and_perm.pl | 41 + > installer/data/mysql/kohastructure.sql | 24 + > .../data/mysql/mandatory/userpermissions.sql | 1 + > .../prog/en/includes/admin-menu.inc | 5 +- > .../prog/en/includes/permissions.inc | 5 + > .../prog/en/modules/admin/admin-home.tt | 6 +- > .../prog/en/modules/admin/sftp_servers.tt | 788 ++++++++++++++++++ > t/Koha/Auth/Permissions.t | 1 + > t/db_dependent/Koha/SFTP/Server.t | 170 ++++ > t/db_dependent/api/v1/sftp_servers.t | 394 +++++++++ > 18 files changed, 2685 insertions(+), 2 deletions(-) > create mode 100644 Koha/REST/V1/Config/SFTP/Servers.pm > create mode 100644 Koha/SFTP/Server.pm > create mode 100644 Koha/SFTP/Servers.pm > create mode 100644 Koha/Schema/Result/SftpServer.pm > create mode 100755 admin/sftp_servers.pl > create mode 100644 api/v1/swagger/definitions/sftp_server.yaml > create mode 100644 api/v1/swagger/paths/config_sftp_servers.yaml > create mode 100755 installer/data/mysql/atomicupdate/bug_35761-add_SFTP_tables_and_perm.pl > create mode 100644 koha-tmpl/intranet-tmpl/prog/en/modules/admin/sftp_servers.tt > create mode 100755 t/db_dependent/Koha/SFTP/Server.t > create mode 100755 t/db_dependent/api/v1/sftp_servers.t > >diff --git a/Koha/REST/V1/Config/SFTP/Servers.pm b/Koha/REST/V1/Config/SFTP/Servers.pm >new file mode 100644 >index 00000000000..f72390ce585 >--- /dev/null >+++ b/Koha/REST/V1/Config/SFTP/Servers.pm >@@ -0,0 +1,169 @@ >+package Koha::REST::V1::Config::SFTP::Servers; >+ >+# 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 <http://www.gnu.org/licenses>. >+ >+use Modern::Perl; >+ >+use Mojo::Base 'Mojolicious::Controller'; >+ >+use Koha::SFTP::Servers; >+ >+use Try::Tiny qw( catch try ); >+ >+=head1 API >+ >+=head2 Methods >+ >+=head3 list >+ >+Controller method that handles listing Koha::SFTP::Server objects >+ >+=cut >+ >+sub list { >+ my $c = shift->openapi->valid_input or return; >+ >+ return try { >+ my $sftp_servers_set = Koha::SFTP::Servers->new; >+ my $sftp_servers = $c->objects->search($sftp_servers_set); >+ return $c->render( >+ status => 200, >+ openapi => $sftp_servers >+ ); >+ } catch { >+ $c->unhandled_exception($_); >+ }; >+} >+ >+=head3 get >+ >+Controller method that handles retrieving a single Koha::SFTP::Server object >+ >+=cut >+ >+sub get { >+ my $c = shift->openapi->valid_input or return; >+ >+ return try { >+ my $sftp_server = Koha::SFTP::Servers->find( $c->param('sftp_server_id') ); >+ >+ return $c->render_resource_not_found("FTP/SFTP server") >+ unless $sftp_server; >+ >+ return $c->render( >+ status => 200, >+ openapi => $c->objects->to_api($sftp_server), >+ ); >+ } catch { >+ $c->unhandled_exception($_); >+ } >+} >+ >+=head3 add >+ >+Controller method that handles adding a new Koha::SFTP::Server object >+ >+=cut >+ >+sub add { >+ my $c = shift->openapi->valid_input or return; >+ >+ return try { >+ >+ my $sftp_server = Koha::SFTP::Server->new_from_api( $c->req->json ); >+ $sftp_server->store->discard_changes; >+ >+ $c->res->headers->location( $c->req->url->to_string . '/' . $sftp_server->id ); >+ >+ return $c->render( >+ status => 201, >+ openapi => $c->objects->to_api($sftp_server), >+ ); >+ } catch { >+ if ( blessed $_ and $_->isa('Koha::Exceptions::Object::DuplicateID') ) { >+ return $c->render( >+ status => 409, >+ openapi => { >+ error => $_->error, >+ conflict => $_->duplicate_id >+ } >+ ); >+ } >+ >+ $c->unhandled_exception($_); >+ }; >+} >+ >+=head3 update >+ >+Controller method that handles updating a Koha::SFTP::Server object >+ >+=cut >+ >+sub update { >+ my $c = shift->openapi->valid_input or return; >+ >+ my $sftp_server = Koha::SFTP::Servers->find( $c->param('sftp_server_id') ); >+ >+ return $c->render_resource_not_found("FTP/SFTP server") >+ unless $sftp_server; >+ >+ return try { >+ $sftp_server->set_from_api( $c->req->json ); >+ $sftp_server->store->discard_changes; >+ >+ return $c->render( >+ status => 200, >+ openapi => $c->objects->to_api($sftp_server), >+ ); >+ } catch { >+ if ( blessed $_ and $_->isa('Koha::Exceptions::Object::DuplicateID') ) { >+ return $c->render( >+ status => 409, >+ openapi => { >+ error => $_->error, >+ conflict => $_->duplicate_id >+ } >+ ); >+ } >+ >+ $c->unhandled_exception($_); >+ }; >+} >+ >+=head3 delete >+ >+Controller method that handles deleting a Koha::SFTP::Server object >+ >+=cut >+ >+sub delete { >+ my $c = shift->openapi->valid_input or return; >+ >+ my $sftp_server = Koha::SFTP::Servers->find( $c->param('sftp_server_id') ); >+ >+ return $c->render_resource_not_found("FTP/SFTP server") >+ unless $sftp_server; >+ >+ return try { >+ $sftp_server->delete; >+ return $c->render_resource_deleted; >+ } catch { >+ $c->unhandled_exception($_); >+ }; >+} >+ >+1; >diff --git a/Koha/SFTP/Server.pm b/Koha/SFTP/Server.pm >new file mode 100644 >index 00000000000..77c5a0f9956 >--- /dev/null >+++ b/Koha/SFTP/Server.pm >@@ -0,0 +1,315 @@ >+package Koha::SFTP::Server; >+ >+# 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 <http://www.gnu.org/licenses>. >+ >+use Modern::Perl; >+ >+use Koha::Database; >+use Koha::Exceptions::Object; >+use Koha::Encryption; >+use Koha::SFTP::Servers; >+ >+use Try::Tiny qw( catch try ); >+use Net::SFTP::Foreign; >+use Net::FTP; >+ >+use base qw(Koha::Object); >+ >+=head1 NAME >+ >+Koha::SFTP::Server - Koha SFTP Server Object class >+ >+=head1 API >+ >+=head2 Class methods >+ >+=head3 store >+ >+ $server->store; >+ >+Overloaded store method. >+ >+=cut >+ >+sub store { >+ my ($self) = @_; >+ >+ # catch password and encrypt it >+ $self->password( >+ $self->password >+ ? Koha::Encryption->new->encrypt_hex( $self->password ) >+ : undef >+ ); >+ >+ # clean up the keyfile & encrypt >+ $self->key_file( >+ $self->key_file >+ ? Koha::Encryption->new->encrypt_hex( _dos2unix( $self->key_file ) ) >+ : undef >+ ); >+ >+ # catch key file and write it >+ $self->write_key_file; >+ >+ return $self->SUPER::store; >+} >+ >+=head3 to_api >+ >+ my $json = $sftp_server->to_api; >+ >+Overloaded method that returns a JSON representation of the Koha::SFTP::Server object, >+suitable for API output. >+ >+=cut >+ >+sub to_api { >+ my ( $self, $params ) = @_; >+ >+ my $json_sftp = $self->SUPER::to_api($params); >+ return unless $json_sftp; >+ delete $json_sftp->{password}; >+ >+ return $json_sftp; >+} >+ >+=head3 to_api_mapping >+ >+This method returns the mapping for representing a Koha::SFTP::Server object >+on the API. >+ >+=cut >+ >+sub to_api_mapping { >+ return { id => 'sftp_server_id' }; >+} >+ >+=head3 plain_text_password >+ >+ $server->plain_text_password; >+Fetches the plaintext password, from the object >+ >+=cut >+ >+sub plain_text_password { >+ my ($self) = @_; >+ >+ return Koha::Encryption->new->decrypt_hex( $self->password ) >+ if $self->password; >+ >+} >+ >+=head3 plain_text_key >+ >+ $server->plain_text_key; >+Fetches the plaintext key file, from the object >+ >+=cut >+ >+sub plain_text_key { >+ my ($self) = @_; >+ >+ return Koha::Encryption->new->decrypt_hex( $self->key_file ) . "\n" ## newline needed >+ if $self->key_file; >+} >+ >+=head3 write_key_file >+ >+ $server->write_key_file; >+Writes the keyfile from the db into a file >+ >+=cut >+ >+sub write_key_file { >+ my ($self) = @_; >+ my $upload_path = C4::Context->config('upload_path') or return; >+ my $key_path = $upload_path . '/ssh_keys'; >+ my $key_file = $key_path . '/id_ssh_' . $self->id; >+ >+ mkdir $key_path if ( !-d $key_path ); >+ >+ unlink $key_file if ( -f $key_file ); >+ my $fh; >+ $fh = IO::File->new( $key_file, 'w' ) or return; >+ chmod 0600, $key_file if ( -f $key_file ); >+ >+ print $fh $self->plain_text_key; >+ >+ undef $fh; >+ >+ return 1; >+} >+ >+=head3 locate_key_file >+ >+ $server->locate_key_file; >+Returns the keyfile's expected path >+ >+=cut >+ >+sub locate_key_file { >+ my ($self) = @_; >+ my $upload_path = C4::Context->config('upload_path'); >+ return if not defined $upload_path; >+ >+ my $keyf_path = $upload_path . '/ssh_keys/id_ssh_' . $self->id; >+ >+ return ( -f $keyf_path ) ? $keyf_path : undef; >+} >+ >+=head3 test_conn >+ >+ $server->test_conn; >+Tests a connection to a given sftp server >+ >+=cut >+ >+sub test_conn { >+ my ($self) = @_; >+ my $test_results = {}; >+ >+ if ( $self->transport eq 'sftp' ) { >+ ## test connectivity >+ my $sftp = Net::SFTP::Foreign->new( >+ host => $self->host, >+ user => $self->user_name, >+ password => $self->plain_text_password, >+ key_path => $self->locate_key_file, >+ port => $self->port, >+ timeout => 10, >+ more => [ >+ qw(-vv), >+ qw(-o StrictHostKeyChecking=no), >+ ], >+ ); >+ $test_results->{'1_sftp_conn'}->{'err'} = $sftp->error >+ if $sftp->error; >+ $test_results->{'1_sftp_conn'}->{'passed'} = 1 >+ unless $sftp->error; >+ >+ ## continue >+ unless ( $sftp->error ) { >+ ## get directory listings >+ $sftp->ls(); >+ $test_results->{'2_sftp_ls'}->{'err'} = $sftp->error >+ if $sftp->error; >+ $test_results->{'2_sftp_ls'}->{'passed'} = 1 >+ unless $sftp->error; >+ >+ ## write file to server >+ open my $fh, '<', \"Hello, world!\n"; >+ close $fh if ( $sftp->put( $fh, '.koha_test_file' ) ); >+ $test_results->{'3_sftp_write'}->{'err'} = $sftp->error >+ if $sftp->error; >+ $test_results->{'3_sftp_write'}->{'passed'} = 1 >+ unless $sftp->error; >+ >+ ## delete test file from server >+ $sftp->remove('.koha_test_file'); >+ $test_results->{'4_sftp_del'}->{'err'} = $sftp->error >+ if $sftp->error; >+ $test_results->{'4_sftp_del'}->{'passed'} = 1 >+ unless $sftp->error; >+ } >+ } elsif ( $self->transport eq 'ftp' ) { >+ ## test connectivity >+ my $ftp = Net::FTP->new( >+ $self->host, >+ Port => $self->port, >+ Timeout => 10, >+ Passive => ( scalar $self->passiv ) ? 1 : 0, >+ ); >+ if ($ftp) { >+ $test_results->{'1_ftp_conn'}->{'passed'} = 1; >+ $test_results->{'1_ftp_conn'}->{'msg'} = $ftp->message; >+ } else { >+ $test_results->{'1_ftp_conn'}->{'err'} = 'cannot connect to ' . $self->host . ': ' . $@; >+ } >+ >+ ## continue >+ if ($ftp) { >+ ## try to login >+ my $login = $ftp->login( >+ $self->user_name, >+ $self->plain_text_password, >+ ); >+ if ($login) { >+ $test_results->{'2_ftp_login'}->{'passed'} = 1; >+ $test_results->{'2_ftp_login'}->{'msg'} = $ftp->message; >+ } else { >+ $test_results->{'2_ftp_login'}->{'err'} = $ftp->message; >+ } >+ >+ ## get directory listings >+ my $ls = $ftp->ls('~'); >+ if ($ls) { >+ $test_results->{'3_ftp_ls'}->{'passed'} = 1; >+ $test_results->{'3_ftp_ls'}->{'msg'} = $ftp->message; >+ } else { >+ $test_results->{'3_ftp_ls'}->{'err'} = $ftp->message; >+ } >+ >+ ## write file to server >+ open my $fh, '<', \"Hello, world!\n"; >+ close $fh if ( my $put = $ftp->put( $fh, '.koha_test_file' ) ); >+ if ($put) { >+ $test_results->{'4_ftp_write'}->{'passed'} = 1; >+ $test_results->{'4_ftp_write'}->{'msg'} = $ftp->message; >+ } else { >+ $test_results->{'4_ftp_write'}->{'err'} = $ftp->message; >+ } >+ >+ ## delete test file from server >+ my $delete = $ftp->delete('.koha_test_file'); >+ if ($delete) { >+ $test_results->{'5_ftp_del'}->{'passed'} = 1; >+ $test_results->{'5_ftp_del'}->{'msg'} = $ftp->message; >+ } else { >+ $test_results->{'5_ftp_del'}->{'err'} = $ftp->message; >+ } >+ } >+ } >+ >+ ## return (note that we return 1 here to signify the run finished) >+ my $test_status = 1; >+ return ( $test_status, $test_results ); >+} >+ >+=head2 Internal methods >+ >+=head3 _dos2unix >+ >+Return a CR-free string from an input >+ >+=cut >+ >+sub _dos2unix { >+ my $dosStr = shift; >+ >+ return $dosStr =~ s/\015\012/\012/gr; >+} >+ >+=head3 _type >+ >+Return type of Object relating to Schema ResultSet >+ >+=cut >+ >+sub _type { >+ return 'SftpServer'; >+} >+ >+1; >diff --git a/Koha/SFTP/Servers.pm b/Koha/SFTP/Servers.pm >new file mode 100644 >index 00000000000..264b2c3c99c >--- /dev/null >+++ b/Koha/SFTP/Servers.pm >@@ -0,0 +1,57 @@ >+package Koha::SFTP::Servers; >+ >+# 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 <http://www.gnu.org/licenses>. >+ >+use Modern::Perl; >+ >+use Koha::Database; >+use Koha::Exceptions; >+ >+use Koha::SFTP::Server; >+ >+use base qw(Koha::Objects); >+ >+=head1 NAME >+ >+Koha::SFTP::Servers - Koha SFTP Server Object set class >+ >+=head1 API >+ >+=head2 Class methods >+ >+=head2 Internal methods >+ >+=head3 _type >+ >+Return type of object, relating to Schema ResultSet >+ >+=cut >+ >+sub _type { >+ return 'SftpServer'; >+} >+ >+=head3 object_class >+ >+Return object class >+ >+=cut >+ >+sub object_class { >+ return 'Koha::SFTP::Server'; >+} >+ >+1; >diff --git a/Koha/Schema/Result/SftpServer.pm b/Koha/Schema/Result/SftpServer.pm >new file mode 100644 >index 00000000000..725992a71f8 >--- /dev/null >+++ b/Koha/Schema/Result/SftpServer.pm >@@ -0,0 +1,183 @@ >+use utf8; >+package Koha::Schema::Result::SftpServer; >+ >+# Created by DBIx::Class::Schema::Loader >+# DO NOT MODIFY THE FIRST PART OF THIS FILE >+ >+=head1 NAME >+ >+Koha::Schema::Result::SftpServer >+ >+=cut >+ >+use strict; >+use warnings; >+ >+use base 'DBIx::Class::Core'; >+ >+=head1 TABLE: C<sftp_servers> >+ >+=cut >+ >+__PACKAGE__->table("sftp_servers"); >+ >+=head1 ACCESSORS >+ >+=head2 id >+ >+ data_type: 'integer' >+ is_auto_increment: 1 >+ is_nullable: 0 >+ >+=head2 name >+ >+ data_type: 'varchar' >+ is_nullable: 0 >+ size: 80 >+ >+=head2 host >+ >+ data_type: 'varchar' >+ default_value: 'localhost' >+ is_nullable: 0 >+ size: 80 >+ >+=head2 port >+ >+ data_type: 'integer' >+ default_value: 22 >+ is_nullable: 0 >+ >+=head2 transport >+ >+ data_type: 'enum' >+ default_value: 'sftp' >+ extra: {list => ["ftp","sftp","file"]} >+ is_nullable: 0 >+ >+=head2 passiv >+ >+ data_type: 'tinyint' >+ default_value: 1 >+ is_nullable: 0 >+ >+=head2 user_name >+ >+ data_type: 'varchar' >+ is_nullable: 1 >+ size: 80 >+ >+=head2 password >+ >+ data_type: 'varchar' >+ is_nullable: 1 >+ size: 80 >+ >+=head2 key_file >+ >+ data_type: 'varchar' >+ is_nullable: 1 >+ size: 4096 >+ >+=head2 auth_mode >+ >+ data_type: 'enum' >+ default_value: 'password' >+ extra: {list => ["password","key_file","noauth"]} >+ is_nullable: 0 >+ >+=head2 debug >+ >+ data_type: 'tinyint' >+ default_value: 0 >+ is_nullable: 0 >+ >+=cut >+ >+__PACKAGE__->add_columns( >+ "id", >+ { data_type => "integer", is_auto_increment => 1, is_nullable => 0 }, >+ "name", >+ { data_type => "varchar", is_nullable => 0, size => 80 }, >+ "host", >+ { >+ data_type => "varchar", >+ default_value => "localhost", >+ is_nullable => 0, >+ size => 80, >+ }, >+ "port", >+ { data_type => "integer", default_value => 22, is_nullable => 0 }, >+ "transport", >+ { >+ data_type => "enum", >+ default_value => "sftp", >+ extra => { list => ["ftp", "sftp", "file"] }, >+ is_nullable => 0, >+ }, >+ "passiv", >+ { data_type => "tinyint", default_value => 1, is_nullable => 0 }, >+ "user_name", >+ { data_type => "varchar", is_nullable => 1, size => 80 }, >+ "password", >+ { data_type => "varchar", is_nullable => 1, size => 80 }, >+ "key_file", >+ { data_type => "varchar", is_nullable => 1, size => 4096 }, >+ "auth_mode", >+ { >+ data_type => "enum", >+ default_value => "password", >+ extra => { list => ["password", "key_file", "noauth"] }, >+ is_nullable => 0, >+ }, >+ "debug", >+ { data_type => "tinyint", default_value => 0, is_nullable => 0 }, >+); >+ >+=head1 PRIMARY KEY >+ >+=over 4 >+ >+=item * L</id> >+ >+=back >+ >+=cut >+ >+__PACKAGE__->set_primary_key("id"); >+ >+=head1 RELATIONS >+ >+=head2 vendor_edi_accounts >+ >+Type: has_many >+ >+Related object: L<Koha::Schema::Result::VendorEdiAccount> >+ >+=cut >+ >+__PACKAGE__->has_many( >+ "vendor_edi_accounts", >+ "Koha::Schema::Result::VendorEdiAccount", >+ { "foreign.sftp_server_id" => "self.id" }, >+ { cascade_copy => 0, cascade_delete => 0 }, >+); >+ >+ >+# Created by DBIx::Class::Schema::Loader v0.07051 @ 2024-10-16 09:01:20 >+# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:LV/voK/j3DzE3wpY0uul3A >+ >+__PACKAGE__->add_columns( >+ '+passiv' => { is_boolean => 1 }, >+ '+debug' => { is_boolean => 1 }, >+); >+ >+sub koha_objects_class { >+ 'Koha::SFTP::Servers'; >+} >+ >+sub koha_object_class { >+ 'Koha::SFTP::Server'; >+} >+ >+1; >diff --git a/admin/sftp_servers.pl b/admin/sftp_servers.pl >new file mode 100755 >index 00000000000..fcaa373bedf >--- /dev/null >+++ b/admin/sftp_servers.pl >@@ -0,0 +1,223 @@ >+#!/usr/bin/perl >+ >+# Copyright 2024 PTFS Europe Ltd >+# >+# 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 <http://www.gnu.org/licenses>. >+ >+use Modern::Perl; >+ >+use CGI qw ( -utf8 ); >+use Scalar::Util qw( blessed ); >+use Try::Tiny qw( catch try ); >+ >+use C4::Auth qw( get_template_and_user ); >+use C4::Output qw( output_html_with_http_headers ); >+ >+use Koha::SFTP::Servers; >+ >+my $input = CGI->new; >+my $op = $input->param('op') || 'list'; >+ >+my ( $template, $borrowernumber, $cookie ) = get_template_and_user( >+ { >+ template_name => "admin/sftp_servers.tt", >+ query => $input, >+ type => "intranet", >+ flagsrequired => { parameters => 'manage_sftp_servers' }, >+ } >+); >+ >+my @messages; >+ >+my $sftp_servers = Koha::SFTP::Servers->search; >+ >+if ( $op eq 'cud-add' ) { >+ my $name = $input->param('sftp_name'); >+ my $host = $input->param('sftp_host') || 'localhost'; >+ my $port = $input->param('sftp_port') || 22; >+ my $transport = $input->param('sftp_transport') || 'sftp'; >+ my $passiv = ( scalar $input->param('sftp_passiv') ) ? 1 : 0; >+ my $auth_mode = $input->param('sftp_auth_mode') || 'password'; >+ my $user_name = $input->param('sftp_user_name') || undef; >+ my $password = $input->param('sftp_password') || undef; >+ my $key_file = $input->param('sftp_key_file') || undef; >+ my $debug = ( scalar $input->param('sftp_debug_mode') ) ? 1 : 0; >+ >+ try { >+ Koha::SFTP::Server->new( >+ { >+ name => $name, >+ host => $host, >+ port => $port, >+ transport => $transport, >+ passiv => $passiv, >+ auth_mode => $auth_mode, >+ user_name => $user_name, >+ password => $password, >+ key_file => $key_file, >+ debug => $debug, >+ } >+ )->store; >+ >+ push @messages, { >+ type => 'message', >+ code => 'success_on_insert' >+ }; >+ } catch { >+ if ( blessed $_ and $_->isa('Koha::Exceptions::Object::DuplicateID') ) { >+ push @messages, { >+ type => 'alert', >+ code => 'error_on_insert', >+ reason => 'duplicate_id' >+ }; >+ } >+ }; >+ >+ # list servers after adding >+ $op = 'list'; >+ >+} elsif ( $op eq 'edit_form' ) { >+ my $sftp_server_id = $input->param('sftp_server_id'); >+ my $sftp_server; >+ my $sftp_server_plain_text_password; >+ my $sftp_server_plain_text_key; >+ >+ $sftp_server = Koha::SFTP::Servers->find($sftp_server_id) >+ unless !$sftp_server_id; >+ >+ unless ( !$sftp_server ) { >+ $sftp_server_plain_text_password = $sftp_server->plain_text_password; >+ $sftp_server_plain_text_key = $sftp_server->plain_text_key; >+ } >+ >+ if ($sftp_server) { >+ $template->param( >+ sftp_server => $sftp_server, >+ sftp_server_plain_text_password => $sftp_server_plain_text_password, >+ sftp_server_plain_text_key => $sftp_server_plain_text_key, >+ ); >+ } else { >+ push @messages, { >+ type => 'alert', >+ code => 'error_on_edit', >+ reason => 'invalid_id' >+ }; >+ } >+ >+} elsif ( $op eq 'cud-edit_save' ) { >+ my $sftp_server_id = $input->param('sftp_server_id'); >+ my $sftp_server_plain_text_password; >+ my $sftp_server; >+ >+ $sftp_server = Koha::SFTP::Servers->find($sftp_server_id) >+ unless !$sftp_server_id; >+ >+ $sftp_server_plain_text_password = $sftp_server->plain_text_password >+ unless !$sftp_server_id; >+ >+ if ($sftp_server) { >+ my $name = $input->param('sftp_name'); >+ my $host = $input->param('sftp_host') || 'localhost'; >+ my $port = $input->param('sftp_port') || 22; >+ my $transport = $input->param('sftp_transport') || 'sftp'; >+ my $passiv = ( scalar $input->param('sftp_passiv') ) ? 1 : 0; >+ my $auth_mode = $input->param('sftp_auth_mode') || 'password'; >+ my $user_name = $input->param('sftp_user_name') || undef; >+ my $password = $input->param('sftp_password') || undef; >+ my $key_file = $input->param('sftp_key_file') || undef; >+ my $debug = ( scalar $input->param('sftp_debug_mode') ) ? 1 : 0; >+ >+ try { >+ $sftp_server->password($password) >+ if defined $password and $password ne '****' >+ or not defined $password; >+ >+ $sftp_server->set( >+ { >+ name => $name, >+ host => $host, >+ port => $port, >+ transport => $transport, >+ passiv => $passiv, >+ auth_mode => $auth_mode, >+ user_name => $user_name, >+ password => $password, >+ key_file => $key_file, >+ debug => $debug, >+ } >+ )->store; >+ >+ push @messages, { >+ type => 'message', >+ code => 'success_on_update' >+ }; >+ >+ } catch { >+ >+ push @messages, { >+ type => 'alert', >+ code => 'error_on_update' >+ }; >+ >+ }; >+ >+ # list servers after adding >+ $op = 'list'; >+ } else { >+ push @messages, { >+ type => 'alert', >+ code => 'error_on_update', >+ reason => 'invalid_id' >+ }; >+ } >+ >+} elsif ( $op eq 'test_form' ) { >+ my $sftp_server_id = $input->param('sftp_server_id'); >+ my $sftp_server; >+ >+ $sftp_server = Koha::SFTP::Servers->find($sftp_server_id) >+ unless !$sftp_server_id; >+ >+ if ($sftp_server) { >+ ## do the test >+ my ( $sftp_server_test_status, $sftp_server_test_result ) = $sftp_server->test_conn; >+ >+ $template->param( >+ sftp_server => $sftp_server, >+ sftp_server_test_result => $sftp_server_test_result, >+ sftp_server_test_status => $sftp_server_test_status, >+ ); >+ } else { >+ push @messages, { >+ type => 'alert', >+ code => 'error_on_test', >+ reason => 'invalid_id', >+ }; >+ } >+} >+ >+if ( $op eq 'list' ) { >+ $template->param( >+ servers_count => $sftp_servers->count, >+ ); >+} >+ >+$template->param( >+ op => $op, >+ messages => \@messages, >+); >+ >+output_html_with_http_headers $input, $cookie, $template->output; >diff --git a/api/v1/swagger/definitions/sftp_server.yaml b/api/v1/swagger/definitions/sftp_server.yaml >new file mode 100644 >index 00000000000..c2937c9e35a >--- /dev/null >+++ b/api/v1/swagger/definitions/sftp_server.yaml >@@ -0,0 +1,54 @@ >+--- >+type: object >+properties: >+ sftp_server_id: >+ type: integer >+ description: Internal FTP/SFTP server identifier >+ readOnly: true >+ name: >+ type: string >+ description: Name of the FTP/SFTP server >+ host: >+ type: string >+ description: FTP/SFTP host name >+ port: >+ type: integer >+ description: TCP port number >+ transport: >+ type: string >+ enum: >+ - ftp >+ - sftp >+ - file >+ description: Transport of FTP/SFTP server >+ auth_mode: >+ type: string >+ enum: >+ - password >+ - key_file >+ - noauth >+ description: Authentication mode to use >+ passiv: >+ type: boolean >+ description: Whether or not to use passive mode >+ user_name: >+ type: >+ - string >+ - "null" >+ description: The user name to use for authentication (optional) >+ password: >+ type: >+ - string >+ - "null" >+ description: The password to use for authentication (optional) >+ key_file: >+ type: >+ - string >+ - "null" >+ description: The key file to use for authentication (optional) >+ debug: >+ type: boolean >+ description: If the SMTP connection is set to debug mode >+additionalProperties: false >+required: >+ - name >diff --git a/api/v1/swagger/paths/config_sftp_servers.yaml b/api/v1/swagger/paths/config_sftp_servers.yaml >new file mode 100644 >index 00000000000..d728829f3da >--- /dev/null >+++ b/api/v1/swagger/paths/config_sftp_servers.yaml >@@ -0,0 +1,236 @@ >+--- >+/config/sftp_servers: >+ get: >+ x-mojo-to: Config::SFTP::Servers#list >+ operationId: listSFTPServers >+ tags: >+ - sftp_servers >+ summary: List FTP/SFTP servers >+ produces: >+ - application/json >+ parameters: >+ - $ref: "../swagger.yaml#/parameters/match" >+ - $ref: "../swagger.yaml#/parameters/order_by" >+ - $ref: "../swagger.yaml#/parameters/page" >+ - $ref: "../swagger.yaml#/parameters/per_page" >+ - $ref: "../swagger.yaml#/parameters/q_param" >+ - $ref: "../swagger.yaml#/parameters/q_body" >+ - $ref: "../swagger.yaml#/parameters/request_id_header" >+ responses: >+ "200": >+ description: A list of FTP/SFTP servers >+ schema: >+ type: array >+ items: >+ $ref: "../swagger.yaml#/definitions/sftp_server" >+ "400": >+ description: | >+ Bad request. Possible `error_code` attribute values: >+ >+ * `invalid_query` >+ schema: >+ $ref: "../swagger.yaml#/definitions/error" >+ "403": >+ description: Access forbidden >+ schema: >+ $ref: "../swagger.yaml#/definitions/error" >+ "500": >+ description: | >+ Internal server error. Possible `error_code` attribute values: >+ >+ * `internal_server_error` >+ schema: >+ $ref: "../swagger.yaml#/definitions/error" >+ "503": >+ description: Under maintenance >+ schema: >+ $ref: "../swagger.yaml#/definitions/error" >+ x-koha-authorization: >+ permissions: >+ parameters: manage_sftp_servers >+ post: >+ x-mojo-to: Config::SFTP::Servers#add >+ operationId: addSFTPServer >+ tags: >+ - sftp_servers >+ summary: Add FTP/SFTP server >+ parameters: >+ - name: body >+ in: body >+ description: A JSON object representing a new FTP/SFTP server configuration >+ required: true >+ schema: >+ $ref: "../swagger.yaml#/definitions/sftp_server" >+ produces: >+ - application/json >+ responses: >+ "201": >+ description: An FTP/SFTP server object >+ schema: >+ $ref: "../swagger.yaml#/definitions/sftp_server" >+ "400": >+ description: Bad request >+ schema: >+ $ref: "../swagger.yaml#/definitions/error" >+ "401": >+ description: Authentication required >+ schema: >+ $ref: "../swagger.yaml#/definitions/error" >+ "403": >+ description: Access forbidden >+ schema: >+ $ref: "../swagger.yaml#/definitions/error" >+ "409": >+ description: Conflict in creating resource >+ schema: >+ $ref: "../swagger.yaml#/definitions/error" >+ "500": >+ description: | >+ Internal server error. Possible `error_code` attribute values: >+ >+ * `internal_server_error` >+ schema: >+ $ref: "../swagger.yaml#/definitions/error" >+ "503": >+ description: Under maintenance >+ schema: >+ $ref: "../swagger.yaml#/definitions/error" >+ x-koha-authorization: >+ permissions: >+ parameters: manage_sftp_servers >+"/config/sftp_servers/{sftp_server_id}": >+ get: >+ x-mojo-to: Config::SFTP::Servers#get >+ operationId: getSFTPServer >+ tags: >+ - sftp_servers >+ summary: Get FTP/SFTP server >+ parameters: >+ - $ref: "../swagger.yaml#/parameters/sftp_server_id_pp" >+ produces: >+ - application/json >+ responses: >+ "200": >+ description: An FTP/SFTP server object >+ schema: >+ $ref: "../swagger.yaml#/definitions/sftp_server" >+ "400": >+ description: Bad request >+ schema: >+ $ref: "../swagger.yaml#/definitions/error" >+ "404": >+ description: Object not found >+ schema: >+ $ref: "../swagger.yaml#/definitions/error" >+ "409": >+ description: Conflict updating resource >+ schema: >+ $ref: "../swagger.yaml#/definitions/error" >+ "500": >+ description: | >+ Internal server error. Possible `error_code` attribute values: >+ >+ * `internal_server_error` >+ schema: >+ $ref: "../swagger.yaml#/definitions/error" >+ "503": >+ description: Under maintenance >+ schema: >+ $ref: "../swagger.yaml#/definitions/error" >+ x-koha-authorization: >+ permissions: >+ parameters: manage_sftp_servers >+ put: >+ x-mojo-to: Config::SFTP::Servers#update >+ operationId: updateSFTPServer >+ tags: >+ - sftp_servers >+ summary: Update FTP/SFTP server >+ parameters: >+ - $ref: "../swagger.yaml#/parameters/sftp_server_id_pp" >+ - name: body >+ in: body >+ description: An FTP/SFTP server object >+ required: true >+ schema: >+ $ref: "../swagger.yaml#/definitions/sftp_server" >+ produces: >+ - application/json >+ responses: >+ "200": >+ description: An FTP/SFTP server object >+ schema: >+ $ref: "../swagger.yaml#/definitions/sftp_server" >+ "400": >+ description: Bad request >+ schema: >+ $ref: "../swagger.yaml#/definitions/error" >+ "401": >+ description: Authentication required >+ schema: >+ $ref: "../swagger.yaml#/definitions/error" >+ "403": >+ description: Access forbidden >+ schema: >+ $ref: "../swagger.yaml#/definitions/error" >+ "404": >+ description: Object not found >+ schema: >+ $ref: "../swagger.yaml#/definitions/error" >+ "500": >+ description: | >+ Internal server error. Possible `error_code` attribute values: >+ >+ * `internal_server_error` >+ schema: >+ $ref: "../swagger.yaml#/definitions/error" >+ "503": >+ description: Under maintenance >+ schema: >+ $ref: "../swagger.yaml#/definitions/error" >+ x-koha-authorization: >+ permissions: >+ parameters: manage_sftp_servers >+ delete: >+ x-mojo-to: Config::SFTP::Servers#delete >+ operationId: deleteSFTPServer >+ tags: >+ - sftp_servers >+ summary: Delete FTP/SFTP server >+ parameters: >+ - $ref: "../swagger.yaml#/parameters/sftp_server_id_pp" >+ produces: >+ - application/json >+ responses: >+ "204": >+ description: FTP/SFTP server deleted >+ "400": >+ description: Bad request >+ schema: >+ $ref: "../swagger.yaml#/definitions/error" >+ "401": >+ description: Authentication required >+ schema: >+ $ref: "../swagger.yaml#/definitions/error" >+ "403": >+ description: Access forbidden >+ schema: >+ $ref: "../swagger.yaml#/definitions/error" >+ "404": >+ description: Object not found >+ schema: >+ $ref: "../swagger.yaml#/definitions/error" >+ "500": >+ description: | >+ Internal server error. Possible `error_code` attribute values: >+ >+ * `internal_server_error` >+ schema: >+ $ref: "../swagger.yaml#/definitions/error" >+ "503": >+ description: Under maintenance >+ schema: >+ $ref: "../swagger.yaml#/definitions/error" >+ x-koha-authorization: >+ permissions: >+ parameters: manage_sftp_servers >diff --git a/api/v1/swagger/swagger.yaml b/api/v1/swagger/swagger.yaml >index ff7b94a174e..8daf7a78c72 100644 >--- a/api/v1/swagger/swagger.yaml >+++ b/api/v1/swagger/swagger.yaml >@@ -174,6 +174,8 @@ definitions: > $ref: ./definitions/search_filter.yaml > smtp_server: > $ref: ./definitions/smtp_server.yaml >+ sftp_server: >+ $ref: ./definitions/sftp_server.yaml > suggestion: > $ref: ./definitions/suggestion.yaml > ticket: >@@ -305,6 +307,10 @@ paths: > $ref: ./paths/config_smtp_servers.yaml#/~1config~1smtp_servers > "/config/smtp_servers/{smtp_server_id}": > $ref: "./paths/config_smtp_servers.yaml#/~1config~1smtp_servers~1{smtp_server_id}" >+ /config/sftp_servers: >+ $ref: ./paths/config_sftp_servers.yaml#/~1config~1sftp_servers >+ "/config/sftp_servers/{sftp_server_id}": >+ $ref: "./paths/config_sftp_servers.yaml#/~1config~1sftp_servers~1{sftp_server_id}" > "/deleted/biblios": > $ref: "./paths/deleted_biblios.yaml#/~1deleted~1biblios" > "/deleted/biblios/{biblio_id}": >@@ -931,6 +937,12 @@ parameters: > name: smtp_server_id > required: true > type: integer >+ sftp_server_id_pp: >+ description: FTP/SFTP server internal identifier >+ in: path >+ name: sftp_server_id >+ required: true >+ type: integer > suggestion_id_pp: > description: Internal suggestion identifier > in: path >@@ -1278,6 +1290,9 @@ tags: > - description: "Manage SMTP servers configurations\n" > name: smtp_servers > x-displayName: SMTP servers >+ - description: "Manage FTP/SFTP servers configurations\n" >+ name: sftp_servers >+ x-displayName: FTP/SFTP servers > - description: "Manage tickets\n" > name: tickets > x-displayName: Tickets >diff --git a/installer/data/mysql/atomicupdate/bug_35761-add_SFTP_tables_and_perm.pl b/installer/data/mysql/atomicupdate/bug_35761-add_SFTP_tables_and_perm.pl >new file mode 100755 >index 00000000000..5d78d98c278 >--- /dev/null >+++ b/installer/data/mysql/atomicupdate/bug_35761-add_SFTP_tables_and_perm.pl >@@ -0,0 +1,41 @@ >+use Modern::Perl; >+ >+return { >+ bug_number => "35761", >+ description => "Add new table and permission for generalised SFTP", >+ up => sub { >+ my ($args) = @_; >+ my ( $dbh, $out ) = @$args{qw(dbh out)}; >+ >+ $dbh->do( >+ q{ >+ INSERT IGNORE INTO permissions (module_bit, code, description) >+ VALUES (3, 'manage_sftp_servers', 'Manage FTP/SFTP servers configuration'); >+ } >+ ); >+ say $out "Added new manage_sftp_servers permission"; >+ >+ unless ( TableExists('sftp_servers') ) { >+ $dbh->do( >+ q { >+ CREATE TABLE `sftp_servers` ( >+ `id` int(11) NOT NULL AUTO_INCREMENT, >+ `name` varchar(80) NOT NULL, >+ `host` varchar(80) NOT NULL DEFAULT 'localhost', >+ `port` int(11) NOT NULL DEFAULT 22, >+ `transport` enum('ftp','sftp') NOT NULL DEFAULT 'sftp', >+ `passiv` tinyint(1) NOT NULL DEFAULT 1, >+ `user_name` varchar(80) DEFAULT NULL, >+ `password` varchar(80) DEFAULT NULL, >+ `key_file` varchar(4096) DEFAULT NULL, >+ `auth_mode` enum('password','key_file','noauth') NOT NULL DEFAULT 'password', >+ `debug` tinyint(1) NOT NULL DEFAULT 0, >+ PRIMARY KEY (`id`), >+ KEY `host_idx` (`host`) >+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; >+ } >+ ); >+ say $out "Added new sftp_servers table"; >+ } >+ }, >+}; >diff --git a/installer/data/mysql/kohastructure.sql b/installer/data/mysql/kohastructure.sql >index d3686595715..87a4ad70439 100644 >--- a/installer/data/mysql/kohastructure.sql >+++ b/installer/data/mysql/kohastructure.sql >@@ -5918,6 +5918,30 @@ CREATE TABLE `sessions` ( > ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; > /*!40101 SET character_set_client = @saved_cs_client */; > >+-- >+-- Table structure for table `sftp_servers` >+-- >+ >+DROP TABLE IF EXISTS `sftp_servers`; >+/*!40101 SET @saved_cs_client = @@character_set_client */; >+/*!40101 SET character_set_client = utf8 */; >+CREATE TABLE `sftp_servers` ( >+ `id` int(11) NOT NULL AUTO_INCREMENT, >+ `name` varchar(80) NOT NULL, >+ `host` varchar(80) NOT NULL DEFAULT 'localhost', >+ `port` int(11) NOT NULL DEFAULT 22, >+ `transport` enum('ftp','sftp') NOT NULL DEFAULT 'sftp', >+ `passiv` tinyint(1) NOT NULL DEFAULT 1, >+ `user_name` varchar(80) DEFAULT NULL, >+ `password` varchar(80) DEFAULT NULL, >+ `key_file` varchar(4096) DEFAULT NULL, >+ `auth_mode` enum('password','key_file','noauth') NOT NULL DEFAULT 'password', >+ `debug` tinyint(1) NOT NULL DEFAULT 0, >+ PRIMARY KEY (`id`), >+ KEY `host_idx` (`host`) >+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; >+/*!40101 SET character_set_client = @saved_cs_client */; >+ > -- > -- Table structure for table `sms_providers` > -- >diff --git a/installer/data/mysql/mandatory/userpermissions.sql b/installer/data/mysql/mandatory/userpermissions.sql >index 1d8b7d034f2..d68932d714d 100644 >--- a/installer/data/mysql/mandatory/userpermissions.sql >+++ b/installer/data/mysql/mandatory/userpermissions.sql >@@ -39,6 +39,7 @@ INSERT INTO permissions (module_bit, code, description) VALUES > ( 3, 'manage_additional_fields', 'Add, edit, or delete additional custom fields for baskets or subscriptions (also requires order_manage or edit_subscription permissions)'), > ( 3, 'manage_keyboard_shortcuts', 'Manage keyboard shortcuts for the advanced cataloging editor'), > ( 3, 'manage_smtp_servers', 'Manage SMTP servers configuration'), >+ ( 3, 'manage_sftp_servers', 'Manage FTP/SFTP servers configuration'), > ( 3, 'manage_background_jobs', 'Manage background jobs'), > ( 3, 'manage_curbside_pickups', 'Manage curbside pickups'), > ( 3, 'manage_search_filters', 'Manage custom search filters'), >diff --git a/koha-tmpl/intranet-tmpl/prog/en/includes/admin-menu.inc b/koha-tmpl/intranet-tmpl/prog/en/includes/admin-menu.inc >index 4851cef036c..e205044ddcb 100644 >--- a/koha-tmpl/intranet-tmpl/prog/en/includes/admin-menu.inc >+++ b/koha-tmpl/intranet-tmpl/prog/en/includes/admin-menu.inc >@@ -147,7 +147,7 @@ > </ul> > [% END %] > >- [% IF ( CAN_user_parameters_manage_identity_providers || CAN_user_parameters_manage_smtp_servers || CAN_user_parameters_manage_search_targets || CAN_user_parameters_manage_didyoumean || CAN_user_parameters_manage_column_config || CAN_user_parameters_manage_audio_alerts || ( CAN_user_parameters_manage_sms_providers && Koha.Preference('SMSSendDriver') == 'Email' ) || CAN_user_parameters_manage_usage_stats || CAN_user_parameters_manage_additional_fields || ( Koha.Preference('EnableAdvancedCatalogingEditor') && CAN_user_parameters_manage_keyboard_shortcuts ) ) %] >+ [% IF ( CAN_user_parameters_manage_identity_providers || CAN_user_parameters_manage_smtp_servers || CAN_user_parameters_manage_sftp_servers || CAN_user_parameters_manage_search_targets || CAN_user_parameters_manage_didyoumean || CAN_user_parameters_manage_column_config || CAN_user_parameters_manage_audio_alerts || ( CAN_user_parameters_manage_sms_providers && Koha.Preference('SMSSendDriver') == 'Email' ) || CAN_user_parameters_manage_usage_stats || CAN_user_parameters_manage_additional_fields || ( Koha.Preference('EnableAdvancedCatalogingEditor') && CAN_user_parameters_manage_keyboard_shortcuts ) ) %] > <h5>Additional parameters</h5> > <ul> > [% IF ( CAN_user_parameters_manage_identity_providers) %] >@@ -160,6 +160,9 @@ > [% IF ( CAN_user_parameters_manage_smtp_servers ) %] > <li><a href="/cgi-bin/koha/admin/smtp_servers.pl">SMTP servers</a></li> > [% END %] >+ [% IF ( CAN_user_parameters_manage_sftp_servers ) %] >+ <li><a href="/cgi-bin/koha/admin/sftp_servers.pl">FTP/SFTP servers</a></li> >+ [% END %] > [% IF ( CAN_user_parameters_manage_didyoumean ) %] > <li><a href="/cgi-bin/koha/admin/didyoumean.pl">Did you mean?</a></li> > [% END %] >diff --git a/koha-tmpl/intranet-tmpl/prog/en/includes/permissions.inc b/koha-tmpl/intranet-tmpl/prog/en/includes/permissions.inc >index 9d8cb5b9102..52ecbd4266e 100644 >--- a/koha-tmpl/intranet-tmpl/prog/en/includes/permissions.inc >+++ b/koha-tmpl/intranet-tmpl/prog/en/includes/permissions.inc >@@ -257,6 +257,11 @@ > Manage SMTP servers > </span> > <span class="permissioncode">([% name | html %])</span> >+ [%- CASE 'manage_sftp_servers' -%] >+ <span class="sub_permission manage_manage_sftp_servers_subpermission"> >+ Manage FTP/SFTP servers >+ </span> >+ <span class="permissioncode">([% name | html %])</span> > [%- CASE 'manage_column_config' -%] > <span class="sub_permission manage_column_config_subpermission"> > Manage column configuration >diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/admin-home.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/admin/admin-home.tt >index 71df7e12e0f..f5be1ec356b 100644 >--- a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/admin-home.tt >+++ b/koha-tmpl/intranet-tmpl/prog/en/modules/admin/admin-home.tt >@@ -253,7 +253,7 @@ > </dl> > [% END %] > >- [% IF ( ( CAN_user_parameters_manage_identity_providers || CAN_user_parameters_manage_smtp_servers || CAN_user_parameters_manage_search_targets || CAN_user_parameters_manage_didyoumean || CAN_user_parameters_manage_column_config || CAN_user_parameters_manage_audio_alerts || CAN_user_parameters_manage_sms_providers && Koha.Preference('SMSSendDriver') == 'Email' ) || CAN_user_parameters_manage_usage_stats || CAN_user_parameters_manage_additional_fields || CAN_user_parameters_manage_mana || (Koha.Preference('EnableAdvancedCatalogingEditor') && CAN_user_parameters_manage_keyboard_shortcuts) ) %] >+ [% IF ( ( CAN_user_parameters_manage_identity_providers || CAN_user_parameters_manage_smtp_servers || CAN_user_parameters_manage_sftp_servers || CAN_user_parameters_manage_search_targets || CAN_user_parameters_manage_didyoumean || CAN_user_parameters_manage_column_config || CAN_user_parameters_manage_audio_alerts || CAN_user_parameters_manage_sms_providers && Koha.Preference('SMSSendDriver') == 'Email' ) || CAN_user_parameters_manage_usage_stats || CAN_user_parameters_manage_additional_fields || CAN_user_parameters_manage_mana || (Koha.Preference('EnableAdvancedCatalogingEditor') && CAN_user_parameters_manage_keyboard_shortcuts) ) %] > <h3>Additional parameters</h3> > <dl> > <!-- <dt><a href="/cgi-bin/koha/admin/printers.pl">Network Printers</a></dt> >@@ -272,6 +272,10 @@ > <dt><a href="/cgi-bin/koha/admin/smtp_servers.pl">SMTP servers</a></dt> > <dd>Define which SMTP servers to use</dd> > [% END %] >+ [% IF ( CAN_user_parameters_manage_sftp_servers ) %] >+ <dt><a href="/cgi-bin/koha/admin/sftp_servers.pl">FTP/SFTP servers</a></dt> >+ <dd>Define available FTP/SFTP servers</dd> >+ [% END %] > [% IF ( CAN_user_parameters_manage_didyoumean ) %] > <dt><a href="/cgi-bin/koha/admin/didyoumean.pl">Did you mean?</a></dt> > <dd>Choose which plugins to use to suggest searches to patrons and staff</dd> >diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/sftp_servers.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/admin/sftp_servers.tt >new file mode 100644 >index 00000000000..0293e054433 >--- /dev/null >+++ b/koha-tmpl/intranet-tmpl/prog/en/modules/admin/sftp_servers.tt >@@ -0,0 +1,788 @@ >+[% USE raw %] >+[% USE Asset %] >+[% PROCESS 'i18n.inc' %] >+[% SET footerjs = 1 %] >+[% INCLUDE 'doc-head-open.inc' %] >+<title>[% FILTER collapse %] >+ [% IF op == 'add_form' %] >+ [% t("New FTP/SFTP server") | html %] › >+ [% ELSIF op == 'edit_form' %] >+ [% tx("Modify FTP/SFTP server '{sftp_server}'", { sftp_server = sftp_server.name }) | html %] › >+ [% ELSIF op == 'test_form' %] >+ [% tx("Test FTP/SFTP server '{sftp_server}'", { sftp_server = sftp_server.name }) | html %] › >+ [% END %] >+ [% t("FTP/SFTP Servers") | html %] › >+ [% t("Administration") | html %] › >+ [% t("Koha") | html %] >+[% END %]</title> >+[% INCLUDE 'doc-head-close.inc' %] >+</head> >+ >+<body id="admin_sftp_servers" class="admin"> >+[% WRAPPER 'header.inc' %] >+ [% INCLUDE 'prefs-admin-search.inc' %] >+[% END %] >+ >+[% WRAPPER 'sub-header.inc' %] >+ [% WRAPPER breadcrumbs %] >+ [% WRAPPER breadcrumb_item %] >+ <a href="/cgi-bin/koha/admin/admin-home.pl">Administration</a> >+ [% END %] >+ >+ [% IF op == 'add_form' || op == 'edit_form' || op == 'test_form' %] >+ [% WRAPPER breadcrumb_item %] >+ <a href="/cgi-bin/koha/admin/sftp_servers.pl">FTP/SFTP servers</a> >+ [% END %] >+ [% END %] >+ >+ [% IF op == 'add_form' %] >+ [% WRAPPER breadcrumb_item bc_active= 1 %] >+ <span>New FTP/SFTP server</span> >+ [% END %] >+ >+ [% ELSIF op == 'edit_form' %] >+ [% WRAPPER breadcrumb_item bc_active= 1 %] >+ [% tx("Modify FTP/SFTP server '{sftp_server}'", { sftp_server = sftp_server.name }) | html %] >+ [% END %] >+ >+ [% ELSIF op == 'test_form' %] >+ [% WRAPPER breadcrumb_item bc_active= 1 %] >+ [% tx("Test FTP/SFTP server '{sftp_server}'", { sftp_server = sftp_server.name }) | html %] >+ [% END %] >+ >+ [% ELSE %] >+ [% WRAPPER breadcrumb_item bc_active= 1 %] >+ <span>FTP/SFTP servers</span> >+ [% END %] >+ [% END %] >+ [% END #/ WRAPPER breadcrumbs %] >+[% END #/ WRAPPER sub-header.inc %] >+ >+<div class="main container-fluid"> >+ <div class="row"> >+ <div class="col-md-10 order-md-2 order-sm-1"> >+ <main> >+ [% INCLUDE 'messages.inc' %] >+ >+[% FOREACH m IN messages %] >+ <div class="alert alert-[% m.type | html %]" id="sftp_action_result_dialog"> >+ [% SWITCH m.code %] >+ [% CASE 'error_on_insert' %] >+ <span>An error occurred when adding the server. The passed ID already exists.</span> >+ [% CASE 'error_on_update' %] >+ <span>An error occurred trying to open the server for editing. The passed ID is invalid.</span> >+ [% CASE 'error_on_edit' %] >+ <span>An error occurred trying to open the server for editing. The passed ID is invalid.</span> >+ [% CASE 'error_on_test' %] >+ <span>An error occurred when connecting to this server. Please see the text below for more information.</span> >+ [% CASE 'success_on_update' %] >+ <span>Server updated successfully.</span> >+ [% CASE 'success_on_insert' %] >+ <span>Server added successfully.</span> >+ [% CASE %] >+ <span>[% m.code | html %]</span> >+ [% END %] >+ </div> >+[% END %] >+ >+ <div class="alert alert-info" id="sftp_delete_success" style="display: none;"></div> >+ <div class="alert alert-warning" id="sftp_delete_error" style="display: none;"></div> >+ >+[% IF op == 'add_form' %] >+ <!-- Modal --> >+ <div id="confirm_key_accept" class="modal" tabindex="-1" role="dialog" aria-labelledby="confirm_key_accept_submit" aria-hidden="true"> >+ <div class="modal-dialog modal-lg"> >+ <div class="modal-content modal-lg"> >+ <div class="modal-header"> >+ <h1 class="modal-title">Are you sure?</h1> >+ <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button> >+ </div> >+ <div class="modal-body"> >+ <div id="modal_message" class="alert alert-warning">Because you are using the SFTP transport, please run a test of this connection to accept the server's host key.</div> >+ <p>Before saving, please check the following details again to make sure you are certain they are correct. Sending or receiving data from an unknown source potentially puts your system at risk.</p> >+ <table class="mx-4 mb-3"> >+ <thead></thead> >+ <tbody> >+ <tr> >+ <td><strong>Host</strong></td> >+ <td id="modal_host"></td> >+ </tr> >+ <tr> >+ <td><strong>Port</strong></td> >+ <td id="modal_port"></td> >+ </tr> >+ <tr> >+ <td><strong>Transport</strong></td> >+ <td id="modal_transport"></td> >+ </tr> >+ <tr> >+ <td><strong>Username</strong></td> >+ <td id="modal_user_name"></td> >+ </tr> >+ <tr> >+ <td><strong>Authentication mode</strong></td> >+ <td id="modal_auth_mode"></td> >+ </tr> >+ </tbody> >+ </table> >+ <p>If you are ready to progress with these details, please click Save.</p> >+ </div> >+ <div class="modal-footer"> >+ <button class="btn btn-default approve" type="submit"><i class="fa fa-check"></i> Save</button> >+ <button class="btn btn-default deny cancel" type="button" data-bs-dismiss="modal"><i class="fa fa-times"></i> Cancel</button> >+ </div> >+ </form> >+ </div> >+ </div> >+ </div> >+ <!-- END Modal --> >+ >+ <h1>New FTP/SFTP server</h1> >+ >+ <form action="/cgi-bin/koha/admin/sftp_servers.pl" id="add" name="add" class="validated" method="post"> >+ [% INCLUDE 'csrf-token.inc' %] >+ <input type="hidden" name="op" value="cud-add" /> >+ <fieldset class="rows"> >+ <ol> >+ <li> >+ <label for="sftp_name" class="required">Name: </label> >+ <input type="text" name="sftp_name" id="sftp_name" size="60" class="required focus" required="required" /> >+ <span class="required">Required</span> >+ </li> >+ </ol> >+ </fieldset> >+ >+ <fieldset class="rows"> >+ <ol> >+ <li> >+ <label for="sftp_host" class="required">Host: </label> >+ <input type="text" value="localhost" name="sftp_host" id="sftp_host" size="60" class="required" /> >+ <span class="required">Required</span> >+ </li> >+ <li> >+ <label for="sftp_port" class="required">Port: </label> >+ <input type="text" inputmode="numeric" pattern="[0-9]*" value="22" name="sftp_port" id="sftp_port" size="20" class="required" /> >+ <span class="required">Required</span> >+ </li> >+ <li> >+ <label for="sftp_transport" class="required">Transport: </label> >+ <select name="sftp_transport" id="sftp_transport" class="required"> >+ <option value="ftp">FTP</option> >+ <option value="sftp" selected="selected">SFTP</option> >+ </select> >+ <span class="required">Required</span> >+ </li> >+ <li> >+ <label for="sftp_passiv">Passive mode: </label> >+ <select name="sftp_passiv" id="sftp_passiv" disabled="disabled"> >+ <option value="1" selected="selected">On (Recommended)</option> >+ <option value="0">Off</option> >+ </select> >+ <span class="hint">Only applies to FTP connections</span> >+ </li> >+ <li> >+ <label for="sftp_auth_mode">Authentication mode: </label> >+ <select name="sftp_auth_mode" id="sftp_auth_mode"> >+ <option value="password" selected="selected">Password-based</option> >+ <option value="key_file">Key file-based</option> >+ <option value="noauth">No authentication</option> >+ </select> >+ </li> >+ <li> >+ <label for="sftp_user_name" class="required">Username: </label> >+ <input type="text" name="sftp_user_name" id="sftp_user_name" size="60" autocomplete="off" class="required" /> >+ <span class="required">Required</span> >+ </li> >+ <li> >+ <label for="sftp_password">Password: </label> >+ <input type="password" name="sftp_password" id="sftp_password" size="60" autocomplete="off" /> >+ </li> >+ <li> >+ <label for="sftp_key_file">Key file: </label> >+ <textarea name="sftp_key_file" id="sftp_key_file" rows="10" cols="58"></textarea> >+ <span class="hint">Only applies to SFTP connections</span> >+ </li> >+ <li> >+ <label for="sftp_debug_mode">Debug mode: </label> >+ <select name="sftp_debug_mode" id="sftp_debug_mode"> >+ <option value="1">Enabled</option> >+ <option value="0" selected="selected">Disabled</option> >+ </select> >+ <span class="hint">Enables additional debug output in the logs</span> >+ </li> >+ </ol> >+ </fieldset> >+ <fieldset class="action"> >+ <a id="confirm_key_accept_submit" data-bs-target="#confirm_key_accept" class="btn btn-primary" data-bs-toggle="modal">Submit</a> >+ <a class="cancel" href="/cgi-bin/koha/admin/sftp_servers.pl">Cancel</a> >+ </fieldset> >+ </form> >+[% END %] >+ >+[% IF op == 'edit_form' && !messages %] >+ <!-- Modal --> >+ <div id="confirm_key_accept" class="modal" tabindex="-1" role="dialog" aria-labelledby="confirm_key_accept_submit" aria-hidden="true"> >+ <div class="modal-dialog modal-lg"> >+ <div class="modal-content modal-lg"> >+ <div class="modal-header"> >+ <h1 class="modal-title">Are you sure?</h1> >+ <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button> >+ </div> >+ <div class="modal-body"> >+ <div id="modal_message" class="alert alert-warning">Because you are using the SFTP transport, please run a test of this connection to accept the server's host key.</div> >+ <p>Before saving, please check the following details again to make sure you are certain they are correct. Sending or receiving data from an unknown source potentially puts your system at risk.</p> >+ <table class="mx-4 mb-3"> >+ <thead></thead> >+ <tbody> >+ <tr> >+ <td><strong>Host</strong></td> >+ <td id="modal_host"></td> >+ </tr> >+ <tr> >+ <td><strong>Port</strong></td> >+ <td id="modal_port"></td> >+ </tr> >+ <tr> >+ <td><strong>Transport</strong></td> >+ <td id="modal_transport"></td> >+ </tr> >+ <tr> >+ <td><strong>Username</strong></td> >+ <td id="modal_user_name"></td> >+ </tr> >+ <tr> >+ <td><strong>Authentication mode</strong></td> >+ <td id="modal_auth_mode"></td> >+ </tr> >+ </tbody> >+ </table> >+ <p>If you are ready to progress with these details, please click Save.</p> >+ </div> >+ <div class="modal-footer"> >+ <button class="btn btn-default approve" type="submit"><i class="fa fa-check"></i> Save</button> >+ <button class="btn btn-default deny cancel" type="button" data-bs-dismiss="modal"><i class="fa fa-times"></i> Cancel</button> >+ </div> >+ </form> >+ </div> >+ </div> >+ </div> >+ <!-- END Modal --> >+ >+ <h1>[% tx("Modify FTP/SFTP server '{sftp_server}'", { sftp_server = sftp_server.name }) | html %]</h1> >+ >+ <form action="/cgi-bin/koha/admin/sftp_servers.pl" id="edit_save" name="edit_save" class="validated" method="post"> >+ [% INCLUDE 'csrf-token.inc' %] >+ <input type="hidden" name="op" value="cud-edit_save" /> >+ <input type="hidden" name="sftp_server_id" value="[%- sftp_server.id | html -%]" /> >+ <fieldset class="rows"> >+ <ol> >+ <li> >+ <label for="sftp_name" class="required">Name: </label> >+ <input type="text" value="[% sftp_server.name | html %]" name="sftp_name" id="sftp_name" size="60" class="required focus" required="required" /> >+ <span class="required">Required</span> >+ </li> >+ </ol> >+ </fieldset> >+ >+ <fieldset class="rows"> >+ <ol> >+ <li> >+ <label for="sftp_host" class="required">Host: </label> >+ <input type="text" value="[% sftp_server.host | html %]" name="sftp_host" id="sftp_host" size="60" class="required" /> >+ <span class="required">Required</span> >+ </li> >+ <li> >+ <label for="sftp_port" class="required">Port: </label> >+ <input type="text" inputmode="numeric" pattern="[0-9]*" value="[% sftp_server.port | html %]" name="sftp_port" id="sftp_port" size="20" class="required"/> >+ <span class="required">Required</span> >+ </li> >+ <li> >+ <label for="sftp_transport" class="required">Transport: </label> >+ <select name="sftp_transport" id="sftp_transport" class="required"> >+ [% IF sftp_server.transport == 'ftp' %] >+ <option value="ftp" selected="selected">FTP</option> >+ [% ELSE %] >+ <option value="ftp">FTP</option> >+ [% END %] >+ [% IF sftp_server.transport == 'sftp' %] >+ <option value="sftp" selected="selected">SFTP</option> >+ [% ELSE %] >+ <option value="sftp">SFTP</option> >+ [% END %] >+ </select> >+ <span class="required">Required</span> >+ </li> >+ <li> >+ <label for="sftp_passiv">Passive mode: </label> >+ <select name="sftp_passiv" id="sftp_passiv" disabled="disabled"> >+ [% IF sftp_server.passiv == 1 %] >+ <option value="1" selected="selected">Enabled (Recommended)</option> >+ [% ELSE %] >+ <option value="1">Enabled (Recommended)</option> >+ [% END %] >+ [% IF sftp_server.passiv == 0 %] >+ <option value="0" selected="selected">Disabled</option> >+ [% ELSE %] >+ <option value="0">Disabled</option> >+ [% END %] >+ </select> >+ <span class="hint">Only applies to FTP connections</span> >+ </li> >+ <li> >+ <label for="sftp_auth_mode">Authentication mode: </label> >+ <select name="sftp_auth_mode" id="sftp_auth_mode"> >+ [% IF sftp_server.auth_mode == 'password' %] >+ <option value="password" selected="selected">Password-based</option> >+ [% ELSE %] >+ option value="password">Password-based</option> >+ [% END %] >+ [% IF sftp_server.auth_mode == 'key_file' %] >+ <option value="key_file" selected="selected">Key file-based</option> >+ [% ELSE %] >+ <option value="key_file">Key file-based</option> >+ [% END %] >+ [% IF sftp_server.auth_mode == 'noauth' %] >+ <option value="noauth" selected="selected">No authentication</option> >+ [% ELSE %] >+ <option value="noauth">No authentication</option> >+ [% END %] >+ </select> >+ </li> >+ <li> >+ <label for="sftp_user_name" class="required">Username: </label> >+ <input type="text" value="[% sftp_server.user_name | html %]" name="sftp_user_name" id="sftp_user_name" size="60" autocomplete="off" class="required" /> >+ <span class="required">Required</span> >+ </li> >+ <li> >+ <label for="sftp_password">Password: </label> >+ <input type="password" value="[% sftp_server_plain_text_password | html %]" name="sftp_password" id="sftp_password" size="60" autocomplete="off" /> >+ </li> >+ <li> >+ <label for="sftp_key_file">Key file path: </label> >+ <textarea name="sftp_key_file" id="sftp_key_file" rows="10" cols="58">[% sftp_server_plain_text_key | html %]</textarea> >+ <span class="hint">Only applies to SFTP connections</span> >+ </li> >+ <li> >+ <label for="sftp_debug_mode">Debug mode: </label> >+ <select name="sftp_debug_mode" id="sftp_debug_mode"> >+ [% IF sftp_server.debug == 1 %] >+ <option value="1" selected="selected">Enabled</option> >+ [% ELSE %] >+ <option value="1">Enabled</option> >+ [% END %] >+ [% IF sftp_server.debug == 0 %] >+ <option value="0" selected="selected">Disabled</option> >+ [% ELSE %] >+ <option value="0">Disabled</option> >+ [% END %] >+ </select> >+ <span class="hint">Enables additional debug output in the logs</span> >+ </li> >+ </ol> >+ </fieldset> >+ <fieldset class="action"> >+ <a id="confirm_key_accept_submit" data-bs-target="#confirm_key_accept" class="btn btn-primary" data-bs-toggle="modal">Submit</a> >+ <a class="cancel" href="/cgi-bin/koha/admin/sftp_servers.pl">Cancel</a> >+ </fieldset> >+ </form> >+[% END %] >+ >+[% IF op == 'test_form' %] >+ <div id="toolbar" class="btn-toolbar"> >+ <a class="btn btn-default" id="newtest" href="/cgi-bin/koha/admin/sftp_servers.pl?op=test_form&sftp_server_id=[% sftp_server.id | html %]"><i class="fa-solid fa-rotate-right"></i> Retry test</a> >+ </div> >+ <h1>[% tx("Test FTP/SFTP server '{sftp_server}'", { sftp_server = sftp_server.name }) | html %]</h1> >+ <div class="page-section"> >+ <pre>>> Testing the FTP/SFTP server for you</pre> >+ <pre>>> Connection details will be as follows:</pre> >+ <pre>Transport: [% sftp_server.transport FILTER upper | html %]</pre> >+ <pre>Username: [% sftp_server.user_name | html %]</pre> >+ <pre>Host: [% sftp_server.host | html %]</pre> >+ <pre>Port: [% sftp_server.port | html %]</pre> >+ <pre>>> Okay, starting tests . . . </pre> >+ [% FOREACH result IN sftp_server_test_result.pairs %] >+ <pre>=================================================</pre> >+ [% SWITCH result.key %] >+ [% CASE '1_sftp_conn' %] >+ <pre>>> Testing SFTP connecivity</pre> >+ [% CASE '1_ftp_conn' %] >+ <pre>>> Testing FTP connecivity</pre> >+ [% CASE ['2_sftp_ls', '3_ftp_ls'] %] >+ <pre>>> Testing we can list directories</pre> >+ [% CASE '2_ftp_login' %] >+ <pre>>> Testing we can log in</pre> >+ [% CASE ['3_sftp_write', '4_ftp_write'] %] >+ <pre>>> Testing we can write a test file</pre> >+ [% CASE ['4_sftp_del', '5_ftp_del'] %] >+ <pre>>> Testing we can delete test file</pre> >+ [% CASE DEFAULT %] >+ <pre>>> [% result.key | html %]</pre> >+ [% END %] >+ <pre>Executing [% result.key | html %] test . . . </pre> >+ [% IF ( result.value.err ) %] >+ <pre>ERROR: [% result.value.err | html %]</pre> >+ [% ELSE %] >+ [% IF ( result.value.msg ) %] >+ <pre>PASSED: [% result.value.msg | html %]</pre> >+ [% ELSE %] >+ <pre>PASSED</pre> >+ [% END %] >+ [% END %] >+ [% END %] >+ <pre>=================================================</pre> >+ </div> >+[% END %] >+ >+[% IF op == 'list' %] >+ >+ <div id="toolbar" class="btn-toolbar"> >+ <a class="btn btn-default" id="new_sftp_server" href="/cgi-bin/koha/admin/sftp_servers.pl?op=add_form"><i class="fa fa-plus"></i> New FTP/SFTP server</a> >+ </div> >+ >+ <h1>FTP/SFTP servers</h1> >+ >+ [% IF servers_count < 1 %] >+ <div class="alert alert-info" id="dno_servers_message"> >+ <p> >+ <em>There are no FTP/SFTP servers defined.</em><br /> >+ To create one, use the <strong>new FTP/SFTP server</strong> button above. >+ </p> >+ </div> >+ [% ELSE %] >+ <div class="page-section"> >+ <table id="sftp_servers"> >+ <thead> >+ <tr> >+ <th>Name</th> >+ <th>Host</th> >+ <th>Port</th> >+ <th>Transport</th> >+ <th>Passive mode</th> >+ <th>Authentication mode</th> >+ <th>Username</th> >+ <th>Debug</th> >+ <th data-class-name="actions noExport">Actions</th> >+ </tr> >+ </thead> >+ </table> >+ </div> <!-- /.page-section --> >+ [% END %] >+[% END %] >+ >+ <div id="delete_confirm_modal" class="modal" tabindex="-1" role="dialog" aria-labelledby="delete_confirm_modal_label" aria-hidden="true"> >+ <div class="modal-dialog"> >+ <div class="modal-content"> >+ <div class="modal-header"> >+ <h1 class="modal-title" id="delete_confirm_modal_label">Delete server</h1> >+ <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button> >+ </div> >+ <div class="modal-body"> >+ <div id="delete_confirm_dialog"></div> >+ </div> >+ <div class="modal-footer"> >+ <button type="button" class="btn btn-danger" id="delete_confirm_modal_button" data-bs-toggle="modal">Delete</button> >+ <button type="button" class="btn btn-default" data-bs-dismiss="modal">Close</button> >+ </div> >+ </div> <!-- /.modal-content --> >+ </div> <!-- /.modal-dialog --> >+ </div> <!-- #delete_confirm_modal --> >+ >+ </main> >+ </div> <!-- /.col-md-10.order-md-2 --> >+ >+ <div class="col-md-2 order-sm-2 order-md-1"> >+ <aside> >+ [% INCLUDE 'admin-menu.inc' %] >+ </aside> >+ </div> <!-- /.col-md-2.order-md-1 --> >+ </div> <!-- /.row --> >+ >+ >+[% MACRO jsinclude BLOCK %] >+ [% Asset.js("js/admin-menu.js") | $raw %] >+ [% INCLUDE 'datatables.inc' %] >+ <script> >+ $(document).ready(function() { >+ >+ var sftp_servers_url = '/api/v1/config/sftp_servers'; >+ window.sftp_servers = $("#sftp_servers").kohaTable({ >+ "ajax": { >+ "url": sftp_servers_url >+ }, >+ 'language': { >+ 'emptyTable': '<div class="alert alert-info">'+_("There are no FTP/SFTP servers defined.")+'</div>' >+ }, >+ "columnDefs": [ { >+ "targets": [0,1], >+ "render": function(data, type, row, meta) { >+ if(type == 'display') { >+ if(data != null) { >+ return data.escapeHtml(); >+ } >+ else { >+ return "Default"; >+ } >+ } >+ return data; >+ } >+ } ], >+ "columns": [ >+ { >+ "data": "name", >+ "searchable": true, >+ "orderable": true >+ }, >+ { >+ "data": "host", >+ "searchable": true, >+ "orderable": true >+ }, >+ { >+ "data": "port", >+ "searchable": true, >+ "orderable": false >+ }, >+ { >+ "data": "transport", >+ "render": function(data, type, row, meta) { >+ return data.toUpperCase(); >+ }, >+ "searchable": true, >+ "orderable": false >+ }, >+ { >+ "data": "passiv", >+ "render": function(data, type, row, meta) { >+ if(data == true) { >+ return "[% tp("Active", "On") | html %]"; >+ } >+ else { >+ return _("Off"); >+ } >+ }, >+ "searchable": false, >+ "orderable": false >+ }, >+ { >+ "data": "auth_mode", >+ "render": function(data, type, row, meta) { >+ if(data == "password") { >+ return _("Password-based"); >+ } >+ else if(data == "key_file") { >+ return _("Key file-based"); >+ } >+ else { >+ return _("No authentication"); >+ } >+ }, >+ "searchable": false, >+ "orderable": false >+ }, >+ { >+ "data": "user_name", >+ "searchable": false, >+ "orderable": false >+ }, >+ { >+ "data": "debug", >+ "render": function(data, type, row, meta) { >+ if(data == true) { >+ return "[% tp("Active", "On") | html %]"; >+ } >+ else { >+ return _("Off"); >+ } >+ }, >+ "searchable": false, >+ "orderable": false >+ }, >+ { >+ "data": function(row, type, val, meta) { >+ var result = '<a class="btn btn-default btn-xs" role="button" href="/cgi-bin/koha/admin/sftp_servers.pl?op=test_form&sftp_server_id='+ encodeURIComponent(row.sftp_server_id) +'"><i class="fa-solid fa-vial" aria-hidden="true"></i> '+_("Test")+'</a>'+"\n"; >+ result += '<a class="btn btn-default btn-xs" role="button" href="/cgi-bin/koha/admin/sftp_servers.pl?op=edit_form&sftp_server_id='+ encodeURIComponent(row.sftp_server_id) +'"><i class="fa-solid fa-pencil" aria-hidden="true"></i> '+_("Edit")+'</a>'+"\n"; >+ result += '<a class="btn btn-default btn-xs delete_server" role="button" href="#" data-bs-toggle="modal" data-bs-target="#delete_confirm_modal" data-sftp-server-id="'+ encodeURIComponent(row.sftp_server_id) +'" data-sftp-server-name="'+ encodeURIComponent(row.name.escapeHtml()) +'"><i class="fa fa-trash-can" aria-hidden="true"></i> '+_("Delete")+'</a>'; >+ return result; >+ }, >+ "searchable": false, >+ "orderable": false >+ } >+ ], >+ createdRow: function(row, data, dataIndex) { >+ if(data.is_default) { >+ $(row).addClass('default warn'); >+ } >+ if(data.debug) { >+ $(row).addClass('debug'); >+ } >+ }, >+ }); >+ >+ $('#sftp_servers').on("click", '.delete_server', function() { >+ var sftp_server_id = $(this).data('sftp-server-id'); >+ var sftp_server_name = decodeURIComponent($(this).data('sftp-server-name')); >+ >+ $("#delete_confirm_dialog").html( >+ _("You are about to delete the '%s' FTP/SFTP server.").format(sftp_server_name) >+ ); >+ $("#delete_confirm_modal_button").data('sftp-server-id', sftp_server_id); >+ $("#delete_confirm_modal_button").data('sftp-server-name', sftp_server_name); >+ }); >+ >+ $("#delete_confirm_modal_button").on("click", function() { >+ >+ var sftp_server_id = $(this).data('sftp-server-id'); >+ var sftp_server_name = $(this).data('sftp-server-name'); >+ >+ $.ajax({ >+ method: "DELETE", >+ url: "/api/v1/config/sftp_servers/"+sftp_server_id >+ }).success(function() { >+ window.sftp_servers.api().ajax.reload(function(data) { >+ $("#sftp_action_result_dialog").hide(); >+ $("#sftp_delete_success").html(_("Server '%s' deleted successfully.").format(sftp_server_name)).show(); >+ }); >+ }).fail(function() { >+ $("#sftp_delete_error").html(_("Error deleting server '%s'. Please ensure all linked EDI accounts are unlinked or deleted. Check the logs for details.").format(sftp_server_name)).show(); >+ }).done(function() { >+ $("#delete_confirm_modal").modal('hide'); >+ }); >+ }); >+ >+ // run transportChange on pageload, and again every time sftp_transport changes >+ transportChange(); >+ $("#sftp_transport").on("change", function(event) { >+ transportChange(); >+ }); >+ >+ // run authModeChange on pageload, and again every time sftp_auth_mode changes >+ authModeChange(); >+ $("#sftp_auth_mode").on("change", function(event) { >+ authModeChange(); >+ }); >+ >+ $('#confirm_key_accept_submit').on('click', function(event) { >+ event.preventDefault(); >+ >+ if ( $('#add').length > 0 ) { // has to be nested to avoid errors :-( >+ if( $('#add').valid() == true ) { >+ modalChange(); >+ $('#confirm_key_accept').modal('show'); >+ } else { >+ $('#confirm_key_accept').modal('hide'); >+ } >+ } >+ >+ if ( $('#edit_save').length > 0 ) { // has to be nested to avoid errors :-( >+ if( $('#edit_save').valid() == true ) { >+ modalChange(); >+ $('#confirm_key_accept').modal('show'); >+ } else { >+ $('#confirm_key_accept').modal('hide'); >+ } >+ } >+ >+ }); >+ >+ $('#confirm_key_accept .approve').on('click', function() { >+ $('#confirm_key_accept .deny').click(); >+ >+ if ( $('#add').length > 0 ) { >+ $('#add').submit(); >+ } >+ >+ if ( $('#edit_save').length > 0 ) { >+ $('#edit_save').submit(); >+ } >+ }); >+ >+ }); >+ >+ function transportChange() { >+ let sftp_transport = $("#sftp_transport"); >+ >+ if(sftp_transport.val() == "ftp") { >+ // disable / enable relevant options >+ $("#sftp_host").removeAttr("disabled"); >+ $("#sftp_port").removeAttr("disabled"); >+ $("#sftp_passiv").removeAttr("disabled"); >+ $("#sftp_auth_mode").removeAttr("disabled"); >+ $("#sftp_user_name").removeAttr("disabled"); >+ $("#sftp_password").removeAttr("disabled"); >+ $("#sftp_key_file").attr("disabled", "disabled"); >+ >+ // ... for auth_mode dropdown >+ $("#sftp_auth_mode option[value='password']").removeAttr("disabled"); >+ $("#sftp_auth_mode option[value='key_file']").attr("disabled", "disabled"); >+ $("#sftp_auth_mode option[value='noauth']").removeAttr("disabled"); >+ // also reset the selected value CONDITIONALLY >+ if($("#sftp_auth_mode option:selected").val() == "key_file") { >+ $("#sftp_auth_mode option[value='password']").prop("selected", true); >+ } >+ >+ // check the port >+ let sftp_port = $("#sftp_port").val(); >+ if(sftp_port == 22) $("#sftp_port").val("21"); >+ >+ // trigger authModeChange so the auth fields are correct >+ authModeChange(); >+ } else if(sftp_transport.val() == "sftp") { >+ // disable / enable relevant options >+ $("#sftp_host").removeAttr("disabled"); >+ $("#sftp_port").removeAttr("disabled"); >+ $("#sftp_passiv").attr("disabled", "disabled"); >+ $("#sftp_auth_mode").removeAttr("disabled"); >+ $("#sftp_user_name").removeAttr("disabled"); >+ $("#sftp_password").removeAttr("disabled"); >+ $("#sftp_key_file").removeAttr("disabled"); >+ >+ // ... for auth_mode dropdown >+ $("#sftp_auth_mode option[value='password']").removeAttr("disabled"); >+ $("#sftp_auth_mode option[value='key_file']").removeAttr("disabled"); >+ $("#sftp_auth_mode option[value='noauth']").removeAttr("disabled"); >+ // also reset the selected value CONDITIONALLY >+ $("#sftp_passiv option[value='1']").prop("selected", true); >+ >+ // check the port >+ let sftp_port = $("#sftp_port").val(); >+ if(sftp_port == 21) $("#sftp_port").val("22"); >+ >+ // trigger authModeChange so the auth fields are correct >+ return authModeChange(); >+ } >+ } >+ >+ function authModeChange() { >+ let sftp_auth_mode = $("#sftp_auth_mode").val(); >+ >+ if(sftp_auth_mode == "password") { >+ // disable / enable relevant options >+ $("#sftp_password").removeAttr("disabled"); >+ $("#sftp_key_file").attr("disabled", "disabled"); >+ } else if(sftp_auth_mode == "key_file") { >+ // disable / enable relevant options >+ $("#sftp_password").attr("disabled", "disabled"); >+ $("#sftp_key_file").removeAttr("disabled"); >+ } else { >+ // disable / enable relevant options >+ $("#sftp_password").attr("disabled", "disabled"); >+ $("#sftp_key_file").attr("disabled", "disabled"); >+ } >+ } >+ >+ function modalChange() { >+ // should we show the sftp warning? >+ $('#modal_message').hide(); >+ if ( $('#sftp_transport').val() == 'sftp' ) $('#modal_message').show(); >+ >+ // populate modal >+ $('#modal_host').text( $('#sftp_host').val() ); >+ $('#modal_port').text( $('#sftp_port').val() ); >+ $('#modal_transport').text( $('#sftp_transport option:selected').text() ); >+ $('#modal_user_name').text( $('#sftp_user_name').val() ); >+ $('#modal_auth_mode').text( $('#sftp_auth_mode option:selected').text() ); >+ } >+ </script> >+[% END %] >+ >+[% INCLUDE 'intranet-bottom.inc' %] >diff --git a/t/Koha/Auth/Permissions.t b/t/Koha/Auth/Permissions.t >index 4b398646f3b..b956f12dfbf 100755 >--- a/t/Koha/Auth/Permissions.t >+++ b/t/Koha/Auth/Permissions.t >@@ -207,6 +207,7 @@ subtest 'superlibrarian tests' => sub { > 'CAN_user_parameters_manage_search_targets' => 1, > 'CAN_user_parameters_manage_sms_providers' => 1, > 'CAN_user_parameters_manage_smtp_servers' => 1, >+ 'CAN_user_parameters_manage_sftp_servers' => 1, > 'CAN_user_parameters_manage_sysprefs' => 1, > 'CAN_user_parameters_manage_transfers' => 1, > 'CAN_user_parameters_manage_usage_stats' => 1, >diff --git a/t/db_dependent/Koha/SFTP/Server.t b/t/db_dependent/Koha/SFTP/Server.t >new file mode 100755 >index 00000000000..1342bd25845 >--- /dev/null >+++ b/t/db_dependent/Koha/SFTP/Server.t >@@ -0,0 +1,170 @@ >+#!/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 <http://www.gnu.org/licenses>. >+ >+use Modern::Perl; >+ >+use Test::More tests => 5; >+use Test::Exception; >+use Test::Warn; >+ >+use Koha::SFTP::Servers; >+ >+use t::lib::TestBuilder; >+use t::lib::Mocks; >+ >+my $schema = Koha::Database->new->schema; >+my $builder = t::lib::TestBuilder->new; >+ >+subtest 'store() tests' => sub { >+ >+ plan tests => 2; >+ >+ $schema->storage->txn_begin; >+ >+ my $sftp_server = $builder->build_object( >+ { >+ class => 'Koha::SFTP::Servers', >+ value => { >+ password => undef, >+ key_file => undef, >+ }, >+ } >+ ); >+ $sftp_server->set( { password => 'test123' } )->store; >+ >+ ok( $sftp_server->password ne 'test123', 'Password should not be in plain text' ); >+ is( length( $sftp_server->password ), 64, 'Password has should be 64 characters long' ); >+ >+ $schema->storage->txn_rollback; >+}; >+ >+subtest 'to_api() tests' => sub { >+ >+ plan tests => 1; >+ >+ $schema->storage->txn_begin; >+ >+ my $sftp_server = $builder->build_object( >+ { >+ class => 'Koha::SFTP::Servers', >+ value => { >+ password => undef, >+ key_file => undef, >+ }, >+ } >+ ); >+ >+ ok( !exists $sftp_server->to_api->{password}, 'Password is not part of the API representation' ); >+ >+ $schema->storage->txn_rollback; >+}; >+ >+subtest 'plain_text_password() tests' => sub { >+ >+ plan tests => 2; >+ >+ $schema->storage->txn_begin; >+ >+ my $sftp_server = $builder->build_object( >+ { >+ class => 'Koha::SFTP::Servers', >+ value => { >+ password => undef, >+ key_file => undef, >+ }, >+ } >+ ); >+ >+ ## create a password >+ $sftp_server->set( { password => 'test123' } )->store; >+ >+ ## retrieve it back out >+ my $sftp_server_plain_text_password = $sftp_server->plain_text_password; >+ >+ isnt( $sftp_server_plain_text_password, $sftp_server->password, 'Password and password hash shouldn\'t match' ); >+ is( $sftp_server_plain_text_password, 'test123', 'Password should be in plain text' ); >+ >+ $schema->storage->txn_rollback; >+}; >+ >+subtest 'plain_text_key() tests' => sub { >+ >+ plan tests => 2; >+ >+ $schema->storage->txn_begin; >+ >+ my $sftp_server = $builder->build_object( >+ { >+ class => 'Koha::SFTP::Servers', >+ value => { >+ password => undef, >+ key_file => undef, >+ }, >+ } >+ ); >+ >+ ## create a key >+ $sftp_server->set( { key_file => '321tset' } )->store; >+ >+ ## retrieve it back out >+ my $sftp_server_plain_text_key = $sftp_server->plain_text_key; >+ >+ isnt( $sftp_server_plain_text_key, $sftp_server->key_file, 'Key file and key file hash shouldn\'t match' ); >+ is( $sftp_server_plain_text_key, "321tset\n", 'Key file should be in plain text' ); >+ >+ $schema->storage->txn_rollback; >+}; >+ >+subtest 'write_key_file() tests' => sub { >+ >+ plan tests => 3; >+ >+ $schema->storage->txn_begin; >+ >+ my $sftp_server = $builder->build_object( >+ { >+ class => 'Koha::SFTP::Servers', >+ value => { >+ password => undef, >+ key_file => undef, >+ }, >+ } >+ ); >+ >+ ## create a key >+ $sftp_server->set( { key_file => '321tset' } )->store; >+ >+ my $path = '/tmp/kohadev_test'; >+ t::lib::Mocks::mock_config( 'upload_path', $path ); >+ mkdir $path if !-d $path; >+ >+ my $first_test = $sftp_server->write_key_file; >+ >+ my $file = $sftp_server->locate_key_file; >+ my $second_test = ( -f $file ); >+ >+ open my $fh, $sftp_server->locate_key_file; >+ my $third_test = <$fh>; >+ >+ is( $first_test, 1, 'Writing key file should return 1' ); >+ is( $second_test, 1, 'Written key file should exist' ); >+ is( $third_test, "321tset\n", 'The contents of the key file should be 321tset\n' ); >+ >+ unlink $file; >+ >+ $schema->storage->txn_rollback; >+}; >diff --git a/t/db_dependent/api/v1/sftp_servers.t b/t/db_dependent/api/v1/sftp_servers.t >new file mode 100755 >index 00000000000..5b73792a985 >--- /dev/null >+++ b/t/db_dependent/api/v1/sftp_servers.t >@@ -0,0 +1,394 @@ >+#!/usr/bin/env 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 <http://www.gnu.org/licenses>. >+ >+use Modern::Perl; >+ >+use Test::More tests => 5; >+use Test::Mojo; >+ >+use t::lib::TestBuilder; >+use t::lib::Mocks; >+ >+use Koha::SFTP::Servers; >+use Koha::Database; >+ >+my $schema = Koha::Database->new->schema; >+my $builder = t::lib::TestBuilder->new; >+ >+my $t = Test::Mojo->new('Koha::REST::V1'); >+t::lib::Mocks::mock_preference( 'RESTBasicAuth', 1 ); >+ >+subtest 'list() tests' => sub { >+ >+ plan tests => 11; >+ >+ $schema->storage->txn_begin; >+ >+ Koha::SFTP::Servers->search->delete; >+ >+ my $librarian = $builder->build_object( >+ { >+ class => 'Koha::Patrons', >+ value => { flags => 3**2 } # parameters flag = 3 >+ } >+ ); >+ my $password = 'thePassword123'; >+ $librarian->set_password( { password => $password, skip_validation => 1 } ); >+ my $userid = $librarian->userid; >+ >+ my $patron = $builder->build_object( >+ { >+ class => 'Koha::Patrons', >+ value => { flags => 0 } >+ } >+ ); >+ >+ $patron->set_password( { password => $password, skip_validation => 1 } ); >+ my $unauth_userid = $patron->userid; >+ >+ ## Authorized user tests >+ # No FTP/SFTP servers, so empty array should be returned >+ $t->get_ok("//$userid:$password@/api/v1/config/sftp_servers")->status_is(200)->json_is( [] ); >+ >+ my $sftp_server = $builder->build_object( >+ { >+ class => 'Koha::SFTP::Servers', >+ value => { >+ password => undef, >+ key_file => undef, >+ }, >+ } >+ ); >+ >+ # One sftp server created, should get returned >+ $t->get_ok("//$userid:$password@/api/v1/config/sftp_servers")->status_is(200)->json_is( [ $sftp_server->to_api ] ); >+ >+ my $another_sftp_server = $builder->build_object( >+ { >+ class => 'Koha::SFTP::Servers', >+ value => { >+ password => undef, >+ key_file => undef, >+ }, >+ } >+ ); >+ >+ # Two FTP/SFTP servers created, they should both be returned >+ $t->get_ok("//$userid:$password@/api/v1/config/sftp_servers")->status_is(200) >+ ->json_is( [ $sftp_server->to_api, $another_sftp_server->to_api, ] ); >+ >+ # Unauthorized access >+ $t->get_ok("//$unauth_userid:$password@/api/v1/config/sftp_servers")->status_is(403); >+ >+ $schema->storage->txn_rollback; >+}; >+ >+subtest 'get() tests' => sub { >+ >+ plan tests => 8; >+ >+ $schema->storage->txn_begin; >+ >+ my $sftp_server = $builder->build_object( >+ { >+ class => 'Koha::SFTP::Servers', >+ value => { >+ password => undef, >+ key_file => undef, >+ }, >+ } >+ ); >+ my $librarian = $builder->build_object( >+ { >+ class => 'Koha::Patrons', >+ value => { flags => 3**2 } # parameters flag = 3 >+ } >+ ); >+ my $password = 'thePassword123'; >+ $librarian->set_password( { password => $password, skip_validation => 1 } ); >+ my $userid = $librarian->userid; >+ >+ my $patron = $builder->build_object( >+ { >+ class => 'Koha::Patrons', >+ value => { flags => 0 } >+ } >+ ); >+ >+ $patron->set_password( { password => $password, skip_validation => 1 } ); >+ my $unauth_userid = $patron->userid; >+ >+ $t->get_ok( "//$userid:$password@/api/v1/config/sftp_servers/" . $sftp_server->id )->status_is(200) >+ ->json_is( $sftp_server->to_api ); >+ >+ $t->get_ok( "//$unauth_userid:$password@/api/v1/config/sftp_servers/" . $sftp_server->id )->status_is(403); >+ >+ my $sftp_server_to_delete = $builder->build_object( >+ { >+ class => 'Koha::SFTP::Servers', >+ value => { >+ password => undef, >+ key_file => undef, >+ }, >+ } >+ ); >+ my $non_existent_id = $sftp_server_to_delete->id; >+ $sftp_server_to_delete->delete; >+ >+ $t->get_ok("//$userid:$password@/api/v1/config/sftp_servers/$non_existent_id")->status_is(404) >+ ->json_is( '/error' => 'FTP/SFTP server not found' ); >+ >+ $schema->storage->txn_rollback; >+}; >+ >+subtest 'add() tests' => sub { >+ >+ plan tests => 15; >+ >+ $schema->storage->txn_begin; >+ >+ Koha::SFTP::Servers->search->delete; >+ >+ my $librarian = $builder->build_object( >+ { >+ class => 'Koha::Patrons', >+ value => { flags => 3**2 } # parameters flag = 3 >+ } >+ ); >+ my $password = 'thePassword123'; >+ $librarian->set_password( { password => $password, skip_validation => 1 } ); >+ my $userid = $librarian->userid; >+ >+ my $patron = $builder->build_object( >+ { >+ class => 'Koha::Patrons', >+ value => { flags => 0 } >+ } >+ ); >+ >+ $patron->set_password( { password => $password, skip_validation => 1 } ); >+ my $unauth_userid = $patron->userid; >+ >+ my $sftp_server = $builder->build_object( >+ { >+ class => 'Koha::SFTP::Servers', >+ value => { >+ password => undef, >+ key_file => undef, >+ }, >+ } >+ ); >+ my $sftp_server_data = $sftp_server->to_api; >+ delete $sftp_server_data->{sftp_server_id}; >+ $sftp_server->delete; >+ >+ # Unauthorized attempt to write >+ $t->post_ok( "//$unauth_userid:$password@/api/v1/config/sftp_servers" => json => $sftp_server_data ) >+ ->status_is(403); >+ >+ # Authorized attempt to write invalid data >+ my $sftp_server_with_invalid_field = { >+ name => 'Some other server', >+ blah => 'blah' >+ }; >+ >+ $t->post_ok( "//$userid:$password@/api/v1/config/sftp_servers" => json => $sftp_server_with_invalid_field ) >+ ->status_is(400)->json_is( >+ "/errors" => [ >+ { >+ message => "Properties not allowed: blah.", >+ path => "/body" >+ } >+ ] >+ ); >+ >+ # Authorized attempt to write >+ my $sftp_server_id = >+ $t->post_ok( "//$userid:$password@/api/v1/config/sftp_servers" => json => $sftp_server_data ) >+ ->status_is( 201, 'SWAGGER3.2.1' ) >+ ->header_like( Location => qr|^\/api\/v1\/config\/sftp_servers\/\d*|, 'SWAGGER3.4.1' ) >+ ->json_is( '/name' => $sftp_server_data->{name} )->tx->res->json->{sftp_server_id}; >+ >+ # Authorized attempt to create with null id >+ $sftp_server_data->{sftp_server_id} = undef; >+ $t->post_ok( "//$userid:$password@/api/v1/config/sftp_servers" => json => $sftp_server_data )->status_is(400) >+ ->json_has('/errors'); >+ >+ # Authorized attempt to create with existing id >+ $sftp_server_data->{sftp_server_id} = $sftp_server_id; >+ $t->post_ok( "//$userid:$password@/api/v1/config/sftp_servers" => json => $sftp_server_data )->status_is(400) >+ ->json_is( >+ "/errors" => [ >+ { >+ message => "Read-only.", >+ path => "/body/sftp_server_id" >+ } >+ ] >+ ); >+ >+ $schema->storage->txn_rollback; >+}; >+ >+subtest 'update() tests' => sub { >+ >+ plan tests => 15; >+ >+ $schema->storage->txn_begin; >+ >+ my $librarian = $builder->build_object( >+ { >+ class => 'Koha::Patrons', >+ value => { flags => 3**2 } # parameters flag = 3 >+ } >+ ); >+ my $password = 'thePassword123'; >+ $librarian->set_password( { password => $password, skip_validation => 1 } ); >+ my $userid = $librarian->userid; >+ >+ my $patron = $builder->build_object( >+ { >+ class => 'Koha::Patrons', >+ value => { flags => 0 } >+ } >+ ); >+ >+ $patron->set_password( { password => $password, skip_validation => 1 } ); >+ my $unauth_userid = $patron->userid; >+ >+ my $sftp_server_id = $builder->build_object( >+ { >+ class => 'Koha::SFTP::Servers', >+ value => { >+ password => undef, >+ key_file => undef, >+ }, >+ } >+ )->id; >+ >+ # Unauthorized attempt to update >+ $t->put_ok( "//$unauth_userid:$password@/api/v1/config/sftp_servers/$sftp_server_id" => json => >+ { name => 'New unauthorized name change' } )->status_is(403); >+ >+ # Attempt partial update on a PUT >+ my $sftp_server_with_missing_field = { >+ host => 'localhost', >+ passiv => '1' >+ }; >+ >+ $t->put_ok( >+ "//$userid:$password@/api/v1/config/sftp_servers/$sftp_server_id" => json => $sftp_server_with_missing_field ) >+ ->status_is(400)->json_is( "/errors" => [ { message => "Missing property.", path => "/body/name" } ] ); >+ >+ # Full object update on PUT >+ my $sftp_server_with_updated_field = { >+ name => "Some name", >+ password => "some_pass", >+ }; >+ >+ $t->put_ok( >+ "//$userid:$password@/api/v1/config/sftp_servers/$sftp_server_id" => json => $sftp_server_with_updated_field ) >+ ->status_is(200)->json_is( '/name' => 'Some name' ); >+ >+ # Authorized attempt to write invalid data >+ my $sftp_server_with_invalid_field = { >+ blah => "Blah", >+ name => 'Some name' >+ }; >+ >+ $t->put_ok( >+ "//$userid:$password@/api/v1/config/sftp_servers/$sftp_server_id" => json => $sftp_server_with_invalid_field ) >+ ->status_is(400)->json_is( >+ "/errors" => [ >+ { >+ message => "Properties not allowed: blah.", >+ path => "/body" >+ } >+ ] >+ ); >+ >+ my $sftp_server_to_delete = $builder->build_object( >+ { >+ class => 'Koha::SFTP::Servers', >+ value => { >+ password => undef, >+ key_file => undef, >+ }, >+ } >+ ); >+ my $non_existent_id = $sftp_server_to_delete->id; >+ $sftp_server_to_delete->delete; >+ >+ $t->put_ok( >+ "//$userid:$password@/api/v1/config/sftp_servers/$non_existent_id" => json => $sftp_server_with_updated_field ) >+ ->status_is(404); >+ >+ # Wrong method (POST) >+ $sftp_server_with_updated_field->{sftp_server_id} = 2; >+ >+ $t->post_ok( >+ "//$userid:$password@/api/v1/config/sftp_servers/$sftp_server_id" => json => $sftp_server_with_updated_field ) >+ ->status_is(404); >+ >+ $schema->storage->txn_rollback; >+}; >+ >+subtest 'delete() tests' => sub { >+ >+ plan tests => 7; >+ >+ $schema->storage->txn_begin; >+ >+ my $librarian = $builder->build_object( >+ { >+ class => 'Koha::Patrons', >+ value => { flags => 3**2 } # parameters flag = 3 >+ } >+ ); >+ my $password = 'thePassword123'; >+ $librarian->set_password( { password => $password, skip_validation => 1 } ); >+ my $userid = $librarian->userid; >+ >+ my $patron = $builder->build_object( >+ { >+ class => 'Koha::Patrons', >+ value => { flags => 0 } >+ } >+ ); >+ >+ $patron->set_password( { password => $password, skip_validation => 1 } ); >+ my $unauth_userid = $patron->userid; >+ >+ my $sftp_server_id = $builder->build_object( >+ { >+ class => 'Koha::SFTP::Servers', >+ value => { >+ password => undef, >+ key_file => undef, >+ }, >+ } >+ )->id; >+ >+ # Unauthorized attempt to delete >+ $t->delete_ok("//$unauth_userid:$password@/api/v1/config/sftp_servers/$sftp_server_id")->status_is(403); >+ >+ $t->delete_ok("//$userid:$password@/api/v1/config/sftp_servers/$sftp_server_id")->status_is( 204, 'SWAGGER3.2.4' ) >+ ->content_is( '', 'SWAGGER3.3.4' ); >+ >+ $t->delete_ok("//$userid:$password@/api/v1/config/sftp_servers/$sftp_server_id")->status_is(404); >+ >+ $schema->storage->txn_rollback; >+}; >-- >2.43.0
You cannot view the attachment while viewing its details because your browser does not support IFRAMEs.
View the attachment on a separate page
.
View Attachment As Diff
View Attachment As Raw
Actions:
View
|
Diff
|
Splinter Review
Attachments on
bug 35761
:
174933
|
174937
|
175030
|
175052
|
175067
|
175166
|
175608
|
175916
|
176429
|
176430