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\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(UserInterface::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, UserInterface::PREF_TREE_ROLE) === UserInterface::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 104 self::isManager($tree, $user) || 105 $tree->getUserPreference($user, UserInterface::PREF_TREE_ROLE) === UserInterface::ROLE_MODERATOR; 106 } 107 108 /** 109 * Is the specified/current user an editor of a tree? 110 * 111 * @param Tree $tree 112 * @param UserInterface|null $user 113 * 114 * @return bool 115 */ 116 public static function isEditor(Tree $tree, UserInterface $user = null): bool 117 { 118 $user = $user ?? self::user(); 119 120 return 121 self::isModerator($tree, $user) || 122 $tree->getUserPreference($user, UserInterface::PREF_TREE_ROLE) === UserInterface::ROLE_EDITOR; 123 } 124 125 /** 126 * Is the specified/current user a member of a tree? 127 * 128 * @param Tree $tree 129 * @param UserInterface|null $user 130 * 131 * @return bool 132 */ 133 public static function isMember(Tree $tree, UserInterface $user = null): bool 134 { 135 $user = $user ?? self::user(); 136 137 return 138 self::isEditor($tree, $user) || 139 $tree->getUserPreference($user, UserInterface::PREF_TREE_ROLE) === UserInterface::ROLE_MEMBER; 140 } 141 142 /** 143 * What is the specified/current user's access level within a tree? 144 * 145 * @param Tree $tree 146 * @param UserInterface|null $user 147 * 148 * @return int 149 */ 150 public static function accessLevel(Tree $tree, UserInterface $user = null): int 151 { 152 $user = $user ?? self::user(); 153 154 if (self::isManager($tree, $user)) { 155 return self::PRIV_NONE; 156 } 157 158 if (self::isMember($tree, $user)) { 159 return self::PRIV_USER; 160 } 161 162 return self::PRIV_PRIVATE; 163 } 164 165 /** 166 * The ID of the authenticated user, from the current session. 167 * 168 * @return int|null 169 */ 170 public static function id(): ?int 171 { 172 return Session::get('wt_user'); 173 } 174 175 /** 176 * The authenticated user, from the current session. 177 * 178 * @return UserInterface 179 */ 180 public static function user(): UserInterface 181 { 182 return app(UserService::class)->find(self::id()) ?? new GuestUser(); 183 } 184 185 /** 186 * Login directly as an explicit user - for masquerading. 187 * 188 * @param UserInterface $user 189 * 190 * @return void 191 */ 192 public static function login(UserInterface $user): void 193 { 194 Session::regenerate(false); 195 Session::put('wt_user', $user->id()); 196 } 197 198 /** 199 * End the session for the current user. 200 * 201 * @return void 202 */ 203 public static function logout(): void 204 { 205 Session::regenerate(true); 206 } 207 208 /** 209 * @param ModuleInterface $module 210 * @param string $interface 211 * @param Tree $tree 212 * @param UserInterface $user 213 * 214 * @return void 215 */ 216 public static function checkComponentAccess(ModuleInterface $module, string $interface, Tree $tree, UserInterface $user): void 217 { 218 if ($module->accessLevel($tree, $interface) < self::accessLevel($tree, $user)) { 219 throw new HttpAccessDeniedException(); 220 } 221 } 222 223 /** 224 * @param Family|null $family 225 * @param bool $edit 226 * 227 * @return Family 228 * @throws FamilyNotFoundException 229 * @throws FamilyAccessDeniedException 230 */ 231 public static function checkFamilyAccess(?Family $family, bool $edit = false): Family 232 { 233 if ($family === null) { 234 throw new FamilyNotFoundException(); 235 } 236 237 if ($edit && $family->canEdit()) { 238 $family->lock(); 239 240 return $family; 241 } 242 243 if ($family->canShow()) { 244 return $family; 245 } 246 247 throw new FamilyAccessDeniedException(); 248 } 249 250 /** 251 * @param Header|null $header 252 * @param bool $edit 253 * 254 * @return Header 255 * @throws RecordNotFoundException 256 * @throws RecordAccessDeniedException 257 */ 258 public static function checkHeaderAccess(?Header $header, bool $edit = false): Header 259 { 260 if ($header === null) { 261 throw new RecordNotFoundException(); 262 } 263 264 if ($edit && $header->canEdit()) { 265 $header->lock(); 266 267 return $header; 268 } 269 270 if ($header->canShow()) { 271 return $header; 272 } 273 274 throw new RecordAccessDeniedException(); 275 } 276 277 /** 278 * @param Individual|null $individual 279 * @param bool $edit 280 * @param bool $chart For some charts, we can show private records 281 * 282 * @return Individual 283 * @throws IndividualNotFoundException 284 * @throws IndividualAccessDeniedException 285 */ 286 public static function checkIndividualAccess(?Individual $individual, bool $edit = false, $chart = false): Individual 287 { 288 if ($individual === null) { 289 throw new IndividualNotFoundException(); 290 } 291 292 if ($edit && $individual->canEdit()) { 293 $individual->lock(); 294 295 return $individual; 296 } 297 298 if ($chart && $individual->tree()->getPreference('SHOW_PRIVATE_RELATIONSHIPS') === '1') { 299 return $individual; 300 } 301 302 if ($individual->canShow()) { 303 return $individual; 304 } 305 306 throw new IndividualAccessDeniedException(); 307 } 308 309 /** 310 * @param Media|null $media 311 * @param bool $edit 312 * 313 * @return Media 314 * @throws MediaNotFoundException 315 * @throws MediaAccessDeniedException 316 */ 317 public static function checkMediaAccess(?Media $media, bool $edit = false): Media 318 { 319 if ($media === null) { 320 throw new MediaNotFoundException(); 321 } 322 323 if ($edit && $media->canEdit()) { 324 $media->lock(); 325 326 return $media; 327 } 328 329 if ($media->canShow()) { 330 return $media; 331 } 332 333 throw new MediaAccessDeniedException(); 334 } 335 336 /** 337 * @param Note|null $note 338 * @param bool $edit 339 * 340 * @return Note 341 * @throws NoteNotFoundException 342 * @throws NoteAccessDeniedException 343 */ 344 public static function checkNoteAccess(?Note $note, bool $edit = false): Note 345 { 346 if ($note === null) { 347 throw new NoteNotFoundException(); 348 } 349 350 if ($edit && $note->canEdit()) { 351 $note->lock(); 352 353 return $note; 354 } 355 356 if ($note->canShow()) { 357 return $note; 358 } 359 360 throw new NoteAccessDeniedException(); 361 } 362 363 /** 364 * @param GedcomRecord|null $record 365 * @param bool $edit 366 * 367 * @return GedcomRecord 368 * @throws RecordNotFoundException 369 * @throws RecordAccessDeniedException 370 */ 371 public static function checkRecordAccess(?GedcomRecord $record, bool $edit = false): GedcomRecord 372 { 373 if ($record === null) { 374 throw new RecordNotFoundException(); 375 } 376 377 if ($edit && $record->canEdit()) { 378 $record->lock(); 379 380 return $record; 381 } 382 383 if ($record->canShow()) { 384 return $record; 385 } 386 387 throw new RecordAccessDeniedException(); 388 } 389 390 /** 391 * @param Repository|null $repository 392 * @param bool $edit 393 * 394 * @return Repository 395 * @throws RepositoryNotFoundException 396 * @throws RepositoryAccessDeniedException 397 */ 398 public static function checkRepositoryAccess(?Repository $repository, bool $edit = false): Repository 399 { 400 if ($repository === null) { 401 throw new RepositoryNotFoundException(); 402 } 403 404 if ($edit && $repository->canEdit()) { 405 $repository->lock(); 406 407 return $repository; 408 } 409 410 if ($repository->canShow()) { 411 return $repository; 412 } 413 414 throw new RepositoryAccessDeniedException(); 415 } 416 417 /** 418 * @param Source|null $source 419 * @param bool $edit 420 * 421 * @return Source 422 * @throws SourceNotFoundException 423 * @throws SourceAccessDeniedException 424 */ 425 public static function checkSourceAccess(?Source $source, bool $edit = false): Source 426 { 427 if ($source === null) { 428 throw new SourceNotFoundException(); 429 } 430 431 if ($edit && $source->canEdit()) { 432 $source->lock(); 433 434 return $source; 435 } 436 437 if ($source->canShow()) { 438 return $source; 439 } 440 441 throw new SourceAccessDeniedException(); 442 } 443 444 /* 445 * @param Submitter|null $submitter 446 * @param bool $edit 447 * 448 * @return Submitter 449 * @throws RecordNotFoundException 450 * @throws RecordAccessDeniedException 451 */ 452 public static function checkSubmitterAccess(?Submitter $submitter, bool $edit = false): Submitter 453 { 454 if ($submitter === null) { 455 throw new RecordNotFoundException(); 456 } 457 458 if ($edit && $submitter->canEdit()) { 459 $submitter->lock(); 460 461 return $submitter; 462 } 463 464 if ($submitter->canShow()) { 465 return $submitter; 466 } 467 468 throw new RecordAccessDeniedException(); 469 } 470 471 /* 472 * @param Submission|null $submission 473 * @param bool $edit 474 * 475 * @return Submission 476 * @throws RecordNotFoundException 477 * @throws RecordAccessDeniedException 478 */ 479 public static function checkSubmissionAccess(?Submission $submission, bool $edit = false): Submission 480 { 481 if ($submission === null) { 482 throw new RecordNotFoundException(); 483 } 484 485 if ($edit && $submission->canEdit()) { 486 $submission->lock(); 487 488 return $submission; 489 } 490 491 if ($submission->canShow()) { 492 return $submission; 493 } 494 495 throw new RecordAccessDeniedException(); 496 } 497 498 /** 499 * @return array<int,string> 500 */ 501 public static function accessLevelNames(): array 502 { 503 return [ 504 self::PRIV_PRIVATE => I18N::translate('Show to visitors'), 505 self::PRIV_USER => I18N::translate('Show to members'), 506 self::PRIV_NONE => I18N::translate('Show to managers'), 507 self::PRIV_HIDE => I18N::translate('Hide from everyone'), 508 ]; 509 } 510 511 /** 512 * @return array<string,string> 513 */ 514 public static function privacyRuleNames(): array 515 { 516 return [ 517 'none' => I18N::translate('Show to visitors'), 518 'privacy' => I18N::translate('Show to members'), 519 'confidential' => I18N::translate('Show to managers'), 520 'hidden' => I18N::translate('Hide from everyone'), 521 ]; 522 } 523} 524