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