xref: /webtrees/app/Auth.php (revision 9b93b7c342a543e7bd55b5a09d97ba0c39fc32b0)
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()) {
230            throw new FamilyAccessDeniedException();
231        }
232
233        if ($edit && !$family->canEdit()) {
234            throw new FamilyAccessDeniedException();
235        }
236    }
237
238    /**
239     * @param Individual|null $individual
240     * @param bool|null       $edit
241     *
242     * @return void
243     * @throws IndividualNotFoundException
244     * @throws IndividualAccessDeniedException
245     */
246    public static function checkIndividualAccess(Individual $individual = null, $edit = false): void
247    {
248        if ($individual === null) {
249            throw new IndividualNotFoundException();
250        }
251
252        if (!$individual->canShow()) {
253            throw new IndividualAccessDeniedException();
254        }
255
256        if ($edit && !$individual->canEdit()) {
257            throw new IndividualAccessDeniedException();
258        }
259    }
260
261    /**
262     * @param Media|null $media
263     * @param bool|null  $edit
264     *
265     * @return void
266     * @throws MediaNotFoundException
267     * @throws MediaAccessDeniedException
268     */
269    public static function checkMediaAccess(Media $media = null, $edit = false): void
270    {
271        if ($media === null) {
272            throw new MediaNotFoundException();
273        }
274
275        if (!$media->canShow()) {
276            throw new MediaAccessDeniedException();
277        }
278
279        if ($edit && !$media->canEdit()) {
280            throw new MediaAccessDeniedException();
281        }
282    }
283
284    /**
285     * @param Note|null $note
286     * @param bool|null $edit
287     *
288     * @return void
289     * @throws NoteNotFoundException
290     * @throws NoteAccessDeniedException
291     */
292    public static function checkNoteAccess(Note $note = null, $edit = false): void
293    {
294        if ($note === null) {
295            throw new NoteNotFoundException();
296        }
297
298        if (!$note->canShow()) {
299            throw new NoteAccessDeniedException();
300        }
301
302        if ($edit && !$note->canEdit()) {
303            throw new NoteAccessDeniedException();
304        }
305    }
306
307    /**
308     * @param GedcomRecord|null $record
309     * @param bool|null         $edit
310     *
311     * @return void
312     * @throws RecordNotFoundException
313     * @throws RecordAccessDeniedException
314     */
315    public static function checkRecordAccess(GedcomRecord $record = null, $edit = false): void
316    {
317        if ($record === null) {
318            throw new RecordNotFoundException();
319        }
320
321        if (!$record->canShow()) {
322            throw new RecordAccessDeniedException();
323        }
324
325        if ($edit && !$record->canEdit()) {
326            throw new RecordAccessDeniedException();
327        }
328    }
329
330    /**
331     * @param Repository|null $repository
332     * @param bool|null       $edit
333     *
334     * @return void
335     * @throws RepositoryNotFoundException
336     * @throws RepositoryAccessDeniedException
337     */
338    public static function checkRepositoryAccess(Repository $repository = null, $edit = false): void
339    {
340        if ($repository === null) {
341            throw new RepositoryNotFoundException();
342        }
343
344        if (!$repository->canShow()) {
345            throw new RepositoryAccessDeniedException();
346        }
347
348        if ($edit && !$repository->canEdit()) {
349            throw new RepositoryAccessDeniedException();
350        }
351    }
352
353    /**
354     * @param Source|null $source
355     * @param bool|null   $edit
356     *
357     * @return void
358     * @throws SourceNotFoundException
359     * @throws SourceAccessDeniedException
360     */
361    public static function checkSourceAccess(Source $source = null, $edit = false): void
362    {
363        if ($source === null) {
364            throw new SourceNotFoundException();
365        }
366
367        if (!$source->canShow()) {
368            throw new SourceAccessDeniedException();
369        }
370
371        if ($edit && !$source->canEdit()) {
372            throw new SourceAccessDeniedException();
373        }
374    }
375}
376