Completed
Push — master ( ac6993...e8ded2 )
by Greg
05:38
created

Auth   F

Complexity

Total Complexity 78

Size/Duplication

Total Lines 504
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 135
c 0
b 0
f 0
dl 0
loc 504
rs 2.16
wmc 78

25 Methods

Rating   Name   Duplication   Size   Complexity  
A logout() 0 3 1
A login() 0 4 1
A checkComponentAccess() 0 4 2
A user() 0 3 1
A isAdmin() 0 5 1
A check() 0 3 1
A isManager() 0 5 2
A isEditor() 0 7 2
B checkIndividualAccess() 0 21 7
A accessLevel() 0 13 3
A id() 0 3 1
A isModerator() 0 7 2
A checkHeaderAccess() 0 17 5
A isMember() 0 7 2
A checkFamilyAccess() 0 17 5
A checkSourceAccess() 0 17 5
A checkRepositoryAccess() 0 17 5
A checkRecordAccess() 0 17 5
A checkMediaAccess() 0 17 5
A checkLocationAccess() 0 17 5
A privacyRuleNames() 0 7 1
A accessLevelNames() 0 7 1
A checkSubmitterAccess() 0 17 5
A checkNoteAccess() 0 17 5
A checkSubmissionAccess() 0 17 5

How to fix   Complexity   

Complex Class

Complex classes like Auth often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Auth, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
/**
4
 * webtrees: online genealogy
5
 * Copyright (C) 2021 webtrees development team
6
 * This program is free software: you can redistribute it and/or modify
7
 * it under the terms of the GNU General Public License as published by
8
 * the Free Software Foundation, either version 3 of the License, or
9
 * (at your option) any later version.
10
 * This program is distributed in the hope that it will be useful,
11
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
 * GNU General Public License for more details.
14
 * You should have received a copy of the GNU General Public License
15
 * along with this program. If not, see <https://www.gnu.org/licenses/>.
16
 */
17
18
declare(strict_types=1);
19
20
namespace Fisharebest\Webtrees;
21
22
use Fisharebest\Webtrees\Contracts\UserInterface;
23
use Fisharebest\Webtrees\Exceptions\FamilyAccessDeniedException;
24
use Fisharebest\Webtrees\Exceptions\FamilyNotFoundException;
25
use Fisharebest\Webtrees\Exceptions\HttpAccessDeniedException;
26
use Fisharebest\Webtrees\Exceptions\IndividualAccessDeniedException;
27
use Fisharebest\Webtrees\Exceptions\IndividualNotFoundException;
28
use Fisharebest\Webtrees\Exceptions\MediaAccessDeniedException;
29
use Fisharebest\Webtrees\Exceptions\MediaNotFoundException;
30
use Fisharebest\Webtrees\Exceptions\NoteAccessDeniedException;
31
use Fisharebest\Webtrees\Exceptions\NoteNotFoundException;
32
use Fisharebest\Webtrees\Exceptions\RecordAccessDeniedException;
33
use Fisharebest\Webtrees\Exceptions\RecordNotFoundException;
34
use Fisharebest\Webtrees\Exceptions\RepositoryAccessDeniedException;
35
use Fisharebest\Webtrees\Exceptions\RepositoryNotFoundException;
36
use Fisharebest\Webtrees\Exceptions\SourceAccessDeniedException;
37
use Fisharebest\Webtrees\Exceptions\SourceNotFoundException;
38
use Fisharebest\Webtrees\Module\ModuleInterface;
39
use Fisharebest\Webtrees\Services\UserService;
40
41
/**
42
 * Authentication.
43
 */
