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