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 void 229 * @throws FamilyNotFoundException 230 * @throws FamilyAccessDeniedException 231 */ 232 public static function checkFamilyAccess(Family $family = null, $edit = false): void 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 247 /** 248 * @param Individual|null $individual 249 * @param bool|null $edit 250 * 251 * @return void 252 * @throws IndividualNotFoundException 253 * @throws IndividualAccessDeniedException 254 */ 255 public static function checkIndividualAccess(Individual $individual = null, $edit = false): void 256 { 257 if ($individual === null) { 258 throw new IndividualNotFoundException(); 259 } 260 261 if (!$individual->canShow()) { 262 throw new IndividualAccessDeniedException(); 263 } 264 265 if ($edit && !$individual->canEdit()) { 266 throw new IndividualAccessDeniedException(); 267 } 268 } 269 270 /** 271 * @param Media|null $media 272 * @param bool|null $edit 273 * 274 * @return void 275 * @throws MediaNotFoundException 276 * @throws MediaAccessDeniedException 277 */ 278 public static function checkMediaAccess(Media $media = null, $edit = false): void 279 { 280 if ($media === null) { 281 throw new MediaNotFoundException(); 282 } 283 284 if (!$media->canShow()) { 285 throw new MediaAccessDeniedException(); 286 } 287 288 if ($edit && !$media->canEdit()) { 289 throw new MediaAccessDeniedException(); 290 } 291 } 292 293 /** 294 * @param Note|null $note 295 * @param bool|null $edit 296 * 297 * @return void 298 * @throws NoteNotFoundException 299 * @throws NoteAccessDeniedException 300 */ 301 public static function checkNoteAccess(Note $note = null, $edit = false): void 302 { 303 if ($note === null) { 304 throw new NoteNotFoundException(); 305 } 306 307 if (!$note->canShow()) { 308 throw new NoteAccessDeniedException(); 309 } 310 311 if ($edit && !$note->canEdit()) { 312 throw new NoteAccessDeniedException(); 313 } 314 } 315 316 /** 317 * @param GedcomRecord|null $record 318 * @param bool|null $edit 319 * 320 * @return void 321 * @throws RecordNotFoundException 322 * @throws RecordAccessDeniedException 323 */ 324 public static function checkRecordAccess(GedcomRecord $record = null, $edit = false): void 325 { 326 if ($record === null) { 327 throw new RecordNotFoundException(); 328 } 329 330 if (!$record->canShow()) { 331 throw new RecordAccessDeniedException(); 332 } 333 334 if ($edit && !$record->canEdit()) { 335 throw new RecordAccessDeniedException(); 336 } 337 } 338 339 /** 340 * @param Repository|null $repository 341 * @param bool|null $edit 342 * 343 * @return void 344 * @throws RepositoryNotFoundException 345 * @throws RepositoryAccessDeniedException 346 */ 347 public static function checkRepositoryAccess(Repository $repository = null, $edit = false): void 348 { 349 if ($repository === null) { 350 throw new RepositoryNotFoundException(); 351 } 352 353 if (!$repository->canShow()) { 354 throw new RepositoryAccessDeniedException(); 355 } 356 357 if ($edit && !$repository->canEdit()) { 358 throw new RepositoryAccessDeniedException(); 359 } 360 } 361 362 /** 363 * @param Source|null $source 364 * @param bool|null $edit 365 * 366 * @return void 367 * @throws SourceNotFoundException 368 * @throws SourceAccessDeniedException 369 */ 370 public static function checkSourceAccess(Source $source = null, $edit = false): void 371 { 372 if ($source === null) { 373 throw new SourceNotFoundException(); 374 } 375 376 if (!$source->canShow()) { 377 throw new SourceAccessDeniedException(); 378 } 379 380 if ($edit && !$source->canEdit()) { 381 throw new SourceAccessDeniedException(); 382 } 383 } 384} 385