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 */ 17declare(strict_types=1); 18 19namespace Fisharebest\Webtrees; 20 21use Fisharebest\Webtrees\Contracts\UserInterface; 22use Fisharebest\Webtrees\Exceptions\FamilyAccessDeniedException; 23use Fisharebest\Webtrees\Exceptions\FamilyNotFoundException; 24use Fisharebest\Webtrees\Exceptions\IndividualAccessDeniedException; 25use Fisharebest\Webtrees\Exceptions\IndividualNotFoundException; 26use Fisharebest\Webtrees\Exceptions\MediaAccessDeniedException; 27use Fisharebest\Webtrees\Exceptions\MediaNotFoundException; 28use Fisharebest\Webtrees\Exceptions\NoteAccessDeniedException; 29use Fisharebest\Webtrees\Exceptions\NoteNotFoundException; 30use Fisharebest\Webtrees\Exceptions\RecordAccessDeniedException; 31use Fisharebest\Webtrees\Exceptions\RecordNotFoundException; 32use Fisharebest\Webtrees\Exceptions\RepositoryAccessDeniedException; 33use Fisharebest\Webtrees\Exceptions\RepositoryNotFoundException; 34use Fisharebest\Webtrees\Exceptions\SourceAccessDeniedException; 35use Fisharebest\Webtrees\Exceptions\SourceNotFoundException; 36use Fisharebest\Webtrees\Module\ModuleInterface; 37use Fisharebest\Webtrees\Services\UserService; 38use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException; 39 40/** 41 * Authentication. 42 */ 43class Auth 44{ 45 // Privacy constants 46 public const PRIV_PRIVATE = 2; // Allows visitors to view the item 47 public const PRIV_USER = 1; // Allows members to access the item 48 public const PRIV_NONE = 0; // Allows managers to access the item 49 public const PRIV_HIDE = -1; // Hide the item to all users 50 51 /** 52 * Are we currently logged in? 53 * 54 * @return bool 55 */ 56 public static function check(): bool 57 { 58 return self::id() !== null; 59 } 60 61 /** 62 * Is the specified/current user an administrator? 63 * 64 * @param UserInterface|null $user 65 * 66 * @return bool 67 */ 68 public static function isAdmin(UserInterface $user = null): bool 69 { 70 $user = $user ?? self::user(); 71 72 return $user->getPreference('canadmin') === '1'; 73 } 74 75 /** 76 * Is the specified/current user a manager of a tree? 77 * 78 * @param Tree $tree 79 * @param UserInterface|null $user 80 * 81 * @return bool 82 */ 83 public static function isManager(Tree $tree, UserInterface $user = null): bool 84 { 85 $user = $user ?? self::user(); 86 87 return self::isAdmin($user) || $tree->getUserPreference($user, 'canedit') === 'admin'; 88 } 89 90 /** 91 * Is the specified/current user a moderator of a tree? 92 * 93 * @param Tree $tree 94 * @param UserInterface|null $user 95 * 96 * @return bool 97 */ 98 public static function isModerator(Tree $tree, UserInterface $user = null): bool 99 { 100 $user = $user ?? self::user(); 101 102 return self::isManager($tree, $user) || $tree->getUserPreference($user, 'canedit') === 'accept'; 103 } 104 105 /** 106 * Is the specified/current user an editor of a tree? 107 * 108 * @param Tree $tree 109 * @param UserInterface|null $user 110 * 111 * @return bool 112 */ 113 public static function isEditor(Tree $tree, UserInterface $user = null): bool 114 { 115 $user = $user ?? self::user(); 116 117 return self::isModerator($tree, $user) || $tree->getUserPreference($user, 'canedit') === 'edit'; 118 } 119 120 /** 121 * Is the specified/current user a member of a tree? 122 * 123 * @param Tree $tree 124 * @param UserInterface|null $user 125 * 126 * @return bool 127 */ 128 public static function isMember(Tree $tree, UserInterface $user = null): bool 129 { 130 $user = $user ?? self::user(); 131 132 return self::isEditor($tree, $user) || $tree->getUserPreference($user, 'canedit') === 'access'; 133 } 134 135 /** 136 * What is the specified/current user's access level within a tree? 137 * 138 * @param Tree $tree 139 * @param UserInterface|null $user 140 * 141 * @return int 142 */ 143 public static function accessLevel(Tree $tree, UserInterface $user = null): int 144 { 145 $user = $user ?? self::user(); 146 147 if (self::isManager($tree, $user)) { 148 return self::PRIV_NONE; 149 } 150 151 if (self::isMember($tree, $user)) { 152 return self::PRIV_USER; 153 } 154 155 return self::PRIV_PRIVATE; 156 } 157 158 /** 159 * The ID of the authenticated user, from the current session. 160 * 161 * @return int|null 162 */ 163 public static function id(): ?int 164 { 165 $id = Session::get('wt_user'); 166 167 if ($id !== null) { 168 // In webtrees 1.x, the ID may have been a string. 169 $id = (int) $id; 170 } 171 172 return $id; 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 (new UserService())->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 $component 211 * @param Tree $tree 212 * @param UserInterface $user 213 * 214 * @return void 215 */ 216 public static function checkComponentAccess(ModuleInterface $module, string $component, Tree $tree, UserInterface $user): void 217 { 218 if ($module->accessLevel($tree, $component) < self::accessLevel($tree, $user)) { 219 throw new AccessDeniedHttpException('Access denied'); 220 } 221 } 222 223 /** 224 * @param Family|null $family 225 * @param bool|null $edit 226 * 227 * @return void 228 * @throws FamilyNotFoundException 229 * @throws FamilyAccessDeniedException 230 */ 231 public static function checkFamilyAccess(Family $family = null, $edit = false): void 232 { 233 if ($family === null) { 234 throw new FamilyNotFoundException(); 235 } 236 237 if (!$family->canShow()) { 238 throw new FamilyAccessDeniedException(); 239 } 240 241 if ($edit && !$family->canEdit()) { 242 throw new FamilyAccessDeniedException(); 243 } 244 } 245 246 /** 247 * @param Individual|null $individual 248 * @param bool|null $edit 249 * 250 * @return void 251 * @throws IndividualNotFoundException 252 * @throws IndividualAccessDeniedException 253 */ 254 public static function checkIndividualAccess(Individual $individual = null, $edit = false): void 255 { 256 if ($individual === null) { 257 throw new IndividualNotFoundException(); 258 } 259 260 if (!$individual->canShow()) { 261 throw new IndividualAccessDeniedException(); 262 } 263 264 if ($edit && !$individual->canEdit()) { 265 throw new IndividualAccessDeniedException(); 266 } 267 } 268 269 /** 270 * @param Media|null $media 271 * @param bool|null $edit 272 * 273 * @return void 274 * @throws MediaNotFoundException 275 * @throws MediaAccessDeniedException 276 */ 277 public static function checkMediaAccess(Media $media = null, $edit = false): void 278 { 279 if ($media === null) { 280 throw new MediaNotFoundException(); 281 } 282 283 if (!$media->canShow()) { 284 throw new MediaAccessDeniedException(); 285 } 286 287 if ($edit && !$media->canEdit()) { 288 throw new MediaAccessDeniedException(); 289 } 290 } 291 292 /** 293 * @param Note|null $note 294 * @param bool|null $edit 295 * 296 * @return void 297 * @throws NoteNotFoundException 298 * @throws NoteAccessDeniedException 299 */ 300 public static function checkNoteAccess(Note $note = null, $edit = false): void 301 { 302 if ($note === null) { 303 throw new NoteNotFoundException(); 304 } 305 306 if (!$note->canShow()) { 307 throw new NoteAccessDeniedException(); 308 } 309 310 if ($edit && !$note->canEdit()) { 311 throw new NoteAccessDeniedException(); 312 } 313 } 314 315 /** 316 * @param GedcomRecord|null $record 317 * @param bool|null $edit 318 * 319 * @return void 320 * @throws RecordNotFoundException 321 * @throws RecordAccessDeniedException 322 */ 323 public static function checkRecordAccess(GedcomRecord $record = null, $edit = false): void 324 { 325 if ($record === null) { 326 throw new RecordNotFoundException(); 327 } 328 329 if (!$record->canShow()) { 330 throw new RecordAccessDeniedException(); 331 } 332 333 if ($edit && !$record->canEdit()) { 334 throw new RecordAccessDeniedException(); 335 } 336 } 337 338 /** 339 * @param Repository|null $repository 340 * @param bool|null $edit 341 * 342 * @return void 343 * @throws RepositoryNotFoundException 344 * @throws RepositoryAccessDeniedException 345 */ 346 public static function checkRepositoryAccess(Repository $repository = null, $edit = false): void 347 { 348 if ($repository === null) { 349 throw new RepositoryNotFoundException(); 350 } 351 352 if (!$repository->canShow()) { 353 throw new RepositoryAccessDeniedException(); 354 } 355 356 if ($edit && !$repository->canEdit()) { 357 throw new RepositoryAccessDeniedException(); 358 } 359 } 360 361 /** 362 * @param Source|null $source 363 * @param bool|null $edit 364 * 365 * @return void 366 * @throws SourceNotFoundException 367 * @throws SourceAccessDeniedException 368 */ 369 public static function checkSourceAccess(Source $source = null, $edit = false): void 370 { 371 if ($source === null) { 372 throw new SourceNotFoundException(); 373 } 374 375 if (!$source->canShow()) { 376 throw new SourceAccessDeniedException(); 377 } 378 379 if ($edit && !$source->canEdit()) { 380 throw new SourceAccessDeniedException(); 381 } 382 } 383} 384