View | Details | Raw Unified | Return to bug 35761
Collapse All | Expand All

(-)a/Koha/REST/V1/Config/SFTP/Servers.pm (+184 lines)
Line 0 Link Here
1
package Koha::REST::V1::Config::SFTP::Servers;
2
3
# This file is part of Koha.
4
#
5
# Koha is free software; you can redistribute it and/or modify it
6
# under the terms of the GNU General Public License as published by
7
# the Free Software Foundation; either version 3 of the License, or
8
# (at your option) any later version.
9
#
10
# Koha is distributed in the hope that it will be useful, but
11
# WITHOUT ANY WARRANTY; without even the implied warranty of
12
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
# GNU General Public License for more details.
14
#
15
# You should have received a copy of the GNU General Public License
16
# along with Koha; if not, see <http://www.gnu.org/licenses>.
17
18
use Modern::Perl;
19
20
use Mojo::Base 'Mojolicious::Controller';
21
22
use Koha::SFTP::Servers;
23
24
use Try::Tiny qw( catch try );
25
26
=head1 API
27
28
=head2 Methods
29
30
=head3 list
31
32
Controller method that handles listing Koha::SFTP::Server objects
33
34
=cut
35
36
sub list {
37
    my $c = shift->openapi->valid_input or return;
38
39
    return try {
40
        my $sftp_servers_set = Koha::SFTP::Servers->new;
41
        my $sftp_servers     = $c->objects->search($sftp_servers_set);
42
        return $c->render(
43
            status  => 200,
44
            openapi => $sftp_servers
45
        );
46
    } catch {
47
        $c->unhandled_exception($_);
48
    };
49
}
50
51
=head3 get
52
53
Controller method that handles retrieving a single Koha::SFTP::Server object
54
55
=cut
56
57
sub get {
58
    my $c = shift->openapi->valid_input or return;
59
60
    return try {
61
        my $sftp_server = Koha::SFTP::Servers->find( $c->param('sftp_server_id') );
62
63
        return $c->render_resource_not_found("FTP/SFTP server")
64
            unless $sftp_server;
65
66
        return $c->render(
67
            status  => 200,
68
            openapi => $c->objects->to_api($sftp_server),
69
        );
70
    } catch {
71
        $c->unhandled_exception($_);
72
    }
73
}
74
75
=head3 add
76
77
Controller method that handles adding a new Koha::SFTP::Server object
78
79
=cut
80
81
sub add {
82
    my $c = shift->openapi->valid_input or return;
83
84
    return try {
85
86
        my $sftp_server = Koha::SFTP::Server->new_from_api( $c->req->json );
87
        $sftp_server->store->discard_changes;
88
89
        $c->res->headers->location( $c->req->url->to_string . '/' . $sftp_server->id );
90
91
        return $c->render(
92
            status  => 201,
93
            openapi => $c->objects->to_api($sftp_server),
94
        );
95
    } catch {
96
        if ( blessed $_ and $_->isa('Koha::Exceptions::Object::DuplicateID') ) {
97
            return $c->render(
98
                status  => 409,
99
                openapi => {
100
                    error    => $_->error,
101
                    conflict => $_->duplicate_id
102
                }
103
            );
104
        }
105
106
        $c->unhandled_exception($_);
107
    };
108
}
109
110
=head3 update
111
112
Controller method that handles updating a Koha::SFTP::Server object
113
114
=cut
115
116
sub update {
117
    my $c = shift->openapi->valid_input or return;
118
119
    my $sftp_server = Koha::SFTP::Servers->find( $c->param('sftp_server_id') );
120
121
    return $c->render_resource_not_found("FTP/SFTP server")
122
        unless $sftp_server;
123
124
    return try {
125
        $sftp_server->set_from_api( $c->req->json );
126
        $sftp_server->store->discard_changes;
127
128
        return $c->render(
129
            status  => 200,
130
            openapi => $c->objects->to_api($sftp_server),
131
        );
132
    } catch {
133
        if ( blessed $_ and $_->isa('Koha::Exceptions::Object::DuplicateID') ) {
134
            return $c->render(
135
                status  => 409,
136
                openapi => {
137
                    error    => $_->error,
138
                    conflict => $_->duplicate_id
139
                }
140
            );
141
        }
142
143
        $c->unhandled_exception($_);
144
    };
145
}
146
147
=head3 delete
148
149
Controller method that handles deleting a Koha::SFTP::Server object
150
151
=cut
152
153
sub delete {
154
    my $c = shift->openapi->valid_input or return;
155
156
    my $sftp_server = Koha::SFTP::Servers->find( $c->param('sftp_server_id') );
157
158
    return $c->render_resource_not_found("FTP/SFTP server")
159
        unless $sftp_server;
160
161
    return try {
162
        $sftp_server->delete;
163
        return $c->render_resource_deleted;
164
    } catch {
165
        $c->unhandled_exception($_);
166
    };
167
}
168
169
=head3 test
170
171
Controller method that invokes Koha::SFTP::Server->test_conn
172
173
=cut
174
175
sub test {
176
    my $c = shift->openapi->valid_input or return;
177
178
    my $sftp_server = Koha::SFTP::Servers->find( $c->param('sftp_server_id') );
179
180
    return $c->render_resource_not_found("FTP/SFTP server")
181
        unless $sftp_server;
182
}
183
184
1;
(-)a/Koha/REST/V1/SFTPServer.pm (+55 lines)
Line 0 Link Here
1
package Koha::REST::V1::SFTPServer;
2
3
# This file is part of Koha.
4
#
5
# Koha is free software; you can redistribute it and/or modify it
6
# under the terms of the GNU General Public License as published by
7
# the Free Software Foundation; either version 3 of the License, or
8
# (at your option) any later version.
9
#
10
# Koha is distributed in the hope that it will be useful, but
11
# WITHOUT ANY WARRANTY; without even the implied warranty of
12
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
# GNU General Public License for more details.
14
#
15
# You should have received a copy of the GNU General Public License
16
# along with Koha; if not, see <http://www.gnu.org/licenses>.
17
18
use Modern::Perl;
19
20
use Mojo::Base 'Mojolicious::Controller';
21
22
use Koha::SFTP::Servers;
23
24
use Try::Tiny qw( catch try );
25
26
=head1 API
27
28
=head2 Methods
29
30
=head3 test
31
32
Controller method that invokes Koha::SFTP::Server->test_conn
33
34
=cut
35
36
sub test {
37
    my $c = shift->openapi->valid_input or return;
38
39
    return try {
40
        my $sftp_server = Koha::SFTP::Servers->find( $c->param('sftp_server_id') );
41
        return $c->render_resource_not_found("FTP/SFTP Server")
42
            unless $sftp_server;
43
44
        my $sftp_server_test_conn = $sftp_server->test_conn;
45
46
        return $c->render(
47
            status  => 200,
48
            openapi => $sftp_server_test_conn,
49
        );
50
    } catch {
51
        $c->unhandled_exception($_);
52
    };
53
}
54
55
1;
(-)a/Koha/SFTP/Server.pm (+319 lines)
Line 0 Link Here
1
package Koha::SFTP::Server;
2
3
# This file is part of Koha.
4
#
5
# Koha is free software; you can redistribute it and/or modify it
6
# under the terms of the GNU General Public License as published by
7
# the Free Software Foundation; either version 3 of the License, or
8
# (at your option) any later version.
9
#
10
# Koha is distributed in the hope that it will be useful, but
11
# WITHOUT ANY WARRANTY; without even the implied warranty of
12
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
# GNU General Public License for more details.
14
#
15
# You should have received a copy of the GNU General Public License
16
# along with Koha; if not, see <http://www.gnu.org/licenses>.
17
18
use Modern::Perl;
19
20
use Koha::Database;
21
use Koha::Exceptions::Object;
22
use Koha::Encryption;
23
use Koha::SFTP::Servers;
24
25
use Try::Tiny qw( catch try );
26
use Mojo::JSON;
27
use Net::SFTP::Foreign;
28
use Net::FTP;
29
30
use base qw(Koha::Object);
31
32
=head1 NAME
33
34
Koha::SFTP::Server - Koha SFTP Server Object class
35
36
=head1 API
37
38
=head2 Class methods
39
40
=head3 store
41
42
    $server->store;
43
44
Overloaded store method.
45
46
=cut
47
48
sub store {
49
    my ($self) = @_;
50
51
    $self->password(
52
        $self->password
53
        ? Koha::Encryption->new->encrypt_hex( $self->password )
54
        : undef
55
    );
56
57
    $self->key_file(
58
        $self->key_file
59
        ? Koha::Encryption->new->encrypt_hex( _dos2unix( $self->key_file ) )
60
        : undef
61
    );
62
63
    $self->write_key_file;
64
65
    return $self->SUPER::store;
66
}
67
68
=head3 to_api
69
70
    my $json = $sftp_server->to_api;
71
72
Overloaded method that returns a JSON representation of the Koha::SFTP::Server object,
73
suitable for API output.
74
75
=cut
76
77
sub to_api {
78
    my ( $self, $params ) = @_;
79
80
    my $json_sftp = $self->SUPER::to_api($params);
81
    return unless $json_sftp;
82
    delete $json_sftp->{password};
83
84
    return $json_sftp;
85
}
86
87
=head3 to_api_mapping
88
89
This method returns the mapping for representing a Koha::SFTP::Server object
90
on the API.
91
92
=cut
93
94
sub to_api_mapping {
95
    return { id => 'sftp_server_id' };
96
}
97
98
=head3 plain_text_password
99
100
    $server->plain_text_password;
101
Fetches the plaintext password, from the object
102
103
=cut
104
105
sub plain_text_password {
106
    my ($self) = @_;
107
108
    return Koha::Encryption->new->decrypt_hex( $self->password )
109
        if $self->password;
110
111
}
112
113
=head3 plain_text_key
114
115
    $server->plain_text_key;
116
Fetches the plaintext key file, from the object
117
118
=cut
119
120
sub plain_text_key {
121
    my ($self) = @_;
122
123
    return Koha::Encryption->new->decrypt_hex( $self->key_file ) . "\n"
124
        if $self->key_file;
125
}
126
127
=head3 write_key_file
128
129
    $server->write_key_file;
130
Writes the keyfile from the db into a file
131
132
=cut
133
134
sub write_key_file {
135
    my ($self)      = @_;
136
    my $upload_path = C4::Context->config('upload_path') or return;
137
    my $key_path    = $upload_path . '/ssh_keys';
138
    my $key_file    = $key_path . '/id_ssh_' . $self->id;
139
140
    mkdir $key_path if ( !-d $key_path );
141
142
    unlink $key_file if ( -f $key_file );
143
    my $fh;
144
    $fh = IO::File->new( $key_file, 'w' ) or return;
145
    chmod 0600, $key_file if ( -f $key_file );
146
147
    print $fh $self->plain_text_key;
148
149
    undef $fh;
150
151
    return 1;
152
}
153
154
=head3 locate_key_file
155
156
    $server->locate_key_file;
157
Returns the keyfile's expected path
158
159
=cut
160
161
sub locate_key_file {
162
    my ($self) = @_;
163
    my $upload_path = C4::Context->config('upload_path');
164
    return if not defined $upload_path;
165
166
    my $keyf_path = $upload_path . '/ssh_keys/id_ssh_' . $self->id;
167
168
    return ( -f $keyf_path ) ? $keyf_path : undef;
169
}
170
171
=head3 test_conn
172
173
    $server->test_conn;
