Bugzilla – Attachment 195116 Details for
Bug 40596
Migrate CAS configuration into Identity Providers
Home
|
New
|
Browse
|
Search
|
[?]
|
Reports
|
Help
|
New Account
|
Log In
[x]
|
Forgot Password
Login:
[x]
[patch]
Bug 40596: Migrate C4::Auth_with_cas to use identity providers
4977ac0.patch (text/plain), 15.22 KB, created by
Martin Renvoize (ashimema)
on 2026-03-11 10:08:00 UTC
(
hide
)
Description:
Bug 40596: Migrate C4::Auth_with_cas to use identity providers
Filename:
MIME Type:
Creator:
Martin Renvoize (ashimema)
Created:
2026-03-11 10:08:00 UTC
Size:
15.22 KB
patch
obsolete
>From 4977ac0af11dabfcd39c4df08b17aaffbef4dd99 Mon Sep 17 00:00:00 2001 >From: Martin Renvoize <martin.renvoize@openfifth.co.uk> >Date: Wed, 11 Mar 2026 10:05:41 +0000 >Subject: [PATCH] Bug 40596: Migrate C4::Auth_with_cas to use identity > providers > >Removes the legacy YAML file and casServerUrl/casServerVersion syspref >lookups. All CAS server configuration is now read from the >identity_providers table via Koha::Auth::Identity::Providers. > >Key changes: > >- multipleAuth() and getMultipleAuth() query enabled CAS providers from > the database instead of reading Auth_cas_servers.yaml. >- login_cas_url(), login_cas(), and checkpw_cas() look up the provider > by code; the provider code is encoded into the CAS service URL as > cas_provider=<code> so it is echoed back with the ticket redirect. >- logout_cas() accepts an optional provider code parameter; the CAS > protocol version is read from the provider's config rather than the > casServerVersion system preference. >- check_api_auth_cas() uses the same provider lookup for proxy tickets. >- Private helper _get_cas_provider() resolves a provider by code or > returns the first enabled CAS provider as a default. >--- > C4/Auth_with_cas.pm | 296 ++++++++++++++++++++------------------------ > 1 file changed, 137 insertions(+), 159 deletions(-) > >diff --git a/C4/Auth_with_cas.pm b/C4/Auth_with_cas.pm >index c21db81d1fd..2e005f36fd5 100644 >--- a/C4/Auth_with_cas.pm >+++ b/C4/Auth_with_cas.pm >@@ -29,254 +29,179 @@ use C4::Context; > use Koha::AuthUtils qw( get_script_name ); > use Authen::CAS::Client; > use CGI qw ( -utf8 ); >-use YAML::XS; > use URI::Escape; > >+use Koha::Auth::Identity::Providers; > use Koha::Logger; > >-my $defaultcasserver; >-my $casservers; >-my $yamlauthfile = C4::Context->config('intranetdir') . "/C4/Auth_cas_servers.yaml"; >- >-# If there's a configuration for multiple cas servers, then we get it >-if ( multipleAuth() ) { >- ( $defaultcasserver, $casservers ) = YAML::XS::LoadFile($yamlauthfile); >- $defaultcasserver = $defaultcasserver->{'default'}; >-} else { >- >- # Else, we fall back to casServerUrl syspref >- $defaultcasserver = 'default'; >- $casservers = { 'default' => C4::Context->preference('casServerUrl') }; >-} >- > =head1 Subroutines > > =cut > >-# Is there a configuration file for multiple cas servers? >- > =head2 multipleAuth > >-Missing POD for multipleAuth. >+Returns true when more than one enabled CAS identity provider is configured. > > =cut > > sub multipleAuth { >- return ( -e qq($yamlauthfile) ); >+ return Koha::Auth::Identity::Providers->search( { protocol => 'CAS', enabled => 1 } )->count > 1; > } > >-# Returns configured CAS servers' list if multiple authentication is enabled >- > =head2 getMultipleAuth > >-Missing POD for getMultipleAuth. >+Returns a hashref of C<< { provider_code => server_url } >> for all enabled >+CAS identity providers. > > =cut > > sub getMultipleAuth { >- return $casservers; >+ my %servers; >+ my $providers = Koha::Auth::Identity::Providers->search( { protocol => 'CAS', enabled => 1 } ); >+ while ( my $p = $providers->next ) { >+ $servers{ $p->code } = $p->get_config->{server_url}; >+ } >+ return \%servers; > } > >-# Logout from CAS >- > =head2 logout_cas > >-Missing POD for logout_cas. >+Redirect the browser to the CAS server logout URL. C<$provider_code> is the >+identity provider code stored in the session; if omitted the first enabled >+CAS provider is used. > > =cut > > sub logout_cas { >- my ( $query, $type ) = @_; >- my ( $cas, $uri ) = _get_cas_and_service( $query, undef, $type ); >+ my ( $query, $type, $provider_code ) = @_; >+ my $provider = _get_cas_provider($provider_code); >+ return unless $provider; >+ >+ my $config = $provider->get_config; >+ my $uri = _url_with_get_params( $query, $type ); > > # We don't want to keep triggering a logout, if we got here, > # the borrower is already logged out of Koha > $uri =~ s/\?logout\.x=1//; > >+ my $cas = Authen::CAS::Client->new( $config->{server_url} ); > my $logout_url = $cas->logout_url( url => $uri ); >- $logout_url =~ s/url=/service=/ >- if C4::Context->preference('casServerVersion') eq '3'; >+ my $version = $config->{version} || '2'; >+ $logout_url =~ s/url=/service=/ if $version eq '3'; > > print $query->redirect($logout_url); > } > >-# Login to CAS >- > =head2 login_cas > >-Missing POD for login_cas. >+Redirect the browser to the CAS login URL. > > =cut > > sub login_cas { > my ( $query, $type ) = @_; >- my ( $cas, $uri ) = _get_cas_and_service( $query, undef, $type ); >+ my $provider = _get_cas_provider( $query->param('cas_provider') ); >+ return unless $provider; >+ my $uri = _url_with_get_params_for_provider( $query, $type, $provider ); >+ my $cas = Authen::CAS::Client->new( $provider->get_config->{server_url} ); > print $query->redirect( $cas->login_url($uri) ); > } > >-# Returns CAS login URL with callback to the requesting URL >- > =head2 login_cas_url > >-Missing POD for login_cas_url. >+Returns the CAS login URL for the provider identified by C<$key> (a provider >+code), or the first enabled CAS provider when C<$key> is not given. >+ >+The provider code is encoded into the returned service URL so that it is >+available when CAS redirects back with a ticket. > > =cut > > sub login_cas_url { > my ( $query, $key, $type ) = @_; >- my ( $cas, $uri ) = _get_cas_and_service( $query, $key, $type ); >+ my $provider = _get_cas_provider($key); >+ return undef unless $provider; >+ my $uri = _url_with_get_params_for_provider( $query, $type, $provider ); >+ my $cas = Authen::CAS::Client->new( $provider->get_config->{server_url} ); > return $cas->login_url($uri); > } > >-# Checks for password correctness >-# In our case : is there a ticket, is it valid and does it match one of our users ? >- > =head2 checkpw_cas > >-Missing POD for checkpw_cas. >+Validates a CAS service ticket. The identity provider is identified by the >+C<cas_provider> CGI parameter that CAS echoed back in the redirect URL. >+ >+Returns C<(1, cardnumber, userid, ticket, patron)> on success or C<0> on >+failure. > > =cut > > sub checkpw_cas { > my ( $ticket, $query, $type ) = @_; >- my $retnumber; >- my ( $cas, $uri ) = _get_cas_and_service( $query, undef, $type ); >- >- # If we got a ticket >- if ($ticket) { >- >- # We try to validate it >- my $val = $cas->service_validate( $uri, $ticket ); >- >- # If it's valid >- if ( $val->is_success() ) { >- >- my $userid = $val->user(); >+ return 0 unless $ticket; > >- # we should store the CAS ticekt too, we need this for single logout https://apereo.github.io/cas/4.2.x/protocol/CAS-Protocol-Specification.html#233-single-logout >+ my $provider = _get_cas_provider( $query->param('cas_provider') ); >+ return 0 unless $provider; > >- # Does it match one of our users ? >- my $dbh = C4::Context->dbh; >- my $patron = Koha::Patrons->find_by_identifier($userid); >- if ($patron) { >- return ( 1, $patron->cardnumber, $patron->userid, $ticket, $patron ); >- } >+ my $config = $provider->get_config; >+ my $uri = _url_with_get_params_for_provider( $query, $type, $provider ); >+ my $cas = Authen::CAS::Client->new( $config->{server_url} ); > >- # If we reach this point, then the user is a valid CAS user, but not a Koha user >- Koha::Logger->get->info("User $userid is not a valid Koha user"); >+ my $val = $cas->service_validate( $uri, $ticket ); > >- } else { >- my $logger = Koha::Logger->get; >- $logger->debug("Problem when validating ticket : $ticket"); >- $logger->debug( "Authen::CAS::Client::Response::Error: " . $val->error() ) if $val->is_error(); >- $logger->debug( "Authen::CAS::Client::Response::Failure: " . $val->message() ) if $val->is_failure(); >- $logger->debug( Data::Dumper::Dumper($@) ) if $val->is_error() or $val->is_failure(); >- return 0; >+ if ( $val->is_success() ) { >+ my $userid = $val->user(); >+ my $patron = Koha::Patrons->find_by_identifier($userid); >+ if ($patron) { >+ return ( 1, $patron->cardnumber, $patron->userid, $ticket, $patron ); > } >+ Koha::Logger->get->info( "CAS provider '" . $provider->code . "': user $userid is not a valid Koha user" ); >+ } else { >+ my $logger = Koha::Logger->get; >+ $logger->debug( "CAS provider '" . $provider->code . "': problem validating ticket: $ticket" ); >+ $logger->debug( "Authen::CAS::Client::Response::Error: " . $val->error() ) if $val->is_error(); >+ $logger->debug( "Authen::CAS::Client::Response::Failure: " . $val->message() ) if $val->is_failure(); > } >+ > return 0; > } > >-# Proxy CAS auth >- > =head2 check_api_auth_cas > >-Missing POD for check_api_auth_cas. >+Proxy CAS ticket validation used by the API auth path. > > =cut > > sub check_api_auth_cas { > my ( $PT, $query, $type ) = @_; >- my $retnumber; >- my ( $cas, $uri ) = _get_cas_and_service( $query, undef, $type ); >- >- # If we have a Proxy Ticket >- if ($PT) { >- my $r = $cas->proxy_validate( $uri, $PT ); >- >- # If the PT is valid >- if ( $r->is_success ) { >- >- # We've got a username ! >- my $userid = $r->user; >- >- # we should store the CAS ticket too, we need this for single logout https://apereo.github.io/cas/4.2.x/protocol/CAS-Protocol-Specification.html#233-single-logout >- >- # Does it match one of our users ? >- my $dbh = C4::Context->dbh; >- my $sth = $dbh->prepare("select cardnumber from borrowers where userid=?"); >- $sth->execute($userid); >- if ( $sth->rows ) { >- $retnumber = $sth->fetchrow; >- return ( 1, $retnumber, $userid, $PT ); >- } >- $sth = $dbh->prepare("select userid from borrowers where cardnumber=?"); >- return $r->user; >- $sth->execute($userid); >- if ( $sth->rows ) { >- $retnumber = $sth->fetchrow; >- return ( 1, $retnumber, $userid, $PT ); >- } >- >- # If we reach this point, then the user is a valid CAS user, but not a Koha user >- Koha::Logger->get->info("User $userid is not a valid Koha user"); >- >- } else { >- Koha::Logger->get->debug("Proxy Ticket authentication failed"); >- return 0; >- } >- } >- return 0; >-} >- >-# Get CAS handler and service URI >-sub _get_cas_and_service { >- my $query = shift; >- my $key = shift; # optional >- my $type = shift; >- >- my $uri = _url_with_get_params( $query, $type ); >- >- my $casparam = $defaultcasserver; >- $casparam = $query->param('cas') if defined $query->param('cas'); >- $casparam = $key if defined $key; >- my $cas = Authen::CAS::Client->new( $casservers->{$casparam} ); >- >- return ( $cas, $uri ); >-} >+ return 0 unless $PT; > >-# Get the current URL with parameters contained directly into URL (GET params) >-# This method replaces $query->url() which will give both GET and POST params >-sub _url_with_get_params { >- my $query = shift; >- my $type = shift; >+ my $provider = _get_cas_provider( $query->param('cas_provider') ); >+ return 0 unless $provider; > >- my $uri_base_part = >- ( $type eq 'opac' ) >- ? C4::Context->preference('OPACBaseURL') >- : C4::Context->preference('staffClientBaseURL'); >- $uri_base_part .= get_script_name(); >+ my $config = $provider->get_config; >+ my $uri = _url_with_get_params_for_provider( $query, $type, $provider ); >+ my $cas = Authen::CAS::Client->new( $config->{server_url} ); > >- my $uri_params_part = ''; >- foreach my $param ( $query->url_param() ) { >+ my $r = $cas->proxy_validate( $uri, $PT ); > >- # url_param() always returns parameters that were deleted by delete() >- # This additional check ensure that parameter was not deleted. >- my $uriPiece = $query->param($param); >- if ($uriPiece) { >- $uri_params_part .= '&' if $uri_params_part; >- $uri_params_part .= $param . '='; >- $uri_params_part .= URI::Escape::uri_escape($uriPiece); >+ if ( $r->is_success ) { >+ my $userid = $r->user; >+ my $patron = Koha::Patrons->find_by_identifier($userid); >+ if ($patron) { >+ return ( 1, $patron->cardnumber, $userid, $PT ); > } >+ Koha::Logger->get->info("CAS proxy user $userid is not a valid Koha user"); >+ } else { >+ Koha::Logger->get->debug("CAS proxy ticket authentication failed"); > } >- $uri_base_part .= '?' if $uri_params_part; > >- return $uri_base_part . $uri_params_part; >+ return 0; > } > > =head2 logout_if_required > >- If using CAS, this subroutine will trigger single-signout of the CAS server. >+If using CAS, this subroutine will trigger single-signout of the CAS server. > > =cut > >@@ -320,23 +245,76 @@ sub delete_cas_session { > } > } > >+# Returns an enabled CAS identity provider by code, or the first enabled one >+# when $code is not given. >+sub _get_cas_provider { >+ my ($code) = @_; >+ if ($code) { >+ return Koha::Auth::Identity::Providers->search( { code => $code, protocol => 'CAS', enabled => 1 } )->next; >+ } >+ return Koha::Auth::Identity::Providers->search( >+ { protocol => 'CAS', enabled => 1 }, >+ { order_by => 'identity_provider_id' } >+ )->next; >+} >+ >+# Build the service URL for $provider, appending cas_provider=<code> so that >+# CAS echoes it back in the redirect and we can identify the provider on return. >+sub _url_with_get_params_for_provider { >+ my ( $query, $type, $provider ) = @_; >+ my $uri = _url_with_get_params( $query, $type ); >+ my $sep = ( $uri =~ /\?/ ) ? '&' : '?'; >+ return $uri . $sep . 'cas_provider=' . URI::Escape::uri_escape( $provider->code ); >+} >+ >+# Get the current URL with parameters contained directly in the URL (GET params). >+# This method replaces $query->url() which gives both GET and POST params. >+sub _url_with_get_params { >+ my $query = shift; >+ my $type = shift; >+ >+ my $uri_base_part = >+ ( $type eq 'opac' ) >+ ? C4::Context->preference('OPACBaseURL') >+ : C4::Context->preference('staffClientBaseURL'); >+ $uri_base_part .= get_script_name(); >+ >+ my $uri_params_part = ''; >+ foreach my $param ( $query->url_param() ) { >+ >+ # url_param() always returns parameters that were deleted by delete() >+ # This additional check ensures that parameter was not deleted. >+ my $uriPiece = $query->param($param); >+ if ($uriPiece) { >+ $uri_params_part .= '&' if $uri_params_part; >+ $uri_params_part .= $param . '='; >+ $uri_params_part .= URI::Escape::uri_escape($uriPiece); >+ } >+ } >+ $uri_base_part .= '?' if $uri_params_part; >+ >+ return $uri_base_part . $uri_params_part; >+} >+ > 1; > __END__ > > =head1 NAME > >-C4::Auth - Authenticates Koha users >+C4::Auth_with_cas - CAS authentication for Koha via Identity Providers > > =head1 SYNOPSIS > > use C4::Auth_with_cas; > >-=cut >+=head1 DESCRIPTION > >-=head1 SEE ALSO >+CAS authentication is configured through the Identity Providers admin interface >+(Admin > Identity providers). Create a provider with protocol C<CAS> and set >+C<server_url> (and optionally C<version>) in the configuration. > >-CGI(3) >+=head1 SEE ALSO > >-Authen::CAS::Client >+CGI(3), Authen::CAS::Client > > =cut >-- >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 40596
:
185168
|
185169
|
185170
|
185171
|
185172
|
185173
|
185174
|
185175
|
185176
|
185177
|
195113
|
195114
|
195115
| 195116 |
195117
|
195118
|
195119