View | Details | Raw Unified | Return to bug 40596
Collapse All | Expand All

(-)a/Koha/Auth/CASCompat.pm (+186 lines)
Line 0 Link Here
1
package Koha::Auth::CASCompat;
2
3
# Copyright 2024 Koha Development team
4
#
5
# This file is part of Koha.
6
#
7
# Koha is free software; you can redistribute it and/or modify it
8
# under the terms of the GNU General Public License as published by
9
# the Free Software Foundation; either version 3 of the License, or
10
# (at your option) any later version.
11
#
12
# Koha is distributed in the hope that it will be useful, but
13
# WITHOUT ANY WARRANTY; without even the implied warranty of
14
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
# GNU General Public License for more details.
16
#
17
# You should have received a copy of the GNU General Public License
18
# along with Koha; if not, see <http://www.gnu.org/licenses>.
19
20
use Modern::Perl;
21
22
use C4::Context;
23
use Try::Tiny qw( catch try );
24
25
use Koha::Auth::Client::CAS;
26
use Koha::Auth::Identity::Providers;
27
use Koha::Logger;
28
29
=head1 NAME
30
31
Koha::Auth::CASCompat - CAS compatibility layer
32
33
=head1 SYNOPSIS
34
35
    use Koha::Auth::CASCompat;
36
37
    my ($success, $cardnumber, $userid, $cas_ticket, $patron) =
38
        Koha::Auth::CASCompat::authenticate_cas_user($ticket, $query, $type, $cas_server);
39
40
=head1 DESCRIPTION
41
42
This module provides a compatibility layer between the legacy CAS authentication
43
system and the modern identity provider architecture. It allows existing code
44
to continue working while using the new client-based authentication internally.
45
46
=head1 FUNCTIONS
47
48
=head2 get_cas_providers
49
50
    my ($default_cas, $casservers) = Koha::Auth::CASCompat::get_cas_providers();
51
52
Returns CAS provider configuration in the format expected by legacy code.
53
54
=cut
55
56
sub get_cas_providers {
57
    my $logger = Koha::Logger->get();
58
59
    # Try to find modern CAS providers
60
    my $providers = Koha::Auth::Identity::Providers->search( { protocol => 'CAS' } );
61
62
    if ( $providers->count ) {
63
        my $casservers = {};
64
        my $default_cas;
65
66
        while ( my $provider = $providers->next ) {
67
            my $config = $provider->get_config;
68
            $casservers->{ $provider->code } = $config->{cas_url};
69
            $default_cas = $provider->code unless $default_cas;
70
        }
71
72
        $logger->debug( "Found " . $providers->count . " modern CAS providers" );
73
        return ( $default_cas, $casservers );
74
    }
75
76
    # Fall back to legacy configuration
77
    my $cas_url = C4::Context->preference('casServerUrl');
78
    if ($cas_url) {
79
        $logger->debug("Using legacy CAS configuration");
80
        return ( 'default', { 'default' => $cas_url } );
81
    }
82
83
    $logger->warn("No CAS configuration found");
84
    return ( undef, {} );
85
}
86
87
=head2 authenticate_cas_user
88
89
    my ($success, $cardnumber, $userid, $cas_ticket, $patron) =
90
        Koha::Auth::CASCompat::authenticate_cas_user($ticket, $query, $type, $cas_server);
91
92
Authenticates a CAS user using the modern implementation but returns data
93
in the format expected by the legacy interface.
94
95
=cut
96
97
sub authenticate_cas_user {
98
    my ( $ticket, $query, $type, $cas_server ) = @_;
99
100
    my $logger = Koha::Logger->get();
101
102
    return (0) unless $ticket;
103
104
    # Determine service URL
105
    my $service_url = _build_service_url( $query, $type );
106
    return (0) unless $service_url;
107
108
    try {
109
        # Try to find a modern CAS provider
110
        my $provider_code = $cas_server || 'default';
111
        my $provider = Koha::Auth::Identity::Providers->search( { protocol => 'CAS', code => $provider_code } )->next;
112
113
        if ($provider) {
114
115
            # Use modern client
116
            my $client = Koha::Auth::Client::CAS->new( { provider => $provider } );
117
            my ( $user_data, $patron ) = $client->get_user(
118
                {
119
                    ticket  => $ticket,
120
                    service => $service_url
121
                }
122
            );
123
124
            if ($patron) {
125
                return (
126
                    1,                      # success
127
                    $patron->cardnumber,    # cardnumber
128
                    $patron->userid,        # userid
129
                    $ticket,                # cas_ticket
130
                    $patron                 # patron object
131
                );
132
            }
133
        } else {
134
135
            # Fall back to legacy-style authentication
136
            my $cas_url = C4::Context->preference('casServerUrl');
137
            if ($cas_url) {
138
139
                # Create a temporary provider-like config
140
                my $config = { cas_url => $cas_url };
141
                my $client = Koha::Auth::Client::CAS->new();
142
143
                # This would need more implementation for full legacy support
144
                $logger->warn("Legacy CAS authentication not fully implemented");
145
            }
146
        }
147
    } catch {
148
        $logger->error("CAS authentication error: $_");
149
    };
150
151
    return (0);    # failure
152
}
153
154
=head2 Internal functions
155
156
=head3 _build_service_url
157
158
    my $service_url = _build_service_url($query, $type);