174
Tests a connection to a given sftp server
175
176
=cut
177
178
sub test_conn {
179
    my ($self) = @_;
180
    my $default_result = {
181
        passed => Mojo::JSON->false,
182
        err    => undef,
183
        msg    => undef,
184
    };
185
    my $sftp_test_results;
186
    my $ftp_test_results;
187
188
    if ( $self->transport eq 'sftp' ) {
189
        $sftp_test_results->{'1_sftp_conn'} = {%$default_result};
190
        my $sftp = Net::SFTP::Foreign->new(
191
            host     => $self->host,
192
            user     => $self->user_name,
193
            password => $self->plain_text_password,
194
            key_path => $self->locate_key_file,
195
            port     => $self->port,
196
            timeout  => 10,
197
            more     => [
198
                qw(-vv),
199
                qw(-o StrictHostKeyChecking=no),
200
            ],
201
        );
202
        $sftp_test_results->{'1_sftp_conn'}->{'err'} = $sftp->error
203
            if $sftp->error;
204
        $sftp_test_results->{'1_sftp_conn'}->{'passed'} = Mojo::JSON->true
205
            unless $sftp->error;
206
207
        unless ( $sftp->error ) {
208
            $sftp_test_results->{'2_sftp_ls'} = {%$default_result};
209
            $sftp->ls();
210
            $sftp_test_results->{'2_sftp_ls'}->{'err'} = $sftp->error
211
                if $sftp->error;
212
            $sftp_test_results->{'2_sftp_ls'}->{'passed'} = Mojo::JSON->true
213
                unless $sftp->error;
214
215
            $sftp_test_results->{'3_sftp_write'} = {%$default_result};
216
            open my $fh, '<', \"Hello, world!\n";
217
            close $fh if ( $sftp->put( $fh, '.koha_test_file' ) );
218
            $sftp_test_results->{'3_sftp_write'}->{'err'} = $sftp->error
219
                if $sftp->error;
220
            $sftp_test_results->{'3_sftp_write'}->{'passed'} = Mojo::JSON->true
221
                unless $sftp->error;
222
223
            $sftp_test_results->{'4_sftp_del'} = {%$default_result};
224
            $sftp->remove('.koha_test_file');
225
            $sftp_test_results->{'4_sftp_del'}->{'err'} = $sftp->error
226
                if $sftp->error;
227
            $sftp_test_results->{'4_sftp_del'}->{'passed'} = Mojo::JSON->true
228
                unless $sftp->error;
229
        }
230
231
        return ( 1, $sftp_test_results );
232
    } elsif ( $self->transport eq 'ftp' ) {
233
        $ftp_test_results->{'1_ftp_conn'} = {%$default_result};
234
        my $ftp = Net::FTP->new(
235
            $self->host,
236
            Port    => $self->port,
237
            Timeout => 10,
238
            Passive => ( scalar $self->passiv ) ? 1 : 0,
239
        );
240
        if ($ftp) {
241
            $ftp_test_results->{'1_ftp_conn'}->{'passed'} = Mojo::JSON->true;
242
            $ftp_test_results->{'1_ftp_conn'}->{'msg'}    = $ftp->message;
243
        } else {
244
            $ftp_test_results->{'1_ftp_conn'}->{'err'} = 'cannot connect to ' . $self->host . ': ' . $@;
245
        }
246
247
        if ($ftp) {
248
            $ftp_test_results->{'2_ftp_login'} = {%$default_result};
249
            my $login = $ftp->login(
250
                $self->user_name,
251
                $self->plain_text_password,
252
            );
253
            if ($login) {
254
                $ftp_test_results->{'2_ftp_login'}->{'passed'} = Mojo::JSON->true;
255
                $ftp_test_results->{'2_ftp_login'}->{'msg'}    = $ftp->message;
256
            } else {
257
                $ftp_test_results->{'2_ftp_login'}->{'err'} = $ftp->message;
258
            }
259
260
            $ftp_test_results->{'3_ftp_ls'} = {%$default_result};
261
            my $ls = $ftp->ls('~');
262
            if ($ls) {
263
                $ftp_test_results->{'3_ftp_ls'}->{'passed'} = Mojo::JSON->true;
264
                $ftp_test_results->{'3_ftp_ls'}->{'msg'}    = $ftp->message;
265
            } else {
266
                $ftp_test_results->{'3_ftp_ls'}->{'err'} = $ftp->message;
267
            }
268
269
            $ftp_test_results->{'4_ftp_write'} = {%$default_result};
270
            open my $fh, '<', \"Hello, world!\n";
271
            close $fh if ( my $put = $ftp->put( $fh, '.koha_test_file' ) );
272
            if ($put) {
273
                $ftp_test_results->{'4_ftp_write'}->{'passed'} = Mojo::JSON->true;
274
                $ftp_test_results->{'4_ftp_write'}->{'msg'}    = $ftp->message;
275
            } else {
276
                $ftp_test_results->{'4_ftp_write'}->{'err'} = $ftp->message;
277
            }
278
279
            $ftp_test_results->{'5_ftp_del'} = {%$default_result};
280
            my $delete = $ftp->delete('.koha_test_file');
281
            if ($delete) {
282
                $ftp_test_results->{'5_ftp_del'}->{'passed'} = Mojo::JSON->true;
283
                $ftp_test_results->{'5_ftp_del'}->{'msg'}    = $ftp->message;
284
            } else {
285
                $ftp_test_results->{'5_ftp_del'}->{'err'} = $ftp->message;
286
            }
287
        }
288
289
        return ( 1, $ftp_test_results );
290
    }
291
292
    return ( 0, undef );
293
}
294
295
=head2 Internal methods
296
297
=head3 _dos2unix
298
299
Return a CR-free string from an input
300
301
=cut
302
303
sub _dos2unix {
304
    my $dosStr = shift;
305
306
    return $dosStr =~ s/\015\012/\012/gr;
307
}
308
309
=head3 _type
310
311
Return type of Object relating to Schema ResultSet
312
313
=cut
314
315
sub _type {
316
    return 'SftpServer';
317
}
318
319
1;
(-)a/Koha/SFTP/Servers.pm (+55 lines)
Line 0 Link Here
1
package Koha::SFTP::Servers;
2
3
# This file is part of Koha.
4
#
5
# Koha is free software; you can redistribute it and/or modify it
6
# under the terms of the GNU General Public License as published by
7
# the Free Software Foundation; either version 3 of the License, or
8
# (at your option) any later version.
9
#
10
# Koha is distributed in the hope that it will be useful, but
11
# WITHOUT ANY WARRANTY; without even the implied warranty of
12
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
# GNU General Public License for more details.
14
#
15
# You should have received a copy of the GNU General Public License
16
# along with Koha; if not, see <http://www.gnu.org/licenses>.
17
18
use Modern::Perl;
19
20
use Koha::Database;
21
use Koha::Exceptions;
22
23
use Koha::SFTP::Server;
24
25
use base qw(Koha::Objects);
26
27
=head1 NAME
28
29
Koha::SFTP::Servers - Koha SFTP Server Object set class
30
31
=head1 API
32
33
=head2 Internal methods
34
35
=head3 _type
36
37
Return type of object, relating to Schema ResultSet
38
39
=cut
40
41
sub _type {
42
    return 'SftpServer';
43
}
44
45
=head3 object_class
46
47
Return object class
48
49
=cut
50
51
sub object_class {
52
    return 'Koha::SFTP::Server';
53
}
54
55
1;
(-)a/Koha/Schema/Result/SftpServer.pm (+166 lines)
Line 0 Link Here
1
use utf8;
2
package Koha::Schema::Result::SftpServer;
3
4
# Created by DBIx::Class::Schema::Loader
5
# DO NOT MODIFY THE FIRST PART OF THIS FILE
6
7
=head1 NAME
8
9
Koha::Schema::Result::SftpServer
10
11
=cut
12
13
use strict;
14
use warnings;
15
16
use base 'DBIx::Class::Core';
17
18
=head1 TABLE: C<sftp_servers>
19
20
=cut
21
22
__PACKAGE__->table("sftp_servers");
23
24
=head1 ACCESSORS
25
26
=head2 id
27
28
  data_type: 'integer'
29
  is_auto_increment: 1
30
  is_nullable: 0
31
32
=head2 name
33
34
  data_type: 'varchar'
35
  is_nullable: 0
36
  size: 80
37
38
=head2 host
39
40
  data_type: 'varchar'
41
  default_value: 'localhost'
42
  is_nullable: 0
43
  size: 80
44
45
=head2 port
46
47
  data_type: 'integer'
48
  default_value: 22
49
  is_nullable: 0
50
51
=head2 transport
52
53
  data_type: 'enum'
54
  default_value: 'sftp'
55
  extra: {list => ["ftp","sftp"]}
56
  is_nullable: 0
57
58
=head2 passiv
59
60
  data_type: 'tinyint'
61
  default_value: 1
62
  is_nullable: 0
63
64
=head2 user_name
65
66
  data_type: 'varchar'
67
  is_nullable: 1
68
  size: 80
69
70
=head2 password
71
72
  data_type: 'varchar'
73
  is_nullable: 1
74
  size: 80
75
76
=head2 key_file
77
78
  data_type: 'varchar'
79
  is_nullable: 1
80
  size: 4096
81
82
=head2 auth_mode
83
84
  data_type: 'enum'
85
  default_value: 'password'
86
  extra: {list => ["password","key_file","noauth"]}
87
  is_nullable: 0
88
89
=head2 debug
90
91
  data_type: 'tinyint'
92
  default_value: 0
93
  is_nullable: 0
94
95
=cut
96
97
__PACKAGE__->add_columns(
98
  "id",
99
  { data_type => "integer", is_auto_increment => 1, is_nullable => 0 },
100
  "name",
101
  { data_type => "varchar", is_nullable => 0, size => 80 },
102
  "host",
103
  {
104
    data_type => "varchar",
105
    default_value => "localhost",
106
    is_nullable => 0,
107
    size => 80,
108
  },
109
  "port",
110
  { data_type => "integer", default_value => 22, is_nullable => 0 },
111
  "transport",
112
  {
113
    data_type => "enum",
114
    default_value => "sftp",
115
    extra => { list => ["ftp", "sftp"] },
116
    is_nullable => 0,
117
  },
118
  "passiv",
119
  { data_type => "tinyint", default_value => 1, is_nullable => 0 },
120
  "user_name",
121
  { data_type => "varchar", is_nullable => 1, size => 80 },
122
  "password",
123
  { data_type => "varchar", is_nullable => 1, size => 80 },
124
  "key_file",
125
  { data_type => "varchar", is_nullable => 1, size => 4096 },
126
  "auth_mode",
127
  {
128
    data_type => "enum",
129
    default_value => "password",
130
    extra => { list => ["password", "key_file", "noauth"] },
131
    is_nullable => 0,
132
  },
133
  "debug",
134
  { data_type => "tinyint", default_value => 0, is_nullable => 0 },
135
);
136
137
=head1 PRIMARY KEY
138
139
=over 4
140
141
=item * L</id>
142
143
=back
144
145
=cut
146
147
__PACKAGE__->set_primary_key("id");
148
149
150
# Created by DBIx::Class::Schema::Loader v0.07051 @ 2024-12-02 11:43:19
151
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:K1+15gYfWmqYKldm0NdgGQ
152
153
__PACKAGE__->add_columns(
154
    '+passiv'     => { is_boolean => 1 },
155
    '+debug'      => { is_boolean => 1 },
156
);
157
158
sub koha_objects_class {
159
    'Koha::SFTP::Servers';
160
}
161
162
sub koha_object_class {
163
    'Koha::SFTP::Server';
164
}
165
166
1;
(-)a/admin/sftp_servers.pl (+218 lines)
Line 0 Link Here
1
#!/usr/bin/perl
2
3
# Copyright 2024 PTFS Europe Ltd
4
#
5
# This file is part of Koha.
6
#
7
# Koha is free software; you can redistribute it and/or modify it
8
# under the terms of the GNU General Public License as published by
9
# the Free Software Foundation; either version 3 of the License, or
10
# (at your option) any later version.
11
#
12
# Koha is distributed in the hope that it will be useful, but
13
# WITHOUT ANY WARRANTY; without even the implied warranty of
14
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
# GNU General Public License for more details.
16
#
17
# You should have received a copy of the GNU General Public License
18
# along with Koha; if not, see <http://www.gnu.org/licenses>.
19
20
use Modern::Perl;
21
22
use CGI          qw ( -utf8 );
23
use Scalar::Util qw( blessed );
24
use Try::Tiny    qw( catch try );
25
26
use C4::Auth   qw( get_template_and_user );
27
use C4::Output qw( output_html_with_http_headers );
28
29
use Koha::SFTP::Servers;
30
31
my $input = CGI->new;
32
my $op    = $input->param('op') || 'list';
33
34
my ( $template, $borrowernumber, $cookie ) = get_template_and_user(
35
    {
36
        template_name => "admin/sftp_servers.tt",
37
        query         => $input,
38
        type          => "intranet",
39
        flagsrequired => { parameters => 'manage_sftp_servers' },
40
    }
41
);
42
43
my @messages;
44
45
my $sftp_servers = Koha::SFTP::Servers->search;
46
47
if ( $op eq 'cud-add' ) {
48
    my $name      = $input->param('sftp_name');
49
    my $host      = $input->param('sftp_host')      || 'localhost';
50
    my $port      = $input->param('sftp_port')      || 22;
51
    my $transport = $input->param('sftp_transport') || 'sftp';
52
    my $passiv    = ( scalar $input->param('sftp_passiv') ) ? 1 : 0;
53
    my $auth_mode = $input->param('sftp_auth_mode') || 'password';
54
    my $user_name = $input->param('sftp_user_name') || undef;
55
    my $password  = $input->param('sftp_password')  || undef;
56
    my $key_file  = $input->param('sftp_key_file')  || undef;
57
    my $debug     = ( scalar $input->param('sftp_debug_mode') ) ? 1 : 0;
58
59
    try {
60
        Koha::SFTP::Server->new(
61
            {
62
                name      => $name,
63
                host      => $host,
64
                port      => $port,
65
                transport => $transport,
66
                passiv    => $passiv,
67
                auth_mode => $auth_mode,
68
                user_name => $user_name,
69
                password  => $password,
70
                key_file  => $key_file,
71
                debug     => $debug,
72
            }
73
        )->store;
74
75
        push @messages, {
76
            type => 'message',
77
            code => 'success_on_insert'
78
        };
79
    } catch {
80
        if ( blessed $_ and $_->isa('Koha::Exceptions::Object::DuplicateID') ) {
81
            push @messages, {
82
                type   => 'alert',
83
                code   => 'error_on_insert',
84
                reason => 'duplicate_id'
85
            };
86
        }
87
    };
88
89
    # list servers after adding
90
    $op = 'list';
91
92
} elsif ( $op eq 'edit_form' ) {
93
    my $sftp_server_id = $input->param('sftp_server_id');
94
    my $sftp_server;
95
    my $sftp_server_plain_text_password;
96
    my $sftp_server_plain_text_key;
97
98
    $sftp_server = Koha::SFTP::Servers->find($sftp_server_id)
99
        unless !$sftp_server_id;
100
101
    unless ( !$sftp_server ) {
102
        $sftp_server_plain_text_password = $sftp_server->plain_text_password;
103
        $sftp_server_plain_text_key      = $sftp_server->plain_text_key;
104
    }
105
106
    if ($sftp_server) {
107
        $template->param(
108
            sftp_server                     => $sftp_server,
109
            sftp_server_plain_text_password => $sftp_server_plain_text_password,
110
            sftp_server_plain_text_key      => $sftp_server_plain_text_key,
111
        );
112
    } else {
113
        push @messages, {
114
            type   => 'alert',
115
            code   => 'error_on_edit',
116
            reason => 'invalid_id'
117
        };
118
    }
119
120
} elsif ( $op eq 'cud-edit_save' ) {
121
    my $sftp_server_id = $input->param('sftp_server_id');
122
    my $sftp_server_plain_text_password;
123
    my $sftp_server;
124
125
    $sftp_server = Koha::SFTP::Servers->find($sftp_server_id)
126
        unless !$sftp_server_id;
127
128
    $sftp_server_plain_text_password = $sftp_server->plain_text_password
129
        unless !$sftp_server_id;
130
131
    if ($sftp_server) {
132
        my $name      = $input->param('sftp_name');
133
        my $host      = $input->param('sftp_host')      || 'localhost';
134
        my $port      = $input->param('sftp_port')      || 22;
135
        my $transport = $input->param('sftp_transport') || 'sftp';
136
        my $passiv    = ( scalar $input->param('sftp_passiv') ) ? 1 : 0;
137
        my $auth_mode = $input->param('sftp_auth_mode') || 'password';
138
        my $user_name = $input->param('sftp_user_name') || undef;
139
        my $password  = $input->param('sftp_password')  || undef;
140
        my $key_file  = $input->param('sftp_key_file')  || undef;
141
        my $debug     = ( scalar $input->param('sftp_debug_mode') ) ? 1 : 0;
142
143
        try {
144
            $sftp_server->password($password)
145
                if defined $password and $password ne '****'
146
                or not defined $password;
147
148
            $sftp_server->set(
149
                {
150
                    name      => $name,
151
                    host      => $host,
152
                    port      => $port,
153
                    transport => $transport,
154
                    passiv    => $passiv,
155
                    auth_mode => $auth_mode,
156
                    user_name => $user_name,
157
                    password  => $password,
158
                    key_file  => $key_file,
159
                    debug     => $debug,
160
                }
161
            )->store;
162
163
            push @messages, {
164
                type => 'message',
165
                code => 'success_on_update'
166
            };
167
168
        } catch {
169
170
            push @messages, {
171
                type => 'alert',
172
                code => 'error_on_update'
173
            };
174
175
        };
176
177
        # list servers after adding
178
        $op = 'list';
179
    } else {
180
        push @messages, {
181
            type   => 'alert',
182
            code   => 'error_on_update',
183
            reason => 'invalid_id'
184
        };
185
    }
186
187
} elsif ( $op eq 'test_form' ) {
188
    my $sftp_server_id = $input->param('sftp_server_id');
189
    my $sftp_server;
190
191
    $sftp_server = Koha::SFTP::Servers->find($sftp_server_id)
192
        unless !$sftp_server_id;
193
194
    if ($sftp_server) {
195
        $template->param(
196
            sftp_server => $sftp_server,
197
        );
198
    } else {
199
        push @messages, {
200
            type   => 'alert',
201
            code   => 'error_on_test',
202
            reason => 'invalid_id',
203
        };
204
    }
205
}
206
207
if ( $op eq 'list' ) {
208
    $template->param(
209
        servers_count => $sftp_servers->count,
210
    );
211
}
212
213
$template->param(
214
    op       => $op,
215
    messages => \@messages,
216
);
217
218
output_html_with_http_headers $input, $cookie, $template->output;
(-)a/api/v1/swagger/definitions/sftp_server.yaml (+54 lines)
Line 0 Link Here
1
---
2
type: object
3
properties:
4
  sftp_server_id:
