From a00cb6e698cf2947abea60615ba84521ba41e2aa Mon Sep 17 00:00:00 2001 From: Martin Renvoize Date: Mon, 23 Feb 2026 15:54:55 +0000 Subject: [PATCH] Bug 39224: Handle patron_attribute:CODE values in auth protocol handlers - OAuth _find_patron_by_matchpoint: search via borrower_attributes join when matchpoint has patron_attribute: prefix - Auth::Client get_user: strip patron_attribute keys from mapped_data before patron->set(), apply them via Koha::Patron::Attribute instead - auth.register: separate patron attributes from borrower fields on patron creation - Shibboleth checkpw_shib: search via borrower_attributes for patron attribute matchpoints - Shibboleth _autocreate/_sync: route patron_attribute:CODE mapping values through Koha::Patron::Attribute rather than borrower hash --- C4/Auth_with_shibboleth.pm | 54 ++++++++++++++++++++++++++++++------ Koha/Auth/Client.pm | 22 ++++++++++++++- Koha/Auth/Client/OAuth.pm | 13 +++++++-- Koha/REST/Plugin/Auth/IdP.pm | 17 +++++++++++- 4 files changed, 93 insertions(+), 13 deletions(-) diff --git a/C4/Auth_with_shibboleth.pm b/C4/Auth_with_shibboleth.pm index f6fbcf99089..59e39382c00 100644 --- a/C4/Auth_with_shibboleth.pm +++ b/C4/Auth_with_shibboleth.pm @@ -38,6 +38,8 @@ use List::MoreUtils qw( any ); use Koha::Logger; use Koha::Auth::Identity::Providers; +use Koha::Patron::Attribute; +use Koha::Patron::Attributes; # Check that shib config is not malformed @@ -99,7 +101,16 @@ sub checkpw_shib { my $config = _get_shib_config(); # Does the given shibboleth attribute value ($match) match a valid koha user ? - my $borrowers = Koha::Patrons->search( { $config->{matchpoint} => $match } ); + my $borrowers; + if ( $config->{matchpoint} =~ /^patron_attribute:(.+)$/ ) { + my $code = $1; + $borrowers = Koha::Patrons->search( + { 'borrower_attributes.code' => $code, 'borrower_attributes.attribute' => $match }, + { join => 'borrower_attributes' } + ); + } else { + $borrowers = Koha::Patrons->search( { $config->{matchpoint} => $match } ); + } if ( $borrowers->count > 1 ) { # If we have more than 1 borrower the matchpoint is not unique @@ -129,17 +140,32 @@ sub checkpw_shib { sub _autocreate { my ( $config, $match ) = @_; - my %borrower = ( $config->{matchpoint} => $match ); + my ( %borrower, %patron_attrs ); + + if ( $config->{matchpoint} =~ /^patron_attribute:(.+)$/ ) { + $patron_attrs{$1} = $match; + } else { + $borrower{ $config->{matchpoint} } = $match; + } while ( my ( $key, $entry ) = each %{ $config->{'mapping'} } ) { - if ( C4::Context->psgi_env ) { - $borrower{$key} = ( $entry->{'is'} && $ENV{ "HTTP_" . uc( $entry->{'is'} ) } ) || $entry->{'content'} || ''; + my $value = + C4::Context->psgi_env + ? ( $entry->{'is'} && $ENV{ "HTTP_" . uc( $entry->{'is'} ) } ) || $entry->{'content'} || '' + : ( $entry->{'is'} && $ENV{ $entry->{'is'} } ) || $entry->{'content'} || ''; + if ( $key =~ /^patron_attribute:(.+)$/ ) { + $patron_attrs{$1} = $value; } else { - $borrower{$key} = ( $entry->{'is'} && $ENV{ $entry->{'is'} } ) || $entry->{'content'} || ''; + $borrower{$key} = $value; } } my $patron = Koha::Patron->new( \%borrower )->store; + + for my $code ( keys %patron_attrs ) { + Koha::Patron::Attribute->new( + { borrowernumber => $patron->borrowernumber, code => $code, attribute => $patron_attrs{$code} } )->store; + } $patron->discard_changes; C4::Members::Messaging::SetMessagingPreferencesFromDefaults( @@ -184,17 +210,27 @@ sub _autocreate { sub _sync { my ( $borrowernumber, $config, $match ) = @_; - my %borrower; + my ( %borrower, %patron_attrs ); $borrower{'borrowernumber'} = $borrowernumber; while ( my ( $key, $entry ) = each %{ $config->{'mapping'} } ) { - if ( C4::Context->psgi_env ) { - $borrower{$key} = ( $entry->{'is'} && $ENV{ "HTTP_" . uc( $entry->{'is'} ) } ) || $entry->{'content'} || ''; + my $value = + C4::Context->psgi_env + ? ( $entry->{'is'} && $ENV{ "HTTP_" . uc( $entry->{'is'} ) } ) || $entry->{'content'} || '' + : ( $entry->{'is'} && $ENV{ $entry->{'is'} } ) || $entry->{'content'} || ''; + if ( $key =~ /^patron_attribute:(.+)$/ ) { + $patron_attrs{$1} = $value; } else { - $borrower{$key} = ( $entry->{'is'} && $ENV{ $entry->{'is'} } ) || $entry->{'content'} || ''; + $borrower{$key} = $value; } } my $patron = Koha::Patrons->find($borrowernumber); $patron->set( \%borrower )->store; + for my $code ( keys %patron_attrs ) { + my $existing = Koha::Patron::Attributes->search( { borrowernumber => $borrowernumber, code => $code } ); + $existing->delete; + Koha::Patron::Attribute->new( + { borrowernumber => $borrowernumber, code => $code, attribute => $patron_attrs{$code} } )->store; + } } sub _get_uri { diff --git a/Koha/Auth/Client.pm b/Koha/Auth/Client.pm index 3fd4913e694..370e2d9c0b2 100644 --- a/Koha/Auth/Client.pm +++ b/Koha/Auth/Client.pm @@ -23,6 +23,8 @@ use C4::Context; use Koha::Exceptions::Auth; use Koha::Auth::Identity::Providers; +use Koha::Patron::Attribute; +use Koha::Patron::Attributes; =head1 NAME @@ -87,7 +89,25 @@ sub get_user { $patron = $args->{'patron'}; $domain = $args->{'domain'}; - $patron->set($mapped_data)->store if $patron && $domain->update_on_auth; + if ( $patron && $domain->update_on_auth ) { + my ( %patron_attrs, %borrower_data ); + for my $key ( keys %$mapped_data ) { + if ( $key =~ /^patron_attribute:(.+)$/ ) { + $patron_attrs{$1} = $mapped_data->{$key}; + } else { + $borrower_data{$key} = $mapped_data->{$key}; + } + } + $patron->set( \%borrower_data )->store; + for my $code ( keys %patron_attrs ) { + my $existing = + Koha::Patron::Attributes->search( { borrowernumber => $patron->borrowernumber, code => $code } ); + $existing->delete; + Koha::Patron::Attribute->new( + { borrowernumber => $patron->borrowernumber, code => $code, attribute => $patron_attrs{$code} } ) + ->store; + } + } $mapped_data->{categorycode} = $domain->default_category_id; $mapped_data->{branchcode} = $domain->default_library_id; diff --git a/Koha/Auth/Client/OAuth.pm b/Koha/Auth/Client/OAuth.pm index df6ff275df1..d50911318ce 100644 --- a/Koha/Auth/Client/OAuth.pm +++ b/Koha/Auth/Client/OAuth.pm @@ -124,9 +124,18 @@ sub _find_patron_by_matchpoint { return unless defined $value; - my $matchpoint_rs = Koha::Patrons->search( { $matchpoint => $value } ); + my $patron_rs; + if ( $matchpoint =~ /^patron_attribute:(.+)$/ ) { + my $code = $1; + $patron_rs = Koha::Patrons->search( + { 'borrower_attributes.code' => $code, 'borrower_attributes.attribute' => $value }, + { join => 'borrower_attributes' } + ); + } else { + $patron_rs = Koha::Patrons->search( { $matchpoint => $value } ); + } - return $matchpoint_rs->count ? $matchpoint_rs->next : undef; + return $patron_rs->count ? $patron_rs->next : undef; } 1; diff --git a/Koha/REST/Plugin/Auth/IdP.pm b/Koha/REST/Plugin/Auth/IdP.pm index dd035fe78fa..2f60bf6e36d 100644 --- a/Koha/REST/Plugin/Auth/IdP.pm +++ b/Koha/REST/Plugin/Auth/IdP.pm @@ -23,6 +23,7 @@ use Mojo::Base 'Mojolicious::Plugin'; use Koha::Exceptions; use Koha::Exceptions::Auth; +use Koha::Patron::Attribute; use Koha::Patrons; use C4::Auth qw(create_basic_session); @@ -89,7 +90,21 @@ if other values or none are passed. Koha::Exceptions::Auth::Unauthorized->throw( code => 401 ); } - return Koha::Patron->new($data)->store; + my ( %patron_attrs, %borrower_data ); + for my $key ( keys %$data ) { + if ( $key =~ /^patron_attribute:(.+)$/ ) { + $patron_attrs{$1} = $data->{$key}; + } else { + $borrower_data{$key} = $data->{$key}; + } + } + my $patron = Koha::Patron->new( \%borrower_data )->store; + for my $code ( keys %patron_attrs ) { + Koha::Patron::Attribute->new( + { borrowernumber => $patron->borrowernumber, code => $code, attribute => $patron_attrs{$code} } ) + ->store; + } + return $patron; } ); -- 2.53.0