159
160
Builds the service URL for CAS validation from the query parameters.
161
162
=cut
163
164
sub _build_service_url {
165
    my ( $query, $type ) = @_;
166
167
    return unless $query;
168
169
    # Build service URL based on interface type
170
    my $base_url;
171
    if ( $type eq 'opac' ) {
172
        $base_url = C4::Context->preference('OPACBaseURL');
173
    } else {
174
        $base_url = C4::Context->preference('staffClientBaseURL');
175
    }
176
177
    return unless $base_url;
178
179
    # Remove ticket parameter and build clean URL
180
    my $url = $query->url( -absolute => 1, -query => 1 );
181
    $url =~ s/[&?]ticket=[^&]*//g;
182
183
    return $base_url . $url;
184
}
185
186
1;
(-)a/Koha/Auth/ShibbolethCompat.pm (-1 / +244 lines)
Line 0 Link Here
0
- 
1
package Koha::Auth::ShibbolethCompat;
2
3
# Copyright 2024 Koha Development team
4
#
5
# This file is part of Koha.
6
#
7
# Koha is free software; you can redistribute it and/or modify it
8
# under the terms of the GNU General Public License as published by
9
# the Free Software Foundation; either version 3 of the License, or
10
# (at your option) any later version.
11
#
12
# Koha is distributed in the hope that it will be useful, but
13
# WITHOUT ANY WARRANTY; without even the implied warranty of
14
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
# GNU General Public License for more details.
16
#
17
# You should have received a copy of the GNU General Public License
18
# along with Koha; if not, see <http://www.gnu.org/licenses>.
19
20
use Modern::Perl;
21
22
use C4::Context;
23
use JSON      qw( encode_json );
24
use Try::Tiny qw( catch try );
25
26
use Koha::Auth::Client::Shibboleth;
27
use Koha::Auth::Identity::Providers;
28
use Koha::Logger;
29
use Koha::Patrons;
30
31
=head1 NAME
32
33
Koha::Auth::ShibbolethCompat - Shibboleth compatibility layer
34
35
=head1 SYNOPSIS
36
37
    use Koha::Auth::ShibbolethCompat;
38
39
    my ($success, $cardnumber, $userid, $patron) =
40
        Koha::Auth::ShibbolethCompat::authenticate_shibboleth_user($shib_login);
41
42
=head1 DESCRIPTION
43
44
This module provides a compatibility layer between the legacy Shibboleth authentication
45
system and the modern identity provider architecture. It allows existing code
46
to continue working while using the new client-based authentication internally.
47
48
=head1 FUNCTIONS
49
50
=head2 get_shibboleth_provider
51
52
    my $provider = Koha::Auth::ShibbolethCompat::get_shibboleth_provider();
53
54
Returns a Shibboleth identity provider, either modern or created from legacy config.
55
56
=cut
57
58
sub get_shibboleth_provider {
59
    my $logger = Koha::Logger->get();
60
61
    # Try to find existing identity provider
62
    my $provider = Koha::Auth::Identity::Providers->search( { protocol => 'Shibboleth' } )->next;
63
64
    if ($provider) {
65
        $logger->debug( "Found Shibboleth identity provider: " . $provider->code );
66
        return $provider->upgrade_class;
67
    }
68
69
    # Fall back to creating temporary provider from legacy config
70
    my $shibboleth_config = C4::Context->config('shibboleth');
71
72
    if ($shibboleth_config) {
73
        $logger->debug("Using legacy Shibboleth configuration");
74
75
        # Create temporary provider object (not stored in database)
76
        require Koha::Auth::Identity::Provider::Shibboleth;
77
        my $temp_provider = Koha::Auth::Identity::Provider::Shibboleth->new(
78
            {
79
                code        => 'shibboleth',
80
                description => "Legacy Shibboleth SSO",
81
                config      => encode_json(
82
                    {
83
                        sso_url   => $shibboleth_config->{ssoUrl}   || '/Shibboleth.sso/Login',
84
                        slo_url   => $shibboleth_config->{sloUrl}   || '/Shibboleth.sso/Logout',
85
                        entity_id => $shibboleth_config->{entityID} || 'koha'
86
                    }
87
                ),
88
                mapping => encode_json(
89
                    {
90
                        userid    => $shibboleth_config->{mapping}->{userid}    || 'HTTP_REMOTE_USER',
91
                        email     => $shibboleth_config->{mapping}->{email}     || 'HTTP_MAIL',
92
                        firstname => $shibboleth_config->{mapping}->{firstname} || 'HTTP_GIVENNAME',
93
                        surname   => $shibboleth_config->{mapping}->{surname}   || 'HTTP_SN'
94
                    }
95
                ),
96
                matchpoint => $shibboleth_config->{matchpoint} || 'userid'
97
            }
98
        );
99
100
        # Don't store - this is just for compatibility
101
        return $temp_provider;
102
    }
103
104
    $logger->warn("No Shibboleth configuration found");
105
    return;
106
}
107
108
=head2 authenticate_shibboleth_user
109
110
    my ($success, $cardnumber, $userid, $patron) =
