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 return Session::get('wt_user'); 165 } 166 167 /** 168 * The authenticated user, from the current session. 169 * 170 * @return UserInterface 171 */ 172 public static function user(): UserInterface 173 { 174 return (new UserService())->find(self::id()) ?? new GuestUser(); 175 } 176 177 /** 178 * Login directly as an explicit user - for masquerading. 179 * 180 * @param UserInterface $user 181 * 182 * @return void 183 */ 184 public static function login(UserInterface $user): void 185 { 186 Session::regenerate(false); 187 Session::put('wt_user', $user->id()); 188 } 189 190 /** 191 * End the session for the current user. 192 * 193 * @return void 194 */ 195 public static function logout(): void 196 { 197 Session::regenerate(true); 198 } 199 200 /** 201 * @param ModuleInterface $module 202 * @param string $component 203 * @param Tree $tree 204 * @param UserInterface $user 205 * 206 * @return void 207 */ 208 public static function checkComponentAccess(ModuleInterface $module, string $component, Tree $tree, UserInterface $user): void 209 { 210 if ($module->accessLevel($tree, $component) < self::accessLevel($tree, $user)) { 211 throw new AccessDeniedHttpException(''); 212 } 213 } 214 215 /** 216 * @param Family|null $family 217 * @param bool|null $edit 218 * 219 * @return void 220 * @throws FamilyNotFoundException 221 * @throws FamilyAccessDeniedException 222 */ 223 public static function checkFamilyAccess(Family $family = null, $edit = false): void 224 { 225 if ($family === null) { 226 throw new FamilyNotFoundException(); 227 } 228 229 if (!$family->canShow()) { 230 throw new FamilyAccessDeniedException(); 231 } 232 233 if ($edit && !$family->canEdit()) { 234 throw new FamilyAccessDeniedException(); 235 } 236 } 237 238 /** 239 * @param Individual|null $individual 240 * @param bool|null $edit 241 * 242 * @return void 243 * @throws IndividualNotFoundException 244 * @throws IndividualAccessDeniedException 245 */ 246 public static function checkIndividualAccess(Individual $individual = null, $edit = false): void 247 { 248 if ($individual === null) { 249 throw new IndividualNotFoundException(); 250 } 251 252 if (!$individual->canShow()) { 253 throw new IndividualAccessDeniedException(); 254 } 255 256 if ($edit && !$individual->canEdit()) { 257 throw new IndividualAccessDeniedException(); 258 } 259 } 260 261 /** 262 * @param Media|null $media 263 * @param bool|null $edit 264 * 265 * @return void 266 * @throws MediaNotFoundException 267 * @throws MediaAccessDeniedException 268 */ 269 public static function checkMediaAccess(Media $media = null, $edit = false): void 270 { 271 if ($media === null) { 272 throw new MediaNotFoundException(); 273 } 274 275 if (!$media->canShow()) { 276 throw new MediaAccessDeniedException(); 277 } 278 279 if ($edit && !$media->canEdit()) { 280 throw new MediaAccessDeniedException(); 281 } 282 } 283 284 /** 285 * @param Note|null $note 286 * @param bool|null $edit 287 * 288 * @return void 289 * @throws NoteNotFoundException 290 * @throws NoteAccessDeniedException 291 */ 292 public static function checkNoteAccess(Note $note = null, $edit = false): void 293 { 294 if ($note === null) { 295 throw new NoteNotFoundException(); 296 } 297 298 if (!$note->canShow()) { 299 throw new NoteAccessDeniedException(); 300 } 301 302 if ($edit && !$note->canEdit()) { 303 throw new NoteAccessDeniedException(); 304 } 305 } 306 307 /** 308 * @param GedcomRecord|null $record 309 * @param bool|null $edit 310 * 311 * @return void 312 * @throws RecordNotFoundException 313 * @throws RecordAccessDeniedException 314 */ 315 public static function checkRecordAccess(GedcomRecord $record = null, $edit = false): void 316 { 317 if ($record === null) { 318 throw new RecordNotFoundException(); 319 } 320 321 if (!$record->canShow()) { 322 throw new RecordAccessDeniedException(); 323 } 324 325 if ($edit && !$record->canEdit()) { 326 throw new RecordAccessDeniedException(); 327 } 328 } 329 330 /** 331 * @param Repository|null $repository 332 * @param bool|null $edit 333 * 334 * @return void 335 * @throws RepositoryNotFoundException 336 * @throws RepositoryAccessDeniedException 337 */ 338 public static function checkRepositoryAccess(Repository $repository = null, $edit = false): void 339 { 340 if ($repository === null) { 341 throw new RepositoryNotFoundException(); 342 } 343 344 if (!$repository->canShow()) { 345 throw new RepositoryAccessDeniedException(); 346 } 347 348 if ($edit && !$repository->canEdit()) { 349 throw new RepositoryAccessDeniedException(); 350 } 351 } 352 353 /** 354 * @param Source|null $source 355 * @param bool|null $edit 356 * 357 * @return void 358 * @throws SourceNotFoundException 359 * @throws SourceAccessDeniedException 360 */ 361 public static function checkSourceAccess(Source $source = null, $edit = false): void 362 { 363 if ($source === null) { 364 throw new SourceNotFoundException(); 365 } 366 367 if (!$source->canShow()) { 368 throw new SourceAccessDeniedException(); 369 } 370 371 if ($edit && !$source->canEdit()) { 372 throw new SourceAccessDeniedException(); 373 } 374 } 375} 376