1<?php 2 3/** 4 * webtrees: online genealogy 5 * Copyright (C) 2020 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 $interface 205 * @param Tree $tree 206 * @param UserInterface $user 207 * 208 * @return void 209 */ 210 public static function checkComponentAccess(ModuleInterface $module, string $interface, Tree $tree, UserInterface $user): void 211 { 212 if ($module->accessLevel($tree, $interface) < 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 $family->lock(); 233 234 return $family; 235 } 236 237 if ($family->canShow()) { 238 return $family; 239 } 240 241 throw new FamilyAccessDeniedException(); 242 } 243 244 /** 245 * @param Header|null $header 246 * @param bool $edit 247 * 248 * @return Header 249 * @throws RecordNotFoundException 250 * @throws RecordAccessDeniedException 251 */ 252 public static function checkHeaderAccess(?Header $header, bool $edit = false): Header 253 { 254 if ($header === null) { 255 throw new RecordNotFoundException(); 256 } 257 258 if ($edit && $header->canEdit()) { 259 $header->lock(); 260 261 return $header; 262 } 263 264 if ($header->canShow()) { 265 return $header; 266 } 267 268 throw new RecordAccessDeniedException(); 269 } 270 271 /** 272 * @param Individual|null $individual 273 * @param bool $edit 274 * @param bool $chart For some charts, we can show private records 275 * 276 * @return Individual 277 * @throws IndividualNotFoundException 278 * @throws IndividualAccessDeniedException 279 */ 280 public static function checkIndividualAccess(?Individual $individual, bool $edit = false, $chart = false): Individual 281 { 282 if ($individual === null) { 283 throw new IndividualNotFoundException(); 284 } 285 286 if ($edit && $individual->canEdit()) { 287 $individual->lock(); 288 289 return $individual; 290 } 291 292 if ($chart && $individual->tree()->getPreference('SHOW_PRIVATE_RELATIONSHIPS') === '1') { 293 return $individual; 294 } 295 296 if ($individual->canShow()) { 297 return $individual; 298 } 299 300 throw new IndividualAccessDeniedException(); 301 } 302 303 /** 304 * @param Media|null $media 305 * @param bool $edit 306 * 307 * @return Media 308 * @throws MediaNotFoundException 309 * @throws MediaAccessDeniedException 310 */ 311 public static function checkMediaAccess(?Media $media, bool $edit = false): Media 312 { 313 if ($media === null) { 314 throw new MediaNotFoundException(); 315 } 316 317 if ($edit && $media->canEdit()) { 318 $media->lock(); 319 320 return $media; 321 } 322 323 if ($media->canShow()) { 324 return $media; 325 } 326 327 throw new MediaAccessDeniedException(); 328 } 329 330 /** 331 * @param Note|null $note 332 * @param bool $edit 333 * 334 * @return Note 335 * @throws NoteNotFoundException 336 * @throws NoteAccessDeniedException 337 */ 338 public static function checkNoteAccess(?Note $note, bool $edit = false): Note 339 { 340 if ($note === null) { 341 throw new NoteNotFoundException(); 342 } 343 344 if ($edit && $note->canEdit()) { 345 $note->lock(); 346 347 return $note; 348 } 349 350 if ($note->canShow()) { 351 return $note; 352 } 353 354 throw new NoteAccessDeniedException(); 355 } 356 357 /** 358 * @param GedcomRecord|null $record 359 * @param bool $edit 360 * 361 * @return GedcomRecord 362 * @throws RecordNotFoundException 363 * @throws RecordAccessDeniedException 364 */ 365 public static function checkRecordAccess(?GedcomRecord $record, bool $edit = false): GedcomRecord 366 { 367 if ($record === null) { 368 throw new RecordNotFoundException(); 369 } 370 371 if ($edit && $record->canEdit()) { 372 $record->lock(); 373 374 return $record; 375 } 376 377 if ($record->canShow()) { 378 return $record; 379 } 380 381 throw new RecordAccessDeniedException(); 382 } 383 384 /** 385 * @param Repository|null $repository 386 * @param bool $edit 387 * 388 * @return Repository 389 * @throws RepositoryNotFoundException 390 * @throws RepositoryAccessDeniedException 391 */ 392 public static function checkRepositoryAccess(?Repository $repository, bool $edit = false): Repository 393 { 394 if ($repository === null) { 395 throw new RepositoryNotFoundException(); 396 } 397 398 if ($edit && $repository->canEdit()) { 399 $repository->lock(); 400 401 return $repository; 402 } 403 404 if ($repository->canShow()) { 405 return $repository; 406 } 407 408 throw new RepositoryAccessDeniedException(); 409 } 410 411 /** 412 * @param Source|null $source 413 * @param bool $edit 414 * 415 * @return Source 416 * @throws SourceNotFoundException 417 * @throws SourceAccessDeniedException 418 */ 419 public static function checkSourceAccess(?Source $source, bool $edit = false): Source 420 { 421 if ($source === null) { 422 throw new SourceNotFoundException(); 423 } 424 425 if ($edit && $source->canEdit()) { 426 $source->lock(); 427 428 return $source; 429 } 430 431 if ($source->canShow()) { 432 return $source; 433 } 434 435 throw new SourceAccessDeniedException(); 436 } 437 438 /* 439 * @param Submitter|null $submitter 440 * @param bool $edit 441 * 442 * @return Submitter 443 * @throws RecordNotFoundException 444 * @throws RecordAccessDeniedException 445 */ 446 public static function checkSubmitterAccess(?Submitter $submitter, bool $edit = false): Submitter 447 { 448 if ($submitter === null) { 449 throw new RecordNotFoundException(); 450 } 451 452 if ($edit && $submitter->canEdit()) { 453 $submitter->lock(); 454 455 return $submitter; 456 } 457 458 if ($submitter->canShow()) { 459 return $submitter; 460 } 461 462 throw new RecordAccessDeniedException(); 463 } 464 465 /* 466 * @param Submission|null $submission 467 * @param bool $edit 468 * 469 * @return Submission 470 * @throws RecordNotFoundException 471 * @throws RecordAccessDeniedException 472 */ 473 public static function checkSubmissionAccess(?Submission $submission, bool $edit = false): Submission 474 { 475 if ($submission === null) { 476 throw new RecordNotFoundException(); 477 } 478 479 if ($edit && $submission->canEdit()) { 480 $submission->lock(); 481 482 return $submission; 483 } 484 485 if ($submission->canShow()) { 486 return $submission; 487 } 488 489 throw new RecordAccessDeniedException(); 490 } 491 492 /** 493 * @return array<int,string> 494 */ 495 public static function accessLevelNames(): array 496 { 497 return [ 498 self::PRIV_PRIVATE => I18N::translate('Show to visitors'), 499 self::PRIV_USER => I18N::translate('Show to members'), 500 self::PRIV_NONE => I18N::translate('Show to managers'), 501 self::PRIV_HIDE => I18N::translate('Hide from everyone'), 502 ]; 503 } 504 505 /** 506 * @return array<string,string> 507 */ 508 public static function privacyRuleNames(): array 509 { 510 return [ 511 'none' => I18N::translate('Show to visitors'), 512 'privacy' => I18N::translate('Show to members'), 513 'confidential' => I18N::translate('Show to managers'), 514 'hidden' => I18N::translate('Hide from everyone'), 515 ]; 516 } 517} 518