From 0bccb2d698ca00471c67a1ad3c80132def3611f3 Mon Sep 17 00:00:00 2001 From: Rudolf Byker Date: Thu, 1 Aug 2024 16:14:29 +0200 Subject: [PATCH] Bug 37543: Use CSRF tokens in connexion_import_daemon.pl Since version 24.05, due to the changes mentioned at https://wiki.koha-community.org/wiki/Koha_/svc/_HTTP_API#Changes_coming_in_Koha_24.05 , the `connexion_import_daemon.pl` stopped working. The reason for this is that it did not use CSRF tokens. To test: 1. Get a Koha instance on 24.05, before applying the patch. 2. Create a plain text file somewhere on the server containing a raw MARC record (not XML). Let's call it `marc.txt`. 3. On the server, create a config file like this: ``` host: 0.0.0.0 port: 5500 koha: http://localhost:82 # Where 82 is the port of the Koha staff interface. user: foo # A Koha staff user. password: Fooo1234 # The Koha staff user's password. import_mode: stage ``` 4. Run `./connexion_import_daemon.pl --config the-config-file-path` 5. In another terminal on the same server (or from anywhere that can reach the port opened by the `connexion_import_daemon.pl` script, run `nc localhost 5500 < marc.txt` 6. Observe in the stderr of the daemon script: `Response: Unsuccessful request` 7. Stop the daemon script. 8. Apply the patch and repeat steps 4 and 5. 9. Observe in the stderr of the daemon script: `Response: Success. Batch number ... - biblio record number HASH(...) added to Koha` 10. Check at /cgi-bin/koha/tools/manage-marc-import.pl for a batch named `(webservice)`. It should contain one record now. This is how we know that authentication between the daemon and Koha worked, which is what this patch tries to address. Thanks-to: David Cook Sponsored-by: Reformational Study Centre Signed-off-by: Nick Clemens --- misc/bin/connexion_import_daemon.pl | 72 +++++++++++++++++++---------- 1 file changed, 47 insertions(+), 25 deletions(-) diff --git a/misc/bin/connexion_import_daemon.pl b/misc/bin/connexion_import_daemon.pl index 40bff5d9653..a4754a8229d 100755 --- a/misc/bin/connexion_import_daemon.pl +++ b/misc/bin/connexion_import_daemon.pl @@ -104,7 +104,7 @@ use MARC::File::XML; use constant CLIENT_READ_TIMEOUT => 5; use constant CLIENT_READ_BUFFER_SIZE => 100000; -use constant AUTH_URI => "/cgi-bin/koha/mainpage.pl"; +use constant AUTH_URI => "/cgi-bin/koha/svc/authentication"; use constant IMPORT_SVC_URI => "/cgi-bin/koha/svc/import_bib"; sub new { @@ -239,6 +239,32 @@ sub _ua { return $ua; } +sub get_current_csrf_token { + my $self = shift; + my $ua = $self->{ua}; + my $url = $self->{koha} . AUTH_URI; + return $ua->get($url)->header('CSRF-TOKEN'); +} + +sub authenticate { + my $self = shift; + my $ua = $self->{ua}; + my $url = $self->{koha} . AUTH_URI; + my $resp = $ua->post( + $url, + { + login_userid => $self->{user}, + login_password => $self->{password}, + csrf_token => $self->get_current_csrf_token, + } + ); + if ( !$resp->is_success ) { + $self->log("Authentication failed", $resp->request->as_string, $resp->as_string); + return; + } + return $resp->header('CSRF-TOKEN'); +} + sub read_request { my ( $self, $io ) = @_; @@ -355,38 +381,34 @@ sub handle_request { } my $base_url = $self->{koha}; + my $post_body = { + 'nomatch_action' => $self->{params}->{nomatch_action}, + 'overlay_action' => $self->{params}->{overlay_action}, + 'match' => $self->{params}->{match}, + 'import_mode' => $self->{params}->{import_mode}, + 'framework' => $self->{params}->{framework}, + 'overlay_framework' => $self->{params}->{overlay_framework}, + 'item_action' => $self->{params}->{item_action}, + 'xml' => $data + }; + + # If we have a token, try it, else, authenticate for the first time. + $self->{csrf_token} = $self->authenticate unless $self->{csrf_token}; my $resp = $ua->post( $base_url . IMPORT_SVC_URI, - { - 'nomatch_action' => $self->{params}->{nomatch_action}, - 'overlay_action' => $self->{params}->{overlay_action}, - 'match' => $self->{params}->{match}, - 'import_mode' => $self->{params}->{import_mode}, - 'framework' => $self->{params}->{framework}, - 'overlay_framework' => $self->{params}->{overlay_framework}, - 'item_action' => $self->{params}->{item_action}, - 'xml' => $data - } + $post_body, + csrf_token => $self->{csrf_token}, ); my $status = $resp->code; if ( $status == HTTP_UNAUTHORIZED || $status == HTTP_FORBIDDEN ) { - my $user = $self->{user}; - my $password = $self->{password}; - $resp = $ua->post( $base_url . AUTH_URI, { userid => $user, password => $password } ); + # Our token might have expired. Re-authenticate and post again. + $self->{csrf_token} = $self->authenticate; $resp = $ua->post( $base_url . IMPORT_SVC_URI, - { - 'nomatch_action' => $self->{params}->{nomatch_action}, - 'overlay_action' => $self->{params}->{overlay_action}, - 'match' => $self->{params}->{match}, - 'import_mode' => $self->{params}->{import_mode}, - 'framework' => $self->{params}->{framework}, - 'overlay_framework' => $self->{params}->{overlay_framework}, - 'item_action' => $self->{params}->{item_action}, - 'xml' => $data - } - ) if $resp->is_success; + $post_body, + csrf_token => $self->{csrf_token}, + ) } unless ($resp->is_success) { $self->log("Unsuccessful request", $resp->request->as_string, $resp->as_string); -- 2.39.2