5
    type: integer
6
    description: Internal FTP/SFTP server identifier
7
    readOnly: true
8
  name:
9
    type: string
10
    description: Name of the FTP/SFTP server
11
  host:
12
    type: string
13
    description: FTP/SFTP host name
14
  port:
15
    type: integer
16
    description: TCP port number
17
  transport:
18
    type: string
19
    enum:
20
      - ftp
21
      - sftp
22
      - file
23
    description: Transport of FTP/SFTP server
24
  auth_mode:
25
    type: string
26
    enum:
27
      - password
28
      - key_file
29
      - noauth
30
    description: Authentication mode to use
31
  passiv:
32
    type: boolean
33
    description: Whether or not to use passive mode
34
  user_name:
35
    type:
36
      - string
37
      - "null"
38
    description: The user name to use for authentication (optional)
39
  password:
40
    type:
41
      - string
42
      - "null"
43
    description: The password to use for authentication (optional)
44
  key_file:
45
    type:
46
      - string
47
      - "null"
48
    description: The key file to use for authentication (optional)
49
  debug:
50
    type: boolean
51
    description: If the SMTP connection is set to debug mode
52
additionalProperties: false
53
required:
54
  - name
(-)a/api/v1/swagger/paths/config_sftp_servers.yaml (+236 lines)
Line 0 Link Here
1
---
2
/config/sftp_servers:
3
  get:
4
    x-mojo-to: Config::SFTP::Servers#list
5
    operationId: listSFTPServers
6
    tags:
7
      - sftp_servers
8
    summary: List FTP/SFTP servers
9
    produces:
10
      - application/json
11
    parameters:
12
      - $ref: "../swagger.yaml#/parameters/match"
13
      - $ref: "../swagger.yaml#/parameters/order_by"
14
      - $ref: "../swagger.yaml#/parameters/page"
15
      - $ref: "../swagger.yaml#/parameters/per_page"
16
      - $ref: "../swagger.yaml#/parameters/q_param"
17
      - $ref: "../swagger.yaml#/parameters/q_body"
18
      - $ref: "../swagger.yaml#/parameters/request_id_header"
19
    responses:
20
      "200":
21
        description: A list of FTP/SFTP servers
22
        schema:
23
          type: array
24
          items:
25
            $ref: "../swagger.yaml#/definitions/sftp_server"
26
      "400":
27
        description: |
28
          Bad request. Possible `error_code` attribute values:
29
30
            * `invalid_query`
31
        schema:
32
          $ref: "../swagger.yaml#/definitions/error"
33
      "403":
34
        description: Access forbidden
35
        schema:
36
          $ref: "../swagger.yaml#/definitions/error"
37
      "500":
38
        description: |
39
          Internal server error. Possible `error_code` attribute values:
40
41
          * `internal_server_error`
42
        schema:
43
          $ref: "../swagger.yaml#/definitions/error"
44
      "503":
45
        description: Under maintenance
46
        schema:
47
          $ref: "../swagger.yaml#/definitions/error"
48
    x-koha-authorization:
49
      permissions:
50
        parameters: manage_sftp_servers
51
  post:
52
    x-mojo-to: Config::SFTP::Servers#add
53
    operationId: addSFTPServer
54
    tags:
55
      - sftp_servers
56
    summary: Add FTP/SFTP server
57
    parameters:
58
      - name: body
59
        in: body
60
        description: A JSON object representing a new FTP/SFTP server configuration
61
        required: true
62
        schema:
63
          $ref: "../swagger.yaml#/definitions/sftp_server"
64
    produces:
65
      - application/json
66
    responses:
67
      "201":
68
        description: An FTP/SFTP server object
69
        schema:
70
          $ref: "../swagger.yaml#/definitions/sftp_server"
71
      "400":
72
        description: Bad request
73
        schema:
74
          $ref: "../swagger.yaml#/definitions/error"
75
      "401":
76
        description: Authentication required
77
        schema:
78
          $ref: "../swagger.yaml#/definitions/error"
79
      "403":
80
        description: Access forbidden
81
        schema:
82
          $ref: "../swagger.yaml#/definitions/error"
83
      "409":
84
        description: Conflict in creating resource
85
        schema:
86
          $ref: "../swagger.yaml#/definitions/error"
87
      "500":
88
        description: |
89
          Internal server error. Possible `error_code` attribute values:
90
91
          * `internal_server_error`
92
        schema:
93
          $ref: "../swagger.yaml#/definitions/error"
94
      "503":
95
        description: Under maintenance
96
        schema:
97
          $ref: "../swagger.yaml#/definitions/error"
98
    x-koha-authorization:
99
      permissions:
100
        parameters: manage_sftp_servers
101
"/config/sftp_servers/{sftp_server_id}":
102
  get:
103
    x-mojo-to: Config::SFTP::Servers#get
104
    operationId: getSFTPServer
105
    tags:
106
      - sftp_servers
107
    summary: Get FTP/SFTP server
108
    parameters:
109
      - $ref: "../swagger.yaml#/parameters/sftp_server_id_pp"
110
    produces:
111
      - application/json
112
    responses:
113
      "200":
114
        description: An FTP/SFTP server object
115
        schema:
116
          $ref: "../swagger.yaml#/definitions/sftp_server"
117
      "400":
118
        description: Bad request
119
        schema:
120
          $ref: "../swagger.yaml#/definitions/error"
121
      "404":
122
        description: Object not found
123
        schema:
124
          $ref: "../swagger.yaml#/definitions/error"
125
      "409":
126
        description: Conflict updating resource
127
        schema:
128
          $ref: "../swagger.yaml#/definitions/error"
129
      "500":
130
        description: |
131
          Internal server error. Possible `error_code` attribute values:
132
133
          * `internal_server_error`
134
        schema:
135
          $ref: "../swagger.yaml#/definitions/error"
136
      "503":
137
        description: Under maintenance
138
        schema:
139
          $ref: "../swagger.yaml#/definitions/error"
140
    x-koha-authorization:
141
      permissions:
142
        parameters: manage_sftp_servers
143
  put:
144
    x-mojo-to: Config::SFTP::Servers#update
145
    operationId: updateSFTPServer
146
    tags:
147
      - sftp_servers
148
    summary: Update FTP/SFTP server
149
    parameters:
150
      - $ref: "../swagger.yaml#/parameters/sftp_server_id_pp"
151
      - name: body
152
        in: body
153
        description: An FTP/SFTP server object
154
        required: true
155
        schema:
156
          $ref: "../swagger.yaml#/definitions/sftp_server"
157
    produces:
158
      - application/json
159
    responses:
160
      "200":
161
        description: An FTP/SFTP server object
162
        schema:
163
          $ref: "../swagger.yaml#/definitions/sftp_server"
164
      "400":
165
        description: Bad request
166
        schema:
167
          $ref: "../swagger.yaml#/definitions/error"
168
      "401":
169
        description: Authentication required
170
        schema:
171
          $ref: "../swagger.yaml#/definitions/error"
172
      "403":
173
        description: Access forbidden
174
        schema:
175
          $ref: "../swagger.yaml#/definitions/error"
176
      "404":
177
        description: Object not found
178
        schema:
179
          $ref: "../swagger.yaml#/definitions/error"
180
      "500":
181
        description: |
182
          Internal server error. Possible `error_code` attribute values:
183
184
          * `internal_server_error`
185
        schema:
186
          $ref: "../swagger.yaml#/definitions/error"
187
      "503":
188
        description: Under maintenance
189
        schema:
190
          $ref: "../swagger.yaml#/definitions/error"
191
    x-koha-authorization:
192
      permissions:
193
        parameters: manage_sftp_servers
194
  delete:
195
    x-mojo-to: Config::SFTP::Servers#delete
196
    operationId: deleteSFTPServer
197
    tags:
198
      - sftp_servers
199
    summary: Delete FTP/SFTP server
200
    parameters:
201
      - $ref: "../swagger.yaml#/parameters/sftp_server_id_pp"
202
    produces:
203
      - application/json
204
    responses:
205
      "204":
206
        description: FTP/SFTP server deleted
207
      "400":
208
        description: Bad request
209
        schema:
210
          $ref: "../swagger.yaml#/definitions/error"
211
      "401":
212
        description: Authentication required
213
        schema:
214
          $ref: "../swagger.yaml#/definitions/error"
215
      "403":
216
        description: Access forbidden
217
        schema:
218
          $ref: "../swagger.yaml#/definitions/error"
219
      "404":
220
        description: Object not found
221
        schema:
222
          $ref: "../swagger.yaml#/definitions/error"
223
      "500":
224
        description: |
225
          Internal server error. Possible `error_code` attribute values:
226
227
          * `internal_server_error`
228
        schema:
229
          $ref: "../swagger.yaml#/definitions/error"
230
      "503":
231
        description: Under maintenance
232
        schema:
233
          $ref: "../swagger.yaml#/definitions/error"
234
    x-koha-authorization:
235
      permissions:
236
        parameters: manage_sftp_servers
(-)a/api/v1/swagger/paths/test_sftp_servers.yaml (+48 lines)
Line 0 Link Here
1
---
2
"/sftp_server/{sftp_server_id}/test_connection":
3
  get:
4
    x-mojo-to: SFTPServer#test
5
    operationId: testSFTPServer
6
    tags:
7
      - sftp_servers
8
    summary: Test FTP/SFTP server
9
    produces:
10
      - application/json
11
    parameters:
12
      - $ref: "../swagger.yaml#/parameters/sftp_server_id_pp"
13
    responses:
14
      "200":
15
        description: Results of FTP/SFTP server test
16
        schema:
17
          type: object
18
          items:
19
            $ref: "../swagger.yaml#/definitions/sftp_server"
20
      "400":
21
        description: |
22
          Bad request. Possible `error_code` attribute values:
23
24
            * `invalid_query`
25
        schema:
26
          $ref: "../swagger.yaml#/definitions/error"
27
      "404":
28
        description: Not Found
29
        schema:
30
          $ref: "../swagger.yaml#/definitions/error"
31
      "403":
32
        description: Access forbidden
33
        schema:
34
          $ref: "../swagger.yaml#/definitions/error"
35
      "500":
36
        description: |
37
          Internal server error. Possible `error_code` attribute values:
38
39
          * `internal_server_error`
40
        schema:
41
          $ref: "../swagger.yaml#/definitions/error"
42
      "503":
43
        description: Under maintenance
44
        schema:
45
          $ref: "../swagger.yaml#/definitions/error"
46
    x-koha-authorization:
47
      permissions:
48
        parameters: manage_sftp_servers
(-)a/api/v1/swagger/swagger.yaml (+17 lines)
Lines 174-179 definitions: Link Here
174
    $ref: ./definitions/search_filter.yaml
174
    $ref: ./definitions/search_filter.yaml
175
  smtp_server:
175
  smtp_server:
176
    $ref: ./definitions/smtp_server.yaml
176
    $ref: ./definitions/smtp_server.yaml
177
  sftp_server:
178
    $ref: ./definitions/sftp_server.yaml
177
  suggestion:
179
  suggestion:
178
    $ref: ./definitions/suggestion.yaml
180
    $ref: ./definitions/suggestion.yaml
179
  ticket:
181
  ticket:
Lines 305-310 paths: Link Here
305
    $ref: ./paths/config_smtp_servers.yaml#/~1config~1smtp_servers
307
    $ref: ./paths/config_smtp_servers.yaml#/~1config~1smtp_servers
306
  "/config/smtp_servers/{smtp_server_id}":
308
  "/config/smtp_servers/{smtp_server_id}":
