From 510c389c4fc241a7f170e1c85079e46cc36fb01a Mon Sep 17 00:00:00 2001 From: Jacob O'Mara Date: Tue, 4 Nov 2025 15:22:22 +0000 Subject: [PATCH] Bug 39224: add comprehensive tests for shibboleth configuration Add tests for: - Koha object models (ShibbolethConfig, ShibbolethFieldMapping) - REST API endpoints - Database migration from XML config --- t/db_dependent/Auth_with_shibboleth.t | 60 ++- t/db_dependent/Koha/ShibbolethConfigs.t | 140 +++++++ t/db_dependent/Koha/ShibbolethFieldMappings.t | 179 +++++++++ t/db_dependent/Shibboleth/Migration.t | 206 ++++++++++ t/db_dependent/api/v1/shibboleth_config.t | 121 ++++++ .../api/v1/shibboleth_field_mappings.t | 356 ++++++++++++++++++ 6 files changed, 1048 insertions(+), 14 deletions(-) create mode 100755 t/db_dependent/Koha/ShibbolethConfigs.t create mode 100755 t/db_dependent/Koha/ShibbolethFieldMappings.t create mode 100755 t/db_dependent/Shibboleth/Migration.t create mode 100755 t/db_dependent/api/v1/shibboleth_config.t create mode 100755 t/db_dependent/api/v1/shibboleth_field_mappings.t diff --git a/t/db_dependent/Auth_with_shibboleth.t b/t/db_dependent/Auth_with_shibboleth.t index 01a229d1929..26cb99fa6f8 100755 --- a/t/db_dependent/Auth_with_shibboleth.t +++ b/t/db_dependent/Auth_with_shibboleth.t @@ -35,6 +35,9 @@ use t::lib::Dates; use C4::Auth_with_shibboleth qw( shib_ok login_shib_url get_login_shib checkpw_shib ); use Koha::Database; use Koha::DateUtils qw( dt_from_string ); +use Koha::ShibbolethConfig; +use Koha::ShibbolethConfigs; +use Koha::ShibbolethFieldMapping; BEGIN { use_ok( @@ -48,9 +51,7 @@ $schema->storage->txn_begin; my $builder = t::lib::TestBuilder->new; my $logger = t::lib::Mocks::Logger->new(); -# Mock variables my $shibboleth; -change_config( {} ); my $interface = 'opac'; # Mock few preferences @@ -59,12 +60,13 @@ t::lib::Mocks::mock_preference( 'StaffClientBaseURL', 'teststaff.com' ); t::lib::Mocks::mock_preference( 'EmailFieldPrimary', '' ); t::lib::Mocks::mock_preference( 'EmailFieldPrecedence', 'emailpro' ); -# Mock Context: config, tz and interface +# Mock Context: tz and interface my $context = Test::MockModule->new('C4::Context'); -$context->mock( 'config', sub { return $shibboleth; } ); # easier than caching by Mocks::mock_config $context->mock( 'timezone', sub { return 'local'; } ); $context->mock( 'interface', sub { return $interface; } ); +change_config( {} ); + # Mock Letters: GetPreparedLetter, EnqueueLetter and SendQueuedMessages # We want to test the params my $mocked_letters = Test::MockModule->new('C4::Letters'); @@ -97,20 +99,21 @@ $mocked_letters->mock( # Start testing ---------------------------------------------------------------- subtest "shib_ok tests" => sub { - plan tests => 5; + plan tests => 6; my $result; # correct config, no debug is( shib_ok(), '1', "good config" ); - # bad config, no debug + # bad config, no debug - clear matchpoint in DB + $schema->resultset('ShibbolethFieldMapping')->update( { is_matchpoint => 0 } ); delete $shibboleth->{matchpoint}; - warnings_are { $result = shib_ok() } - [ { carped => 'shibboleth matchpoint not defined' }, ], - "undefined matchpoint = fatal config, warning given"; + $result = shib_ok(); + $logger->warn_is( 'No matchpoint configured in Shibboleth field mappings', "matchpoint warning logged" ) + ->warn_is( 'shibboleth config could not be loaded', "config could not be loaded warning" )->clear(); is( $result, '0', "bad config" ); - change_config( { matchpoint => 'email' } ); + change_config( { matchpoint => 'email', mapping => { email => { content => 'default@example.com' } } } ); warnings_are { $result = shib_ok() } [ { carped => 'shibboleth matchpoint not mapped' }, ], "unmapped matchpoint = fatal config, warning given"; @@ -273,6 +276,8 @@ subtest "checkpw_shib tests" => sub { ); # sync user + my $config = Koha::ShibbolethConfigs->new->get_configuration; + $config->sync(1)->store; $shibboleth->{sync} = 1; $ENV{'city'} = 'AnotherCity'; ( $retval, $retcard, $retuserid, $retpatron ) = checkpw_shib($shib_login); @@ -327,7 +332,7 @@ subtest "checkpw_shib tests" => sub { $ENV{'emailpro'} = 'me@myemail.com'; $ENV{branchcode} = $library->branchcode; # needed since T::D::C does no longer hides the FK constraint - checkpw($shib_login); + ( $retval, $retcard, $retuserid, $retpatron ) = checkpw_shib($shib_login); ok( my $new_user_autocreated = Koha::Patrons->find( { userid => 'test43210' } ), "new user found" @@ -416,6 +421,19 @@ $schema->storage->txn_rollback; sub change_config { my $params = shift; + $schema->resultset('ShibbolethConfig')->delete; + $schema->resultset('ShibbolethFieldMapping')->delete; + + Koha::ShibbolethConfig->new( + { + force_opac_sso => 0, + force_staff_sso => 0, + autocreate => $params->{autocreate} // 0, + sync => $params->{sync} // 0, + welcome => $params->{welcome} // 0, + } + )->store; + my %mapping = ( 'userid' => { 'is' => 'uid' }, 'surname' => { 'is' => 'sn' }, @@ -429,15 +447,29 @@ sub change_config { if ( exists $params->{mapping} ) { $mapping{$_} = $params->{mapping}->{$_} for keys %{ $params->{mapping} }; } - $shibboleth = { + + my $matchpoint = $params->{matchpoint} // 'userid'; + + foreach my $koha_field ( keys %mapping ) { + Koha::ShibbolethFieldMapping->new( + { + koha_field => $koha_field, + idp_field => $mapping{$koha_field}->{is}, + is_matchpoint => $koha_field eq $matchpoint ? 1 : 0, + default_content => $mapping{$koha_field}->{content}, + } + )->store; + } + + my $db_config = Koha::ShibbolethConfigs->new->get_configuration; + $shibboleth = $db_config ? $db_config->get_combined_config : { autocreate => $params->{autocreate} // 0, welcome => $params->{welcome} // 0, sync => $params->{sync} // 0, - matchpoint => $params->{matchpoint} // 'userid', + matchpoint => $matchpoint, mapping => \%mapping, }; - # Change environment too $ENV{'uid'} = "test1234"; $ENV{'sn'} = undef; $ENV{'exp'} = undef; diff --git a/t/db_dependent/Koha/ShibbolethConfigs.t b/t/db_dependent/Koha/ShibbolethConfigs.t new file mode 100755 index 00000000000..6ef25ee220e --- /dev/null +++ b/t/db_dependent/Koha/ShibbolethConfigs.t @@ -0,0 +1,140 @@ +#!/usr/bin/perl + +# This file is part of Koha +# +# Koha is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# Koha is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Koha; if not, see . + +use Modern::Perl; + +use Test::NoWarnings; +use Test::More tests => 6; + +use Koha::ShibbolethConfig; +use Koha::ShibbolethConfigs; +use Koha::Database; + +use t::lib::TestBuilder; + +my $schema = Koha::Database->new->schema; +$schema->storage->txn_begin; + +my $builder = t::lib::TestBuilder->new; + +subtest 'get_configuration' => sub { + plan tests => 3; + + $schema->resultset('ShibbolethConfig')->delete; + + my $config = Koha::ShibbolethConfigs->new->get_configuration; + + is( $config->shibboleth_config_id, 1, 'Configuration always has ID 1' ); + is( $config->force_opac_sso, 0, 'Default force_opac_sso is 0' ); + is( $config->autocreate, 0, 'Default autocreate is 0' ); +}; + +subtest 'store always updates singleton' => sub { + plan tests => 3; + + $schema->resultset('ShibbolethConfig')->delete; + + my $config1 = Koha::ShibbolethConfig->new( + { + force_opac_sso => 1, + force_staff_sso => 0, + autocreate => 1, + sync => 1, + welcome => 0 + } + )->store; + + is( $config1->shibboleth_config_id, 1, 'First config has ID 1' ); + + my $config2 = Koha::ShibbolethConfig->new( + { + force_opac_sso => 0, + force_staff_sso => 1, + autocreate => 0, + sync => 0, + welcome => 1 + } + )->store; + + is( $config2->shibboleth_config_id, 1, 'Second store still has ID 1' ); + is( Koha::ShibbolethConfigs->search->count, 1, 'Only one config exists' ); +}; + +subtest 'get_value' => sub { + plan tests => 3; + + $schema->resultset('ShibbolethConfig')->delete; + + my $config = Koha::ShibbolethConfigs->new->get_configuration; + $config->force_opac_sso(1)->store; + + is( $config->get_value('force_opac_sso'), 1, 'get_value returns correct value' ); + is( $config->get_value('force_staff_sso'), 0, 'get_value returns correct default' ); + is( $config->get_value('nonexistent'), undef, 'get_value returns undef for nonexistent column' ); +}; + +subtest 'mappings' => sub { + plan tests => 1; + + $schema->resultset('ShibbolethConfig')->delete; + + my $config = Koha::ShibbolethConfigs->new->get_configuration; + my $mappings = $config->mappings; + + isa_ok( $mappings, 'Koha::ShibbolethFieldMappings', 'mappings returns correct type' ); +}; + +subtest 'get_combined_config' => sub { + plan tests => 4; + + $schema->resultset('ShibbolethConfig')->delete; + $schema->resultset('ShibbolethFieldMapping')->delete; + + my $config = Koha::ShibbolethConfigs->new->get_configuration; + $config->force_opac_sso(1)->store; + + $builder->build_object( + { + class => 'Koha::ShibbolethFieldMappings', + value => { + idp_field => 'eppn', + koha_field => 'userid', + is_matchpoint => 1 + } + } + ); + + $builder->build_object( + { + class => 'Koha::ShibbolethFieldMappings', + value => { + idp_field => 'mail', + koha_field => 'email', + is_matchpoint => 0 + } + } + ); + + my $combined = $config->get_combined_config; + + is( $combined->{force_opac_sso}, 1, 'Combined config includes base settings' ); + is( $combined->{matchpoint}, 'userid', 'Combined config includes matchpoint' ); + ok( exists $combined->{mapping}->{userid}, 'Combined config includes userid mapping' ); + ok( exists $combined->{mapping}->{email}, 'Combined config includes email mapping' ); +}; + +$schema->storage->txn_rollback; diff --git a/t/db_dependent/Koha/ShibbolethFieldMappings.t b/t/db_dependent/Koha/ShibbolethFieldMappings.t new file mode 100755 index 00000000000..37abc66c3c9 --- /dev/null +++ b/t/db_dependent/Koha/ShibbolethFieldMappings.t @@ -0,0 +1,179 @@ +#!/usr/bin/perl + +# This file is part of Koha +# +# Koha is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# Koha is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Koha; if not, see . + +use Modern::Perl; + +use Test::NoWarnings; +use Test::More tests => 6; +use Test::Exception; + +use Koha::ShibbolethFieldMapping; +use Koha::ShibbolethFieldMappings; +use Koha::Database; + +use t::lib::TestBuilder; + +my $schema = Koha::Database->new->schema; +$schema->storage->txn_begin; + +my $builder = t::lib::TestBuilder->new; + +subtest 'basic CRUD operations' => sub { + plan tests => 4; + + $schema->resultset('ShibbolethFieldMapping')->delete; + + my $nb_of_mappings = Koha::ShibbolethFieldMappings->search->count; + my $new_mapping_1 = Koha::ShibbolethFieldMapping->new( + { + idp_field => 'eppn', + koha_field => 'userid', + is_matchpoint => 1, + } + )->store; + + like( $new_mapping_1->mapping_id, qr|^\d+$|, 'Adding a new mapping should set the mapping_id' ); + is( Koha::ShibbolethFieldMappings->search->count, $nb_of_mappings + 1, 'The mapping should have been added' ); + + my $retrieved_mapping_1 = Koha::ShibbolethFieldMappings->find( $new_mapping_1->mapping_id ); + is( + $retrieved_mapping_1->koha_field, $new_mapping_1->koha_field, + 'Find a mapping by id should return the correct mapping' + ); + + $retrieved_mapping_1->delete; + is( Koha::ShibbolethFieldMappings->search->count, $nb_of_mappings, 'Delete should have deleted the mapping' ); +}; + +subtest 'store validation' => sub { + plan tests => 2; + + $schema->resultset('ShibbolethFieldMapping')->delete; + + throws_ok { + Koha::ShibbolethFieldMapping->new( + { + idp_field => 'eppn', + } + )->store; + } + 'Koha::Exceptions::MissingParameter', 'Store without koha_field throws exception'; + + throws_ok { + Koha::ShibbolethFieldMapping->new( + { + koha_field => 'userid', + } + )->store; + } + 'Koha::Exceptions::MissingParameter', 'Store without idp_field throws exception'; +}; + +subtest 'ensure_single_matchpoint' => sub { + plan tests => 3; + + $schema->resultset('ShibbolethFieldMapping')->delete; + + my $mapping1 = Koha::ShibbolethFieldMapping->new( + { + idp_field => 'eppn', + koha_field => 'userid', + is_matchpoint => 1 + } + )->store; + + my $mapping2 = Koha::ShibbolethFieldMapping->new( + { + idp_field => 'mail', + koha_field => 'email', + is_matchpoint => 1 + } + )->store; + + $mapping1->discard_changes; + is( $mapping1->is_matchpoint, 0, 'First matchpoint was cleared' ); + is( $mapping2->is_matchpoint, 1, 'Second matchpoint remains set' ); + + my $matchpoint_count = Koha::ShibbolethFieldMappings->search( { is_matchpoint => 1 } )->count; + is( $matchpoint_count, 1, 'Only one matchpoint exists' ); +}; + +subtest 'get_matchpoint' => sub { + plan tests => 2; + + $schema->resultset('ShibbolethFieldMapping')->delete; + + my $mapping = $builder->build_object( + { + class => 'Koha::ShibbolethFieldMappings', + value => { + idp_field => 'eppn', + koha_field => 'userid', + is_matchpoint => 1 + } + } + ); + + my $matchpoint = Koha::ShibbolethFieldMappings->new->get_matchpoint; + is( $matchpoint->mapping_id, $mapping->mapping_id, 'get_matchpoint returns correct mapping' ); + + $mapping->is_matchpoint(0)->store; + $matchpoint = Koha::ShibbolethFieldMappings->new->get_matchpoint; + is( $matchpoint, undef, 'get_matchpoint returns undef when no matchpoint exists' ); +}; + +subtest 'get_mapping_config' => sub { + plan tests => 6; + + $schema->resultset('ShibbolethFieldMapping')->delete; + + my ( $success, $config ) = Koha::ShibbolethFieldMappings->new->get_mapping_config; + is( $success, 0, 'get_mapping_config returns failure when no matchpoint' ); + is( $config, undef, 'get_mapping_config returns undef config when no matchpoint' ); + + $builder->build_object( + { + class => 'Koha::ShibbolethFieldMappings', + value => { + idp_field => 'eppn', + koha_field => 'userid', + is_matchpoint => 1, + default_content => undef + } + } + ); + + $builder->build_object( + { + class => 'Koha::ShibbolethFieldMappings', + value => { + idp_field => 'mail', + koha_field => 'email', + is_matchpoint => 0, + default_content => undef + } + } + ); + + ( $success, $config ) = Koha::ShibbolethFieldMappings->new->get_mapping_config; + is( $success, 1, 'get_mapping_config returns success with valid mappings' ); + is( $config->{matchpoint}, 'userid', 'Matchpoint field is correct' ); + is( $config->{mapping}->{userid}->{is}, 'eppn', 'userid mapping is correct' ); + is( $config->{mapping}->{email}->{is}, 'mail', 'email mapping is correct' ); +}; + +$schema->storage->txn_rollback; diff --git a/t/db_dependent/Shibboleth/Migration.t b/t/db_dependent/Shibboleth/Migration.t new file mode 100755 index 00000000000..9c46e91b081 --- /dev/null +++ b/t/db_dependent/Shibboleth/Migration.t @@ -0,0 +1,206 @@ +use Modern::Perl; +use Test::More tests => 4; +use Test::NoWarnings; + +use t::lib::TestBuilder; +use File::Temp qw(tempfile); +use XML::Simple; + +use C4::Installer qw( TableExists run_atomic_updates ); +use Koha::Database; +use Koha::ShibbolethConfigs; +use Koha::ShibbolethFieldMappings; + +my $schema = Koha::Database->new->schema; + +subtest 'Migration from XML config' => sub { + plan tests => 15; + + $schema->storage->txn_begin; + + my $dbh = C4::Context->dbh; + + Koha::ShibbolethConfigs->search()->delete; + Koha::ShibbolethFieldMappings->search()->delete; + $dbh->do( + "DELETE FROM systempreferences WHERE variable IN ('ShibbolethAuthentication', 'staffShibOnly', 'OPACShibOnly')" + ); + + my $xml_content = <<'EOT'; + + + + 1 + + 1 + 1 + 1 + email + + + + + + + + + + + + + + +EOT + + my ( $fh, $filename ) = tempfile(); + print $fh $xml_content; + close $fh; + + $schema->resultset('Systempreference')->update_or_create( + { + variable => 'staffShibOnly', + value => 1, + explanation => 'If ON enables shibboleth only authentication for the staff client', + type => 'YesNo' + } + ); + + $schema->resultset('Systempreference')->update_or_create( + { + variable => 'OPACShibOnly', + value => 1, + explanation => 'If ON enables shibboleth only authentication for the opac', + type => 'YesNo' + } + ); + + local $ENV{KOHA_CONF} = $filename; + + run_atomic_updates( ['bug_39224_add_shibboleth_tables.pl'] ); + + ok( $dbh->selectrow_array(q{SHOW TABLES LIKE 'shibboleth_config'}), "shibboleth_config table exists" ); + ok( + $dbh->selectrow_array(q{SHOW TABLES LIKE 'shibboleth_field_mappings'}), + "shibboleth_field_mappings table exists" + ); + + my ($syspref_value) = + $dbh->selectrow_array("SELECT value FROM systempreferences WHERE variable = 'ShibbolethAuthentication'"); + is( $syspref_value, 1, "ShibbolethAuthentication syspref created with correct value" ); + + my $config = $dbh->selectrow_hashref("SELECT * FROM shibboleth_config"); + ok( $config, "Config row created" ); + is( $config->{force_opac_sso}, 1, "OPACShibOnly migrated correctly" ); + is( $config->{force_staff_sso}, 1, "staffShibOnly migrated correctly" ); + is( $config->{autocreate}, 1, "autocreate setting migrated" ); + is( $config->{sync}, 1, "sync setting migrated" ); + is( $config->{welcome}, 1, "welcome setting migrated" ); + + my $old_prefs = $dbh->selectall_arrayref( + "SELECT variable FROM systempreferences WHERE variable IN ('staffShibOnly','OPACShibOnly')"); + is( scalar @$old_prefs, 0, "Old sysprefs were removed" ); + + my @mappings = Koha::ShibbolethFieldMappings->search()->as_list; + is( scalar @mappings, 9, "All 9 field mappings migrated" ); + + my $email_mapping = Koha::ShibbolethFieldMappings->find( { koha_field => 'email' } ); + is( $email_mapping->idp_field, 'mail', "email mapping IDP field correct" ); + is( $email_mapping->is_matchpoint, 1, "email is matchpoint" ); + + my $branchcode_mapping = Koha::ShibbolethFieldMappings->find( { koha_field => 'branchcode' } ); + is( $branchcode_mapping->default_content, 'MAIN', "branchcode default content correct" ); + is( $branchcode_mapping->idp_field, undef, "branchcode has no IDP field" ); + + $schema->storage->txn_rollback; +}; + +subtest 'Idempotency - running migration twice' => sub { + plan tests => 5; + + $schema->storage->txn_begin; + + my $dbh = C4::Context->dbh; + Koha::ShibbolethConfigs->search()->delete; + Koha::ShibbolethFieldMappings->search()->delete; + $dbh->do("DELETE FROM systempreferences WHERE variable = 'ShibbolethAuthentication'"); + + my $xml_content = <<'EOT'; + + + + 1 + + 1 + 1 + 1 + email + + + + + + + +EOT + + my ( $fh, $filename ) = tempfile(); + print $fh $xml_content; + close $fh; + + local $ENV{KOHA_CONF} = $filename; + + run_atomic_updates( ['bug_39224_add_shibboleth_tables.pl'] ); + + my $config_count_before = Koha::ShibbolethConfigs->count; + my $mapping_count_before = Koha::ShibbolethFieldMappings->count; + my ($syspref_count_before) = + $dbh->selectrow_array("SELECT COUNT(*) FROM systempreferences WHERE variable = 'ShibbolethAuthentication'"); + + run_atomic_updates( ['bug_39224_add_shibboleth_tables.pl'] ); + + my $config_count_after = Koha::ShibbolethConfigs->count; + my $mapping_count_after = Koha::ShibbolethFieldMappings->count; + my ($syspref_count_after) = + $dbh->selectrow_array("SELECT COUNT(*) FROM systempreferences WHERE variable = 'ShibbolethAuthentication'"); + + is( $config_count_after, $config_count_before, "Config row count unchanged after second run" ); + is( $mapping_count_after, $mapping_count_before, "Mapping row count unchanged after second run" ); + is( $syspref_count_after, $syspref_count_before, "Syspref count unchanged after second run" ); + + my $config = Koha::ShibbolethConfigs->search()->single; + ok( $config, "Config found after second run" ); + is( $config->autocreate, 1, "Config values still correct after second run" ) if $config; + + $schema->storage->txn_rollback; +}; + +subtest 'Migration without XML config or old sysprefs' => sub { + plan tests => 4; + + $schema->storage->txn_begin; + + my $dbh = C4::Context->dbh; + Koha::ShibbolethConfigs->search()->delete; + Koha::ShibbolethFieldMappings->search()->delete; + $dbh->do( + "DELETE FROM systempreferences WHERE variable IN ('ShibbolethAuthentication', 'staffShibOnly', 'OPACShibOnly')" + ); + + local $ENV{KOHA_CONF} = '/nonexistent/path.xml'; + + run_atomic_updates( ['bug_39224_add_shibboleth_tables.pl'] ); + + ok( $dbh->selectrow_array(q{SHOW TABLES LIKE 'shibboleth_config'}), "Tables created without XML" ); + + my ($syspref_value) = + $dbh->selectrow_array("SELECT value FROM systempreferences WHERE variable = 'ShibbolethAuthentication'"); + is( $syspref_value, 0, "ShibbolethAuthentication defaults to 0 without XML" ); + + my $config_count = Koha::ShibbolethConfigs->count; + is( $config_count, 0, "No config row created without XML or old sysprefs" ); + + my $mapping_count = Koha::ShibbolethFieldMappings->count; + is( $mapping_count, 0, "No field mappings created without XML" ); + + $schema->storage->txn_rollback; +}; diff --git a/t/db_dependent/api/v1/shibboleth_config.t b/t/db_dependent/api/v1/shibboleth_config.t new file mode 100755 index 00000000000..ebb0b128037 --- /dev/null +++ b/t/db_dependent/api/v1/shibboleth_config.t @@ -0,0 +1,121 @@ +#!/usr/bin/env perl + +# This file is part of Koha. +# +# Koha is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# Koha is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Koha; if not, see . + +use Modern::Perl; + +use Test::NoWarnings; +use Test::More tests => 3; +use Test::Mojo; + +use t::lib::TestBuilder; +use t::lib::Mocks; + +use Koha::ShibbolethConfigs; +use Koha::Database; + +my $schema = Koha::Database->new->schema; +my $builder = t::lib::TestBuilder->new; + +my $t = Test::Mojo->new('Koha::REST::V1'); +t::lib::Mocks::mock_preference( 'RESTBasicAuth', 1 ); + +subtest 'get() tests' => sub { + + plan tests => 5; + + $schema->storage->txn_begin; + + my $librarian = $builder->build_object( + { + class => 'Koha::Patrons', + value => { flags => 2**3 } + } + ); + my $password = 'thePassword123'; + $librarian->set_password( { password => $password, skip_validation => 1 } ); + my $userid = $librarian->userid; + + my $patron = $builder->build_object( + { + class => 'Koha::Patrons', + value => { flags => 0 } + } + ); + + $patron->set_password( { password => $password, skip_validation => 1 } ); + my $unauth_userid = $patron->userid; + + $schema->resultset('ShibbolethConfig')->delete; + + my $config = Koha::ShibbolethConfigs->new->get_configuration; + + $t->get_ok("//$userid:$password@/api/v1/shibboleth/config")->status_is(200)->json_is( $config->to_api ); + + $t->get_ok("//$unauth_userid:$password@/api/v1/shibboleth/config")->status_is(403); + + $schema->storage->txn_rollback; +}; + +subtest 'update() tests' => sub { + + plan tests => 10; + + $schema->storage->txn_begin; + + my $librarian = $builder->build_object( + { + class => 'Koha::Patrons', + value => { flags => 2**3 } + } + ); + my $password = 'thePassword123'; + $librarian->set_password( { password => $password, skip_validation => 1 } ); + my $userid = $librarian->userid; + + my $patron = $builder->build_object( + { + class => 'Koha::Patrons', + value => { flags => 0 } + } + ); + + $patron->set_password( { password => $password, skip_validation => 1 } ); + my $unauth_userid = $patron->userid; + + $schema->resultset('ShibbolethConfig')->delete; + + my $config = Koha::ShibbolethConfigs->new->get_configuration; + + my $updated_config = { + force_opac_sso => 1, + force_staff_sso => 1, + autocreate => 1, + sync => 1, + welcome => 0 + }; + + $t->put_ok( "//$unauth_userid:$password@/api/v1/shibboleth/config" => json => $updated_config )->status_is(403); + + $t->put_ok( "//$userid:$password@/api/v1/shibboleth/config" => json => $updated_config )->status_is(200) + ->json_is( '/force_opac_sso' => 1 )->json_is( '/force_staff_sso' => 1 )->json_is( '/autocreate' => 1 ) + ->json_is( '/sync' => 1 )->json_is( '/welcome' => 0 ); + + my $updated = Koha::ShibbolethConfigs->find(1); + is( $updated->force_opac_sso, 1, 'force_opac_sso updated in database' ); + + $schema->storage->txn_rollback; +}; diff --git a/t/db_dependent/api/v1/shibboleth_field_mappings.t b/t/db_dependent/api/v1/shibboleth_field_mappings.t new file mode 100755 index 00000000000..0947f1f4bc6 --- /dev/null +++ b/t/db_dependent/api/v1/shibboleth_field_mappings.t @@ -0,0 +1,356 @@ +#!/usr/bin/env perl + +# This file is part of Koha. +# +# Koha is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# Koha is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Koha; if not, see . + +use Modern::Perl; + +use Test::NoWarnings; +use Test::More tests => 6; +use Test::Mojo; + +use t::lib::TestBuilder; +use t::lib::Mocks; + +use Koha::ShibbolethFieldMappings; +use Koha::Database; + +my $schema = Koha::Database->new->schema; +my $builder = t::lib::TestBuilder->new; + +my $t = Test::Mojo->new('Koha::REST::V1'); +t::lib::Mocks::mock_preference( 'RESTBasicAuth', 1 ); + +subtest 'list() tests' => sub { + + plan tests => 20; + + $schema->storage->txn_begin; + + Koha::ShibbolethFieldMappings->search->delete; + + my $librarian = $builder->build_object( + { + class => 'Koha::Patrons', + value => { flags => 2**3 } + } + ); + my $password = 'thePassword123'; + $librarian->set_password( { password => $password, skip_validation => 1 } ); + my $userid = $librarian->userid; + + my $patron = $builder->build_object( + { + class => 'Koha::Patrons', + value => { flags => 0 } + } + ); + + $patron->set_password( { password => $password, skip_validation => 1 } ); + my $unauth_userid = $patron->userid; + + $t->get_ok("//$userid:$password@/api/v1/shibboleth/mappings")->status_is(200)->json_is( [] ); + + my $mapping = $builder->build_object( { class => 'Koha::ShibbolethFieldMappings' } ); + + $t->get_ok("//$userid:$password@/api/v1/shibboleth/mappings")->status_is(200)->json_is( [ $mapping->to_api ] ); + + my $another_mapping = $builder->build_object( + { + class => 'Koha::ShibbolethFieldMappings', + value => { idp_field => 'eppn', koha_field => 'userid' } + } + ); + + $t->get_ok("//$userid:$password@/api/v1/shibboleth/mappings")->status_is(200)->json_is( + [ + $mapping->to_api, + $another_mapping->to_api, + ] + ); + + $mapping->delete; + $another_mapping->delete; + $t->get_ok(qq~//$userid:$password@/api/v1/shibboleth/mappings?q=[{"me.koha_field":{"like":"%user%"}}]~) + ->status_is(200)->json_is( [] ); + + my $mapping_to_search = $builder->build_object( + { + class => 'Koha::ShibbolethFieldMappings', + value => { + koha_field => 'userid', + } + } + ); + + $t->get_ok(qq~//$userid:$password@/api/v1/shibboleth/mappings?q=[{"me.koha_field":{"like":"%user%"}}]~) + ->status_is(200)->json_is( [ $mapping_to_search->to_api ] ); + + $t->get_ok("//$userid:$password@/api/v1/shibboleth/mappings?blah=blah")->status_is(400) + ->json_is( [ { path => '/query/blah', message => 'Malformed query string' } ] ); + + $t->get_ok("//$unauth_userid:$password@/api/v1/shibboleth/mappings")->status_is(403); + + $schema->storage->txn_rollback; +}; + +subtest 'get() tests' => sub { + + plan tests => 8; + + $schema->storage->txn_begin; + + my $mapping = $builder->build_object( { class => 'Koha::ShibbolethFieldMappings' } ); + my $librarian = $builder->build_object( + { + class => 'Koha::Patrons', + value => { flags => 2**3 } + } + ); + my $password = 'thePassword123'; + $librarian->set_password( { password => $password, skip_validation => 1 } ); + my $userid = $librarian->userid; + + my $patron = $builder->build_object( + { + class => 'Koha::Patrons', + value => { flags => 0 } + } + ); + + $patron->set_password( { password => $password, skip_validation => 1 } ); + my $unauth_userid = $patron->userid; + + $t->get_ok( "//$userid:$password@/api/v1/shibboleth/mappings/" . $mapping->mapping_id )->status_is(200) + ->json_is( $mapping->to_api ); + + $t->get_ok( "//$unauth_userid:$password@/api/v1/shibboleth/mappings/" . $mapping->mapping_id )->status_is(403); + + my $mapping_to_delete = $builder->build_object( { class => 'Koha::ShibbolethFieldMappings' } ); + my $non_existent_id = $mapping_to_delete->mapping_id; + $mapping_to_delete->delete; + + $t->get_ok("//$userid:$password@/api/v1/shibboleth/mappings/$non_existent_id")->status_is(404) + ->json_is( '/error' => 'Mapping not found' ); + + $schema->storage->txn_rollback; +}; + +subtest 'add() tests' => sub { + + plan tests => 19; + + $schema->storage->txn_begin; + + my $librarian = $builder->build_object( + { + class => 'Koha::Patrons', + value => { flags => 2**3 } + } + ); + my $password = 'thePassword123'; + $librarian->set_password( { password => $password, skip_validation => 1 } ); + my $userid = $librarian->userid; + + my $patron = $builder->build_object( + { + class => 'Koha::Patrons', + value => { flags => 0 } + } + ); + + $patron->set_password( { password => $password, skip_validation => 1 } ); + my $unauth_userid = $patron->userid; + + my $mapping = { + idp_field => "mail", + koha_field => "email", + is_matchpoint => 0, + }; + + $t->post_ok( "//$unauth_userid:$password@/api/v1/shibboleth/mappings" => json => $mapping )->status_is(403); + + my $mapping_with_invalid_field = { + blah => "Mapping Blah", + idp_field => "mail", + koha_field => "email", + is_matchpoint => 0, + }; + + $t->post_ok( "//$userid:$password@/api/v1/shibboleth/mappings" => json => $mapping_with_invalid_field ) + ->status_is(400)->json_is( + "/errors" => [ + { + message => "Properties not allowed: blah.", + path => "/body" + } + ] + ); + + my $mapping_id = + $t->post_ok( "//$userid:$password@/api/v1/shibboleth/mappings" => json => $mapping ) + ->status_is( 201, 'REST3.2.1' )->header_like( + Location => qr|^/api/v1/shibboleth/mappings/\d*|, + 'REST3.4.1' + )->json_is( '/idp_field' => $mapping->{idp_field} )->json_is( '/koha_field' => $mapping->{koha_field} ) + ->tx->res->json->{mapping_id}; + + $mapping->{mapping_id} = undef; + $t->post_ok( "//$userid:$password@/api/v1/shibboleth/mappings" => json => $mapping )->status_is(400) + ->json_has('/errors'); + + $mapping->{mapping_id} = $mapping_id; + + $t->post_ok( "//$userid:$password@/api/v1/shibboleth/mappings" => json => $mapping )->status_is(400)->json_is( + "/errors" => [ + { + message => "Read-only.", + path => "/body/mapping_id" + } + ] + ); + + my $mapping_without_idp_or_default = { + koha_field => "phonepro", + is_matchpoint => 0, + }; + + $t->post_ok( "//$userid:$password@/api/v1/shibboleth/mappings" => json => $mapping_without_idp_or_default ) + ->status_is(400)->json_is( "/error" => "Either idp_field or default_content must be provided" ); + + $schema->storage->txn_rollback; +}; + +subtest 'update() tests' => sub { + + plan tests => 15; + + $schema->storage->txn_begin; + + my $librarian = $builder->build_object( + { + class => 'Koha::Patrons', + value => { flags => 2**3 } + } + ); + my $password = 'thePassword123'; + $librarian->set_password( { password => $password, skip_validation => 1 } ); + my $userid = $librarian->userid; + + my $patron = $builder->build_object( + { + class => 'Koha::Patrons', + value => { flags => 0 } + } + ); + + $patron->set_password( { password => $password, skip_validation => 1 } ); + my $unauth_userid = $patron->userid; + + my $mapping_id = $builder->build_object( { class => 'Koha::ShibbolethFieldMappings' } )->mapping_id; + + $t->put_ok( "//$unauth_userid:$password@/api/v1/shibboleth/mappings/$mapping_id" => json => + { idp_field => 'New unauthorized name change' } )->status_is(403); + + my $mapping_without_idp_or_default = { + koha_field => 'surname', + idp_field => undef, + default_content => undef, + is_matchpoint => 0, + }; + + $t->put_ok( + "//$userid:$password@/api/v1/shibboleth/mappings/$mapping_id" => json => $mapping_without_idp_or_default ) + ->status_is(400)->json_is( "/error" => "Either idp_field or default_content must be provided" ); + + my $mapping_with_updated_field = { + idp_field => "sn", + koha_field => "surname", + is_matchpoint => 0, + }; + + $t->put_ok( "//$userid:$password@/api/v1/shibboleth/mappings/$mapping_id" => json => $mapping_with_updated_field ) + ->status_is(200)->json_is( '/idp_field' => 'sn' ); + + my $mapping_with_invalid_field = { + blah => "Mapping Blah", + idp_field => "sn", + koha_field => "surname", + is_matchpoint => 0, + }; + + $t->put_ok( "//$userid:$password@/api/v1/shibboleth/mappings/$mapping_id" => json => $mapping_with_invalid_field ) + ->status_is(400)->json_is( + "/errors" => [ + { + message => "Properties not allowed: blah.", + path => "/body" + } + ] + ); + + my $mapping_to_delete = $builder->build_object( { class => 'Koha::ShibbolethFieldMappings' } ); + my $non_existent_id = $mapping_to_delete->mapping_id; + $mapping_to_delete->delete; + + $t->put_ok( + "//$userid:$password@/api/v1/shibboleth/mappings/$non_existent_id" => json => $mapping_with_updated_field ) + ->status_is(404); + + $mapping_with_updated_field->{mapping_id} = 2; + + $t->post_ok( "//$userid:$password@/api/v1/shibboleth/mappings/$mapping_id" => json => $mapping_with_updated_field ) + ->status_is(404); + + $schema->storage->txn_rollback; +}; + +subtest 'delete() tests' => sub { + + plan tests => 7; + + $schema->storage->txn_begin; + + my $librarian = $builder->build_object( + { + class => 'Koha::Patrons', + value => { flags => 2**3 } + } + ); + my $password = 'thePassword123'; + $librarian->set_password( { password => $password, skip_validation => 1 } ); + my $userid = $librarian->userid; + + my $patron = $builder->build_object( + { + class => 'Koha::Patrons', + value => { flags => 0 } + } + ); + + $patron->set_password( { password => $password, skip_validation => 1 } ); + my $unauth_userid = $patron->userid; + + my $mapping_id = $builder->build_object( { class => 'Koha::ShibbolethFieldMappings' } )->mapping_id; + + $t->delete_ok("//$unauth_userid:$password@/api/v1/shibboleth/mappings/$mapping_id")->status_is(403); + + $t->delete_ok("//$userid:$password@/api/v1/shibboleth/mappings/$mapping_id")->status_is( 204, 'REST3.2.4' ) + ->content_is( '', 'REST3.3.4' ); + + $t->delete_ok("//$userid:$password@/api/v1/shibboleth/mappings/$mapping_id")->status_is(404); + + $schema->storage->txn_rollback; +}; -- 2.39.5