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) 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() 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) 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() 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) 209 { 210 if ($module->accessLevel($tree, $component) < Auth::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) 224 { 225 if ($family === null) { 226 throw new FamilyNotFoundException(); 227 } 228 229 if (!$family->canShow() || $edit && (!$family->canEdit() || $family->isPendingDeletion())) { 230 throw new FamilyAccessDeniedException(); 231 } 232 } 233 234 /** 235 * @param Individual|null $individual 236 * @param bool|null $edit 237 * 238 * @return void 239 * @throws IndividualNotFoundException 240 * @throws IndividualAccessDeniedException 241 */ 242 public static function checkIndividualAccess(Individual $individual = null, $edit = false) 243 { 244 if ($individual === null) { 245 throw new IndividualNotFoundException(); 246 } 247 248 if (!$individual->canShow() || $edit && (!$individual->canEdit() || $individual->isPendingDeletion())) { 249 throw new IndividualAccessDeniedException(); 250 } 251 } 252 253 /** 254 * @param Media|null $media 255 * @param bool|null $edit 256 * 257 * @return void 258 * @throws MediaNotFoundException 259 * @throws MediaAccessDeniedException 260 */ 261 public static function checkMediaAccess(Media $media = null, $edit = false) 262 { 263 if ($media === null) { 264 throw new MediaNotFoundException(); 265 } 266 267 if (!$media->canShow() || $edit && (!$media->canEdit() || $media->isPendingDeletion())) { 268 throw new MediaAccessDeniedException(); 269 } 270 } 271 272 /** 273 * @param Note|null $note 274 * @param bool|null $edit 275 * 276 * @return void 277 * @throws NoteNotFoundException 278 * @throws NoteAccessDeniedException 279 */ 280 public static function checkNoteAccess(Note $note = null, $edit = false) 281 { 282 if ($note === null) { 283 throw new NoteNotFoundException(); 284 } 285 286 if (!$note->canShow() || $edit && (!$note->canEdit() || $note->isPendingDeletion())) { 287 throw new NoteAccessDeniedException(); 288 } 289 } 290 291 /** 292 * @param GedcomRecord|null $record 293 * @param bool|null $edit 294 * 295 * @return void 296 * @throws RecordNotFoundException 297 * @throws RecordAccessDeniedException 298 */ 299 public static function checkRecordAccess(GedcomRecord $record = null, $edit = false) 300 { 301 if ($record === null) { 302 throw new RecordNotFoundException(); 303 } 304 305 if (!$record->canShow() || $edit && (!$record->canEdit() || $record->isPendingDeletion())) { 306 throw new RecordAccessDeniedException(); 307 } 308 } 309 310 /** 311 * @param Repository|null $repository 312 * @param bool|null $edit 313 * 314 * @return void 315 * @throws RepositoryNotFoundException 316 * @throws RepositoryAccessDeniedException 317 */ 318 public static function checkRepositoryAccess(Repository $repository = null, $edit = false) 319 { 320 if ($repository === null) { 321 throw new RepositoryNotFoundException(); 322 } 323 324 if (!$repository->canShow() || $edit && (!$repository->canEdit() || $repository->isPendingDeletion())) { 325 throw new RepositoryAccessDeniedException(); 326 } 327 } 328 329 /** 330 * @param Source|null $source 331 * @param bool|null $edit 332 * 333 * @return void 334 * @throws SourceNotFoundException 335 * @throws SourceAccessDeniedException 336 */ 337 public static function checkSourceAccess(Source $source = null, $edit = false) 338 { 339 if ($source === null) { 340 throw new SourceNotFoundException(); 341 } 342 343 if (!$source->canShow() || $edit && (!$source->canEdit() || $source->isPendingDeletion())) { 344 throw new SourceAccessDeniedException(); 345 } 346 } 347} 348