307
    $ref: "./paths/config_smtp_servers.yaml#/~1config~1smtp_servers~1{smtp_server_id}"
309
    $ref: "./paths/config_smtp_servers.yaml#/~1config~1smtp_servers~1{smtp_server_id}"
310
  /config/sftp_servers:
311
    $ref: ./paths/config_sftp_servers.yaml#/~1config~1sftp_servers
312
  "/config/sftp_servers/{sftp_server_id}":
313
    $ref: "./paths/config_sftp_servers.yaml#/~1config~1sftp_servers~1{sftp_server_id}"
308
  "/deleted/biblios":
314
  "/deleted/biblios":
309
    $ref: "./paths/deleted_biblios.yaml#/~1deleted~1biblios"
315
    $ref: "./paths/deleted_biblios.yaml#/~1deleted~1biblios"
310
  "/deleted/biblios/{biblio_id}":
316
  "/deleted/biblios/{biblio_id}":
Lines 551-556 paths: Link Here
551
    $ref: "./paths/rotas.yaml#/~1rotas~1{rota_id}"
557
    $ref: "./paths/rotas.yaml#/~1rotas~1{rota_id}"
552
  "/rotas/{rota_id}/stages/{stage_id}/position":
558
  "/rotas/{rota_id}/stages/{stage_id}/position":
553
    $ref: "./paths/rotas.yaml#/~1rotas~1{rota_id}~1stages~1{stage_id}~1position"
559
    $ref: "./paths/rotas.yaml#/~1rotas~1{rota_id}~1stages~1{stage_id}~1position"
560
  "/sftp_server/{sftp_server_id}/test_connection":
561
    $ref: "./paths/test_sftp_servers.yaml#/~1sftp_server~1{sftp_server_id}~1test_connection"
554
  /suggestions:
562
  /suggestions:
555
    $ref: ./paths/suggestions.yaml#/~1suggestions
563
    $ref: ./paths/suggestions.yaml#/~1suggestions
556
  "/suggestions/{suggestion_id}":
564
  "/suggestions/{suggestion_id}":
Lines 931-936 parameters: Link Here
931
    name: smtp_server_id
939
    name: smtp_server_id
932
    required: true
940
    required: true
933
    type: integer
941
    type: integer
942
  sftp_server_id_pp:
943
    description: FTP/SFTP server internal identifier
944
    in: path
945
    name: sftp_server_id
946
    required: true
947
    type: integer
934
  suggestion_id_pp:
948
  suggestion_id_pp:
935
    description: Internal suggestion identifier
949
    description: Internal suggestion identifier
936
    in: path
950
    in: path
Lines 1278-1283 tags: Link Here
1278
  - description: "Manage SMTP servers configurations\n"
1292
  - description: "Manage SMTP servers configurations\n"
1279
    name: smtp_servers
1293
    name: smtp_servers
1280
    x-displayName: SMTP servers
1294
    x-displayName: SMTP servers
1295
  - description: "Manage FTP/SFTP servers configurations\n"
1296
    name: sftp_servers
1297
    x-displayName: FTP/SFTP servers
1281
  - description: "Manage tickets\n"
1298
  - description: "Manage tickets\n"
1282
    name: tickets
1299
    name: tickets
1283
    x-displayName: Tickets
1300
    x-displayName: Tickets
(-)a/installer/data/mysql/atomicupdate/bug_35761-add_SFTP_tables_and_perm.pl (+41 lines)
Line 0 Link Here
1
use Modern::Perl;
2
3
return {
4
    bug_number  => "35761",
5
    description => "Add new table and permission for generalised SFTP",
6
    up          => sub {
7
        my ($args) = @_;
8
        my ( $dbh, $out ) = @$args{qw(dbh out)};
9
10
        $dbh->do(
11
            q{
12
                INSERT IGNORE INTO permissions (module_bit, code, description)
13
                VALUES (3, 'manage_sftp_servers', 'Manage FTP/SFTP servers configuration');
14
            }
15
        );
16
        say $out "Added new manage_sftp_servers permission";
17
18
        unless ( TableExists('sftp_servers') ) {
19
            $dbh->do(
20
                q {
21
                    CREATE TABLE `sftp_servers` (
22
                    `id` int(11) NOT NULL AUTO_INCREMENT,
23
                    `name` varchar(80) NOT NULL,
24
                    `host` varchar(80) NOT NULL DEFAULT 'localhost',
25
                    `port` int(11) NOT NULL DEFAULT 22,
26
                    `transport` enum('ftp','sftp') NOT NULL DEFAULT 'sftp',
27
                    `passiv` tinyint(1) NOT NULL DEFAULT 1,
28
                    `user_name` varchar(80) DEFAULT NULL,
29
                    `password` varchar(80) DEFAULT NULL,
30
                    `key_file` varchar(4096) DEFAULT NULL,
31
                    `auth_mode` enum('password','key_file','noauth') NOT NULL DEFAULT 'password',
32
                    `debug` tinyint(1) NOT NULL DEFAULT 0,
33
                    PRIMARY KEY (`id`),
34
                    KEY `host_idx` (`host`)
35
                    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
36
                }
37
            );
38
            say $out "Added new sftp_servers table";
39
        }
40
    },
41
};
(-)a/installer/data/mysql/kohastructure.sql (+24 lines)
Lines 5922-5927 CREATE TABLE `sessions` ( Link Here
5922
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
5922
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
5923
/*!40101 SET character_set_client = @saved_cs_client */;
5923
/*!40101 SET character_set_client = @saved_cs_client */;
5924
5924
5925
--
5926
-- Table structure for table `sftp_servers`
5927
--
5928
5929
DROP TABLE IF EXISTS `sftp_servers`;
5930
/*!40101 SET @saved_cs_client     = @@character_set_client */;
5931
/*!40101 SET character_set_client = utf8 */;
5932
CREATE TABLE `sftp_servers` (
5933
  `id` int(11) NOT NULL AUTO_INCREMENT,
5934
  `name` varchar(80) NOT NULL,
5935
  `host` varchar(80) NOT NULL DEFAULT 'localhost',
5936
  `port` int(11) NOT NULL DEFAULT 22,
5937
  `transport` enum('ftp','sftp') NOT NULL DEFAULT 'sftp',
5938
  `passiv` tinyint(1) NOT NULL DEFAULT 1,
5939
  `user_name` varchar(80) DEFAULT NULL,
5940
  `password` varchar(80) DEFAULT NULL,
5941
  `key_file` varchar(4096) DEFAULT NULL,
5942
  `auth_mode` enum('password','key_file','noauth') NOT NULL DEFAULT 'password',
5943
  `debug` tinyint(1) NOT NULL DEFAULT 0,
5944
  PRIMARY KEY (`id`),
5945
  KEY `host_idx` (`host`)
5946
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
5947
/*!40101 SET character_set_client = @saved_cs_client */;
5948
5925
--
5949
--
5926
-- Table structure for table `sms_providers`
5950
-- Table structure for table `sms_providers`
5927
--
5951
--
(-)a/installer/data/mysql/mandatory/userpermissions.sql (+1 lines)
Lines 39-44 INSERT INTO permissions (module_bit, code, description) VALUES Link Here
39
   ( 3, 'manage_additional_fields', 'Add, edit, or delete additional custom fields for baskets or subscriptions (also requires order_manage or edit_subscription permissions)'),
39
   ( 3, 'manage_additional_fields', 'Add, edit, or delete additional custom fields for baskets or subscriptions (also requires order_manage or edit_subscription permissions)'),
40
   ( 3, 'manage_keyboard_shortcuts', 'Manage keyboard shortcuts for the advanced cataloging editor'),
40
   ( 3, 'manage_keyboard_shortcuts', 'Manage keyboard shortcuts for the advanced cataloging editor'),
41
   ( 3, 'manage_smtp_servers', 'Manage SMTP servers configuration'),
41
   ( 3, 'manage_smtp_servers', 'Manage SMTP servers configuration'),
42
   ( 3, 'manage_sftp_servers', 'Manage FTP/SFTP servers configuration'),
42
   ( 3, 'manage_background_jobs', 'Manage background jobs'),
43
   ( 3, 'manage_background_jobs', 'Manage background jobs'),
43
   ( 3, 'manage_curbside_pickups', 'Manage curbside pickups'),
44
   ( 3, 'manage_curbside_pickups', 'Manage curbside pickups'),
44
   ( 3, 'manage_search_filters', 'Manage custom search filters'),
45
   ( 3, 'manage_search_filters', 'Manage custom search filters'),
(-)a/koha-tmpl/intranet-tmpl/prog/en/includes/admin-menu.inc (-1 / +4 lines)
Lines 147-153 Link Here
147
        </ul>
147
        </ul>
148
    [% END %]
148
    [% END %]
149
149
150
    [% 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 ) ) %]
150
    [% 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 ) ) %]
151
        <h5>Additional parameters</h5>
151
        <h5>Additional parameters</h5>
152
        <ul>
152
        <ul>
153
            [% IF ( CAN_user_parameters_manage_identity_providers) %]
153
            [% IF ( CAN_user_parameters_manage_identity_providers) %]
Lines 160-165 Link Here
160
            [% IF ( CAN_user_parameters_manage_smtp_servers ) %]
160
            [% IF ( CAN_user_parameters_manage_smtp_servers ) %]
161
                <li><a href="/cgi-bin/koha/admin/smtp_servers.pl">SMTP servers</a></li>
161
                <li><a href="/cgi-bin/koha/admin/smtp_servers.pl">SMTP servers</a></li>
162
            [% END %]
162
            [% END %]
163
            [% IF ( CAN_user_parameters_manage_sftp_servers ) %]
164
                <li><a href="/cgi-bin/koha/admin/sftp_servers.pl">FTP/SFTP servers</a></li>
165
            [% END %]
163
            [% IF ( CAN_user_parameters_manage_didyoumean ) %]
166
            [% IF ( CAN_user_parameters_manage_didyoumean ) %]
164
                <li><a href="/cgi-bin/koha/admin/didyoumean.pl">Did you mean?</a></li>
167
                <li><a href="/cgi-bin/koha/admin/didyoumean.pl">Did you mean?</a></li>
165
            [% END %]
168
            [% END %]
(-)a/koha-tmpl/intranet-tmpl/prog/en/includes/permissions.inc (+5 lines)
Lines 257-262 Link Here
257
            Manage SMTP servers
257
            Manage SMTP servers
258
        </span>
258
        </span>
259
        <span class="permissioncode">([% name | html %])</span>
259
        <span class="permissioncode">([% name | html %])</span>
260
    [%- CASE 'manage_sftp_servers' -%]
261
        <span class="sub_permission manage_manage_sftp_servers_subpermission">
262
            Manage FTP/SFTP servers
263
        </span>
264
        <span class="permissioncode">([% name | html %])</span>
260
    [%- CASE 'manage_column_config' -%]
265
    [%- CASE 'manage_column_config' -%]
261
        <span class="sub_permission manage_column_config_subpermission">
266
        <span class="sub_permission manage_column_config_subpermission">
262
            Manage column configuration
267
            Manage column configuration
(-)a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/admin-home.tt (-1 / +5 lines)
Lines 253-259 Link Here
253
                </dl>
253
                </dl>
254
            [% END %]
254
            [% END %]
255
255
256
            [% 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) ) %]
256
            [% 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) ) %]
257
                <h3>Additional parameters</h3>
257
                <h3>Additional parameters</h3>
258
                <dl>
258
                <dl>
259
                        <!-- <dt><a href="/cgi-bin/koha/admin/printers.pl">Network Printers</a></dt>
259
                        <!-- <dt><a href="/cgi-bin/koha/admin/printers.pl">Network Printers</a></dt>
Lines 272-277 Link Here
272
                        <dt><a href="/cgi-bin/koha/admin/smtp_servers.pl">SMTP servers</a></dt>
272
                        <dt><a href="/cgi-bin/koha/admin/smtp_servers.pl">SMTP servers</a></dt>
273
                        <dd>Define which SMTP servers to use</dd>
273
                        <dd>Define which SMTP servers to use</dd>
274
                    [% END %]
274
                    [% END %]
275
                    [% IF ( CAN_user_parameters_manage_sftp_servers ) %]
276
                        <dt><a href="/cgi-bin/koha/admin/sftp_servers.pl">FTP/SFTP servers</a></dt>
277
                        <dd>Define available FTP/SFTP servers</dd>
278
                    [% END %]
275
                    [% IF ( CAN_user_parameters_manage_didyoumean ) %]
279
                    [% IF ( CAN_user_parameters_manage_didyoumean ) %]
276
                        <dt><a href="/cgi-bin/koha/admin/didyoumean.pl">Did you mean?</a></dt>
280
                        <dt><a href="/cgi-bin/koha/admin/didyoumean.pl">Did you mean?</a></dt>
277
                        <dd>Choose which plugins to use to suggest searches to patrons and staff</dd>
281
                        <dd>Choose which plugins to use to suggest searches to patrons and staff</dd>
(-)a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/sftp_servers.tt (+876 lines)
Line 0 Link Here
1
[% USE raw %]
2
[% USE Asset %]
3
[% PROCESS 'i18n.inc' %]
4
[% SET footerjs = 1 %]
5
[% INCLUDE 'doc-head-open.inc' %]
6
<title>[% FILTER collapse %]
7
    [% IF op == 'add_form' %]
8
        [% t("New FTP/SFTP server") | html %] &rsaquo;
9
    [% ELSIF op == 'edit_form' %]
10
        [% tx("Modify FTP/SFTP server '{sftp_server}'", { sftp_server = sftp_server.name }) | html %] &rsaquo;
11
    [% ELSIF op == 'test_form' %]
12
        [% tx("Test FTP/SFTP server '{sftp_server}'", { sftp_server = sftp_server.name }) | html %] &rsaquo;
13
    [% END %]
14
    [% t("FTP/SFTP Servers") | html %] &rsaquo;
15
    [% t("Administration") | html %] &rsaquo;
16
    [% t("Koha") | html %]
17
[% END %]</title>
18
[% INCLUDE 'doc-head-close.inc' %]
19
<style>
20
    #testOutput {
