Passed
Push — main ( 31c7e7...eeec55 )
by Greg
06:50
created

Auth::id()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 5
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 2
nc 2
nop 0
dl 0
loc 5
rs 10
c 0
b 0
f 0
1
<?php
2
3
/**
4
 * webtrees: online genealogy
5
 * Copyright (C) 2022 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\Http\Exceptions\HttpAccessDeniedException;
24
use Fisharebest\Webtrees\Http\Exceptions\HttpNotFoundException;
25
use Fisharebest\Webtrees\Module\ModuleInterface;
26
use Fisharebest\Webtrees\Services\UserService;
27
28
use function is_int;
29
30
/**
31
 * Authentication.
32
 */
33
class Auth
34
{
35
    // Privacy constants
36
    public const PRIV_PRIVATE = 2; // Allows visitors to view the item
37
    public const PRIV_USER    = 1; // Allows members to access the item
38
    public const PRIV_NONE    = 0; // Allows managers to access the item
39
    public const PRIV_HIDE    = -1; // Hide the item to all users
40
41
    /**
42
     * Are we currently logged in?
43
     *
44
     * @return bool
45
     */
46
    public static function check(): bool
47
    {
48
        return self::id() !== null;
49
    }
50
51
    /**
52
     * Is the specified/current user an administrator?
53
     *
54
     * @param UserInterface|null $user
55
     *
56
     * @return bool
57
     */
58
    public static function isAdmin(UserInterface $user = null): bool
59
    {
60
        $user = $user ?? self::user();
61
62
        return $user->getPreference(UserInterface::PREF_IS_ADMINISTRATOR) === '1';
63
    }
64
65
    /**
66
     * Is the specified/current user a manager of a tree?
67
     *
68
     * @param Tree               $tree
69
     * @param UserInterface|null $user
70
     *
71
     * @return bool
72
     */
73
    public static function isManager(Tree $tree, UserInterface $user = null): bool
74
    {
75
        $user = $user ?? self::user();
76
77
        return self::isAdmin($user) || $tree->getUserPreference($user, UserInterface::PREF_TREE_ROLE) === UserInterface::ROLE_MANAGER;
78
    }
79
80
    /**
81
     * Is the specified/current user a moderator of a tree?
82
     *
83
     * @param Tree               $tree
84
     * @param UserInterface|null $user
85
     *
86
     * @return bool
87
     */
88
    public static function isModerator(Tree $tree, UserInterface $user = null): bool
89
    {
90
        $user = $user ?? self::user();
91
92
        return
93
            self::isManager($tree, $user) ||
94
            $tree->getUserPreference($user, UserInterface::PREF_TREE_ROLE) === UserInterface::ROLE_MODERATOR;
95
    }
96
97
    /**
98
     * Is the specified/current user an editor of a tree?
99
     *
100
     * @param Tree               $tree
101
     * @param UserInterface|null $user
102
     *
103
     * @return bool
104
     */
105
    public static function isEditor(Tree $tree, UserInterface $user = null): bool
106
    {
107
        $user = $user ?? self::user();
108
109
        return
110
            self::isModerator($tree, $user) ||
111
            $tree->getUserPreference($user, UserInterface::PREF_TREE_ROLE) === UserInterface::ROLE_EDITOR;
112
    }
113
114
    /**
115
     * Is the specified/current user a member of a tree?
116
     *
117
     * @param Tree               $tree
118
     * @param UserInterface|null $user
119
     *
120
     * @return bool
121
     */
122
    public static function isMember(Tree $tree, UserInterface $user = null): bool
123
    {
124
        $user = $user ?? self::user();
125
126
        return
127
            self::isEditor($tree, $user) ||
128
            $tree->getUserPreference($user, UserInterface::PREF_TREE_ROLE) === UserInterface::ROLE_MEMBER;
129
    }
130
131
    /**
132
     * What is the specified/current user's access level within a tree?
133
     *
134
     * @param Tree               $tree
135
     * @param UserInterface|null $user
136
     *
137
     * @return int
138
     */
139
    public static function accessLevel(Tree $tree, UserInterface $user = null): int
140
    {
141
        $user = $user ?? self::user();
142
143
        if (self::isManager($tree, $user)) {
144
            return self::PRIV_NONE;
145
        }
146
147
        if (self::isMember($tree, $user)) {
148
            return self::PRIV_USER;
149
        }
150
151
        return self::PRIV_PRIVATE;
152
    }
153
154
    /**
155
     * The ID of the authenticated user, from the current session.
156
     *
157
     * @return int|null
158
     */
159
    public static function id(): ?int
160
    {
161
        $wt_user = Session::get('wt_user');
162
163
        return is_int($wt_user) ? $wt_user : null;
164
    }
165
166
    /**
167
     * The authenticated user, from the current session.
168
     *
169
     * @return UserInterface
170
     */
171
    public static function user(): UserInterface
172
    {
173
        $user_service = app(UserService::class);
174
        assert($user_service instanceof UserService);
175
176
        return $user_service->find(self::id()) ?? new GuestUser();
177
    }
178
179
    /**
180
     * Login directly as an explicit user - for masquerading.
181
     *
182
     * @param UserInterface $user
183
     *
184
     * @return void
185
     */
186
    public static function login(UserInterface $user): void
187
    {
188
        Session::regenerate();
189
        Session::put('wt_user', $user->id());
190
    }
191
192
    /**
193
     * End the session for the current user.
194
     *
195
     * @return void
196
     */
197
    public static function logout(): void
198
    {
199
        Session::regenerate(true);
200
    }
201
202
    /**
203
     * @param ModuleInterface $module
204
     * @param string          $interface
205
     * @param Tree            $tree
206
     * @param UserInterface   $user
207
     *
208
     * @return void
209
     */
210
    public static function checkComponentAccess(ModuleInterface $module, string $interface, Tree $tree, UserInterface $user): void
211
    {
212
        if ($module->accessLevel($tree, $interface) < self::accessLevel($tree, $user)) {
213
            throw new HttpAccessDeniedException();
214
        }
215
    }
216
217
    /**
218
     * @param Family|null $family
219
     * @param bool        $edit
220
     *
221
     * @return Family
222
     * @throws HttpNotFoundException
223
     * @throws HttpAccessDeniedException
224
     */
225
    public static function checkFamilyAccess(?Family $family, bool $edit = false): Family
226
    {
227
        $message = I18N::translate('This family does not exist or you do not have permission to view it.');
228
229
        if ($family === null) {
230
            throw new HttpNotFoundException($message);
231
        }
232
233
        if ($edit && $family->canEdit()) {
234
            $family->lock();
235
236
            return $family;
237
        }
238
239
        if ($family->canShow()) {
240
            return $family;
241
        }
242
243
        throw new HttpAccessDeniedException($message);
244
    }
245
246
    /**
247
     * @param Header|null $header
248
     * @param bool        $edit
249
     *
250
     * @return Header
251
     * @throws HttpNotFoundException
252
     * @throws HttpAccessDeniedException
253
     */
254
    public static function checkHeaderAccess(?Header $header, bool $edit = false): Header
255
    {
256
        $message = I18N::translate('This record does not exist or you do not have permission to view it.');
257
258
        if ($header === null) {
259
            throw new HttpNotFoundException($message);
260
        }
261
262
        if ($edit && $header->canEdit()) {
263
            $header->lock();
264
265
            return $header;
266
        }
267
268
        if ($header->canShow()) {
269
            return $header;
270
        }
271
272
        throw new HttpAccessDeniedException($message);
273
    }
274
275
    /**
276
     * @param Individual|null $individual
277
     * @param bool            $edit
278
     * @param bool            $chart For some charts, we can show private records
279
     *
280
     * @return Individual
281
     * @throws HttpNotFoundException
282
     * @throws HttpAccessDeniedException
283
     */
284
    public static function checkIndividualAccess(?Individual $individual, bool $edit = false, bool $chart = false): Individual
285
    {
286
        $message = I18N::translate('This individual does not exist or you do not have permission to view it.');
287
288
        if ($individual === null) {
289
            throw new HttpNotFoundException($message);
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 HttpAccessDeniedException($message);
307
    }
308
309
    /**
310
     * @param Location|null $location
311
     * @param bool          $edit
312
     *
313
     * @return Location
314
     * @throws HttpNotFoundException
315
     * @throws HttpAccessDeniedException
316
     */
317
    public static function checkLocationAccess(?Location $location, bool $edit = false): Location
318
    {
319
        $message = I18N::translate('This record does not exist or you do not have permission to view it.');
320
321
        if ($location === null) {
322
            throw new HttpNotFoundException($message);
323
        }
324
325
        if ($edit && $location->canEdit()) {
326
            $location->lock();
327
328
            return $location;
329
        }
330
331
        if ($location->canShow()) {
332
            return $location;
333
        }
334
335
        throw new HttpAccessDeniedException($message);
336
    }
337
338
    /**
339
     * @param Media|null $media
340
     * @param bool       $edit
341
     *
342
     * @return Media
343
     * @throws HttpNotFoundException
344
     * @throws HttpAccessDeniedException
345
     */
346
    public static function checkMediaAccess(?Media $media, bool $edit = false): Media
347
    {
348
        $message = I18N::translate('This media object does not exist or you do not have permission to view it.');
349
350
        if ($media === null) {
351
            throw new HttpNotFoundException($message);
352
        }
353
354
        if ($edit && $media->canEdit()) {
355
            $media->lock();
356
357
            return $media;
358
        }
359
360
        if ($media->canShow()) {
361
            return $media;
362
        }
363
364
        throw new HttpAccessDeniedException($message);
365
    }
366
367
    /**
368
     * @param Note|null $note
369
     * @param bool      $edit
370
     *
371
     * @return Note
372
     * @throws HttpNotFoundException
373
     * @throws HttpAccessDeniedException
374
     */
375
    public static function checkNoteAccess(?Note $note, bool $edit = false): Note
376
    {
377
        $message = I18N::translate('This note does not exist or you do not have permission to view it.');
378
379
        if ($note === null) {
380
            throw new HttpNotFoundException($message);
381
        }
382
383
        if ($edit && $note->canEdit()) {
384
            $note->lock();
385
386
            return $note;
387
        }
388
389
        if ($note->canShow()) {
390
            return $note;
391
        }
392
393
        throw new HttpAccessDeniedException($message);
394
    }
395
396
    /**
397
     * @param SharedNote|null $shared_note
398
     * @param bool            $edit
399
     *
400
     * @return SharedNote
401
     * @throws HttpNotFoundException
402
     * @throws HttpAccessDeniedException
403
     */
404
    public static function checkSharedNoteAccess(?SharedNote $shared_note, bool $edit = false): SharedNote
405
    {
406
        $message = I18N::translate('This note does not exist or you do not have permission to view it.');
407
408
        if ($shared_note === null) {
409
            throw new HttpNotFoundException($message);
410
        }
411
412
        if ($edit && $shared_note->canEdit()) {
413
            $shared_note->lock();
414
415
            return $shared_note;
416
        }
417
418
        if ($shared_note->canShow()) {
419
            return $shared_note;
420
        }
421
422
        throw new HttpAccessDeniedException($message);
423
    }
424
425
    /**
426
     * @param GedcomRecord|null $record
427
     * @param bool              $edit
428
     *
429
     * @return GedcomRecord
430
     * @throws HttpNotFoundException
431
     * @throws HttpAccessDeniedException
432
     */
433
    public static function checkRecordAccess(?GedcomRecord $record, bool $edit = false): GedcomRecord
434
    {
435
        $message = I18N::translate('This record does not exist or you do not have permission to view it.');
436
437
        if ($record === null) {
438
            throw new HttpNotFoundException($message);
439
        }
440
441
        if ($edit && $record->canEdit()) {
442
            $record->lock();
443
444
            return $record;
445
        }
446
447
        if ($record->canShow()) {
448
            return $record;
449
        }
450
451
        throw new HttpAccessDeniedException($message);
452
    }
453
454
    /**
455
     * @param Repository|null $repository
456
     * @param bool            $edit
457
     *
458
     * @return Repository
459
     * @throws HttpNotFoundException
460
     * @throws HttpAccessDeniedException
461
     */
462
    public static function checkRepositoryAccess(?Repository $repository, bool $edit = false): Repository
463
    {
464
        $message = I18N::translate('This repository does not exist or you do not have permission to view it.');
465
466
        if ($repository === null) {
467
            throw new HttpNotFoundException($message);
468
        }
469
470
        if ($edit && $repository->canEdit()) {
471
            $repository->lock();
472
473
            return $repository;
474
        }
475
476
        if ($repository->canShow()) {
477
            return $repository;
478
        }
479
480
        throw new HttpAccessDeniedException($message);
481
    }
482
483
    /**
484
     * @param Source|null $source
485
     * @param bool        $edit
486
     *
487
     * @return Source
488
     * @throws HttpNotFoundException
489
     * @throws HttpAccessDeniedException
490
     */
491
    public static function checkSourceAccess(?Source $source, bool $edit = false): Source
492
    {
493
        $message = I18N::translate('This source does not exist or you do not have permission to view it.');
494
495
        if ($source === null) {
496
            throw new HttpNotFoundException($message);
497
        }
498
499
        if ($edit && $source->canEdit()) {
500
            $source->lock();
501
502
            return $source;
503
        }
504
505
        if ($source->canShow()) {
506
            return $source;
507
        }
508
509
        throw new HttpAccessDeniedException($message);
510
    }
511
512
    /*
513
     * @param Submitter|null $submitter
514
     * @param bool           $edit
515
     *
516
     * @return Submitter
517
     * @throws HttpFoundException
518
     * @throws HttpDeniedException
519
     */
520
    public static function checkSubmitterAccess(?Submitter $submitter, bool $edit = false): Submitter
521
    {
522
        $message = I18N::translate('This record does not exist or you do not have permission to view it.');
523
524
        if ($submitter === null) {
525
            throw new HttpNotFoundException($message);
526
        }
527
528
        if ($edit && $submitter->canEdit()) {
529
            $submitter->lock();
530
531
            return $submitter;
532
        }
533
534
        if ($submitter->canShow()) {
535
            return $submitter;
536
        }
537
538
        throw new HttpAccessDeniedException($message);
539
    }
540
541
    /*
542
     * @param Submission|null $submission
543
     * @param bool            $edit
544
     *
545
     * @return Submission
546
     * @throws HttpNotFoundException
547
     * @throws HttpAccessDeniedException
548
     */
549
    public static function checkSubmissionAccess(?Submission $submission, bool $edit = false): Submission
550
    {
551
        $message = I18N::translate('This record does not exist or you do not have permission to view it.');
552
553
        if ($submission === null) {
554
            throw new HttpNotFoundException($message);
555
        }
556
557
        if ($edit && $submission->canEdit()) {
558
            $submission->lock();
559
560
            return $submission;
561
        }
562
563
        if ($submission->canShow()) {
564
            return $submission;
565
        }
566
567
        throw new HttpAccessDeniedException($message);
568
    }
569
570
    /**
571
     * @param Tree          $tree
572
     * @param UserInterface $user
573
     *
574
     * @return bool
575
     */
576
    public static function canUploadMedia(Tree $tree, UserInterface $user): bool
577
    {
578
        return
579
            self::isEditor($tree, $user) &&
580
            self::accessLevel($tree, $user) <= (int) $tree->getPreference('MEDIA_UPLOAD');
581
    }
582
583
584
    /**
585
     * @return array<int,string>
586
     */
587
    public static function accessLevelNames(): array
588
    {
589
        return [
590
            self::PRIV_PRIVATE => I18N::translate('Show to visitors'),
591
            self::PRIV_USER    => I18N::translate('Show to members'),
592
            self::PRIV_NONE    => I18N::translate('Show to managers'),
593
            self::PRIV_HIDE    => I18N::translate('Hide from everyone'),
594
        ];
595
    }
596
597
    /**
598
     * @return array<string,string>
599
     */
600
    public static function privacyRuleNames(): array
601
    {
602
        return [
603
            'none'         => I18N::translate('Show to visitors'),
604
            'privacy'      => I18N::translate('Show to members'),
605
            'confidential' => I18N::translate('Show to managers'),
606
            'hidden'       => I18N::translate('Hide from everyone'),
607
        ];
608
    }
609
}
610