From 1587d4e828afc1ebdbbe63556fa96aa90b9d9836 Mon Sep 17 00:00:00 2001 From: Lari Taskula Date: Wed, 18 Sep 2024 08:24:47 +0000 Subject: [PATCH] Bug 28907: Add Koha::REST::Plugin::Auth::PublicRoutes To test: 1. prove t/db_dependent/Koha/REST/Plugin/Auth/PublicRoutes.t Signed-off-by: Victor Grousset/tuxayo --- Koha/REST/Plugin/Auth/PublicRoutes.pm | 119 ++++++++++++++ Koha/REST/V1.pm | 1 + .../Koha/REST/Plugin/Auth/PublicRoutes.t | 145 ++++++++++++++++++ 3 files changed, 265 insertions(+) create mode 100644 Koha/REST/Plugin/Auth/PublicRoutes.pm create mode 100755 t/db_dependent/Koha/REST/Plugin/Auth/PublicRoutes.t diff --git a/Koha/REST/Plugin/Auth/PublicRoutes.pm b/Koha/REST/Plugin/Auth/PublicRoutes.pm new file mode 100644 index 0000000000..c0c067fd2d --- /dev/null +++ b/Koha/REST/Plugin/Auth/PublicRoutes.pm @@ -0,0 +1,119 @@ +package Koha::REST::Plugin::Auth::PublicRoutes; + +# Copyright Hypernova Oy 2024 +# +# This file is part of Koha. +# +# Koha is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# Koha is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Koha; if not, see . + +use Modern::Perl; + +use Mojo::Base 'Mojolicious::Plugin'; + +use Koha::Exception; +use Koha::Exceptions; +use Koha::Exceptions::REST; +use Koha::Patrons; + +use Module::Load::Conditional; + +=head1 NAME + +Koha::REST::Plugin::Auth::PublicRoutes + +=head1 API + +=head2 Mojolicious::Plugin methods + +=head3 register + +=cut + +sub register { + my ( $self, $app ) = @_; + +=head2 Helper methods + +=head3 auth.public + + $c->auth->public( $public_user_id ); + +=cut + + $app->helper( + 'auth.public' => sub { + my ( $c, $public_user_id ) = @_; + + unless ( $c->stash('is_public') ) { + Koha::Exception->throw( error => "This is not a public route!" ); + } + + my $user = $c->stash('koha.user'); + + unless ($user) { + Koha::Exceptions::REST::Public::Authentication::Required->throw( error => "Authentication failure." ); + } + + unless ($public_user_id) { + Koha::Exceptions::REST::Public::Unauthorized->throw( + error => "Unprivileged user cannot access another user's resources" ); + } + + if ( $user->borrowernumber == $public_user_id ) { + return 1; + } else { + Koha::Exceptions::REST::Public::Unauthorized->throw( + error => "Unprivileged user cannot access another user's resources" ); + } + } + ); + +=head3 auth.public_guarantor + + $c->auth->public_guarantor( $public_user_id ); + +=cut + + $app->helper( + 'auth.public_guarantor' => sub { + my ( $c, $public_user_id ) = @_; + + unless ( $c->stash('is_public') ) { + Koha::Exception->throw( error => "This is not a public route!" ); + } + + my $user = $c->stash('koha.user'); + + unless ($user) { + Koha::Exceptions::REST::Public::Authentication::Required->throw( error => "Authentication failure." ); + } + + unless ($public_user_id) { + Koha::Exceptions::REST::Public::Unauthorized->throw( + error => "Unprivileged user cannot access another user's resources" ); + } + + my $guarantees = $user->guarantee_relationships->guarantees->as_list; + foreach my $guarantee ( @{$guarantees} ) { + if ( $guarantee->borrowernumber == $public_user_id ) { + return 1; + } + } + Koha::Exceptions::REST::Public::Unauthorized->throw( + error => "Unprivileged user cannot access another user's resources" ); + } + ); +} + +1; diff --git a/Koha/REST/V1.pm b/Koha/REST/V1.pm index 52bd15697e..7695de9c40 100644 --- a/Koha/REST/V1.pm +++ b/Koha/REST/V1.pm @@ -159,6 +159,7 @@ sub startup { $self->plugin('Koha::REST::Plugin::Exceptions'); $self->plugin('Koha::REST::Plugin::Responses'); $self->plugin('Koha::REST::Plugin::Auth::IdP'); + $self->plugin('Koha::REST::Plugin::Auth::PublicRoutes'); $self->plugin( 'Mojolicious::Plugin::OAuth2' => $oauth_configuration ); } diff --git a/t/db_dependent/Koha/REST/Plugin/Auth/PublicRoutes.t b/t/db_dependent/Koha/REST/Plugin/Auth/PublicRoutes.t new file mode 100755 index 0000000000..991a8c1597 --- /dev/null +++ b/t/db_dependent/Koha/REST/Plugin/Auth/PublicRoutes.t @@ -0,0 +1,145 @@ +#!/usr/bin/perl + +# Copyright 2024 Hypernova Oy +# +# This file is part of Koha +# +# Koha is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# Koha is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Koha; if not, see . + +use Modern::Perl; + +use Koha::Patrons; +use Try::Tiny; + +# Dummy app for testing the plugin +use Mojolicious::Lite; + +plugin 'Koha::REST::Plugin::Auth::PublicRoutes'; + +post '/public' => sub { + my $c = shift; + my $params = $c->req->json; + + $c->stash( 'koha.user' => Koha::Patrons->find( $params->{'user'} ) ); + $c->stash( 'is_public' => 1 ); + + try { + my $patron_id = $params->{'patron_id'}; + $c->auth->public($patron_id); + $c->render( status => 200, json => "OK" ); + } catch { + if ( ref($_) eq 'Koha::Exceptions::REST::Public::Authentication::Required' ) { + $c->render( status => 401, json => { message => 'authentication_required' } ); + } elsif ( ref($_) eq 'Koha::Exceptions::REST::Public::Unauthorized' ) { + $c->render( status => 403, json => { message => 'unauthorized' } ); + } else { + $c->render( status => 500, json => { message => 'other error' } ); + } + } +}; + +post '/public_guarantor' => sub { + my $c = shift; + my $params = $c->req->json; + + $c->stash( 'koha.user' => Koha::Patrons->find( $params->{'user'} ) ); + $c->stash( 'is_public' => 1 ); + + try { + my $patron_id = $params->{'patron_id'}; + $c->auth->public_guarantor($patron_id); + $c->render( status => 200, json => "OK" ); + } catch { + if ( ref($_) eq 'Koha::Exceptions::REST::Public::Authentication::Required' ) { + $c->render( status => 401, json => { message => 'authentication_required' } ); + } elsif ( ref($_) eq 'Koha::Exceptions::REST::Public::Unauthorized' ) { + $c->render( status => 403, json => { message => 'unauthorized' } ); + } else { + $c->render( status => 500, json => { message => 'other error' } ); + } + } +}; + +use Test::More tests => 2; +use Test::Mojo; + +use t::lib::Mocks; +use t::lib::TestBuilder; +use Koha::Database; + +my $schema = Koha::Database->new()->schema(); +my $builder = t::lib::TestBuilder->new; + +# FIXME: sessionStorage defaults to mysql, but it seems to break transaction handling +# this affects the other REST api tests +t::lib::Mocks::mock_preference( 'SessionStorage', 'tmp' ); + +subtest 'auth.public helper' => sub { + plan tests => 9; + + $schema->storage->txn_begin; + + my $unprivileged_patron = $builder->build_object( { class => 'Koha::Patrons' } ); + my $another_patron = $builder->build_object( { class => 'Koha::Patrons' } ); + + my $t = Test::Mojo->new; + + $t->post_ok( '/public' => json => { patron_id => $another_patron->borrowernumber } )->status_is(401) + ->json_is( { message => 'authentication_required' } ); + + $t->post_ok( '/public' => json => + { user => $unprivileged_patron->borrowernumber, patron_id => $another_patron->borrowernumber } ) + ->status_is(403)->json_is( { message => 'unauthorized' } ); + + $t->post_ok( '/public' => json => + { user => $unprivileged_patron->borrowernumber, patron_id => $unprivileged_patron->borrowernumber } ) + ->status_is(200)->json_is('OK'); + + $schema->storage->txn_rollback; +}; + +subtest 'auth.public_guarantor helper' => sub { + plan tests => 12; + + $schema->storage->txn_begin; + + t::lib::Mocks::mock_preference( 'borrowerRelationship', 'test' ); + + my $guarantee = $builder->build_object( { class => 'Koha::Patrons' } ); + my $guarantor = $builder->build_object( { class => 'Koha::Patrons' } ); + $guarantee->add_guarantor( { guarantor_id => $guarantor->borrowernumber, relationship => 'test' } ); + my $another_patron = $builder->build_object( { class => 'Koha::Patrons' } ); + + my $t = Test::Mojo->new; + + $t->post_ok( '/public_guarantor' => json => { patron_id => $another_patron->borrowernumber } )->status_is(401) + ->json_is( { message => 'authentication_required' } ); + + $t->post_ok( '/public_guarantor' => json => + { user => $guarantor->borrowernumber, patron_id => $another_patron->borrowernumber } )->status_is(403) + ->json_is( { message => 'unauthorized' } ); + + # user is not a guarantor of themself + $t->post_ok( + '/public_guarantor' => json => { user => $guarantor->borrowernumber, patron_id => $guarantor->borrowernumber } ) + ->status_is(403)->json_is( { message => 'unauthorized' } ); + + $t->post_ok( + '/public_guarantor' => json => { user => $guarantor->borrowernumber, patron_id => $guarantee->borrowernumber } ) + ->status_is(200)->json_is('OK'); + + $schema->storage->txn_rollback; +}; + +1; -- 2.46.2