1<?php 2/** 3 * webtrees: online genealogy 4 * Copyright (C) 2019 webtrees development team 5 * This program is free software: you can redistribute it and/or modify 6 * it under the terms of the GNU General Public License as published by 7 * the Free Software Foundation, either version 3 of the License, or 8 * (at your option) any later version. 9 * This program is distributed in the hope that it will be useful, 10 * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 * GNU General Public License for more details. 13 * You should have received a copy of the GNU General Public License 14 * along with this program. If not, see <http://www.gnu.org/licenses/>. 15 */ 16declare(strict_types=1); 17 18namespace Fisharebest\Webtrees; 19 20use Fisharebest\Webtrees\Contracts\UserInterface; 21use Fisharebest\Webtrees\Exceptions\FamilyAccessDeniedException; 22use Fisharebest\Webtrees\Exceptions\FamilyNotFoundException; 23use Fisharebest\Webtrees\Exceptions\IndividualAccessDeniedException; 24use Fisharebest\Webtrees\Exceptions\IndividualNotFoundException; 25use Fisharebest\Webtrees\Exceptions\MediaAccessDeniedException; 26use Fisharebest\Webtrees\Exceptions\MediaNotFoundException; 27use Fisharebest\Webtrees\Exceptions\NoteAccessDeniedException; 28use Fisharebest\Webtrees\Exceptions\NoteNotFoundException; 29use Fisharebest\Webtrees\Exceptions\RecordAccessDeniedException; 30use Fisharebest\Webtrees\Exceptions\RecordNotFoundException; 31use Fisharebest\Webtrees\Exceptions\RepositoryAccessDeniedException; 32use Fisharebest\Webtrees\Exceptions\RepositoryNotFoundException; 33use Fisharebest\Webtrees\Exceptions\SourceAccessDeniedException; 34use Fisharebest\Webtrees\Exceptions\SourceNotFoundException; 35use Fisharebest\Webtrees\Module\ModuleInterface; 36use Fisharebest\Webtrees\Services\UserService; 37use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException; 38 39/** 40 * Authentication. 41 */ 42class Auth 43{ 44 // Privacy constants 45 public const PRIV_PRIVATE = 2; // Allows visitors to view the item 46 public const PRIV_USER = 1; // Allows members to access the item 47 public const PRIV_NONE = 0; // Allows managers to access the item 48 public const PRIV_HIDE = -1; // Hide the item to all users 49 50 /** 51 * Are we currently logged in? 52 * 53 * @return bool 54 */ 55 public static function check(): bool 56 { 57 return self::id() !== null; 58 } 59 60 /** 61 * Is the specified/current user an administrator? 62 * 63 * @param UserInterface|null $user 64 * 65 * @return bool 66 */ 67 public static function isAdmin(UserInterface $user = null): bool 68 { 69 $user = $user ?? self::user(); 70 71 return $user->getPreference('canadmin') === '1'; 72 } 73 74 /** 75 * Is the specified/current user a manager of a tree? 76 * 77 * @param Tree $tree 78 * @param UserInterface|null $user 79 * 80 * @return bool 81 */ 82 public static function isManager(Tree $tree, UserInterface $user = null): bool 83 { 84 $user = $user ?? self::user(); 85 86 return self::isAdmin($user) || $tree->getUserPreference($user, 'canedit') === 'admin'; 87 } 88 89 /** 90 * Is the specified/current user a moderator of a tree? 91 * 92 * @param Tree $tree 93 * @param UserInterface|null $user 94 * 95 * @return bool 96 */ 97 public static function isModerator(Tree $tree, UserInterface $user = null): bool 98 { 99 $user = $user ?? self::user(); 100 101 return self::isManager($tree, $user) || $tree->getUserPreference($user, 'canedit') === 'accept'; 102 } 103 104 /** 105 * Is the specified/current user an editor of a tree? 106 * 107 * @param Tree $tree 108 * @param UserInterface|null $user 109 * 110 * @return bool 111 */ 112 public static function isEditor(Tree $tree, UserInterface $user = null): bool 113 { 114 $user = $user ?? self::user(); 115 116 return self::isModerator($tree, $user) || $tree->getUserPreference($user, 'canedit') === 'edit'; 117 } 118 119 /** 120 * Is the specified/current user a member of a tree? 121 * 122 * @param Tree $tree 123 * @param UserInterface|null $user 124 * 125 * @return bool 126 */ 127 public static function isMember(Tree $tree, UserInterface $user = null): bool 128 { 129 $user = $user ?? self::user(); 130 131 return self::isEditor($tree, $user) || $tree->getUserPreference($user, 'canedit') === 'access'; 132 } 133 134 /** 135 * What is the specified/current user's access level within a tree? 136 * 137 * @param Tree $tree 138 * @param UserInterface|null $user 139 * 140 * @return int 141 */ 142 public static function accessLevel(Tree $tree, UserInterface $user = null): int 143 { 144 $user = $user ?? self::user(); 145 146 if (self::isManager($tree, $user)) { 147 return self::PRIV_NONE; 148 } 149 150 if (self::isMember($tree, $user)) { 151 return self::PRIV_USER; 152 } 153 154 return self::PRIV_PRIVATE; 155 } 156 157 /** 158 * The ID of the authenticated user, from the current session. 159 * 160 * @return int|null 161 */ 162 public static function id(): ?int 163 { 164 $id = Session::get('wt_user'); 165 166 if ($id !== null) { 167 // In webtrees 1.x, the ID may have been a string. 168 $id = (int) $id; 169 } 170 171 return $id; 172 } 173 174 /** 175 * The authenticated user, from the current session. 176 * 177 * @return UserInterface 178 */ 179 public static function user(): UserInterface 180 { 181 return (new UserService())->find(self::id()) ?? new GuestUser(); 182 } 183 184 /** 185 * Login directly as an explicit user - for masquerading. 186 * 187 * @param UserInterface $user 188 * 189 * @return void 190 */ 191 public static function login(UserInterface $user): void 192 { 193 Session::regenerate(false); 194 Session::put('wt_user', $user->id()); 195 } 196 197 /** 198 * End the session for the current user. 199 * 200 * @return void 201 */ 202 public static function logout(): void 203 { 204 Session::regenerate(true); 205 } 206 207 /** 208 * @param ModuleInterface $module 209 * @param string $component 210 * @param Tree $tree 211 * @param UserInterface $user 212 * 213 * @return void 214 */ 215 public static function checkComponentAccess(ModuleInterface $module, string $component, Tree $tree, UserInterface $user): void 216 { 217 if ($module->accessLevel($tree, $component) < self::accessLevel($tree, $user)) { 218 throw new AccessDeniedHttpException(''); 219 } 220 } 221 222 /** 223 * @param Family|null $family 224 * @param bool|null $edit 225 * 226 * @return void 227 * @throws FamilyNotFoundException 228 * @throws FamilyAccessDeniedException 229 */ 230 public static function checkFamilyAccess(Family $family = null, $edit = false): void 231 { 232 if ($family === null) { 233 throw new FamilyNotFoundException(); 234 } 235 236 if (!$family->canShow()) { 237 throw new FamilyAccessDeniedException(); 238 } 239 240 if ($edit && !$family->canEdit()) { 241 throw new FamilyAccessDeniedException(); 242 } 243 } 244 245 /** 246 * @param Individual|null $individual 247 * @param bool|null $edit 248 * 249 * @return void 250 * @throws IndividualNotFoundException 251 * @throws IndividualAccessDeniedException 252 */ 253 public static function checkIndividualAccess(Individual $individual = null, $edit = false): void 254 { 255 if ($individual === null) { 256 throw new IndividualNotFoundException(); 257 } 258 259 if (!$individual->canShow()) { 260 throw new IndividualAccessDeniedException(); 261 } 262 263 if ($edit && !$individual->canEdit()) { 264 throw new IndividualAccessDeniedException(); 265 } 266 } 267 268 /** 269 * @param Media|null $media 270 * @param bool|null $edit 271 * 272 * @return void 273 * @throws MediaNotFoundException 274 * @throws MediaAccessDeniedException 275 */ 276 public static function checkMediaAccess(Media $media = null, $edit = false): void 277 { 278 if ($media === null) { 279 throw new MediaNotFoundException(); 280 } 281 282 if (!$media->canShow()) { 283 throw new MediaAccessDeniedException(); 284 } 285 286 if ($edit && !$media->canEdit()) { 287 throw new MediaAccessDeniedException(); 288 } 289 } 290 291 /** 292 * @param Note|null $note 293 * @param bool|null $edit 294 * 295 * @return void 296 * @throws NoteNotFoundException 297 * @throws NoteAccessDeniedException 298 */ 299 public static function checkNoteAccess(Note $note = null, $edit = false): void 300 { 301 if ($note === null) { 302 throw new NoteNotFoundException(); 303 } 304 305 if (!$note->canShow()) { 306 throw new NoteAccessDeniedException(); 307 } 308 309 if ($edit && !$note->canEdit()) { 310 throw new NoteAccessDeniedException(); 311 } 312 } 313 314 /** 315 * @param GedcomRecord|null $record 316 * @param bool|null $edit 317 * 318 * @return void 319 * @throws RecordNotFoundException 320 * @throws RecordAccessDeniedException 321 */ 322 public static function checkRecordAccess(GedcomRecord $record = null, $edit = false): void 323 { 324 if ($record === null) { 325 throw new RecordNotFoundException(); 326 } 327 328 if (!$record->canShow()) { 329 throw new RecordAccessDeniedException(); 330 } 331 332 if ($edit && !$record->canEdit()) { 333 throw new RecordAccessDeniedException(); 334 } 335 } 336 337 /** 338 * @param Repository|null $repository 339 * @param bool|null $edit 340 * 341 * @return void 342 * @throws RepositoryNotFoundException 343 * @throws RepositoryAccessDeniedException 344 */ 345 public static function checkRepositoryAccess(Repository $repository = null, $edit = false): void 346 { 347 if ($repository === null) { 348 throw new RepositoryNotFoundException(); 349 } 350 351 if (!$repository->canShow()) { 352 throw new RepositoryAccessDeniedException(); 353 } 354 355 if ($edit && !$repository->canEdit()) { 356 throw new RepositoryAccessDeniedException(); 357 } 358 } 359 360 /** 361 * @param Source|null $source 362 * @param bool|null $edit 363 * 364 * @return void 365 * @throws SourceNotFoundException 366 * @throws SourceAccessDeniedException 367 */ 368 public static function checkSourceAccess(Source $source = null, $edit = false): void 369 { 370 if ($source === null) { 371 throw new SourceNotFoundException(); 372 } 373 374 if (!$source->canShow()) { 375 throw new SourceAccessDeniedException(); 376 } 377 378 if ($edit && !$source->canEdit()) { 379 throw new SourceAccessDeniedException(); 380 } 381 } 382} 383