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 $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, bool $edit = false): Family 233 { 234 if ($family === null) { 235 throw new FamilyNotFoundException(); 236 } 237 238 if ($edit && $family->canEdit()) { 239 return $family; 240 } 241 242 if ($family->canShow()) { 243 return $family; 244 } 245 246 throw new FamilyAccessDeniedException(); 247 } 248 249 /** 250 * @param Individual|null $individual 251 * @param bool $edit 252 * @param bool $chart For some charts, we can show private records 253 * 254 * @return Individual 255 * @throws IndividualNotFoundException 256 * @throws IndividualAccessDeniedException 257 */ 258 public static function checkIndividualAccess(?Individual $individual, bool $edit = false, $chart = false): Individual 259 { 260 if ($individual === null) { 261 throw new IndividualNotFoundException(); 262 } 263 264 if ($edit && $individual->canEdit()) { 265 return $individual; 266 } 267 268 if ($chart && $individual->tree()->getPreference('SHOW_PRIVATE_RELATIONSHIPS') === '1') { 269 return $individual; 270 } 271 272 if ($individual->canShow()) { 273 return $individual; 274 } 275 276 throw new IndividualAccessDeniedException(); 277 } 278 279 /** 280 * @param Media|null $media 281 * @param bool $edit 282 * 283 * @return Media 284 * @throws MediaNotFoundException 285 * @throws MediaAccessDeniedException 286 */ 287 public static function checkMediaAccess(?Media $media, bool $edit = false): Media 288 { 289 if ($media === null) { 290 throw new MediaNotFoundException(); 291 } 292 293 if ($edit && $media->canEdit()) { 294 return $media; 295 } 296 297 if ($media->canShow()) { 298 return $media; 299 } 300 301 throw new MediaAccessDeniedException(); 302 } 303 304 /** 305 * @param Note|null $note 306 * @param bool $edit 307 * 308 * @return Note 309 * @throws NoteNotFoundException 310 * @throws NoteAccessDeniedException 311 */ 312 public static function checkNoteAccess(?Note $note, bool $edit = false): Note 313 { 314 if ($note === null) { 315 throw new NoteNotFoundException(); 316 } 317 318 if ($edit && $note->canEdit()) { 319 return $note; 320 } 321 322 if ($note->canShow()) { 323 return $note; 324 } 325 326 throw new NoteAccessDeniedException(); 327 } 328 329 /** 330 * @param GedcomRecord|null $record 331 * @param bool $edit 332 * 333 * @return GedcomRecord 334 * @throws RecordNotFoundException 335 * @throws RecordAccessDeniedException 336 */ 337 public static function checkRecordAccess(?GedcomRecord $record, bool $edit = false): GedcomRecord 338 { 339 if ($record === null) { 340 throw new RecordNotFoundException(); 341 } 342 343 if ($edit && $record->canEdit()) { 344 return $record; 345 } 346 347 if ($record->canShow()) { 348 return $record; 349 } 350 351 throw new RecordAccessDeniedException(); 352 } 353 354 /** 355 * @param Repository|null $repository 356 * @param bool $edit 357 * 358 * @return Repository 359 * @throws RepositoryNotFoundException 360 * @throws RepositoryAccessDeniedException 361 */ 362 public static function checkRepositoryAccess(?Repository $repository, bool $edit = false): Repository 363 { 364 if ($repository === null) { 365 throw new RepositoryNotFoundException(); 366 } 367 368 if ($edit && $repository->canEdit()) { 369 return $repository; 370 } 371 372 if ($repository->canShow()) { 373 return $repository; 374 } 375 376 throw new RepositoryAccessDeniedException(); 377 } 378 379 /** 380 * @param Source|null $source 381 * @param bool $edit 382 * 383 * @return Source 384 * @throws SourceNotFoundException 385 * @throws SourceAccessDeniedException 386 */ 387 public static function checkSourceAccess(?Source $source, bool $edit = false): Source 388 { 389 if ($source === null) { 390 throw new SourceNotFoundException(); 391 } 392 393 if ($edit && $source->canEdit()) { 394 return $source; 395 } 396 397 if ($source->canShow()) { 398 return $source; 399 } 400 401 throw new SourceAccessDeniedException(); 402 } 403 404 /* 405 * @param Submitter|null $submitter 406 * @param bool $edit 407 * 408 * @return Submitter 409 * @throws RecordNotFoundException 410 * @throws RecordAccessDeniedException 411 */ 412 public static function checkSubmitterAccess(?Submitter $submitter, bool $edit = false): Submitter 413 { 414 if ($submitter === null) { 415 throw new RecordNotFoundException(); 416 } 417 418 if ($edit && $submitter->canEdit()) { 419 return $submitter; 420 } 421 422 if ($submitter->canShow()) { 423 return $submitter; 424 } 425 426 throw new RecordAccessDeniedException(); 427 } 428} 429