Bugzilla – Attachment 193686 Details for
Bug 39224
Migrate SAML/Shibboleth configuration into Identity Providers
Home
|
New
|
Browse
|
Search
|
[?]
|
Reports
|
Help
|
New Account
|
Log In
[x]
|
Forgot Password
Login:
[x]
[patch]
Bug 39224: Unit tests for identity provider object classes
f3e45a7.patch (text/plain), 42.17 KB, created by
Martin Renvoize (ashimema)
on 2026-02-24 07:33:49 UTC
(
hide
)
Description:
Bug 39224: Unit tests for identity provider object classes
Filename:
MIME Type:
Creator:
Martin Renvoize (ashimema)
Created:
2026-02-24 07:33:49 UTC
Size:
42.17 KB
patch
obsolete
>From f3e45a774520c198f11c7649810d3cbd76b6e1b5 Mon Sep 17 00:00:00 2001 >From: Martin Renvoize <martin.renvoize@openfifth.co.uk> >Date: Sun, 22 Feb 2026 20:18:43 +0000 >Subject: [PATCH] Bug 39224: Unit tests for identity provider object classes > >Adds unit tests for the new Koha object classes: > >- t/db_dependent/Koha/Auth/Hostname.t: Tests for hostname CRUD > and validation >- t/db_dependent/Koha/Auth/Identity/Provider.t: Extended tests > for provider with mappings and hostnames >- t/db_dependent/Koha/Auth/Identity/Provider/Mapping.t: Tests > for field mapping CRUD and validation >- t/db_dependent/Auth_with_shibboleth.t: Updated tests for new > provider structure > >Test plan: >1. Run: prove t/db_dependent/Koha/Auth/Hostname.t >2. Run: prove t/db_dependent/Koha/Auth/Identity/Provider.t >3. Run: prove t/db_dependent/Koha/Auth/Identity/Provider/Mapping.t >4. Run: prove t/db_dependent/Auth_with_shibboleth.t > >Sponsored-by: ByWater Solutions >--- > t/db_dependent/Auth_with_shibboleth.t | 86 ++++++++-- > t/db_dependent/Koha/Auth/Client.t | 60 +++++-- > t/db_dependent/Koha/Auth/Client/OAuth.t | 97 +++++++---- > t/db_dependent/Koha/Auth/Hostname.t | 139 +++++++++++++++ > t/db_dependent/Koha/Auth/Identity/Provider.t | 158 +++++++++++++----- > .../Koha/Auth/Identity/Provider/Mapping.t | 140 ++++++++++++++++ > t/db_dependent/Koha/Plugins/Auth/Client.t | 56 +++++-- > t/db_dependent/Koha/REST/Plugin/Auth/IdP.t | 3 +- > 8 files changed, 624 insertions(+), 115 deletions(-) > create mode 100755 t/db_dependent/Koha/Auth/Hostname.t > create mode 100755 t/db_dependent/Koha/Auth/Identity/Provider/Mapping.t > >diff --git a/t/db_dependent/Auth_with_shibboleth.t b/t/db_dependent/Auth_with_shibboleth.t >index 8a101ac07e6..2dda98b3335 100755 >--- a/t/db_dependent/Auth_with_shibboleth.t >+++ b/t/db_dependent/Auth_with_shibboleth.t >@@ -35,6 +35,13 @@ 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::Auth::Hostname; >+use Koha::Auth::Hostnames; >+use Koha::Auth::Identity::Provider; >+use Koha::Auth::Identity::Provider::Hostname; >+use Koha::Auth::Identity::Provider::Mapping; >+use Koha::Auth::Identity::Provider::SAML2; >+use Koha::Auth::Identity::Providers; > > BEGIN { > use_ok( >@@ -48,9 +55,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 +64,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 +103,22 @@ $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 on hostname entries >+ $schema->resultset('IdentityProviderHostname')->update( { matchpoint => undef } ); > delete $shibboleth->{matchpoint}; >+ $logger->clear(); > warnings_are { $result = shib_ok() } >- [ { carped => 'shibboleth matchpoint not defined' }, ], >- "undefined matchpoint = fatal config, warning given"; >+ [ { carped => 'shibboleth matchpoint not defined' } ], >+ "matchpoint warning given when no matchpoint set"; > 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"; >@@ -119,6 +127,7 @@ subtest "shib_ok tests" => sub { > # add test for undefined shibboleth block > $logger->clear; > change_config( {} ); >+ is( shib_ok(), '1', 'good config after reset' ); > }; > > subtest "login_shib_url tests" => sub { >@@ -279,6 +288,10 @@ subtest "checkpw_shib tests" => sub { > ); > > # sync user >+ my $saml2_provider = Koha::Auth::Identity::Providers->search( { protocol => 'SAML2' } )->next; >+ my $saml2_config = $saml2_provider->get_config // {}; >+ $saml2_config->{sync} = 1; >+ $saml2_provider->set_config($saml2_config)->store; > $shibboleth->{sync} = 1; > $ENV{'city'} = 'AnotherCity'; > ( $retval, $retcard, $retuserid, $retpatron ) = checkpw_shib($shib_login); >@@ -335,7 +348,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" >@@ -424,6 +437,38 @@ $schema->storage->txn_rollback; > sub change_config { > my $params = shift; > >+ # Remove any existing SAML2 providers >+ Koha::Auth::Identity::Providers->search( { protocol => 'SAML2' } )->delete; >+ >+ my $provider = Koha::Auth::Identity::Provider::SAML2->new( >+ { >+ code => 'shibboleth', >+ description => 'Shibboleth', >+ enabled => 1, >+ } >+ )->store; >+ >+ $provider->set_config( >+ { >+ autocreate => $params->{autocreate} // 0, >+ sync => $params->{sync} // 0, >+ welcome => $params->{welcome} // 0, >+ } >+ )->store; >+ >+ # Link provider to the test hostname so _get_shib_config can find it >+ my $test_hostname = 'testopac.com'; >+ $ENV{SERVER_NAME} = $test_hostname; >+ my $hostname_obj = Koha::Auth::Hostnames->search( { hostname => $test_hostname } )->next >+ // $builder->build_object( { class => 'Koha::Auth::Hostnames', value => { hostname => $test_hostname } } ); >+ Koha::Auth::Identity::Provider::Hostname->new( >+ { >+ hostname_id => $hostname_obj->hostname_id, >+ identity_provider_id => $provider->identity_provider_id, >+ is_enabled => 1, >+ } >+ )->store; >+ > my %mapping = ( > 'userid' => { 'is' => 'uid' }, > 'surname' => { 'is' => 'sn' }, >@@ -437,15 +482,32 @@ sub change_config { > if ( exists $params->{mapping} ) { > $mapping{$_} = $params->{mapping}->{$_} for keys %{ $params->{mapping} }; > } >+ >+ my $matchpoint = $params->{matchpoint} // 'userid'; >+ >+ foreach my $koha_field ( keys %mapping ) { >+ Koha::Auth::Identity::Provider::Mapping->new( >+ { >+ identity_provider_id => $provider->identity_provider_id, >+ koha_field => $koha_field, >+ provider_field => $mapping{$koha_field}->{is}, >+ default_content => $mapping{$koha_field}->{content}, >+ } >+ )->store; >+ } >+ >+ $schema->resultset('IdentityProviderHostname') >+ ->search( { identity_provider_id => $provider->identity_provider_id } ) >+ ->update( { matchpoint => $matchpoint } ); >+ > $shibboleth = { > 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/Auth/Client.t b/t/db_dependent/Koha/Auth/Client.t >index 47cb73c32c0..358f06848a9 100755 >--- a/t/db_dependent/Koha/Auth/Client.t >+++ b/t/db_dependent/Koha/Auth/Client.t >@@ -45,9 +45,20 @@ subtest 'get_user() tests' => sub { > > $schema->storage->txn_begin; > >- my $client = Koha::Auth::Client::OAuth->new; >- my $provider = >- $builder->build_object( { class => 'Koha::Auth::Identity::Providers', value => { matchpoint => 'email' } } ); >+ my $client = Koha::Auth::Client::OAuth->new; >+ my $provider = $builder->build_object( { class => 'Koha::Auth::Identity::Providers' } ); >+ my $hostname_obj = >+ $builder->build_object( { class => 'Koha::Auth::Hostnames', value => { hostname => 'test.library.com' } } ); >+ $builder->build_object( >+ { >+ class => 'Koha::Auth::Identity::Provider::Hostnames', >+ value => { >+ identity_provider_id => $provider->id, >+ hostname_id => $hostname_obj->id, >+ matchpoint => 'email', >+ } >+ } >+ ); > my $domain = $builder->build_object( > { > class => 'Koha::Auth::Identity::Provider::Domains', >@@ -59,12 +70,26 @@ subtest 'get_user() tests' => sub { > ); > my $patron = $builder->build_object( { class => 'Koha::Patrons', value => { email => 'patron@test.com' } } ); > t::lib::Mocks::mock_userenv( { patron => $patron } ); >- my $mapping = { >- email => 'electronic_mail', >- firstname => 'given_name', >- surname => 'family_name' >- }; >- $provider->set_mapping($mapping)->store; >+ $builder->build_object( >+ { >+ class => 'Koha::Auth::Identity::Provider::Mappings', >+ value => >+ { identity_provider_id => $provider->id, koha_field => 'email', provider_field => 'electronic_mail' } >+ } >+ ); >+ $builder->build_object( >+ { >+ class => 'Koha::Auth::Identity::Provider::Mappings', >+ value => >+ { identity_provider_id => $provider->id, koha_field => 'firstname', provider_field => 'given_name' } >+ } >+ ); >+ $builder->build_object( >+ { >+ class => 'Koha::Auth::Identity::Provider::Mappings', >+ value => { identity_provider_id => $provider->id, koha_field => 'surname', provider_field => 'family_name' } >+ } >+ ); > > my $id_token = 'header.' . encode_base64url( > encode_json( >@@ -78,7 +103,8 @@ subtest 'get_user() tests' => sub { > my $data = { id_token => $id_token }; > > my ( $resolved_patron, $mapped_data, $resolved_domain ) = >- $client->get_user( { provider => $provider->code, data => $data, interface => 'opac' } ); >+ $client->get_user( >+ { provider => $provider->code, data => $data, interface => 'opac', hostname => 'test.library.com' } ); > is_deeply( > $resolved_patron->to_api( { user => $patron } ), $patron->to_api( { user => $patron } ), > 'Patron correctly retrieved' >@@ -95,10 +121,9 @@ subtest 'get_valid_domain_config() tests' => sub { > > $schema->storage->txn_begin; > >- my $client = Koha::Auth::Client->new; >- my $provider = >- $builder->build_object( { class => 'Koha::Auth::Identity::Providers', value => { matchpoint => 'email' } } ); >- my $domain1 = $builder->build_object( >+ my $client = Koha::Auth::Client->new; >+ my $provider = $builder->build_object( { class => 'Koha::Auth::Identity::Providers' } ); >+ my $domain1 = $builder->build_object( > { > class => 'Koha::Auth::Identity::Provider::Domains', > value => { identity_provider_id => $provider->id, domain => '', allow_opac => 0, allow_staff => 0 } >@@ -200,10 +225,9 @@ subtest 'has_valid_domain_config() tests' => sub { > plan tests => 2; > $schema->storage->txn_begin; > >- my $client = Koha::Auth::Client->new; >- my $provider = >- $builder->build_object( { class => 'Koha::Auth::Identity::Providers', value => { matchpoint => 'email' } } ); >- my $domain1 = $builder->build_object( >+ my $client = Koha::Auth::Client->new; >+ my $provider = $builder->build_object( { class => 'Koha::Auth::Identity::Providers' } ); >+ my $domain1 = $builder->build_object( > { > class => 'Koha::Auth::Identity::Provider::Domains', > value => { identity_provider_id => $provider->id, domain => '', allow_opac => 1, allow_staff => 0 } >diff --git a/t/db_dependent/Koha/Auth/Client/OAuth.t b/t/db_dependent/Koha/Auth/Client/OAuth.t >index 0cca7126a22..c0839f97754 100755 >--- a/t/db_dependent/Koha/Auth/Client/OAuth.t >+++ b/t/db_dependent/Koha/Auth/Client/OAuth.t >@@ -50,19 +50,32 @@ subtest '_get_data_and_patron() with id_token tests' => sub { > ); > > # Create provider with email matchpoint >- my $provider = $builder->build_object( >+ my $provider = $builder->build_object( { class => 'Koha::Auth::Identity::Providers' } ); >+ my $hostname_obj = >+ $builder->build_object( { class => 'Koha::Auth::Hostnames', value => { hostname => 'test.library.com' } } ); >+ $builder->build_object( > { >- class => 'Koha::Auth::Identity::Providers', >- value => { >- matchpoint => 'email', >- mapping => encode_json( >- { >- email => 'mail', >- firstname => 'given_name', >- surname => 'family_name' >- } >- ) >- } >+ class => 'Koha::Auth::Identity::Provider::Hostnames', >+ value => { identity_provider_id => $provider->id, hostname_id => $hostname_obj->id, matchpoint => 'email' } >+ } >+ ); >+ $builder->build_object( >+ { >+ class => 'Koha::Auth::Identity::Provider::Mappings', >+ value => { identity_provider_id => $provider->id, koha_field => 'email', provider_field => 'mail' } >+ } >+ ); >+ $builder->build_object( >+ { >+ class => 'Koha::Auth::Identity::Provider::Mappings', >+ value => >+ { identity_provider_id => $provider->id, koha_field => 'firstname', provider_field => 'given_name' } >+ } >+ ); >+ $builder->build_object( >+ { >+ class => 'Koha::Auth::Identity::Provider::Mappings', >+ value => { identity_provider_id => $provider->id, koha_field => 'surname', provider_field => 'family_name' } > } > ); > >@@ -83,7 +96,8 @@ subtest '_get_data_and_patron() with id_token tests' => sub { > { > provider => $provider, > data => $data, >- config => $config >+ config => $config, >+ hostname => 'test.library.com', > } > ); > >@@ -112,18 +126,26 @@ subtest '_get_data_and_patron() with userinfo_url tests' => sub { > ); > > # Create provider with email matchpoint >- my $provider = $builder->build_object( >+ my $provider = $builder->build_object( { class => 'Koha::Auth::Identity::Providers' } ); >+ my $hostname_obj = >+ $builder->build_object( { class => 'Koha::Auth::Hostnames', value => { hostname => 'test2.library.com' } } ); >+ $builder->build_object( > { >- class => 'Koha::Auth::Identity::Providers', >- value => { >- matchpoint => 'email', >- mapping => encode_json( >- { >- email => 'email', >- firstname => 'first_name' >- } >- ) >- } >+ class => 'Koha::Auth::Identity::Provider::Hostnames', >+ value => { identity_provider_id => $provider->id, hostname_id => $hostname_obj->id, matchpoint => 'email' } >+ } >+ ); >+ $builder->build_object( >+ { >+ class => 'Koha::Auth::Identity::Provider::Mappings', >+ value => { identity_provider_id => $provider->id, koha_field => 'email', provider_field => 'email' } >+ } >+ ); >+ $builder->build_object( >+ { >+ class => 'Koha::Auth::Identity::Provider::Mappings', >+ value => >+ { identity_provider_id => $provider->id, koha_field => 'firstname', provider_field => 'first_name' } > } > ); > >@@ -179,7 +201,8 @@ subtest '_get_data_and_patron() with userinfo_url tests' => sub { > { > provider => $provider, > data => $data, >- config => $config >+ config => $config, >+ hostname => 'test2.library.com', > } > ); > >@@ -205,7 +228,8 @@ subtest '_get_data_and_patron() with userinfo_url tests' => sub { > { > provider => $provider, > data => $data, >- config => $config >+ config => $config, >+ hostname => 'test2.library.com', > } > ); > >@@ -223,13 +247,19 @@ subtest '_get_data_and_patron() no patron found tests' => sub { > my $client = Koha::Auth::Client::OAuth->new; > > # Create provider >- my $provider = $builder->build_object( >+ my $provider = $builder->build_object( { class => 'Koha::Auth::Identity::Providers' } ); >+ my $hostname_obj = >+ $builder->build_object( { class => 'Koha::Auth::Hostnames', value => { hostname => 'test3.library.com' } } ); >+ $builder->build_object( >+ { >+ class => 'Koha::Auth::Identity::Provider::Hostnames', >+ value => { identity_provider_id => $provider->id, hostname_id => $hostname_obj->id, matchpoint => 'email' } >+ } >+ ); >+ $builder->build_object( > { >- class => 'Koha::Auth::Identity::Providers', >- value => { >- matchpoint => 'email', >- mapping => encode_json( { email => 'mail' } ) >- } >+ class => 'Koha::Auth::Identity::Provider::Mappings', >+ value => { identity_provider_id => $provider->id, koha_field => 'email', provider_field => 'mail' } > } > ); > >@@ -245,7 +275,8 @@ subtest '_get_data_and_patron() no patron found tests' => sub { > { > provider => $provider, > data => $data, >- config => $config >+ config => $config, >+ hostname => 'test3.library.com', > } > ); > >diff --git a/t/db_dependent/Koha/Auth/Hostname.t b/t/db_dependent/Koha/Auth/Hostname.t >new file mode 100755 >index 00000000000..c66f993408b >--- /dev/null >+++ b/t/db_dependent/Koha/Auth/Hostname.t >@@ -0,0 +1,139 @@ >+#!/usr/bin/perl >+ >+# Copyright Koha Community 2026 >+# >+# 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 <https://www.gnu.org/licenses>. >+ >+use Modern::Perl; >+ >+use Test::NoWarnings; >+use Test::More tests => 4; >+ >+use Koha::Auth::Hostname; >+use Koha::Auth::Hostnames; >+ >+use t::lib::TestBuilder; >+use t::lib::Mocks; >+ >+my $schema = Koha::Database->new->schema; >+my $builder = t::lib::TestBuilder->new; >+ >+subtest 'sync_from_syspref() tests' => sub { >+ >+ plan tests => 12; >+ >+ $schema->storage->txn_begin; >+ >+ Koha::Auth::Hostnames->search->delete; >+ >+ # OPACBaseURL maps to hostname_id=1 >+ t::lib::Mocks::mock_preference( 'OPACBaseURL', 'https://opac.example.com' ); >+ Koha::Auth::Hostname->sync_from_syspref( 'OPACBaseURL', 'https://opac.example.com' ); >+ >+ my $opac = Koha::Auth::Hostnames->find(1); >+ ok( $opac, 'OPACBaseURL creates hostname_id=1' ); >+ is( $opac->hostname, 'opac.example.com', 'hostname_id=1 has correct hostname' ); >+ >+ # staffClientBaseURL maps to hostname_id=2 >+ Koha::Auth::Hostname->sync_from_syspref( 'staffClientBaseURL', 'https://staff.example.com' ); >+ >+ my $staff = Koha::Auth::Hostnames->find(2); >+ ok( $staff, 'staffClientBaseURL creates hostname_id=2' ); >+ is( $staff->hostname, 'staff.example.com', 'hostname_id=2 has correct hostname' ); >+ >+ # Updating the URL changes the stored hostname in-place >+ Koha::Auth::Hostname->sync_from_syspref( 'OPACBaseURL', 'https://newopac.example.com' ); >+ $opac->discard_changes; >+ is( $opac->hostname, 'newopac.example.com', 'hostname_id=1 updated when OPACBaseURL changes' ); >+ is( Koha::Auth::Hostnames->search( { hostname_id => 1 } )->count, 1, 'Still only one row for hostname_id=1' ); >+ >+ # Idempotent: calling again with the same value is a no-op >+ Koha::Auth::Hostname->sync_from_syspref( 'OPACBaseURL', 'https://newopac.example.com' ); >+ is( >+ Koha::Auth::Hostnames->search( { hostname => 'newopac.example.com' } )->count, 1, >+ 'Idempotent: no duplicate created' >+ ); >+ >+ # http:// URLs are supported >+ Koha::Auth::Hostname->sync_from_syspref( 'OPACBaseURL', 'http://opac.example.org' ); >+ $opac->discard_changes; >+ is( $opac->hostname, 'opac.example.org', 'http:// URL extracted correctly' ); >+ >+ # Port numbers are stripped >+ Koha::Auth::Hostname->sync_from_syspref( 'OPACBaseURL', 'https://opac.example.net:8080/path' ); >+ $opac->discard_changes; >+ is( $opac->hostname, 'opac.example.net', 'Port number stripped from hostname' ); >+ >+ # Empty value is a no-op >+ my $hostname_before = $opac->hostname; >+ Koha::Auth::Hostname->sync_from_syspref( 'OPACBaseURL', '' ); >+ $opac->discard_changes; >+ is( $opac->hostname, $hostname_before, 'Empty value leaves existing hostname unchanged' ); >+ >+ # Malformed URL (no protocol) is a no-op >+ Koha::Auth::Hostname->sync_from_syspref( 'OPACBaseURL', 'not-a-valid-url' ); >+ $opac->discard_changes; >+ is( $opac->hostname, $hostname_before, 'Malformed URL leaves existing hostname unchanged' ); >+ >+ # Unknown preference name is ignored >+ Koha::Auth::Hostname->sync_from_syspref( 'SomeOtherPref', 'https://other.example.com' ); >+ is( >+ Koha::Auth::Hostnames->search( { hostname => 'other.example.com' } )->count, >+ 0, 'Unknown syspref name is ignored' >+ ); >+ >+ $schema->storage->txn_rollback; >+}; >+ >+subtest 'sync_from_sysprefs() tests' => sub { >+ >+ plan tests => 4; >+ >+ $schema->storage->txn_begin; >+ >+ Koha::Auth::Hostnames->search->delete; >+ >+ t::lib::Mocks::mock_preference( 'OPACBaseURL', 'https://opac.example.com' ); >+ t::lib::Mocks::mock_preference( 'staffClientBaseURL', 'https://staff.example.com' ); >+ >+ Koha::Auth::Hostname->sync_from_sysprefs; >+ >+ my $opac = Koha::Auth::Hostnames->find(1); >+ my $staff = Koha::Auth::Hostnames->find(2); >+ >+ ok( $opac, 'hostname_id=1 exists after sync_from_sysprefs' ); >+ is( $opac->hostname, 'opac.example.com', 'hostname_id=1 has OPAC hostname' ); >+ >+ ok( $staff, 'hostname_id=2 exists after sync_from_sysprefs' ); >+ is( $staff->hostname, 'staff.example.com', 'hostname_id=2 has staff hostname' ); >+ >+ $schema->storage->txn_rollback; >+}; >+ >+subtest 'Koha::Auth::Hostnames type tests' => sub { >+ >+ plan tests => 2; >+ >+ $schema->storage->txn_begin; >+ >+ my $hostname = $builder->build_object( { class => 'Koha::Auth::Hostnames' } ); >+ is( ref($hostname), 'Koha::Auth::Hostname', 'build_object returns correct object type' ); >+ >+ my $hostnames = Koha::Auth::Hostnames->search( { hostname_id => $hostname->hostname_id } ); >+ is( ref($hostnames), 'Koha::Auth::Hostnames', 'search returns correct collection type' ); >+ >+ $schema->storage->txn_rollback; >+}; >diff --git a/t/db_dependent/Koha/Auth/Identity/Provider.t b/t/db_dependent/Koha/Auth/Identity/Provider.t >index 8fe84cb8982..04e1decb374 100755 >--- a/t/db_dependent/Koha/Auth/Identity/Provider.t >+++ b/t/db_dependent/Koha/Auth/Identity/Provider.t >@@ -20,13 +20,17 @@ > use Modern::Perl; > > use Test::NoWarnings; >-use Test::More tests => 7; >+use Test::More tests => 8; > > use Test::MockModule; > use Test::Exception; > > use JSON qw(encode_json); > >+use Koha::Auth::Hostnames; >+use Koha::Auth::Identity::Provider; >+use Koha::Auth::Identity::Provider::Hostnames; >+use Koha::Auth::Identity::Provider::Mappings; > use Koha::Auth::Identity::Providers; > > use t::lib::TestBuilder; >@@ -88,7 +92,6 @@ subtest 'set_config() tests' => sub { > > my $provider = > $builder->build_object( { class => 'Koha::Auth::Identity::Providers', value => { protocol => 'OIDC' } } ); >- $provider = $provider->upgrade_class; > > my $config = { > key => 'key', >@@ -114,7 +117,6 @@ subtest 'set_config() tests' => sub { > > my $provider = > $builder->build_object( { class => 'Koha::Auth::Identity::Providers', value => { protocol => 'OAuth' } } ); >- $provider = $provider->upgrade_class; > > my $config = { > key => 'key', >@@ -135,15 +137,15 @@ subtest 'set_config() tests' => sub { > is_deeply( $provider->get_config, $config, 'Configuration stored correctly' ); > }; > >- subtest 'Unsupported protocol tests' => sub { >+ subtest 'Base class (unsupported protocol) tests' => sub { > > plan tests => 2; > >- my $provider = >- $builder->build_object( { class => 'Koha::Auth::Identity::Providers', value => { protocol => 'CAS' } } ); >+ # Cannot build in DB with invalid protocol; instantiate base class directly >+ my $provider = Koha::Auth::Identity::Provider->new( { protocol => 'OAuth' } ); > >- throws_ok { $provider->set_config() } >- 'Koha::Exception', 'Exception thrown on unsupported protocol'; >+ throws_ok { $provider->set_config( {} ) } >+ 'Koha::Exception', 'Exception thrown when calling set_config on base class'; > > like( "$@", qr/This method needs to be subclassed/, 'Message is correct' ); > }; >@@ -151,48 +153,66 @@ subtest 'set_config() tests' => sub { > $schema->storage->txn_rollback; > }; > >-subtest 'get_mapping() tests' => sub { >+subtest 'mappings() tests' => sub { > >- plan tests => 2; >+ plan tests => 3; > > $schema->storage->txn_begin; > >- my $provider = $builder->build_object( { class => 'Koha::Auth::Identity::Providers', value => { config => '{' } } ); >- >- throws_ok { $provider->get_mapping() } >- 'Koha::Exceptions::Object::BadValue', 'Expected exception thrown on bad JSON'; >- >- my $mapping = { some => 'value', and => 'another' }; >- $provider->mapping( encode_json($mapping) )->store; >- >- is_deeply( $provider->get_mapping, $mapping, 'Mapping correctly retrieved' ); >+ my $provider = $builder->build_object( { class => 'Koha::Auth::Identity::Providers' } ); >+ my $mappings = $provider->mappings; >+ >+ is( ref($mappings), 'Koha::Auth::Identity::Provider::Mappings', 'Type is correct' ); >+ is( $mappings->count, 0, 'No mappings defined' ); >+ >+ $builder->build( >+ { >+ source => 'IdentityProviderMapping', >+ value => { identity_provider_id => $provider->id, koha_field => 'userid', provider_field => 'uid' } >+ } >+ ); >+ $builder->build( >+ { >+ source => 'IdentityProviderMapping', >+ value => { identity_provider_id => $provider->id, koha_field => 'email', provider_field => 'mail' } >+ } >+ ); >+ >+ is( $provider->mappings->count, 2, 'The provider has 2 mappings defined' ); > > $schema->storage->txn_rollback; > }; > >-subtest 'set_mapping() tests' => sub { >+subtest 'hostnames() tests' => sub { > >- plan tests => 1; >+ plan tests => 3; > > $schema->storage->txn_begin; > >- my $provider = $builder->build_object( { class => 'Koha::Auth::Identity::Providers' } ); >+ my $provider = $builder->build_object( { class => 'Koha::Auth::Identity::Providers' } ); >+ my $hostnames = $provider->hostnames; > >- my $mapping = { some => 'value', and => 'another' }; >- $provider->set_mapping($mapping)->store; >+ is( ref($hostnames), 'Koha::Auth::Identity::Provider::Hostnames', 'Type is correct' ); >+ is( $hostnames->count, 0, 'No hostnames defined' ); >+ >+ $builder->build_object( >+ { class => 'Koha::Auth::Identity::Provider::Hostnames', value => { identity_provider_id => $provider->id } } ); >+ $builder->build_object( >+ { class => 'Koha::Auth::Identity::Provider::Hostnames', value => { identity_provider_id => $provider->id } } ); > >- is_deeply( $provider->get_mapping, $mapping, 'Mapping correctly retrieved' ); >+ is( $provider->hostnames->count, 2, 'The provider has 2 hostnames defined' ); > > $schema->storage->txn_rollback; > }; > >-subtest 'upgrade_class() tests' => sub { >+subtest 'polymorphic_retrieval() tests' => sub { > >- plan tests => 5; >+ plan tests => 7; > > $schema->storage->txn_begin; > >- my $mapping = Koha::Auth::Identity::Provider::protocol_to_class_mapping; >+ my $providers = Koha::Auth::Identity::Providers->new; >+ my $mapping = $providers->_polymorphic_map; > my @protocols = keys %{$mapping}; > > foreach my $protocol (@protocols) { >@@ -204,20 +224,82 @@ subtest 'upgrade_class() tests' => sub { > } > ); > >- is( ref($provider), 'Koha::Auth::Identity::Provider', "Base class used for $protocol" ); >+ is( ref($provider), $mapping->{$protocol}, "build_object returns correct subclass for $protocol" ); > >- # upgrade >- $provider = $provider->upgrade_class; >- is( >- ref($provider), $mapping->{$protocol}, >- "Class upgraded to " . $mapping->{$protocol} . "for protocol $protocol" >- ); >+ my $found = Koha::Auth::Identity::Providers->find( $provider->id ); >+ is( ref($found), $mapping->{$protocol}, "Providers->find returns correct subclass for $protocol" ); > } > > my $provider = Koha::Auth::Identity::Provider->new( { protocol => 'Invalid' } ); >- throws_ok { $provider->upgrade } >- 'Koha::Exception', >- 'Exception throw on invalid protocol'; >+ throws_ok { $provider->set_config( {} ) } 'Koha::Exception', 'Exception thrown calling set_config on base class'; >+ >+ $schema->storage->txn_rollback; >+}; >+ >+subtest 'find_forced_provider() tests' => sub { >+ >+ plan tests => 8; >+ >+ $schema->storage->txn_begin; >+ >+ # No hostname argument returns undef >+ is( Koha::Auth::Identity::Providers->find_forced_provider(undef), undef, 'Returns undef when hostname is undef' ); >+ is( >+ Koha::Auth::Identity::Providers->find_forced_provider(''), undef, >+ 'Returns undef when hostname is empty string' >+ ); >+ >+ my $provider = $builder->build_object( >+ { class => 'Koha::Auth::Identity::Providers', value => { protocol => 'OIDC', enabled => 1 } } ); >+ >+ my $hostname_record = >+ $builder->build_object( { class => 'Koha::Auth::Hostnames', value => { hostname => 'sso.example.com' } } ); >+ >+ # Hostname linked but force_sso=0 (default) >+ $builder->build_object( >+ { >+ class => 'Koha::Auth::Identity::Provider::Hostnames', >+ value => { >+ identity_provider_id => $provider->id, >+ hostname_id => $hostname_record->hostname_id, >+ is_enabled => 1, >+ force_sso => 0, >+ } >+ } >+ ); >+ is( >+ Koha::Auth::Identity::Providers->find_forced_provider('sso.example.com'), >+ undef, 'Returns undef when hostname is linked but force_sso is 0' >+ ); >+ >+ # Update to force_sso=1 but is_enabled=0 on the hostname link >+ Koha::Auth::Identity::Provider::Hostnames->search( >+ { identity_provider_id => $provider->id, hostname_id => $hostname_record->hostname_id } ) >+ ->update( { force_sso => 1, is_enabled => 0 } ); >+ is( >+ Koha::Auth::Identity::Providers->find_forced_provider('sso.example.com'), >+ undef, 'Returns undef when hostname link has is_enabled=0' >+ ); >+ >+ # Enable the hostname link but disable the provider itself >+ Koha::Auth::Identity::Provider::Hostnames->search( >+ { identity_provider_id => $provider->id, hostname_id => $hostname_record->hostname_id } ) >+ ->update( { is_enabled => 1 } ); >+ $provider->update( { enabled => 0 } ); >+ is( >+ Koha::Auth::Identity::Providers->find_forced_provider('sso.example.com'), >+ undef, 'Returns undef when provider itself is disabled' >+ ); >+ >+ # Re-enable the provider - now all conditions met >+ $provider->update( { enabled => 1 } ); >+ my $forced = Koha::Auth::Identity::Providers->find_forced_provider('sso.example.com'); >+ ok( $forced, 'Returns provider when hostname linked with force_sso=1, is_enabled=1, and provider enabled=1' ); >+ is( >+ ref($forced), 'Koha::Auth::Identity::Provider::OIDC', >+ 'Returned object is the correct Identity Provider subclass' >+ ); >+ is( $forced->id, $provider->id, 'Returns the correct provider' ); > > $schema->storage->txn_rollback; > }; >diff --git a/t/db_dependent/Koha/Auth/Identity/Provider/Mapping.t b/t/db_dependent/Koha/Auth/Identity/Provider/Mapping.t >new file mode 100755 >index 00000000000..b15a4e48b02 >--- /dev/null >+++ b/t/db_dependent/Koha/Auth/Identity/Provider/Mapping.t >@@ -0,0 +1,140 @@ >+#!/usr/bin/perl >+ >+# Copyright Koha Community 2026 >+# >+# 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 <https://www.gnu.org/licenses>. >+ >+use Modern::Perl; >+ >+use Test::NoWarnings; >+use Test::More tests => 3; >+use Test::Exception; >+ >+use Koha::Auth::Hostnames; >+use Koha::Auth::Identity::Provider::Hostname; >+use Koha::Auth::Identity::Provider::Mapping; >+use Koha::Auth::Identity::Provider::Mappings; >+use Koha::Auth::Identity::Providers; >+ >+use t::lib::TestBuilder; >+ >+my $schema = Koha::Database->new->schema; >+my $builder = t::lib::TestBuilder->new; >+ >+subtest 'Mapping::store() tests' => sub { >+ >+ plan tests => 4; >+ >+ $schema->storage->txn_begin; >+ >+ my $provider = $builder->build_object( { class => 'Koha::Auth::Identity::Providers' } ); >+ >+ # Must throw if neither provider_field nor default_content is set >+ throws_ok { >+ Koha::Auth::Identity::Provider::Mapping->new( >+ { >+ identity_provider_id => $provider->id, >+ koha_field => 'userid', >+ } >+ )->store; >+ } >+ 'Koha::Exceptions::MissingParameter', >+ 'Exception thrown when neither provider_field nor default_content is provided'; >+ >+ like( $@->parameter, qr/provider_field or default_content/, 'Exception parameter message is correct' ); >+ >+ # Succeeds with provider_field only >+ my $m1 = Koha::Auth::Identity::Provider::Mapping->new( >+ { >+ identity_provider_id => $provider->id, >+ koha_field => 'userid', >+ provider_field => 'uid', >+ } >+ )->store; >+ ok( $m1, 'store() succeeds when only provider_field is supplied' ); >+ >+ # Succeeds with default_content only >+ my $m2 = Koha::Auth::Identity::Provider::Mapping->new( >+ { >+ identity_provider_id => $provider->id, >+ koha_field => 'categorycode', >+ default_content => 'PT', >+ } >+ )->store; >+ ok( $m2, 'store() succeeds when only default_content is supplied' ); >+ >+ $schema->storage->txn_rollback; >+}; >+ >+subtest 'Mappings::as_auth_mapping() tests' => sub { >+ >+ plan tests => 3; >+ >+ $schema->storage->txn_begin; >+ >+ my $provider = $builder->build_object( { class => 'Koha::Auth::Identity::Providers' } ); >+ >+ my $mapping = $provider->mappings->as_auth_mapping; >+ is_deeply( $mapping, {}, 'Returns empty hashref when provider has no mappings' ); >+ >+ # Create mappings: one with provider_field, one with default_content only >+ $builder->build( >+ { >+ source => 'IdentityProviderMapping', >+ value => { >+ identity_provider_id => $provider->id, >+ koha_field => 'userid', >+ provider_field => 'uid', >+ default_content => undef, >+ } >+ } >+ ); >+ $builder->build( >+ { >+ source => 'IdentityProviderMapping', >+ value => { >+ identity_provider_id => $provider->id, >+ koha_field => 'categorycode', >+ provider_field => undef, >+ default_content => 'PT', >+ } >+ } >+ ); >+ >+ $mapping = $provider->mappings->as_auth_mapping; >+ is_deeply( >+ $mapping, >+ { >+ userid => { is => 'uid', content => undef }, >+ categorycode => { is => undef, content => 'PT' }, >+ }, >+ 'Returns correct mapping hashref with is/content keys for each koha_field' >+ ); >+ >+ # Matchpoint is stored on the hostname association, not the provider >+ my $hostname_obj = $builder->build_object( >+ { class => 'Koha::Auth::Hostnames', value => { hostname => 'matchpoint.example.com' } } ); >+ my $hostname_assoc = Koha::Auth::Identity::Provider::Hostname->new( >+ { >+ identity_provider_id => $provider->id, >+ hostname_id => $hostname_obj->hostname_id, >+ matchpoint => 'userid', >+ } >+ )->store; >+ is( $hostname_assoc->matchpoint, 'userid', 'Hostname matchpoint is stored and retrieved correctly' ); >+ >+ $schema->storage->txn_rollback; >+}; >diff --git a/t/db_dependent/Koha/Plugins/Auth/Client.t b/t/db_dependent/Koha/Plugins/Auth/Client.t >index 4356572eb59..1920b27dc07 100755 >--- a/t/db_dependent/Koha/Plugins/Auth/Client.t >+++ b/t/db_dependent/Koha/Plugins/Auth/Client.t >@@ -56,9 +56,20 @@ subtest 'auth_client_get_user hook tests' => sub { > # Test Plugin manipulates mapped_data > my $plugin = Koha::Plugin::Test->new->enable; > >- my $client = Koha::Auth::Client::OAuth->new; >- my $provider = >- $builder->build_object( { class => 'Koha::Auth::Identity::Providers', value => { matchpoint => 'email' } } ); >+ my $client = Koha::Auth::Client::OAuth->new; >+ my $provider = $builder->build_object( { class => 'Koha::Auth::Identity::Providers' } ); >+ my $hostname_obj = >+ $builder->build_object( { class => 'Koha::Auth::Hostnames', value => { hostname => 'test.library.com' } } ); >+ $builder->build_object( >+ { >+ class => 'Koha::Auth::Identity::Provider::Hostnames', >+ value => { >+ identity_provider_id => $provider->id, >+ hostname_id => $hostname_obj->id, >+ matchpoint => 'email', >+ } >+ } >+ ); > my $domain = $builder->build_object( > { > class => 'Koha::Auth::Identity::Provider::Domains', >@@ -68,14 +79,34 @@ subtest 'auth_client_get_user hook tests' => sub { > } > } > ); >- my $patron = $builder->build_object( { class => 'Koha::Patrons', value => { email => 'patron@test.com' } } ); >- my $mapping = { >- email => 'electronic_mail', >- firstname => 'given_name', >- surname => 'family_name', >- cardnumber => 'cardnumber', >- }; >- $provider->set_mapping($mapping)->store; >+ my $patron = $builder->build_object( { class => 'Koha::Patrons', value => { email => 'patron@test.com' } } ); >+ $builder->build_object( >+ { >+ class => 'Koha::Auth::Identity::Provider::Mappings', >+ value => >+ { identity_provider_id => $provider->id, koha_field => 'email', provider_field => 'electronic_mail' } >+ } >+ ); >+ $builder->build_object( >+ { >+ class => 'Koha::Auth::Identity::Provider::Mappings', >+ value => >+ { identity_provider_id => $provider->id, koha_field => 'firstname', provider_field => 'given_name' } >+ } >+ ); >+ $builder->build_object( >+ { >+ class => 'Koha::Auth::Identity::Provider::Mappings', >+ value => { identity_provider_id => $provider->id, koha_field => 'surname', provider_field => 'family_name' } >+ } >+ ); >+ $builder->build_object( >+ { >+ class => 'Koha::Auth::Identity::Provider::Mappings', >+ value => >+ { identity_provider_id => $provider->id, koha_field => 'cardnumber', provider_field => 'cardnumber' } >+ } >+ ); > > my $id_token = 'header.' . encode_base64url( > encode_json( >@@ -90,7 +121,8 @@ subtest 'auth_client_get_user hook tests' => sub { > my $data = { id_token => $id_token }; > > my ( $resolved_patron, $mapped_data, $resolved_domain ) = >- $client->get_user( { provider => $provider->code, data => $data, interface => 'opac' } ); >+ $client->get_user( >+ { provider => $provider->code, data => $data, interface => 'opac', hostname => 'test.library.com' } ); > is( $mapped_data->{cardnumber}, '12345', 'Plugin manipulated mapped_data successfully' ); > isnt( $resolved_patron->borrowernumber, $patron->borrowernumber, 'Plugin changed the resolved patron' ); > isnt( $resolved_domain->domain, $domain->domain, 'Plugin changed the resolved domain' ); >diff --git a/t/db_dependent/Koha/REST/Plugin/Auth/IdP.t b/t/db_dependent/Koha/REST/Plugin/Auth/IdP.t >index 1c91541ee04..db4765eb37e 100755 >--- a/t/db_dependent/Koha/REST/Plugin/Auth/IdP.t >+++ b/t/db_dependent/Koha/REST/Plugin/Auth/IdP.t >@@ -105,8 +105,7 @@ subtest 'auth.register helper' => sub { > $patron_to_delete_1->delete; > $patron_to_delete_2->delete; > >- my $provider = >- $builder->build_object( { class => 'Koha::Auth::Identity::Providers', value => { matchpoint => 'email' } } ); >+ my $provider = $builder->build_object( { class => 'Koha::Auth::Identity::Providers' } ); > > my $domain_1 = $builder->build_object( > { >-- >2.53.0 >
You cannot view the attachment while viewing its details because your browser does not support IFRAMEs.
View the attachment on a separate page
.
View Attachment As Diff
View Attachment As Raw
Actions:
View
|
Diff
|
Splinter Review
Attachments on
bug 39224
:
189234
|
189235
|
189236
|
189237
|
189238
|
189239
|
189240
|
189241
|
189242
|
189243
|
189244
|
193618
|
193619
|
193620
|
193621
|
193622
|
193623
|
193624
|
193625
|
193626
|
193627
|
193681
|
193682
|
193683
|
193684
|
193685
| 193686 |
193687
|
193688
|
193689
|
193690
|
193691
|
193692