xref: /webtrees/app/Auth.php (revision b43e8d2bb2c20432b6d427f0f5d9a32679a9aa4f)
1<?php
2
3/**
4 * webtrees: online genealogy
5 * Copyright (C) 2020 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 <http://www.gnu.org/licenses/>.
16 */
17
18declare(strict_types=1);
19
20namespace Fisharebest\Webtrees;
21
22use Fisharebest\Webtrees\Contracts\UserInterface;
23use Fisharebest\Webtrees\Exceptions\FamilyAccessDeniedException;
24use Fisharebest\Webtrees\Exceptions\FamilyNotFoundException;
25use Fisharebest\Webtrees\Exceptions\HttpAccessDeniedException;
26use Fisharebest\Webtrees\Exceptions\IndividualAccessDeniedException;
27use Fisharebest\Webtrees\Exceptions\IndividualNotFoundException;
28use Fisharebest\Webtrees\Exceptions\MediaAccessDeniedException;
29use Fisharebest\Webtrees\Exceptions\MediaNotFoundException;
30use Fisharebest\Webtrees\Exceptions\NoteAccessDeniedException;
31use Fisharebest\Webtrees\Exceptions\NoteNotFoundException;
32use Fisharebest\Webtrees\Exceptions\RecordAccessDeniedException;
33use Fisharebest\Webtrees\Exceptions\RecordNotFoundException;
34use Fisharebest\Webtrees\Exceptions\RepositoryAccessDeniedException;
35use Fisharebest\Webtrees\Exceptions\RepositoryNotFoundException;
36use Fisharebest\Webtrees\Exceptions\SourceAccessDeniedException;
37use Fisharebest\Webtrees\Exceptions\SourceNotFoundException;
38use Fisharebest\Webtrees\Module\ModuleInterface;
39use Fisharebest\Webtrees\Services\UserService;
40
41/**
42 * Authentication.
43 */
44class 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(User::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, User::PREF_TREE_ROLE) === User::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 self::isManager($tree, $user) || $tree->getUserPreference($user, User::PREF_TREE_ROLE) === User::ROLE_MODERATOR;
104    }
105
106    /**
107     * Is the specified/current user an editor of a tree?
108     *
109     * @param Tree               $tree
110     * @param UserInterface|null $user
111     *
112     * @return bool
113     */
114    public static function isEditor(Tree $tree, UserInterface $user = null): bool
115    {
116        $user = $user ?? self::user();
117
118        return self::isModerator($tree, $user) || $tree->getUserPreference($user, User::PREF_TREE_ROLE) === 'edit';
119    }
120
121    /**
122     * Is the specified/current user a member of a tree?
123     *
124     * @param Tree               $tree
125     * @param UserInterface|null $user
126     *
127     * @return bool
128     */
129    public static function isMember(Tree $tree, UserInterface $user = null): bool
130    {
131        $user = $user ?? self::user();
132
133        return self::isEditor($tree, $user) || $tree->getUserPreference($user, User::PREF_TREE_ROLE) === 'access';
134    }
135
136    /**
137     * What is the specified/current user's access level within a tree?
138     *
139     * @param Tree               $tree
140     * @param UserInterface|null $user
141     *
142     * @return int
143     */
144    public static function accessLevel(Tree $tree, UserInterface $user = null): int
145    {
146        $user = $user ?? self::user();
147
148        if (self::isManager($tree, $user)) {
149            return self::PRIV_NONE;
150        }
151
152        if (self::isMember($tree, $user)) {
153            return self::PRIV_USER;
154        }
155
156        return self::PRIV_PRIVATE;
157    }
158
159    /**
160     * The ID of the authenticated user, from the current session.
161     *
162     * @return int|null
163     */
164    public static function id(): ?int
165    {
166        return Session::get('wt_user');
167    }
168
169    /**
170     * The authenticated user, from the current session.
171     *
172     * @return UserInterface
173     */
174    public static function user(): UserInterface
175    {
176        return app(UserService::class)->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(false);
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 FamilyNotFoundException
223     * @throws FamilyAccessDeniedException
224     */
225    public static function checkFamilyAccess(?Family $family, bool $edit = false): Family
226    {
227        if ($family === null) {
228            throw new FamilyNotFoundException();
229        }
230
231        if ($edit && $family->canEdit()) {
232            $family->lock();
233
234            return $family;
235        }
236
237        if ($family->canShow()) {
238            return $family;
239        }
240
241        throw new FamilyAccessDeniedException();
242    }
243
244    /**
245     * @param Header|null $header
246     * @param bool        $edit
247     *
248     * @return Header
249     * @throws RecordNotFoundException
250     * @throws RecordAccessDeniedException
251     */
252    public static function checkHeaderAccess(?Header $header, bool $edit = false): Header
253    {
254        if ($header === null) {
255            throw new RecordNotFoundException();
256        }
257
258        if ($edit && $header->canEdit()) {
259            $header->lock();
260
261            return $header;
262        }
263
264        if ($header->canShow()) {
265            return $header;
266        }
267
268        throw new RecordAccessDeniedException();
269    }
270
271    /**
272     * @param Individual|null $individual
273     * @param bool            $edit
274     * @param bool            $chart      For some charts, we can show private records
275     *
276     * @return Individual
277     * @throws IndividualNotFoundException
278     * @throws IndividualAccessDeniedException
279     */
280    public static function checkIndividualAccess(?Individual $individual, bool $edit = false, $chart = false): Individual
281    {
282        if ($individual === null) {
283            throw new IndividualNotFoundException();
284        }
285
286        if ($edit && $individual->canEdit()) {
287            $individual->lock();
288
289            return $individual;
290        }
291
292        if ($chart && $individual->tree()->getPreference('SHOW_PRIVATE_RELATIONSHIPS') === '1') {
293            return $individual;
294        }
295
296        if ($individual->canShow()) {
297            return $individual;
298        }
299
300        throw new IndividualAccessDeniedException();
301    }
302
303    /**
304     * @param Media|null $media
305     * @param bool       $edit
306     *
307     * @return Media
308     * @throws MediaNotFoundException
309     * @throws MediaAccessDeniedException
310     */
311    public static function checkMediaAccess(?Media $media, bool $edit = false): Media
312    {
313        if ($media === null) {
314            throw new MediaNotFoundException();
315        }
316
317        if ($edit && $media->canEdit()) {
318            $media->lock();
319
320            return $media;
321        }
322
323        if ($media->canShow()) {
324            return $media;
325        }
326
327        throw new MediaAccessDeniedException();
328    }
329
330    /**
331     * @param Note|null $note
332     * @param bool      $edit
333     *
334     * @return Note
335     * @throws NoteNotFoundException
336     * @throws NoteAccessDeniedException
337     */
338    public static function checkNoteAccess(?Note $note, bool $edit = false): Note
339    {
340        if ($note === null) {
341            throw new NoteNotFoundException();
342        }
343
344        if ($edit && $note->canEdit()) {
345            $note->lock();
346
347            return $note;
348        }
349
350        if ($note->canShow()) {
351            return $note;
352        }
353
354        throw new NoteAccessDeniedException();
355    }
356
357    /**
358     * @param GedcomRecord|null $record
359     * @param bool              $edit
360     *
361     * @return GedcomRecord
362     * @throws RecordNotFoundException
363     * @throws RecordAccessDeniedException
364     */
365    public static function checkRecordAccess(?GedcomRecord $record, bool $edit = false): GedcomRecord
366    {
367        if ($record === null) {
368            throw new RecordNotFoundException();
369        }
370
371        if ($edit && $record->canEdit()) {
372            $record->lock();
373
374            return $record;
375        }
376
377        if ($record->canShow()) {
378            return $record;
379        }
380
381        throw new RecordAccessDeniedException();
382    }
383
384    /**
385     * @param Repository|null $repository
386     * @param bool            $edit
387     *
388     * @return Repository
389     * @throws RepositoryNotFoundException
390     * @throws RepositoryAccessDeniedException
391     */
392    public static function checkRepositoryAccess(?Repository $repository, bool $edit = false): Repository
393    {
394        if ($repository === null) {
395            throw new RepositoryNotFoundException();
396        }
397
398        if ($edit && $repository->canEdit()) {
399            $repository->lock();
400
401            return $repository;
402        }
403
404        if ($repository->canShow()) {
405            return $repository;
406        }
407
408        throw new RepositoryAccessDeniedException();
409    }
410
411    /**
412     * @param Source|null $source
413     * @param bool        $edit
414     *
415     * @return Source
416     * @throws SourceNotFoundException
417     * @throws SourceAccessDeniedException
418     */
419    public static function checkSourceAccess(?Source $source, bool $edit = false): Source
420    {
421        if ($source === null) {
422            throw new SourceNotFoundException();
423        }
424
425        if ($edit && $source->canEdit()) {
426            $source->lock();
427
428            return $source;
429        }
430
431        if ($source->canShow()) {
432            return $source;
433        }
434
435        throw new SourceAccessDeniedException();
436    }
437
438    /*
439     * @param Submitter|null $submitter
440     * @param bool           $edit
441     *
442     * @return Submitter
443     * @throws RecordNotFoundException
444     * @throws RecordAccessDeniedException
445     */
446    public static function checkSubmitterAccess(?Submitter $submitter, bool $edit = false): Submitter
447    {
448        if ($submitter === null) {
449            throw new RecordNotFoundException();
450        }
451
452        if ($edit && $submitter->canEdit()) {
453            $submitter->lock();
454
455            return $submitter;
456        }
457
458        if ($submitter->canShow()) {
459            return $submitter;
460        }
461
462        throw new RecordAccessDeniedException();
463    }
464
465    /*
466     * @param Submission|null $submission
467     * @param bool            $edit
468     *
469     * @return Submission
470     * @throws RecordNotFoundException
471     * @throws RecordAccessDeniedException
472     */
473    public static function checkSubmissionAccess(?Submission $submission, bool $edit = false): Submission
474    {
475        if ($submission === null) {
476            throw new RecordNotFoundException();
477        }
478
479        if ($edit && $submission->canEdit()) {
480            $submission->lock();
481
482            return $submission;
483        }
484
485        if ($submission->canShow()) {
486            return $submission;
487        }
488
489        throw new RecordAccessDeniedException();
490    }
491
492    /**
493     * @return array<int,string>
494     */
495    public static function accessLevelNames(): array
496    {
497        return [
498            self::PRIV_PRIVATE => I18N::translate('Show to visitors'),
499            self::PRIV_USER    => I18N::translate('Show to members'),
500            self::PRIV_NONE    => I18N::translate('Show to managers'),
501            self::PRIV_HIDE    => I18N::translate('Hide from everyone'),
502        ];
503    }
504
505    /**
506     * @return array<string,string>
507     */
508    public static function privacyRuleNames(): array
509    {
510        return [
511            'none'         => I18N::translate('Show to visitors'),
512            'privacy'      => I18N::translate('Show to members'),
513            'confidential' => I18N::translate('Show to managers'),
514            'hidden'       => I18N::translate('Hide from everyone'),
515        ];
516    }
517}
518