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 18declare(strict_types=1); 19 20namespace Fisharebest\Webtrees; 21 22use Fisharebest\Webtrees\Contracts\UserInterface; 23use Fisharebest\Webtrees\Exceptions\FamilyAccessDeniedException; 24use Fisharebest\Webtrees\Exceptions\FamilyNotFoundException; 25use Fisharebest\Webtrees\Exceptions\HttpAccessDeniedException; 26use Fisharebest\Webtrees\Exceptions\IndividualAccessDeniedException; 27use Fisharebest\Webtrees\Exceptions\IndividualNotFoundException; 28use Fisharebest\Webtrees\Exceptions\MediaAccessDeniedException; 29use Fisharebest\Webtrees\Exceptions\MediaNotFoundException; 30use Fisharebest\Webtrees\Exceptions\NoteAccessDeniedException; 31use Fisharebest\Webtrees\Exceptions\NoteNotFoundException; 32use Fisharebest\Webtrees\Exceptions\RecordAccessDeniedException; 33use Fisharebest\Webtrees\Exceptions\RecordNotFoundException; 34use Fisharebest\Webtrees\Exceptions\RepositoryAccessDeniedException; 35use Fisharebest\Webtrees\Exceptions\RepositoryNotFoundException; 36use Fisharebest\Webtrees\Exceptions\SourceAccessDeniedException; 37use Fisharebest\Webtrees\Exceptions\SourceNotFoundException; 38use Fisharebest\Webtrees\Module\ModuleInterface; 39use Fisharebest\Webtrees\Services\UserService; 40 41/** 42 * Authentication. 43 */ 44class 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 return Session::get('wt_user'); 167 } 168 169 /** 170 * The authenticated user, from the current session. 171 * 172 * @return UserInterface 173 */ 174 public static function user(): UserInterface 175 { 176 return app(UserService::class)->find(self::id()) ?? new GuestUser(); 177 } 178 179 /** 180 * Login directly as an explicit user - for masquerading. 181 * 182 * @param UserInterface $user 183 * 184 * @return void 185 */ 186 public static function login(UserInterface $user): void 187 { 188 Session::regenerate(false); 189 Session::put('wt_user', $user->id()); 190 } 191 192 /** 193 * End the session for the current user. 194 * 195 * @return void 196 */ 197 public static function logout(): void 198 { 199 Session::regenerate(true); 200 } 201 202 /** 203 * @param ModuleInterface $module 204 * @param string $component 205 * @param Tree $tree 206 * @param UserInterface $user 207 * 208 * @return void 209 */ 210 public static function checkComponentAccess(ModuleInterface $module, string $component, Tree $tree, UserInterface $user): void 211 { 212 if ($module->accessLevel($tree, $component) < self::accessLevel($tree, $user)) { 213 throw new HttpAccessDeniedException(); 214 } 215 } 216 217 /** 218 * @param Family|null $family 219 * @param bool $edit 220 * 221 * @return Family 222 * @throws FamilyNotFoundException 223 * @throws FamilyAccessDeniedException 224 */ 225 public static function checkFamilyAccess(?Family $family, bool $edit = false): Family 226 { 227 if ($family === null) { 228 throw new FamilyNotFoundException(); 229 } 230 231 if ($edit && $family->canEdit()) { 232 return $family; 233 } 234 235 if ($family->canShow()) { 236 return $family; 237 } 238 239 throw new FamilyAccessDeniedException(); 240 } 241 242 /** 243 * @param Header|null $header 244 * @param bool $edit 245 * 246 * @return Header 247 * @throws RecordNotFoundException 248 * @throws RecordAccessDeniedException 249 */ 250 public static function checkHeaderAccess(?Header $header, bool $edit = false): Header 251 { 252 if ($header === null) { 253 throw new RecordNotFoundException(); 254 } 255 256 if ($edit && $header->canEdit()) { 257 return $header; 258 } 259 260 if ($header->canShow()) { 261 return $header; 262 } 263 264 throw new RecordAccessDeniedException(); 265 } 266 267 /** 268 * @param Individual|null $individual 269 * @param bool $edit 270 * @param bool $chart For some charts, we can show private records 271 * 272 * @return Individual 273 * @throws IndividualNotFoundException 274 * @throws IndividualAccessDeniedException 275 */ 276 public static function checkIndividualAccess(?Individual $individual, bool $edit = false, $chart = false): Individual 277 { 278 if ($individual === null) { 279 throw new IndividualNotFoundException(); 280 } 281 282 if ($edit && $individual->canEdit()) { 283 return $individual; 284 } 285 286 if ($chart && $individual->tree()->getPreference('SHOW_PRIVATE_RELATIONSHIPS') === '1') { 287 return $individual; 288 } 289 290 if ($individual->canShow()) { 291 return $individual; 292 } 293 294 throw new IndividualAccessDeniedException(); 295 } 296 297 /** 298 * @param Media|null $media 299 * @param bool $edit 300 * 301 * @return Media 302 * @throws MediaNotFoundException 303 * @throws MediaAccessDeniedException 304 */ 305 public static function checkMediaAccess(?Media $media, bool $edit = false): Media 306 { 307 if ($media === null) { 308 throw new MediaNotFoundException(); 309 } 310 311 if ($edit && $media->canEdit()) { 312 return $media; 313 } 314 315 if ($media->canShow()) { 316 return $media; 317 } 318 319 throw new MediaAccessDeniedException(); 320 } 321 322 /** 323 * @param Note|null $note 324 * @param bool $edit 325 * 326 * @return Note 327 * @throws NoteNotFoundException 328 * @throws NoteAccessDeniedException 329 */ 330 public static function checkNoteAccess(?Note $note, bool $edit = false): Note 331 { 332 if ($note === null) { 333 throw new NoteNotFoundException(); 334 } 335 336 if ($edit && $note->canEdit()) { 337 return $note; 338 } 339 340 if ($note->canShow()) { 341 return $note; 342 } 343 344 throw new NoteAccessDeniedException(); 345 } 346 347 /** 348 * @param GedcomRecord|null $record 349 * @param bool $edit 350 * 351 * @return GedcomRecord 352 * @throws RecordNotFoundException 353 * @throws RecordAccessDeniedException 354 */ 355 public static function checkRecordAccess(?GedcomRecord $record, bool $edit = false): GedcomRecord 356 { 357 if ($record === null) { 358 throw new RecordNotFoundException(); 359 } 360 361 if ($edit && $record->canEdit()) { 362 return $record; 363 } 364 365 if ($record->canShow()) { 366 return $record; 367 } 368 369 throw new RecordAccessDeniedException(); 370 } 371 372 /** 373 * @param Repository|null $repository 374 * @param bool $edit 375 * 376 * @return Repository 377 * @throws RepositoryNotFoundException 378 * @throws RepositoryAccessDeniedException 379 */ 380 public static function checkRepositoryAccess(?Repository $repository, bool $edit = false): Repository 381 { 382 if ($repository === null) { 383 throw new RepositoryNotFoundException(); 384 } 385 386 if ($edit && $repository->canEdit()) { 387 return $repository; 388 } 389 390 if ($repository->canShow()) { 391 return $repository; 392 } 393 394 throw new RepositoryAccessDeniedException(); 395 } 396 397 /** 398 * @param Source|null $source 399 * @param bool $edit 400 * 401 * @return Source 402 * @throws SourceNotFoundException 403 * @throws SourceAccessDeniedException 404 */ 405 public static function checkSourceAccess(?Source $source, bool $edit = false): Source 406 { 407 if ($source === null) { 408 throw new SourceNotFoundException(); 409 } 410 411 if ($edit && $source->canEdit()) { 412 return $source; 413 } 414 415 if ($source->canShow()) { 416 return $source; 417 } 418 419 throw new SourceAccessDeniedException(); 420 } 421 422 /* 423 * @param Submitter|null $submitter 424 * @param bool $edit 425 * 426 * @return Submitter 427 * @throws RecordNotFoundException 428 * @throws RecordAccessDeniedException 429 */ 430 public static function checkSubmitterAccess(?Submitter $submitter, bool $edit = false): Submitter 431 { 432 if ($submitter === null) { 433 throw new RecordNotFoundException(); 434 } 435 436 if ($edit && $submitter->canEdit()) { 437 return $submitter; 438 } 439 440 if ($submitter->canShow()) { 441 return $submitter; 442 } 443 444 throw new RecordAccessDeniedException(); 445 } 446 447 /* 448 * @param Submission|null $submission 449 * @param bool $edit 450 * 451 * @return Submission 452 * @throws RecordNotFoundException 453 * @throws RecordAccessDeniedException 454 */ 455 public static function checkSubmissionAccess(?Submission $submission, bool $edit = false): Submission 456 { 457 if ($submission === null) { 458 throw new RecordNotFoundException(); 459 } 460 461 if ($edit && $submission->canEdit()) { 462 return $submission; 463 } 464 465 if ($submission->canShow()) { 466 return $submission; 467 } 468 469 throw new RecordAccessDeniedException(); 470 } 471 472 /** 473 * @return array<int,string> 474 */ 475 public static function accessLevelNames(): array 476 { 477 return [ 478 Auth::PRIV_PRIVATE => I18N::translate('Show to visitors'), 479 Auth::PRIV_USER => I18N::translate('Show to members'), 480 Auth::PRIV_NONE => I18N::translate('Show to managers'), 481 Auth::PRIV_HIDE => I18N::translate('Hide from everyone'), 482 ]; 483 } 484 485 /** 486 * @return array<string,string> 487 */ 488 public static function privacyRuleNames(): array 489 { 490 return [ 491 'none' => I18N::translate('Show to visitors'), 492 'privacy' => I18N::translate('Show to members'), 493 'confidential' => I18N::translate('Show to managers'), 494 'hidden' => I18N::translate('Hide from everyone'), 495 ]; 496 } 497} 498