From 0c72794008f761dab44810ccd9c3a3739f48180b Mon Sep 17 00:00:00 2001 From: Tomas Cohen Arazi Date: Tue, 5 Aug 2025 15:33:21 -0300 Subject: [PATCH] Bug 40596: Add CAS and Shibboleth Identity Provider classes Adds identity provider implementations for CAS and Shibboleth protocols: * Koha::Auth::Identity::Provider::CAS - Defines mandatory config attributes for CAS providers (cas_url) * Koha::Auth::Identity::Provider::Shibboleth - Defines mandatory config attributes for Shibboleth providers (sso_url, slo_url, entity_id) These classes extend the base identity provider functionality to support protocol-specific configuration validation. Test plan: 1. Apply patch 2. Verify classes can be instantiated 3. Verify mandatory_config_attributes returns expected values 4. Test with identity provider upgrade_class() method --- Koha/Auth/Identity/Provider/CAS.pm | 70 ++++++++ Koha/Auth/Identity/Provider/Shibboleth.pm | 72 +++++++++ .../Koha/Auth/Identity/Provider/CAS.t | 112 +++++++++++++ .../Koha/Auth/Identity/Provider/Shibboleth.t | 153 ++++++++++++++++++ 4 files changed, 407 insertions(+) create mode 100644 Koha/Auth/Identity/Provider/CAS.pm create mode 100644 Koha/Auth/Identity/Provider/Shibboleth.pm create mode 100755 t/db_dependent/Koha/Auth/Identity/Provider/CAS.t create mode 100755 t/db_dependent/Koha/Auth/Identity/Provider/Shibboleth.t diff --git a/Koha/Auth/Identity/Provider/CAS.pm b/Koha/Auth/Identity/Provider/CAS.pm new file mode 100644 index 00000000000..3d9de7c749b --- /dev/null +++ b/Koha/Auth/Identity/Provider/CAS.pm @@ -0,0 +1,70 @@ +package Koha::Auth::Identity::Provider::CAS; + +# Copyright 2025 Koha Development team +# +# 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 base qw(Koha::Auth::Identity::Provider); + +=head1 NAME + +Koha::Auth::Identity::Provider::CAS - CAS identity provider + +=head1 API + +=head2 Class methods + +=head3 new + +Constructor. Inherits from L. + +=cut + +=head2 Instance methods + +=head3 mandatory_config_attributes + + my @attributes = $provider->mandatory_config_attributes + +Returns the list of mandatory configuration attributes for CAS providers. + +=cut + +sub mandatory_config_attributes { + return qw( + cas_url + ); +} + +=head2 Internal methods + +=head3 _type + +=cut + +sub _type { + return 'IdentityProvider'; +} + +=head1 AUTHOR + +Koha Development Team + +=cut + +1; diff --git a/Koha/Auth/Identity/Provider/Shibboleth.pm b/Koha/Auth/Identity/Provider/Shibboleth.pm new file mode 100644 index 00000000000..d4447cea9c8 --- /dev/null +++ b/Koha/Auth/Identity/Provider/Shibboleth.pm @@ -0,0 +1,72 @@ +package Koha::Auth::Identity::Provider::Shibboleth; + +# Copyright 2025 Koha Development team +# +# 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 base qw(Koha::Auth::Identity::Provider); + +=head1 NAME + +Koha::Auth::Identity::Provider::Shibboleth - Shibboleth identity provider + +=head1 API + +=head2 Class methods + +=head3 new + +Constructor. Inherits from L. + +=cut + +=head2 Instance methods + +=head3 mandatory_config_attributes + + my @attributes = $provider->mandatory_config_attributes + +Returns the list of mandatory configuration attributes for Shibboleth providers. + +=cut + +sub mandatory_config_attributes { + return qw( + sso_url + slo_url + entity_id + ); +} + +=head2 Internal methods + +=head3 _type + +=cut + +sub _type { + return 'IdentityProvider'; +} + +=head1 AUTHOR + +Koha Development Team + +=cut + +1; diff --git a/t/db_dependent/Koha/Auth/Identity/Provider/CAS.t b/t/db_dependent/Koha/Auth/Identity/Provider/CAS.t new file mode 100755 index 00000000000..0a99f8de524 --- /dev/null +++ b/t/db_dependent/Koha/Auth/Identity/Provider/CAS.t @@ -0,0 +1,112 @@ +#!/usr/bin/perl + +# Copyright 2025 Koha Development team +# +# 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::More tests => 5; +use Test::Exception; +use Test::NoWarnings; + +use t::lib::TestBuilder; +use t::lib::Mocks; + +use Koha::Database; +use Koha::Auth::Identity::Providers; + +BEGIN { + use_ok('Koha::Auth::Identity::Provider::CAS'); +} + +my $schema = Koha::Database->schema; +my $builder = t::lib::TestBuilder->new; + +subtest 'CAS provider instantiation' => sub { + plan tests => 3; + + $schema->storage->txn_begin; + + # Test direct instantiation + my $provider_data = { + code => 'cas_test', + description => 'Test CAS Provider', + protocol => 'CAS', + config => '{"cas_url": "https://cas.example.com/cas"}', + mapping => '{"userid": "user", "email": "mail"}', + matchpoint => 'userid' + }; + + my $provider = Koha::Auth::Identity::Provider::CAS->new($provider_data); + isa_ok( $provider, 'Koha::Auth::Identity::Provider::CAS' ); + isa_ok( $provider, 'Koha::Auth::Identity::Provider' ); + is( $provider->protocol, 'CAS', 'Protocol is set correctly' ); + + $schema->storage->txn_rollback; +}; + +subtest 'mandatory_config_attributes() tests' => sub { + plan tests => 2; + + $schema->storage->txn_begin; + + my $provider = Koha::Auth::Identity::Provider::CAS->new(); + my @mandatory = $provider->mandatory_config_attributes(); + + is( scalar @mandatory, 1, 'Returns correct number of mandatory attributes' ); + is( $mandatory[0], 'cas_url', 'Returns cas_url as mandatory attribute' ); + + $schema->storage->txn_rollback; +}; + +subtest 'Configuration validation' => sub { + plan tests => 4; + + $schema->storage->txn_begin; + + my $provider = $builder->build_object( + { + class => 'Koha::Auth::Identity::Providers', + value => { + protocol => 'CAS', + config => '{}', + mapping => '{}', + } + } + ); + + # Upgrade to CAS provider + my $cas_provider = $provider->upgrade_class(); + isa_ok( $cas_provider, 'Koha::Auth::Identity::Provider::CAS' ); + + # Test valid configuration + my $valid_config = { cas_url => 'https://cas.example.com/cas' }; + + lives_ok { $cas_provider->set_config($valid_config) } 'Valid config is accepted'; + + # Test invalid configuration (missing cas_url) + my $invalid_config = { some_other_param => 'value' }; + + throws_ok { + $cas_provider->set_config($invalid_config) + } + 'Koha::Exceptions::MissingParameter', 'Missing cas_url throws exception'; + + is( $@->parameter, 'cas_url', 'Exception indicates missing cas_url parameter' ); + + $schema->storage->txn_rollback; +}; diff --git a/t/db_dependent/Koha/Auth/Identity/Provider/Shibboleth.t b/t/db_dependent/Koha/Auth/Identity/Provider/Shibboleth.t new file mode 100755 index 00000000000..0ea4ea6c636 --- /dev/null +++ b/t/db_dependent/Koha/Auth/Identity/Provider/Shibboleth.t @@ -0,0 +1,153 @@ +#!/usr/bin/perl + +# Copyright 2025 Koha Development team +# +# 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::More tests => 5; +use Test::Exception; +use Test::NoWarnings; + +use t::lib::TestBuilder; +use t::lib::Mocks; + +use Koha::Database; +use Koha::Auth::Identity::Providers; + +BEGIN { + use_ok('Koha::Auth::Identity::Provider::Shibboleth'); +} + +my $schema = Koha::Database->schema; +my $builder = t::lib::TestBuilder->new; + +subtest 'Shibboleth provider instantiation' => sub { + plan tests => 3; + + $schema->storage->txn_begin; + + # Test direct instantiation + my $provider_data = { + code => 'shibboleth_test', + description => 'Test Shibboleth Provider', + protocol => 'Shibboleth', + config => + '{"sso_url": "https://idp.example.com/sso", "slo_url": "https://idp.example.com/slo", "entity_id": "https://idp.example.com"}', + mapping => '{"userid": "REMOTE_USER", "email": "mail"}', + matchpoint => 'userid' + }; + + my $provider = Koha::Auth::Identity::Provider::Shibboleth->new($provider_data); + isa_ok( $provider, 'Koha::Auth::Identity::Provider::Shibboleth' ); + isa_ok( $provider, 'Koha::Auth::Identity::Provider' ); + is( $provider->protocol, 'Shibboleth', 'Protocol is set correctly' ); + + $schema->storage->txn_rollback; +}; + +subtest 'mandatory_config_attributes() tests' => sub { + plan tests => 5; + + $schema->storage->txn_begin; + + my $provider = Koha::Auth::Identity::Provider::Shibboleth->new(); + my @mandatory = $provider->mandatory_config_attributes(); + + is( scalar @mandatory, 3, 'Returns correct number of mandatory attributes' ); + + my %mandatory_hash = map { $_ => 1 } @mandatory; + ok( exists $mandatory_hash{'sso_url'}, 'sso_url is mandatory' ); + ok( exists $mandatory_hash{'slo_url'}, 'slo_url is mandatory' ); + ok( exists $mandatory_hash{'entity_id'}, 'entity_id is mandatory' ); + + # Verify order (should be consistent) + is_deeply( \@mandatory, [qw(sso_url slo_url entity_id)], 'Mandatory attributes in expected order' ); + + $schema->storage->txn_rollback; +}; + +subtest 'Configuration validation' => sub { + plan tests => 8; + + $schema->storage->txn_begin; + + my $provider = $builder->build_object( + { + class => 'Koha::Auth::Identity::Providers', + value => { + protocol => 'Shibboleth', + config => '{}', + mapping => '{}', + } + } + ); + + # Upgrade to Shibboleth provider + my $shibboleth_provider = $provider->upgrade_class(); + isa_ok( $shibboleth_provider, 'Koha::Auth::Identity::Provider::Shibboleth' ); + + # Test valid configuration + my $valid_config = { + sso_url => 'https://idp.example.com/sso', + slo_url => 'https://idp.example.com/slo', + entity_id => 'https://idp.example.com' + }; + + lives_ok { $shibboleth_provider->set_config($valid_config) } 'Valid config is accepted'; + + # Test invalid configuration (missing sso_url) + my $invalid_config1 = { + slo_url => 'https://idp.example.com/slo', + entity_id => 'https://idp.example.com' + }; + + throws_ok { + $shibboleth_provider->set_config($invalid_config1) + } + 'Koha::Exceptions::MissingParameter', 'Missing sso_url throws exception'; + + is( $@->parameter, 'sso_url', 'Exception indicates missing sso_url parameter' ); + + # Test invalid configuration (missing slo_url) + my $invalid_config2 = { + sso_url => 'https://idp.example.com/sso', + entity_id => 'https://idp.example.com' + }; + + throws_ok { + $shibboleth_provider->set_config($invalid_config2) + } + 'Koha::Exceptions::MissingParameter', 'Missing slo_url throws exception'; + + is( $@->parameter, 'slo_url', 'Exception indicates missing slo_url parameter' ); + + # Test invalid configuration (missing entity_id) + my $invalid_config3 = { + sso_url => 'https://idp.example.com/sso', + slo_url => 'https://idp.example.com/slo' + }; + + throws_ok { + $shibboleth_provider->set_config($invalid_config3) + } + 'Koha::Exceptions::MissingParameter', 'Missing entity_id throws exception'; + + is( $@->parameter, 'entity_id', 'Exception indicates missing entity_id parameter' ); + + $schema->storage->txn_rollback; +}; -- 2.50.1