xref: /webtrees/app/Auth.php (revision 3976b4703df669696105ed6b024b96d433c8fbdb)
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 */
17declare(strict_types=1);
18
19namespace Fisharebest\Webtrees;
20
21use Fisharebest\Webtrees\Contracts\UserInterface;
22use Fisharebest\Webtrees\Exceptions\FamilyAccessDeniedException;
23use Fisharebest\Webtrees\Exceptions\FamilyNotFoundException;
24use Fisharebest\Webtrees\Exceptions\IndividualAccessDeniedException;
25use Fisharebest\Webtrees\Exceptions\IndividualNotFoundException;
26use Fisharebest\Webtrees\Exceptions\MediaAccessDeniedException;
27use Fisharebest\Webtrees\Exceptions\MediaNotFoundException;
28use Fisharebest\Webtrees\Exceptions\NoteAccessDeniedException;
29use Fisharebest\Webtrees\Exceptions\NoteNotFoundException;
30use Fisharebest\Webtrees\Exceptions\RecordAccessDeniedException;
31use Fisharebest\Webtrees\Exceptions\RecordNotFoundException;
32use Fisharebest\Webtrees\Exceptions\RepositoryAccessDeniedException;
33use Fisharebest\Webtrees\Exceptions\RepositoryNotFoundException;
34use Fisharebest\Webtrees\Exceptions\SourceAccessDeniedException;
35use Fisharebest\Webtrees\Exceptions\SourceNotFoundException;
36use Fisharebest\Webtrees\Module\ModuleInterface;
37use Fisharebest\Webtrees\Services\UserService;
38use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
39
40/**
41 * Authentication.
42 */
43class Auth
44{
45    // Privacy constants
46    public const PRIV_PRIVATE = 2; // Allows visitors to view the item
47    public const PRIV_USER    = 1; // Allows members to access the item
48    public const PRIV_NONE    = 0; // Allows managers to access the item
49    public const PRIV_HIDE    = -1; // Hide the item to all users
50
51    /**
52     * Are we currently logged in?
53     *
54     * @return bool
55     */
56    public static function check(): bool
57    {
58        return self::id() !== null;
59    }
60
61    /**
62     * Is the specified/current user an administrator?
63     *
64     * @param UserInterface|null $user
65     *
66     * @return bool
67     */
68    public static function isAdmin(UserInterface $user = null): bool
69    {
70        $user = $user ?? self::user();
71
72        return $user->getPreference('canadmin') === '1';
73    }
74
75    /**
76     * Is the specified/current user a manager of a tree?
77     *
78     * @param Tree               $tree
79     * @param UserInterface|null $user
80     *
81     * @return bool
82     */
83    public static function isManager(Tree $tree, UserInterface $user = null): bool
84    {
85        $user = $user ?? self::user();
86
87        return self::isAdmin($user) || $tree->getUserPreference($user, 'canedit') === 'admin';
88    }
89
90    /**
91     * Is the specified/current user a moderator of a tree?
92     *
93     * @param Tree               $tree
94     * @param UserInterface|null $user
95     *
96     * @return bool
97     */
98    public static function isModerator(Tree $tree, UserInterface $user = null): bool
99    {
100        $user = $user ?? self::user();
101
102        return self::isManager($tree, $user) || $tree->getUserPreference($user, 'canedit') === 'accept';
103    }
104
105    /**
106     * Is the specified/current user an editor of a tree?
107     *
108     * @param Tree               $tree
109     * @param UserInterface|null $user
110     *
111     * @return bool
112     */
113    public static function isEditor(Tree $tree, UserInterface $user = null): bool
114    {
115        $user = $user ?? self::user();
116
117        return self::isModerator($tree, $user) || $tree->getUserPreference($user, 'canedit') === 'edit';
118    }
119
120    /**
121     * Is the specified/current user a member of a tree?
122     *
123     * @param Tree               $tree
124     * @param UserInterface|null $user
125     *
126     * @return bool
127     */
128    public static function isMember(Tree $tree, UserInterface $user = null): bool
129    {
130        $user = $user ?? self::user();
131
132        return self::isEditor($tree, $user) || $tree->getUserPreference($user, 'canedit') === 'access';
133    }
134
135    /**
136     * What is the specified/current user's access level within a tree?
137     *
138     * @param Tree               $tree
139     * @param UserInterface|null $user
140     *
141     * @return int
142     */
143    public static function accessLevel(Tree $tree, UserInterface $user = null): int
144    {
145        $user = $user ?? self::user();
146
147        if (self::isManager($tree, $user)) {
148            return self::PRIV_NONE;
149        }
150
151        if (self::isMember($tree, $user)) {
152            return self::PRIV_USER;
153        }
154
155        return self::PRIV_PRIVATE;
156    }
157
158    /**
159     * The ID of the authenticated user, from the current session.
160     *
161     * @return int|null
162     */
163    public static function id(): ?int
164    {
165        $id = Session::get('wt_user');
166
167        if ($id !== null) {
168            // In webtrees 1.x, the ID may have been a string.
169            $id = (int) $id;
170        }
171
172        return $id;
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 (new UserService())->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          $component
211     * @param Tree            $tree
212     * @param UserInterface   $user
213     *
214     * @return void
215     */
216    public static function checkComponentAccess(ModuleInterface $module, string $component, Tree $tree, UserInterface $user): void
217    {
218        if ($module->accessLevel($tree, $component) < self::accessLevel($tree, $user)) {
219            throw new AccessDeniedHttpException('Access denied');
220        }
221    }
222
223    /**
224     * @param Family|null $family
225     * @param bool|null   $edit
226     *
227     * @return void
228     * @throws FamilyNotFoundException
229     * @throws FamilyAccessDeniedException
230     */
231    public static function checkFamilyAccess(Family $family = null, $edit = false): void
232    {
233        if ($family === null) {
234            throw new FamilyNotFoundException();
235        }
236
237        if (!$family->canShow()) {
238            throw new FamilyAccessDeniedException();
239        }
240
241        if ($edit && !$family->canEdit()) {
242            throw new FamilyAccessDeniedException();
243        }
244    }
245
246    /**
247     * @param Individual|null $individual
248     * @param bool|null       $edit
249     *
250     * @return void
251     * @throws IndividualNotFoundException
252     * @throws IndividualAccessDeniedException
253     */
254    public static function checkIndividualAccess(Individual $individual = null, $edit = false): void
255    {
256        if ($individual === null) {
257            throw new IndividualNotFoundException();
258        }
259
260        if (!$individual->canShow()) {
261            throw new IndividualAccessDeniedException();
262        }
263
264        if ($edit && !$individual->canEdit()) {
265            throw new IndividualAccessDeniedException();
266        }
267    }
268
269    /**
270     * @param Media|null $media
271     * @param bool|null  $edit
272     *
273     * @return void
274     * @throws MediaNotFoundException
275     * @throws MediaAccessDeniedException
276     */
277    public static function checkMediaAccess(Media $media = null, $edit = false): void
278    {
279        if ($media === null) {
280            throw new MediaNotFoundException();
281        }
282
283        if (!$media->canShow()) {
284            throw new MediaAccessDeniedException();
285        }
286
287        if ($edit && !$media->canEdit()) {
288            throw new MediaAccessDeniedException();
289        }
290    }
291
292    /**
293     * @param Note|null $note
294     * @param bool|null $edit
295     *
296     * @return void
297     * @throws NoteNotFoundException
298     * @throws NoteAccessDeniedException
299     */
300    public static function checkNoteAccess(Note $note = null, $edit = false): void
301    {
302        if ($note === null) {
303            throw new NoteNotFoundException();
304        }
305
306        if (!$note->canShow()) {
307            throw new NoteAccessDeniedException();
308        }
309
310        if ($edit && !$note->canEdit()) {
311            throw new NoteAccessDeniedException();
312        }
313    }
314
315    /**
316     * @param GedcomRecord|null $record
317     * @param bool|null         $edit
318     *
319     * @return void
320     * @throws RecordNotFoundException
321     * @throws RecordAccessDeniedException
322     */
323    public static function checkRecordAccess(GedcomRecord $record = null, $edit = false): void
324    {
325        if ($record === null) {
326            throw new RecordNotFoundException();
327        }
328
329        if (!$record->canShow()) {
330            throw new RecordAccessDeniedException();
331        }
332
333        if ($edit && !$record->canEdit()) {
334            throw new RecordAccessDeniedException();
335        }
336    }
337
338    /**
339     * @param Repository|null $repository
340     * @param bool|null       $edit
341     *
342     * @return void
343     * @throws RepositoryNotFoundException
344     * @throws RepositoryAccessDeniedException
345     */
346    public static function checkRepositoryAccess(Repository $repository = null, $edit = false): void
347    {
348        if ($repository === null) {
349            throw new RepositoryNotFoundException();
350        }
351
352        if (!$repository->canShow()) {
353            throw new RepositoryAccessDeniedException();
354        }
355
356        if ($edit && !$repository->canEdit()) {
357            throw new RepositoryAccessDeniedException();
358        }
359    }
360
361    /**
362     * @param Source|null $source
363     * @param bool|null   $edit
364     *
365     * @return void
366     * @throws SourceNotFoundException
367     * @throws SourceAccessDeniedException
368     */
369    public static function checkSourceAccess(Source $source = null, $edit = false): void
370    {
371        if ($source === null) {
372            throw new SourceNotFoundException();
373        }
374
375        if (!$source->canShow()) {
376            throw new SourceAccessDeniedException();
377        }
378
379        if ($edit && !$source->canEdit()) {
380            throw new SourceAccessDeniedException();
381        }
382    }
383}
384