21
        font-size: 1.25rem;
22
    }
23
24
    #testOutput .pending-loading {
25
        font-size: 1.5rem;
26
        margin-left: 1.25rem;
27
    }
28
29
    #testOutput code {
30
        font-size: 87.5%;
31
        color: #e83e8c;
32
        background: transparent;
33
        word-break: break-word;
34
    }
35
</style>
36
37
</head>
38
39
<body id="admin_sftp_servers" class="admin">
40
[% WRAPPER 'header.inc' %]
41
    [% INCLUDE 'prefs-admin-search.inc' %]
42
[% END %]
43
44
[% WRAPPER 'sub-header.inc' %]
45
    [% WRAPPER breadcrumbs %]
46
        [% WRAPPER breadcrumb_item %]
47
            <a href="/cgi-bin/koha/admin/admin-home.pl">Administration</a>
48
        [% END %]
49
50
        [% IF op == 'add_form' || op == 'edit_form' || op == 'test_form' %]
51
            [% WRAPPER breadcrumb_item %]
52
                <a href="/cgi-bin/koha/admin/sftp_servers.pl">FTP/SFTP servers</a>
53
            [% END %]
54
        [% END %]
55
56
        [% IF op == 'add_form' %]
57
            [% WRAPPER breadcrumb_item bc_active= 1 %]
58
                <span>New FTP/SFTP server</span>
59
            [% END %]
60
61
        [% ELSIF op == 'edit_form' %]
62
            [% WRAPPER breadcrumb_item bc_active= 1 %]
63
                [% tx("Modify FTP/SFTP server '{sftp_server}'", { sftp_server = sftp_server.name }) | html %]
64
            [% END %]
65
66
        [% ELSIF op == 'test_form' %]
67
            [% WRAPPER breadcrumb_item bc_active= 1 %]
68
                [% tx("Test FTP/SFTP server '{sftp_server}'", { sftp_server = sftp_server.name }) | html %]
69
            [% END %]
70
71
        [% ELSE %]
72
            [% WRAPPER breadcrumb_item bc_active= 1 %]
73
                <span>FTP/SFTP servers</span>
74
            [% END %]
75
        [% END %]
