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

(-)a/t/db_dependent/Auth_with_shibboleth.t (-12 / +71 lines)
Lines 35-40 use t::lib::Dates; Link Here
35
use C4::Auth_with_shibboleth qw( shib_ok login_shib_url get_login_shib checkpw_shib );
35
use C4::Auth_with_shibboleth qw( shib_ok login_shib_url get_login_shib checkpw_shib );
36
use Koha::Database;
36
use Koha::Database;
37
use Koha::DateUtils qw( dt_from_string );
37
use Koha::DateUtils qw( dt_from_string );
38
use Koha::Auth::Hostname;
39
use Koha::Auth::Hostnames;
40
use Koha::Auth::Identity::Provider;
41
use Koha::Auth::Identity::Provider::Hostname;
42
use Koha::Auth::Identity::Provider::Mapping;
43
use Koha::Auth::Identity::Provider::SAML2;
44
use Koha::Auth::Identity::Providers;
38
45
39
BEGIN {
46
BEGIN {
40
    use_ok(
47
    use_ok(
Lines 48-56 $schema->storage->txn_begin; Link Here
48
my $builder = t::lib::TestBuilder->new;
55
my $builder = t::lib::TestBuilder->new;
49
my $logger  = t::lib::Mocks::Logger->new();
56
my $logger  = t::lib::Mocks::Logger->new();
50
57
51
# Mock variables
52
my $shibboleth;
58
my $shibboleth;
53
change_config( {} );
54
my $interface = 'opac';
59
my $interface = 'opac';
55
60
56
# Mock few preferences
61
# Mock few preferences
Lines 59-70 t::lib::Mocks::mock_preference( 'StaffClientBaseURL', 'teststaff.com' ); Link Here
59
t::lib::Mocks::mock_preference( 'EmailFieldPrimary',    '' );
64
t::lib::Mocks::mock_preference( 'EmailFieldPrimary',    '' );
60
t::lib::Mocks::mock_preference( 'EmailFieldPrecedence', 'emailpro' );
65
t::lib::Mocks::mock_preference( 'EmailFieldPrecedence', 'emailpro' );
61
66
62
# Mock Context: config, tz and interface
67
# Mock Context: tz and interface
63
my $context = Test::MockModule->new('C4::Context');
68
my $context = Test::MockModule->new('C4::Context');
64
$context->mock( 'config',    sub { return $shibboleth; } );    # easier than caching by Mocks::mock_config
65
$context->mock( 'timezone',  sub { return 'local'; } );
69
$context->mock( 'timezone',  sub { return 'local'; } );
66
$context->mock( 'interface', sub { return $interface; } );
70
$context->mock( 'interface', sub { return $interface; } );
67
71
72
change_config( {} );
73
68
# Mock Letters: GetPreparedLetter, EnqueueLetter and SendQueuedMessages
74
# Mock Letters: GetPreparedLetter, EnqueueLetter and SendQueuedMessages
69
# We want to test the params
75
# We want to test the params
70
my $mocked_letters = Test::MockModule->new('C4::Letters');
76
my $mocked_letters = Test::MockModule->new('C4::Letters');
Lines 97-116 $mocked_letters->mock( Link Here
97
# Start testing ----------------------------------------------------------------
103
# Start testing ----------------------------------------------------------------
98
104
99
subtest "shib_ok tests" => sub {
105
subtest "shib_ok tests" => sub {
100
    plan tests => 5;
106
    plan tests => 6;
101
    my $result;
107
    my $result;
102
108
103
    # correct config, no debug
109
    # correct config, no debug
104
    is( shib_ok(), '1', "good config" );
110
    is( shib_ok(), '1', "good config" );
105
111
106
    # bad config, no debug
112
    # bad config, no debug - clear matchpoint in DB
113
    $schema->resultset('IdentityProviderMapping')->update( { is_matchpoint => 0 } );
107
    delete $shibboleth->{matchpoint};
114
    delete $shibboleth->{matchpoint};
115
    $logger->clear();
108
    warnings_are { $result = shib_ok() }
116
    warnings_are { $result = shib_ok() }
109
    [ { carped => 'shibboleth matchpoint not defined' }, ],
117
    [ { carped => 'shibboleth matchpoint not defined' } ],
110
        "undefined matchpoint = fatal config, warning given";
118
        "matchpoint warning given when no matchpoint set";
111
    is( $result, '0', "bad config" );
119
    is( $result, '0', "bad config" );
112
120
113
    change_config( { matchpoint => 'email' } );
121
    change_config( { matchpoint => 'email', mapping => { email => { content => 'default@example.com' } } } );
114
    warnings_are { $result = shib_ok() }
122
    warnings_are { $result = shib_ok() }
115
    [ { carped => 'shibboleth matchpoint not mapped' }, ],
123
    [ { carped => 'shibboleth matchpoint not mapped' }, ],
116
        "unmapped matchpoint = fatal config, warning given";
124
        "unmapped matchpoint = fatal config, warning given";
Lines 119-124 subtest "shib_ok tests" => sub { Link Here
119
    # add test for undefined shibboleth block
127
    # add test for undefined shibboleth block
120
    $logger->clear;
128
    $logger->clear;
121
    change_config( {} );
129
    change_config( {} );
130
    is( shib_ok(), '1', 'good config after reset' );
122
};
131
};
123
132
124
subtest "login_shib_url tests" => sub {
133
subtest "login_shib_url tests" => sub {
Lines 279-284 subtest "checkpw_shib tests" => sub { Link Here
279
    );
288
    );
280
289
281
    # sync user
290
    # sync user
291
    my $saml2_provider = Koha::Auth::Identity::Providers->search( { protocol => 'SAML2' } )->next;
292
    my $saml2_config   = $saml2_provider->get_config // {};
293
    $saml2_config->{sync} = 1;
294
    $saml2_provider->set_config($saml2_config)->store;
282
    $shibboleth->{sync} = 1;
295
    $shibboleth->{sync} = 1;
283
    $ENV{'city'} = 'AnotherCity';
296
    $ENV{'city'} = 'AnotherCity';
284
    ( $retval, $retcard, $retuserid, $retpatron ) = checkpw_shib($shib_login);
297
    ( $retval, $retcard, $retuserid, $retpatron ) = checkpw_shib($shib_login);
Lines 335-341 subtest "checkpw_shib tests" => sub { Link Here
335
    $ENV{'emailpro'} = 'me@myemail.com';
348
    $ENV{'emailpro'} = 'me@myemail.com';
336
    $ENV{branchcode} = $library->branchcode;      # needed since T::D::C does no longer hides the FK constraint
349
    $ENV{branchcode} = $library->branchcode;      # needed since T::D::C does no longer hides the FK constraint
337
350
338
    checkpw($shib_login);
351
    ( $retval, $retcard, $retuserid, $retpatron ) = checkpw_shib($shib_login);
339
    ok(
352
    ok(
340
        my $new_user_autocreated = Koha::Patrons->find( { userid => 'test43210' } ),
353
        my $new_user_autocreated = Koha::Patrons->find( { userid => 'test43210' } ),
341
        "new user found"
354
        "new user found"
Lines 424-429 $schema->storage->txn_rollback; Link Here
424
sub change_config {
437
sub change_config {
425
    my $params = shift;
438
    my $params = shift;
426
439
440
    # Remove any existing SAML2 providers
441
    Koha::Auth::Identity::Providers->search( { protocol => 'SAML2' } )->delete;
442
443
    my $provider = Koha::Auth::Identity::Provider::SAML2->new(
444
        {
445
            code        => 'shibboleth',
446
            description => 'Shibboleth',
447
            enabled     => 1,
448
        }
449
    )->store;
450
451
    $provider->set_config(
452
        {
453
            autocreate => $params->{autocreate} // 0,
454
            sync       => $params->{sync}       // 0,
455
            welcome    => $params->{welcome}    // 0,
456
        }
457
    )->store;
458
459
    # Link provider to the test hostname so _get_shib_config can find it
460
    my $test_hostname = 'testopac.com';
461
    $ENV{SERVER_NAME} = $test_hostname;
462
    my $hostname_obj = Koha::Auth::Hostnames->search( { hostname => $test_hostname } )->next
463
        // $builder->build_object( { class => 'Koha::Auth::Hostnames', value => { hostname => $test_hostname } } );
464
    Koha::Auth::Identity::Provider::Hostname->new(
465
        {
466
            hostname_id          => $hostname_obj->hostname_id,
467
            identity_provider_id => $provider->identity_provider_id,
468
            is_enabled           => 1,
469
        }
470
    )->store;
471
427
    my %mapping = (
472
    my %mapping = (
428
        'userid'       => { 'is' => 'uid' },
473
        'userid'       => { 'is' => 'uid' },
429
        'surname'      => { 'is' => 'sn' },
474
        'surname'      => { 'is' => 'sn' },
Lines 437-451 sub change_config { Link Here
437
    if ( exists $params->{mapping} ) {
482
    if ( exists $params->{mapping} ) {
438
        $mapping{$_} = $params->{mapping}->{$_} for keys %{ $params->{mapping} };
483
        $mapping{$_} = $params->{mapping}->{$_} for keys %{ $params->{mapping} };
439
    }
484
    }
485
486
    my $matchpoint = $params->{matchpoint} // 'userid';
487
488
    foreach my $koha_field ( keys %mapping ) {
489
        Koha::Auth::Identity::Provider::Mapping->new(
490
            {
491
                identity_provider_id => $provider->identity_provider_id,
492
                koha_field           => $koha_field,
493
                provider_field       => $mapping{$koha_field}->{is},
494
                is_matchpoint        => $koha_field eq $matchpoint ? 1 : 0,
495
                default_content      => $mapping{$koha_field}->{content},
496
            }
497
        )->store;
498
    }
499
440
    $shibboleth = {
500
    $shibboleth = {
441
        autocreate => $params->{autocreate} // 0,
501
        autocreate => $params->{autocreate} // 0,
442
        welcome    => $params->{welcome}    // 0,
502
        welcome    => $params->{welcome}    // 0,
443
        sync       => $params->{sync}       // 0,
503
        sync       => $params->{sync}       // 0,
444
        matchpoint => $params->{matchpoint} // 'userid',
504
        matchpoint => $matchpoint,
445
        mapping    => \%mapping,
505
        mapping    => \%mapping,
446
    };
506
    };
447
507
448
    # Change environment too
449
    $ENV{'uid'}      = "test1234";
508
    $ENV{'uid'}      = "test1234";
450
    $ENV{'sn'}       = undef;
509
    $ENV{'sn'}       = undef;
451
    $ENV{'exp'}      = undef;
510
    $ENV{'exp'}      = undef;
(-)a/t/db_dependent/Koha/Auth/Hostname.t (+139 lines)
Line 0 Link Here
1
#!/usr/bin/perl
2
3
# Copyright Koha Community 2026
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 <https://www.gnu.org/licenses>.
19
20
use Modern::Perl;
21
22
use Test::NoWarnings;
23
use Test::More tests => 4;
24
25
use Koha::Auth::Hostname;
26
use Koha::Auth::Hostnames;
27
28
use t::lib::TestBuilder;
29
use t::lib::Mocks;
30
31
my $schema  = Koha::Database->new->schema;
32
my $builder = t::lib::TestBuilder->new;
33
34
subtest 'sync_from_syspref() tests' => sub {
35
36
    plan tests => 12;
37
38
    $schema->storage->txn_begin;
39
40
    Koha::Auth::Hostnames->search->delete;
41
42
    # OPACBaseURL maps to hostname_id=1
43
    t::lib::Mocks::mock_preference( 'OPACBaseURL', 'https://opac.example.com' );
44
    Koha::Auth::Hostname->sync_from_syspref( 'OPACBaseURL', 'https://opac.example.com' );
45
46
    my $opac = Koha::Auth::Hostnames->find(1);
47
    ok( $opac, 'OPACBaseURL creates hostname_id=1' );
48
    is( $opac->hostname, 'opac.example.com', 'hostname_id=1 has correct hostname' );
49
50
    # staffClientBaseURL maps to hostname_id=2
51
    Koha::Auth::Hostname->sync_from_syspref( 'staffClientBaseURL', 'https://staff.example.com' );
52
53
    my $staff = Koha::Auth::Hostnames->find(2);
54
    ok( $staff, 'staffClientBaseURL creates hostname_id=2' );
55
    is( $staff->hostname, 'staff.example.com', 'hostname_id=2 has correct hostname' );
56
57
    # Updating the URL changes the stored hostname in-place
58
    Koha::Auth::Hostname->sync_from_syspref( 'OPACBaseURL', 'https://newopac.example.com' );
59
    $opac->discard_changes;
60
    is( $opac->hostname, 'newopac.example.com', 'hostname_id=1 updated when OPACBaseURL changes' );
61
    is( Koha::Auth::Hostnames->search( { hostname_id => 1 } )->count, 1, 'Still only one row for hostname_id=1' );
62
63
    # Idempotent: calling again with the same value is a no-op
64
    Koha::Auth::Hostname->sync_from_syspref( 'OPACBaseURL', 'https://newopac.example.com' );
65
    is(
66
        Koha::Auth::Hostnames->search( { hostname => 'newopac.example.com' } )->count, 1,
67
        'Idempotent: no duplicate created'
68
    );
69
70
    # http:// URLs are supported
71
    Koha::Auth::Hostname->sync_from_syspref( 'OPACBaseURL', 'http://opac.example.org' );
72
    $opac->discard_changes;
73
    is( $opac->hostname, 'opac.example.org', 'http:// URL extracted correctly' );
74
75
    # Port numbers are stripped
76
    Koha::Auth::Hostname->sync_from_syspref( 'OPACBaseURL', 'https://opac.example.net:8080/path' );
77
    $opac->discard_changes;
78
    is( $opac->hostname, 'opac.example.net', 'Port number stripped from hostname' );
79
80
    # Empty value is a no-op
81
    my $hostname_before = $opac->hostname;
82
    Koha::Auth::Hostname->sync_from_syspref( 'OPACBaseURL', '' );
83
    $opac->discard_changes;
84
    is( $opac->hostname, $hostname_before, 'Empty value leaves existing hostname unchanged' );
85
86
    # Malformed URL (no protocol) is a no-op
87
    Koha::Auth::Hostname->sync_from_syspref( 'OPACBaseURL', 'not-a-valid-url' );
88
    $opac->discard_changes;
89
    is( $opac->hostname, $hostname_before, 'Malformed URL leaves existing hostname unchanged' );
90
91
    # Unknown preference name is ignored
92
    Koha::Auth::Hostname->sync_from_syspref( 'SomeOtherPref', 'https://other.example.com' );
93
    is(
94
        Koha::Auth::Hostnames->search( { hostname => 'other.example.com' } )->count,
95
        0, 'Unknown syspref name is ignored'
96
    );
97
98
    $schema->storage->txn_rollback;
99
};
100
101
subtest 'sync_from_sysprefs() tests' => sub {
102
103
    plan tests => 4;
104
105
    $schema->storage->txn_begin;
106
107
    Koha::Auth::Hostnames->search->delete;
108
109
    t::lib::Mocks::mock_preference( 'OPACBaseURL',        'https://opac.example.com' );
110
    t::lib::Mocks::mock_preference( 'staffClientBaseURL', 'https://staff.example.com' );
111
112
    Koha::Auth::Hostname->sync_from_sysprefs;
113
114
    my $opac  = Koha::Auth::Hostnames->find(1);
115
    my $staff = Koha::Auth::Hostnames->find(2);
116
117
    ok( $opac, 'hostname_id=1 exists after sync_from_sysprefs' );
118
    is( $opac->hostname, 'opac.example.com', 'hostname_id=1 has OPAC hostname' );
119
120
    ok( $staff, 'hostname_id=2 exists after sync_from_sysprefs' );
121
    is( $staff->hostname, 'staff.example.com', 'hostname_id=2 has staff hostname' );
122
123
    $schema->storage->txn_rollback;
124
};
125
126
subtest 'Koha::Auth::Hostnames type tests' => sub {
127
128
    plan tests => 2;
129
130
    $schema->storage->txn_begin;
131
132
    my $hostname = $builder->build_object( { class => 'Koha::Auth::Hostnames' } );
133
    is( ref($hostname), 'Koha::Auth::Hostname', 'build_object returns correct object type' );
134
135
    my $hostnames = Koha::Auth::Hostnames->search( { hostname_id => $hostname->hostname_id } );
136
    is( ref($hostnames), 'Koha::Auth::Hostnames', 'search returns correct collection type' );
137
138
    $schema->storage->txn_rollback;
139
};
(-)a/t/db_dependent/Koha/Auth/Identity/Provider.t (-38 / +120 lines)
Lines 20-32 Link Here
20
use Modern::Perl;
20
use Modern::Perl;
21
21
22
use Test::NoWarnings;
22
use Test::NoWarnings;
23
use Test::More tests => 7;
23
use Test::More tests => 8;
24
24
25
use Test::MockModule;
25
use Test::MockModule;
26
use Test::Exception;
26
use Test::Exception;
27
27
28
use JSON qw(encode_json);
28
use JSON qw(encode_json);
29
29
30
use Koha::Auth::Hostnames;
31
use Koha::Auth::Identity::Provider;
32
use Koha::Auth::Identity::Provider::Hostnames;
33
use Koha::Auth::Identity::Provider::Mappings;
30
use Koha::Auth::Identity::Providers;
34
use Koha::Auth::Identity::Providers;
31
35
32
use t::lib::TestBuilder;
36
use t::lib::TestBuilder;
Lines 88-94 subtest 'set_config() tests' => sub { Link Here
88
92
89
        my $provider =
93
        my $provider =
90
            $builder->build_object( { class => 'Koha::Auth::Identity::Providers', value => { protocol => 'OIDC' } } );
94
            $builder->build_object( { class => 'Koha::Auth::Identity::Providers', value => { protocol => 'OIDC' } } );
91
        $provider = $provider->upgrade_class;
92
95
93
        my $config = {
96
        my $config = {
94
            key    => 'key',
97
            key    => 'key',
Lines 114-120 subtest 'set_config() tests' => sub { Link Here
114
117
115
        my $provider =
118
        my $provider =
116
            $builder->build_object( { class => 'Koha::Auth::Identity::Providers', value => { protocol => 'OAuth' } } );
119
            $builder->build_object( { class => 'Koha::Auth::Identity::Providers', value => { protocol => 'OAuth' } } );
117
        $provider = $provider->upgrade_class;
118
120
119
        my $config = {
121
        my $config = {
120
            key       => 'key',
122
            key       => 'key',
Lines 135-149 subtest 'set_config() tests' => sub { Link Here
135
        is_deeply( $provider->get_config, $config, 'Configuration stored correctly' );
137
        is_deeply( $provider->get_config, $config, 'Configuration stored correctly' );
136
    };
138
    };
137
139
138
    subtest 'Unsupported protocol tests' => sub {
140
    subtest 'Base class (unsupported protocol) tests' => sub {
139
141
140
        plan tests => 2;
142
        plan tests => 2;
141
143
142
        my $provider =
144
        # Cannot build in DB with invalid protocol; instantiate base class directly
143
            $builder->build_object( { class => 'Koha::Auth::Identity::Providers', value => { protocol => 'CAS' } } );
145
        my $provider = Koha::Auth::Identity::Provider->new( { protocol => 'OAuth' } );
144
146
145
        throws_ok { $provider->set_config() }
147
        throws_ok { $provider->set_config( {} ) }
146
        'Koha::Exception', 'Exception thrown on unsupported protocol';
148
        'Koha::Exception', 'Exception thrown when calling set_config on base class';
147
149
148
        like( "$@", qr/This method needs to be subclassed/, 'Message is correct' );
150
        like( "$@", qr/This method needs to be subclassed/, 'Message is correct' );
149
    };
151
    };
Lines 151-198 subtest 'set_config() tests' => sub { Link Here
151
    $schema->storage->txn_rollback;
153
    $schema->storage->txn_rollback;
152
};
154
};
153
155
154
subtest 'get_mapping() tests' => sub {
156
subtest 'mappings() tests' => sub {
155
157
156
    plan tests => 2;
158
    plan tests => 3;
157
159
158
    $schema->storage->txn_begin;
160
    $schema->storage->txn_begin;
159
161
160
    my $provider = $builder->build_object( { class => 'Koha::Auth::Identity::Providers', value => { config => '{' } } );
162
    my $provider = $builder->build_object( { class => 'Koha::Auth::Identity::Providers' } );
161
163
    my $mappings = $provider->mappings;
162
    throws_ok { $provider->get_mapping() }
164
163
    'Koha::Exceptions::Object::BadValue', 'Expected exception thrown on bad JSON';
165
    is( ref($mappings),   'Koha::Auth::Identity::Provider::Mappings', 'Type is correct' );
164
166
    is( $mappings->count, 0,                                          'No mappings defined' );
165
    my $mapping = { some => 'value', and => 'another' };
167
166
    $provider->mapping( encode_json($mapping) )->store;
168
    $builder->build(
167
169
        {
168
    is_deeply( $provider->get_mapping, $mapping, 'Mapping correctly retrieved' );
170
            source => 'IdentityProviderMapping',
171
            value  => { identity_provider_id => $provider->id, koha_field => 'userid', provider_field => 'uid' }
172
        }
173
    );
174
    $builder->build(
175
        {
176
            source => 'IdentityProviderMapping',
177
            value  => { identity_provider_id => $provider->id, koha_field => 'email', provider_field => 'mail' }
178
        }
179
    );
180
181
    is( $provider->mappings->count, 2, 'The provider has 2 mappings defined' );
169
182
170
    $schema->storage->txn_rollback;
183
    $schema->storage->txn_rollback;
171
};
184
};
172
185
173
subtest 'set_mapping() tests' => sub {
186
subtest 'hostnames() tests' => sub {
174
187
175
    plan tests => 1;
188
    plan tests => 3;
176
189
177
    $schema->storage->txn_begin;
190
    $schema->storage->txn_begin;
178
191
179
    my $provider = $builder->build_object( { class => 'Koha::Auth::Identity::Providers' } );
192
    my $provider  = $builder->build_object( { class => 'Koha::Auth::Identity::Providers' } );
193
    my $hostnames = $provider->hostnames;
180
194
181
    my $mapping = { some => 'value', and => 'another' };
195
    is( ref($hostnames),   'Koha::Auth::Identity::Provider::Hostnames', 'Type is correct' );
182
    $provider->set_mapping($mapping)->store;
196
    is( $hostnames->count, 0,                                           'No hostnames defined' );
197
198
    $builder->build_object(
199
        { class => 'Koha::Auth::Identity::Provider::Hostnames', value => { identity_provider_id => $provider->id } } );
200
    $builder->build_object(
201
        { class => 'Koha::Auth::Identity::Provider::Hostnames', value => { identity_provider_id => $provider->id } } );
183
202
184
    is_deeply( $provider->get_mapping, $mapping, 'Mapping correctly retrieved' );
203
    is( $provider->hostnames->count, 2, 'The provider has 2 hostnames defined' );
185
204
186
    $schema->storage->txn_rollback;
205
    $schema->storage->txn_rollback;
187
};
206
};
188
207
189
subtest 'upgrade_class() tests' => sub {
208
subtest 'polymorphic_retrieval() tests' => sub {
190
209
191
    plan tests => 5;
210
    plan tests => 7;
192
211
193
    $schema->storage->txn_begin;
212
    $schema->storage->txn_begin;
194
213
195
    my $mapping   = Koha::Auth::Identity::Provider::protocol_to_class_mapping;
214
    my $providers = Koha::Auth::Identity::Providers->new;
215
    my $mapping   = $providers->_polymorphic_map;
196
    my @protocols = keys %{$mapping};
216
    my @protocols = keys %{$mapping};
197
217
198
    foreach my $protocol (@protocols) {
218
    foreach my $protocol (@protocols) {
Lines 204-223 subtest 'upgrade_class() tests' => sub { Link Here
204
            }
224
            }
205
        );
225
        );
206
226
207
        is( ref($provider), 'Koha::Auth::Identity::Provider', "Base class used for $protocol" );
227
        is( ref($provider), $mapping->{$protocol}, "build_object returns correct subclass for $protocol" );
208
228
209
        # upgrade
229
        my $found = Koha::Auth::Identity::Providers->find( $provider->id );
210
        $provider = $provider->upgrade_class;
230
        is( ref($found), $mapping->{$protocol}, "Providers->find returns correct subclass for $protocol" );
211
        is(
212
            ref($provider), $mapping->{$protocol},
213
            "Class upgraded to " . $mapping->{$protocol} . "for protocol $protocol"
214
        );
215
    }
231
    }
216
232
217
    my $provider = Koha::Auth::Identity::Provider->new( { protocol => 'Invalid' } );
233
    my $provider = Koha::Auth::Identity::Provider->new( { protocol => 'Invalid' } );
218
    throws_ok { $provider->upgrade }
234
    throws_ok { $provider->set_config( {} ) } 'Koha::Exception', 'Exception thrown calling set_config on base class';
219
    'Koha::Exception',
235
220
        'Exception throw on invalid protocol';
236
    $schema->storage->txn_rollback;
237
};
238
239
subtest 'find_forced_provider() tests' => sub {
240
241
    plan tests => 8;
242
243
    $schema->storage->txn_begin;
244
245
    # No hostname argument returns undef
246
    is( Koha::Auth::Identity::Providers->find_forced_provider(undef), undef, 'Returns undef when hostname is undef' );
247
    is(
248
        Koha::Auth::Identity::Providers->find_forced_provider(''), undef,
249
        'Returns undef when hostname is empty string'
250
    );
251
252
    my $provider = $builder->build_object(
253
        { class => 'Koha::Auth::Identity::Providers', value => { protocol => 'OIDC', enabled => 1 } } );
254
255
    my $hostname_record =
256
        $builder->build_object( { class => 'Koha::Auth::Hostnames', value => { hostname => 'sso.example.com' } } );
257
258
    # Hostname linked but force_sso=0 (default)
259
    $builder->build_object(
260
        {
261
            class => 'Koha::Auth::Identity::Provider::Hostnames',
262
            value => {
263
                identity_provider_id => $provider->id,
264
                hostname_id          => $hostname_record->hostname_id,
265
                is_enabled           => 1,
266
                force_sso            => 0,
267
            }
268
        }
269
    );
270
    is(
271
        Koha::Auth::Identity::Providers->find_forced_provider('sso.example.com'),
272
        undef, 'Returns undef when hostname is linked but force_sso is 0'
273
    );
274
275
    # Update to force_sso=1 but is_enabled=0 on the hostname link
276
    Koha::Auth::Identity::Provider::Hostnames->search(
277
        { identity_provider_id => $provider->id, hostname_id => $hostname_record->hostname_id } )
278
        ->update( { force_sso => 1, is_enabled => 0 } );
279
    is(
280
        Koha::Auth::Identity::Providers->find_forced_provider('sso.example.com'),
281
        undef, 'Returns undef when hostname link has is_enabled=0'
282
    );
283
284
    # Enable the hostname link but disable the provider itself
285
    Koha::Auth::Identity::Provider::Hostnames->search(
286
        { identity_provider_id => $provider->id, hostname_id => $hostname_record->hostname_id } )
287
        ->update( { is_enabled => 1 } );
288
    $provider->update( { enabled => 0 } );
289
    is(
290
        Koha::Auth::Identity::Providers->find_forced_provider('sso.example.com'),
291
        undef, 'Returns undef when provider itself is disabled'
292
    );
293
294
    # Re-enable the provider - now all conditions met
295
    $provider->update( { enabled => 1 } );
296
    my $forced = Koha::Auth::Identity::Providers->find_forced_provider('sso.example.com');
297
    ok( $forced, 'Returns provider when hostname linked with force_sso=1, is_enabled=1, and provider enabled=1' );
298
    is(
299
        ref($forced), 'Koha::Auth::Identity::Provider::OIDC',
300
        'Returned object is the correct Identity Provider subclass'
301
    );
302
    is( $forced->id, $provider->id, 'Returns the correct provider' );
221
303
222
    $schema->storage->txn_rollback;
304
    $schema->storage->txn_rollback;
223
};
305
};
(-)a/t/db_dependent/Koha/Auth/Identity/Provider/Mapping.t (-1 / +207 lines)
Line 0 Link Here
0
- 
1
#!/usr/bin/perl
2
3
# Copyright Koha Community 2026
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 <https://www.gnu.org/licenses>.
19
20
use Modern::Perl;
21
22
use Test::NoWarnings;
23
use Test::More tests => 4;
24
use Test::Exception;
25
26
use Koha::Auth::Identity::Provider::Mapping;
27
use Koha::Auth::Identity::Provider::Mappings;
28
use Koha::Auth::Identity::Providers;
29
30
use t::lib::TestBuilder;
31
32
my $schema  = Koha::Database->new->schema;
33
my $builder = t::lib::TestBuilder->new;
34
35
subtest 'Mapping::store() tests' => sub {
36
37
    plan tests => 8;
38
39
    $schema->storage->txn_begin;
40
41
    my $provider = $builder->build_object( { class => 'Koha::Auth::Identity::Providers' } );
42
43
    # Must throw if neither provider_field nor default_content is set
44
    throws_ok {
45
        Koha::Auth::Identity::Provider::Mapping->new(
46
            {
47
                identity_provider_id => $provider->id,
48
                koha_field           => 'userid',
49
            }
50
        )->store;
51
    }
52
    'Koha::Exceptions::MissingParameter',
53
        'Exception thrown when neither provider_field nor default_content is provided';
54
55
    like( $@->parameter, qr/provider_field or default_content/, 'Exception parameter message is correct' );
56
57
    # Succeeds with provider_field only
58
    my $m1 = Koha::Auth::Identity::Provider::Mapping->new(
59
        {
60
            identity_provider_id => $provider->id,
61
            koha_field           => 'userid',
62
            provider_field       => 'uid',
63
        }
64
    )->store;
65
    ok( $m1, 'store() succeeds when only provider_field is supplied' );
66
67
    # Succeeds with default_content only
68
    my $m2 = Koha::Auth::Identity::Provider::Mapping->new(
69
        {
70
            identity_provider_id => $provider->id,
71
            koha_field           => 'categorycode',
72
            default_content      => 'PT',
73
        }
74
    )->store;
75
    ok( $m2, 'store() succeeds when only default_content is supplied' );
76
77
    # Succeeds with both provider_field and default_content
78
    my $m3 = Koha::Auth::Identity::Provider::Mapping->new(
79
        {
80
            identity_provider_id => $provider->id,
81
            koha_field           => 'branchcode',
82
            provider_field       => 'branch',
83
            default_content      => 'CPL',
84
        }
85
    )->store;
86
    ok( $m3, 'store() succeeds when both provider_field and default_content are supplied' );
87
88
    # Setting is_matchpoint=1 clears any existing matchpoint for the same provider
89
    my $first_mp = Koha::Auth::Identity::Provider::Mapping->new(
90
        {
91
            identity_provider_id => $provider->id,
92
            koha_field           => 'email',
93
            provider_field       => 'mail',
94
            is_matchpoint        => 1,
95
        }
96
    )->store;
97
    is( $first_mp->is_matchpoint, 1, 'First matchpoint stored correctly' );
98
99
    my $second_mp = Koha::Auth::Identity::Provider::Mapping->new(
100
        {
101
            identity_provider_id => $provider->id,
102
            koha_field           => 'surname',
103
            provider_field       => 'sn',
104
            is_matchpoint        => 1,
105
        }
106
    )->store;
107
108
    $first_mp->discard_changes;
109
    is( $first_mp->is_matchpoint,  0, 'Previous matchpoint cleared when new matchpoint is stored' );
110
    is( $second_mp->is_matchpoint, 1, 'New matchpoint is active' );
111
112
    $schema->storage->txn_rollback;
113
};
114
115
subtest 'Mappings::get_matchpoint() tests' => sub {
116
117
    plan tests => 3;
118
119
    $schema->storage->txn_begin;
120
121
    my $provider = $builder->build_object( { class => 'Koha::Auth::Identity::Providers' } );
122
123
    is( $provider->mappings->get_matchpoint, undef, 'Returns undef when provider has no mappings' );
124
125
    # Add a non-matchpoint mapping via builder (bypasses store() constraint)
126
    $builder->build(
127
        {
128
            source => 'IdentityProviderMapping',
129
            value  => {
130
                identity_provider_id => $provider->id,
131
                koha_field           => 'userid',
132
                provider_field       => 'uid',
133
                is_matchpoint        => 0,
134
            }
135
        }
136
    );
137
    is( $provider->mappings->get_matchpoint, undef, 'Returns undef when no mapping is flagged as matchpoint' );
138
139
    # Add a matchpoint mapping
140
    $builder->build(
141
        {
142
            source => 'IdentityProviderMapping',
143
            value  => {
144
                identity_provider_id => $provider->id,
145
                koha_field           => 'email',
146
                provider_field       => 'mail',
147
                is_matchpoint        => 1,
148
            }
149
        }
150
    );
151
    my $matchpoint = $provider->mappings->get_matchpoint;
152
    is( $matchpoint->koha_field, 'email', 'Returns the mapping flagged as matchpoint' );
153
154
    $schema->storage->txn_rollback;
155
};
156
157
subtest 'Mappings::as_auth_mapping() tests' => sub {
158
159
    plan tests => 4;
160
161
    $schema->storage->txn_begin;
162
163
    my $provider = $builder->build_object( { class => 'Koha::Auth::Identity::Providers' } );
164
165
    my ( $mapping, $matchpoint ) = $provider->mappings->as_auth_mapping;
166
    is_deeply( $mapping, {}, 'Returns empty hashref when provider has no mappings' );
167
    is( $matchpoint, undef, 'Returns undef matchpoint when provider has no matchpoint' );
168
169
    # Create mappings: one with provider_field (matchpoint), one with default_content only
170
    $builder->build(
171
        {
172
            source => 'IdentityProviderMapping',
173
            value  => {
174
                identity_provider_id => $provider->id,
175
                koha_field           => 'userid',
176
                provider_field       => 'uid',
177
                default_content      => undef,
178
                is_matchpoint        => 1,
179
            }
180
        }
181
    );
182
    $builder->build(
183
        {
184
            source => 'IdentityProviderMapping',
185
            value  => {
186
                identity_provider_id => $provider->id,
187
                koha_field           => 'categorycode',
188
                provider_field       => undef,
189
                default_content      => 'PT',
190
                is_matchpoint        => 0,
191
            }
192
        }
193
    );
194
195
    ( $mapping, $matchpoint ) = $provider->mappings->as_auth_mapping;
196
    is_deeply(
197
        $mapping,
198
        {
199
            userid       => { is => 'uid', content => undef },
200
            categorycode => { is => undef, content => 'PT' },
201
        },
202
        'Returns correct mapping hashref with is/content keys for each koha_field'
203
    );
204
    is( $matchpoint, 'userid', 'Returns the koha_field name of the matchpoint mapping' );
205
206
    $schema->storage->txn_rollback;
207
};

Return to bug 39224