Issues (2963)

tests/AuthSSOTest.php (4 issues)

1
<?php
2
/**
3
 * AuthSSO.php
4
 *
5
 * -Description-
6
 *
7
 * This program is free software: you can redistribute it and/or modify
8
 * it 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
 * This program is distributed in the hope that it will be useful,
13
 * but 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 this program.  If not, see <https://www.gnu.org/licenses/>.
19
 *
20
 * @link       https://librenms.org
21
 *
22
 * @copyright  2017 Adam Bishop
23
 * @author     Adam Bishop <[email protected]>
24
 */
25
26
namespace LibreNMS\Tests;
27
28
use Illuminate\Foundation\Testing\DatabaseTransactions;
29
use Illuminate\Support\Str;
30
use LibreNMS\Authentication\LegacyAuth;
31
use LibreNMS\Config;
32
33
class AuthSSOTest extends DBTestCase
34
{
35
    use DatabaseTransactions;
36
37
    private $original_auth_mech = null;
38
    private $server;
39
40
    protected function setUp(): void
41
    {
42
        parent::setUp();
43
44
        $this->original_auth_mech = Config::get('auth_mechanism');
45
        Config::set('auth_mechanism', 'sso');
46
47
        $this->server = $_SERVER;
48
    }
49
50
    // Set up an SSO config for tests
51
    public function basicConfig()
52
    {
53
        Config::set('sso.mode', 'env');
54
        Config::set('sso.create_users', true);
55
        Config::set('sso.update_users', true);
56
        Config::set('sso.trusted_proxies', ['127.0.0.1', '::1']);
57
        Config::set('sso.user_attr', 'REMOTE_USER');
58
        Config::set('sso.realname_attr', 'displayName');
59
        Config::set('sso.email_attr', 'mail');
60
        Config::set('sso.descr_attr', null);
61
        Config::set('sso.level_attr', null);
62
        Config::set('sso.group_strategy', 'static');
63
        Config::set('sso.group_attr', 'member');
64
        Config::set('sso.group_filter', '/(.*)/i');
65
        Config::set('sso.group_delimiter', ';');
66
        Config::set('sso.group_level_map', null);
67
        Config::set('sso.static_level', -1);
68
    }
69
70
    // Set up $_SERVER in env mode
71
    public function basicEnvironmentEnv()
72
    {
73
        unset($_SERVER);
74
75
        Config::set('sso.mode', 'env');
76
77
        $_SERVER['REMOTE_ADDR'] = '::1';
78
        $_SERVER['REMOTE_USER'] = 'test';
79
80
        $_SERVER['mail'] = '[email protected]';
81
        $_SERVER['displayName'] = Str::random();
82
    }
83
84
    // Set up $_SERVER in header mode
85
    public function basicEnvironmentHeader()
86
    {
87
        unset($_SERVER);
88
89
        Config::set('sso.mode', 'header');
90
91
        $_SERVER['REMOTE_ADDR'] = '::1';
92
        $_SERVER['REMOTE_USER'] = Str::random();
93
94
        $_SERVER['HTTP_MAIL'] = '[email protected]';
95
        $_SERVER['HTTP_DISPLAYNAME'] = 'Test User';
96
    }
97
98
    public function makeUser()
99
    {
100
        $user = Str::random();
101
        $_SERVER['REMOTE_USER'] = $user;
102
103
        return $user;
104
    }
105
106
    // Excercise general auth flow
107
    public function testValidAuthNoCreateUpdate()
108
    {
109
        $this->basicConfig();
110
        $a = LegacyAuth::reset();
111
112
        Config::set('sso.create_users', false);
113
        Config::set('sso.update_users', false);
114
115
        // Create a random username and store it with the defaults
116
        $this->basicEnvironmentEnv();
117
        $user = $this->makeUser();
118
        $this->assertTrue($a->authenticate(['username' => $user]));
119
120
        // Retrieve it and validate
121
        $dbuser = $a->getUser($a->getUserid($user));
122
        $this->assertFalse($dbuser);
123
    }
124
125
    // Excercise general auth flow with creation enabled
126
    public function testValidAuthCreateOnly()
127
    {
128
        $this->basicConfig();
129
        /** @var \LibreNMS\Authentication\SSOAuthorizer */
130
        $a = LegacyAuth::reset();
131
132
        Config::set('sso.create_users', true);
133
        Config::set('sso.update_users', false);
134
135
        // Create a random username and store it with the defaults
136
        $this->basicEnvironmentEnv();
137
        $user = $this->makeUser();
138
        $this->assertTrue($a->authenticate(['username' => $user]));
139
140
        // Retrieve it and validate
141
        $dbuser = $a->getUser($a->getUserid($user));
142
        $this->assertTrue($a->authSSOGetAttr(Config::get('sso.realname_attr')) === $dbuser['realname']);
0 ignored issues
show
The method authSSOGetAttr() does not exist on LibreNMS\Interfaces\Authentication\Authorizer. It seems like you code against a sub-type of LibreNMS\Interfaces\Authentication\Authorizer such as LibreNMS\Authentication\SSOAuthorizer. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

142
        $this->assertTrue($a->/** @scrutinizer ignore-call */ authSSOGetAttr(Config::get('sso.realname_attr')) === $dbuser['realname']);
Loading history...
143
        $this->assertTrue($dbuser['level'] == -1);
144
        $this->assertTrue($a->authSSOGetAttr(Config::get('sso.email_attr')) === $dbuser['email']);
145
146
        // Change a few things and reauth
147
        $_SERVER['mail'] = '[email protected]';
148
        $_SERVER['displayName'] = 'Testier User';
149
        Config::set('sso.static_level', 10);
150
        $this->assertTrue($a->authenticate(['username' => $user]));
151
152
        // Retrieve it and validate the update was not persisted
153
        $dbuser = $a->getUser($a->getUserid($user));
154
        $this->assertFalse($a->authSSOGetAttr(Config::get('sso.realname_attr')) === $dbuser['realname']);
155
        $this->assertFalse($dbuser['level'] === '10');
156
        $this->assertFalse($a->authSSOGetAttr(Config::get('sso.email_attr')) === $dbuser['email']);
157
    }
158
159
    // Excercise general auth flow with updates enabled
160
    public function testValidAuthUpdate()
161
    {
162
        $this->basicConfig();
163
        /** @var \LibreNMS\Authentication\SSOAuthorizer */
164
        $a = LegacyAuth::reset();
165
166
        // Create a random username and store it with the defaults
167
        $this->basicEnvironmentEnv();
168
        $user = $this->makeUser();
169
        $this->assertTrue($a->authenticate(['username' => $user]));
170
171
        // Change a few things and reauth
172
        $_SERVER['mail'] = '[email protected]';
173
        $_SERVER['displayName'] = 'Testier User';
174
        Config::set('sso.static_level', 10);
175
        $this->assertTrue($a->authenticate(['username' => $user]));
176
177
        // Retrieve it and validate the update persisted
178
        $dbuser = $a->getUser($a->getUserid($user));
179
        $this->assertTrue($a->authSSOGetAttr(Config::get('sso.realname_attr')) === $dbuser['realname']);
180
        $this->assertTrue($dbuser['level'] == 10);
181
        $this->assertTrue($a->authSSOGetAttr(Config::get('sso.email_attr')) === $dbuser['email']);
182
    }
183
184
    // Check some invalid authentication modes
185
    public function testBadAuth()
186
    {
187
        $this->basicConfig();
188
        /** @var \LibreNMS\Authentication\SSOAuthorizer */
189
        $a = LegacyAuth::reset();
190
191
        $this->basicEnvironmentEnv();
192
        unset($_SERVER);
193
194
        $this->expectException('LibreNMS\Exceptions\AuthenticationException');
195
        $a->authenticate([]);
196
197
        $this->basicEnvironmentHeader();
198
        unset($_SERVER);
199
200
        $this->expectException('LibreNMS\Exceptions\AuthenticationException');
201
        $a->authenticate([]);
202
    }
203
204
    // Test some missing attributes
205
    public function testNoAttribute()
206
    {
207
        $this->basicConfig();
208
        /** @var \LibreNMS\Authentication\SSOAuthorizer */
209
        $a = LegacyAuth::reset();
210
211
        $this->basicEnvironmentEnv();
212
        unset($_SERVER['displayName']);
213
        unset($_SERVER['mail']);
214
215
        $this->assertTrue($a->authenticate(['username' => $this->makeUser()]));
216
217
        $this->basicEnvironmentHeader();
218
        unset($_SERVER['HTTP_DISPLAYNAME']);
219
        unset($_SERVER['HTTP_MAIL']);
220
221
        $this->assertTrue($a->authenticate(['username' => $this->makeUser()]));
222
    }
223
224
    // Document the modules current behaviour, so that changes trigger test failures
225
    public function testCapabilityFunctions()
226
    {
227
        $a = LegacyAuth::reset();
228
229
        $this->assertFalse($a->canUpdatePasswords());
230
        $this->assertFalse($a->changePassword(null, null));
231
        $this->assertTrue($a->canManageUsers());
232
        $this->assertTrue($a->canUpdateUsers());
233
        $this->assertTrue($a->authIsExternal());
234
    }
235
236
    /* Everything from here comprises of targeted tests to excercise single methods */
237
238
    public function testGetExternalUserName()
239
    {
240
        $this->basicConfig();
241
        /** @var \LibreNMS\Authentication\SSOAuthorizer */
242
        $a = LegacyAuth::reset();
243
244
        $this->basicEnvironmentEnv();
245
        $this->assertIsString($a->getExternalUsername());
246
247
        // Missing
248
        unset($_SERVER['REMOTE_USER']);
249
        $this->assertNull($a->getExternalUsername());
250
        $this->basicEnvironmentEnv();
251
252
        // Missing pointer to attribute
253
        Config::forget('sso.user_attr');
254
        $this->assertNull($a->getExternalUsername());
255
        $this->basicEnvironmentEnv();
256
257
        // Non-existant attribute
258
        Config::set('sso.user_attr', 'foobar');
259
        $this->assertNull($a->getExternalUsername());
260
        $this->basicEnvironmentEnv();
261
262
        // null pointer to attribute
263
        Config::set('sso.user_attr', null);
264
        $this->assertNull($a->getExternalUsername());
265
        $this->basicEnvironmentEnv();
266
267
        // null attribute
268
        Config::set('sso.user_attr', 'REMOTE_USER');
269
        $_SERVER['REMOTE_USER'] = null;
270
        $this->assertNull($a->getExternalUsername());
271
    }
272
273
    public function testGetAttr()
274
    {
275
        /** @var \LibreNMS\Authentication\SSOAuthorizer */
276
        $a = LegacyAuth::reset();
277
278
        $_SERVER['HTTP_VALID_ATTR'] = 'string';
279
        $_SERVER['alsoVALID-ATTR'] = 'otherstring';
280
281
        Config::set('sso.mode', 'env');
282
        $this->assertNull($a->authSSOGetAttr('foobar'));
283
        $this->assertNull($a->authSSOGetAttr(null));
284
        $this->assertNull($a->authSSOGetAttr(1));
285
        $this->assertIsString($a->authSSOGetAttr('alsoVALID-ATTR'));
286
        $this->assertIsString($a->authSSOGetAttr('HTTP_VALID_ATTR'));
287
288
        Config::set('sso.mode', 'header');
289
        $this->assertNull($a->authSSOGetAttr('foobar'));
290
        $this->assertNull($a->authSSOGetAttr(null));
291
        $this->assertNull($a->authSSOGetAttr(1));
292
        $this->assertNull($a->authSSOGetAttr('alsoVALID-ATTR'));
293
        $this->assertIsString($a->authSSOGetAttr('VALID-ATTR'));
294
    }
295
296
    public function testTrustedProxies()
297
    {
298
        /** @var \LibreNMS\Authentication\SSOAuthorizer */
299
        $a = LegacyAuth::reset();
300
301
        Config::set('sso.trusted_proxies', ['127.0.0.1', '::1', '2001:630:50::/48', '8.8.8.0/25']);
302
303
        // v4 valid CIDR
304
        $_SERVER['REMOTE_ADDR'] = '8.8.8.8';
305
        $this->assertTrue($a->authSSOProxyTrusted());
0 ignored issues
show
The method authSSOProxyTrusted() does not exist on LibreNMS\Interfaces\Authentication\Authorizer. It seems like you code against a sub-type of LibreNMS\Interfaces\Authentication\Authorizer such as LibreNMS\Authentication\SSOAuthorizer. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

305
        $this->assertTrue($a->/** @scrutinizer ignore-call */ authSSOProxyTrusted());
Loading history...
306
307
        // v4 valid single
308
        $_SERVER['REMOTE_ADDR'] = '127.0.0.1';
309
        $this->assertTrue($a->authSSOProxyTrusted());
310
311
        // v4 invalid CIDR
312
        $_SERVER['REMOTE_ADDR'] = '9.8.8.8';
313
        $this->assertFalse($a->authSSOProxyTrusted());
314
315
        // v6 valid CIDR
316
        $_SERVER['REMOTE_ADDR'] = '2001:630:50:baad:beef:feed:face:cafe';
317
        $this->assertTrue($a->authSSOProxyTrusted());
318
319
        // v6 valid single
320
        $_SERVER['REMOTE_ADDR'] = '::1';
321
        $this->assertTrue($a->authSSOProxyTrusted());
322
323
        // v6 invalid CIDR
324
        $_SERVER['REMOTE_ADDR'] = '2600::';
325
        $this->assertFalse($a->authSSOProxyTrusted());
326
327
        // Not an IP
328
        $_SERVER['REMOTE_ADDR'] = 16;
329
        $this->assertFalse($a->authSSOProxyTrusted());
330
331
        //null
332
        $_SERVER['REMOTE_ADDR'] = null;
333
        $this->assertFalse($a->authSSOProxyTrusted());
334
335
        // Invalid String
336
        $_SERVER['REMOTE_ADDR'] = 'Not an IP address at all, but maybe PHP will end up type juggling somehow';
337
        $this->assertFalse($a->authSSOProxyTrusted());
338
339
        // Not a list
340
        Config::set('sso.trusted_proxies', '8.8.8.0/25');
341
        $_SERVER['REMOTE_ADDR'] = '8.8.8.8';
342
        $this->assertFalse($a->authSSOProxyTrusted());
343
344
        // Unset
345
        unset($_SERVER['REMOTE_ADDR']);
346
        $this->assertFalse($a->authSSOProxyTrusted());
347
    }
348
349
    public function testLevelCaulculationFromAttr()
350
    {
351
        /** @var \LibreNMS\Authentication\SSOAuthorizer */
352
        $a = LegacyAuth::reset();
353
354
        Config::set('sso.mode', 'env');
355
        Config::set('sso.group_strategy', 'attribute');
356
357
        //Integer
358
        Config::set('sso.level_attr', 'level');
359
        $_SERVER['level'] = 9;
360
        $this->assertTrue($a->authSSOCalculateLevel() === 9);
0 ignored issues
show
The method authSSOCalculateLevel() does not exist on LibreNMS\Interfaces\Authentication\Authorizer. It seems like you code against a sub-type of LibreNMS\Interfaces\Authentication\Authorizer such as LibreNMS\Authentication\SSOAuthorizer. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

360
        $this->assertTrue($a->/** @scrutinizer ignore-call */ authSSOCalculateLevel() === 9);
Loading history...
361
362
        //String
363
        Config::set('sso.level_attr', 'level');
364
        $_SERVER['level'] = '9';
365
        $this->assertTrue($a->authSSOCalculateLevel() === 9);
366
367
        //Invalid String
368
        Config::set('sso.level_attr', 'level');
369
        $_SERVER['level'] = 'foobar';
370
        $this->expectException('LibreNMS\Exceptions\AuthenticationException');
371
        $a->authSSOCalculateLevel();
372
373
        //null
374
        Config::set('sso.level_attr', 'level');
375
        $_SERVER['level'] = null;
376
        $this->expectException('LibreNMS\Exceptions\AuthenticationException');
377
        $a->authSSOCalculateLevel();
378
379
        //Unset pointer
380
        Config::forget('sso.level_attr');
381
        $_SERVER['level'] = '9';
382
        $this->expectException('LibreNMS\Exceptions\AuthenticationException');
383
        $a->authSSOCalculateLevel();
384
385
        //Unset attr
386
        Config::set('sso.level_attr', 'level');
387
        unset($_SERVER['level']);
388
        $this->expectException('LibreNMS\Exceptions\AuthenticationException');
389
        $a->authSSOCalculateLevel();
390
    }
391
392
    public function testGroupParsing()
393
    {
394
        $this->basicConfig();
395
        /** @var \LibreNMS\Authentication\SSOAuthorizer */
396
        $a = LegacyAuth::reset();
397
398
        $this->basicEnvironmentEnv();
399
400
        Config::set('sso.group_strategy', 'map');
401
        Config::set('sso.group_delimiter', ';');
402
        Config::set('sso.group_attr', 'member');
403
        Config::set('sso.group_level_map', ['librenms-admins' => 10, 'librenms-readers' => 1, 'librenms-billingcontacts' => 5]);
404
        $_SERVER['member'] = 'librenms-admins;librenms-readers;librenms-billingcontacts;unrelatedgroup;confluence-admins';
405
406
        // Valid options
407
        $this->assertTrue($a->authSSOParseGroups() === 10);
0 ignored issues
show
The method authSSOParseGroups() does not exist on LibreNMS\Interfaces\Authentication\Authorizer. It seems like you code against a sub-type of LibreNMS\Interfaces\Authentication\Authorizer such as LibreNMS\Authentication\SSOAuthorizer. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

407
        $this->assertTrue($a->/** @scrutinizer ignore-call */ authSSOParseGroups() === 10);
Loading history...
408
409
        // No match
410
        $_SERVER['member'] = 'confluence-admins';
411
        $this->assertTrue($a->authSSOParseGroups() === 0);
412
413
        // Delimiter only
414
        $_SERVER['member'] = ';;;;';
415
        $this->assertTrue($a->authSSOParseGroups() === 0);
416
417
        // Empty
418
        $_SERVER['member'] = '';
419
        $this->assertTrue($a->authSSOParseGroups() === 0);
420
421
        // Null
422
        $_SERVER['member'] = null;
423
        $this->assertTrue($a->authSSOParseGroups() === 0);
424
425
        // Unset
426
        unset($_SERVER['member']);
427
        $this->assertTrue($a->authSSOParseGroups() === 0);
428
429
        $_SERVER['member'] = 'librenms-admins;librenms-readers;librenms-billingcontacts;unrelatedgroup;confluence-admins';
430
431
        // Empty
432
        Config::set('sso.group_level_map', []);
433
        $this->assertTrue($a->authSSOParseGroups() === 0);
434
435
        // Not associative
436
        Config::set('sso.group_level_map', ['foo', 'bar', 'librenms-admins']);
437
        $this->assertTrue($a->authSSOParseGroups() === 0);
438
439
        // Null
440
        Config::set('sso.group_level_map', null);
441
        $this->assertTrue($a->authSSOParseGroups() === 0);
442
443
        // Unset
444
        Config::forget('sso.group_level_map');
445
        $this->assertTrue($a->authSSOParseGroups() === 0);
446
447
        // No delimiter
448
        Config::forget('sso.group_delimiter');
449
        $this->assertTrue($a->authSSOParseGroups() === 0);
450
451
        // Test group filtering by regex
452
        Config::set('sso.group_filter', '/confluence-(.*)/i');
453
        Config::set('sso.group_delimiter', ';');
454
        Config::set('sso.group_level_map', ['librenms-admins' => 10, 'librenms-readers' => 1, 'librenms-billingcontacts' => 5, 'confluence-admins' => 7]);
455
        $this->assertTrue($a->authSSOParseGroups() === 7);
456
457
        // Test group filtering by empty regex
458
        Config::set('sso.group_filter', '');
459
        $this->assertTrue($a->authSSOParseGroups() === 10);
460
461
        // Test group filtering by null regex
462
        Config::set('sso.group_filter', null);
463
        $this->assertTrue($a->authSSOParseGroups() === 10);
464
    }
465
466
    protected function tearDown(): void
467
    {
468
        Config::set('auth_mechanism', $this->original_auth_mech);
469
        Config::forget('sso');
470
        $_SERVER = $this->server;
471
        parent::tearDown();
472
    }
473
}
474