xref: /webtrees/app/Auth.php (revision 8af6bbf82759f6df0fe2b5b49cb4dd05118748d1)
1<?php
2/**
3 * webtrees: online genealogy
4 * Copyright (C) 2019 webtrees development team
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 */
16declare(strict_types=1);
17
18namespace Fisharebest\Webtrees;
19
20use Fisharebest\Webtrees\Exceptions\FamilyAccessDeniedException;
21use Fisharebest\Webtrees\Exceptions\FamilyNotFoundException;
22use Fisharebest\Webtrees\Exceptions\IndividualAccessDeniedException;
23use Fisharebest\Webtrees\Exceptions\IndividualNotFoundException;
24use Fisharebest\Webtrees\Exceptions\MediaAccessDeniedException;
25use Fisharebest\Webtrees\Exceptions\MediaNotFoundException;
26use Fisharebest\Webtrees\Exceptions\NoteAccessDeniedException;
27use Fisharebest\Webtrees\Exceptions\NoteNotFoundException;
28use Fisharebest\Webtrees\Exceptions\RecordAccessDeniedException;
29use Fisharebest\Webtrees\Exceptions\RecordNotFoundException;
30use Fisharebest\Webtrees\Exceptions\RepositoryAccessDeniedException;
31use Fisharebest\Webtrees\Exceptions\RepositoryNotFoundException;
32use Fisharebest\Webtrees\Exceptions\SourceAccessDeniedException;
33use Fisharebest\Webtrees\Exceptions\SourceNotFoundException;
34use Fisharebest\Webtrees\Module\ModuleInterface;
35use stdClass;
36use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
37
38/**
39 * Authentication.
40 */
41class Auth
42{
43    // Privacy constants
44    public const PRIV_PRIVATE = 2; // Allows visitors to view the item
45    public const PRIV_USER    = 1; // Allows members to access the item
46    public const PRIV_NONE    = 0; // Allows managers to access the item
47    public const PRIV_HIDE    = -1; // Hide the item to all users
48
49    /**
50     * Are we currently logged in?
51     *
52     * @return bool
53     */
54    public static function check(): bool
55    {
56        return self::id() !== null;
57    }
58
59    /**
60     * Is the specified/current user an administrator?
61     *
62     * @param User|null $user
63     *
64     * @return bool
65     */
66    public static function isAdmin(User $user = null): bool
67    {
68        $user = $user ?? self::user();
69
70        return $user->getPreference('canadmin') === '1';
71    }
72
73    /**
74     * Is the specified/current user a manager of a tree?
75     *
76     * @param Tree      $tree
77     * @param User|null $user
78     *
79     * @return bool
80     */
81    public static function isManager(Tree $tree, User $user = null): bool
82    {
83        $user = $user ?? self::user();
84
85        return self::isAdmin($user) || $tree->getUserPreference($user, 'canedit') === 'admin';
86    }
87
88    /**
89     * Is the specified/current user a moderator of a tree?
90     *
91     * @param Tree      $tree
92     * @param User|null $user
93     *
94     * @return bool
95     */
96    public static function isModerator(Tree $tree, User $user = null): bool
97    {
98        $user = $user ?? self::user();
99
100        return self::isManager($tree, $user) || $tree->getUserPreference($user, 'canedit') === 'accept';
101    }
102
103    /**
104     * Is the specified/current user an editor of a tree?
105     *
106     * @param Tree      $tree
107     * @param User|null $user
108     *
109     * @return bool
110     */
111    public static function isEditor(Tree $tree, User $user = null): bool
112    {
113        $user = $user ?? self::user();
114
115        return self::isModerator($tree, $user) || $tree->getUserPreference($user, 'canedit') === 'edit';
116    }
117
118    /**
119     * Is the specified/current user a member of a tree?
120     *
121     * @param Tree      $tree
122     * @param User|null $user
123     *
124     * @return bool
125     */
126    public static function isMember(Tree $tree, User $user = null): bool
127    {
128        $user = $user ?? self::user();
129
130        return self::isEditor($tree, $user) || $tree->getUserPreference($user, 'canedit') === 'access';
131    }
132
133    /**
134     * What is the specified/current user's access level within a tree?
135     *
136     * @param Tree      $tree
137     * @param User|null $user
138     *
139     * @return int
140     */
141    public static function accessLevel(Tree $tree, User $user = null)
142    {
143        $user = $user ?? self::user();
144
145        if (self::isManager($tree, $user)) {
146            return self::PRIV_NONE;
147        }
148
149        if (self::isMember($tree, $user)) {
150            return self::PRIV_USER;
151        }
152
153        return self::PRIV_PRIVATE;
154    }
155
156    /**
157     * The ID of the authenticated user, from the current session.
158     *
159     * @return int|null
160     */
161    public static function id()
162    {
163        return Session::get('wt_user');
164    }
165
166    /**
167     * The authenticated user, from the current session.
168     *
169     * @return User
170     */
171    public static function user()
172    {
173        return User::find(self::id()) ?? User::visitor();
174    }
175
176    /**
177     * Login directly as an explicit user - for masquerading.
178     *
179     * @param User $user
180     *
181     * @return void
182     */
183    public static function login(User $user)
184    {
185        Session::regenerate(false);
186        Session::put('wt_user', $user->id());
187    }
188
189    /**
190     * End the session for the current user.
191     *
192     * @return void
193     */
194    public static function logout()
195    {
196        Session::regenerate(true);
197    }
198
199    /**
200     * @param ModuleInterface $module
201     * @param string          $component
202     * @param Tree            $tree
203     * @param User            $user
204     *
205     * @return void
206     */
207    public static function checkComponentAccess(ModuleInterface $module, string $component, Tree $tree, User $user)
208    {
209        if ($module->accessLevel($tree, $component) < Auth::accessLevel($tree, $user)) {
210            throw new AccessDeniedHttpException('');
211        }
212    }
213
214    /**
215     * @param Family|null $family
216     * @param bool|null   $edit
217     *
218     * @return void
219     * @throws FamilyNotFoundException
220     * @throws FamilyAccessDeniedException
221     */
222    public static function checkFamilyAccess(Family $family = null, $edit = false)
223    {
224        if ($family === null) {
225            throw new FamilyNotFoundException();
226        }
227
228        if (!$family->canShow() || $edit && (!$family->canEdit() || $family->isPendingDeletion())) {
229            throw new FamilyAccessDeniedException();
230        }
231    }
232
233    /**
234     * @param Individual|null $individual
235     * @param bool|null       $edit
236     *
237     * @return void
238     * @throws IndividualNotFoundException
239     * @throws IndividualAccessDeniedException
240     */
241    public static function checkIndividualAccess(Individual $individual = null, $edit = false)
242    {
243        if ($individual === null) {
244            throw new IndividualNotFoundException();
245        }
246
247        if (!$individual->canShow() || $edit && (!$individual->canEdit() || $individual->isPendingDeletion())) {
248            throw new IndividualAccessDeniedException();
249        }
250    }
251
252    /**
253     * @param Media|null $media
254     * @param bool|null  $edit
255     *
256     * @return void
257     * @throws MediaNotFoundException
258     * @throws MediaAccessDeniedException
259     */
260    public static function checkMediaAccess(Media $media = null, $edit = false)
261    {
262        if ($media === null) {
263            throw new MediaNotFoundException();
264        }
265
266        if (!$media->canShow() || $edit && (!$media->canEdit() || $media->isPendingDeletion())) {
267            throw new MediaAccessDeniedException();
268        }
269    }
270
271    /**
272     * @param Note|null $note
273     * @param bool|null $edit
274     *
275     * @return void
276     * @throws NoteNotFoundException
277     * @throws NoteAccessDeniedException
278     */
279    public static function checkNoteAccess(Note $note = null, $edit = false)
280    {
281        if ($note === null) {
282            throw new NoteNotFoundException();
283        }
284
285        if (!$note->canShow() || $edit && (!$note->canEdit() || $note->isPendingDeletion())) {
286            throw new NoteAccessDeniedException();
287        }
288    }
289
290    /**
291     * @param GedcomRecord|null $record
292     * @param bool|null         $edit
293     *
294     * @return void
295     * @throws RecordNotFoundException
296     * @throws RecordAccessDeniedException
297     */
298    public static function checkRecordAccess(GedcomRecord $record = null, $edit = false)
299    {
300        if ($record === null) {
301            throw new RecordNotFoundException();
302        }
303
304        if (!$record->canShow() || $edit && (!$record->canEdit() || $record->isPendingDeletion())) {
305            throw new RecordAccessDeniedException();
306        }
307    }
308
309    /**
310     * @param Repository|null $repository
311     * @param bool|null       $edit
312     *
313     * @return void
314     * @throws RepositoryNotFoundException
315     * @throws RepositoryAccessDeniedException
316     */
317    public static function checkRepositoryAccess(Repository $repository = null, $edit = false)
318    {
319        if ($repository === null) {
320            throw new RepositoryNotFoundException();
321        }
322
323        if (!$repository->canShow() || $edit && (!$repository->canEdit() || $repository->isPendingDeletion())) {
324            throw new RepositoryAccessDeniedException();
325        }
326    }
327
328    /**
329     * @param Source|null $source
330     * @param bool|null   $edit
331     *
332     * @return void
333     * @throws SourceNotFoundException
334     * @throws SourceAccessDeniedException
335     */
336    public static function checkSourceAccess(Source $source = null, $edit = false)
337    {
338        if ($source === null) {
339            throw new SourceNotFoundException();
340        }
341
342        if (!$source->canShow() || $edit && (!$source->canEdit() || $source->isPendingDeletion())) {
343            throw new SourceAccessDeniedException();
344        }
345    }
346}
347