76
    [% END #/ WRAPPER breadcrumbs %]
77
[% END #/ WRAPPER sub-header.inc %]
78
79
<div class="main container-fluid">
80
    <div class="row">
81
        <div class="col-md-10 order-md-2 order-sm-1">
82
            <main>
83
                [% INCLUDE 'messages.inc' %]
84
85
[% FOREACH m IN messages %]
86
    <div class="alert alert-[% m.type | html %]" id="sftp_action_result_dialog">
87
        [% SWITCH m.code %]
88
        [% CASE 'error_on_insert' %]
89
            <span>An error occurred when adding the server. The passed ID already exists.</span>
90
        [% CASE 'error_on_update' %]
91
            <span>An error occurred trying to open the server for editing. The passed ID is invalid.</span>
92
        [% CASE 'error_on_edit' %]
93
            <span>An error occurred trying to open the server for editing. The passed ID is invalid.</span>
94
        [% CASE 'error_on_test' %]
95
            <span>An error occurred when connecting to this server. Please see the text below for more information.</span>
96
        [% CASE 'success_on_update' %]
97
            <span>Server updated successfully.</span>
98
        [% CASE 'success_on_insert' %]
99
            <span>Server added successfully.</span>
100
        [% CASE %]
101
            <span>[% m.code | html %]</span>
102
        [% END %]
103
    </div>
104
[% END %]
105
106
    <div class="alert alert-info"    id="sftp_delete_success" style="display: none;"></div>
107
    <div class="alert alert-warning" id="sftp_delete_error"   style="display: none;"></div>
108
109
[% IF op == 'add_form' %]
110
    <!-- Modal -->
111
    <div id="confirm_key_accept" class="modal" tabindex="-1" role="dialog" aria-labelledby="confirm_key_accept_submit" aria-hidden="true">
112
        <div class="modal-dialog modal-lg">
113
            <div class="modal-content modal-lg">
114
                    <div class="modal-header">
115
                        <h1 class="modal-title">Are you sure?</h1>
116
                        <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
117
                    </div>
118
                    <div class="modal-body">
119
                        <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>
120
                        <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>
121
                        <table class="mx-4 mb-3">
122
                            <thead></thead>
123
                            <tbody>
124
                                <tr>
125
                                    <td><strong>Host</strong></td>
126
                                    <td id="modal_host"></td>
127
                                </tr>
128
                                <tr>
129
                                    <td><strong>Port</strong></td>
130
                                    <td id="modal_port"></td>
131
                                </tr>
132
                                <tr>
133
                                    <td><strong>Transport</strong></td>
134
                                    <td id="modal_transport"></td>
135
                                </tr>
136
                                <tr>
137
                                    <td><strong>Username</strong></td>
138
                                    <td id="modal_user_name"></td>
139
                                </tr>
140
                                <tr>
141
                                    <td><strong>Authentication mode</strong></td>
142
                                    <td id="modal_auth_mode"></td>
143
                                </tr>
144
                            </tbody>
145
                        </table>
146
                        <p>If you are ready to progress with these details, please click Save.</p>
147
                    </div>
148
                    <div class="modal-footer">
149
                        <button class="btn btn-default approve" type="submit"><i class="fa fa-check"></i> Save</button>
150
                        <button class="btn btn-default deny cancel" type="button" data-bs-dismiss="modal"><i class="fa fa-times"></i> Cancel</button>
151
                    </div>
152
                </form>
153
            </div>
154
        </div>
155
    </div>
156
    <!-- END Modal -->
157
158
    <h1>New FTP/SFTP server</h1>
159
160
    <form action="/cgi-bin/koha/admin/sftp_servers.pl" id="add" name="add" class="validated" method="post">
161
        [% INCLUDE 'csrf-token.inc' %]
162
        <input type="hidden" name="op" value="cud-add" />
163
        <fieldset class="rows">
164
            <ol>
165
                <li>
166
                    <label for="sftp_name" class="required">Name: </label>
167
                    <input type="text" name="sftp_name" id="sftp_name" size="60" class="required focus" required="required" />
168
                    <span class="required">Required</span>
169
                </li>
170
            </ol>
171
        </fieldset>
172
173
        <fieldset class="rows">
174
            <ol>
175
                <li>
176
                    <label for="sftp_host" class="required">Host: </label>
177
                    <input type="text" value="localhost" name="sftp_host" id="sftp_host" size="60" class="required" />
178
                    <span class="required">Required</span>
179
                </li>
180
                <li>
181
                    <label for="sftp_port" class="required">Port: </label>
182
                    <input type="text" inputmode="numeric" pattern="[0-9]*" value="22" name="sftp_port" id="sftp_port" size="20" class="required" />
183
                    <span class="required">Required</span>
184
                </li>
185
                <li>
186
                    <label for="sftp_transport" class="required">Transport: </label>
187
                    <select name="sftp_transport" id="sftp_transport" class="required">
188
                        <option value="ftp">FTP</option>
189
                        <option value="sftp" selected="selected">SFTP</option>
190
                    </select>
191
                    <span class="required">Required</span>
192
                </li>
193
                <li>
194
                    <label for="sftp_passiv">Passive mode: </label>
195
                    <select name="sftp_passiv" id="sftp_passiv" disabled="disabled">
196
                        <option value="1" selected="selected">On (Recommended)</option>
197
                        <option value="0">Off</option>
198
                    </select>
199
                    <span class="hint">Only applies to FTP connections</span>
200
                </li>
201
                <li>
202
                    <label for="sftp_auth_mode">Authentication mode: </label>
203
                    <select name="sftp_auth_mode" id="sftp_auth_mode">
204
                        <option value="password" selected="selected">Password-based</option>
205
                        <option value="key_file">Key file-based</option>
206
                        <option value="noauth">No authentication</option>
207
                    </select>
208
                </li>
209
                <li>
210
                    <label for="sftp_user_name" class="required">Username: </label>
211
                    <input type="text" name="sftp_user_name" id="sftp_user_name" size="60" autocomplete="off" class="required" />
212
                    <span class="required">Required</span>
213
                </li>
214
                <li>
215
                    <label for="sftp_password">Password: </label>
216
                    <input type="password" name="sftp_password" id="sftp_password" size="60" autocomplete="off" />
217
                </li>
218
                <li>
219
                    <label for="sftp_key_file">Key file: </label>
220
                    <textarea name="sftp_key_file" id="sftp_key_file" rows="10" cols="58"></textarea>
221
                    <span class="hint">Only applies to SFTP connections</span>
222
                </li>
223
                <li>
224
                    <label for="sftp_debug_mode">Debug mode: </label>
225
                    <select name="sftp_debug_mode" id="sftp_debug_mode">
226
                        <option value="1">Enabled</option>
227
                        <option value="0" selected="selected">Disabled</option>
228
                    </select>
229
                    <span class="hint">Enables additional debug output in the logs</span>
230
                </li>
231
            </ol>
232
        </fieldset>
233
        <fieldset class="action">
234
            <a id="confirm_key_accept_submit" data-bs-target="#confirm_key_accept" class="btn btn-primary" data-bs-toggle="modal">Submit</a>
235
            <a class="cancel" href="/cgi-bin/koha/admin/sftp_servers.pl">Cancel</a>
236
        </fieldset>
237
    </form>
238
[% END %]
239
240
[% IF op == 'edit_form' && !messages %]
241
    <!-- Modal -->
242
    <div id="confirm_key_accept" class="modal" tabindex="-1" role="dialog" aria-labelledby="confirm_key_accept_submit" aria-hidden="true">
243
        <div class="modal-dialog modal-lg">
244
            <div class="modal-content modal-lg">
245
                    <div class="modal-header">
246
                        <h1 class="modal-title">Are you sure?</h1>
247
                        <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
248
                    </div>
249
                    <div class="modal-body">
250
                        <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>
251
                        <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>
252
                        <table class="mx-4 mb-3">
253
                            <thead></thead>
254
                            <tbody>
255
                                <tr>
256
                                    <td><strong>Host</strong></td>
257
                                    <td id="modal_host"></td>
258
                                </tr>
259
                                <tr>
260
                                    <td><strong>Port</strong></td>
261
                                    <td id="modal_port"></td>
262
                                </tr>
263
                                <tr>
264
                                    <td><strong>Transport</strong></td>
265
                                    <td id="modal_transport"></td>
266
                                </tr>
267
                                <tr>
268
                                    <td><strong>Username</strong></td>
269
                                    <td id="modal_user_name"></td>
270
                                </tr>
271
                                <tr>
272
                                    <td><strong>Authentication mode</strong></td>
273
                                    <td id="modal_auth_mode"></td>
274
                                </tr>
275
                            </tbody>
276
                        </table>
277
                        <p>If you are ready to progress with these details, please click Save.</p>
278
                    </div>
279
                    <div class="modal-footer">
280
                        <button class="btn btn-default approve" type="submit"><i class="fa fa-check"></i> Save</button>
281
                        <button class="btn btn-default deny cancel" type="button" data-bs-dismiss="modal"><i class="fa fa-times"></i> Cancel</button>
282
                    </div>
283
                </form>
284
            </div>
285
        </div>
286
    </div>
287
    <!-- END Modal -->
288
289
    <h1>[% tx("Modify FTP/SFTP server '{sftp_server}'", { sftp_server = sftp_server.name }) | html %]</h1>
290
291
    <form action="/cgi-bin/koha/admin/sftp_servers.pl" id="edit_save" name="edit_save" class="validated" method="post">
292
        [% INCLUDE 'csrf-token.inc' %]
293
        <input type="hidden" name="op" value="cud-edit_save" />
294
        <input type="hidden" name="sftp_server_id" value="[%- sftp_server.id | html -%]" />
295
        <fieldset class="rows">
296
            <ol>
297
                <li>
298
                    <label for="sftp_name" class="required">Name: </label>
299
                    <input type="text" value="[% sftp_server.name | html %]" name="sftp_name" id="sftp_name" size="60" class="required focus" required="required" />
300
                    <span class="required">Required</span>
301
                </li>
302
            </ol>
303
        </fieldset>
304
305
        <fieldset class="rows">
306
            <ol>
307
                <li>
308
                    <label for="sftp_host" class="required">Host: </label>
309
                    <input type="text" value="[% sftp_server.host | html %]" name="sftp_host" id="sftp_host" size="60" class="required" />
310
                    <span class="required">Required</span>
311
                </li>
312
                <li>
313
                    <label for="sftp_port" class="required">Port: </label>
314
                    <input type="text" inputmode="numeric" pattern="[0-9]*" value="[% sftp_server.port | html %]" name="sftp_port" id="sftp_port" size="20" class="required"/>
315
                    <span class="required">Required</span>
316
                </li>
317
                <li>
318
                    <label for="sftp_transport" class="required">Transport: </label>
319
                    <select name="sftp_transport" id="sftp_transport" class="required">
320
                        [% IF sftp_server.transport == 'ftp' %]
321
                        <option value="ftp" selected="selected">FTP</option>
322
                        [% ELSE %]
323
                        <option value="ftp">FTP</option>
324
                        [% END %]
325
                        [% IF sftp_server.transport == 'sftp' %]
326
                        <option value="sftp" selected="selected">SFTP</option>
327
                        [% ELSE %]
328
                        <option value="sftp">SFTP</option>
329
                        [% END %]
330
                    </select>
331
                    <span class="required">Required</span>
332
                </li>
333
                <li>
334
                    <label for="sftp_passiv">Passive mode: </label>
335
                    <select name="sftp_passiv" id="sftp_passiv" disabled="disabled">
336
                        [% IF sftp_server.passiv == 1 %]
337
                        <option value="1" selected="selected">Enabled (Recommended)</option>
338
                        [% ELSE %]
339
                        <option value="1">Enabled (Recommended)</option>
340
                        [% END %]
341
                        [% IF sftp_server.passiv == 0 %]
342
                        <option value="0" selected="selected">Disabled</option>
343
                        [% ELSE %]
344
                        <option value="0">Disabled</option>
345
                        [% END %]
346
                    </select>
347
                    <span class="hint">Only applies to FTP connections</span>
348
                </li>
349
                <li>
350
                    <label for="sftp_auth_mode">Authentication mode: </label>
351
                    <select name="sftp_auth_mode" id="sftp_auth_mode">
352
                        [% IF sftp_server.auth_mode == 'password' %]
353
                        <option value="password" selected="selected">Password-based</option>
354
                        [% ELSE %]
355
                        option value="password">Password-based</option>
356
                        [% END %]
357
                        [% IF sftp_server.auth_mode == 'key_file' %]
358
                        <option value="key_file" selected="selected">Key file-based</option>
359
                        [% ELSE %]
360
                        <option value="key_file">Key file-based</option>
361
                        [% END %]
362
                        [% IF sftp_server.auth_mode == 'noauth' %]
363
                        <option value="noauth" selected="selected">No authentication</option>
364
                        [% ELSE %]
365
                        <option value="noauth">No authentication</option>
366
                        [% END %]
367
                    </select>
368
                </li>
369
                <li>
370
                    <label for="sftp_user_name" class="required">Username: </label>
371
                    <input type="text" value="[% sftp_server.user_name | html %]" name="sftp_user_name" id="sftp_user_name" size="60" autocomplete="off" class="required" />
372
                    <span class="required">Required</span>
373
                </li>
374
                <li>
375
                    <label for="sftp_password">Password: </label>
376
                    <input type="password" value="[% sftp_server_plain_text_password | html %]" name="sftp_password" id="sftp_password" size="60" autocomplete="off" />
377
                </li>
378
                <li>
379
                    <label for="sftp_key_file">Key file path: </label>
380
                    <textarea name="sftp_key_file" id="sftp_key_file" rows="10" cols="58">[% sftp_server_plain_text_key | html %]</textarea>
381
                    <span class="hint">Only applies to SFTP connections</span>
382
                </li>
383
                <li>
384
                    <label for="sftp_debug_mode">Debug mode: </label>
385
                    <select name="sftp_debug_mode" id="sftp_debug_mode">
386
                        [% IF sftp_server.debug == 1 %]
387
                        <option value="1" selected="selected">Enabled</option>
388
                        [% ELSE %]
389
                        <option value="1">Enabled</option>
390
                        [% END %]
391
                        [% IF sftp_server.debug == 0 %]
392
                        <option value="0" selected="selected">Disabled</option>
393
                        [% ELSE %]
394
                        <option value="0">Disabled</option>
395
                        [% END %]
396
                    </select>
397
                    <span class="hint">Enables additional debug output in the logs</span>
398
                </li>
399
            </ol>
400
        </fieldset>
401
        <fieldset class="action">
402
            <a id="confirm_key_accept_submit" data-bs-target="#confirm_key_accept" class="btn btn-primary" data-bs-toggle="modal">Submit</a>
403
            <a class="cancel" href="/cgi-bin/koha/admin/sftp_servers.pl">Cancel</a>
404
        </fieldset>
405
    </form>
406
[% END %]
407
408
[% IF op == 'test_form' %]
409
    <div id="toolbar" class="btn-toolbar">
410
        <a class="btn btn-default" id="newtest" href="/cgi-bin/koha/admin/sftp_servers.pl?op=test_form&amp;sftp_server_id=[% sftp_server.id | html %]"><i class="fa-solid fa-rotate-right"></i> Retry test</a>
411
    </div>
412
413
    <h1>[% tx("Test FTP/SFTP server '{sftp_server}'", { sftp_server = sftp_server.name }) | html %]</h1>
414
    <div class="page-section">
415
    [% IF sftp_server.id %]
416
        <div class="row">
417
            <div class="col-12 col-lg-6 order-1 order-lg-0">
418
                <h3>Test results</h3>
419
                <div id="testOutput">
420
                    <div class="spinner-border text-warning" style="height:1.5rem;width:1.5rem" role="status">
421
                        <span class="sr-only">Loading...</span>
422
                    </div>
423
                    <span class="pending-loading">Running tests...</span>
424
                </div>
425
            </div>
426
            <div class="col-12 col-lg-6 order-0 order-lg-1">
427
                <h3>Test details</h3>
428
                <p>Connection details are as follows:</p>
429
                <table class="mx-4 mb-3">
430
                    <thead></thead>
431
                    <tbody>
432
                        <tr>
433
                            <td><strong>Host</strong></td>
434
                            <td>[% sftp_server.host | html %]</td>
435
                        </tr>
436
                        <tr>
437
                            <td><strong>Port</strong></td>
438
                            <td>[% sftp_server.port | html %]</td>
439
                        </tr>
440
                        <tr>
441
                            <td><strong>Transport</strong></td>
442
                            <td>[% sftp_server.transport FILTER upper | html %]</td>
443
                        </tr>
444
                        <tr>
445
                            <td><strong>Username</strong></td>
446
                            <td>[% sftp_server.user_name | html %]</td>
447
                        </tr>
448
                        <tr>
449
                            <td><strong>Authentication mode</strong></td>
450
                            <td>
451
                                [% IF sftp_server.auth_mode == 'password' %]
452
                                Password-based
453
                                [% ELSE %]
454
                                Key file-based
455
                                [% END %]
456
                            </td>
457
                        </tr>
458
                    </tbody>
459
                </table>
460
            </div>
461
        </div>
462
    [% ELSE %]
463
    <h3>Oops &ndash; Not Found</h3>
464
    <p>An FTP/SFTP server with that ID was not found. Please go back and try again.</p>
465
    [% END %]
466
    </div>
467
[% END %]
468
469
[% IF op == 'list' %]
470
471
    <div id="toolbar" class="btn-toolbar">
472
        <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>
473
    </div>
474
475
    <h1>FTP/SFTP servers</h1>
476
477
    [% IF servers_count < 1 %]
478
        <div class="alert alert-info" id="dno_servers_message">
479
            <p>
480
                <em>There are no FTP/SFTP servers defined.</em><br />
481
                To create one, use the <strong>new FTP/SFTP server</strong> button above.
482
            </p>
483
        </div>
484
    [% ELSE %]
485
        <div class="page-section">
486
            <table id="sftp_servers">
487
                <thead>
488
                    <tr>
489
                        <th>Name</th>
490
                        <th>Host</th>
491
                        <th>Port</th>
492
                        <th>Transport</th>
493
                        <th>Passive mode</th>
494
                        <th>Authentication mode</th>
495
                        <th>Username</th>
496
                        <th>Debug</th>
497
                        <th data-class-name="actions noExport">Actions</th>
498
                    </tr>
499
                </thead>
500
            </table>
501
        </div> <!-- /.page-section -->
502
    [% END %]
503
[% END %]
504
505
            <div id="delete_confirm_modal" class="modal" tabindex="-1" role="dialog" aria-labelledby="delete_confirm_modal_label" aria-hidden="true">
506
                <div class="modal-dialog">
507
                    <div class="modal-content">
508
                        <div class="modal-header">
509
                            <h1 class="modal-title" id="delete_confirm_modal_label">Delete server</h1>
510
                            <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
511
                        </div>
512
                        <div class="modal-body">
513
                            <div id="delete_confirm_dialog"></div>
514
                        </div>
515
                        <div class="modal-footer">
516
                            <button type="button" class="btn btn-danger" id="delete_confirm_modal_button" data-bs-toggle="modal">Delete</button>
517
                            <button type="button" class="btn btn-default" data-bs-dismiss="modal">Close</button>
518
                        </div>
519
                    </div> <!-- /.modal-content -->
520
                </div> <!-- /.modal-dialog -->
521
            </div> <!-- #delete_confirm_modal -->
522
523
            </main>
524
        </div> <!-- /.col-md-10.order-md-2 -->
525
526
        <div class="col-md-2 order-sm-2 order-md-1">
527
            <aside>
528
                [% INCLUDE 'admin-menu.inc' %]
529
            </aside>
530
        </div> <!-- /.col-md-2.order-md-1 -->
531
     </div> <!-- /.row -->
532
533
534
[% MACRO jsinclude BLOCK %]
535
    [% Asset.js("js/admin-menu.js") | $raw %]
536
    [% INCLUDE 'datatables.inc' %]
537
    <script>
538
        $(document).ready(function() {
539
540
            var sftp_servers_url = '/api/v1/config/sftp_servers';
541
            window.sftp_servers = $("#sftp_servers").kohaTable({
542
                "ajax": {
543
                    "url": sftp_servers_url
544
                },
545
                'language': {
546
                    'emptyTable': '<div class="alert alert-info">'+_("There are no FTP/SFTP servers defined.")+'</div>'
547
                },
548
                "columnDefs": [ {
549
                    "targets": [0,1],
550
                    "render": function(data, type, row, meta) {
551
                        if(type == 'display') {
552
                            if(data != null) {
553
                                return data.escapeHtml();
554
                            }
555
                            else {
556
                                return "Default";
557
                            }
558
                        }
559
                        return data;
560
                    }
561
                } ],
562
                "columns": [
563
                    {
564
                        "data": "name",
565
                        "searchable": true,
566
                        "orderable": true
567
                    },
568
                    {
569
                        "data": "host",
570
                        "searchable": true,
571
                        "orderable": true
572
                    },
573
                    {
574
                        "data": "port",
575
                        "searchable": true,
576
                        "orderable": false
577
                    },
578
                    {
579
                        "data": "transport",
580
                        "render": function(data, type, row, meta) {
581
                            return data.toUpperCase();
582
                        },
583
                        "searchable": true,
584
                        "orderable": false
585
                    },
586
                    {
587
                        "data": "passiv",
588
                        "render": function(data, type, row, meta) {
589
                            if(data == true) {
590
                                return "[% tp("Active", "On") | html %]";
591
                            }
592
                            else {
593
                                return _("Off");
594
                            }
595
                        },
596
                        "searchable": false,
597
                        "orderable": false
598
                    },
599
                    {
600
                        "data": "auth_mode",
601
                        "render": function(data, type, row, meta) {
602
                            if(data == "password") {
603
                                return _("Password-based");
604
                            }
605
                            else if(data == "key_file") {
606
                                return _("Key file-based");
607
                            }
608
                            else {
609
                                return _("No authentication");
610
                            }
611
                        },
612
                        "searchable": false,
613
                        "orderable": false
614
                    },
615
                    {
616
                        "data": "user_name",
617
                        "searchable": false,
618
                        "orderable": false
619
                    },
620
                    {
621
                        "data": "debug",
622
                        "render": function(data, type, row, meta) {
623
                            if(data == true) {
624
                                return "[% tp("Active", "On") | html %]";
625
                            }
626
                            else {
627
                                return _("Off");
628
                            }
629
                        },
630
                        "searchable": false,
631
                        "orderable": false
632
                    },
633
                    {
634
                        "data": function(row, type, val, meta) {
635
                            var result = '<a class="btn btn-default btn-xs" role="button" href="/cgi-bin/koha/admin/sftp_servers.pl?op=test_form&amp;sftp_server_id='+ encodeURIComponent(row.sftp_server_id) +'"><i class="fa-solid fa-vial" aria-hidden="true"></i> '+_("Test")+'</a>'+"\n";
636
                            result += '<a class="btn btn-default btn-xs" role="button" href="/cgi-bin/koha/admin/sftp_servers.pl?op=edit_form&amp;sftp_server_id='+ encodeURIComponent(row.sftp_server_id) +'"><i class="fa-solid fa-pencil" aria-hidden="true"></i> '+_("Edit")+'</a>'+"\n";
637
                            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>';
638
                            return result;
639
                        },
640
                        "searchable": false,
641
                        "orderable": false
642
                    }
643
                ],
644
                createdRow: function(row, data, dataIndex) {
645
                    if(data.is_default) {
646
                        $(row).addClass('default warn');
647
                    }
648
                    if(data.debug) {
649
                        $(row).addClass('debug');
650
                    }
651
                },
652
            });
653
654
            $('#sftp_servers').on("click", '.delete_server', function() {
655
                var sftp_server_id   = $(this).data('sftp-server-id');
656
                var sftp_server_name = decodeURIComponent($(this).data('sftp-server-name'));
657
658
                $("#delete_confirm_dialog").html(
659
                    _("You are about to delete the '%s' FTP/SFTP server.").format(sftp_server_name)
660
                );
661
                $("#delete_confirm_modal_button").data('sftp-server-id', sftp_server_id);
662
                $("#delete_confirm_modal_button").data('sftp-server-name', sftp_server_name);
663
            });
664
665
            $("#delete_confirm_modal_button").on("click", function() {
666
667
                var sftp_server_id   = $(this).data('sftp-server-id');
668
                var sftp_server_name = $(this).data('sftp-server-name');
669
670
                $.ajax({
671
                    method: "DELETE",
672
                    url: "/api/v1/config/sftp_servers/"+sftp_server_id
673
                }).success(function() {
674
                    window.sftp_servers.api().ajax.reload(function(data) {
675
                        $("#sftp_action_result_dialog").hide();
676
                        $("#sftp_delete_success").html(_("Server '%s' deleted successfully.").format(sftp_server_name)).show();
677
                    });
678
                }).fail(function() {
679
                    $("#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();
680
                }).done(function() {
681
                    $("#delete_confirm_modal").modal('hide');
682
                });
683
            });
684
685
            if(window.location.pathname == '/cgi-bin/koha/admin/sftp_servers.pl') {
686
                handleTests();
687
            }
688
689
            transportChange();
690
            $("#sftp_transport").on("change", function(event) {
691
                transportChange();
692
            });
693
694
            authModeChange();
695
            $("#sftp_auth_mode").on("change", function(event) {
696
                authModeChange();
697
            });
698
699
            $('#confirm_key_accept_submit').on('click', function(event) {
700
                event.preventDefault();
701
702
                if ( $('#add').length > 0 ) {
703
                    if( $('#add').valid() == true ) {
704
                        modalChange();
705
                        $('#confirm_key_accept').modal('show');
706
                    } else {
707
                        $('#confirm_key_accept').modal('hide');
708
                    }
709
                }
710
711
                if ( $('#edit_save').length > 0 ) {
712
                    if( $('#edit_save').valid() == true ) {
713
                        modalChange();
714
                        $('#confirm_key_accept').modal('show');
715
                    } else {
716
                        $('#confirm_key_accept').modal('hide');
717
                    }
718
                }
719
720
            });
721
722
            $('#confirm_key_accept .approve').on('click', function() {
723
                $('#confirm_key_accept .deny').click();
724
725
                if ( $('#add').length > 0 ) {
726
                    $('#add').submit();
727
                }
728
729
                if ( $('#edit_save').length > 0 ) {
730
                    $('#edit_save').submit();
731
                }
732
            });
733
734
        });
735
736
        function transportChange() {
737
            let sftp_transport = $("#sftp_transport");
738
739
            if(sftp_transport.val() == "ftp") {
740
                $("#sftp_host").removeAttr("disabled");
741
                $("#sftp_port").removeAttr("disabled");
742
                $("#sftp_passiv").removeAttr("disabled");
743
                $("#sftp_auth_mode").removeAttr("disabled");
744
                $("#sftp_user_name").removeAttr("disabled");
745
                $("#sftp_password").removeAttr("disabled");
746
                $("#sftp_key_file").attr("disabled", "disabled");
747
748
                $("#sftp_auth_mode option[value='password']").removeAttr("disabled");
749
                $("#sftp_auth_mode option[value='key_file']").attr("disabled", "disabled");
750
                $("#sftp_auth_mode option[value='noauth']").removeAttr("disabled");
751
                if($("#sftp_auth_mode option:selected").val() == "key_file") {
752
                    $("#sftp_auth_mode option[value='password']").prop("selected", true);
753
                }
754
755
                let sftp_port = $("#sftp_port").val();
756
                if(sftp_port == 22) $("#sftp_port").val("21");
757
758
                authModeChange();
759
            } else if(sftp_transport.val() == "sftp") {
760
                $("#sftp_host").removeAttr("disabled");
761
                $("#sftp_port").removeAttr("disabled");
762
                $("#sftp_passiv").attr("disabled", "disabled");
763
                $("#sftp_auth_mode").removeAttr("disabled");
764
                $("#sftp_user_name").removeAttr("disabled");
765
                $("#sftp_password").removeAttr("disabled");
766
                $("#sftp_key_file").removeAttr("disabled");
767
768
                $("#sftp_auth_mode option[value='password']").removeAttr("disabled");
769
                $("#sftp_auth_mode option[value='key_file']").removeAttr("disabled");
770
                $("#sftp_auth_mode option[value='noauth']").removeAttr("disabled");
771
                $("#sftp_passiv option[value='1']").prop("selected", true);
772
773
                let sftp_port = $("#sftp_port").val();
774
                if(sftp_port == 21) $("#sftp_port").val("22");
775
776
                return authModeChange();
777
            }
778
        }
779
780
        function authModeChange() {
781
            let sftp_auth_mode = $("#sftp_auth_mode").val();
782
783
            if(sftp_auth_mode == "password") {
784
                $("#sftp_password").removeAttr("disabled");
785
                $("#sftp_key_file").attr("disabled", "disabled");
786
            } else if(sftp_auth_mode == "key_file") {
787
                $("#sftp_password").attr("disabled", "disabled");
788
                $("#sftp_key_file").removeAttr("disabled");
789
            } else {
790
                $("#sftp_password").attr("disabled", "disabled");
791
                $("#sftp_key_file").attr("disabled", "disabled");
792
            }
793
        }
794
795
        function modalChange() {
796
            $('#modal_message').hide();
797
            if ( $('#sftp_transport').val() == 'sftp' ) $('#modal_message').show();
798
799
            $('#modal_host').text( $('#sftp_host').val() );
800
            $('#modal_port').text( $('#sftp_port').val() );
801
            $('#modal_transport').text( $('#sftp_transport option:selected').text() );
802
            $('#modal_user_name').text( $('#sftp_user_name').val() );
803
            $('#modal_auth_mode').text( $('#sftp_auth_mode option:selected').text() );
804
        }
805
806
        function handleTests() {
807
            var testOutput = $('#testOutput');
808
809
            $.ajax({
810
                url: "/api/v1/sftp_server/[% sftp_server.id | html %]/test_connection",
811
            })
812
            .done(function(data) {
813
                testOutput.text('');
814
815
                for ( let [key, value] of Object.entries( data ) ) {
816
                    var title;
817
                    switch(key) {
818
                        case '1_sftp_conn':
819
                            title = _("Testing SFTP connectivity");
820
                            break;
821
                        case '1_ftp_conn':
822
                            title = _("Testing FTP connectivity");
823
                            break;
824
                        case '2_sftp_ls':
825
                        case '3_ftp_ls':
826
                            title = _("Testing FTP connectivity");
827
                            break;
828
                        case '2_ftp_login':
829
                            title = _("Testing we can log in");
830
                            break;
831
                        case '3_sftp_write':
832
                        case '4_ftp_write':
833
                            title = _("Testing we can write a test file");
834
                            break;
835
                        case '4_sftp_del':
836
                        case '5_ftp_del':
837
                            title = _("Testing we can delete test file");
838
                            break;
839
                        default:
840
                            title = key
841
                    }
842
843
                    if ( value.passed ) {
844
                        testOutput.append(
845
                            '<i class="text-success fa-solid fa-circle-check"></i> '
846
                            + title
847
                            + '... <span class="text-success">'
848
                            + _("Passed")
849
                            + '</span><br />'
850
                        );
851
                        if( value.msg ) testOutput.append( _("Message: ") + '<code>' + value.msg + '</code><br />' );
852
                    } else {
853
                        testOutput.append(
854
                            '<i class="text-danger fa-solid fa-circle-xmark"></i> '
855
                            + title
856
                            + '... <span class="text-danger">'
857
                            + _("Failed")
858
                            + '</span><br />'
859
                        );
860
                        if( value.err ) testOutput.append( _("Error message: ") + '<code>' + value.err + '</code><br />' );
861
                    }
862
                    testOutput.append( '<br />' );
863
                }
864
            })
865
            .fail(function(data) {
866
                if( data.status == 404 ) {
867
                    return testOutput.text( '<i class="text-success fa-solid fa-circle-check"></i> ' + _("FTP/SFTP Server not found") );
868
                } else {
869
                    return testOutput.text( '<i class="text-success fa-solid fa-circle-check"></i> ' + _("Internal Server Error. Please check the server logs") );
870
                }
871
            });
872
        }
873
    </script>
874
[% END %]
875
876
[% INCLUDE 'intranet-bottom.inc' %]
(-)a/t/Koha/Auth/Permissions.t (+1 lines)
Lines 207-212 subtest 'superlibrarian tests' => sub { Link Here
207
        'CAN_user_parameters_manage_search_targets'                 => 1,
207
        'CAN_user_parameters_manage_search_targets'                 => 1,
208
        'CAN_user_parameters_manage_sms_providers'                  => 1,
208
        'CAN_user_parameters_manage_sms_providers'                  => 1,
209
        'CAN_user_parameters_manage_smtp_servers'                   => 1,
209
        'CAN_user_parameters_manage_smtp_servers'                   => 1,
210
        'CAN_user_parameters_manage_sftp_servers'                   => 1,
210
        'CAN_user_parameters_manage_sysprefs'                       => 1,
211
        'CAN_user_parameters_manage_sysprefs'                       => 1,
211
        'CAN_user_parameters_manage_transfers'                      => 1,
212
        'CAN_user_parameters_manage_transfers'                      => 1,
212
        'CAN_user_parameters_manage_usage_stats'                    => 1,
213
        'CAN_user_parameters_manage_usage_stats'                    => 1,
(-)a/t/db_dependent/Koha/SFTP/Server.t (+165 lines)
Line 0 Link Here
1
#!/usr/bin/perl
2
3
# This file is part of Koha.
4
#
5
# Koha is free software; you can redistribute it and/or modify it
6
# under the terms of the GNU General Public License as published by
7
# the Free Software Foundation; either version 3 of the License, or
8
# (at your option) any later version.
9
#
10
# Koha is distributed in the hope that it will be useful, but
11
# WITHOUT ANY WARRANTY; without even the implied warranty of
12
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
# GNU General Public License for more details.
14
#
15
# You should have received a copy of the GNU General Public License
16
# along with Koha; if not, see <http://www.gnu.org/licenses>.
17
18
use Modern::Perl;
19
20
use Test::More tests => 5;
21
use Test::Exception;
22
use Test::Warn;
23
24
use Koha::SFTP::Servers;
25
26
use t::lib::TestBuilder;
27
use t::lib::Mocks;
28
29
my $schema  = Koha::Database->new->schema;
30
my $builder = t::lib::TestBuilder->new;
31
32
subtest 'store() tests' => sub {
33
34
    plan tests => 2;
35
36
    $schema->storage->txn_begin;
37
38
    my $sftp_server = $builder->build_object(
39
        {
40
            class => 'Koha::SFTP::Servers',
41
            value => {
42
                password => undef,
43
                key_file => undef,
44
            },
45
        }
46
    );
47
    $sftp_server->set( { password => 'test123' } )->store;
48
49
    ok( $sftp_server->password ne 'test123', 'Password should not be in plain text' );
50
    is( length( $sftp_server->password ), 64, 'Password has should be 64 characters long' );
51
52
    $schema->storage->txn_rollback;
53
};
54
55
subtest 'to_api() tests' => sub {
56
57
    plan tests => 1;
58
59
    $schema->storage->txn_begin;
60
61
    my $sftp_server = $builder->build_object(
62
        {
63
            class => 'Koha::SFTP::Servers',
64
            value => {
65
                password => undef,
66
                key_file => undef,
67
            },
68
        }
69
    );
70
71
    ok( !exists $sftp_server->to_api->{password}, 'Password is not part of the API representation' );
72
73
    $schema->storage->txn_rollback;
74
};
75
76
subtest 'plain_text_password() tests' => sub {
77
78
    plan tests => 2;
79
80
    $schema->storage->txn_begin;
81
82
    my $sftp_server = $builder->build_object(
83
        {
84
            class => 'Koha::SFTP::Servers',
85
            value => {
86
                password => undef,
87
                key_file => undef,
88
            },
89
        }
90
    );
91
92
    $sftp_server->set( { password => 'test123' } )->store;
93
94
    my $sftp_server_plain_text_password = $sftp_server->plain_text_password;
95
96
    isnt( $sftp_server_plain_text_password, $sftp_server->password, 'Password and password hash shouldn\'t match' );
97
    is( $sftp_server_plain_text_password, 'test123', 'Password should be in plain text' );
98
99
    $schema->storage->txn_rollback;
100
};
101
102
subtest 'plain_text_key() tests' => sub {
103
104
    plan tests => 2;
105
106
    $schema->storage->txn_begin;
107
108
    my $sftp_server = $builder->build_object(
109
        {
110
            class => 'Koha::SFTP::Servers',
111
            value => {
112
                password => undef,
113
                key_file => undef,
114
            },
115
        }
116
    );
117
118
    $sftp_server->set( { key_file => '321tset' } )->store;
119
120
    my $sftp_server_plain_text_key = $sftp_server->plain_text_key;
121
122
    isnt( $sftp_server_plain_text_key, $sftp_server->key_file, 'Key file and key file hash shouldn\'t match' );
123
    is( $sftp_server_plain_text_key, "321tset\n", 'Key file should be in plain text' );
124
125
    $schema->storage->txn_rollback;
126
};
127
128
subtest 'write_key_file() tests' => sub {
129
130
    plan tests => 3;
131
132
    $schema->storage->txn_begin;
133
134
    my $sftp_server = $builder->build_object(
135
        {
136
            class => 'Koha::SFTP::Servers',
137
            value => {
138
                password => undef,
139
                key_file => undef,
140
            },
141
        }
142
    );
143
144
    $sftp_server->set( { key_file => '321tset' } )->store;
145
146
    my $path = '/tmp/kohadev_test';
147
    t::lib::Mocks::mock_config( 'upload_path', $path );
148
    mkdir $path if !-d $path;
149
150
    my $first_test = $sftp_server->write_key_file;
151
152
    my $file        = $sftp_server->locate_key_file;
153
    my $second_test = ( -f $file );
154
155
    open( my $fh, '<', $sftp_server->locate_key_file );
156
    my $third_test = <$fh>;
157
158
    is( $first_test,  1,           'Writing key file should return 1' );
159
    is( $second_test, 1,           'Written key file should exist' );
160
    is( $third_test,  "321tset\n", 'The contents of the key file should be 321tset\n' );
161
162
    unlink $file;
163
164
    $schema->storage->txn_rollback;
165
};
(-)a/t/db_dependent/api/v1/sftp_servers.t (-1 / +443 lines)
Line 0 Link Here
0
- 
1
#!/usr/bin/env perl
2
3
# This file is part of Koha.
4
#
5
# Koha is free software; you can redistribute it and/or modify it
6
# under the terms of the GNU General Public License as published by
7
# the Free Software Foundation; either version 3 of the License, or
8
# (at your option) any later version.
9
#
10
# Koha is distributed in the hope that it will be useful, but
11
# WITHOUT ANY WARRANTY; without even the implied warranty of
12
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
# GNU General Public License for more details.
14
#
15
# You should have received a copy of the GNU General Public License
16
# along with Koha; if not, see <http://www.gnu.org/licenses>.
17
18
use Modern::Perl;
19
20
use Test::More tests => 6;
21
use Test::Mojo;
22
23
use t::lib::TestBuilder;
24
use t::lib::Mocks;
25
26
use Koha::SFTP::Servers;
27
use Koha::Database;
28
29
my $schema  = Koha::Database->new->schema;
30
my $builder = t::lib::TestBuilder->new;
31
32
my $t = Test::Mojo->new('Koha::REST::V1');
33
t::lib::Mocks::mock_preference( 'RESTBasicAuth', 1 );
34
35
subtest 'list() tests' => sub {
36
37
    plan tests => 11;
38
39
    $schema->storage->txn_begin;
40
41
    Koha::SFTP::Servers->search->delete;
42
43
    my $librarian = $builder->build_object(
44
        {
45
            class => 'Koha::Patrons',
46
            value => { flags => 3**2 }    # parameters flag = 3
47
        }
48
    );
49
    my $password = 'thePassword123';
50
    $librarian->set_password( { password => $password, skip_validation => 1 } );
51
    my $userid = $librarian->userid;
52
53
    my $patron = $builder->build_object(
54
        {
55
            class => 'Koha::Patrons',
56
            value => { flags => 0 }
57
        }
58
    );
59
60
    $patron->set_password( { password => $password, skip_validation => 1 } );
61
    my $unauth_userid = $patron->userid;
62
63
    ## Authorized user tests
64
    # No FTP/SFTP servers, so empty array should be returned
65
    $t->get_ok("//$userid:$password@/api/v1/config/sftp_servers")->status_is(200)->json_is( [] );
66
67
    my $sftp_server = $builder->build_object(
68
        {
69
            class => 'Koha::SFTP::Servers',
70
            value => {
71
                password => undef,
72
                key_file => undef,
73
            },
74
        }
75
    );
76
77
    # One sftp server created, should get returned
78
    $t->get_ok("//$userid:$password@/api/v1/config/sftp_servers")->status_is(200)->json_is( [ $sftp_server->to_api ] );
79
80
    my $another_sftp_server = $builder->build_object(
81
        {
82
            class => 'Koha::SFTP::Servers',
83
            value => {
84
                password => undef,
85
                key_file => undef,
86
            },
87
        }
88
    );
89
90
    # Two FTP/SFTP servers created, they should both be returned
91
    $t->get_ok("//$userid:$password@/api/v1/config/sftp_servers")->status_is(200)
92
        ->json_is( [ $sftp_server->to_api, $another_sftp_server->to_api, ] );
93
94
    # Unauthorized access
95
    $t->get_ok("//$unauth_userid:$password@/api/v1/config/sftp_servers")->status_is(403);
96
97
    $schema->storage->txn_rollback;
98
};
99
100
subtest 'get() tests' => sub {
101
102
    plan tests => 8;
103
104
    $schema->storage->txn_begin;
105
106
    my $sftp_server = $builder->build_object(
107
        {
108
            class => 'Koha::SFTP::Servers',
109
            value => {
110
                password => undef,
111
                key_file => undef,
112
            },
113
        }
114
    );
115
    my $librarian = $builder->build_object(
116
        {
117
            class => 'Koha::Patrons',
118
            value => { flags => 3**2 }    # parameters flag = 3
119
        }
120
    );
121
    my $password = 'thePassword123';
122
    $librarian->set_password( { password => $password, skip_validation => 1 } );
123
    my $userid = $librarian->userid;
124
125
    my $patron = $builder->build_object(
126
        {
127
            class => 'Koha::Patrons',
128
            value => { flags => 0 }
129
        }
130
    );
131
132
    $patron->set_password( { password => $password, skip_validation => 1 } );
133
    my $unauth_userid = $patron->userid;
134
135
    $t->get_ok( "//$userid:$password@/api/v1/config/sftp_servers/" . $sftp_server->id )->status_is(200)
136
        ->json_is( $sftp_server->to_api );
137
138
    $t->get_ok( "//$unauth_userid:$password@/api/v1/config/sftp_servers/" . $sftp_server->id )->status_is(403);
139
140
    my $sftp_server_to_delete = $builder->build_object(
141
        {
142
            class => 'Koha::SFTP::Servers',
143
            value => {
144
                password => undef,
145
                key_file => undef,
146
            },
147
        }
148
    );
149
    my $non_existent_id = $sftp_server_to_delete->id;
150
    $sftp_server_to_delete->delete;
151
152
    $t->get_ok("//$userid:$password@/api/v1/config/sftp_servers/$non_existent_id")->status_is(404)
153
        ->json_is( '/error' => 'FTP/SFTP server not found' );
154
155
    $schema->storage->txn_rollback;
156
};
157
158
subtest 'add() tests' => sub {
159
160
    plan tests => 15;
161
162
    $schema->storage->txn_begin;
163
164
    Koha::SFTP::Servers->search->delete;
165
166
    my $librarian = $builder->build_object(
167
        {
168
            class => 'Koha::Patrons',
169
            value => { flags => 3**2 }    # parameters flag = 3
170
        }
171
    );
172
    my $password = 'thePassword123';
173
    $librarian->set_password( { password => $password, skip_validation => 1 } );
174
    my $userid = $librarian->userid;
175
176
    my $patron = $builder->build_object(
177
        {
178
            class => 'Koha::Patrons',
179
            value => { flags => 0 }
180
        }
181
    );
182
183
    $patron->set_password( { password => $password, skip_validation => 1 } );
184
    my $unauth_userid = $patron->userid;
185
186
    my $sftp_server = $builder->build_object(
187
        {
188
            class => 'Koha::SFTP::Servers',
189
            value => {
190
                password => undef,
191
                key_file => undef,
192
            },
193
        }
194
    );
195
    my $sftp_server_data = $sftp_server->to_api;
196
    delete $sftp_server_data->{sftp_server_id};
197
    $sftp_server->delete;
198
199
    # Unauthorized attempt to write
200
    $t->post_ok( "//$unauth_userid:$password@/api/v1/config/sftp_servers" => json => $sftp_server_data )
201
        ->status_is(403);
202
203
    # Authorized attempt to write invalid data
204
    my $sftp_server_with_invalid_field = {
205
        name => 'Some other server',
206
        blah => 'blah'
207
    };
208
209
    $t->post_ok( "//$userid:$password@/api/v1/config/sftp_servers" => json => $sftp_server_with_invalid_field )
210
        ->status_is(400)->json_is(
211
        "/errors" => [
212
            {
213
                message => "Properties not allowed: blah.",
214
                path    => "/body"
215
            }
216
        ]
217
        );
218
219
    # Authorized attempt to write
220
    my $sftp_server_id =
221
        $t->post_ok( "//$userid:$password@/api/v1/config/sftp_servers" => json => $sftp_server_data )
222
        ->status_is( 201, 'SWAGGER3.2.1' )
223
        ->header_like( Location => qr|^\/api\/v1\/config\/sftp_servers\/\d*|, 'SWAGGER3.4.1' )
224
        ->json_is( '/name' => $sftp_server_data->{name} )->tx->res->json->{sftp_server_id};
225
226
    # Authorized attempt to create with null id
227
    $sftp_server_data->{sftp_server_id} = undef;
228
    $t->post_ok( "//$userid:$password@/api/v1/config/sftp_servers" => json => $sftp_server_data )->status_is(400)
229
        ->json_has('/errors');
230
231
    # Authorized attempt to create with existing id
232
    $sftp_server_data->{sftp_server_id} = $sftp_server_id;
233
    $t->post_ok( "//$userid:$password@/api/v1/config/sftp_servers" => json => $sftp_server_data )->status_is(400)
234
        ->json_is(
235
        "/errors" => [
236
            {
237
                message => "Read-only.",
238
                path    => "/body/sftp_server_id"
239
            }
240
        ]
241
        );
242
243
    $schema->storage->txn_rollback;
244
};
245
246
subtest 'update() tests' => sub {
247
248
    plan tests => 15;
249
250
    $schema->storage->txn_begin;
251
252
    my $librarian = $builder->build_object(
253
        {
254
            class => 'Koha::Patrons',
255
            value => { flags => 3**2 }    # parameters flag = 3
256
        }
257
    );
258
    my $password = 'thePassword123';
259
    $librarian->set_password( { password => $password, skip_validation => 1 } );
260
    my $userid = $librarian->userid;
261
262
    my $patron = $builder->build_object(
263
        {
264
            class => 'Koha::Patrons',
265
            value => { flags => 0 }
266
        }
267
    );
268
269
    $patron->set_password( { password => $password, skip_validation => 1 } );
270
    my $unauth_userid = $patron->userid;
271
272
    my $sftp_server_id = $builder->build_object(
273
        {
274
            class => 'Koha::SFTP::Servers',
275
            value => {
276
                password => undef,
277
                key_file => undef,
278
            },
279
        }
280
    )->id;
281
282
    # Unauthorized attempt to update
283
    $t->put_ok( "//$unauth_userid:$password@/api/v1/config/sftp_servers/$sftp_server_id" => json =>
284
            { name => 'New unauthorized name change' } )->status_is(403);
285
286
    # Attempt partial update on a PUT
287
    my $sftp_server_with_missing_field = {
288
        host   => 'localhost',
289
        passiv => '1'
290
    };
291
292
    $t->put_ok(
293
        "//$userid:$password@/api/v1/config/sftp_servers/$sftp_server_id" => json => $sftp_server_with_missing_field )
294
        ->status_is(400)->json_is( "/errors" => [ { message => "Missing property.", path => "/body/name" } ] );
295
296
    # Full object update on PUT
297
    my $sftp_server_with_updated_field = {
298
        name     => "Some name",
299
        password => "some_pass",
300
    };
301
302
    $t->put_ok(
303
        "//$userid:$password@/api/v1/config/sftp_servers/$sftp_server_id" => json => $sftp_server_with_updated_field )
304
        ->status_is(200)->json_is( '/name' => 'Some name' );
305
306
    # Authorized attempt to write invalid data
307
    my $sftp_server_with_invalid_field = {
308
        blah => "Blah",
309
        name => 'Some name'
310
    };
311
312
    $t->put_ok(
313
        "//$userid:$password@/api/v1/config/sftp_servers/$sftp_server_id" => json => $sftp_server_with_invalid_field )
314
        ->status_is(400)->json_is(
315
        "/errors" => [
316
            {
317
                message => "Properties not allowed: blah.",
318
                path    => "/body"
319
            }
320
        ]
321
        );
322
323
    my $sftp_server_to_delete = $builder->build_object(
324
        {
325
            class => 'Koha::SFTP::Servers',
326
            value => {
327
                password => undef,
328
                key_file => undef,
329
            },
330
        }
331
    );
332
    my $non_existent_id = $sftp_server_to_delete->id;
333
    $sftp_server_to_delete->delete;
334
335
    $t->put_ok(
336
        "//$userid:$password@/api/v1/config/sftp_servers/$non_existent_id" => json => $sftp_server_with_updated_field )
337
        ->status_is(404);
338
339
    # Wrong method (POST)
340
    $sftp_server_with_updated_field->{sftp_server_id} = 2;
341
342
    $t->post_ok(
343
        "//$userid:$password@/api/v1/config/sftp_servers/$sftp_server_id" => json => $sftp_server_with_updated_field )
344
        ->status_is(404);
345
346
    $schema->storage->txn_rollback;
347
};
348
349
subtest 'delete() tests' => sub {
350
351
    plan tests => 7;
352
353
    $schema->storage->txn_begin;
354
355
    my $librarian = $builder->build_object(
356
        {
357
            class => 'Koha::Patrons',
358
            value => { flags => 3**2 }    # parameters flag = 3
359
        }
360
    );
361
    my $password = 'thePassword123';
362
    $librarian->set_password( { password => $password, skip_validation => 1 } );
363
    my $userid = $librarian->userid;
364
365
    my $patron = $builder->build_object(
366
        {
367
            class => 'Koha::Patrons',
368
            value => { flags => 0 }
369
        }
370
    );
371
372
    $patron->set_password( { password => $password, skip_validation => 1 } );
373
    my $unauth_userid = $patron->userid;
374
375
    my $sftp_server_id = $builder->build_object(
376
        {
377
            class => 'Koha::SFTP::Servers',
378
            value => {
379
                password => undef,
380
                key_file => undef,
381
            },
382
        }
383
    )->id;
384
385
    # Unauthorized attempt to delete
386
    $t->delete_ok("//$unauth_userid:$password@/api/v1/config/sftp_servers/$sftp_server_id")->status_is(403);
387
388
    $t->delete_ok("//$userid:$password@/api/v1/config/sftp_servers/$sftp_server_id")->status_is( 204, 'SWAGGER3.2.4' )
389
        ->content_is( '', 'SWAGGER3.3.4' );
390
391
    $t->delete_ok("//$userid:$password@/api/v1/config/sftp_servers/$sftp_server_id")->status_is(404);
392
393
    $schema->storage->txn_rollback;
394
};
395
396
subtest 'test() tests' => sub {
397
398
    plan tests => 5;
399
400
    $schema->storage->txn_begin;
401
402
    my $librarian = $builder->build_object(
403
        {
404
            class => 'Koha::Patrons',
405
            value => { flags => 3**2 }    # parameters flag = 3
406
        }
407
    );
408
    my $password = 'thePassword123';
409
    $librarian->set_password( { password => $password, skip_validation => 1 } );
410
    my $userid = $librarian->userid;
411
412
    my $patron = $builder->build_object(
413
        {
414
            class => 'Koha::Patrons',
415
            value => { flags => 0 }
416
        }
417
    );
418
419
    $patron->set_password( { password => $password, skip_validation => 1 } );
420
    my $unauth_userid = $patron->userid;
421
422
    my $sftp_server = $builder->build_object(
423
        {
424
            class => 'Koha::SFTP::Servers',
425
            value => {
426
                password => undef,
427
                key_file => undef,
428
            },
429
        }
430
    );
431
    my $sftp_server_id = $sftp_server->id;
432
433
    # Unauthorized attempt to test
434
    $t->get_ok("//$unauth_userid:$password@/api/v1/sftp_server/$sftp_server_id/test_connection")->status_is(403);
435
436
    $t->get_ok("//$userid:$password@/api/v1/sftp_server/$sftp_server_id/test_connection")
437
        ->status_is( 200, 'SWAGGER3.2.4' )
438
        ->content_is( '{"1_ftp_conn":{"err":"cannot connect to '
439
            . $sftp_server->host
440
            . ': Name or service not known","msg":null,"passed":false}}', 'SWAGGER3.3.4' );
441
442
    $schema->storage->txn_rollback;
443
};

Return to bug 35761