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\IndividualAccessDeniedException; 26use Fisharebest\Webtrees\Exceptions\IndividualNotFoundException; 27use Fisharebest\Webtrees\Exceptions\MediaAccessDeniedException; 28use Fisharebest\Webtrees\Exceptions\MediaNotFoundException; 29use Fisharebest\Webtrees\Exceptions\NoteAccessDeniedException; 30use Fisharebest\Webtrees\Exceptions\NoteNotFoundException; 31use Fisharebest\Webtrees\Exceptions\RecordAccessDeniedException; 32use Fisharebest\Webtrees\Exceptions\RecordNotFoundException; 33use Fisharebest\Webtrees\Exceptions\RepositoryAccessDeniedException; 34use Fisharebest\Webtrees\Exceptions\RepositoryNotFoundException; 35use Fisharebest\Webtrees\Exceptions\SourceAccessDeniedException; 36use Fisharebest\Webtrees\Exceptions\SourceNotFoundException; 37use Fisharebest\Webtrees\Module\ModuleInterface; 38use Fisharebest\Webtrees\Services\UserService; 39use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException; 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('canadmin') === '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, 'canedit') === 'admin'; 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, 'canedit') === 'accept'; 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, 'canedit') === '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, 'canedit') === '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 AccessDeniedHttpException('Access denied'); 221 } 222 } 223 224 /** 225 * @param Family|null $family 226 * @param bool|null $edit 227 * 228 * @return Family 229 * @throws FamilyNotFoundException 230 * @throws FamilyAccessDeniedException 231 */ 232 public static function checkFamilyAccess(Family $family = null, $edit = false): Family 233 { 234 if ($family === null) { 235 throw new FamilyNotFoundException(); 236 } 237 238 if (!$family->canShow()) { 239 throw new FamilyAccessDeniedException(); 240 } 241 242 if ($edit && !$family->canEdit()) { 243 throw new FamilyAccessDeniedException(); 244 } 245 246 return $family; 247 } 248 249 /** 250 * @param Individual|null $individual 251 * @param bool|null $edit 252 * 253 * @return Individual 254 * @throws IndividualNotFoundException 255 * @throws IndividualAccessDeniedException 256 */ 257 public static function checkIndividualAccess(Individual $individual = null, $edit = false): Individual 258 { 259 if ($individual === null) { 260 throw new IndividualNotFoundException(); 261 } 262 263 if (!$individual->canShow()) { 264 throw new IndividualAccessDeniedException(); 265 } 266 267 if ($edit && !$individual->canEdit()) { 268 throw new IndividualAccessDeniedException(); 269 } 270 271 return $individual; 272 } 273 274 /** 275 * @param Media|null $media 276 * @param bool|null $edit 277 * 278 * @return Media 279 * @throws MediaNotFoundException 280 * @throws MediaAccessDeniedException 281 */ 282 public static function checkMediaAccess(Media $media = null, $edit = false): Media 283 { 284 if ($media === null) { 285 throw new MediaNotFoundException(); 286 } 287 288 if (!$media->canShow()) { 289 throw new MediaAccessDeniedException(); 290 } 291 292 if ($edit && !$media->canEdit()) { 293 throw new MediaAccessDeniedException(); 294 } 295 296 return $media; 297 } 298 299 /** 300 * @param Note|null $note 301 * @param bool|null $edit 302 * 303 * @return Note 304 * @throws NoteNotFoundException 305 * @throws NoteAccessDeniedException 306 */ 307 public static function checkNoteAccess(Note $note = null, $edit = false): Note 308 { 309 if ($note === null) { 310 throw new NoteNotFoundException(); 311 } 312 313 if (!$note->canShow()) { 314 throw new NoteAccessDeniedException(); 315 } 316 317 if ($edit && !$note->canEdit()) { 318 throw new NoteAccessDeniedException(); 319 } 320 321 return $note; 322 } 323 324 /** 325 * @param GedcomRecord|null $record 326 * @param bool|null $edit 327 * 328 * @return GedcomRecord 329 * @throws RecordNotFoundException 330 * @throws RecordAccessDeniedException 331 */ 332 public static function checkRecordAccess(GedcomRecord $record = null, $edit = false): GedcomRecord 333 { 334 if ($record === null) { 335 throw new RecordNotFoundException(); 336 } 337 338 if (!$record->canShow()) { 339 throw new RecordAccessDeniedException(); 340 } 341 342 if ($edit && !$record->canEdit()) { 343 throw new RecordAccessDeniedException(); 344 } 345 346 return $record; 347 } 348 349 /** 350 * @param Repository|null $repository 351 * @param bool|null $edit 352 * 353 * @return Repository 354 * @throws RepositoryNotFoundException 355 * @throws RepositoryAccessDeniedException 356 */ 357 public static function checkRepositoryAccess(Repository $repository = null, $edit = false): Repository 358 { 359 if ($repository === null) { 360 throw new RepositoryNotFoundException(); 361 } 362 363 if (!$repository->canShow()) { 364 throw new RepositoryAccessDeniedException(); 365 } 366 367 if ($edit && !$repository->canEdit()) { 368 throw new RepositoryAccessDeniedException(); 369 } 370 371 return $repository; 372 } 373 374 /** 375 * @param Source|null $source 376 * @param bool|null $edit 377 * 378 * @return Source 379 * @throws SourceNotFoundException 380 * @throws SourceAccessDeniedException 381 */ 382 public static function checkSourceAccess(Source $source = null, $edit = false): Source 383 { 384 if ($source === null) { 385 throw new SourceNotFoundException(); 386 } 387 388 if (!$source->canShow()) { 389 throw new SourceAccessDeniedException(); 390 } 391 392 if ($edit && !$source->canEdit()) { 393 throw new SourceAccessDeniedException(); 394 } 395 396 return $source; 397 } 398} 399