44
class Auth
45
{
46
    // Privacy constants
47
    public const PRIV_PRIVATE = 2; // Allows visitors to view the item
48
    public const PRIV_USER    = 1; // Allows members to access the item
49
    public const PRIV_NONE    = 0; // Allows managers to access the item
50
    public const PRIV_HIDE    = -1; // Hide the item to all users
51
52
    /**
53
     * Are we currently logged in?
54
     *
55
     * @return bool
56
     */
57
    public static function check(): bool
58
    {
59
        return self::id() !== null;
60
    }
61
62
    /**
63
     * Is the specified/current user an administrator?
64
     *
65
     * @param UserInterface|null $user
66
     *
67
     * @return bool
68
     */
69
    public static function isAdmin(UserInterface $user = null): bool
70
    {
71
        $user = $user ?? self::user();
72
73
        return $user->getPreference(UserInterface::PREF_IS_ADMINISTRATOR) === '1';
74
    }
75
76
    /**
77
     * Is the specified/current user a manager of a tree?
78
     *
79
     * @param Tree               $tree
80
     * @param UserInterface|null $user
81
     *
82
     * @return bool
83
     */
84
    public static function isManager(Tree $tree, UserInterface $user = null): bool
85
    {
86
        $user = $user ?? self::user();
87
88
        return self::isAdmin($user) || $tree->getUserPreference($user, UserInterface::PREF_TREE_ROLE) === UserInterface::ROLE_MANAGER;
89
    }
90
91
    /**
92
     * Is the specified/current user a moderator of a tree?
93
     *
94
     * @param Tree               $tree
95
     * @param UserInterface|null $user
96
     *
97
     * @return bool
98
     */
99
    public static function isModerator(Tree $tree, UserInterface $user = null): bool
100
    {
101
        $user = $user ?? self::user();
102
103
        return
104
            self::isManager($tree, $user) ||
105
            $tree->getUserPreference($user, UserInterface::PREF_TREE_ROLE) === UserInterface::ROLE_MODERATOR;
106
    }
107
108
    /**
109
     * Is the specified/current user an editor of a tree?
110
     *
111
     * @param Tree               $tree
112
     * @param UserInterface|null $user
113
     *
114
     * @return bool
115
     */
116
    public static function isEditor(Tree $tree, UserInterface $user = null): bool
117
    {
118
        $user = $user ?? self::user();
119
120
        return
121
            self::isModerator($tree, $user) ||
122
            $tree->getUserPreference($user, UserInterface::PREF_TREE_ROLE) === UserInterface::ROLE_EDITOR;
123
    }
124
125
    /**
126
     * Is the specified/current user a member of a tree?
127
     *
128
     * @param Tree               $tree
129
     * @param UserInterface|null $user
130
     *
131
     * @return bool
132
     */
133
    public static function isMember(Tree $tree, UserInterface $user = null): bool
134
    {
135
        $user = $user ?? self::user();
136
137
        return
138
            self::isEditor($tree, $user) ||
139
            $tree->getUserPreference($user, UserInterface::PREF_TREE_ROLE) === UserInterface::ROLE_MEMBER;
140
    }
141
142
    /**
143
     * What is the specified/current user's access level within a tree?
144
     *
145
     * @param Tree               $tree
146
     * @param UserInterface|null $user
147
     *
148
     * @return int
149
     */
150
    public static function accessLevel(Tree $tree, UserInterface $user = null): int
151
    {
152
        $user = $user ?? self::user();
153
154
        if (self::isManager($tree, $user)) {
155
            return self::PRIV_NONE;
156
        }
157
158
        if (self::isMember($tree, $user)) {
159
            return self::PRIV_USER;
160
        }
161
162
        return self::PRIV_PRIVATE;
163
    }
164
165
    /**
166
     * The ID of the authenticated user, from the current session.
167
     *
168
     * @return int|null
169
     */
170
    public static function id(): ?int
171
    {
172
        return Session::get('wt_user');
173
    }
174
175
    /**
176
     * The authenticated user, from the current session.
177
     *
178
     * @return UserInterface
179
     */
180
    public static function user(): UserInterface
181
    {
182
        return app(UserService::class)->find(self::id()) ?? new GuestUser();
183
    }
184
185
    /**
186
     * Login directly as an explicit user - for masquerading.
187
     *
188
     * @param UserInterface $user
189
     *
190
     * @return void
191
     */
192
    public static function login(UserInterface $user): void
193
    {
194
        Session::regenerate(false);
195
        Session::put('wt_user', $user->id());
196
    }
197
198
    /**
199
     * End the session for the current user.
200
     *
201
     * @return void
202
     */
203
    public static function logout(): void
204
    {
205
        Session::regenerate(true);
206
    }
207
208
    /**
209
     * @param ModuleInterface $module
210
     * @param string          $interface
211
     * @param Tree            $tree
212
     * @param UserInterface   $user
213
     *
214
     * @return void
215
     */
216
    public static function checkComponentAccess(ModuleInterface $module, string $interface, Tree $tree, UserInterface $user): void
217
    {
218
        if ($module->accessLevel($tree, $interface) < self::accessLevel($tree, $user)) {
219
            throw new HttpAccessDeniedException();
220
        }
221
    }
222
223
    /**
224
     * @param Family|null $family
225
     * @param bool        $edit
226
     *
227
     * @return Family
228
     * @throws FamilyNotFoundException
229
     * @throws FamilyAccessDeniedException
230
     */
231
    public static function checkFamilyAccess(?Family $family, bool $edit = false): Family
232
    {
233
        if ($family === null) {
234
            throw new FamilyNotFoundException();
235
        }
236
237
        if ($edit && $family->canEdit()) {
238
            $family->lock();
239
240
            return $family;
241
        }
242
243
        if ($family->canShow()) {
244
            return $family;
245
        }
246
247
        throw new FamilyAccessDeniedException();
248
    }
249
250
    /**
251
     * @param Header|null $header
252
     * @param bool        $edit
253
     *
254
     * @return Header
255
     * @throws RecordNotFoundException
256
     * @throws RecordAccessDeniedException
257
     */
258
    public static function checkHeaderAccess(?Header $header, bool $edit = false): Header
259
    {
260
        if ($header === null) {
261
            throw new RecordNotFoundException();
262
        }
263
264
        if ($edit && $header->canEdit()) {
265
            $header->lock();
266
267
            return $header;
268
        }
269
270
        if ($header->canShow()) {
271
            return $header;
272
        }
273
274
        throw new RecordAccessDeniedException();
275
    }
276
277
    /**
278
     * @param Individual|null $individual
279
     * @param bool            $edit
280
     * @param bool            $chart      For some charts, we can show private records
281
     *
282
     * @return Individual
283
     * @throws IndividualNotFoundException
284
     * @throws IndividualAccessDeniedException
285
     */
286
    public static function checkIndividualAccess(?Individual $individual, bool $edit = false, $chart = false): Individual
287
    {
288
        if ($individual === null) {
289
            throw new IndividualNotFoundException();
290
        }
291
292
        if ($edit && $individual->canEdit()) {
293
            $individual->lock();
294
295
            return $individual;
296
        }
297
298
        if ($chart && $individual->tree()->getPreference('SHOW_PRIVATE_RELATIONSHIPS') === '1') {
299
            return $individual;
300
        }
301
302
        if ($individual->canShow()) {
303
            return $individual;
304
        }
305
306
        throw new IndividualAccessDeniedException();
307
    }
308
309
    /**
310
     * @param Location|null $location
311
     * @param bool       $edit
312
     *
313
     * @return Location
314
     * @throws RecordNotFoundException
315
     * @throws RecordAccessDeniedException
316
     */
317
    public static function checkLocationAccess(?Location $location, bool $edit = false): Location
318
    {
319
        if ($location === null) {
320
            throw new RecordNotFoundException();
321
        }
322
323
        if ($edit && $location->canEdit()) {
324
            $location->lock();
325
326
            return $location;
327
        }
328
329
        if ($location->canShow()) {
330
            return $location;
331
        }
332
333
        throw new RecordAccessDeniedException();
334
    }
335
336
    /**
337
     * @param Media|null $media
338
     * @param bool       $edit
339
     *
340
     * @return Media
341
     * @throws MediaNotFoundException
342
     * @throws MediaAccessDeniedException
343
     */
344
    public static function checkMediaAccess(?Media $media, bool $edit = false): Media
345
    {
346
        if ($media === null) {
347
            throw new MediaNotFoundException();
348
        }
349
350
        if ($edit && $media->canEdit()) {
351
            $media->lock();
352
353
            return $media;
354
        }
355
356
        if ($media->canShow()) {
357
            return $media;
358
        }
359
360
        throw new MediaAccessDeniedException();
361
    }
362
363
    /**
364
     * @param Note|null $note
365
     * @param bool      $edit
366
     *
367
     * @return Note
368
     * @throws NoteNotFoundException
369
     * @throws NoteAccessDeniedException
370
     */
371
    public static function checkNoteAccess(?Note $note, bool $edit = false): Note
372
    {
373
        if ($note === null) {
374
            throw new NoteNotFoundException();
375
        }
376
377
        if ($edit && $note->canEdit()) {
378
            $note->lock();
379
380
            return $note;
381
        }
382
383
        if ($note->canShow()) {
384
            return $note;
385
        }
386
387
        throw new NoteAccessDeniedException();
388
    }
389
390
    /**
391
     * @param GedcomRecord|null $record
392
     * @param bool              $edit
393
     *
394
     * @return GedcomRecord
395
     * @throws RecordNotFoundException
396
     * @throws RecordAccessDeniedException
397
     */
398
    public static function checkRecordAccess(?GedcomRecord $record, bool $edit = false): GedcomRecord
399
    {
400
        if ($record === null) {
401
            throw new RecordNotFoundException();
402
        }
403
404
        if ($edit && $record->canEdit()) {
405
            $record->lock();
406
407
            return $record;
408
        }
409
410
        if ($record->canShow()) {
411
            return $record;
412
        }
413
414
        throw new RecordAccessDeniedException();
415
    }
416
417
    /**
418
     * @param Repository|null $repository
419
     * @param bool            $edit
420
     *
421
     * @return Repository
422
     * @throws RepositoryNotFoundException
423
     * @throws RepositoryAccessDeniedException
424
     */
425
    public static function checkRepositoryAccess(?Repository $repository, bool $edit = false): Repository
426
    {
427
        if ($repository === null) {
428
            throw new RepositoryNotFoundException();
429
        }
430
431
        if ($edit && $repository->canEdit()) {
432
            $repository->lock();
433
434
            return $repository;
435
        }
436
437
        if ($repository->canShow()) {
438
            return $repository;
439
        }
440
441
        throw new RepositoryAccessDeniedException();
442
    }
443
444
    /**
445
     * @param Source|null $source
446
     * @param bool        $edit
447
     *
448
     * @return Source
449
     * @throws SourceNotFoundException
450
     * @throws SourceAccessDeniedException
451
     */
452
    public static function checkSourceAccess(?Source $source, bool $edit = false): Source
453
    {
454
        if ($source === null) {
455
            throw new SourceNotFoundException();
456
        }
457
458
        if ($edit && $source->canEdit()) {
459
            $source->lock();
460
461
            return $source;
462
        }
463
464
        if ($source->canShow()) {
465
            return $source;
466
        }
467
468
        throw new SourceAccessDeniedException();
469
    }
470
471
    /*
472
     * @param Submitter|null $submitter
473
     * @param bool           $edit
474
     *
475
     * @return Submitter
476
     * @throws RecordNotFoundException
477
     * @throws RecordAccessDeniedException
478
     */
479
    public static function checkSubmitterAccess(?Submitter $submitter, bool $edit = false): Submitter
480
    {
481
        if ($submitter === null) {
482
            throw new RecordNotFoundException();
483
        }
484
485
        if ($edit && $submitter->canEdit()) {
486
            $submitter->lock();
487
488
            return $submitter;
489
        }
490
491
        if ($submitter->canShow()) {
492
            return $submitter;
493
        }
494
495
        throw new RecordAccessDeniedException();
496
    }
497
498
    /*
499
     * @param Submission|null $submission
500
     * @param bool            $edit
501
     *
502
     * @return Submission
503
     * @throws RecordNotFoundException
504
     * @throws RecordAccessDeniedException
505
     */
506
    public static function checkSubmissionAccess(?Submission $submission, bool $edit = false): Submission
507
    {
508
        if ($submission === null) {
509
            throw new RecordNotFoundException();
510
        }
511
512
        if ($edit && $submission->canEdit()) {
513
            $submission->lock();
514
515
            return $submission;
516
        }
517
518
        if ($submission->canShow()) {
519
            return $submission;
520
        }
521
522
        throw new RecordAccessDeniedException();
523
    }
524
525
    /**
526
     * @return array<int,string>
527
     */
528
    public static function accessLevelNames(): array
529
    {
530
        return [
531
            self::PRIV_PRIVATE => I18N::translate('Show to visitors'),
532
            self::PRIV_USER    => I18N::translate('Show to members'),
533
            self::PRIV_NONE    => I18N::translate('Show to managers'),
534
            self::PRIV_HIDE    => I18N::translate('Hide from everyone'),
535
        ];
536
    }
537
538
    /**
539
     * @return array<string,string>
540
     */
541
    public static function privacyRuleNames(): array
542
    {
543
        return [
544
            'none'         => I18N::translate('Show to visitors'),
545
            'privacy'      => I18N::translate('Show to members'),
546
            'confidential' => I18N::translate('Show to managers'),
547
            'hidden'       => I18N::translate('Hide from everyone'),
548
        ];
549
    }
550
}
551