111
        Koha::Auth::ShibbolethCompat::authenticate_shibboleth_user($shib_login);
112
113
Authenticates a Shibboleth user using the modern implementation but returns data
114
in the format expected by the legacy interface.
115
116
=cut
117
118
sub authenticate_shibboleth_user {
119
    my ($shib_login) = @_;
120
121
    my $logger = Koha::Logger->get();
122
123
    return (0) unless $shib_login;
124
125
    # Get legacy Shibboleth configuration
126
    my $config = get_shibboleth_config();
127
    return (0) unless $config;
128
129
    # Extract Shibboleth attributes from environment
130
    my $mapped_data = {};
131
    my $mapping     = $config->{mapping} || {};
132
133
    foreach my $koha_field ( keys %$mapping ) {
134
        my $shib_attribute_config = $mapping->{$koha_field};
135
136
        # Handle legacy format: { 'is' => 'attribute_name' }
137
        my $shib_attribute;
138
        if ( ref($shib_attribute_config) eq 'HASH' && exists $shib_attribute_config->{is} ) {
139
            $shib_attribute = $shib_attribute_config->{is};
140
        } else {
141
142
            # Handle simple string format
143
            $shib_attribute = $shib_attribute_config;
144
        }
145
146
        if ( exists $ENV{$shib_attribute} ) {
147
            $mapped_data->{$koha_field} = $ENV{$shib_attribute};
148
        }
149
    }
150
151
    return (0) unless %$mapped_data;
152
153
    # Find existing patron using matchpoint
154
    my $matchpoint = $config->{matchpoint} || 'userid';
155
    my $patron;
156
157
    if ( my $match_value = $mapped_data->{$matchpoint} ) {
158
        my $patron_rs = Koha::Patrons->search( { $matchpoint => $match_value } );
159
        if ( $patron_rs->count ) {
160
            $patron = $patron_rs->next;
161
            $logger->debug( "Found existing patron: " . $patron->borrowernumber );
162
163
            # Update patron if sync is enabled
164
            if ( $config->{sync} ) {
165
166
                # Remove fields that shouldn't be updated
167
                my $update_data = {%$mapped_data};
168
                delete $update_data->{borrowernumber};
169
                delete $update_data->{categorycode};
170
                delete $update_data->{branchcode};
171
172
                $patron->set($update_data)->store;
173
                $logger->debug( "Updated patron data for: " . $patron->userid );
174
            }
175
        }
176
    }
177
178
    # Auto-create user if enabled and no existing patron found
179
    if ( !$patron && $config->{autocreate} ) {
180
        $logger->debug("Auto-creating new patron for: $shib_login");
181
182
        # Set required fields from environment or defaults
183
        $mapped_data->{categorycode} = $ENV{cat}        || $ENV{categorycode} || 'PT';    # Default patron category
184
        $mapped_data->{branchcode}   = $ENV{branchcode} || C4::Context->userenv->{branch} || 'CPL';
185
186
        try {
187
            $patron = Koha::Patron->new($mapped_data)->store;
188
            $logger->info( "Auto-created patron: " . $patron->userid );
189
        } catch {
190
            $logger->error("Failed to auto-create patron: $_");
191
            return (0);
192
        };
193
    }
194
195
    if ($patron) {
196
        $logger->info( "Shibboleth authentication successful for user: " . $patron->userid );
197
        return (
198
            1,                      # success
199
            $patron->cardnumber,    # cardnumber
200
            $patron->userid,        # userid
201
            $patron                 # patron object
202
        );
203
    } else {
204
        $logger->warn("Shibboleth authentication failed - no patron found/created for: $shib_login");
205
    }
206
207
    return (0);    # failure
208
}
209
210
=head2 get_shibboleth_config
211
212
    my $config = Koha::Auth::ShibbolethCompat::get_shibboleth_config();
213
214
Returns Shibboleth configuration in the legacy format expected by C4::Auth_with_shibboleth.
215
216
=cut
217
218
sub get_shibboleth_config {
219
220
    # Try modern provider first
221
    my $providers = Koha::Auth::Identity::Providers->search( { protocol => 'Shibboleth' } );
222
    if ( $providers->count ) {
223
        my $provider = $providers->next->upgrade_class;
224
        my $config   = $provider->get_config;
225
        my $mapping  = $provider->get_mapping;
226
227
        # Convert to legacy format
228
        return {
229
            matchpoint => $provider->matchpoint,
230
            mapping    => $mapping,
231
            ssoUrl     => $config->{sso_url},
232
            sloUrl     => $config->{slo_url},
233
            entityID   => $config->{entity_id}
234
        };
235
    }
236
237
    # Fall back to legacy configuration directly
238
    # The test mocks C4::Context->config to return the shibboleth config directly
239
    my $config = C4::Context->config('shibboleth') || C4::Context->config();
240
241
    return $config;
242
}
243
244
1;

Return to bug 40596