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