| Total Complexity | 124 |
| Total Lines | 921 |
| Duplicated Lines | 0 % |
| Changes | 1 | ||
| Bugs | 0 | Features | 0 |
Complex classes like Session often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
While breaking up the class, it is a good idea to analyze how other classes use Session, and based on these observations, apply Extract Interface, too.
| 1 | <?php |
||
| 89 | class Session implements IUserSession, Emitter { |
||
| 90 | |||
| 91 | /** @var Manager|PublicEmitter $manager */ |
||
| 92 | private $manager; |
||
| 93 | |||
| 94 | /** @var ISession $session */ |
||
| 95 | private $session; |
||
| 96 | |||
| 97 | /** @var ITimeFactory */ |
||
| 98 | private $timeFactory; |
||
| 99 | |||
| 100 | /** @var IProvider */ |
||
| 101 | private $tokenProvider; |
||
| 102 | |||
| 103 | /** @var IConfig */ |
||
| 104 | private $config; |
||
| 105 | |||
| 106 | /** @var User $activeUser */ |
||
| 107 | protected $activeUser; |
||
| 108 | |||
| 109 | /** @var ISecureRandom */ |
||
| 110 | private $random; |
||
| 111 | |||
| 112 | /** @var ILockdownManager */ |
||
| 113 | private $lockdownManager; |
||
| 114 | |||
| 115 | /** @var ILogger */ |
||
| 116 | private $logger; |
||
| 117 | /** @var IEventDispatcher */ |
||
| 118 | private $dispatcher; |
||
| 119 | |||
| 120 | /** |
||
| 121 | * @param Manager $manager |
||
| 122 | * @param ISession $session |
||
| 123 | * @param ITimeFactory $timeFactory |
||
| 124 | * @param IProvider $tokenProvider |
||
| 125 | * @param IConfig $config |
||
| 126 | * @param ISecureRandom $random |
||
| 127 | * @param ILockdownManager $lockdownManager |
||
| 128 | * @param ILogger $logger |
||
| 129 | */ |
||
| 130 | public function __construct(Manager $manager, |
||
| 131 | ISession $session, |
||
| 132 | ITimeFactory $timeFactory, |
||
| 133 | $tokenProvider, |
||
| 134 | IConfig $config, |
||
| 135 | ISecureRandom $random, |
||
| 136 | ILockdownManager $lockdownManager, |
||
| 137 | ILogger $logger, |
||
| 138 | IEventDispatcher $dispatcher) { |
||
| 139 | $this->manager = $manager; |
||
| 140 | $this->session = $session; |
||
| 141 | $this->timeFactory = $timeFactory; |
||
| 142 | $this->tokenProvider = $tokenProvider; |
||
| 143 | $this->config = $config; |
||
| 144 | $this->random = $random; |
||
| 145 | $this->lockdownManager = $lockdownManager; |
||
| 146 | $this->logger = $logger; |
||
| 147 | $this->dispatcher = $dispatcher; |
||
| 148 | } |
||
| 149 | |||
| 150 | /** |
||
| 151 | * @param IProvider $provider |
||
| 152 | */ |
||
| 153 | public function setTokenProvider(IProvider $provider) { |
||
| 154 | $this->tokenProvider = $provider; |
||
| 155 | } |
||
| 156 | |||
| 157 | /** |
||
| 158 | * @param string $scope |
||
| 159 | * @param string $method |
||
| 160 | * @param callable $callback |
||
| 161 | */ |
||
| 162 | public function listen($scope, $method, callable $callback) { |
||
| 163 | $this->manager->listen($scope, $method, $callback); |
||
| 164 | } |
||
| 165 | |||
| 166 | /** |
||
| 167 | * @param string $scope optional |
||
| 168 | * @param string $method optional |
||
| 169 | * @param callable $callback optional |
||
| 170 | */ |
||
| 171 | public function removeListener($scope = null, $method = null, callable $callback = null) { |
||
| 172 | $this->manager->removeListener($scope, $method, $callback); |
||
| 173 | } |
||
| 174 | |||
| 175 | /** |
||
| 176 | * get the manager object |
||
| 177 | * |
||
| 178 | * @return Manager|PublicEmitter |
||
| 179 | */ |
||
| 180 | public function getManager() { |
||
| 181 | return $this->manager; |
||
| 182 | } |
||
| 183 | |||
| 184 | /** |
||
| 185 | * get the session object |
||
| 186 | * |
||
| 187 | * @return ISession |
||
| 188 | */ |
||
| 189 | public function getSession() { |
||
| 191 | } |
||
| 192 | |||
| 193 | /** |
||
| 194 | * set the session object |
||
| 195 | * |
||
| 196 | * @param ISession $session |
||
| 197 | */ |
||
| 198 | public function setSession(ISession $session) { |
||
| 199 | if ($this->session instanceof ISession) { |
||
|
|
|||
| 200 | $this->session->close(); |
||
| 201 | } |
||
| 202 | $this->session = $session; |
||
| 203 | $this->activeUser = null; |
||
| 204 | } |
||
| 205 | |||
| 206 | /** |
||
| 207 | * set the currently active user |
||
| 208 | * |
||
| 209 | * @param IUser|null $user |
||
| 210 | */ |
||
| 211 | public function setUser($user) { |
||
| 212 | if (is_null($user)) { |
||
| 213 | $this->session->remove('user_id'); |
||
| 214 | } else { |
||
| 215 | $this->session->set('user_id', $user->getUID()); |
||
| 216 | } |
||
| 217 | $this->activeUser = $user; |
||
| 218 | } |
||
| 219 | |||
| 220 | /** |
||
| 221 | * get the current active user |
||
| 222 | * |
||
| 223 | * @return IUser|null Current user, otherwise null |
||
| 224 | */ |
||
| 225 | public function getUser() { |
||
| 226 | // FIXME: This is a quick'n dirty work-around for the incognito mode as |
||
| 227 | // described at https://github.com/owncloud/core/pull/12912#issuecomment-67391155 |
||
| 228 | if (OC_User::isIncognitoMode()) { |
||
| 229 | return null; |
||
| 230 | } |
||
| 231 | if (is_null($this->activeUser)) { |
||
| 232 | $uid = $this->session->get('user_id'); |
||
| 233 | if (is_null($uid)) { |
||
| 234 | return null; |
||
| 235 | } |
||
| 236 | $this->activeUser = $this->manager->get($uid); |
||
| 237 | if (is_null($this->activeUser)) { |
||
| 238 | return null; |
||
| 239 | } |
||
| 240 | $this->validateSession(); |
||
| 241 | } |
||
| 242 | return $this->activeUser; |
||
| 243 | } |
||
| 244 | |||
| 245 | /** |
||
| 246 | * Validate whether the current session is valid |
||
| 247 | * |
||
| 248 | * - For token-authenticated clients, the token validity is checked |
||
| 249 | * - For browsers, the session token validity is checked |
||
| 250 | */ |
||
| 251 | protected function validateSession() { |
||
| 268 | } |
||
| 269 | } |
||
| 270 | |||
| 271 | /** |
||
| 272 | * Checks whether the user is logged in |
||
| 273 | * |
||
| 274 | * @return bool if logged in |
||
| 275 | */ |
||
| 276 | public function isLoggedIn() { |
||
| 277 | $user = $this->getUser(); |
||
| 278 | if (is_null($user)) { |
||
| 279 | return false; |
||
| 280 | } |
||
| 281 | |||
| 282 | return $user->isEnabled(); |
||
| 283 | } |
||
| 284 | |||
| 285 | /** |
||
| 286 | * set the login name |
||
| 287 | * |
||
| 288 | * @param string|null $loginName for the logged in user |
||
| 289 | */ |
||
| 290 | public function setLoginName($loginName) { |
||
| 295 | } |
||
| 296 | } |
||
| 297 | |||
| 298 | /** |
||
| 299 | * get the login name of the current user |
||
| 300 | * |
||
| 301 | * @return string |
||
| 302 | */ |
||
| 303 | public function getLoginName() { |
||
| 315 | } |
||
| 316 | |||
| 317 | /** |
||
| 318 | * @return mixed |
||
| 319 | */ |
||
| 320 | public function getImpersonatingUserID(): ?string { |
||
| 321 | |||
| 322 | return $this->session->get('oldUserId'); |
||
| 323 | |||
| 324 | } |
||
| 325 | |||
| 326 | public function setImpersonatingUserID(bool $useCurrentUser = true): void { |
||
| 327 | if ($useCurrentUser === false) { |
||
| 328 | $this->session->remove('oldUserId'); |
||
| 329 | return; |
||
| 330 | } |
||
| 331 | |||
| 332 | $currentUser = $this->getUser(); |
||
| 333 | |||
| 334 | if ($currentUser === null) { |
||
| 335 | throw new \OC\User\NoUserException(); |
||
| 336 | } |
||
| 337 | $this->session->set('oldUserId', $currentUser->getUID()); |
||
| 338 | |||
| 339 | } |
||
| 340 | /** |
||
| 341 | * set the token id |
||
| 342 | * |
||
| 343 | * @param int|null $token that was used to log in |
||
| 344 | */ |
||
| 345 | protected function setToken($token) { |
||
| 346 | if ($token === null) { |
||
| 347 | $this->session->remove('token-id'); |
||
| 348 | } else { |
||
| 349 | $this->session->set('token-id', $token); |
||
| 350 | } |
||
| 351 | } |
||
| 352 | |||
| 353 | /** |
||
| 354 | * try to log in with the provided credentials |
||
| 355 | * |
||
| 356 | * @param string $uid |
||
| 357 | * @param string $password |
||
| 358 | * @return boolean|null |
||
| 359 | * @throws LoginException |
||
| 360 | */ |
||
| 361 | public function login($uid, $password) { |
||
| 362 | $this->session->regenerateId(); |
||
| 363 | if ($this->validateToken($password, $uid)) { |
||
| 364 | return $this->loginWithToken($password); |
||
| 365 | } |
||
| 366 | return $this->loginWithPassword($uid, $password); |
||
| 367 | } |
||
| 368 | |||
| 369 | /** |
||
| 370 | * @param IUser $user |
||
| 371 | * @param array $loginDetails |
||
| 372 | * @param bool $regenerateSessionId |
||
| 373 | * @return true returns true if login successful or an exception otherwise |
||
| 374 | * @throws LoginException |
||
| 375 | */ |
||
| 376 | public function completeLogin(IUser $user, array $loginDetails, $regenerateSessionId = true) { |
||
| 377 | if (!$user->isEnabled()) { |
||
| 378 | // disabled users can not log in |
||
| 379 | // injecting l10n does not work - there is a circular dependency between session and \OCP\L10N\IFactory |
||
| 380 | $message = \OC::$server->getL10N('lib')->t('User disabled'); |
||
| 381 | throw new LoginException($message); |
||
| 382 | } |
||
| 383 | |||
| 384 | if($regenerateSessionId) { |
||
| 385 | $this->session->regenerateId(); |
||
| 386 | } |
||
| 387 | |||
| 388 | $this->setUser($user); |
||
| 389 | $this->setLoginName($loginDetails['loginName']); |
||
| 390 | |||
| 391 | $isToken = isset($loginDetails['token']) && $loginDetails['token'] instanceof IToken; |
||
| 392 | if ($isToken) { |
||
| 393 | $this->setToken($loginDetails['token']->getId()); |
||
| 394 | $this->lockdownManager->setToken($loginDetails['token']); |
||
| 395 | $firstTimeLogin = false; |
||
| 396 | } else { |
||
| 397 | $this->setToken(null); |
||
| 398 | $firstTimeLogin = $user->updateLastLoginTimestamp(); |
||
| 399 | } |
||
| 400 | |||
| 401 | $postLoginEvent = new OC\User\Events\PostLoginEvent( |
||
| 402 | $user, |
||
| 403 | $loginDetails['password'], |
||
| 404 | $isToken |
||
| 405 | ); |
||
| 406 | $this->dispatcher->dispatch(OC\User\Events\PostLoginEvent::class, $postLoginEvent); |
||
| 407 | |||
| 408 | $this->manager->emit('\OC\User', 'postLogin', [ |
||
| 409 | $user, |
||
| 410 | $loginDetails['password'], |
||
| 411 | $isToken, |
||
| 412 | ]); |
||
| 413 | if($this->isLoggedIn()) { |
||
| 414 | $this->prepareUserLogin($firstTimeLogin, $regenerateSessionId); |
||
| 415 | return true; |
||
| 416 | } |
||
| 417 | |||
| 418 | $message = \OC::$server->getL10N('lib')->t('Login canceled by app'); |
||
| 419 | throw new LoginException($message); |
||
| 420 | } |
||
| 421 | |||
| 422 | /** |
||
| 423 | * Tries to log in a client |
||
| 424 | * |
||
| 425 | * Checks token auth enforced |
||
| 426 | * Checks 2FA enabled |
||
| 427 | * |
||
| 428 | * @param string $user |
||
| 429 | * @param string $password |
||
| 430 | * @param IRequest $request |
||
| 431 | * @param OC\Security\Bruteforce\Throttler $throttler |
||
| 432 | * @throws LoginException |
||
| 433 | * @throws PasswordLoginForbiddenException |
||
| 434 | * @return boolean |
||
| 435 | */ |
||
| 436 | public function logClientIn($user, |
||
| 437 | $password, |
||
| 438 | IRequest $request, |
||
| 439 | OC\Security\Bruteforce\Throttler $throttler) { |
||
| 440 | $currentDelay = $throttler->sleepDelay($request->getRemoteAddress(), 'login'); |
||
| 441 | |||
| 442 | if ($this->manager instanceof PublicEmitter) { |
||
| 443 | $this->manager->emit('\OC\User', 'preLogin', array($user, $password)); |
||
| 444 | } |
||
| 445 | |||
| 446 | try { |
||
| 447 | $isTokenPassword = $this->isTokenPassword($password); |
||
| 448 | } catch (ExpiredTokenException $e) { |
||
| 449 | // Just return on an expired token no need to check further or record a failed login |
||
| 450 | return false; |
||
| 451 | } |
||
| 452 | |||
| 453 | if (!$isTokenPassword && $this->isTokenAuthEnforced()) { |
||
| 454 | throw new PasswordLoginForbiddenException(); |
||
| 455 | } |
||
| 456 | if (!$isTokenPassword && $this->isTwoFactorEnforced($user)) { |
||
| 457 | throw new PasswordLoginForbiddenException(); |
||
| 458 | } |
||
| 459 | |||
| 460 | // Try to login with this username and password |
||
| 461 | if (!$this->login($user, $password) ) { |
||
| 462 | |||
| 463 | // Failed, maybe the user used their email address |
||
| 464 | $users = $this->manager->getByEmail($user); |
||
| 465 | if (!(\count($users) === 1 && $this->login($users[0]->getUID(), $password))) { |
||
| 466 | |||
| 467 | $this->logger->warning('Login failed: \'' . $user . '\' (Remote IP: \'' . \OC::$server->getRequest()->getRemoteAddress() . '\')', ['app' => 'core']); |
||
| 468 | |||
| 469 | $throttler->registerAttempt('login', $request->getRemoteAddress(), ['user' => $user]); |
||
| 470 | if ($currentDelay === 0) { |
||
| 471 | $throttler->sleepDelay($request->getRemoteAddress(), 'login'); |
||
| 472 | } |
||
| 473 | return false; |
||
| 474 | } |
||
| 475 | } |
||
| 476 | |||
| 477 | if ($isTokenPassword) { |
||
| 478 | $this->session->set('app_password', $password); |
||
| 479 | } else if($this->supportsCookies($request)) { |
||
| 480 | // Password login, but cookies supported -> create (browser) session token |
||
| 481 | $this->createSessionToken($request, $this->getUser()->getUID(), $user, $password); |
||
| 482 | } |
||
| 483 | |||
| 484 | return true; |
||
| 485 | } |
||
| 486 | |||
| 487 | protected function supportsCookies(IRequest $request) { |
||
| 488 | if (!is_null($request->getCookie('cookie_test'))) { |
||
| 489 | return true; |
||
| 490 | } |
||
| 491 | setcookie('cookie_test', 'test', $this->timeFactory->getTime() + 3600); |
||
| 492 | return false; |
||
| 493 | } |
||
| 494 | |||
| 495 | private function isTokenAuthEnforced() { |
||
| 496 | return $this->config->getSystemValue('token_auth_enforced', false); |
||
| 497 | } |
||
| 498 | |||
| 499 | protected function isTwoFactorEnforced($username) { |
||
| 500 | Util::emitHook( |
||
| 501 | '\OCA\Files_Sharing\API\Server2Server', |
||
| 502 | 'preLoginNameUsedAsUserName', |
||
| 503 | array('uid' => &$username) |
||
| 504 | ); |
||
| 505 | $user = $this->manager->get($username); |
||
| 506 | if (is_null($user)) { |
||
| 507 | $users = $this->manager->getByEmail($username); |
||
| 508 | if (empty($users)) { |
||
| 509 | return false; |
||
| 510 | } |
||
| 511 | if (count($users) !== 1) { |
||
| 512 | return true; |
||
| 513 | } |
||
| 514 | $user = $users[0]; |
||
| 515 | } |
||
| 516 | // DI not possible due to cyclic dependencies :'-/ |
||
| 517 | return OC::$server->getTwoFactorAuthManager()->isTwoFactorAuthenticated($user); |
||
| 518 | } |
||
| 519 | |||
| 520 | /** |
||
| 521 | * Check if the given 'password' is actually a device token |
||
| 522 | * |
||
| 523 | * @param string $password |
||
| 524 | * @return boolean |
||
| 525 | * @throws ExpiredTokenException |
||
| 526 | */ |
||
| 527 | public function isTokenPassword($password) { |
||
| 528 | try { |
||
| 529 | $this->tokenProvider->getToken($password); |
||
| 530 | return true; |
||
| 531 | } catch (ExpiredTokenException $e) { |
||
| 532 | throw $e; |
||
| 533 | } catch (InvalidTokenException $ex) { |
||
| 534 | return false; |
||
| 535 | } |
||
| 536 | } |
||
| 537 | |||
| 538 | protected function prepareUserLogin($firstTimeLogin, $refreshCsrfToken = true) { |
||
| 539 | if ($refreshCsrfToken) { |
||
| 540 | // TODO: mock/inject/use non-static |
||
| 541 | // Refresh the token |
||
| 542 | \OC::$server->getCsrfTokenManager()->refreshToken(); |
||
| 543 | } |
||
| 544 | |||
| 545 | //we need to pass the user name, which may differ from login name |
||
| 546 | $user = $this->getUser()->getUID(); |
||
| 547 | OC_Util::setupFS($user); |
||
| 548 | |||
| 549 | if ($firstTimeLogin) { |
||
| 550 | // TODO: lock necessary? |
||
| 551 | //trigger creation of user home and /files folder |
||
| 552 | $userFolder = \OC::$server->getUserFolder($user); |
||
| 553 | |||
| 554 | try { |
||
| 555 | // copy skeleton |
||
| 556 | \OC_Util::copySkeleton($user, $userFolder); |
||
| 557 | } catch (NotPermittedException $ex) { |
||
| 558 | // read only uses |
||
| 559 | } |
||
| 560 | |||
| 561 | // trigger any other initialization |
||
| 562 | \OC::$server->getEventDispatcher()->dispatch(IUser::class . '::firstLogin', new GenericEvent($this->getUser())); |
||
| 563 | } |
||
| 564 | } |
||
| 565 | |||
| 566 | /** |
||
| 567 | * Tries to login the user with HTTP Basic Authentication |
||
| 568 | * |
||
| 569 | * @todo do not allow basic auth if the user is 2FA enforced |
||
| 570 | * @param IRequest $request |
||
| 571 | * @param OC\Security\Bruteforce\Throttler $throttler |
||
| 572 | * @return boolean if the login was successful |
||
| 573 | */ |
||
| 574 | public function tryBasicAuthLogin(IRequest $request, |
||
| 575 | OC\Security\Bruteforce\Throttler $throttler) { |
||
| 576 | if (!empty($request->server['PHP_AUTH_USER']) && !empty($request->server['PHP_AUTH_PW'])) { |
||
| 577 | try { |
||
| 578 | if ($this->logClientIn($request->server['PHP_AUTH_USER'], $request->server['PHP_AUTH_PW'], $request, $throttler)) { |
||
| 579 | /** |
||
| 580 | * Add DAV authenticated. This should in an ideal world not be |
||
| 581 | * necessary but the iOS App reads cookies from anywhere instead |
||
| 582 | * only the DAV endpoint. |
||
| 583 | * This makes sure that the cookies will be valid for the whole scope |
||
| 584 | * @see https://github.com/owncloud/core/issues/22893 |
||
| 585 | */ |
||
| 586 | $this->session->set( |
||
| 587 | Auth::DAV_AUTHENTICATED, $this->getUser()->getUID() |
||
| 588 | ); |
||
| 589 | |||
| 590 | // Set the last-password-confirm session to make the sudo mode work |
||
| 591 | $this->session->set('last-password-confirm', $this->timeFactory->getTime()); |
||
| 592 | |||
| 593 | return true; |
||
| 594 | } |
||
| 595 | } catch (PasswordLoginForbiddenException $ex) { |
||
| 596 | // Nothing to do |
||
| 597 | } |
||
| 598 | } |
||
| 599 | return false; |
||
| 600 | } |
||
| 601 | |||
| 602 | /** |
||
| 603 | * Log an user in via login name and password |
||
| 604 | * |
||
| 605 | * @param string $uid |
||
| 606 | * @param string $password |
||
| 607 | * @return boolean |
||
| 608 | * @throws LoginException if an app canceld the login process or the user is not enabled |
||
| 609 | */ |
||
| 610 | private function loginWithPassword($uid, $password) { |
||
| 611 | $user = $this->manager->checkPasswordNoLogging($uid, $password); |
||
| 612 | if ($user === false) { |
||
| 613 | // Password check failed |
||
| 614 | return false; |
||
| 615 | } |
||
| 616 | |||
| 617 | return $this->completeLogin($user, ['loginName' => $uid, 'password' => $password], false); |
||
| 618 | } |
||
| 619 | |||
| 620 | /** |
||
| 621 | * Log an user in with a given token (id) |
||
| 622 | * |
||
| 623 | * @param string $token |
||
| 624 | * @return boolean |
||
| 625 | * @throws LoginException if an app canceled the login process or the user is not enabled |
||
| 626 | */ |
||
| 627 | private function loginWithToken($token) { |
||
| 628 | try { |
||
| 629 | $dbToken = $this->tokenProvider->getToken($token); |
||
| 630 | } catch (InvalidTokenException $ex) { |
||
| 631 | return false; |
||
| 632 | } |
||
| 633 | $uid = $dbToken->getUID(); |
||
| 634 | |||
| 635 | // When logging in with token, the password must be decrypted first before passing to login hook |
||
| 636 | $password = ''; |
||
| 637 | try { |
||
| 638 | $password = $this->tokenProvider->getPassword($dbToken, $token); |
||
| 639 | } catch (PasswordlessTokenException $ex) { |
||
| 640 | // Ignore and use empty string instead |
||
| 641 | } |
||
| 642 | |||
| 643 | $this->manager->emit('\OC\User', 'preLogin', array($uid, $password)); |
||
| 644 | |||
| 645 | $user = $this->manager->get($uid); |
||
| 646 | if (is_null($user)) { |
||
| 647 | // user does not exist |
||
| 648 | return false; |
||
| 649 | } |
||
| 650 | |||
| 651 | return $this->completeLogin( |
||
| 652 | $user, |
||
| 653 | [ |
||
| 654 | 'loginName' => $dbToken->getLoginName(), |
||
| 655 | 'password' => $password, |
||
| 656 | 'token' => $dbToken |
||
| 657 | ], |
||
| 658 | false); |
||
| 659 | } |
||
| 660 | |||
| 661 | /** |
||
| 662 | * Create a new session token for the given user credentials |
||
| 663 | * |
||
| 664 | * @param IRequest $request |
||
| 665 | * @param string $uid user UID |
||
| 666 | * @param string $loginName login name |
||
| 667 | * @param string $password |
||
| 668 | * @param int $remember |
||
| 669 | * @return boolean |
||
| 670 | */ |
||
| 671 | public function createSessionToken(IRequest $request, $uid, $loginName, $password = null, $remember = IToken::DO_NOT_REMEMBER) { |
||
| 672 | if (is_null($this->manager->get($uid))) { |
||
| 673 | // User does not exist |
||
| 674 | return false; |
||
| 675 | } |
||
| 676 | $name = isset($request->server['HTTP_USER_AGENT']) ? $request->server['HTTP_USER_AGENT'] : 'unknown browser'; |
||
| 677 | try { |
||
| 678 | $sessionId = $this->session->getId(); |
||
| 679 | $pwd = $this->getPassword($password); |
||
| 680 | // Make sure the current sessionId has no leftover tokens |
||
| 681 | $this->tokenProvider->invalidateToken($sessionId); |
||
| 682 | $this->tokenProvider->generateToken($sessionId, $uid, $loginName, $pwd, $name, IToken::TEMPORARY_TOKEN, $remember); |
||
| 683 | return true; |
||
| 684 | } catch (SessionNotAvailableException $ex) { |
||
| 685 | // This can happen with OCC, where a memory session is used |
||
| 686 | // if a memory session is used, we shouldn't create a session token anyway |
||
| 687 | return false; |
||
| 688 | } |
||
| 689 | } |
||
| 690 | |||
| 691 | /** |
||
| 692 | * Checks if the given password is a token. |
||
| 693 | * If yes, the password is extracted from the token. |
||
| 694 | * If no, the same password is returned. |
||
| 695 | * |
||
| 696 | * @param string $password either the login password or a device token |
||
| 697 | * @return string|null the password or null if none was set in the token |
||
| 698 | */ |
||
| 699 | private function getPassword($password) { |
||
| 700 | if (is_null($password)) { |
||
| 701 | // This is surely no token ;-) |
||
| 702 | return null; |
||
| 703 | } |
||
| 704 | try { |
||
| 705 | $token = $this->tokenProvider->getToken($password); |
||
| 706 | try { |
||
| 707 | return $this->tokenProvider->getPassword($token, $password); |
||
| 708 | } catch (PasswordlessTokenException $ex) { |
||
| 709 | return null; |
||
| 710 | } |
||
| 711 | } catch (InvalidTokenException $ex) { |
||
| 712 | return $password; |
||
| 713 | } |
||
| 714 | } |
||
| 715 | |||
| 716 | /** |
||
| 717 | * @param IToken $dbToken |
||
| 718 | * @param string $token |
||
| 719 | * @return boolean |
||
| 720 | */ |
||
| 721 | private function checkTokenCredentials(IToken $dbToken, $token) { |
||
| 722 | // Check whether login credentials are still valid and the user was not disabled |
||
| 723 | // This check is performed each 5 minutes |
||
| 724 | $lastCheck = $dbToken->getLastCheck() ? : 0; |
||
| 725 | $now = $this->timeFactory->getTime(); |
||
| 726 | if ($lastCheck > ($now - 60 * 5)) { |
||
| 727 | // Checked performed recently, nothing to do now |
||
| 728 | return true; |
||
| 729 | } |
||
| 730 | |||
| 731 | try { |
||
| 732 | $pwd = $this->tokenProvider->getPassword($dbToken, $token); |
||
| 733 | } catch (InvalidTokenException $ex) { |
||
| 734 | // An invalid token password was used -> log user out |
||
| 735 | return false; |
||
| 736 | } catch (PasswordlessTokenException $ex) { |
||
| 737 | // Token has no password |
||
| 738 | |||
| 739 | if (!is_null($this->activeUser) && !$this->activeUser->isEnabled()) { |
||
| 740 | $this->tokenProvider->invalidateToken($token); |
||
| 741 | return false; |
||
| 742 | } |
||
| 743 | |||
| 744 | $dbToken->setLastCheck($now); |
||
| 745 | return true; |
||
| 746 | } |
||
| 747 | |||
| 748 | // Invalidate token if the user is no longer active |
||
| 749 | if (!is_null($this->activeUser) && !$this->activeUser->isEnabled()) { |
||
| 750 | $this->tokenProvider->invalidateToken($token); |
||
| 751 | return false; |
||
| 752 | } |
||
| 753 | |||
| 754 | // If the token password is no longer valid mark it as such |
||
| 755 | if ($this->manager->checkPassword($dbToken->getLoginName(), $pwd) === false) { |
||
| 756 | $this->tokenProvider->markPasswordInvalid($dbToken, $token); |
||
| 757 | // User is logged out |
||
| 758 | return false; |
||
| 759 | } |
||
| 760 | |||
| 761 | $dbToken->setLastCheck($now); |
||
| 762 | return true; |
||
| 763 | } |
||
| 764 | |||
| 765 | /** |
||
| 766 | * Check if the given token exists and performs password/user-enabled checks |
||
| 767 | * |
||
| 768 | * Invalidates the token if checks fail |
||
| 769 | * |
||
| 770 | * @param string $token |
||
| 771 | * @param string $user login name |
||
| 772 | * @return boolean |
||
| 773 | */ |
||
| 774 | private function validateToken($token, $user = null) { |
||
| 775 | try { |
||
| 776 | $dbToken = $this->tokenProvider->getToken($token); |
||
| 777 | } catch (InvalidTokenException $ex) { |
||
| 778 | return false; |
||
| 779 | } |
||
| 780 | |||
| 781 | // Check if login names match |
||
| 782 | if (!is_null($user) && $dbToken->getLoginName() !== $user) { |
||
| 783 | // TODO: this makes it imposssible to use different login names on browser and client |
||
| 784 | // e.g. login by e-mail '[email protected]' on browser for generating the token will not |
||
| 785 | // allow to use the client token with the login name 'user'. |
||
| 786 | return false; |
||
| 787 | } |
||
| 788 | |||
| 789 | if (!$this->checkTokenCredentials($dbToken, $token)) { |
||
| 790 | return false; |
||
| 791 | } |
||
| 792 | |||
| 793 | // Update token scope |
||
| 794 | $this->lockdownManager->setToken($dbToken); |
||
| 795 | |||
| 796 | $this->tokenProvider->updateTokenActivity($dbToken); |
||
| 797 | |||
| 798 | return true; |
||
| 799 | } |
||
| 800 | |||
| 801 | /** |
||
| 802 | * Tries to login the user with auth token header |
||
| 803 | * |
||
| 804 | * @param IRequest $request |
||
| 805 | * @todo check remember me cookie |
||
| 806 | * @return boolean |
||
| 807 | */ |
||
| 808 | public function tryTokenLogin(IRequest $request) { |
||
| 809 | $authHeader = $request->getHeader('Authorization'); |
||
| 810 | if (strpos($authHeader, 'Bearer ') === false) { |
||
| 811 | // No auth header, let's try session id |
||
| 812 | try { |
||
| 813 | $token = $this->session->getId(); |
||
| 814 | } catch (SessionNotAvailableException $ex) { |
||
| 815 | return false; |
||
| 816 | } |
||
| 817 | } else { |
||
| 818 | $token = substr($authHeader, 7); |
||
| 819 | } |
||
| 820 | |||
| 821 | if (!$this->loginWithToken($token)) { |
||
| 822 | return false; |
||
| 823 | } |
||
| 824 | if(!$this->validateToken($token)) { |
||
| 825 | return false; |
||
| 826 | } |
||
| 827 | |||
| 828 | // Set the session variable so we know this is an app password |
||
| 829 | $this->session->set('app_password', $token); |
||
| 830 | |||
| 831 | return true; |
||
| 832 | } |
||
| 833 | |||
| 834 | /** |
||
| 835 | * perform login using the magic cookie (remember login) |
||
| 836 | * |
||
| 837 | * @param string $uid the username |
||
| 838 | * @param string $currentToken |
||
| 839 | * @param string $oldSessionId |
||
| 840 | * @return bool |
||
| 841 | */ |
||
| 842 | public function loginWithCookie($uid, $currentToken, $oldSessionId) { |
||
| 889 | } |
||
| 890 | |||
| 891 | /** |
||
| 892 | * @param IUser $user |
||
| 893 | */ |
||
| 894 | public function createRememberMeToken(IUser $user) { |
||
| 898 | } |
||
| 899 | |||
| 900 | /** |
||
| 901 | * logout the user from the session |
||
| 902 | */ |
||
| 903 | public function logout() { |
||
| 919 | } |
||
| 920 | |||
| 921 | /** |
||
| 922 | * Set cookie value to use in next page load |
||
| 923 | * |
||
| 924 | * @param string $username username to be set |
||
| 925 | * @param string $token |
||
| 926 | */ |
||
| 927 | public function setMagicInCookie($username, $token) { |
||
| 928 | $secureCookie = OC::$server->getRequest()->getServerProtocol() === 'https'; |
||
| 929 | $webRoot = \OC::$WEBROOT; |
||
| 930 | if ($webRoot === '') { |
||
| 931 | $webRoot = '/'; |
||
| 932 | } |
||
| 933 | |||
| 934 | $maxAge = $this->config->getSystemValue('remember_login_cookie_lifetime', 60 * 60 * 24 * 15); |
||
| 935 | \OC\Http\CookieHelper::setCookie( |
||
| 936 | 'nc_username', |
||
| 937 | $username, |
||
| 938 | $maxAge, |
||
| 939 | $webRoot, |
||
| 940 | '', |
||
| 941 | $secureCookie, |
||
| 942 | true, |
||
| 943 | \OC\Http\CookieHelper::SAMESITE_LAX |
||
| 944 | ); |
||
| 945 | \OC\Http\CookieHelper::setCookie( |
||
| 946 | 'nc_token', |
||
| 947 | $token, |
||
| 948 | $maxAge, |
||
| 949 | $webRoot, |
||
| 950 | '', |
||
| 951 | $secureCookie, |
||
| 952 | true, |
||
| 953 | \OC\Http\CookieHelper::SAMESITE_LAX |
||
| 954 | ); |
||
| 955 | try { |
||
| 956 | \OC\Http\CookieHelper::setCookie( |
||
| 957 | 'nc_session_id', |
||
| 958 | $this->session->getId(), |
||
| 959 | $maxAge, |
||
| 960 | $webRoot, |
||
| 961 | '', |
||
| 962 | $secureCookie, |
||
| 963 | true, |
||
| 964 | \OC\Http\CookieHelper::SAMESITE_LAX |
||
| 965 | ); |
||
| 966 | } catch (SessionNotAvailableException $ex) { |
||
| 967 | // ignore |
||
| 968 | } |
||
| 969 | } |
||
| 970 | |||
| 971 | /** |
||
| 972 | * Remove cookie for "remember username" |
||
| 973 | */ |
||
| 974 | public function unsetMagicInCookie() { |
||
| 975 | //TODO: DI for cookies and IRequest |
||
| 976 | $secureCookie = OC::$server->getRequest()->getServerProtocol() === 'https'; |
||
| 977 | |||
| 978 | unset($_COOKIE['nc_username']); //TODO: DI |
||
| 979 | unset($_COOKIE['nc_token']); |
||
| 980 | unset($_COOKIE['nc_session_id']); |
||
| 981 | setcookie('nc_username', '', $this->timeFactory->getTime() - 3600, OC::$WEBROOT, '', $secureCookie, true); |
||
| 982 | setcookie('nc_token', '', $this->timeFactory->getTime() - 3600, OC::$WEBROOT, '', $secureCookie, true); |
||
| 983 | setcookie('nc_session_id', '', $this->timeFactory->getTime() - 3600, OC::$WEBROOT, '', $secureCookie, true); |
||
| 984 | // old cookies might be stored under /webroot/ instead of /webroot |
||
| 985 | // and Firefox doesn't like it! |
||
| 986 | setcookie('nc_username', '', $this->timeFactory->getTime() - 3600, OC::$WEBROOT . '/', '', $secureCookie, true); |
||
| 987 | setcookie('nc_token', '', $this->timeFactory->getTime() - 3600, OC::$WEBROOT . '/', '', $secureCookie, true); |
||
| 988 | setcookie('nc_session_id', '', $this->timeFactory->getTime() - 3600, OC::$WEBROOT . '/', '', $secureCookie, true); |
||
| 989 | } |
||
| 990 | |||
| 991 | /** |
||
| 992 | * Update password of the browser session token if there is one |
||
| 993 | * |
||
| 994 | * @param string $password |
||
| 995 | */ |
||
| 996 | public function updateSessionTokenPassword($password) { |
||
| 1004 | // Nothing to do |
||
| 1005 | } |
||
| 1006 | } |
||
| 1007 | |||
| 1008 | public function updateTokens(string $uid, string $password) { |
||
| 1009 | $this->tokenProvider->updatePasswords($uid, $password); |
||
| 1010 | } |
||
| 1011 | |||
| 1012 | |||
| 1013 | } |
||
| 1014 |