xref: /webtrees/app/Auth.php (revision ddeb33548102a68bedfc3a35cbda68303ed365ca)
1<?php
2
3/**
4 * webtrees: online genealogy
5 * Copyright (C) 2019 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\IndividualAccessDeniedException;
26use Fisharebest\Webtrees\Exceptions\IndividualNotFoundException;
27use Fisharebest\Webtrees\Exceptions\MediaAccessDeniedException;
28use Fisharebest\Webtrees\Exceptions\MediaNotFoundException;
29use Fisharebest\Webtrees\Exceptions\NoteAccessDeniedException;
30use Fisharebest\Webtrees\Exceptions\NoteNotFoundException;
31use Fisharebest\Webtrees\Exceptions\RecordAccessDeniedException;
32use Fisharebest\Webtrees\Exceptions\RecordNotFoundException;
33use Fisharebest\Webtrees\Exceptions\RepositoryAccessDeniedException;
34use Fisharebest\Webtrees\Exceptions\RepositoryNotFoundException;
35use Fisharebest\Webtrees\Exceptions\SourceAccessDeniedException;
36use Fisharebest\Webtrees\Exceptions\SourceNotFoundException;
37use Fisharebest\Webtrees\Module\ModuleInterface;
38use Fisharebest\Webtrees\Services\UserService;
39use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
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('canadmin') === '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, 'canedit') === 'admin';
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, 'canedit') === 'accept';
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, 'canedit') === '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, 'canedit') === '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        $id = Session::get('wt_user');
167
168        if ($id !== null) {
169            // In webtrees 1.x, the ID may have been a string.
170            $id = (int) $id;
171        }
172
173        return $id;
174    }
175
176    /**
177     * The authenticated user, from the current session.
178     *
179     * @return UserInterface
180     */
181    public static function user(): UserInterface
182    {
183        return app(UserService::class)->find(self::id()) ?? new GuestUser();
184    }
185
186    /**
187     * Login directly as an explicit user - for masquerading.
188     *
189     * @param UserInterface $user
190     *
191     * @return void
192     */
193    public static function login(UserInterface $user): void
194    {
195        Session::regenerate(false);
196        Session::put('wt_user', $user->id());
197    }
198
199    /**
200     * End the session for the current user.
201     *
202     * @return void
203     */
204    public static function logout(): void
205    {
206        Session::regenerate(true);
207    }
208
209    /**
210     * @param ModuleInterface $module
211     * @param string          $component
212     * @param Tree            $tree
213     * @param UserInterface   $user
214     *
215     * @return void
216     */
217    public static function checkComponentAccess(ModuleInterface $module, string $component, Tree $tree, UserInterface $user): void
218    {
219        if ($module->accessLevel($tree, $component) < self::accessLevel($tree, $user)) {
220            throw new AccessDeniedHttpException('Access denied');
221        }
222    }
223
224    /**
225     * @param Family|null $family
226     * @param bool|null   $edit
227     *
228     * @return Family
229     * @throws FamilyNotFoundException
230     * @throws FamilyAccessDeniedException
231     */
232    public static function checkFamilyAccess(Family $family = null, $edit = false): Family
233    {
234        if ($family === null) {
235            throw new FamilyNotFoundException();
236        }
237
238        if (!$family->canShow()) {
239            throw new FamilyAccessDeniedException();
240        }
241
242        if ($edit && !$family->canEdit()) {
243            throw new FamilyAccessDeniedException();
244        }
245
246        return $family;
247    }
248
249    /**
250     * @param Individual|null $individual
251     * @param bool|null       $edit
252     *
253     * @return Individual
254     * @throws IndividualNotFoundException
255     * @throws IndividualAccessDeniedException
256     */
257    public static function checkIndividualAccess(Individual $individual = null, $edit = false): Individual
258    {
259        if ($individual === null) {
260            throw new IndividualNotFoundException();
261        }
262
263        if (!$individual->canShow()) {
264            throw new IndividualAccessDeniedException();
265        }
266
267        if ($edit && !$individual->canEdit()) {
268            throw new IndividualAccessDeniedException();
269        }
270
271        return $individual;
272    }
273
274    /**
275     * @param Media|null $media
276     * @param bool|null  $edit
277     *
278     * @return Media
279     * @throws MediaNotFoundException
280     * @throws MediaAccessDeniedException
281     */
282    public static function checkMediaAccess(Media $media = null, $edit = false): Media
283    {
284        if ($media === null) {
285            throw new MediaNotFoundException();
286        }
287
288        if (!$media->canShow()) {
289            throw new MediaAccessDeniedException();
290        }
291
292        if ($edit && !$media->canEdit()) {
293            throw new MediaAccessDeniedException();
294        }
295
296        return $media;
297    }
298
299    /**
300     * @param Note|null $note
301     * @param bool|null $edit
302     *
303     * @return Note
304     * @throws NoteNotFoundException
305     * @throws NoteAccessDeniedException
306     */
307    public static function checkNoteAccess(Note $note = null, $edit = false): Note
308    {
309        if ($note === null) {
310            throw new NoteNotFoundException();
311        }
312
313        if (!$note->canShow()) {
314            throw new NoteAccessDeniedException();
315        }
316
317        if ($edit && !$note->canEdit()) {
318            throw new NoteAccessDeniedException();
319        }
320
321        return $note;
322    }
323
324    /**
325     * @param GedcomRecord|null $record
326     * @param bool|null         $edit
327     *
328     * @return GedcomRecord
329     * @throws RecordNotFoundException
330     * @throws RecordAccessDeniedException
331     */
332    public static function checkRecordAccess(GedcomRecord $record = null, $edit = false): GedcomRecord
333    {
334        if ($record === null) {
335            throw new RecordNotFoundException();
336        }
337
338        if (!$record->canShow()) {
339            throw new RecordAccessDeniedException();
340        }
341
342        if ($edit && !$record->canEdit()) {
343            throw new RecordAccessDeniedException();
344        }
345
346        return $record;
347    }
348
349    /**
350     * @param Repository|null $repository
351     * @param bool|null       $edit
352     *
353     * @return Repository
354     * @throws RepositoryNotFoundException
355     * @throws RepositoryAccessDeniedException
356     */
357    public static function checkRepositoryAccess(Repository $repository = null, $edit = false): Repository
358    {
359        if ($repository === null) {
360            throw new RepositoryNotFoundException();
361        }
362
363        if (!$repository->canShow()) {
364            throw new RepositoryAccessDeniedException();
365        }
366
367        if ($edit && !$repository->canEdit()) {
368            throw new RepositoryAccessDeniedException();
369        }
370
371        return $repository;
372    }
373
374    /**
375     * @param Source|null $source
376     * @param bool|null   $edit
377     *
378     * @return Source
379     * @throws SourceNotFoundException
380     * @throws SourceAccessDeniedException
381     */
382    public static function checkSourceAccess(Source $source = null, $edit = false): Source
383    {
384        if ($source === null) {
385            throw new SourceNotFoundException();
386        }
387
388        if (!$source->canShow()) {
389            throw new SourceAccessDeniedException();
390        }
391
392        if ($edit && !$source->canEdit()) {
393            throw new SourceAccessDeniedException();
394        }
395
396        return $source;
397    }
398}
399