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