From cfb77bfd29b521b23732516bdc26e61625856942 Mon Sep 17 00:00:00 2001 From: Jacob O'Mara Date: Tue, 4 Nov 2025 15:22:16 +0000 Subject: [PATCH] Bug 39224: add Koha object models for shibboleth configuration Add Koha::ShibbolethConfig and Koha::ShibbolethFieldMapping classes to manage Shibboleth configuration stored in the database. --- Koha/ShibbolethConfig.pm | 113 ++++++++++++++++++++++++++++++++ Koha/ShibbolethConfigs.pm | 63 ++++++++++++++++++ Koha/ShibbolethFieldMapping.pm | 52 +++++++++++++++ Koha/ShibbolethFieldMappings.pm | 99 ++++++++++++++++++++++++++++ 4 files changed, 327 insertions(+) create mode 100644 Koha/ShibbolethConfig.pm create mode 100644 Koha/ShibbolethConfigs.pm create mode 100644 Koha/ShibbolethFieldMapping.pm create mode 100644 Koha/ShibbolethFieldMappings.pm diff --git a/Koha/ShibbolethConfig.pm b/Koha/ShibbolethConfig.pm new file mode 100644 index 00000000000..0d08dbf6bcf --- /dev/null +++ b/Koha/ShibbolethConfig.pm @@ -0,0 +1,113 @@ +package Koha::ShibbolethConfig; + +use Modern::Perl; +use base qw(Koha::Object); + +use Koha::ShibbolethFieldMappings; + +=head1 NAME + +Koha::ShibbolethConfig - Koha ShibbolethConfig Object class + +=head1 API + +=head2 Class Methods + +=cut + +=head3 _type + +=cut + +sub _type { + return 'ShibbolethConfig'; +} + +=head3 store + +Override store method to ensure we always update the single config row + +=cut + +sub store { + my ($self) = @_; + + # Force ID to be 1 + $self->shibboleth_config_id(1); + + # If record exists, update it + if ( my $existing = Koha::ShibbolethConfigs->find(1) ) { + foreach my $field (qw( force_opac_sso force_staff_sso autocreate sync welcome )) { + $existing->$field( $self->$field ) if defined $self->$field; + } + return $existing->SUPER::store(); + } + + # Otherwise create new record with ID 1 + return $self->SUPER::store(); +} + +=head3 mappings + +Returns field mappings associated with this config + +=cut + +sub mappings { + my ($self) = @_; + return Koha::ShibbolethFieldMappings->new; +} + +=head3 get_combined_config + +Returns complete configuration including mappings + +=cut + +sub get_combined_config { + my ($self) = @_; + + my $config = $self->unblessed; + my ( $success, $mapping_config ) = $self->mappings->get_mapping_config; + + if ($success) { + + # Merge mapping config with base config + $config->{matchpoint} = $mapping_config->{matchpoint}; + $config->{mapping} = $mapping_config->{mapping}; + return $config; + } + + return; +} + +=head3 get_field_mappings + +Returns a hashref of field mappings in config format + +=cut + +sub get_field_mappings { + my ($self) = @_; + my ( $success, $mapping_config ) = $self->mappings->get_mapping_config; + return $mapping_config->{mapping} if $success; + return {}; +} + +=head3 get_value + +Returns the value of a specific configuration column. + +@param string $column The name of the configuration column to retrieve +@return mixed The value of the configuration column or undef if not found + +=cut + +sub get_value { + my ( $self, $column ) = @_; + return unless $column; + return unless $self->_result->result_source->has_column($column); + return $self->$column; +} + +1; diff --git a/Koha/ShibbolethConfigs.pm b/Koha/ShibbolethConfigs.pm new file mode 100644 index 00000000000..a9cc4d5604f --- /dev/null +++ b/Koha/ShibbolethConfigs.pm @@ -0,0 +1,63 @@ +package Koha::ShibbolethConfigs; + +use Modern::Perl; +use base qw(Koha::Objects); +use Koha::ShibbolethConfig; + +=head1 NAME + +Koha::ShibbolethConfigs - Koha ShibbolethConfig Object set class + +=head1 API + +=head2 Class Methods + +=cut + +=head3 get_configuration + +Returns the Shibboleth configuration settings. +Always returns the single configuration row with ID=1. + +=cut + +sub get_configuration { + my ($self) = @_; + + # Always get config with ID=1 + my $config = $self->find(1); + unless ($config) { + + # Create default config with ID=1 if it doesn't exist + $config = Koha::ShibbolethConfig->new( + { + shibboleth_config_id => 1, + force_opac_sso => 0, + force_staff_sso => 0, + autocreate => 0, + sync => 0, + welcome => 0 + } + )->store; + } + + return $config; +} + +=head3 _type + +=cut + +sub _type { + return 'ShibbolethConfig'; +} + +=head3 object_class + +=cut + +sub object_class { + return 'Koha::ShibbolethConfig'; +} + +1; diff --git a/Koha/ShibbolethFieldMapping.pm b/Koha/ShibbolethFieldMapping.pm new file mode 100644 index 00000000000..0818f876750 --- /dev/null +++ b/Koha/ShibbolethFieldMapping.pm @@ -0,0 +1,52 @@ +package Koha::ShibbolethFieldMapping; + +use Modern::Perl; +use Koha::Exceptions; +use base qw(Koha::Object); + +=head1 NAME + +Koha::ShibbolethFieldMapping - Koha ShibbolethFieldMapping Object class + +=head1 API + +=head2 Class Methods + +=cut + +=head3 store + +Override the base store method to enforce business rules: +- Only one matchpoint can be set at a time +- Required fields must be present + +=cut + +sub store { + my $self = shift; + + unless ( $self->koha_field ) { + Koha::Exceptions::MissingParameter->throw( error => "koha_field is required" ); + } + + unless ( $self->idp_field || $self->default_content ) { + Koha::Exceptions::MissingParameter->throw( error => "Either idp_field or default_content must be provided" ); + } + + # Handle matchpoint logic before storing + if ( $self->is_matchpoint ) { + Koha::ShibbolethFieldMappings->new->ensure_single_matchpoint( $self->mapping_id ); + } + + return $self->SUPER::store(@_); +} + +=head3 _type + +=cut + +sub _type { + return 'ShibbolethFieldMapping'; +} + +1; diff --git a/Koha/ShibbolethFieldMappings.pm b/Koha/ShibbolethFieldMappings.pm new file mode 100644 index 00000000000..00ce1e8fd93 --- /dev/null +++ b/Koha/ShibbolethFieldMappings.pm @@ -0,0 +1,99 @@ +package Koha::ShibbolethFieldMappings; + +use Modern::Perl; +use base qw(Koha::Objects); +use Koha::ShibbolethFieldMapping; +use Koha::Logger; + +=head1 NAME + +Koha::ShibbolethFieldMappings - Koha ShibbolethFieldMapping Object set class + +=head1 API + +=head2 Class Methods + +=cut + +=head3 ensure_single_matchpoint + +Ensures only one matchpoint exists by clearing other matchpoints when setting a new one + +=cut + +sub ensure_single_matchpoint { + my ( $self, $new_matchpoint_id ) = @_; + + return $self->search( + { + is_matchpoint => 1, + mapping_id => { '!=' => $new_matchpoint_id || 0 } + } + )->update( { is_matchpoint => 0 } ); +} + +=head3 get_matchpoint + +Returns the field mapping that is set as the matchpoint + +=cut + +sub get_matchpoint { + my ($self) = @_; + my $matchpoint = $self->search( { is_matchpoint => 1 } )->single; + + unless ($matchpoint) { + Koha::Logger->get->warn('No matchpoint configured in Shibboleth field mappings'); + } + + return $matchpoint; +} + +=head3 get_mapping_config + +Returns the field mappings formatted for use in authentication configuration. + +=cut + +sub get_mapping_config { + my ($self) = @_; + + my $mappings = {}; + my $matchpoint = $self->get_matchpoint; + + return ( 0, undef ) unless $matchpoint; + + my $all_mappings = $self->search; + while ( my $mapping = $all_mappings->next ) { + $mappings->{ $mapping->koha_field } = { + is => $mapping->idp_field, + content => $mapping->default_content, + }; + } + + return ( + 1, + { + matchpoint => $matchpoint->koha_field, + mapping => $mappings + } + ); +} + +=head3 _type + +=cut + +sub _type { + return 'ShibbolethFieldMapping'; +} + +=head3 object_class + +=cut + +sub object_class { + return 'Koha::ShibbolethFieldMapping'; +} + +1; -- 2.39.5