Bugzilla – Attachment 185173 Details for
Bug 40596
Migrate CAS and Shibboleth into the identity providers
Home
|
New
|
Browse
|
Search
|
[?]
|
Reports
|
Help
|
New Account
|
Log In
[x]
|
Forgot Password
Login:
[x]
[patch]
Bug 40596: Add CAS and Shibboleth compatibility layers
Bug-40596-Add-CAS-and-Shibboleth-compatibility-lay.patch (text/plain), 15.72 KB, created by
Tomás Cohen Arazi (tcohen)
on 2025-08-06 20:05:10 UTC
(
hide
)
Description:
Bug 40596: Add CAS and Shibboleth compatibility layers
Filename:
MIME Type:
Creator:
Tomás Cohen Arazi (tcohen)
Created:
2025-08-06 20:05:10 UTC
Size:
15.72 KB
patch
obsolete
>From 99864cacf49064d91b221f8e78199d8ccf2afb2f Mon Sep 17 00:00:00 2001 >From: Tomas Cohen Arazi <tomascohen@theke.io> >Date: Tue, 5 Aug 2025 15:34:54 -0300 >Subject: [PATCH] Bug 40596: Add CAS and Shibboleth compatibility layers > >Provides compatibility layers between legacy authentication systems and >modern identity provider architecture: > >Koha::Auth::CASCompat: >* get_cas_providers() - Returns CAS configuration in legacy format >* authenticate_cas_user() - Bridges legacy checkpw_cas interface to modern clients >* Supports both modern identity providers and legacy system preferences > >Koha::Auth::ShibbolethCompat: >* get_shibboleth_provider() - Creates provider from legacy or modern config >* authenticate_shibboleth_user() - Bridges legacy checkpw_shib interface >* get_shibboleth_config() - Returns config in legacy format >* Handles attribute mapping and user auto-creation/updates > >These layers allow existing code to continue working unchanged while >using modern authentication internally. > >Test plan: >1. Apply patch >2. Test CAS authentication with existing configurations >3. Test Shibboleth authentication with existing configurations >4. Verify user auto-creation and updates work as expected >5. Run t/db_dependent/Auth_with_cas.t >6. Run t/db_dependent/Auth_with_shibboleth.t >--- > Koha/Auth/CASCompat.pm | 186 ++++++++++++++++++++++++++ > Koha/Auth/ShibbolethCompat.pm | 244 ++++++++++++++++++++++++++++++++++ > 2 files changed, 430 insertions(+) > create mode 100644 Koha/Auth/CASCompat.pm > create mode 100644 Koha/Auth/ShibbolethCompat.pm > >diff --git a/Koha/Auth/CASCompat.pm b/Koha/Auth/CASCompat.pm >new file mode 100644 >index 00000000000..5b471b9c0e5 >--- /dev/null >+++ b/Koha/Auth/CASCompat.pm >@@ -0,0 +1,186 @@ >+package Koha::Auth::CASCompat; >+ >+# Copyright 2024 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 <http://www.gnu.org/licenses>. >+ >+use Modern::Perl; >+ >+use C4::Context; >+use Try::Tiny qw( catch try ); >+ >+use Koha::Auth::Client::CAS; >+use Koha::Auth::Identity::Providers; >+use Koha::Logger; >+ >+=head1 NAME >+ >+Koha::Auth::CASCompat - CAS compatibility layer >+ >+=head1 SYNOPSIS >+ >+ use Koha::Auth::CASCompat; >+ >+ my ($success, $cardnumber, $userid, $cas_ticket, $patron) = >+ Koha::Auth::CASCompat::authenticate_cas_user($ticket, $query, $type, $cas_server); >+ >+=head1 DESCRIPTION >+ >+This module provides a compatibility layer between the legacy CAS authentication >+system and the modern identity provider architecture. It allows existing code >+to continue working while using the new client-based authentication internally. >+ >+=head1 FUNCTIONS >+ >+=head2 get_cas_providers >+ >+ my ($default_cas, $casservers) = Koha::Auth::CASCompat::get_cas_providers(); >+ >+Returns CAS provider configuration in the format expected by legacy code. >+ >+=cut >+ >+sub get_cas_providers { >+ my $logger = Koha::Logger->get(); >+ >+ # Try to find modern CAS providers >+ my $providers = Koha::Auth::Identity::Providers->search( { protocol => 'CAS' } ); >+ >+ if ( $providers->count ) { >+ my $casservers = {}; >+ my $default_cas; >+ >+ while ( my $provider = $providers->next ) { >+ my $config = $provider->get_config; >+ $casservers->{ $provider->code } = $config->{cas_url}; >+ $default_cas = $provider->code unless $default_cas; >+ } >+ >+ $logger->debug( "Found " . $providers->count . " modern CAS providers" ); >+ return ( $default_cas, $casservers ); >+ } >+ >+ # Fall back to legacy configuration >+ my $cas_url = C4::Context->preference('casServerUrl'); >+ if ($cas_url) { >+ $logger->debug("Using legacy CAS configuration"); >+ return ( 'default', { 'default' => $cas_url } ); >+ } >+ >+ $logger->warn("No CAS configuration found"); >+ return ( undef, {} ); >+} >+ >+=head2 authenticate_cas_user >+ >+ my ($success, $cardnumber, $userid, $cas_ticket, $patron) = >+ Koha::Auth::CASCompat::authenticate_cas_user($ticket, $query, $type, $cas_server); >+ >+Authenticates a CAS user using the modern implementation but returns data >+in the format expected by the legacy interface. >+ >+=cut >+ >+sub authenticate_cas_user { >+ my ( $ticket, $query, $type, $cas_server ) = @_; >+ >+ my $logger = Koha::Logger->get(); >+ >+ return (0) unless $ticket; >+ >+ # Determine service URL >+ my $service_url = _build_service_url( $query, $type ); >+ return (0) unless $service_url; >+ >+ try { >+ # Try to find a modern CAS provider >+ my $provider_code = $cas_server || 'default'; >+ my $provider = Koha::Auth::Identity::Providers->search( { protocol => 'CAS', code => $provider_code } )->next; >+ >+ if ($provider) { >+ >+ # Use modern client >+ my $client = Koha::Auth::Client::CAS->new( { provider => $provider } ); >+ my ( $user_data, $patron ) = $client->get_user( >+ { >+ ticket => $ticket, >+ service => $service_url >+ } >+ ); >+ >+ if ($patron) { >+ return ( >+ 1, # success >+ $patron->cardnumber, # cardnumber >+ $patron->userid, # userid >+ $ticket, # cas_ticket >+ $patron # patron object >+ ); >+ } >+ } else { >+ >+ # Fall back to legacy-style authentication >+ my $cas_url = C4::Context->preference('casServerUrl'); >+ if ($cas_url) { >+ >+ # Create a temporary provider-like config >+ my $config = { cas_url => $cas_url }; >+ my $client = Koha::Auth::Client::CAS->new(); >+ >+ # This would need more implementation for full legacy support >+ $logger->warn("Legacy CAS authentication not fully implemented"); >+ } >+ } >+ } catch { >+ $logger->error("CAS authentication error: $_"); >+ }; >+ >+ return (0); # failure >+} >+ >+=head2 Internal functions >+ >+=head3 _build_service_url >+ >+ my $service_url = _build_service_url($query, $type); >+ >+Builds the service URL for CAS validation from the query parameters. >+ >+=cut >+ >+sub _build_service_url { >+ my ( $query, $type ) = @_; >+ >+ return unless $query; >+ >+ # Build service URL based on interface type >+ my $base_url; >+ if ( $type eq 'opac' ) { >+ $base_url = C4::Context->preference('OPACBaseURL'); >+ } else { >+ $base_url = C4::Context->preference('staffClientBaseURL'); >+ } >+ >+ return unless $base_url; >+ >+ # Remove ticket parameter and build clean URL >+ my $url = $query->url( -absolute => 1, -query => 1 ); >+ $url =~ s/[&?]ticket=[^&]*//g; >+ >+ return $base_url . $url; >+} >+ >+1; >diff --git a/Koha/Auth/ShibbolethCompat.pm b/Koha/Auth/ShibbolethCompat.pm >new file mode 100644 >index 00000000000..cce2c5121d1 >--- /dev/null >+++ b/Koha/Auth/ShibbolethCompat.pm >@@ -0,0 +1,244 @@ >+package Koha::Auth::ShibbolethCompat; >+ >+# Copyright 2024 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 <http://www.gnu.org/licenses>. >+ >+use Modern::Perl; >+ >+use C4::Context; >+use JSON qw( encode_json ); >+use Try::Tiny qw( catch try ); >+ >+use Koha::Auth::Client::Shibboleth; >+use Koha::Auth::Identity::Providers; >+use Koha::Logger; >+use Koha::Patrons; >+ >+=head1 NAME >+ >+Koha::Auth::ShibbolethCompat - Shibboleth compatibility layer >+ >+=head1 SYNOPSIS >+ >+ use Koha::Auth::ShibbolethCompat; >+ >+ my ($success, $cardnumber, $userid, $patron) = >+ Koha::Auth::ShibbolethCompat::authenticate_shibboleth_user($shib_login); >+ >+=head1 DESCRIPTION >+ >+This module provides a compatibility layer between the legacy Shibboleth authentication >+system and the modern identity provider architecture. It allows existing code >+to continue working while using the new client-based authentication internally. >+ >+=head1 FUNCTIONS >+ >+=head2 get_shibboleth_provider >+ >+ my $provider = Koha::Auth::ShibbolethCompat::get_shibboleth_provider(); >+ >+Returns a Shibboleth identity provider, either modern or created from legacy config. >+ >+=cut >+ >+sub get_shibboleth_provider { >+ my $logger = Koha::Logger->get(); >+ >+ # Try to find existing identity provider >+ my $provider = Koha::Auth::Identity::Providers->search( { protocol => 'Shibboleth' } )->next; >+ >+ if ($provider) { >+ $logger->debug( "Found Shibboleth identity provider: " . $provider->code ); >+ return $provider->upgrade_class; >+ } >+ >+ # Fall back to creating temporary provider from legacy config >+ my $shibboleth_config = C4::Context->config('shibboleth'); >+ >+ if ($shibboleth_config) { >+ $logger->debug("Using legacy Shibboleth configuration"); >+ >+ # Create temporary provider object (not stored in database) >+ require Koha::Auth::Identity::Provider::Shibboleth; >+ my $temp_provider = Koha::Auth::Identity::Provider::Shibboleth->new( >+ { >+ code => 'shibboleth', >+ description => "Legacy Shibboleth SSO", >+ config => encode_json( >+ { >+ sso_url => $shibboleth_config->{ssoUrl} || '/Shibboleth.sso/Login', >+ slo_url => $shibboleth_config->{sloUrl} || '/Shibboleth.sso/Logout', >+ entity_id => $shibboleth_config->{entityID} || 'koha' >+ } >+ ), >+ mapping => encode_json( >+ { >+ userid => $shibboleth_config->{mapping}->{userid} || 'HTTP_REMOTE_USER', >+ email => $shibboleth_config->{mapping}->{email} || 'HTTP_MAIL', >+ firstname => $shibboleth_config->{mapping}->{firstname} || 'HTTP_GIVENNAME', >+ surname => $shibboleth_config->{mapping}->{surname} || 'HTTP_SN' >+ } >+ ), >+ matchpoint => $shibboleth_config->{matchpoint} || 'userid' >+ } >+ ); >+ >+ # Don't store - this is just for compatibility >+ return $temp_provider; >+ } >+ >+ $logger->warn("No Shibboleth configuration found"); >+ return; >+} >+ >+=head2 authenticate_shibboleth_user >+ >+ my ($success, $cardnumber, $userid, $patron) = >+ Koha::Auth::ShibbolethCompat::authenticate_shibboleth_user($shib_login); >+ >+Authenticates a Shibboleth user using the modern implementation but returns data >+in the format expected by the legacy interface. >+ >+=cut >+ >+sub authenticate_shibboleth_user { >+ my ($shib_login) = @_; >+ >+ my $logger = Koha::Logger->get(); >+ >+ return (0) unless $shib_login; >+ >+ # Get legacy Shibboleth configuration >+ my $config = get_shibboleth_config(); >+ return (0) unless $config; >+ >+ # Extract Shibboleth attributes from environment >+ my $mapped_data = {}; >+ my $mapping = $config->{mapping} || {}; >+ >+ foreach my $koha_field ( keys %$mapping ) { >+ my $shib_attribute_config = $mapping->{$koha_field}; >+ >+ # Handle legacy format: { 'is' => 'attribute_name' } >+ my $shib_attribute; >+ if ( ref($shib_attribute_config) eq 'HASH' && exists $shib_attribute_config->{is} ) { >+ $shib_attribute = $shib_attribute_config->{is}; >+ } else { >+ >+ # Handle simple string format >+ $shib_attribute = $shib_attribute_config; >+ } >+ >+ if ( exists $ENV{$shib_attribute} ) { >+ $mapped_data->{$koha_field} = $ENV{$shib_attribute}; >+ } >+ } >+ >+ return (0) unless %$mapped_data; >+ >+ # Find existing patron using matchpoint >+ my $matchpoint = $config->{matchpoint} || 'userid'; >+ my $patron; >+ >+ if ( my $match_value = $mapped_data->{$matchpoint} ) { >+ my $patron_rs = Koha::Patrons->search( { $matchpoint => $match_value } ); >+ if ( $patron_rs->count ) { >+ $patron = $patron_rs->next; >+ $logger->debug( "Found existing patron: " . $patron->borrowernumber ); >+ >+ # Update patron if sync is enabled >+ if ( $config->{sync} ) { >+ >+ # Remove fields that shouldn't be updated >+ my $update_data = {%$mapped_data}; >+ delete $update_data->{borrowernumber}; >+ delete $update_data->{categorycode}; >+ delete $update_data->{branchcode}; >+ >+ $patron->set($update_data)->store; >+ $logger->debug( "Updated patron data for: " . $patron->userid ); >+ } >+ } >+ } >+ >+ # Auto-create user if enabled and no existing patron found >+ if ( !$patron && $config->{autocreate} ) { >+ $logger->debug("Auto-creating new patron for: $shib_login"); >+ >+ # Set required fields from environment or defaults >+ $mapped_data->{categorycode} = $ENV{cat} || $ENV{categorycode} || 'PT'; # Default patron category >+ $mapped_data->{branchcode} = $ENV{branchcode} || C4::Context->userenv->{branch} || 'CPL'; >+ >+ try { >+ $patron = Koha::Patron->new($mapped_data)->store; >+ $logger->info( "Auto-created patron: " . $patron->userid ); >+ } catch { >+ $logger->error("Failed to auto-create patron: $_"); >+ return (0); >+ }; >+ } >+ >+ if ($patron) { >+ $logger->info( "Shibboleth authentication successful for user: " . $patron->userid ); >+ return ( >+ 1, # success >+ $patron->cardnumber, # cardnumber >+ $patron->userid, # userid >+ $patron # patron object >+ ); >+ } else { >+ $logger->warn("Shibboleth authentication failed - no patron found/created for: $shib_login"); >+ } >+ >+ return (0); # failure >+} >+ >+=head2 get_shibboleth_config >+ >+ my $config = Koha::Auth::ShibbolethCompat::get_shibboleth_config(); >+ >+Returns Shibboleth configuration in the legacy format expected by C4::Auth_with_shibboleth. >+ >+=cut >+ >+sub get_shibboleth_config { >+ >+ # Try modern provider first >+ my $providers = Koha::Auth::Identity::Providers->search( { protocol => 'Shibboleth' } ); >+ if ( $providers->count ) { >+ my $provider = $providers->next->upgrade_class; >+ my $config = $provider->get_config; >+ my $mapping = $provider->get_mapping; >+ >+ # Convert to legacy format >+ return { >+ matchpoint => $provider->matchpoint, >+ mapping => $mapping, >+ ssoUrl => $config->{sso_url}, >+ sloUrl => $config->{slo_url}, >+ entityID => $config->{entity_id} >+ }; >+ } >+ >+ # Fall back to legacy configuration directly >+ # The test mocks C4::Context->config to return the shibboleth config directly >+ my $config = C4::Context->config('shibboleth') || C4::Context->config(); >+ >+ return $config; >+} >+ >+1; >-- >2.50.1
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 40596
:
185168
|
185169
|
185170
|
185171
|
185172
| 185173 |
185174
|
185175
|
185176
|
185177