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