1<?php 2 3/** 4 * webtrees: online genealogy 5 * Copyright (C) 2021 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 <https://www.gnu.org/licenses/>. 16 */ 17 18declare(strict_types=1); 19 20namespace Fisharebest\Webtrees; 21 22use Fisharebest\Webtrees\Contracts\UserInterface; 23use Fisharebest\Webtrees\Http\Exceptions\HttpAccessDeniedException; 24use Fisharebest\Webtrees\Http\Exceptions\HttpNotFoundException; 25use Fisharebest\Webtrees\Module\ModuleInterface; 26use Fisharebest\Webtrees\Services\UserService; 27 28/** 29 * Authentication. 30 */ 31class Auth 32{ 33 // Privacy constants 34 public const PRIV_PRIVATE = 2; // Allows visitors to view the item 35 public const PRIV_USER = 1; // Allows members to access the item 36 public const PRIV_NONE = 0; // Allows managers to access the item 37 public const PRIV_HIDE = -1; // Hide the item to all users 38 39 /** 40 * Are we currently logged in? 41 * 42 * @return bool 43 */ 44 public static function check(): bool 45 { 46 return self::id() !== null; 47 } 48 49 /** 50 * Is the specified/current user an administrator? 51 * 52 * @param UserInterface|null $user 53 * 54 * @return bool 55 */ 56 public static function isAdmin(UserInterface $user = null): bool 57 { 58 $user = $user ?? self::user(); 59 60 return $user->getPreference(UserInterface::PREF_IS_ADMINISTRATOR) === '1'; 61 } 62 63 /** 64 * Is the specified/current user a manager of a tree? 65 * 66 * @param Tree $tree 67 * @param UserInterface|null $user 68 * 69 * @return bool 70 */ 71 public static function isManager(Tree $tree, UserInterface $user = null): bool 72 { 73 $user = $user ?? self::user(); 74 75 return self::isAdmin($user) || $tree->getUserPreference($user, UserInterface::PREF_TREE_ROLE) === UserInterface::ROLE_MANAGER; 76 } 77 78 /** 79 * Is the specified/current user a moderator of a tree? 80 * 81 * @param Tree $tree 82 * @param UserInterface|null $user 83 * 84 * @return bool 85 */ 86 public static function isModerator(Tree $tree, UserInterface $user = null): bool 87 { 88 $user = $user ?? self::user(); 89 90 return 91 self::isManager($tree, $user) || 92 $tree->getUserPreference($user, UserInterface::PREF_TREE_ROLE) === UserInterface::ROLE_MODERATOR; 93 } 94 95 /** 96 * Is the specified/current user an editor of a tree? 97 * 98 * @param Tree $tree 99 * @param UserInterface|null $user 100 * 101 * @return bool 102 */ 103 public static function isEditor(Tree $tree, UserInterface $user = null): bool 104 { 105 $user = $user ?? self::user(); 106 107 return 108 self::isModerator($tree, $user) || 109 $tree->getUserPreference($user, UserInterface::PREF_TREE_ROLE) === UserInterface::ROLE_EDITOR; 110 } 111 112 /** 113 * Is the specified/current user a member of a tree? 114 * 115 * @param Tree $tree 116 * @param UserInterface|null $user 117 * 118 * @return bool 119 */ 120 public static function isMember(Tree $tree, UserInterface $user = null): bool 121 { 122 $user = $user ?? self::user(); 123 124 return 125 self::isEditor($tree, $user) || 126 $tree->getUserPreference($user, UserInterface::PREF_TREE_ROLE) === UserInterface::ROLE_MEMBER; 127 } 128 129 /** 130 * What is the specified/current user's access level within a tree? 131 * 132 * @param Tree $tree 133 * @param UserInterface|null $user 134 * 135 * @return int 136 */ 137 public static function accessLevel(Tree $tree, UserInterface $user = null): int 138 { 139 $user = $user ?? self::user(); 140 141 if (self::isManager($tree, $user)) { 142 return self::PRIV_NONE; 143 } 144 145 if (self::isMember($tree, $user)) { 146 return self::PRIV_USER; 147 } 148 149 return self::PRIV_PRIVATE; 150 } 151 152 /** 153 * The ID of the authenticated user, from the current session. 154 * 155 * @return int|null 156 */ 157 public static function id(): ?int 158 { 159 return Session::get('wt_user'); 160 } 161 162 /** 163 * The authenticated user, from the current session. 164 * 165 * @return UserInterface 166 */ 167 public static function user(): UserInterface 168 { 169 return app(UserService::class)->find(self::id()) ?? new GuestUser(); 170 } 171 172 /** 173 * Login directly as an explicit user - for masquerading. 174 * 175 * @param UserInterface $user 176 * 177 * @return void 178 */ 179 public static function login(UserInterface $user): void 180 { 181 Session::regenerate(); 182 Session::put('wt_user', $user->id()); 183 } 184 185 /** 186 * End the session for the current user. 187 * 188 * @return void 189 */ 190 public static function logout(): void 191 { 192 Session::regenerate(true); 193 } 194 195 /** 196 * @param ModuleInterface $module 197 * @param string $interface 198 * @param Tree $tree 199 * @param UserInterface $user 200 * 201 * @return void 202 */ 203 public static function checkComponentAccess(ModuleInterface $module, string $interface, Tree $tree, UserInterface $user): void 204 { 205 if ($module->accessLevel($tree, $interface) < self::accessLevel($tree, $user)) { 206 throw new HttpAccessDeniedException(); 207 } 208 } 209 210 /** 211 * @param Family|null $family 212 * @param bool $edit 213 * 214 * @return Family 215 * @throws HttpNotFoundException 216 * @throws HttpAccessDeniedException 217 */ 218 public static function checkFamilyAccess(?Family $family, bool $edit = false): Family 219 { 220 $message = I18N::translate('This family does not exist or you do not have permission to view it.'); 221 222 if ($family === null) { 223 throw new HttpNotFoundException($message); 224 } 225 226 if ($edit && $family->canEdit()) { 227 $family->lock(); 228 229 return $family; 230 } 231 232 if ($family->canShow()) { 233 return $family; 234 } 235 236 throw new HttpAccessDeniedException($message); 237 } 238 239 /** 240 * @param Header|null $header 241 * @param bool $edit 242 * 243 * @return Header 244 * @throws HttpNotFoundException 245 * @throws HttpAccessDeniedException 246 */ 247 public static function checkHeaderAccess(?Header $header, bool $edit = false): Header 248 { 249 $message = I18N::translate('This record does not exist or you do not have permission to view it.'); 250 251 if ($header === null) { 252 throw new HttpNotFoundException($message); 253 } 254 255 if ($edit && $header->canEdit()) { 256 $header->lock(); 257 258 return $header; 259 } 260 261 if ($header->canShow()) { 262 return $header; 263 } 264 265 throw new HttpAccessDeniedException($message); 266 } 267 268 /** 269 * @param Individual|null $individual 270 * @param bool $edit 271 * @param bool $chart For some charts, we can show private records 272 * 273 * @return Individual 274 * @throws HttpNotFoundException 275 * @throws HttpAccessDeniedException 276 */ 277 public static function checkIndividualAccess(?Individual $individual, bool $edit = false, bool $chart = false): Individual 278 { 279 $message = I18N::translate('This individual does not exist or you do not have permission to view it.'); 280 281 if ($individual === null) { 282 throw new HttpNotFoundException($message); 283 } 284 285 if ($edit && $individual->canEdit()) { 286 $individual->lock(); 287 288 return $individual; 289 } 290 291 if ($chart && $individual->tree()->getPreference('SHOW_PRIVATE_RELATIONSHIPS') === '1') { 292 return $individual; 293 } 294 295 if ($individual->canShow()) { 296 return $individual; 297 } 298 299 throw new HttpAccessDeniedException($message); 300 } 301 302 /** 303 * @param Location|null $location 304 * @param bool $edit 305 * 306 * @return Location 307 * @throws HttpNotFoundException 308 * @throws HttpAccessDeniedException 309 */ 310 public static function checkLocationAccess(?Location $location, bool $edit = false): Location 311 { 312 $message = I18N::translate('This record does not exist or you do not have permission to view it.'); 313 314 if ($location === null) { 315 throw new HttpNotFoundException($message); 316 } 317 318 if ($edit && $location->canEdit()) { 319 $location->lock(); 320 321 return $location; 322 } 323 324 if ($location->canShow()) { 325 return $location; 326 } 327 328 throw new HttpAccessDeniedException($message); 329 } 330 331 /** 332 * @param Media|null $media 333 * @param bool $edit 334 * 335 * @return Media 336 * @throws HttpNotFoundException 337 * @throws HttpAccessDeniedException 338 */ 339 public static function checkMediaAccess(?Media $media, bool $edit = false): Media 340 { 341 $message = I18N::translate('This media object does not exist or you do not have permission to view it.'); 342 343 if ($media === null) { 344 throw new HttpNotFoundException($message); 345 } 346 347 if ($edit && $media->canEdit()) { 348 $media->lock(); 349 350 return $media; 351 } 352 353 if ($media->canShow()) { 354 return $media; 355 } 356 357 throw new HttpAccessDeniedException($message); 358 } 359 360 /** 361 * @param Note|null $note 362 * @param bool $edit 363 * 364 * @return Note 365 * @throws HttpNotFoundException 366 * @throws HttpAccessDeniedException 367 */ 368 public static function checkNoteAccess(?Note $note, bool $edit = false): Note 369 { 370 $message = I18N::translate('This note does not exist or you do not have permission to view it.'); 371 372 if ($note === null) { 373 throw new HttpNotFoundException($message); 374 } 375 376 if ($edit && $note->canEdit()) { 377 $note->lock(); 378 379 return $note; 380 } 381 382 if ($note->canShow()) { 383 return $note; 384 } 385 386 throw new HttpAccessDeniedException($message); 387 } 388 389 /** 390 * @param GedcomRecord|null $record 391 * @param bool $edit 392 * 393 * @return GedcomRecord 394 * @throws HttpNotFoundException 395 * @throws HttpAccessDeniedException 396 */ 397 public static function checkRecordAccess(?GedcomRecord $record, bool $edit = false): GedcomRecord 398 { 399 $message = I18N::translate('This record does not exist or you do not have permission to view it.'); 400 401 if ($record === null) { 402 throw new HttpNotFoundException($message); 403 } 404 405 if ($edit && $record->canEdit()) { 406 $record->lock(); 407 408 return $record; 409 } 410 411 if ($record->canShow()) { 412 return $record; 413 } 414 415 throw new HttpAccessDeniedException($message); 416 } 417 418 /** 419 * @param Repository|null $repository 420 * @param bool $edit 421 * 422 * @return Repository 423 * @throws HttpNotFoundException 424 * @throws HttpAccessDeniedException 425 */ 426 public static function checkRepositoryAccess(?Repository $repository, bool $edit = false): Repository 427 { 428 $message = I18N::translate('This repository does not exist or you do not have permission to view it.'); 429 430 if ($repository === null) { 431 throw new HttpNotFoundException($message); 432 } 433 434 if ($edit && $repository->canEdit()) { 435 $repository->lock(); 436 437 return $repository; 438 } 439 440 if ($repository->canShow()) { 441 return $repository; 442 } 443 444 throw new HttpAccessDeniedException($message); 445 } 446 447 /** 448 * @param Source|null $source 449 * @param bool $edit 450 * 451 * @return Source 452 * @throws HttpNotFoundException 453 * @throws HttpAccessDeniedException 454 */ 455 public static function checkSourceAccess(?Source $source, bool $edit = false): Source 456 { 457 $message = I18N::translate('This source does not exist or you do not have permission to view it.'); 458 459 if ($source === null) { 460 throw new HttpNotFoundException($message); 461 } 462 463 if ($edit && $source->canEdit()) { 464 $source->lock(); 465 466 return $source; 467 } 468 469 if ($source->canShow()) { 470 return $source; 471 } 472 473 throw new HttpAccessDeniedException($message); 474 } 475 476 /* 477 * @param Submitter|null $submitter 478 * @param bool $edit 479 * 480 * @return Submitter 481 * @throws HttpFoundException 482 * @throws HttpDeniedException 483 */ 484 public static function checkSubmitterAccess(?Submitter $submitter, bool $edit = false): Submitter 485 { 486 $message = I18N::translate('This record does not exist or you do not have permission to view it.'); 487 488 if ($submitter === null) { 489 throw new HttpNotFoundException($message); 490 } 491 492 if ($edit && $submitter->canEdit()) { 493 $submitter->lock(); 494 495 return $submitter; 496 } 497 498 if ($submitter->canShow()) { 499 return $submitter; 500 } 501 502 throw new HttpAccessDeniedException($message); 503 } 504 505 /* 506 * @param Submission|null $submission 507 * @param bool $edit 508 * 509 * @return Submission 510 * @throws HttpNotFoundException 511 * @throws HttpAccessDeniedException 512 */ 513 public static function checkSubmissionAccess(?Submission $submission, bool $edit = false): Submission 514 { 515 $message = I18N::translate('This record does not exist or you do not have permission to view it.'); 516 517 if ($submission === null) { 518 throw new HttpNotFoundException($message); 519 } 520 521 if ($edit && $submission->canEdit()) { 522 $submission->lock(); 523 524 return $submission; 525 } 526 527 if ($submission->canShow()) { 528 return $submission; 529 } 530 531 throw new HttpAccessDeniedException($message); 532 } 533 534 /** 535 * @return array<int,string> 536 */ 537 public static function accessLevelNames(): array 538 { 539 return [ 540 self::PRIV_PRIVATE => I18N::translate('Show to visitors'), 541 self::PRIV_USER => I18N::translate('Show to members'), 542 self::PRIV_NONE => I18N::translate('Show to managers'), 543 self::PRIV_HIDE => I18N::translate('Hide from everyone'), 544 ]; 545 } 546 547 /** 548 * @return array<string,string> 549 */ 550 public static function privacyRuleNames(): array 551 { 552 return [ 553 'none' => I18N::translate('Show to visitors'), 554 'privacy' => I18N::translate('Show to members'), 555 'confidential' => I18N::translate('Show to managers'), 556 'hidden' => I18N::translate('Hide from everyone'), 557 ]; 558 } 559} 560