| @@ -86,832 +86,832 @@ | ||
| 86 | 86 | */ | 
| 87 | 87 |  class Session implements IUserSession, Emitter { | 
| 88 | 88 | |
| 89 | - /** @var Manager|PublicEmitter $manager */ | |
| 90 | - private $manager; | |
| 91 | - | |
| 92 | - /** @var ISession $session */ | |
| 93 | - private $session; | |
| 94 | - | |
| 95 | - /** @var ITimeFactory */ | |
| 96 | - private $timeFactory; | |
| 97 | - | |
| 98 | - /** @var IProvider */ | |
| 99 | - private $tokenProvider; | |
| 100 | - | |
| 101 | - /** @var IConfig */ | |
| 102 | - private $config; | |
| 103 | - | |
| 104 | - /** @var User $activeUser */ | |
| 105 | - protected $activeUser; | |
| 106 | - | |
| 107 | - /** @var ISecureRandom */ | |
| 108 | - private $random; | |
| 109 | - | |
| 110 | - /** @var ILockdownManager */ | |
| 111 | - private $lockdownManager; | |
| 112 | - | |
| 113 | - /** @var ILogger */ | |
| 114 | - private $logger; | |
| 115 | - | |
| 116 | - /** | |
| 117 | - * @param Manager $manager | |
| 118 | - * @param ISession $session | |
| 119 | - * @param ITimeFactory $timeFactory | |
| 120 | - * @param IProvider $tokenProvider | |
| 121 | - * @param IConfig $config | |
| 122 | - * @param ISecureRandom $random | |
| 123 | - * @param ILockdownManager $lockdownManager | |
| 124 | - * @param ILogger $logger | |
| 125 | - */ | |
| 126 | - public function __construct(Manager $manager, | |
| 127 | - ISession $session, | |
| 128 | - ITimeFactory $timeFactory, | |
| 129 | - $tokenProvider, | |
| 130 | - IConfig $config, | |
| 131 | - ISecureRandom $random, | |
| 132 | - ILockdownManager $lockdownManager, | |
| 133 | -								ILogger $logger) { | |
| 134 | - $this->manager = $manager; | |
| 135 | - $this->session = $session; | |
| 136 | - $this->timeFactory = $timeFactory; | |
| 137 | - $this->tokenProvider = $tokenProvider; | |
| 138 | - $this->config = $config; | |
| 139 | - $this->random = $random; | |
| 140 | - $this->lockdownManager = $lockdownManager; | |
| 141 | - $this->logger = $logger; | |
| 142 | - } | |
| 143 | - | |
| 144 | - /** | |
| 145 | - * @param IProvider $provider | |
| 146 | - */ | |
| 147 | -	public function setTokenProvider(IProvider $provider) { | |
| 148 | - $this->tokenProvider = $provider; | |
| 149 | - } | |
| 150 | - | |
| 151 | - /** | |
| 152 | - * @param string $scope | |
| 153 | - * @param string $method | |
| 154 | - * @param callable $callback | |
| 155 | - */ | |
| 156 | -	public function listen($scope, $method, callable $callback) { | |
| 157 | - $this->manager->listen($scope, $method, $callback); | |
| 158 | - } | |
| 159 | - | |
| 160 | - /** | |
| 161 | - * @param string $scope optional | |
| 162 | - * @param string $method optional | |
| 163 | - * @param callable $callback optional | |
| 164 | - */ | |
| 165 | -	public function removeListener($scope = null, $method = null, callable $callback = null) { | |
| 166 | - $this->manager->removeListener($scope, $method, $callback); | |
| 167 | - } | |
| 168 | - | |
| 169 | - /** | |
| 170 | - * get the manager object | |
| 171 | - * | |
| 172 | - * @return Manager|PublicEmitter | |
| 173 | - */ | |
| 174 | -	public function getManager() { | |
| 175 | - return $this->manager; | |
| 176 | - } | |
| 177 | - | |
| 178 | - /** | |
| 179 | - * get the session object | |
| 180 | - * | |
| 181 | - * @return ISession | |
| 182 | - */ | |
| 183 | -	public function getSession() { | |
| 184 | - return $this->session; | |
| 185 | - } | |
| 186 | - | |
| 187 | - /** | |
| 188 | - * set the session object | |
| 189 | - * | |
| 190 | - * @param ISession $session | |
| 191 | - */ | |
| 192 | -	public function setSession(ISession $session) { | |
| 193 | -		if ($this->session instanceof ISession) { | |
| 194 | - $this->session->close(); | |
| 195 | - } | |
| 196 | - $this->session = $session; | |
| 197 | - $this->activeUser = null; | |
| 198 | - } | |
| 199 | - | |
| 200 | - /** | |
| 201 | - * set the currently active user | |
| 202 | - * | |
| 203 | - * @param IUser|null $user | |
| 204 | - */ | |
| 205 | -	public function setUser($user) { | |
| 206 | -		if (is_null($user)) { | |
| 207 | -			$this->session->remove('user_id'); | |
| 208 | -		} else { | |
| 209 | -			$this->session->set('user_id', $user->getUID()); | |
| 210 | - } | |
| 211 | - $this->activeUser = $user; | |
| 212 | - } | |
| 213 | - | |
| 214 | - /** | |
| 215 | - * get the current active user | |
| 216 | - * | |
| 217 | - * @return IUser|null Current user, otherwise null | |
| 218 | - */ | |
| 219 | -	public function getUser() { | |
| 220 | - // FIXME: This is a quick'n dirty work-around for the incognito mode as | |
| 221 | - // described at https://github.com/owncloud/core/pull/12912#issuecomment-67391155 | |
| 222 | -		if (OC_User::isIncognitoMode()) { | |
| 223 | - return null; | |
| 224 | - } | |
| 225 | -		if (is_null($this->activeUser)) { | |
| 226 | -			$uid = $this->session->get('user_id'); | |
| 227 | -			if (is_null($uid)) { | |
| 228 | - return null; | |
| 229 | - } | |
| 230 | - $this->activeUser = $this->manager->get($uid); | |
| 231 | -			if (is_null($this->activeUser)) { | |
| 232 | - return null; | |
| 233 | - } | |
| 234 | - $this->validateSession(); | |
| 235 | - } | |
| 236 | - return $this->activeUser; | |
| 237 | - } | |
| 238 | - | |
| 239 | - /** | |
| 240 | - * Validate whether the current session is valid | |
| 241 | - * | |
| 242 | - * - For token-authenticated clients, the token validity is checked | |
| 243 | - * - For browsers, the session token validity is checked | |
| 244 | - */ | |
| 245 | -	protected function validateSession() { | |
| 246 | - $token = null; | |
| 247 | -		$appPassword = $this->session->get('app_password'); | |
| 248 | - | |
| 249 | -		if (is_null($appPassword)) { | |
| 250 | -			try { | |
| 251 | - $token = $this->session->getId(); | |
| 252 | -			} catch (SessionNotAvailableException $ex) { | |
| 253 | - return; | |
| 254 | - } | |
| 255 | -		} else { | |
| 256 | - $token = $appPassword; | |
| 257 | - } | |
| 258 | - | |
| 259 | -		if (!$this->validateToken($token)) { | |
| 260 | - // Session was invalidated | |
| 261 | - $this->logout(); | |
| 262 | - } | |
| 263 | - } | |
| 264 | - | |
| 265 | - /** | |
| 266 | - * Checks whether the user is logged in | |
| 267 | - * | |
| 268 | - * @return bool if logged in | |
| 269 | - */ | |
| 270 | -	public function isLoggedIn() { | |
| 271 | - $user = $this->getUser(); | |
| 272 | -		if (is_null($user)) { | |
| 273 | - return false; | |
| 274 | - } | |
| 275 | - | |
| 276 | - return $user->isEnabled(); | |
| 277 | - } | |
| 278 | - | |
| 279 | - /** | |
| 280 | - * set the login name | |
| 281 | - * | |
| 282 | - * @param string|null $loginName for the logged in user | |
| 283 | - */ | |
| 284 | -	public function setLoginName($loginName) { | |
| 285 | -		if (is_null($loginName)) { | |
| 286 | -			$this->session->remove('loginname'); | |
| 287 | -		} else { | |
| 288 | -			$this->session->set('loginname', $loginName); | |
| 289 | - } | |
| 290 | - } | |
| 291 | - | |
| 292 | - /** | |
| 293 | - * get the login name of the current user | |
| 294 | - * | |
| 295 | - * @return string | |
| 296 | - */ | |
| 297 | -	public function getLoginName() { | |
| 298 | -		if ($this->activeUser) { | |
| 299 | -			return $this->session->get('loginname'); | |
| 300 | - } | |
| 301 | - | |
| 302 | -		$uid = $this->session->get('user_id'); | |
| 303 | -		if ($uid) { | |
| 304 | - $this->activeUser = $this->manager->get($uid); | |
| 305 | -			return $this->session->get('loginname'); | |
| 306 | - } | |
| 307 | - | |
| 308 | - return null; | |
| 309 | - } | |
| 310 | - | |
| 311 | - /** | |
| 312 | - * set the token id | |
| 313 | - * | |
| 314 | - * @param int|null $token that was used to log in | |
| 315 | - */ | |
| 316 | -	protected function setToken($token) { | |
| 317 | -		if ($token === null) { | |
| 318 | -			$this->session->remove('token-id'); | |
| 319 | -		} else { | |
| 320 | -			$this->session->set('token-id', $token); | |
| 321 | - } | |
| 322 | - } | |
| 323 | - | |
| 324 | - /** | |
| 325 | - * try to log in with the provided credentials | |
| 326 | - * | |
| 327 | - * @param string $uid | |
| 328 | - * @param string $password | |
| 329 | - * @return boolean|null | |
| 330 | - * @throws LoginException | |
| 331 | - */ | |
| 332 | -	public function login($uid, $password) { | |
| 333 | - $this->session->regenerateId(); | |
| 334 | -		if ($this->validateToken($password, $uid)) { | |
| 335 | - return $this->loginWithToken($password); | |
| 336 | - } | |
| 337 | - return $this->loginWithPassword($uid, $password); | |
| 338 | - } | |
| 339 | - | |
| 340 | - /** | |
| 341 | - * @param IUser $user | |
| 342 | - * @param array $loginDetails | |
| 343 | - * @param bool $regenerateSessionId | |
| 344 | - * @return true returns true if login successful or an exception otherwise | |
| 345 | - * @throws LoginException | |
| 346 | - */ | |
| 347 | -	public function completeLogin(IUser $user, array $loginDetails, $regenerateSessionId = true) { | |
| 348 | -		if (!$user->isEnabled()) { | |
| 349 | - // disabled users can not log in | |
| 350 | - // injecting l10n does not work - there is a circular dependency between session and \OCP\L10N\IFactory | |
| 351 | -			$message = \OC::$server->getL10N('lib')->t('User disabled'); | |
| 352 | - throw new LoginException($message); | |
| 353 | - } | |
| 354 | - | |
| 355 | -		if($regenerateSessionId) { | |
| 356 | - $this->session->regenerateId(); | |
| 357 | - } | |
| 358 | - | |
| 359 | - $this->setUser($user); | |
| 360 | - $this->setLoginName($loginDetails['loginName']); | |
| 361 | - | |
| 362 | -		if(isset($loginDetails['token']) && $loginDetails['token'] instanceof IToken) { | |
| 363 | - $this->setToken($loginDetails['token']->getId()); | |
| 364 | - $this->lockdownManager->setToken($loginDetails['token']); | |
| 365 | - $firstTimeLogin = false; | |
| 366 | -		} else { | |
| 367 | - $this->setToken(null); | |
| 368 | - $firstTimeLogin = $user->updateLastLoginTimestamp(); | |
| 369 | - } | |
| 370 | -		$this->manager->emit('\OC\User', 'postLogin', [$user, $loginDetails['password']]); | |
| 371 | -		if($this->isLoggedIn()) { | |
| 372 | - $this->prepareUserLogin($firstTimeLogin, $regenerateSessionId); | |
| 373 | - return true; | |
| 374 | - } | |
| 375 | - | |
| 376 | -		$message = \OC::$server->getL10N('lib')->t('Login canceled by app'); | |
| 377 | - throw new LoginException($message); | |
| 378 | - } | |
| 379 | - | |
| 380 | - /** | |
| 381 | - * Tries to log in a client | |
| 382 | - * | |
| 383 | - * Checks token auth enforced | |
| 384 | - * Checks 2FA enabled | |
| 385 | - * | |
| 386 | - * @param string $user | |
| 387 | - * @param string $password | |
| 388 | - * @param IRequest $request | |
| 389 | - * @param OC\Security\Bruteforce\Throttler $throttler | |
| 390 | - * @throws LoginException | |
| 391 | - * @throws PasswordLoginForbiddenException | |
| 392 | - * @return boolean | |
| 393 | - */ | |
| 394 | - public function logClientIn($user, | |
| 395 | - $password, | |
| 396 | - IRequest $request, | |
| 397 | -								OC\Security\Bruteforce\Throttler $throttler) { | |
| 398 | - $currentDelay = $throttler->sleepDelay($request->getRemoteAddress(), 'login'); | |
| 399 | - | |
| 400 | -		if ($this->manager instanceof PublicEmitter) { | |
| 401 | -			$this->manager->emit('\OC\User', 'preLogin', array($user, $password)); | |
| 402 | - } | |
| 403 | - | |
| 404 | - $isTokenPassword = $this->isTokenPassword($password); | |
| 405 | -		if (!$isTokenPassword && $this->isTokenAuthEnforced()) { | |
| 406 | - throw new PasswordLoginForbiddenException(); | |
| 407 | - } | |
| 408 | -		if (!$isTokenPassword && $this->isTwoFactorEnforced($user)) { | |
| 409 | - throw new PasswordLoginForbiddenException(); | |
| 410 | - } | |
| 411 | - | |
| 412 | - // Try to login with this username and password | |
| 413 | -		if (!$this->login($user, $password) ) { | |
| 414 | - | |
| 415 | - // Failed, maybe the user used their email address | |
| 416 | - $users = $this->manager->getByEmail($user); | |
| 417 | -			if (!(\count($users) === 1 && $this->login($users[0]->getUID(), $password))) { | |
| 418 | - | |
| 419 | -				$this->logger->warning('Login failed: \'' . $user . '\' (Remote IP: \'' . \OC::$server->getRequest()->getRemoteAddress() . '\')', ['app' => 'core']); | |
| 420 | - | |
| 421 | -				$throttler->registerAttempt('login', $request->getRemoteAddress(), ['uid' => $user]); | |
| 422 | -				if ($currentDelay === 0) { | |
| 423 | - $throttler->sleepDelay($request->getRemoteAddress(), 'login'); | |
| 424 | - } | |
| 425 | - return false; | |
| 426 | - } | |
| 427 | - } | |
| 428 | - | |
| 429 | -		if ($isTokenPassword) { | |
| 430 | -			$this->session->set('app_password', $password); | |
| 431 | -		} else if($this->supportsCookies($request)) { | |
| 432 | - // Password login, but cookies supported -> create (browser) session token | |
| 433 | - $this->createSessionToken($request, $this->getUser()->getUID(), $user, $password); | |
| 434 | - } | |
| 435 | - | |
| 436 | - return true; | |
| 437 | - } | |
| 438 | - | |
| 439 | -	protected function supportsCookies(IRequest $request) { | |
| 440 | -		if (!is_null($request->getCookie('cookie_test'))) { | |
| 441 | - return true; | |
| 442 | - } | |
| 443 | -		setcookie('cookie_test', 'test', $this->timeFactory->getTime() + 3600); | |
| 444 | - return false; | |
| 445 | - } | |
| 446 | - | |
| 447 | -	private function isTokenAuthEnforced() { | |
| 448 | -		return $this->config->getSystemValue('token_auth_enforced', false); | |
| 449 | - } | |
| 450 | - | |
| 451 | -	protected function isTwoFactorEnforced($username) { | |
| 452 | - Util::emitHook( | |
| 453 | - '\OCA\Files_Sharing\API\Server2Server', | |
| 454 | - 'preLoginNameUsedAsUserName', | |
| 455 | -			array('uid' => &$username) | |
| 456 | - ); | |
| 457 | - $user = $this->manager->get($username); | |
| 458 | -		if (is_null($user)) { | |
| 459 | - $users = $this->manager->getByEmail($username); | |
| 460 | -			if (empty($users)) { | |
| 461 | - return false; | |
| 462 | - } | |
| 463 | -			if (count($users) !== 1) { | |
| 464 | - return true; | |
| 465 | - } | |
| 466 | - $user = $users[0]; | |
| 467 | - } | |
| 468 | - // DI not possible due to cyclic dependencies :'-/ | |
| 469 | - return OC::$server->getTwoFactorAuthManager()->isTwoFactorAuthenticated($user); | |
| 470 | - } | |
| 471 | - | |
| 472 | - /** | |
| 473 | - * Check if the given 'password' is actually a device token | |
| 474 | - * | |
| 475 | - * @param string $password | |
| 476 | - * @return boolean | |
| 477 | - */ | |
| 478 | -	public function isTokenPassword($password) { | |
| 479 | -		try { | |
| 480 | - $this->tokenProvider->getToken($password); | |
| 481 | - return true; | |
| 482 | -		} catch (InvalidTokenException $ex) { | |
| 483 | - return false; | |
| 484 | - } | |
| 485 | - } | |
| 486 | - | |
| 487 | -	protected function prepareUserLogin($firstTimeLogin, $refreshCsrfToken = true) { | |
| 488 | -		if ($refreshCsrfToken) { | |
| 489 | - // TODO: mock/inject/use non-static | |
| 490 | - // Refresh the token | |
| 491 | - \OC::$server->getCsrfTokenManager()->refreshToken(); | |
| 492 | - } | |
| 493 | - | |
| 494 | - //we need to pass the user name, which may differ from login name | |
| 495 | - $user = $this->getUser()->getUID(); | |
| 496 | - OC_Util::setupFS($user); | |
| 497 | - | |
| 498 | -		if ($firstTimeLogin) { | |
| 499 | - // TODO: lock necessary? | |
| 500 | - //trigger creation of user home and /files folder | |
| 501 | - $userFolder = \OC::$server->getUserFolder($user); | |
| 502 | - | |
| 503 | -			try { | |
| 504 | - // copy skeleton | |
| 505 | - \OC_Util::copySkeleton($user, $userFolder); | |
| 506 | -			} catch (NotPermittedException $ex) { | |
| 507 | - // read only uses | |
| 508 | - } | |
| 509 | - | |
| 510 | - // trigger any other initialization | |
| 511 | - \OC::$server->getEventDispatcher()->dispatch(IUser::class . '::firstLogin', new GenericEvent($this->getUser())); | |
| 512 | - } | |
| 513 | - } | |
| 514 | - | |
| 515 | - /** | |
| 516 | - * Tries to login the user with HTTP Basic Authentication | |
| 517 | - * | |
| 518 | - * @todo do not allow basic auth if the user is 2FA enforced | |
| 519 | - * @param IRequest $request | |
| 520 | - * @param OC\Security\Bruteforce\Throttler $throttler | |
| 521 | - * @return boolean if the login was successful | |
| 522 | - */ | |
| 523 | - public function tryBasicAuthLogin(IRequest $request, | |
| 524 | -									  OC\Security\Bruteforce\Throttler $throttler) { | |
| 525 | -		if (!empty($request->server['PHP_AUTH_USER']) && !empty($request->server['PHP_AUTH_PW'])) { | |
| 526 | -			try { | |
| 527 | -				if ($this->logClientIn($request->server['PHP_AUTH_USER'], $request->server['PHP_AUTH_PW'], $request, $throttler)) { | |
| 528 | - /** | |
| 529 | - * Add DAV authenticated. This should in an ideal world not be | |
| 530 | - * necessary but the iOS App reads cookies from anywhere instead | |
| 531 | - * only the DAV endpoint. | |
| 532 | - * This makes sure that the cookies will be valid for the whole scope | |
| 533 | - * @see https://github.com/owncloud/core/issues/22893 | |
| 534 | - */ | |
| 535 | - $this->session->set( | |
| 536 | - Auth::DAV_AUTHENTICATED, $this->getUser()->getUID() | |
| 537 | - ); | |
| 538 | - | |
| 539 | - // Set the last-password-confirm session to make the sudo mode work | |
| 540 | -					 $this->session->set('last-password-confirm', $this->timeFactory->getTime()); | |
| 541 | - | |
| 542 | - return true; | |
| 543 | - } | |
| 544 | -			} catch (PasswordLoginForbiddenException $ex) { | |
| 545 | - // Nothing to do | |
| 546 | - } | |
| 547 | - } | |
| 548 | - return false; | |
| 549 | - } | |
| 550 | - | |
| 551 | - /** | |
| 552 | - * Log an user in via login name and password | |
| 553 | - * | |
| 554 | - * @param string $uid | |
| 555 | - * @param string $password | |
| 556 | - * @return boolean | |
| 557 | - * @throws LoginException if an app canceld the login process or the user is not enabled | |
| 558 | - */ | |
| 559 | -	private function loginWithPassword($uid, $password) { | |
| 560 | - $user = $this->manager->checkPasswordNoLogging($uid, $password); | |
| 561 | -		if ($user === false) { | |
| 562 | - // Password check failed | |
| 563 | - return false; | |
| 564 | - } | |
| 565 | - | |
| 566 | - return $this->completeLogin($user, ['loginName' => $uid, 'password' => $password], false); | |
| 567 | - } | |
| 568 | - | |
| 569 | - /** | |
| 570 | - * Log an user in with a given token (id) | |
| 571 | - * | |
| 572 | - * @param string $token | |
| 573 | - * @return boolean | |
| 574 | - * @throws LoginException if an app canceled the login process or the user is not enabled | |
| 575 | - */ | |
| 576 | -	private function loginWithToken($token) { | |
| 577 | -		try { | |
| 578 | - $dbToken = $this->tokenProvider->getToken($token); | |
| 579 | -		} catch (InvalidTokenException $ex) { | |
| 580 | - return false; | |
| 581 | - } | |
| 582 | - $uid = $dbToken->getUID(); | |
| 583 | - | |
| 584 | - // When logging in with token, the password must be decrypted first before passing to login hook | |
| 585 | - $password = ''; | |
| 586 | -		try { | |
| 587 | - $password = $this->tokenProvider->getPassword($dbToken, $token); | |
| 588 | -		} catch (PasswordlessTokenException $ex) { | |
| 589 | - // Ignore and use empty string instead | |
| 590 | - } | |
| 591 | - | |
| 592 | -		$this->manager->emit('\OC\User', 'preLogin', array($uid, $password)); | |
| 593 | - | |
| 594 | - $user = $this->manager->get($uid); | |
| 595 | -		if (is_null($user)) { | |
| 596 | - // user does not exist | |
| 597 | - return false; | |
| 598 | - } | |
| 599 | - | |
| 600 | - return $this->completeLogin( | |
| 601 | - $user, | |
| 602 | - [ | |
| 603 | - 'loginName' => $dbToken->getLoginName(), | |
| 604 | - 'password' => $password, | |
| 605 | - 'token' => $dbToken | |
| 606 | - ], | |
| 607 | - false); | |
| 608 | - } | |
| 609 | - | |
| 610 | - /** | |
| 611 | - * Create a new session token for the given user credentials | |
| 612 | - * | |
| 613 | - * @param IRequest $request | |
| 614 | - * @param string $uid user UID | |
| 615 | - * @param string $loginName login name | |
| 616 | - * @param string $password | |
| 617 | - * @param int $remember | |
| 618 | - * @return boolean | |
| 619 | - */ | |
| 620 | -	public function createSessionToken(IRequest $request, $uid, $loginName, $password = null, $remember = IToken::DO_NOT_REMEMBER) { | |
| 621 | -		if (is_null($this->manager->get($uid))) { | |
| 622 | - // User does not exist | |
| 623 | - return false; | |
| 624 | - } | |
| 625 | - $name = isset($request->server['HTTP_USER_AGENT']) ? $request->server['HTTP_USER_AGENT'] : 'unknown browser'; | |
| 626 | -		try { | |
| 627 | - $sessionId = $this->session->getId(); | |
| 628 | - $pwd = $this->getPassword($password); | |
| 629 | - // Make sure the current sessionId has no leftover tokens | |
| 630 | - $this->tokenProvider->invalidateToken($sessionId); | |
| 631 | - $this->tokenProvider->generateToken($sessionId, $uid, $loginName, $pwd, $name, IToken::TEMPORARY_TOKEN, $remember); | |
| 632 | - return true; | |
| 633 | -		} catch (SessionNotAvailableException $ex) { | |
| 634 | - // This can happen with OCC, where a memory session is used | |
| 635 | - // if a memory session is used, we shouldn't create a session token anyway | |
| 636 | - return false; | |
| 637 | - } | |
| 638 | - } | |
| 639 | - | |
| 640 | - /** | |
| 641 | - * Checks if the given password is a token. | |
| 642 | - * If yes, the password is extracted from the token. | |
| 643 | - * If no, the same password is returned. | |
| 644 | - * | |
| 645 | - * @param string $password either the login password or a device token | |
| 646 | - * @return string|null the password or null if none was set in the token | |
| 647 | - */ | |
| 648 | -	private function getPassword($password) { | |
| 649 | -		if (is_null($password)) { | |
| 650 | - // This is surely no token ;-) | |
| 651 | - return null; | |
| 652 | - } | |
| 653 | -		try { | |
| 654 | - $token = $this->tokenProvider->getToken($password); | |
| 655 | -			try { | |
| 656 | - return $this->tokenProvider->getPassword($token, $password); | |
| 657 | -			} catch (PasswordlessTokenException $ex) { | |
| 658 | - return null; | |
| 659 | - } | |
| 660 | -		} catch (InvalidTokenException $ex) { | |
| 661 | - return $password; | |
| 662 | - } | |
| 663 | - } | |
| 664 | - | |
| 665 | - /** | |
| 666 | - * @param IToken $dbToken | |
| 667 | - * @param string $token | |
| 668 | - * @return boolean | |
| 669 | - */ | |
| 670 | -	private function checkTokenCredentials(IToken $dbToken, $token) { | |
| 671 | - // Check whether login credentials are still valid and the user was not disabled | |
| 672 | - // This check is performed each 5 minutes | |
| 673 | - $lastCheck = $dbToken->getLastCheck() ? : 0; | |
| 674 | - $now = $this->timeFactory->getTime(); | |
| 675 | -		if ($lastCheck > ($now - 60 * 5)) { | |
| 676 | - // Checked performed recently, nothing to do now | |
| 677 | - return true; | |
| 678 | - } | |
| 679 | - | |
| 680 | -		try { | |
| 681 | - $pwd = $this->tokenProvider->getPassword($dbToken, $token); | |
| 682 | -		} catch (InvalidTokenException $ex) { | |
| 683 | - // An invalid token password was used -> log user out | |
| 684 | - return false; | |
| 685 | -		} catch (PasswordlessTokenException $ex) { | |
| 686 | - // Token has no password | |
| 687 | - | |
| 688 | -			if (!is_null($this->activeUser) && !$this->activeUser->isEnabled()) { | |
| 689 | - $this->tokenProvider->invalidateToken($token); | |
| 690 | - return false; | |
| 691 | - } | |
| 692 | - | |
| 693 | - $dbToken->setLastCheck($now); | |
| 694 | - return true; | |
| 695 | - } | |
| 696 | - | |
| 697 | - if ($this->manager->checkPassword($dbToken->getLoginName(), $pwd) === false | |
| 698 | -			|| (!is_null($this->activeUser) && !$this->activeUser->isEnabled())) { | |
| 699 | - $this->tokenProvider->invalidateToken($token); | |
| 700 | - // Password has changed or user was disabled -> log user out | |
| 701 | - return false; | |
| 702 | - } | |
| 703 | - $dbToken->setLastCheck($now); | |
| 704 | - return true; | |
| 705 | - } | |
| 706 | - | |
| 707 | - /** | |
| 708 | - * Check if the given token exists and performs password/user-enabled checks | |
| 709 | - * | |
| 710 | - * Invalidates the token if checks fail | |
| 711 | - * | |
| 712 | - * @param string $token | |
| 713 | - * @param string $user login name | |
| 714 | - * @return boolean | |
| 715 | - */ | |
| 716 | -	private function validateToken($token, $user = null) { | |
| 717 | -		try { | |
| 718 | - $dbToken = $this->tokenProvider->getToken($token); | |
| 719 | -		} catch (InvalidTokenException $ex) { | |
| 720 | - return false; | |
| 721 | - } | |
| 722 | - | |
| 723 | - // Check if login names match | |
| 724 | -		if (!is_null($user) && $dbToken->getLoginName() !== $user) { | |
| 725 | - // TODO: this makes it imposssible to use different login names on browser and client | |
| 726 | - // e.g. login by e-mail '[email protected]' on browser for generating the token will not | |
| 727 | - // allow to use the client token with the login name 'user'. | |
| 728 | - return false; | |
| 729 | - } | |
| 730 | - | |
| 731 | -		if (!$this->checkTokenCredentials($dbToken, $token)) { | |
| 732 | - return false; | |
| 733 | - } | |
| 734 | - | |
| 735 | - $this->tokenProvider->updateTokenActivity($dbToken); | |
| 736 | - | |
| 737 | - return true; | |
| 738 | - } | |
| 739 | - | |
| 740 | - /** | |
| 741 | - * Tries to login the user with auth token header | |
| 742 | - * | |
| 743 | - * @param IRequest $request | |
| 744 | - * @todo check remember me cookie | |
| 745 | - * @return boolean | |
| 746 | - */ | |
| 747 | -	public function tryTokenLogin(IRequest $request) { | |
| 748 | -		$authHeader = $request->getHeader('Authorization'); | |
| 749 | -		if (strpos($authHeader, 'Bearer ') === false) { | |
| 750 | - // No auth header, let's try session id | |
| 751 | -			try { | |
| 752 | - $token = $this->session->getId(); | |
| 753 | -			} catch (SessionNotAvailableException $ex) { | |
| 754 | - return false; | |
| 755 | - } | |
| 756 | -		} else { | |
| 757 | - $token = substr($authHeader, 7); | |
| 758 | - } | |
| 759 | - | |
| 760 | -		if (!$this->loginWithToken($token)) { | |
| 761 | - return false; | |
| 762 | - } | |
| 763 | -		if(!$this->validateToken($token)) { | |
| 764 | - return false; | |
| 765 | - } | |
| 766 | - return true; | |
| 767 | - } | |
| 768 | - | |
| 769 | - /** | |
| 770 | - * perform login using the magic cookie (remember login) | |
| 771 | - * | |
| 772 | - * @param string $uid the username | |
| 773 | - * @param string $currentToken | |
| 774 | - * @param string $oldSessionId | |
| 775 | - * @return bool | |
| 776 | - */ | |
| 777 | -	public function loginWithCookie($uid, $currentToken, $oldSessionId) { | |
| 778 | - $this->session->regenerateId(); | |
| 779 | -		$this->manager->emit('\OC\User', 'preRememberedLogin', array($uid)); | |
| 780 | - $user = $this->manager->get($uid); | |
| 781 | -		if (is_null($user)) { | |
| 782 | - // user does not exist | |
| 783 | - return false; | |
| 784 | - } | |
| 785 | - | |
| 786 | - // get stored tokens | |
| 787 | - $tokens = $this->config->getUserKeys($uid, 'login_token'); | |
| 788 | - // test cookies token against stored tokens | |
| 789 | -		if (!in_array($currentToken, $tokens, true)) { | |
| 790 | - return false; | |
| 791 | - } | |
| 792 | - // replace successfully used token with a new one | |
| 793 | - $this->config->deleteUserValue($uid, 'login_token', $currentToken); | |
| 794 | - $newToken = $this->random->generate(32); | |
| 795 | - $this->config->setUserValue($uid, 'login_token', $newToken, $this->timeFactory->getTime()); | |
| 796 | - | |
| 797 | -		try { | |
| 798 | - $sessionId = $this->session->getId(); | |
| 799 | - $this->tokenProvider->renewSessionToken($oldSessionId, $sessionId); | |
| 800 | -		} catch (SessionNotAvailableException $ex) { | |
| 801 | - return false; | |
| 802 | -		} catch (InvalidTokenException $ex) { | |
| 803 | -			\OC::$server->getLogger()->warning('Renewing session token failed', ['app' => 'core']); | |
| 804 | - return false; | |
| 805 | - } | |
| 806 | - | |
| 807 | - $this->setMagicInCookie($user->getUID(), $newToken); | |
| 808 | - $token = $this->tokenProvider->getToken($sessionId); | |
| 809 | - | |
| 810 | - //login | |
| 811 | - $this->setUser($user); | |
| 812 | - $this->setLoginName($token->getLoginName()); | |
| 813 | - $this->setToken($token->getId()); | |
| 814 | - $this->lockdownManager->setToken($token); | |
| 815 | - $user->updateLastLoginTimestamp(); | |
| 816 | - $password = null; | |
| 817 | -		try { | |
| 818 | - $password = $this->tokenProvider->getPassword($token, $sessionId); | |
| 819 | -		} catch (PasswordlessTokenException $ex) { | |
| 820 | - // Ignore | |
| 821 | - } | |
| 822 | -		$this->manager->emit('\OC\User', 'postRememberedLogin', [$user, $password]); | |
| 823 | - return true; | |
| 824 | - } | |
| 825 | - | |
| 826 | - /** | |
| 827 | - * @param IUser $user | |
| 828 | - */ | |
| 829 | -	public function createRememberMeToken(IUser $user) { | |
| 830 | - $token = $this->random->generate(32); | |
| 831 | - $this->config->setUserValue($user->getUID(), 'login_token', $token, $this->timeFactory->getTime()); | |
| 832 | - $this->setMagicInCookie($user->getUID(), $token); | |
| 833 | - } | |
| 834 | - | |
| 835 | - /** | |
| 836 | - * logout the user from the session | |
| 837 | - */ | |
| 838 | -	public function logout() { | |
| 839 | -		$this->manager->emit('\OC\User', 'logout'); | |
| 840 | - $user = $this->getUser(); | |
| 841 | -		if (!is_null($user)) { | |
| 842 | -			try { | |
| 843 | - $this->tokenProvider->invalidateToken($this->session->getId()); | |
| 844 | -			} catch (SessionNotAvailableException $ex) { | |
| 845 | - | |
| 846 | - } | |
| 847 | - } | |
| 848 | - $this->setUser(null); | |
| 849 | - $this->setLoginName(null); | |
| 850 | - $this->setToken(null); | |
| 851 | - $this->unsetMagicInCookie(); | |
| 852 | - $this->session->clear(); | |
| 853 | -		$this->manager->emit('\OC\User', 'postLogout'); | |
| 854 | - } | |
| 855 | - | |
| 856 | - /** | |
| 857 | - * Set cookie value to use in next page load | |
| 858 | - * | |
| 859 | - * @param string $username username to be set | |
| 860 | - * @param string $token | |
| 861 | - */ | |
| 862 | -	public function setMagicInCookie($username, $token) { | |
| 863 | - $secureCookie = OC::$server->getRequest()->getServerProtocol() === 'https'; | |
| 864 | - $webRoot = \OC::$WEBROOT; | |
| 865 | -		if ($webRoot === '') { | |
| 866 | - $webRoot = '/'; | |
| 867 | - } | |
| 868 | - | |
| 869 | -		$expires = $this->timeFactory->getTime() + $this->config->getSystemValue('remember_login_cookie_lifetime', 60 * 60 * 24 * 15); | |
| 870 | -		setcookie('nc_username', $username, $expires, $webRoot, '', $secureCookie, true); | |
| 871 | -		setcookie('nc_token', $token, $expires, $webRoot, '', $secureCookie, true); | |
| 872 | -		try { | |
| 873 | -			setcookie('nc_session_id', $this->session->getId(), $expires, $webRoot, '', $secureCookie, true); | |
| 874 | -		} catch (SessionNotAvailableException $ex) { | |
| 875 | - // ignore | |
| 876 | - } | |
| 877 | - } | |
| 878 | - | |
| 879 | - /** | |
| 880 | - * Remove cookie for "remember username" | |
| 881 | - */ | |
| 882 | -	public function unsetMagicInCookie() { | |
| 883 | - //TODO: DI for cookies and IRequest | |
| 884 | - $secureCookie = OC::$server->getRequest()->getServerProtocol() === 'https'; | |
| 885 | - | |
| 886 | - unset($_COOKIE['nc_username']); //TODO: DI | |
| 887 | - unset($_COOKIE['nc_token']); | |
| 888 | - unset($_COOKIE['nc_session_id']); | |
| 889 | -		setcookie('nc_username', '', $this->timeFactory->getTime() - 3600, OC::$WEBROOT, '', $secureCookie, true); | |
| 890 | -		setcookie('nc_token', '', $this->timeFactory->getTime() - 3600, OC::$WEBROOT, '', $secureCookie, true); | |
| 891 | -		setcookie('nc_session_id', '', $this->timeFactory->getTime() - 3600, OC::$WEBROOT, '', $secureCookie, true); | |
| 892 | - // old cookies might be stored under /webroot/ instead of /webroot | |
| 893 | - // and Firefox doesn't like it! | |
| 894 | -		setcookie('nc_username', '', $this->timeFactory->getTime() - 3600, OC::$WEBROOT . '/', '', $secureCookie, true); | |
| 895 | -		setcookie('nc_token', '', $this->timeFactory->getTime() - 3600, OC::$WEBROOT . '/', '', $secureCookie, true); | |
| 896 | -		setcookie('nc_session_id', '', $this->timeFactory->getTime() - 3600, OC::$WEBROOT . '/', '', $secureCookie, true); | |
| 897 | - } | |
| 898 | - | |
| 899 | - /** | |
| 900 | - * Update password of the browser session token if there is one | |
| 901 | - * | |
| 902 | - * @param string $password | |
| 903 | - */ | |
| 904 | -	public function updateSessionTokenPassword($password) { | |
| 905 | -		try { | |
| 906 | - $sessionId = $this->session->getId(); | |
| 907 | - $token = $this->tokenProvider->getToken($sessionId); | |
| 908 | - $this->tokenProvider->setPassword($token, $sessionId, $password); | |
| 909 | -		} catch (SessionNotAvailableException $ex) { | |
| 910 | - // Nothing to do | |
| 911 | -		} catch (InvalidTokenException $ex) { | |
| 912 | - // Nothing to do | |
| 913 | - } | |
| 914 | - } | |
| 89 | + /** @var Manager|PublicEmitter $manager */ | |
| 90 | + private $manager; | |
| 91 | + | |
| 92 | + /** @var ISession $session */ | |
| 93 | + private $session; | |
| 94 | + | |
| 95 | + /** @var ITimeFactory */ | |
| 96 | + private $timeFactory; | |
| 97 | + | |
| 98 | + /** @var IProvider */ | |
| 99 | + private $tokenProvider; | |
| 100 | + | |
| 101 | + /** @var IConfig */ | |
| 102 | + private $config; | |
| 103 | + | |
| 104 | + /** @var User $activeUser */ | |
| 105 | + protected $activeUser; | |
| 106 | + | |
| 107 | + /** @var ISecureRandom */ | |
| 108 | + private $random; | |
| 109 | + | |
| 110 | + /** @var ILockdownManager */ | |
| 111 | + private $lockdownManager; | |
| 112 | + | |
| 113 | + /** @var ILogger */ | |
| 114 | + private $logger; | |
| 115 | + | |
| 116 | + /** | |
| 117 | + * @param Manager $manager | |
| 118 | + * @param ISession $session | |
| 119 | + * @param ITimeFactory $timeFactory | |
| 120 | + * @param IProvider $tokenProvider | |
| 121 | + * @param IConfig $config | |
| 122 | + * @param ISecureRandom $random | |
| 123 | + * @param ILockdownManager $lockdownManager | |
| 124 | + * @param ILogger $logger | |
| 125 | + */ | |
| 126 | + public function __construct(Manager $manager, | |
| 127 | + ISession $session, | |
| 128 | + ITimeFactory $timeFactory, | |
| 129 | + $tokenProvider, | |
| 130 | + IConfig $config, | |
| 131 | + ISecureRandom $random, | |
| 132 | + ILockdownManager $lockdownManager, | |
| 133 | +                                ILogger $logger) { | |
| 134 | + $this->manager = $manager; | |
| 135 | + $this->session = $session; | |
| 136 | + $this->timeFactory = $timeFactory; | |
| 137 | + $this->tokenProvider = $tokenProvider; | |
| 138 | + $this->config = $config; | |
| 139 | + $this->random = $random; | |
| 140 | + $this->lockdownManager = $lockdownManager; | |
| 141 | + $this->logger = $logger; | |
| 142 | + } | |
| 143 | + | |
| 144 | + /** | |
| 145 | + * @param IProvider $provider | |
| 146 | + */ | |
| 147 | +    public function setTokenProvider(IProvider $provider) { | |
| 148 | + $this->tokenProvider = $provider; | |
| 149 | + } | |
| 150 | + | |
| 151 | + /** | |
| 152 | + * @param string $scope | |
| 153 | + * @param string $method | |
| 154 | + * @param callable $callback | |
| 155 | + */ | |
| 156 | +    public function listen($scope, $method, callable $callback) { | |
| 157 | + $this->manager->listen($scope, $method, $callback); | |
| 158 | + } | |
| 159 | + | |
| 160 | + /** | |
| 161 | + * @param string $scope optional | |
| 162 | + * @param string $method optional | |
| 163 | + * @param callable $callback optional | |
| 164 | + */ | |
| 165 | +    public function removeListener($scope = null, $method = null, callable $callback = null) { | |
| 166 | + $this->manager->removeListener($scope, $method, $callback); | |
| 167 | + } | |
| 168 | + | |
| 169 | + /** | |
| 170 | + * get the manager object | |
| 171 | + * | |
| 172 | + * @return Manager|PublicEmitter | |
| 173 | + */ | |
| 174 | +    public function getManager() { | |
| 175 | + return $this->manager; | |
| 176 | + } | |
| 177 | + | |
| 178 | + /** | |
| 179 | + * get the session object | |
| 180 | + * | |
| 181 | + * @return ISession | |
| 182 | + */ | |
| 183 | +    public function getSession() { | |
| 184 | + return $this->session; | |
| 185 | + } | |
| 186 | + | |
| 187 | + /** | |
| 188 | + * set the session object | |
| 189 | + * | |
| 190 | + * @param ISession $session | |
| 191 | + */ | |
| 192 | +    public function setSession(ISession $session) { | |
| 193 | +        if ($this->session instanceof ISession) { | |
| 194 | + $this->session->close(); | |
| 195 | + } | |
| 196 | + $this->session = $session; | |
| 197 | + $this->activeUser = null; | |
| 198 | + } | |
| 199 | + | |
| 200 | + /** | |
| 201 | + * set the currently active user | |
| 202 | + * | |
| 203 | + * @param IUser|null $user | |
| 204 | + */ | |
| 205 | +    public function setUser($user) { | |
| 206 | +        if (is_null($user)) { | |
| 207 | +            $this->session->remove('user_id'); | |
| 208 | +        } else { | |
| 209 | +            $this->session->set('user_id', $user->getUID()); | |
| 210 | + } | |
| 211 | + $this->activeUser = $user; | |
| 212 | + } | |
| 213 | + | |
| 214 | + /** | |
| 215 | + * get the current active user | |
| 216 | + * | |
| 217 | + * @return IUser|null Current user, otherwise null | |
| 218 | + */ | |
| 219 | +    public function getUser() { | |
| 220 | + // FIXME: This is a quick'n dirty work-around for the incognito mode as | |
| 221 | + // described at https://github.com/owncloud/core/pull/12912#issuecomment-67391155 | |
| 222 | +        if (OC_User::isIncognitoMode()) { | |
| 223 | + return null; | |
| 224 | + } | |
| 225 | +        if (is_null($this->activeUser)) { | |
| 226 | +            $uid = $this->session->get('user_id'); | |
| 227 | +            if (is_null($uid)) { | |
| 228 | + return null; | |
| 229 | + } | |
| 230 | + $this->activeUser = $this->manager->get($uid); | |
| 231 | +            if (is_null($this->activeUser)) { | |
| 232 | + return null; | |
| 233 | + } | |
| 234 | + $this->validateSession(); | |
| 235 | + } | |
| 236 | + return $this->activeUser; | |
| 237 | + } | |
| 238 | + | |
| 239 | + /** | |
| 240 | + * Validate whether the current session is valid | |
| 241 | + * | |
| 242 | + * - For token-authenticated clients, the token validity is checked | |
| 243 | + * - For browsers, the session token validity is checked | |
| 244 | + */ | |
| 245 | +    protected function validateSession() { | |
| 246 | + $token = null; | |
| 247 | +        $appPassword = $this->session->get('app_password'); | |
| 248 | + | |
| 249 | +        if (is_null($appPassword)) { | |
| 250 | +            try { | |
| 251 | + $token = $this->session->getId(); | |
| 252 | +            } catch (SessionNotAvailableException $ex) { | |
| 253 | + return; | |
| 254 | + } | |
| 255 | +        } else { | |
| 256 | + $token = $appPassword; | |
| 257 | + } | |
| 258 | + | |
| 259 | +        if (!$this->validateToken($token)) { | |
| 260 | + // Session was invalidated | |
| 261 | + $this->logout(); | |
| 262 | + } | |
| 263 | + } | |
| 264 | + | |
| 265 | + /** | |
| 266 | + * Checks whether the user is logged in | |
| 267 | + * | |
| 268 | + * @return bool if logged in | |
| 269 | + */ | |
| 270 | +    public function isLoggedIn() { | |
| 271 | + $user = $this->getUser(); | |
| 272 | +        if (is_null($user)) { | |
| 273 | + return false; | |
| 274 | + } | |
| 275 | + | |
| 276 | + return $user->isEnabled(); | |
| 277 | + } | |
| 278 | + | |
| 279 | + /** | |
| 280 | + * set the login name | |
| 281 | + * | |
| 282 | + * @param string|null $loginName for the logged in user | |
| 283 | + */ | |
| 284 | +    public function setLoginName($loginName) { | |
| 285 | +        if (is_null($loginName)) { | |
| 286 | +            $this->session->remove('loginname'); | |
| 287 | +        } else { | |
| 288 | +            $this->session->set('loginname', $loginName); | |
| 289 | + } | |
| 290 | + } | |
| 291 | + | |
| 292 | + /** | |
| 293 | + * get the login name of the current user | |
| 294 | + * | |
| 295 | + * @return string | |
| 296 | + */ | |
| 297 | +    public function getLoginName() { | |
| 298 | +        if ($this->activeUser) { | |
| 299 | +            return $this->session->get('loginname'); | |
| 300 | + } | |
| 301 | + | |
| 302 | +        $uid = $this->session->get('user_id'); | |
| 303 | +        if ($uid) { | |
| 304 | + $this->activeUser = $this->manager->get($uid); | |
| 305 | +            return $this->session->get('loginname'); | |
| 306 | + } | |
| 307 | + | |
| 308 | + return null; | |
| 309 | + } | |
| 310 | + | |
| 311 | + /** | |
| 312 | + * set the token id | |
| 313 | + * | |
| 314 | + * @param int|null $token that was used to log in | |
| 315 | + */ | |
| 316 | +    protected function setToken($token) { | |
| 317 | +        if ($token === null) { | |
| 318 | +            $this->session->remove('token-id'); | |
| 319 | +        } else { | |
| 320 | +            $this->session->set('token-id', $token); | |
| 321 | + } | |
| 322 | + } | |
| 323 | + | |
| 324 | + /** | |
| 325 | + * try to log in with the provided credentials | |
| 326 | + * | |
| 327 | + * @param string $uid | |
| 328 | + * @param string $password | |
| 329 | + * @return boolean|null | |
| 330 | + * @throws LoginException | |
| 331 | + */ | |
| 332 | +    public function login($uid, $password) { | |
| 333 | + $this->session->regenerateId(); | |
| 334 | +        if ($this->validateToken($password, $uid)) { | |
| 335 | + return $this->loginWithToken($password); | |
| 336 | + } | |
| 337 | + return $this->loginWithPassword($uid, $password); | |
| 338 | + } | |
| 339 | + | |
| 340 | + /** | |
| 341 | + * @param IUser $user | |
| 342 | + * @param array $loginDetails | |
| 343 | + * @param bool $regenerateSessionId | |
| 344 | + * @return true returns true if login successful or an exception otherwise | |
| 345 | + * @throws LoginException | |
| 346 | + */ | |
| 347 | +    public function completeLogin(IUser $user, array $loginDetails, $regenerateSessionId = true) { | |
| 348 | +        if (!$user->isEnabled()) { | |
| 349 | + // disabled users can not log in | |
| 350 | + // injecting l10n does not work - there is a circular dependency between session and \OCP\L10N\IFactory | |
| 351 | +            $message = \OC::$server->getL10N('lib')->t('User disabled'); | |
| 352 | + throw new LoginException($message); | |
| 353 | + } | |
| 354 | + | |
| 355 | +        if($regenerateSessionId) { | |
| 356 | + $this->session->regenerateId(); | |
| 357 | + } | |
| 358 | + | |
| 359 | + $this->setUser($user); | |
| 360 | + $this->setLoginName($loginDetails['loginName']); | |
| 361 | + | |
| 362 | +        if(isset($loginDetails['token']) && $loginDetails['token'] instanceof IToken) { | |
| 363 | + $this->setToken($loginDetails['token']->getId()); | |
| 364 | + $this->lockdownManager->setToken($loginDetails['token']); | |
| 365 | + $firstTimeLogin = false; | |
| 366 | +        } else { | |
| 367 | + $this->setToken(null); | |
| 368 | + $firstTimeLogin = $user->updateLastLoginTimestamp(); | |
| 369 | + } | |
| 370 | +        $this->manager->emit('\OC\User', 'postLogin', [$user, $loginDetails['password']]); | |
| 371 | +        if($this->isLoggedIn()) { | |
| 372 | + $this->prepareUserLogin($firstTimeLogin, $regenerateSessionId); | |
| 373 | + return true; | |
| 374 | + } | |
| 375 | + | |
| 376 | +        $message = \OC::$server->getL10N('lib')->t('Login canceled by app'); | |
| 377 | + throw new LoginException($message); | |
| 378 | + } | |
| 379 | + | |
| 380 | + /** | |
| 381 | + * Tries to log in a client | |
| 382 | + * | |
| 383 | + * Checks token auth enforced | |
| 384 | + * Checks 2FA enabled | |
| 385 | + * | |
| 386 | + * @param string $user | |
| 387 | + * @param string $password | |
| 388 | + * @param IRequest $request | |
| 389 | + * @param OC\Security\Bruteforce\Throttler $throttler | |
| 390 | + * @throws LoginException | |
| 391 | + * @throws PasswordLoginForbiddenException | |
| 392 | + * @return boolean | |
| 393 | + */ | |
| 394 | + public function logClientIn($user, | |
| 395 | + $password, | |
| 396 | + IRequest $request, | |
| 397 | +                                OC\Security\Bruteforce\Throttler $throttler) { | |
| 398 | + $currentDelay = $throttler->sleepDelay($request->getRemoteAddress(), 'login'); | |
| 399 | + | |
| 400 | +        if ($this->manager instanceof PublicEmitter) { | |
| 401 | +            $this->manager->emit('\OC\User', 'preLogin', array($user, $password)); | |
| 402 | + } | |
| 403 | + | |
| 404 | + $isTokenPassword = $this->isTokenPassword($password); | |
| 405 | +        if (!$isTokenPassword && $this->isTokenAuthEnforced()) { | |
| 406 | + throw new PasswordLoginForbiddenException(); | |
| 407 | + } | |
| 408 | +        if (!$isTokenPassword && $this->isTwoFactorEnforced($user)) { | |
| 409 | + throw new PasswordLoginForbiddenException(); | |
| 410 | + } | |
| 411 | + | |
| 412 | + // Try to login with this username and password | |
| 413 | +        if (!$this->login($user, $password) ) { | |
| 414 | + | |
| 415 | + // Failed, maybe the user used their email address | |
| 416 | + $users = $this->manager->getByEmail($user); | |
| 417 | +            if (!(\count($users) === 1 && $this->login($users[0]->getUID(), $password))) { | |
| 418 | + | |
| 419 | +                $this->logger->warning('Login failed: \'' . $user . '\' (Remote IP: \'' . \OC::$server->getRequest()->getRemoteAddress() . '\')', ['app' => 'core']); | |
| 420 | + | |
| 421 | +                $throttler->registerAttempt('login', $request->getRemoteAddress(), ['uid' => $user]); | |
| 422 | +                if ($currentDelay === 0) { | |
| 423 | + $throttler->sleepDelay($request->getRemoteAddress(), 'login'); | |
| 424 | + } | |
| 425 | + return false; | |
| 426 | + } | |
| 427 | + } | |
| 428 | + | |
| 429 | +        if ($isTokenPassword) { | |
| 430 | +            $this->session->set('app_password', $password); | |
| 431 | +        } else if($this->supportsCookies($request)) { | |
| 432 | + // Password login, but cookies supported -> create (browser) session token | |
| 433 | + $this->createSessionToken($request, $this->getUser()->getUID(), $user, $password); | |
| 434 | + } | |
| 435 | + | |
| 436 | + return true; | |
| 437 | + } | |
| 438 | + | |
| 439 | +    protected function supportsCookies(IRequest $request) { | |
| 440 | +        if (!is_null($request->getCookie('cookie_test'))) { | |
| 441 | + return true; | |
| 442 | + } | |
| 443 | +        setcookie('cookie_test', 'test', $this->timeFactory->getTime() + 3600); | |
| 444 | + return false; | |
| 445 | + } | |
| 446 | + | |
| 447 | +    private function isTokenAuthEnforced() { | |
| 448 | +        return $this->config->getSystemValue('token_auth_enforced', false); | |
| 449 | + } | |
| 450 | + | |
| 451 | +    protected function isTwoFactorEnforced($username) { | |
| 452 | + Util::emitHook( | |
| 453 | + '\OCA\Files_Sharing\API\Server2Server', | |
| 454 | + 'preLoginNameUsedAsUserName', | |
| 455 | +            array('uid' => &$username) | |
| 456 | + ); | |
| 457 | + $user = $this->manager->get($username); | |
| 458 | +        if (is_null($user)) { | |
| 459 | + $users = $this->manager->getByEmail($username); | |
| 460 | +            if (empty($users)) { | |
| 461 | + return false; | |
| 462 | + } | |
| 463 | +            if (count($users) !== 1) { | |
| 464 | + return true; | |
| 465 | + } | |
| 466 | + $user = $users[0]; | |
| 467 | + } | |
| 468 | + // DI not possible due to cyclic dependencies :'-/ | |
| 469 | + return OC::$server->getTwoFactorAuthManager()->isTwoFactorAuthenticated($user); | |
| 470 | + } | |
| 471 | + | |
| 472 | + /** | |
| 473 | + * Check if the given 'password' is actually a device token | |
| 474 | + * | |
| 475 | + * @param string $password | |
| 476 | + * @return boolean | |
| 477 | + */ | |
| 478 | +    public function isTokenPassword($password) { | |
| 479 | +        try { | |
| 480 | + $this->tokenProvider->getToken($password); | |
| 481 | + return true; | |
| 482 | +        } catch (InvalidTokenException $ex) { | |
| 483 | + return false; | |
| 484 | + } | |
| 485 | + } | |
| 486 | + | |
| 487 | +    protected function prepareUserLogin($firstTimeLogin, $refreshCsrfToken = true) { | |
| 488 | +        if ($refreshCsrfToken) { | |
| 489 | + // TODO: mock/inject/use non-static | |
| 490 | + // Refresh the token | |
| 491 | + \OC::$server->getCsrfTokenManager()->refreshToken(); | |
| 492 | + } | |
| 493 | + | |
| 494 | + //we need to pass the user name, which may differ from login name | |
| 495 | + $user = $this->getUser()->getUID(); | |
| 496 | + OC_Util::setupFS($user); | |
| 497 | + | |
| 498 | +        if ($firstTimeLogin) { | |
| 499 | + // TODO: lock necessary? | |
| 500 | + //trigger creation of user home and /files folder | |
| 501 | + $userFolder = \OC::$server->getUserFolder($user); | |
| 502 | + | |
| 503 | +            try { | |
| 504 | + // copy skeleton | |
| 505 | + \OC_Util::copySkeleton($user, $userFolder); | |
| 506 | +            } catch (NotPermittedException $ex) { | |
| 507 | + // read only uses | |
| 508 | + } | |
| 509 | + | |
| 510 | + // trigger any other initialization | |
| 511 | + \OC::$server->getEventDispatcher()->dispatch(IUser::class . '::firstLogin', new GenericEvent($this->getUser())); | |
| 512 | + } | |
| 513 | + } | |
| 514 | + | |
| 515 | + /** | |
| 516 | + * Tries to login the user with HTTP Basic Authentication | |
| 517 | + * | |
| 518 | + * @todo do not allow basic auth if the user is 2FA enforced | |
| 519 | + * @param IRequest $request | |
| 520 | + * @param OC\Security\Bruteforce\Throttler $throttler | |
| 521 | + * @return boolean if the login was successful | |
| 522 | + */ | |
| 523 | + public function tryBasicAuthLogin(IRequest $request, | |
| 524 | +                                        OC\Security\Bruteforce\Throttler $throttler) { | |
| 525 | +        if (!empty($request->server['PHP_AUTH_USER']) && !empty($request->server['PHP_AUTH_PW'])) { | |
| 526 | +            try { | |
| 527 | +                if ($this->logClientIn($request->server['PHP_AUTH_USER'], $request->server['PHP_AUTH_PW'], $request, $throttler)) { | |
| 528 | + /** | |
| 529 | + * Add DAV authenticated. This should in an ideal world not be | |
| 530 | + * necessary but the iOS App reads cookies from anywhere instead | |
| 531 | + * only the DAV endpoint. | |
| 532 | + * This makes sure that the cookies will be valid for the whole scope | |
| 533 | + * @see https://github.com/owncloud/core/issues/22893 | |
| 534 | + */ | |
| 535 | + $this->session->set( | |
| 536 | + Auth::DAV_AUTHENTICATED, $this->getUser()->getUID() | |
| 537 | + ); | |
| 538 | + | |
| 539 | + // Set the last-password-confirm session to make the sudo mode work | |
| 540 | +                        $this->session->set('last-password-confirm', $this->timeFactory->getTime()); | |
| 541 | + | |
| 542 | + return true; | |
| 543 | + } | |
| 544 | +            } catch (PasswordLoginForbiddenException $ex) { | |
| 545 | + // Nothing to do | |
| 546 | + } | |
| 547 | + } | |
| 548 | + return false; | |
| 549 | + } | |
| 550 | + | |
| 551 | + /** | |
| 552 | + * Log an user in via login name and password | |
| 553 | + * | |
| 554 | + * @param string $uid | |
| 555 | + * @param string $password | |
| 556 | + * @return boolean | |
| 557 | + * @throws LoginException if an app canceld the login process or the user is not enabled | |
| 558 | + */ | |
| 559 | +    private function loginWithPassword($uid, $password) { | |
| 560 | + $user = $this->manager->checkPasswordNoLogging($uid, $password); | |
| 561 | +        if ($user === false) { | |
| 562 | + // Password check failed | |
| 563 | + return false; | |
| 564 | + } | |
| 565 | + | |
| 566 | + return $this->completeLogin($user, ['loginName' => $uid, 'password' => $password], false); | |
| 567 | + } | |
| 568 | + | |
| 569 | + /** | |
| 570 | + * Log an user in with a given token (id) | |
| 571 | + * | |
| 572 | + * @param string $token | |
| 573 | + * @return boolean | |
| 574 | + * @throws LoginException if an app canceled the login process or the user is not enabled | |
| 575 | + */ | |
| 576 | +    private function loginWithToken($token) { | |
| 577 | +        try { | |
| 578 | + $dbToken = $this->tokenProvider->getToken($token); | |
| 579 | +        } catch (InvalidTokenException $ex) { | |
| 580 | + return false; | |
| 581 | + } | |
| 582 | + $uid = $dbToken->getUID(); | |
| 583 | + | |
| 584 | + // When logging in with token, the password must be decrypted first before passing to login hook | |
| 585 | + $password = ''; | |
| 586 | +        try { | |
| 587 | + $password = $this->tokenProvider->getPassword($dbToken, $token); | |
| 588 | +        } catch (PasswordlessTokenException $ex) { | |
| 589 | + // Ignore and use empty string instead | |
| 590 | + } | |
| 591 | + | |
| 592 | +        $this->manager->emit('\OC\User', 'preLogin', array($uid, $password)); | |
| 593 | + | |
| 594 | + $user = $this->manager->get($uid); | |
| 595 | +        if (is_null($user)) { | |
| 596 | + // user does not exist | |
| 597 | + return false; | |
| 598 | + } | |
| 599 | + | |
| 600 | + return $this->completeLogin( | |
| 601 | + $user, | |
| 602 | + [ | |
| 603 | + 'loginName' => $dbToken->getLoginName(), | |
| 604 | + 'password' => $password, | |
| 605 | + 'token' => $dbToken | |
| 606 | + ], | |
| 607 | + false); | |
| 608 | + } | |
| 609 | + | |
| 610 | + /** | |
| 611 | + * Create a new session token for the given user credentials | |
| 612 | + * | |
| 613 | + * @param IRequest $request | |
| 614 | + * @param string $uid user UID | |
| 615 | + * @param string $loginName login name | |
| 616 | + * @param string $password | |
| 617 | + * @param int $remember | |
| 618 | + * @return boolean | |
| 619 | + */ | |
| 620 | +    public function createSessionToken(IRequest $request, $uid, $loginName, $password = null, $remember = IToken::DO_NOT_REMEMBER) { | |
| 621 | +        if (is_null($this->manager->get($uid))) { | |
| 622 | + // User does not exist | |
| 623 | + return false; | |
| 624 | + } | |
| 625 | + $name = isset($request->server['HTTP_USER_AGENT']) ? $request->server['HTTP_USER_AGENT'] : 'unknown browser'; | |
| 626 | +        try { | |
| 627 | + $sessionId = $this->session->getId(); | |
| 628 | + $pwd = $this->getPassword($password); | |
| 629 | + // Make sure the current sessionId has no leftover tokens | |
| 630 | + $this->tokenProvider->invalidateToken($sessionId); | |
| 631 | + $this->tokenProvider->generateToken($sessionId, $uid, $loginName, $pwd, $name, IToken::TEMPORARY_TOKEN, $remember); | |
| 632 | + return true; | |
| 633 | +        } catch (SessionNotAvailableException $ex) { | |
| 634 | + // This can happen with OCC, where a memory session is used | |
| 635 | + // if a memory session is used, we shouldn't create a session token anyway | |
| 636 | + return false; | |
| 637 | + } | |
| 638 | + } | |
| 639 | + | |
| 640 | + /** | |
| 641 | + * Checks if the given password is a token. | |
| 642 | + * If yes, the password is extracted from the token. | |
| 643 | + * If no, the same password is returned. | |
| 644 | + * | |
| 645 | + * @param string $password either the login password or a device token | |
| 646 | + * @return string|null the password or null if none was set in the token | |
| 647 | + */ | |
| 648 | +    private function getPassword($password) { | |
| 649 | +        if (is_null($password)) { | |
| 650 | + // This is surely no token ;-) | |
| 651 | + return null; | |
| 652 | + } | |
| 653 | +        try { | |
| 654 | + $token = $this->tokenProvider->getToken($password); | |
| 655 | +            try { | |
| 656 | + return $this->tokenProvider->getPassword($token, $password); | |
| 657 | +            } catch (PasswordlessTokenException $ex) { | |
| 658 | + return null; | |
| 659 | + } | |
| 660 | +        } catch (InvalidTokenException $ex) { | |
| 661 | + return $password; | |
| 662 | + } | |
| 663 | + } | |
| 664 | + | |
| 665 | + /** | |
| 666 | + * @param IToken $dbToken | |
| 667 | + * @param string $token | |
| 668 | + * @return boolean | |
| 669 | + */ | |
| 670 | +    private function checkTokenCredentials(IToken $dbToken, $token) { | |
| 671 | + // Check whether login credentials are still valid and the user was not disabled | |
| 672 | + // This check is performed each 5 minutes | |
| 673 | + $lastCheck = $dbToken->getLastCheck() ? : 0; | |
| 674 | + $now = $this->timeFactory->getTime(); | |
| 675 | +        if ($lastCheck > ($now - 60 * 5)) { | |
| 676 | + // Checked performed recently, nothing to do now | |
| 677 | + return true; | |
| 678 | + } | |
| 679 | + | |
| 680 | +        try { | |
| 681 | + $pwd = $this->tokenProvider->getPassword($dbToken, $token); | |
| 682 | +        } catch (InvalidTokenException $ex) { | |
| 683 | + // An invalid token password was used -> log user out | |
| 684 | + return false; | |
| 685 | +        } catch (PasswordlessTokenException $ex) { | |
| 686 | + // Token has no password | |
| 687 | + | |
| 688 | +            if (!is_null($this->activeUser) && !$this->activeUser->isEnabled()) { | |
| 689 | + $this->tokenProvider->invalidateToken($token); | |
| 690 | + return false; | |
| 691 | + } | |
| 692 | + | |
| 693 | + $dbToken->setLastCheck($now); | |
| 694 | + return true; | |
| 695 | + } | |
| 696 | + | |
| 697 | + if ($this->manager->checkPassword($dbToken->getLoginName(), $pwd) === false | |
| 698 | +            || (!is_null($this->activeUser) && !$this->activeUser->isEnabled())) { | |
| 699 | + $this->tokenProvider->invalidateToken($token); | |
| 700 | + // Password has changed or user was disabled -> log user out | |
| 701 | + return false; | |
| 702 | + } | |
| 703 | + $dbToken->setLastCheck($now); | |
| 704 | + return true; | |
| 705 | + } | |
| 706 | + | |
| 707 | + /** | |
| 708 | + * Check if the given token exists and performs password/user-enabled checks | |
| 709 | + * | |
| 710 | + * Invalidates the token if checks fail | |
| 711 | + * | |
| 712 | + * @param string $token | |
| 713 | + * @param string $user login name | |
| 714 | + * @return boolean | |
| 715 | + */ | |
| 716 | +    private function validateToken($token, $user = null) { | |
| 717 | +        try { | |
| 718 | + $dbToken = $this->tokenProvider->getToken($token); | |
| 719 | +        } catch (InvalidTokenException $ex) { | |
| 720 | + return false; | |
| 721 | + } | |
| 722 | + | |
| 723 | + // Check if login names match | |
| 724 | +        if (!is_null($user) && $dbToken->getLoginName() !== $user) { | |
| 725 | + // TODO: this makes it imposssible to use different login names on browser and client | |
| 726 | + // e.g. login by e-mail '[email protected]' on browser for generating the token will not | |
| 727 | + // allow to use the client token with the login name 'user'. | |
| 728 | + return false; | |
| 729 | + } | |
| 730 | + | |
| 731 | +        if (!$this->checkTokenCredentials($dbToken, $token)) { | |
| 732 | + return false; | |
| 733 | + } | |
| 734 | + | |
| 735 | + $this->tokenProvider->updateTokenActivity($dbToken); | |
| 736 | + | |
| 737 | + return true; | |
| 738 | + } | |
| 739 | + | |
| 740 | + /** | |
| 741 | + * Tries to login the user with auth token header | |
| 742 | + * | |
| 743 | + * @param IRequest $request | |
| 744 | + * @todo check remember me cookie | |
| 745 | + * @return boolean | |
| 746 | + */ | |
| 747 | +    public function tryTokenLogin(IRequest $request) { | |
| 748 | +        $authHeader = $request->getHeader('Authorization'); | |
| 749 | +        if (strpos($authHeader, 'Bearer ') === false) { | |
| 750 | + // No auth header, let's try session id | |
| 751 | +            try { | |
| 752 | + $token = $this->session->getId(); | |
| 753 | +            } catch (SessionNotAvailableException $ex) { | |
| 754 | + return false; | |
| 755 | + } | |
| 756 | +        } else { | |
| 757 | + $token = substr($authHeader, 7); | |
| 758 | + } | |
| 759 | + | |
| 760 | +        if (!$this->loginWithToken($token)) { | |
| 761 | + return false; | |
| 762 | + } | |
| 763 | +        if(!$this->validateToken($token)) { | |
| 764 | + return false; | |
| 765 | + } | |
| 766 | + return true; | |
| 767 | + } | |
| 768 | + | |
| 769 | + /** | |
| 770 | + * perform login using the magic cookie (remember login) | |
| 771 | + * | |
| 772 | + * @param string $uid the username | |
| 773 | + * @param string $currentToken | |
| 774 | + * @param string $oldSessionId | |
| 775 | + * @return bool | |
| 776 | + */ | |
| 777 | +    public function loginWithCookie($uid, $currentToken, $oldSessionId) { | |
| 778 | + $this->session->regenerateId(); | |
| 779 | +        $this->manager->emit('\OC\User', 'preRememberedLogin', array($uid)); | |
| 780 | + $user = $this->manager->get($uid); | |
| 781 | +        if (is_null($user)) { | |
| 782 | + // user does not exist | |
| 783 | + return false; | |
| 784 | + } | |
| 785 | + | |
| 786 | + // get stored tokens | |
| 787 | + $tokens = $this->config->getUserKeys($uid, 'login_token'); | |
| 788 | + // test cookies token against stored tokens | |
| 789 | +        if (!in_array($currentToken, $tokens, true)) { | |
| 790 | + return false; | |
| 791 | + } | |
| 792 | + // replace successfully used token with a new one | |
| 793 | + $this->config->deleteUserValue($uid, 'login_token', $currentToken); | |
| 794 | + $newToken = $this->random->generate(32); | |
| 795 | + $this->config->setUserValue($uid, 'login_token', $newToken, $this->timeFactory->getTime()); | |
| 796 | + | |
| 797 | +        try { | |
| 798 | + $sessionId = $this->session->getId(); | |
| 799 | + $this->tokenProvider->renewSessionToken($oldSessionId, $sessionId); | |
| 800 | +        } catch (SessionNotAvailableException $ex) { | |
| 801 | + return false; | |
| 802 | +        } catch (InvalidTokenException $ex) { | |
| 803 | +            \OC::$server->getLogger()->warning('Renewing session token failed', ['app' => 'core']); | |
| 804 | + return false; | |
| 805 | + } | |
| 806 | + | |
| 807 | + $this->setMagicInCookie($user->getUID(), $newToken); | |
| 808 | + $token = $this->tokenProvider->getToken($sessionId); | |
| 809 | + | |
| 810 | + //login | |
| 811 | + $this->setUser($user); | |
| 812 | + $this->setLoginName($token->getLoginName()); | |
| 813 | + $this->setToken($token->getId()); | |
| 814 | + $this->lockdownManager->setToken($token); | |
| 815 | + $user->updateLastLoginTimestamp(); | |
| 816 | + $password = null; | |
| 817 | +        try { | |
| 818 | + $password = $this->tokenProvider->getPassword($token, $sessionId); | |
| 819 | +        } catch (PasswordlessTokenException $ex) { | |
| 820 | + // Ignore | |
| 821 | + } | |
| 822 | +        $this->manager->emit('\OC\User', 'postRememberedLogin', [$user, $password]); | |
| 823 | + return true; | |
| 824 | + } | |
| 825 | + | |
| 826 | + /** | |
| 827 | + * @param IUser $user | |
| 828 | + */ | |
| 829 | +    public function createRememberMeToken(IUser $user) { | |
| 830 | + $token = $this->random->generate(32); | |
| 831 | + $this->config->setUserValue($user->getUID(), 'login_token', $token, $this->timeFactory->getTime()); | |
| 832 | + $this->setMagicInCookie($user->getUID(), $token); | |
| 833 | + } | |
| 834 | + | |
| 835 | + /** | |
| 836 | + * logout the user from the session | |
| 837 | + */ | |
| 838 | +    public function logout() { | |
| 839 | +        $this->manager->emit('\OC\User', 'logout'); | |
| 840 | + $user = $this->getUser(); | |
| 841 | +        if (!is_null($user)) { | |
| 842 | +            try { | |
| 843 | + $this->tokenProvider->invalidateToken($this->session->getId()); | |
| 844 | +            } catch (SessionNotAvailableException $ex) { | |
| 845 | + | |
| 846 | + } | |
| 847 | + } | |
| 848 | + $this->setUser(null); | |
| 849 | + $this->setLoginName(null); | |
| 850 | + $this->setToken(null); | |
| 851 | + $this->unsetMagicInCookie(); | |
| 852 | + $this->session->clear(); | |
| 853 | +        $this->manager->emit('\OC\User', 'postLogout'); | |
| 854 | + } | |
| 855 | + | |
| 856 | + /** | |
| 857 | + * Set cookie value to use in next page load | |
| 858 | + * | |
| 859 | + * @param string $username username to be set | |
| 860 | + * @param string $token | |
| 861 | + */ | |
| 862 | +    public function setMagicInCookie($username, $token) { | |
| 863 | + $secureCookie = OC::$server->getRequest()->getServerProtocol() === 'https'; | |
| 864 | + $webRoot = \OC::$WEBROOT; | |
| 865 | +        if ($webRoot === '') { | |
| 866 | + $webRoot = '/'; | |
| 867 | + } | |
| 868 | + | |
| 869 | +        $expires = $this->timeFactory->getTime() + $this->config->getSystemValue('remember_login_cookie_lifetime', 60 * 60 * 24 * 15); | |
| 870 | +        setcookie('nc_username', $username, $expires, $webRoot, '', $secureCookie, true); | |
| 871 | +        setcookie('nc_token', $token, $expires, $webRoot, '', $secureCookie, true); | |
| 872 | +        try { | |
| 873 | +            setcookie('nc_session_id', $this->session->getId(), $expires, $webRoot, '', $secureCookie, true); | |
| 874 | +        } catch (SessionNotAvailableException $ex) { | |
| 875 | + // ignore | |
| 876 | + } | |
| 877 | + } | |
| 878 | + | |
| 879 | + /** | |
| 880 | + * Remove cookie for "remember username" | |
| 881 | + */ | |
| 882 | +    public function unsetMagicInCookie() { | |
| 883 | + //TODO: DI for cookies and IRequest | |
| 884 | + $secureCookie = OC::$server->getRequest()->getServerProtocol() === 'https'; | |
| 885 | + | |
| 886 | + unset($_COOKIE['nc_username']); //TODO: DI | |
| 887 | + unset($_COOKIE['nc_token']); | |
| 888 | + unset($_COOKIE['nc_session_id']); | |
| 889 | +        setcookie('nc_username', '', $this->timeFactory->getTime() - 3600, OC::$WEBROOT, '', $secureCookie, true); | |
| 890 | +        setcookie('nc_token', '', $this->timeFactory->getTime() - 3600, OC::$WEBROOT, '', $secureCookie, true); | |
| 891 | +        setcookie('nc_session_id', '', $this->timeFactory->getTime() - 3600, OC::$WEBROOT, '', $secureCookie, true); | |
| 892 | + // old cookies might be stored under /webroot/ instead of /webroot | |
| 893 | + // and Firefox doesn't like it! | |
| 894 | +        setcookie('nc_username', '', $this->timeFactory->getTime() - 3600, OC::$WEBROOT . '/', '', $secureCookie, true); | |
| 895 | +        setcookie('nc_token', '', $this->timeFactory->getTime() - 3600, OC::$WEBROOT . '/', '', $secureCookie, true); | |
| 896 | +        setcookie('nc_session_id', '', $this->timeFactory->getTime() - 3600, OC::$WEBROOT . '/', '', $secureCookie, true); | |
| 897 | + } | |
| 898 | + | |
| 899 | + /** | |
| 900 | + * Update password of the browser session token if there is one | |
| 901 | + * | |
| 902 | + * @param string $password | |
| 903 | + */ | |
| 904 | +    public function updateSessionTokenPassword($password) { | |
| 905 | +        try { | |
| 906 | + $sessionId = $this->session->getId(); | |
| 907 | + $token = $this->tokenProvider->getToken($sessionId); | |
| 908 | + $this->tokenProvider->setPassword($token, $sessionId, $password); | |
| 909 | +        } catch (SessionNotAvailableException $ex) { | |
| 910 | + // Nothing to do | |
| 911 | +        } catch (InvalidTokenException $ex) { | |
| 912 | + // Nothing to do | |
| 913 | + } | |
| 914 | + } | |
| 915 | 915 | |
| 916 | 916 | |
| 917 | 917 | } | 
| @@ -39,86 +39,86 @@ | ||
| 39 | 39 | * @package OC\Session | 
| 40 | 40 | */ | 
| 41 | 41 |  class Memory extends Session { | 
| 42 | - protected $data; | |
| 42 | + protected $data; | |
| 43 | 43 | |
| 44 | -	public function __construct(string $name) { | |
| 45 | - //no need to use $name since all data is already scoped to this instance | |
| 46 | - $this->data = []; | |
| 47 | - } | |
| 44 | +    public function __construct(string $name) { | |
| 45 | + //no need to use $name since all data is already scoped to this instance | |
| 46 | + $this->data = []; | |
| 47 | + } | |
| 48 | 48 | |
| 49 | - /** | |
| 50 | - * @param string $key | |
| 51 | - * @param integer $value | |
| 52 | - */ | |
| 53 | -	public function set(string $key, $value) { | |
| 54 | - $this->validateSession(); | |
| 55 | - $this->data[$key] = $value; | |
| 56 | - } | |
| 49 | + /** | |
| 50 | + * @param string $key | |
| 51 | + * @param integer $value | |
| 52 | + */ | |
| 53 | +    public function set(string $key, $value) { | |
| 54 | + $this->validateSession(); | |
| 55 | + $this->data[$key] = $value; | |
| 56 | + } | |
| 57 | 57 | |
| 58 | - /** | |
| 59 | - * @param string $key | |
| 60 | - * @return mixed | |
| 61 | - */ | |
| 62 | -	public function get(string $key) { | |
| 63 | -		if (!$this->exists($key)) { | |
| 64 | - return null; | |
| 65 | - } | |
| 66 | - return $this->data[$key]; | |
| 67 | - } | |
| 58 | + /** | |
| 59 | + * @param string $key | |
| 60 | + * @return mixed | |
| 61 | + */ | |
| 62 | +    public function get(string $key) { | |
| 63 | +        if (!$this->exists($key)) { | |
| 64 | + return null; | |
| 65 | + } | |
| 66 | + return $this->data[$key]; | |
| 67 | + } | |
| 68 | 68 | |
| 69 | - /** | |
| 70 | - * @param string $key | |
| 71 | - * @return bool | |
| 72 | - */ | |
| 73 | -	public function exists(string $key): bool { | |
| 74 | - return isset($this->data[$key]); | |
| 75 | - } | |
| 69 | + /** | |
| 70 | + * @param string $key | |
| 71 | + * @return bool | |
| 72 | + */ | |
| 73 | +    public function exists(string $key): bool { | |
| 74 | + return isset($this->data[$key]); | |
| 75 | + } | |
| 76 | 76 | |
| 77 | - /** | |
| 78 | - * @param string $key | |
| 79 | - */ | |
| 80 | -	public function remove(string $key) { | |
| 81 | - $this->validateSession(); | |
| 82 | - unset($this->data[$key]); | |
| 83 | - } | |
| 77 | + /** | |
| 78 | + * @param string $key | |
| 79 | + */ | |
| 80 | +    public function remove(string $key) { | |
| 81 | + $this->validateSession(); | |
| 82 | + unset($this->data[$key]); | |
| 83 | + } | |
| 84 | 84 | |
| 85 | -	public function clear() { | |
| 86 | - $this->data = []; | |
| 87 | - } | |
| 85 | +    public function clear() { | |
| 86 | + $this->data = []; | |
| 87 | + } | |
| 88 | 88 | |
| 89 | - /** | |
| 90 | - * Stub since the session ID does not need to get regenerated for the cache | |
| 91 | - * | |
| 92 | - * @param bool $deleteOldSession | |
| 93 | - */ | |
| 94 | -	public function regenerateId(bool $deleteOldSession = true, bool $updateToken = false) {} | |
| 89 | + /** | |
| 90 | + * Stub since the session ID does not need to get regenerated for the cache | |
| 91 | + * | |
| 92 | + * @param bool $deleteOldSession | |
| 93 | + */ | |
| 94 | +    public function regenerateId(bool $deleteOldSession = true, bool $updateToken = false) {} | |
| 95 | 95 | |
| 96 | - /** | |
| 97 | - * Wrapper around session_id | |
| 98 | - * | |
| 99 | - * @return string | |
| 100 | - * @throws SessionNotAvailableException | |
| 101 | - * @since 9.1.0 | |
| 102 | - */ | |
| 103 | -	public function getId(): string { | |
| 104 | -		throw new SessionNotAvailableException('Memory session does not have an ID'); | |
| 105 | - } | |
| 96 | + /** | |
| 97 | + * Wrapper around session_id | |
| 98 | + * | |
| 99 | + * @return string | |
| 100 | + * @throws SessionNotAvailableException | |
| 101 | + * @since 9.1.0 | |
| 102 | + */ | |
| 103 | +    public function getId(): string { | |
| 104 | +        throw new SessionNotAvailableException('Memory session does not have an ID'); | |
| 105 | + } | |
| 106 | 106 | |
| 107 | - /** | |
| 108 | - * Helper function for PHPUnit execution - don't use in non-test code | |
| 109 | - */ | |
| 110 | -	public function reopen() { | |
| 111 | - $this->sessionClosed = false; | |
| 112 | - } | |
| 107 | + /** | |
| 108 | + * Helper function for PHPUnit execution - don't use in non-test code | |
| 109 | + */ | |
| 110 | +    public function reopen() { | |
| 111 | + $this->sessionClosed = false; | |
| 112 | + } | |
| 113 | 113 | |
| 114 | - /** | |
| 115 | - * In case the session has already been locked an exception will be thrown | |
| 116 | - * | |
| 117 | - * @throws Exception | |
| 118 | - */ | |
| 119 | -	private function validateSession() { | |
| 120 | -		if ($this->sessionClosed) { | |
| 121 | -			throw new Exception('Session has been closed - no further changes to the session are allowed'); | |
| 122 | - } | |
| 123 | - } | |
| 114 | + /** | |
| 115 | + * In case the session has already been locked an exception will be thrown | |
| 116 | + * | |
| 117 | + * @throws Exception | |
| 118 | + */ | |
| 119 | +    private function validateSession() { | |
| 120 | +        if ($this->sessionClosed) { | |
| 121 | +            throw new Exception('Session has been closed - no further changes to the session are allowed'); | |
| 122 | + } | |
| 123 | + } | |
| 124 | 124 | } | 
| @@ -37,177 +37,177 @@ | ||
| 37 | 37 | * @package OC\Session | 
| 38 | 38 | */ | 
| 39 | 39 |  class CryptoSessionData implements \ArrayAccess, ISession { | 
| 40 | - /** @var ISession */ | |
| 41 | - protected $session; | |
| 42 | - /** @var \OCP\Security\ICrypto */ | |
| 43 | - protected $crypto; | |
| 44 | - /** @var string */ | |
| 45 | - protected $passphrase; | |
| 46 | - /** @var array */ | |
| 47 | - protected $sessionValues; | |
| 48 | - /** @var bool */ | |
| 49 | - protected $isModified = false; | |
| 50 | - CONST encryptedSessionName = 'encrypted_session_data'; | |
| 51 | - | |
| 52 | - /** | |
| 53 | - * @param ISession $session | |
| 54 | - * @param ICrypto $crypto | |
| 55 | - * @param string $passphrase | |
| 56 | - */ | |
| 57 | - public function __construct(ISession $session, | |
| 58 | - ICrypto $crypto, | |
| 59 | -								string $passphrase) { | |
| 60 | - $this->crypto = $crypto; | |
| 61 | - $this->session = $session; | |
| 62 | - $this->passphrase = $passphrase; | |
| 63 | - $this->initializeSession(); | |
| 64 | - } | |
| 65 | - | |
| 66 | - /** | |
| 67 | - * Close session if class gets destructed | |
| 68 | - */ | |
| 69 | -	public function __destruct() { | |
| 70 | -		try { | |
| 71 | - $this->close(); | |
| 72 | -		} catch (SessionNotAvailableException $e){ | |
| 73 | - // This exception can occur if session is already closed | |
| 74 | - // So it is safe to ignore it and let the garbage collector to proceed | |
| 75 | - } | |
| 76 | - } | |
| 77 | - | |
| 78 | -	protected function initializeSession() { | |
| 79 | - $encryptedSessionData = $this->session->get(self::encryptedSessionName) ?: ''; | |
| 80 | -		try { | |
| 81 | - $this->sessionValues = json_decode( | |
| 82 | - $this->crypto->decrypt($encryptedSessionData, $this->passphrase), | |
| 83 | - true | |
| 84 | - ); | |
| 85 | -		} catch (\Exception $e) { | |
| 86 | - $this->sessionValues = []; | |
| 87 | - } | |
| 88 | - } | |
| 89 | - | |
| 90 | - /** | |
| 91 | - * Set a value in the session | |
| 92 | - * | |
| 93 | - * @param string $key | |
| 94 | - * @param mixed $value | |
| 95 | - */ | |
| 96 | -	public function set(string $key, $value) { | |
| 97 | - $this->sessionValues[$key] = $value; | |
| 98 | - $this->isModified = true; | |
| 99 | - } | |
| 100 | - | |
| 101 | - /** | |
| 102 | - * Get a value from the session | |
| 103 | - * | |
| 104 | - * @param string $key | |
| 105 | - * @return string|null Either the value or null | |
| 106 | - */ | |
| 107 | -	public function get(string $key) { | |
| 108 | -		if(isset($this->sessionValues[$key])) { | |
| 109 | - return $this->sessionValues[$key]; | |
| 110 | - } | |
| 111 | - | |
| 112 | - return null; | |
| 113 | - } | |
| 114 | - | |
| 115 | - /** | |
| 116 | - * Check if a named key exists in the session | |
| 117 | - * | |
| 118 | - * @param string $key | |
| 119 | - * @return bool | |
| 120 | - */ | |
| 121 | -	public function exists(string $key): bool { | |
| 122 | - return isset($this->sessionValues[$key]); | |
| 123 | - } | |
| 124 | - | |
| 125 | - /** | |
| 126 | - * Remove a $key/$value pair from the session | |
| 127 | - * | |
| 128 | - * @param string $key | |
| 129 | - */ | |
| 130 | -	public function remove(string $key) { | |
| 131 | - $this->isModified = true; | |
| 132 | - unset($this->sessionValues[$key]); | |
| 133 | - $this->session->remove(self::encryptedSessionName); | |
| 134 | - } | |
| 135 | - | |
| 136 | - /** | |
| 137 | - * Reset and recreate the session | |
| 138 | - */ | |
| 139 | -	public function clear() { | |
| 140 | -		$requesttoken = $this->get('requesttoken'); | |
| 141 | - $this->sessionValues = []; | |
| 142 | -		if ($requesttoken !== null) { | |
| 143 | -			$this->set('requesttoken', $requesttoken); | |
| 144 | - } | |
| 145 | - $this->isModified = true; | |
| 146 | - $this->session->clear(); | |
| 147 | - } | |
| 148 | - | |
| 149 | - /** | |
| 150 | - * Wrapper around session_regenerate_id | |
| 151 | - * | |
| 152 | - * @param bool $deleteOldSession Whether to delete the old associated session file or not. | |
| 153 | - * @param bool $updateToken Wheater to update the associated auth token | |
| 154 | - * @return void | |
| 155 | - */ | |
| 156 | -	public function regenerateId(bool $deleteOldSession = true, bool $updateToken = false) { | |
| 157 | - $this->session->regenerateId($deleteOldSession, $updateToken); | |
| 158 | - } | |
| 159 | - | |
| 160 | - /** | |
| 161 | - * Wrapper around session_id | |
| 162 | - * | |
| 163 | - * @return string | |
| 164 | - * @throws SessionNotAvailableException | |
| 165 | - * @since 9.1.0 | |
| 166 | - */ | |
| 167 | -	public function getId(): string { | |
| 168 | - return $this->session->getId(); | |
| 169 | - } | |
| 170 | - | |
| 171 | - /** | |
| 172 | - * Close the session and release the lock, also writes all changed data in batch | |
| 173 | - */ | |
| 174 | -	public function close() { | |
| 175 | -		if($this->isModified) { | |
| 176 | - $encryptedValue = $this->crypto->encrypt(json_encode($this->sessionValues), $this->passphrase); | |
| 177 | - $this->session->set(self::encryptedSessionName, $encryptedValue); | |
| 178 | - $this->isModified = false; | |
| 179 | - } | |
| 180 | - $this->session->close(); | |
| 181 | - } | |
| 182 | - | |
| 183 | - /** | |
| 184 | - * @param mixed $offset | |
| 185 | - * @return bool | |
| 186 | - */ | |
| 187 | -	public function offsetExists($offset): bool { | |
| 188 | - return $this->exists($offset); | |
| 189 | - } | |
| 190 | - | |
| 191 | - /** | |
| 192 | - * @param mixed $offset | |
| 193 | - * @return mixed | |
| 194 | - */ | |
| 195 | -	public function offsetGet($offset) { | |
| 196 | - return $this->get($offset); | |
| 197 | - } | |
| 198 | - | |
| 199 | - /** | |
| 200 | - * @param mixed $offset | |
| 201 | - * @param mixed $value | |
| 202 | - */ | |
| 203 | -	public function offsetSet($offset, $value) { | |
| 204 | - $this->set($offset, $value); | |
| 205 | - } | |
| 206 | - | |
| 207 | - /** | |
| 208 | - * @param mixed $offset | |
| 209 | - */ | |
| 210 | -	public function offsetUnset($offset) { | |
| 211 | - $this->remove($offset); | |
| 212 | - } | |
| 40 | + /** @var ISession */ | |
| 41 | + protected $session; | |
| 42 | + /** @var \OCP\Security\ICrypto */ | |
| 43 | + protected $crypto; | |
| 44 | + /** @var string */ | |
| 45 | + protected $passphrase; | |
| 46 | + /** @var array */ | |
| 47 | + protected $sessionValues; | |
| 48 | + /** @var bool */ | |
| 49 | + protected $isModified = false; | |
| 50 | + CONST encryptedSessionName = 'encrypted_session_data'; | |
| 51 | + | |
| 52 | + /** | |
| 53 | + * @param ISession $session | |
| 54 | + * @param ICrypto $crypto | |
| 55 | + * @param string $passphrase | |
| 56 | + */ | |
| 57 | + public function __construct(ISession $session, | |
| 58 | + ICrypto $crypto, | |
| 59 | +                                string $passphrase) { | |
| 60 | + $this->crypto = $crypto; | |
| 61 | + $this->session = $session; | |
| 62 | + $this->passphrase = $passphrase; | |
| 63 | + $this->initializeSession(); | |
| 64 | + } | |
| 65 | + | |
| 66 | + /** | |
| 67 | + * Close session if class gets destructed | |
| 68 | + */ | |
| 69 | +    public function __destruct() { | |
| 70 | +        try { | |
| 71 | + $this->close(); | |
| 72 | +        } catch (SessionNotAvailableException $e){ | |
| 73 | + // This exception can occur if session is already closed | |
| 74 | + // So it is safe to ignore it and let the garbage collector to proceed | |
| 75 | + } | |
| 76 | + } | |
| 77 | + | |
| 78 | +    protected function initializeSession() { | |
| 79 | + $encryptedSessionData = $this->session->get(self::encryptedSessionName) ?: ''; | |
| 80 | +        try { | |
| 81 | + $this->sessionValues = json_decode( | |
| 82 | + $this->crypto->decrypt($encryptedSessionData, $this->passphrase), | |
| 83 | + true | |
| 84 | + ); | |
| 85 | +        } catch (\Exception $e) { | |
| 86 | + $this->sessionValues = []; | |
| 87 | + } | |
| 88 | + } | |
| 89 | + | |
| 90 | + /** | |
| 91 | + * Set a value in the session | |
| 92 | + * | |
| 93 | + * @param string $key | |
| 94 | + * @param mixed $value | |
| 95 | + */ | |
| 96 | +    public function set(string $key, $value) { | |
| 97 | + $this->sessionValues[$key] = $value; | |
| 98 | + $this->isModified = true; | |
| 99 | + } | |
| 100 | + | |
| 101 | + /** | |
| 102 | + * Get a value from the session | |
| 103 | + * | |
| 104 | + * @param string $key | |
| 105 | + * @return string|null Either the value or null | |
| 106 | + */ | |
| 107 | +    public function get(string $key) { | |
| 108 | +        if(isset($this->sessionValues[$key])) { | |
| 109 | + return $this->sessionValues[$key]; | |
| 110 | + } | |
| 111 | + | |
| 112 | + return null; | |
| 113 | + } | |
| 114 | + | |
| 115 | + /** | |
| 116 | + * Check if a named key exists in the session | |
| 117 | + * | |
| 118 | + * @param string $key | |
| 119 | + * @return bool | |
| 120 | + */ | |
| 121 | +    public function exists(string $key): bool { | |
| 122 | + return isset($this->sessionValues[$key]); | |
| 123 | + } | |
| 124 | + | |
| 125 | + /** | |
| 126 | + * Remove a $key/$value pair from the session | |
| 127 | + * | |
| 128 | + * @param string $key | |
| 129 | + */ | |
| 130 | +    public function remove(string $key) { | |
| 131 | + $this->isModified = true; | |
| 132 | + unset($this->sessionValues[$key]); | |
| 133 | + $this->session->remove(self::encryptedSessionName); | |
| 134 | + } | |
| 135 | + | |
| 136 | + /** | |
| 137 | + * Reset and recreate the session | |
| 138 | + */ | |
| 139 | +    public function clear() { | |
| 140 | +        $requesttoken = $this->get('requesttoken'); | |
| 141 | + $this->sessionValues = []; | |
| 142 | +        if ($requesttoken !== null) { | |
| 143 | +            $this->set('requesttoken', $requesttoken); | |
| 144 | + } | |
| 145 | + $this->isModified = true; | |
| 146 | + $this->session->clear(); | |
| 147 | + } | |
| 148 | + | |
| 149 | + /** | |
| 150 | + * Wrapper around session_regenerate_id | |
| 151 | + * | |
| 152 | + * @param bool $deleteOldSession Whether to delete the old associated session file or not. | |
| 153 | + * @param bool $updateToken Wheater to update the associated auth token | |
| 154 | + * @return void | |
| 155 | + */ | |
| 156 | +    public function regenerateId(bool $deleteOldSession = true, bool $updateToken = false) { | |
| 157 | + $this->session->regenerateId($deleteOldSession, $updateToken); | |
| 158 | + } | |
| 159 | + | |
| 160 | + /** | |
| 161 | + * Wrapper around session_id | |
| 162 | + * | |
| 163 | + * @return string | |
| 164 | + * @throws SessionNotAvailableException | |
| 165 | + * @since 9.1.0 | |
| 166 | + */ | |
| 167 | +    public function getId(): string { | |
| 168 | + return $this->session->getId(); | |
| 169 | + } | |
| 170 | + | |
| 171 | + /** | |
| 172 | + * Close the session and release the lock, also writes all changed data in batch | |
| 173 | + */ | |
| 174 | +    public function close() { | |
| 175 | +        if($this->isModified) { | |
| 176 | + $encryptedValue = $this->crypto->encrypt(json_encode($this->sessionValues), $this->passphrase); | |
| 177 | + $this->session->set(self::encryptedSessionName, $encryptedValue); | |
| 178 | + $this->isModified = false; | |
| 179 | + } | |
| 180 | + $this->session->close(); | |
| 181 | + } | |
| 182 | + | |
| 183 | + /** | |
| 184 | + * @param mixed $offset | |
| 185 | + * @return bool | |
| 186 | + */ | |
| 187 | +    public function offsetExists($offset): bool { | |
| 188 | + return $this->exists($offset); | |
| 189 | + } | |
| 190 | + | |
| 191 | + /** | |
| 192 | + * @param mixed $offset | |
| 193 | + * @return mixed | |
| 194 | + */ | |
| 195 | +    public function offsetGet($offset) { | |
| 196 | + return $this->get($offset); | |
| 197 | + } | |
| 198 | + | |
| 199 | + /** | |
| 200 | + * @param mixed $offset | |
| 201 | + * @param mixed $value | |
| 202 | + */ | |
| 203 | +    public function offsetSet($offset, $value) { | |
| 204 | + $this->set($offset, $value); | |
| 205 | + } | |
| 206 | + | |
| 207 | + /** | |
| 208 | + * @param mixed $offset | |
| 209 | + */ | |
| 210 | +    public function offsetUnset($offset) { | |
| 211 | + $this->remove($offset); | |
| 212 | + } | |
| 213 | 213 | } | 
| @@ -70,604 +70,604 @@ | ||
| 70 | 70 | */ | 
| 71 | 71 |  class ShareController extends Controller { | 
| 72 | 72 | |
| 73 | - /** @var IConfig */ | |
| 74 | - protected $config; | |
| 75 | - /** @var IURLGenerator */ | |
| 76 | - protected $urlGenerator; | |
| 77 | - /** @var IUserManager */ | |
| 78 | - protected $userManager; | |
| 79 | - /** @var ILogger */ | |
| 80 | - protected $logger; | |
| 81 | - /** @var \OCP\Activity\IManager */ | |
| 82 | - protected $activityManager; | |
| 83 | - /** @var \OCP\Share\IManager */ | |
| 84 | - protected $shareManager; | |
| 85 | - /** @var ISession */ | |
| 86 | - protected $session; | |
| 87 | - /** @var IPreview */ | |
| 88 | - protected $previewManager; | |
| 89 | - /** @var IRootFolder */ | |
| 90 | - protected $rootFolder; | |
| 91 | - /** @var FederatedShareProvider */ | |
| 92 | - protected $federatedShareProvider; | |
| 93 | - /** @var EventDispatcherInterface */ | |
| 94 | - protected $eventDispatcher; | |
| 95 | - /** @var IL10N */ | |
| 96 | - protected $l10n; | |
| 97 | - /** @var Defaults */ | |
| 98 | - protected $defaults; | |
| 99 | - | |
| 100 | - /** | |
| 101 | - * @param string $appName | |
| 102 | - * @param IRequest $request | |
| 103 | - * @param IConfig $config | |
| 104 | - * @param IURLGenerator $urlGenerator | |
| 105 | - * @param IUserManager $userManager | |
| 106 | - * @param ILogger $logger | |
| 107 | - * @param \OCP\Activity\IManager $activityManager | |
| 108 | - * @param \OCP\Share\IManager $shareManager | |
| 109 | - * @param ISession $session | |
| 110 | - * @param IPreview $previewManager | |
| 111 | - * @param IRootFolder $rootFolder | |
| 112 | - * @param FederatedShareProvider $federatedShareProvider | |
| 113 | - * @param EventDispatcherInterface $eventDispatcher | |
| 114 | - * @param IL10N $l10n | |
| 115 | - * @param Defaults $defaults | |
| 116 | - */ | |
| 117 | - public function __construct($appName, | |
| 118 | - IRequest $request, | |
| 119 | - IConfig $config, | |
| 120 | - IURLGenerator $urlGenerator, | |
| 121 | - IUserManager $userManager, | |
| 122 | - ILogger $logger, | |
| 123 | - \OCP\Activity\IManager $activityManager, | |
| 124 | - \OCP\Share\IManager $shareManager, | |
| 125 | - ISession $session, | |
| 126 | - IPreview $previewManager, | |
| 127 | - IRootFolder $rootFolder, | |
| 128 | - FederatedShareProvider $federatedShareProvider, | |
| 129 | - EventDispatcherInterface $eventDispatcher, | |
| 130 | - IL10N $l10n, | |
| 131 | -								Defaults $defaults) { | |
| 132 | - parent::__construct($appName, $request); | |
| 133 | - | |
| 134 | - $this->config = $config; | |
| 135 | - $this->urlGenerator = $urlGenerator; | |
| 136 | - $this->userManager = $userManager; | |
| 137 | - $this->logger = $logger; | |
| 138 | - $this->activityManager = $activityManager; | |
| 139 | - $this->shareManager = $shareManager; | |
| 140 | - $this->session = $session; | |
| 141 | - $this->previewManager = $previewManager; | |
| 142 | - $this->rootFolder = $rootFolder; | |
| 143 | - $this->federatedShareProvider = $federatedShareProvider; | |
| 144 | - $this->eventDispatcher = $eventDispatcher; | |
| 145 | - $this->l10n = $l10n; | |
| 146 | - $this->defaults = $defaults; | |
| 147 | - } | |
| 148 | - | |
| 149 | - /** | |
| 150 | - * @PublicPage | |
| 151 | - * @NoCSRFRequired | |
| 152 | - * | |
| 153 | - * @param string $token | |
| 154 | - * @return TemplateResponse|RedirectResponse | |
| 155 | - */ | |
| 156 | -	public function showAuthenticate($token) { | |
| 157 | - $share = $this->shareManager->getShareByToken($token); | |
| 158 | - | |
| 159 | -		if($this->linkShareAuth($share)) { | |
| 160 | -			return new RedirectResponse($this->urlGenerator->linkToRoute('files_sharing.sharecontroller.showShare', array('token' => $token))); | |
| 161 | - } | |
| 162 | - | |
| 163 | - return new TemplateResponse($this->appName, 'authenticate', array(), 'guest'); | |
| 164 | - } | |
| 165 | - | |
| 166 | - /** | |
| 167 | - * @PublicPage | |
| 168 | - * @UseSession | |
| 169 | - * @BruteForceProtection(action=publicLinkAuth) | |
| 170 | - * | |
| 171 | - * Authenticates against password-protected shares | |
| 172 | - * @param string $token | |
| 173 | - * @param string $redirect | |
| 174 | - * @param string $password | |
| 175 | - * @return RedirectResponse|TemplateResponse|NotFoundResponse | |
| 176 | - */ | |
| 177 | -	public function authenticate($token, $redirect, $password = '') { | |
| 178 | - | |
| 179 | - // Check whether share exists | |
| 180 | -		try { | |
| 181 | - $share = $this->shareManager->getShareByToken($token); | |
| 182 | -		} catch (ShareNotFound $e) { | |
| 183 | - return new NotFoundResponse(); | |
| 184 | - } | |
| 185 | - | |
| 186 | - $authenticate = $this->linkShareAuth($share, $password); | |
| 187 | - | |
| 188 | - // if download was requested before auth, redirect to download | |
| 189 | -		if ($authenticate === true && $redirect === 'download') { | |
| 190 | - return new RedirectResponse($this->urlGenerator->linkToRoute( | |
| 191 | - 'files_sharing.sharecontroller.downloadShare', | |
| 192 | -				array('token' => $token)) | |
| 193 | - ); | |
| 194 | -		} else if ($authenticate === true) { | |
| 195 | - return new RedirectResponse($this->urlGenerator->linkToRoute( | |
| 196 | - 'files_sharing.sharecontroller.showShare', | |
| 197 | -				array('token' => $token)) | |
| 198 | - ); | |
| 199 | - } | |
| 200 | - | |
| 201 | -		$response = new TemplateResponse($this->appName, 'authenticate', array('wrongpw' => true), 'guest'); | |
| 202 | - $response->throttle(); | |
| 203 | - return $response; | |
| 204 | - } | |
| 205 | - | |
| 206 | - /** | |
| 207 | - * Authenticate a link item with the given password. | |
| 208 | - * Or use the session if no password is provided. | |
| 209 | - * | |
| 210 | - * This is a modified version of Helper::authenticate | |
| 211 | - * TODO: Try to merge back eventually with Helper::authenticate | |
| 212 | - * | |
| 213 | - * @param \OCP\Share\IShare $share | |
| 214 | - * @param string|null $password | |
| 215 | - * @return bool | |
| 216 | - */ | |
| 217 | -	private function linkShareAuth(\OCP\Share\IShare $share, $password = null) { | |
| 218 | -		if ($password !== null) { | |
| 219 | -			if ($this->shareManager->checkPassword($share, $password)) { | |
| 220 | - $this->session->regenerateId(true, true); | |
| 221 | -				$this->session->set('public_link_authenticated', (string)$share->getId()); | |
| 222 | -			} else { | |
| 223 | - $this->emitAccessShareHook($share, 403, 'Wrong password'); | |
| 224 | - return false; | |
| 225 | - } | |
| 226 | -		} else { | |
| 227 | - // not authenticated ? | |
| 228 | -			if ( ! $this->session->exists('public_link_authenticated') | |
| 229 | -				|| $this->session->get('public_link_authenticated') !== (string)$share->getId()) { | |
| 230 | - return false; | |
| 231 | - } | |
| 232 | - } | |
| 233 | - return true; | |
| 234 | - } | |
| 235 | - | |
| 236 | - /** | |
| 237 | - * throws hooks when a share is attempted to be accessed | |
| 238 | - * | |
| 239 | - * @param \OCP\Share\IShare|string $share the Share instance if available, | |
| 240 | - * otherwise token | |
| 241 | - * @param int $errorCode | |
| 242 | - * @param string $errorMessage | |
| 243 | - * @throws \OC\HintException | |
| 244 | - * @throws \OC\ServerNotAvailableException | |
| 245 | - */ | |
| 246 | -	protected function emitAccessShareHook($share, $errorCode = 200, $errorMessage = '') { | |
| 247 | - $itemType = $itemSource = $uidOwner = ''; | |
| 248 | - $token = $share; | |
| 249 | - $exception = null; | |
| 250 | -		if($share instanceof \OCP\Share\IShare) { | |
| 251 | -			try { | |
| 252 | - $token = $share->getToken(); | |
| 253 | - $uidOwner = $share->getSharedBy(); | |
| 254 | - $itemType = $share->getNodeType(); | |
| 255 | - $itemSource = $share->getNodeId(); | |
| 256 | -			} catch (\Exception $e) { | |
| 257 | - // we log what we know and pass on the exception afterwards | |
| 258 | - $exception = $e; | |
| 259 | - } | |
| 260 | - } | |
| 261 | - \OC_Hook::emit(Share::class, 'share_link_access', [ | |
| 262 | - 'itemType' => $itemType, | |
| 263 | - 'itemSource' => $itemSource, | |
| 264 | - 'uidOwner' => $uidOwner, | |
| 265 | - 'token' => $token, | |
| 266 | - 'errorCode' => $errorCode, | |
| 267 | - 'errorMessage' => $errorMessage, | |
| 268 | - ]); | |
| 269 | -		if(!is_null($exception)) { | |
| 270 | - throw $exception; | |
| 271 | - } | |
| 272 | - } | |
| 273 | - | |
| 274 | - /** | |
| 275 | - * Validate the permissions of the share | |
| 276 | - * | |
| 277 | - * @param Share\IShare $share | |
| 278 | - * @return bool | |
| 279 | - */ | |
| 280 | -	private function validateShare(\OCP\Share\IShare $share) { | |
| 281 | - return $share->getNode()->isReadable() && $share->getNode()->isShareable(); | |
| 282 | - } | |
| 283 | - | |
| 284 | - /** | |
| 285 | - * @PublicPage | |
| 286 | - * @NoCSRFRequired | |
| 287 | - * | |
| 288 | - * @param string $token | |
| 289 | - * @param string $path | |
| 290 | - * @return TemplateResponse|RedirectResponse|NotFoundResponse | |
| 291 | - * @throws NotFoundException | |
| 292 | - * @throws \Exception | |
| 293 | - */ | |
| 294 | -	public function showShare($token, $path = '') { | |
| 295 | - \OC_User::setIncognitoMode(true); | |
| 296 | - | |
| 297 | - // Check whether share exists | |
| 298 | -		try { | |
| 299 | - $share = $this->shareManager->getShareByToken($token); | |
| 300 | -		} catch (ShareNotFound $e) { | |
| 301 | - $this->emitAccessShareHook($token, 404, 'Share not found'); | |
| 302 | - return new NotFoundResponse(); | |
| 303 | - } | |
| 304 | - | |
| 305 | - // Share is password protected - check whether the user is permitted to access the share | |
| 306 | -		if ($share->getPassword() !== null && !$this->linkShareAuth($share)) { | |
| 307 | -			return new RedirectResponse($this->urlGenerator->linkToRoute('files_sharing.sharecontroller.authenticate', | |
| 308 | -				array('token' => $token, 'redirect' => 'preview'))); | |
| 309 | - } | |
| 310 | - | |
| 311 | -		if (!$this->validateShare($share)) { | |
| 312 | - throw new NotFoundException(); | |
| 313 | - } | |
| 314 | - // We can't get the path of a file share | |
| 315 | -		try { | |
| 316 | -			if ($share->getNode() instanceof \OCP\Files\File && $path !== '') { | |
| 317 | - $this->emitAccessShareHook($share, 404, 'Share not found'); | |
| 318 | - throw new NotFoundException(); | |
| 319 | - } | |
| 320 | -		} catch (\Exception $e) { | |
| 321 | - $this->emitAccessShareHook($share, 404, 'Share not found'); | |
| 322 | - throw $e; | |
| 323 | - } | |
| 324 | - | |
| 325 | - $shareTmpl = []; | |
| 326 | - $shareTmpl['displayName'] = $this->userManager->get($share->getShareOwner())->getDisplayName(); | |
| 327 | - $shareTmpl['owner'] = $share->getShareOwner(); | |
| 328 | - $shareTmpl['filename'] = $share->getNode()->getName(); | |
| 329 | - $shareTmpl['directory_path'] = $share->getTarget(); | |
| 330 | - $shareTmpl['mimetype'] = $share->getNode()->getMimetype(); | |
| 331 | - $shareTmpl['previewSupported'] = $this->previewManager->isMimeSupported($share->getNode()->getMimetype()); | |
| 332 | - $shareTmpl['dirToken'] = $token; | |
| 333 | - $shareTmpl['sharingToken'] = $token; | |
| 334 | - $shareTmpl['server2serversharing'] = $this->federatedShareProvider->isOutgoingServer2serverShareEnabled(); | |
| 335 | - $shareTmpl['protected'] = $share->getPassword() !== null ? 'true' : 'false'; | |
| 336 | - $shareTmpl['dir'] = ''; | |
| 337 | - $shareTmpl['nonHumanFileSize'] = $share->getNode()->getSize(); | |
| 338 | - $shareTmpl['fileSize'] = \OCP\Util::humanFileSize($share->getNode()->getSize()); | |
| 339 | - | |
| 340 | - // Show file list | |
| 341 | - $hideFileList = false; | |
| 342 | -		if ($share->getNode() instanceof \OCP\Files\Folder) { | |
| 343 | - /** @var \OCP\Files\Folder $rootFolder */ | |
| 344 | - $rootFolder = $share->getNode(); | |
| 345 | - | |
| 346 | -			try { | |
| 347 | - $folderNode = $rootFolder->get($path); | |
| 348 | -			} catch (\OCP\Files\NotFoundException $e) { | |
| 349 | - $this->emitAccessShareHook($share, 404, 'Share not found'); | |
| 350 | - throw new NotFoundException(); | |
| 351 | - } | |
| 352 | - | |
| 353 | - $shareTmpl['dir'] = $rootFolder->getRelativePath($folderNode->getPath()); | |
| 354 | - | |
| 355 | - /* | |
| 73 | + /** @var IConfig */ | |
| 74 | + protected $config; | |
| 75 | + /** @var IURLGenerator */ | |
| 76 | + protected $urlGenerator; | |
| 77 | + /** @var IUserManager */ | |
| 78 | + protected $userManager; | |
| 79 | + /** @var ILogger */ | |
| 80 | + protected $logger; | |
| 81 | + /** @var \OCP\Activity\IManager */ | |
| 82 | + protected $activityManager; | |
| 83 | + /** @var \OCP\Share\IManager */ | |
| 84 | + protected $shareManager; | |
| 85 | + /** @var ISession */ | |
| 86 | + protected $session; | |
| 87 | + /** @var IPreview */ | |
| 88 | + protected $previewManager; | |
| 89 | + /** @var IRootFolder */ | |
| 90 | + protected $rootFolder; | |
| 91 | + /** @var FederatedShareProvider */ | |
| 92 | + protected $federatedShareProvider; | |
| 93 | + /** @var EventDispatcherInterface */ | |
| 94 | + protected $eventDispatcher; | |
| 95 | + /** @var IL10N */ | |
| 96 | + protected $l10n; | |
| 97 | + /** @var Defaults */ | |
| 98 | + protected $defaults; | |
| 99 | + | |
| 100 | + /** | |
| 101 | + * @param string $appName | |
| 102 | + * @param IRequest $request | |
| 103 | + * @param IConfig $config | |
| 104 | + * @param IURLGenerator $urlGenerator | |
| 105 | + * @param IUserManager $userManager | |
| 106 | + * @param ILogger $logger | |
| 107 | + * @param \OCP\Activity\IManager $activityManager | |
| 108 | + * @param \OCP\Share\IManager $shareManager | |
| 109 | + * @param ISession $session | |
| 110 | + * @param IPreview $previewManager | |
| 111 | + * @param IRootFolder $rootFolder | |
| 112 | + * @param FederatedShareProvider $federatedShareProvider | |
| 113 | + * @param EventDispatcherInterface $eventDispatcher | |
| 114 | + * @param IL10N $l10n | |
| 115 | + * @param Defaults $defaults | |
| 116 | + */ | |
| 117 | + public function __construct($appName, | |
| 118 | + IRequest $request, | |
| 119 | + IConfig $config, | |
| 120 | + IURLGenerator $urlGenerator, | |
| 121 | + IUserManager $userManager, | |
| 122 | + ILogger $logger, | |
| 123 | + \OCP\Activity\IManager $activityManager, | |
| 124 | + \OCP\Share\IManager $shareManager, | |
| 125 | + ISession $session, | |
| 126 | + IPreview $previewManager, | |
| 127 | + IRootFolder $rootFolder, | |
| 128 | + FederatedShareProvider $federatedShareProvider, | |
| 129 | + EventDispatcherInterface $eventDispatcher, | |
| 130 | + IL10N $l10n, | |
| 131 | +                                Defaults $defaults) { | |
| 132 | + parent::__construct($appName, $request); | |
| 133 | + | |
| 134 | + $this->config = $config; | |
| 135 | + $this->urlGenerator = $urlGenerator; | |
| 136 | + $this->userManager = $userManager; | |
| 137 | + $this->logger = $logger; | |
| 138 | + $this->activityManager = $activityManager; | |
| 139 | + $this->shareManager = $shareManager; | |
| 140 | + $this->session = $session; | |
| 141 | + $this->previewManager = $previewManager; | |
| 142 | + $this->rootFolder = $rootFolder; | |
| 143 | + $this->federatedShareProvider = $federatedShareProvider; | |
| 144 | + $this->eventDispatcher = $eventDispatcher; | |
| 145 | + $this->l10n = $l10n; | |
| 146 | + $this->defaults = $defaults; | |
| 147 | + } | |
| 148 | + | |
| 149 | + /** | |
| 150 | + * @PublicPage | |
| 151 | + * @NoCSRFRequired | |
| 152 | + * | |
| 153 | + * @param string $token | |
| 154 | + * @return TemplateResponse|RedirectResponse | |
| 155 | + */ | |
| 156 | +    public function showAuthenticate($token) { | |
| 157 | + $share = $this->shareManager->getShareByToken($token); | |
| 158 | + | |
| 159 | +        if($this->linkShareAuth($share)) { | |
| 160 | +            return new RedirectResponse($this->urlGenerator->linkToRoute('files_sharing.sharecontroller.showShare', array('token' => $token))); | |
| 161 | + } | |
| 162 | + | |
| 163 | + return new TemplateResponse($this->appName, 'authenticate', array(), 'guest'); | |
| 164 | + } | |
| 165 | + | |
| 166 | + /** | |
| 167 | + * @PublicPage | |
| 168 | + * @UseSession | |
| 169 | + * @BruteForceProtection(action=publicLinkAuth) | |
| 170 | + * | |
| 171 | + * Authenticates against password-protected shares | |
| 172 | + * @param string $token | |
| 173 | + * @param string $redirect | |
| 174 | + * @param string $password | |
| 175 | + * @return RedirectResponse|TemplateResponse|NotFoundResponse | |
| 176 | + */ | |
| 177 | +    public function authenticate($token, $redirect, $password = '') { | |
| 178 | + | |
| 179 | + // Check whether share exists | |
| 180 | +        try { | |
| 181 | + $share = $this->shareManager->getShareByToken($token); | |
| 182 | +        } catch (ShareNotFound $e) { | |
| 183 | + return new NotFoundResponse(); | |
| 184 | + } | |
| 185 | + | |
| 186 | + $authenticate = $this->linkShareAuth($share, $password); | |
| 187 | + | |
| 188 | + // if download was requested before auth, redirect to download | |
| 189 | +        if ($authenticate === true && $redirect === 'download') { | |
| 190 | + return new RedirectResponse($this->urlGenerator->linkToRoute( | |
| 191 | + 'files_sharing.sharecontroller.downloadShare', | |
| 192 | +                array('token' => $token)) | |
| 193 | + ); | |
| 194 | +        } else if ($authenticate === true) { | |
| 195 | + return new RedirectResponse($this->urlGenerator->linkToRoute( | |
| 196 | + 'files_sharing.sharecontroller.showShare', | |
| 197 | +                array('token' => $token)) | |
| 198 | + ); | |
| 199 | + } | |
| 200 | + | |
| 201 | +        $response = new TemplateResponse($this->appName, 'authenticate', array('wrongpw' => true), 'guest'); | |
| 202 | + $response->throttle(); | |
| 203 | + return $response; | |
| 204 | + } | |
| 205 | + | |
| 206 | + /** | |
| 207 | + * Authenticate a link item with the given password. | |
| 208 | + * Or use the session if no password is provided. | |
| 209 | + * | |
| 210 | + * This is a modified version of Helper::authenticate | |
| 211 | + * TODO: Try to merge back eventually with Helper::authenticate | |
| 212 | + * | |
| 213 | + * @param \OCP\Share\IShare $share | |
| 214 | + * @param string|null $password | |
| 215 | + * @return bool | |
| 216 | + */ | |
| 217 | +    private function linkShareAuth(\OCP\Share\IShare $share, $password = null) { | |
| 218 | +        if ($password !== null) { | |
| 219 | +            if ($this->shareManager->checkPassword($share, $password)) { | |
| 220 | + $this->session->regenerateId(true, true); | |
| 221 | +                $this->session->set('public_link_authenticated', (string)$share->getId()); | |
| 222 | +            } else { | |
| 223 | + $this->emitAccessShareHook($share, 403, 'Wrong password'); | |
| 224 | + return false; | |
| 225 | + } | |
| 226 | +        } else { | |
| 227 | + // not authenticated ? | |
| 228 | +            if ( ! $this->session->exists('public_link_authenticated') | |
| 229 | +                || $this->session->get('public_link_authenticated') !== (string)$share->getId()) { | |
| 230 | + return false; | |
| 231 | + } | |
| 232 | + } | |
| 233 | + return true; | |
| 234 | + } | |
| 235 | + | |
| 236 | + /** | |
| 237 | + * throws hooks when a share is attempted to be accessed | |
| 238 | + * | |
| 239 | + * @param \OCP\Share\IShare|string $share the Share instance if available, | |
| 240 | + * otherwise token | |
| 241 | + * @param int $errorCode | |
| 242 | + * @param string $errorMessage | |
| 243 | + * @throws \OC\HintException | |
| 244 | + * @throws \OC\ServerNotAvailableException | |
| 245 | + */ | |
| 246 | +    protected function emitAccessShareHook($share, $errorCode = 200, $errorMessage = '') { | |
| 247 | + $itemType = $itemSource = $uidOwner = ''; | |
| 248 | + $token = $share; | |
| 249 | + $exception = null; | |
| 250 | +        if($share instanceof \OCP\Share\IShare) { | |
| 251 | +            try { | |
| 252 | + $token = $share->getToken(); | |
| 253 | + $uidOwner = $share->getSharedBy(); | |
| 254 | + $itemType = $share->getNodeType(); | |
| 255 | + $itemSource = $share->getNodeId(); | |
| 256 | +            } catch (\Exception $e) { | |
| 257 | + // we log what we know and pass on the exception afterwards | |
| 258 | + $exception = $e; | |
| 259 | + } | |
| 260 | + } | |
| 261 | + \OC_Hook::emit(Share::class, 'share_link_access', [ | |
| 262 | + 'itemType' => $itemType, | |
| 263 | + 'itemSource' => $itemSource, | |
| 264 | + 'uidOwner' => $uidOwner, | |
| 265 | + 'token' => $token, | |
| 266 | + 'errorCode' => $errorCode, | |
| 267 | + 'errorMessage' => $errorMessage, | |
| 268 | + ]); | |
| 269 | +        if(!is_null($exception)) { | |
| 270 | + throw $exception; | |
| 271 | + } | |
| 272 | + } | |
| 273 | + | |
| 274 | + /** | |
| 275 | + * Validate the permissions of the share | |
| 276 | + * | |
| 277 | + * @param Share\IShare $share | |
| 278 | + * @return bool | |
| 279 | + */ | |
| 280 | +    private function validateShare(\OCP\Share\IShare $share) { | |
| 281 | + return $share->getNode()->isReadable() && $share->getNode()->isShareable(); | |
| 282 | + } | |
| 283 | + | |
| 284 | + /** | |
| 285 | + * @PublicPage | |
| 286 | + * @NoCSRFRequired | |
| 287 | + * | |
| 288 | + * @param string $token | |
| 289 | + * @param string $path | |
| 290 | + * @return TemplateResponse|RedirectResponse|NotFoundResponse | |
| 291 | + * @throws NotFoundException | |
| 292 | + * @throws \Exception | |
| 293 | + */ | |
| 294 | +    public function showShare($token, $path = '') { | |
| 295 | + \OC_User::setIncognitoMode(true); | |
| 296 | + | |
| 297 | + // Check whether share exists | |
| 298 | +        try { | |
| 299 | + $share = $this->shareManager->getShareByToken($token); | |
| 300 | +        } catch (ShareNotFound $e) { | |
| 301 | + $this->emitAccessShareHook($token, 404, 'Share not found'); | |
| 302 | + return new NotFoundResponse(); | |
| 303 | + } | |
| 304 | + | |
| 305 | + // Share is password protected - check whether the user is permitted to access the share | |
| 306 | +        if ($share->getPassword() !== null && !$this->linkShareAuth($share)) { | |
| 307 | +            return new RedirectResponse($this->urlGenerator->linkToRoute('files_sharing.sharecontroller.authenticate', | |
| 308 | +                array('token' => $token, 'redirect' => 'preview'))); | |
| 309 | + } | |
| 310 | + | |
| 311 | +        if (!$this->validateShare($share)) { | |
| 312 | + throw new NotFoundException(); | |
| 313 | + } | |
| 314 | + // We can't get the path of a file share | |
| 315 | +        try { | |
| 316 | +            if ($share->getNode() instanceof \OCP\Files\File && $path !== '') { | |
| 317 | + $this->emitAccessShareHook($share, 404, 'Share not found'); | |
| 318 | + throw new NotFoundException(); | |
| 319 | + } | |
| 320 | +        } catch (\Exception $e) { | |
| 321 | + $this->emitAccessShareHook($share, 404, 'Share not found'); | |
| 322 | + throw $e; | |
| 323 | + } | |
| 324 | + | |
| 325 | + $shareTmpl = []; | |
| 326 | + $shareTmpl['displayName'] = $this->userManager->get($share->getShareOwner())->getDisplayName(); | |
| 327 | + $shareTmpl['owner'] = $share->getShareOwner(); | |
| 328 | + $shareTmpl['filename'] = $share->getNode()->getName(); | |
| 329 | + $shareTmpl['directory_path'] = $share->getTarget(); | |
| 330 | + $shareTmpl['mimetype'] = $share->getNode()->getMimetype(); | |
| 331 | + $shareTmpl['previewSupported'] = $this->previewManager->isMimeSupported($share->getNode()->getMimetype()); | |
| 332 | + $shareTmpl['dirToken'] = $token; | |
| 333 | + $shareTmpl['sharingToken'] = $token; | |
| 334 | + $shareTmpl['server2serversharing'] = $this->federatedShareProvider->isOutgoingServer2serverShareEnabled(); | |
| 335 | + $shareTmpl['protected'] = $share->getPassword() !== null ? 'true' : 'false'; | |
| 336 | + $shareTmpl['dir'] = ''; | |
| 337 | + $shareTmpl['nonHumanFileSize'] = $share->getNode()->getSize(); | |
| 338 | + $shareTmpl['fileSize'] = \OCP\Util::humanFileSize($share->getNode()->getSize()); | |
| 339 | + | |
| 340 | + // Show file list | |
| 341 | + $hideFileList = false; | |
| 342 | +        if ($share->getNode() instanceof \OCP\Files\Folder) { | |
| 343 | + /** @var \OCP\Files\Folder $rootFolder */ | |
| 344 | + $rootFolder = $share->getNode(); | |
| 345 | + | |
| 346 | +            try { | |
| 347 | + $folderNode = $rootFolder->get($path); | |
| 348 | +            } catch (\OCP\Files\NotFoundException $e) { | |
| 349 | + $this->emitAccessShareHook($share, 404, 'Share not found'); | |
| 350 | + throw new NotFoundException(); | |
| 351 | + } | |
| 352 | + | |
| 353 | + $shareTmpl['dir'] = $rootFolder->getRelativePath($folderNode->getPath()); | |
| 354 | + | |
| 355 | + /* | |
| 356 | 356 | * The OC_Util methods require a view. This just uses the node API | 
| 357 | 357 | */ | 
| 358 | - $freeSpace = $share->getNode()->getStorage()->free_space($share->getNode()->getInternalPath()); | |
| 359 | -			if ($freeSpace < \OCP\Files\FileInfo::SPACE_UNLIMITED) { | |
| 360 | - $freeSpace = max($freeSpace, 0); | |
| 361 | -			} else { | |
| 362 | - $freeSpace = (INF > 0) ? INF: PHP_INT_MAX; // work around https://bugs.php.net/bug.php?id=69188 | |
| 363 | - } | |
| 364 | - | |
| 365 | - $hideFileList = !($share->getPermissions() & \OCP\Constants::PERMISSION_READ); | |
| 366 | - $maxUploadFilesize = $freeSpace; | |
| 367 | - | |
| 368 | -			$folder = new Template('files', 'list', ''); | |
| 369 | -			$folder->assign('dir', $rootFolder->getRelativePath($folderNode->getPath())); | |
| 370 | -			$folder->assign('dirToken', $token); | |
| 371 | -			$folder->assign('permissions', \OCP\Constants::PERMISSION_READ); | |
| 372 | -			$folder->assign('isPublic', true); | |
| 373 | -			$folder->assign('hideFileList', $hideFileList); | |
| 374 | -			$folder->assign('publicUploadEnabled', 'no'); | |
| 375 | -			$folder->assign('uploadMaxFilesize', $maxUploadFilesize); | |
| 376 | -			$folder->assign('uploadMaxHumanFilesize', \OCP\Util::humanFileSize($maxUploadFilesize)); | |
| 377 | -			$folder->assign('freeSpace', $freeSpace); | |
| 378 | -			$folder->assign('usedSpacePercent', 0); | |
| 379 | -			$folder->assign('trash', false); | |
| 380 | - $shareTmpl['folder'] = $folder->fetchPage(); | |
| 381 | - } | |
| 382 | - | |
| 383 | - $shareTmpl['hideFileList'] = $hideFileList; | |
| 384 | - $shareTmpl['shareOwner'] = $this->userManager->get($share->getShareOwner())->getDisplayName(); | |
| 385 | -		$shareTmpl['downloadURL'] = $this->urlGenerator->linkToRouteAbsolute('files_sharing.sharecontroller.downloadShare', ['token' => $token]); | |
| 386 | -		$shareTmpl['shareUrl'] = $this->urlGenerator->linkToRouteAbsolute('files_sharing.sharecontroller.showShare', ['token' => $token]); | |
| 387 | -		$shareTmpl['maxSizeAnimateGif'] = $this->config->getSystemValue('max_filesize_animated_gifs_public_sharing', 10); | |
| 388 | -		$shareTmpl['previewEnabled'] = $this->config->getSystemValue('enable_previews', true); | |
| 389 | -		$shareTmpl['previewMaxX'] = $this->config->getSystemValue('preview_max_x', 1024); | |
| 390 | -		$shareTmpl['previewMaxY'] = $this->config->getSystemValue('preview_max_y', 1024); | |
| 391 | -		$shareTmpl['disclaimer'] = $this->config->getAppValue('core', 'shareapi_public_link_disclaimertext', null); | |
| 392 | - $shareTmpl['previewURL'] = $shareTmpl['downloadURL']; | |
| 393 | - $ogPreview = ''; | |
| 394 | -		if ($shareTmpl['previewSupported']) { | |
| 395 | - $shareTmpl['previewImage'] = $this->urlGenerator->linkToRouteAbsolute( 'files_sharing.PublicPreview.getPreview', | |
| 396 | - ['x' => 200, 'y' => 200, 'file' => $shareTmpl['directory_path'], 't' => $shareTmpl['dirToken']]); | |
| 397 | - $ogPreview = $shareTmpl['previewImage']; | |
| 398 | - | |
| 399 | - // We just have direct previews for image files | |
| 400 | -			if ($share->getNode()->getMimePart() === 'image') { | |
| 401 | -				$shareTmpl['previewURL'] = $this->urlGenerator->linkToRouteAbsolute('files_sharing.publicpreview.directLink', ['token' => $token]); | |
| 402 | - | |
| 403 | - $ogPreview = $shareTmpl['previewURL']; | |
| 404 | - | |
| 405 | - //Whatapp is kind of picky about their size requirements | |
| 406 | -				if ($this->request->isUserAgent(['/^WhatsApp/'])) { | |
| 407 | -					$ogPreview = $this->urlGenerator->linkToRouteAbsolute('files_sharing.PublicPreview.getPreview', [ | |
| 408 | - 't' => $token, | |
| 409 | - 'x' => 256, | |
| 410 | - 'y' => 256, | |
| 411 | - 'a' => true, | |
| 412 | - ]); | |
| 413 | - } | |
| 414 | - } | |
| 415 | -		} else { | |
| 416 | -			$shareTmpl['previewImage'] = $this->urlGenerator->getAbsoluteURL($this->urlGenerator->imagePath('core', 'favicon-fb.png')); | |
| 417 | - $ogPreview = $shareTmpl['previewImage']; | |
| 418 | - } | |
| 419 | - | |
| 420 | - // Load files we need | |
| 421 | -		\OCP\Util::addScript('files', 'file-upload'); | |
| 422 | -		\OCP\Util::addStyle('files_sharing', 'publicView'); | |
| 423 | -		\OCP\Util::addScript('files_sharing', 'public'); | |
| 424 | -		\OCP\Util::addScript('files', 'fileactions'); | |
| 425 | -		\OCP\Util::addScript('files', 'fileactionsmenu'); | |
| 426 | -		\OCP\Util::addScript('files', 'jquery.fileupload'); | |
| 427 | -		\OCP\Util::addScript('files_sharing', 'files_drop'); | |
| 428 | - | |
| 429 | -		if (isset($shareTmpl['folder'])) { | |
| 430 | - // JS required for folders | |
| 431 | -			\OCP\Util::addStyle('files', 'merged'); | |
| 432 | -			\OCP\Util::addScript('files', 'filesummary'); | |
| 433 | -			\OCP\Util::addScript('files', 'breadcrumb'); | |
| 434 | -			\OCP\Util::addScript('files', 'fileinfomodel'); | |
| 435 | -			\OCP\Util::addScript('files', 'newfilemenu'); | |
| 436 | -			\OCP\Util::addScript('files', 'files'); | |
| 437 | -			\OCP\Util::addScript('files', 'filelist'); | |
| 438 | -			\OCP\Util::addScript('files', 'keyboardshortcuts'); | |
| 439 | - } | |
| 440 | - | |
| 441 | - // OpenGraph Support: http://ogp.me/ | |
| 442 | -		\OCP\Util::addHeader('meta', ['property' => "og:title", 'content' => $shareTmpl['filename']]); | |
| 443 | -		\OCP\Util::addHeader('meta', ['property' => "og:description", 'content' => $this->defaults->getName() . ($this->defaults->getSlogan() !== '' ? ' - ' . $this->defaults->getSlogan() : '')]); | |
| 444 | -		\OCP\Util::addHeader('meta', ['property' => "og:site_name", 'content' => $this->defaults->getName()]); | |
| 445 | -		\OCP\Util::addHeader('meta', ['property' => "og:url", 'content' => $shareTmpl['shareUrl']]); | |
| 446 | -		\OCP\Util::addHeader('meta', ['property' => "og:type", 'content' => "object"]); | |
| 447 | -		\OCP\Util::addHeader('meta', ['property' => "og:image", 'content' => $ogPreview]); | |
| 448 | - | |
| 449 | -		$this->eventDispatcher->dispatch('OCA\Files_Sharing::loadAdditionalScripts'); | |
| 450 | - | |
| 451 | - $csp = new \OCP\AppFramework\Http\ContentSecurityPolicy(); | |
| 452 | -		$csp->addAllowedFrameDomain('\'self\''); | |
| 453 | - | |
| 454 | - $response = new PublicTemplateResponse($this->appName, 'public', $shareTmpl); | |
| 455 | - $response->setHeaderTitle($shareTmpl['filename']); | |
| 456 | -		$response->setHeaderDetails($this->l10n->t('shared by %s', [$shareTmpl['displayName']])); | |
| 457 | - $response->setHeaderActions([ | |
| 458 | -			new SimpleMenuAction('download', $this->l10n->t('Download'), 'icon-download-white', $shareTmpl['downloadURL'], 0), | |
| 459 | -			new SimpleMenuAction('download', $this->l10n->t('Download'), 'icon-download', $shareTmpl['downloadURL'], 10, $shareTmpl['fileSize']), | |
| 460 | -			new LinkMenuAction($this->l10n->t('Direct link'), 'icon-public', $shareTmpl['previewURL']), | |
| 461 | -			new ExternalShareMenuAction($this->l10n->t('Add to your Nextcloud'), 'icon-external', $shareTmpl['owner'], $shareTmpl['displayName'], $shareTmpl['filename']), | |
| 462 | - ]); | |
| 463 | - | |
| 464 | - $response->setContentSecurityPolicy($csp); | |
| 465 | - | |
| 466 | - $this->emitAccessShareHook($share); | |
| 467 | - | |
| 468 | - return $response; | |
| 469 | - } | |
| 470 | - | |
| 471 | - /** | |
| 472 | - * @PublicPage | |
| 473 | - * @NoCSRFRequired | |
| 474 | - * | |
| 475 | - * @param string $token | |
| 476 | - * @param string $files | |
| 477 | - * @param string $path | |
| 478 | - * @param string $downloadStartSecret | |
| 479 | - * @return void|\OCP\AppFramework\Http\Response | |
| 480 | - * @throws NotFoundException | |
| 481 | - */ | |
| 482 | -	public function downloadShare($token, $files = null, $path = '', $downloadStartSecret = '') { | |
| 483 | - \OC_User::setIncognitoMode(true); | |
| 484 | - | |
| 485 | - $share = $this->shareManager->getShareByToken($token); | |
| 486 | - | |
| 487 | -		if(!($share->getPermissions() & \OCP\Constants::PERMISSION_READ)) { | |
| 488 | -			return new \OCP\AppFramework\Http\DataResponse('Share is read-only'); | |
| 489 | - } | |
| 490 | - | |
| 491 | - // Share is password protected - check whether the user is permitted to access the share | |
| 492 | -		if ($share->getPassword() !== null && !$this->linkShareAuth($share)) { | |
| 493 | -			return new RedirectResponse($this->urlGenerator->linkToRoute('files_sharing.sharecontroller.authenticate', | |
| 494 | - ['token' => $token, 'redirect' => 'download'])); | |
| 495 | - } | |
| 496 | - | |
| 497 | - $files_list = null; | |
| 498 | -		if (!is_null($files)) { // download selected files | |
| 499 | - $files_list = json_decode($files); | |
| 500 | - // in case we get only a single file | |
| 501 | -			if ($files_list === null) { | |
| 502 | - $files_list = [$files]; | |
| 503 | - } | |
| 504 | - // Just in case $files is a single int like '1234' | |
| 505 | -			if (!is_array($files_list)) { | |
| 506 | - $files_list = [$files_list]; | |
| 507 | - } | |
| 508 | - } | |
| 509 | - | |
| 510 | - $userFolder = $this->rootFolder->getUserFolder($share->getShareOwner()); | |
| 511 | - $originalSharePath = $userFolder->getRelativePath($share->getNode()->getPath()); | |
| 512 | - | |
| 513 | -		if (!$this->validateShare($share)) { | |
| 514 | - throw new NotFoundException(); | |
| 515 | - } | |
| 516 | - | |
| 517 | - // Single file share | |
| 518 | -		if ($share->getNode() instanceof \OCP\Files\File) { | |
| 519 | - // Single file download | |
| 520 | - $this->singleFileDownloaded($share, $share->getNode()); | |
| 521 | - } | |
| 522 | - // Directory share | |
| 523 | -		else { | |
| 524 | - /** @var \OCP\Files\Folder $node */ | |
| 525 | - $node = $share->getNode(); | |
| 526 | - | |
| 527 | - // Try to get the path | |
| 528 | -			if ($path !== '') { | |
| 529 | -				try { | |
| 530 | - $node = $node->get($path); | |
| 531 | -				} catch (NotFoundException $e) { | |
| 532 | - $this->emitAccessShareHook($share, 404, 'Share not found'); | |
| 533 | - return new NotFoundResponse(); | |
| 534 | - } | |
| 535 | - } | |
| 536 | - | |
| 537 | - $originalSharePath = $userFolder->getRelativePath($node->getPath()); | |
| 538 | - | |
| 539 | -			if ($node instanceof \OCP\Files\File) { | |
| 540 | - // Single file download | |
| 541 | - $this->singleFileDownloaded($share, $share->getNode()); | |
| 542 | -			} else if (!empty($files_list)) { | |
| 543 | - $this->fileListDownloaded($share, $files_list, $node); | |
| 544 | -			} else { | |
| 545 | - // The folder is downloaded | |
| 546 | - $this->singleFileDownloaded($share, $share->getNode()); | |
| 547 | - } | |
| 548 | - } | |
| 549 | - | |
| 550 | - /* FIXME: We should do this all nicely in OCP */ | |
| 551 | - OC_Util::tearDownFS(); | |
| 552 | - OC_Util::setupFS($share->getShareOwner()); | |
| 553 | - | |
| 554 | - /** | |
| 555 | - * this sets a cookie to be able to recognize the start of the download | |
| 556 | - * the content must not be longer than 32 characters and must only contain | |
| 557 | - * alphanumeric characters | |
| 558 | - */ | |
| 559 | - if (!empty($downloadStartSecret) | |
| 560 | - && !isset($downloadStartSecret[32]) | |
| 561 | -			&& preg_match('!^[a-zA-Z0-9]+$!', $downloadStartSecret) === 1) { | |
| 562 | - | |
| 563 | - // FIXME: set on the response once we use an actual app framework response | |
| 564 | -			setcookie('ocDownloadStarted', $downloadStartSecret, time() + 20, '/'); | |
| 565 | - } | |
| 566 | - | |
| 567 | - $this->emitAccessShareHook($share); | |
| 568 | - | |
| 569 | - $server_params = array( 'head' => $this->request->getMethod() === 'HEAD' ); | |
| 570 | - | |
| 571 | - /** | |
| 572 | - * Http range requests support | |
| 573 | - */ | |
| 574 | -		if (isset($_SERVER['HTTP_RANGE'])) { | |
| 575 | -			$server_params['range'] = $this->request->getHeader('Range'); | |
| 576 | - } | |
| 577 | - | |
| 578 | - // download selected files | |
| 579 | -		if (!is_null($files) && $files !== '') { | |
| 580 | - // FIXME: The exit is required here because otherwise the AppFramework is trying to add headers as well | |
| 581 | - // after dispatching the request which results in a "Cannot modify header information" notice. | |
| 582 | - OC_Files::get($originalSharePath, $files_list, $server_params); | |
| 583 | - exit(); | |
| 584 | -		} else { | |
| 585 | - // FIXME: The exit is required here because otherwise the AppFramework is trying to add headers as well | |
| 586 | - // after dispatching the request which results in a "Cannot modify header information" notice. | |
| 587 | - OC_Files::get(dirname($originalSharePath), basename($originalSharePath), $server_params); | |
| 588 | - exit(); | |
| 589 | - } | |
| 590 | - } | |
| 591 | - | |
| 592 | - /** | |
| 593 | - * create activity for every downloaded file | |
| 594 | - * | |
| 595 | - * @param Share\IShare $share | |
| 596 | - * @param array $files_list | |
| 597 | - * @param \OCP\Files\Folder $node | |
| 598 | - */ | |
| 599 | -	protected function fileListDownloaded(Share\IShare $share, array $files_list, \OCP\Files\Folder $node) { | |
| 600 | -		foreach ($files_list as $file) { | |
| 601 | - $subNode = $node->get($file); | |
| 602 | - $this->singleFileDownloaded($share, $subNode); | |
| 603 | - } | |
| 604 | - | |
| 605 | - } | |
| 606 | - | |
| 607 | - /** | |
| 608 | - * create activity if a single file was downloaded from a link share | |
| 609 | - * | |
| 610 | - * @param Share\IShare $share | |
| 611 | - */ | |
| 612 | -	protected function singleFileDownloaded(Share\IShare $share, \OCP\Files\Node $node) { | |
| 613 | - | |
| 614 | - $fileId = $node->getId(); | |
| 615 | - | |
| 616 | - $userFolder = $this->rootFolder->getUserFolder($share->getSharedBy()); | |
| 617 | - $userNodeList = $userFolder->getById($fileId); | |
| 618 | - $userNode = $userNodeList[0]; | |
| 619 | - $ownerFolder = $this->rootFolder->getUserFolder($share->getShareOwner()); | |
| 620 | - $userPath = $userFolder->getRelativePath($userNode->getPath()); | |
| 621 | - $ownerPath = $ownerFolder->getRelativePath($node->getPath()); | |
| 622 | - | |
| 623 | - $parameters = [$userPath]; | |
| 624 | - | |
| 625 | -		if ($share->getShareType() === \OCP\Share::SHARE_TYPE_EMAIL) { | |
| 626 | -			if ($node instanceof \OCP\Files\File) { | |
| 627 | - $subject = Downloads::SUBJECT_SHARED_FILE_BY_EMAIL_DOWNLOADED; | |
| 628 | -			} else { | |
| 629 | - $subject = Downloads::SUBJECT_SHARED_FOLDER_BY_EMAIL_DOWNLOADED; | |
| 630 | - } | |
| 631 | - $parameters[] = $share->getSharedWith(); | |
| 632 | -		} else { | |
| 633 | -			if ($node instanceof \OCP\Files\File) { | |
| 634 | - $subject = Downloads::SUBJECT_PUBLIC_SHARED_FILE_DOWNLOADED; | |
| 635 | -			} else { | |
| 636 | - $subject = Downloads::SUBJECT_PUBLIC_SHARED_FOLDER_DOWNLOADED; | |
| 637 | - } | |
| 638 | - } | |
| 639 | - | |
| 640 | - $this->publishActivity($subject, $parameters, $share->getSharedBy(), $fileId, $userPath); | |
| 641 | - | |
| 642 | -		if ($share->getShareOwner() !== $share->getSharedBy()) { | |
| 643 | - $parameters[0] = $ownerPath; | |
| 644 | - $this->publishActivity($subject, $parameters, $share->getShareOwner(), $fileId, $ownerPath); | |
| 645 | - } | |
| 646 | - } | |
| 647 | - | |
| 648 | - /** | |
| 649 | - * publish activity | |
| 650 | - * | |
| 651 | - * @param string $subject | |
| 652 | - * @param array $parameters | |
| 653 | - * @param string $affectedUser | |
| 654 | - * @param int $fileId | |
| 655 | - * @param string $filePath | |
| 656 | - */ | |
| 657 | - protected function publishActivity($subject, | |
| 658 | - array $parameters, | |
| 659 | - $affectedUser, | |
| 660 | - $fileId, | |
| 661 | -										$filePath) { | |
| 662 | - | |
| 663 | - $event = $this->activityManager->generateEvent(); | |
| 664 | -		$event->setApp('files_sharing') | |
| 665 | -			->setType('public_links') | |
| 666 | - ->setSubject($subject, $parameters) | |
| 667 | - ->setAffectedUser($affectedUser) | |
| 668 | -			->setObject('files', $fileId, $filePath); | |
| 669 | - $this->activityManager->publish($event); | |
| 670 | - } | |
| 358 | + $freeSpace = $share->getNode()->getStorage()->free_space($share->getNode()->getInternalPath()); | |
| 359 | +            if ($freeSpace < \OCP\Files\FileInfo::SPACE_UNLIMITED) { | |
| 360 | + $freeSpace = max($freeSpace, 0); | |
| 361 | +            } else { | |
| 362 | + $freeSpace = (INF > 0) ? INF: PHP_INT_MAX; // work around https://bugs.php.net/bug.php?id=69188 | |
| 363 | + } | |
| 364 | + | |
| 365 | + $hideFileList = !($share->getPermissions() & \OCP\Constants::PERMISSION_READ); | |
| 366 | + $maxUploadFilesize = $freeSpace; | |
| 367 | + | |
| 368 | +            $folder = new Template('files', 'list', ''); | |
| 369 | +            $folder->assign('dir', $rootFolder->getRelativePath($folderNode->getPath())); | |
| 370 | +            $folder->assign('dirToken', $token); | |
| 371 | +            $folder->assign('permissions', \OCP\Constants::PERMISSION_READ); | |
| 372 | +            $folder->assign('isPublic', true); | |
| 373 | +            $folder->assign('hideFileList', $hideFileList); | |
| 374 | +            $folder->assign('publicUploadEnabled', 'no'); | |
| 375 | +            $folder->assign('uploadMaxFilesize', $maxUploadFilesize); | |
| 376 | +            $folder->assign('uploadMaxHumanFilesize', \OCP\Util::humanFileSize($maxUploadFilesize)); | |
| 377 | +            $folder->assign('freeSpace', $freeSpace); | |
| 378 | +            $folder->assign('usedSpacePercent', 0); | |
| 379 | +            $folder->assign('trash', false); | |
| 380 | + $shareTmpl['folder'] = $folder->fetchPage(); | |
| 381 | + } | |
| 382 | + | |
| 383 | + $shareTmpl['hideFileList'] = $hideFileList; | |
| 384 | + $shareTmpl['shareOwner'] = $this->userManager->get($share->getShareOwner())->getDisplayName(); | |
| 385 | +        $shareTmpl['downloadURL'] = $this->urlGenerator->linkToRouteAbsolute('files_sharing.sharecontroller.downloadShare', ['token' => $token]); | |
| 386 | +        $shareTmpl['shareUrl'] = $this->urlGenerator->linkToRouteAbsolute('files_sharing.sharecontroller.showShare', ['token' => $token]); | |
| 387 | +        $shareTmpl['maxSizeAnimateGif'] = $this->config->getSystemValue('max_filesize_animated_gifs_public_sharing', 10); | |
| 388 | +        $shareTmpl['previewEnabled'] = $this->config->getSystemValue('enable_previews', true); | |
| 389 | +        $shareTmpl['previewMaxX'] = $this->config->getSystemValue('preview_max_x', 1024); | |
| 390 | +        $shareTmpl['previewMaxY'] = $this->config->getSystemValue('preview_max_y', 1024); | |
| 391 | +        $shareTmpl['disclaimer'] = $this->config->getAppValue('core', 'shareapi_public_link_disclaimertext', null); | |
| 392 | + $shareTmpl['previewURL'] = $shareTmpl['downloadURL']; | |
| 393 | + $ogPreview = ''; | |
| 394 | +        if ($shareTmpl['previewSupported']) { | |
| 395 | + $shareTmpl['previewImage'] = $this->urlGenerator->linkToRouteAbsolute( 'files_sharing.PublicPreview.getPreview', | |
| 396 | + ['x' => 200, 'y' => 200, 'file' => $shareTmpl['directory_path'], 't' => $shareTmpl['dirToken']]); | |
| 397 | + $ogPreview = $shareTmpl['previewImage']; | |
| 398 | + | |
| 399 | + // We just have direct previews for image files | |
| 400 | +            if ($share->getNode()->getMimePart() === 'image') { | |
| 401 | +                $shareTmpl['previewURL'] = $this->urlGenerator->linkToRouteAbsolute('files_sharing.publicpreview.directLink', ['token' => $token]); | |
| 402 | + | |
| 403 | + $ogPreview = $shareTmpl['previewURL']; | |
| 404 | + | |
| 405 | + //Whatapp is kind of picky about their size requirements | |
| 406 | +                if ($this->request->isUserAgent(['/^WhatsApp/'])) { | |
| 407 | +                    $ogPreview = $this->urlGenerator->linkToRouteAbsolute('files_sharing.PublicPreview.getPreview', [ | |
| 408 | + 't' => $token, | |
| 409 | + 'x' => 256, | |
| 410 | + 'y' => 256, | |
| 411 | + 'a' => true, | |
| 412 | + ]); | |
| 413 | + } | |
| 414 | + } | |
| 415 | +        } else { | |
| 416 | +            $shareTmpl['previewImage'] = $this->urlGenerator->getAbsoluteURL($this->urlGenerator->imagePath('core', 'favicon-fb.png')); | |
| 417 | + $ogPreview = $shareTmpl['previewImage']; | |
| 418 | + } | |
| 419 | + | |
| 420 | + // Load files we need | |
| 421 | +        \OCP\Util::addScript('files', 'file-upload'); | |
| 422 | +        \OCP\Util::addStyle('files_sharing', 'publicView'); | |
| 423 | +        \OCP\Util::addScript('files_sharing', 'public'); | |
| 424 | +        \OCP\Util::addScript('files', 'fileactions'); | |
| 425 | +        \OCP\Util::addScript('files', 'fileactionsmenu'); | |
| 426 | +        \OCP\Util::addScript('files', 'jquery.fileupload'); | |
| 427 | +        \OCP\Util::addScript('files_sharing', 'files_drop'); | |
| 428 | + | |
| 429 | +        if (isset($shareTmpl['folder'])) { | |
| 430 | + // JS required for folders | |
| 431 | +            \OCP\Util::addStyle('files', 'merged'); | |
| 432 | +            \OCP\Util::addScript('files', 'filesummary'); | |
| 433 | +            \OCP\Util::addScript('files', 'breadcrumb'); | |
| 434 | +            \OCP\Util::addScript('files', 'fileinfomodel'); | |
| 435 | +            \OCP\Util::addScript('files', 'newfilemenu'); | |
| 436 | +            \OCP\Util::addScript('files', 'files'); | |
| 437 | +            \OCP\Util::addScript('files', 'filelist'); | |
| 438 | +            \OCP\Util::addScript('files', 'keyboardshortcuts'); | |
| 439 | + } | |
| 440 | + | |
| 441 | + // OpenGraph Support: http://ogp.me/ | |
| 442 | +        \OCP\Util::addHeader('meta', ['property' => "og:title", 'content' => $shareTmpl['filename']]); | |
| 443 | +        \OCP\Util::addHeader('meta', ['property' => "og:description", 'content' => $this->defaults->getName() . ($this->defaults->getSlogan() !== '' ? ' - ' . $this->defaults->getSlogan() : '')]); | |
| 444 | +        \OCP\Util::addHeader('meta', ['property' => "og:site_name", 'content' => $this->defaults->getName()]); | |
| 445 | +        \OCP\Util::addHeader('meta', ['property' => "og:url", 'content' => $shareTmpl['shareUrl']]); | |
| 446 | +        \OCP\Util::addHeader('meta', ['property' => "og:type", 'content' => "object"]); | |
| 447 | +        \OCP\Util::addHeader('meta', ['property' => "og:image", 'content' => $ogPreview]); | |
| 448 | + | |
| 449 | +        $this->eventDispatcher->dispatch('OCA\Files_Sharing::loadAdditionalScripts'); | |
| 450 | + | |
| 451 | + $csp = new \OCP\AppFramework\Http\ContentSecurityPolicy(); | |
| 452 | +        $csp->addAllowedFrameDomain('\'self\''); | |
| 453 | + | |
| 454 | + $response = new PublicTemplateResponse($this->appName, 'public', $shareTmpl); | |
| 455 | + $response->setHeaderTitle($shareTmpl['filename']); | |
| 456 | +        $response->setHeaderDetails($this->l10n->t('shared by %s', [$shareTmpl['displayName']])); | |
| 457 | + $response->setHeaderActions([ | |
| 458 | +            new SimpleMenuAction('download', $this->l10n->t('Download'), 'icon-download-white', $shareTmpl['downloadURL'], 0), | |
| 459 | +            new SimpleMenuAction('download', $this->l10n->t('Download'), 'icon-download', $shareTmpl['downloadURL'], 10, $shareTmpl['fileSize']), | |
| 460 | +            new LinkMenuAction($this->l10n->t('Direct link'), 'icon-public', $shareTmpl['previewURL']), | |
| 461 | +            new ExternalShareMenuAction($this->l10n->t('Add to your Nextcloud'), 'icon-external', $shareTmpl['owner'], $shareTmpl['displayName'], $shareTmpl['filename']), | |
| 462 | + ]); | |
| 463 | + | |
| 464 | + $response->setContentSecurityPolicy($csp); | |
| 465 | + | |
| 466 | + $this->emitAccessShareHook($share); | |
| 467 | + | |
| 468 | + return $response; | |
| 469 | + } | |
| 470 | + | |
| 471 | + /** | |
| 472 | + * @PublicPage | |
| 473 | + * @NoCSRFRequired | |
| 474 | + * | |
| 475 | + * @param string $token | |
| 476 | + * @param string $files | |
| 477 | + * @param string $path | |
| 478 | + * @param string $downloadStartSecret | |
| 479 | + * @return void|\OCP\AppFramework\Http\Response | |
| 480 | + * @throws NotFoundException | |
| 481 | + */ | |
| 482 | +    public function downloadShare($token, $files = null, $path = '', $downloadStartSecret = '') { | |
| 483 | + \OC_User::setIncognitoMode(true); | |
| 484 | + | |
| 485 | + $share = $this->shareManager->getShareByToken($token); | |
| 486 | + | |
| 487 | +        if(!($share->getPermissions() & \OCP\Constants::PERMISSION_READ)) { | |
| 488 | +            return new \OCP\AppFramework\Http\DataResponse('Share is read-only'); | |
| 489 | + } | |
| 490 | + | |
| 491 | + // Share is password protected - check whether the user is permitted to access the share | |
| 492 | +        if ($share->getPassword() !== null && !$this->linkShareAuth($share)) { | |
| 493 | +            return new RedirectResponse($this->urlGenerator->linkToRoute('files_sharing.sharecontroller.authenticate', | |
| 494 | + ['token' => $token, 'redirect' => 'download'])); | |
| 495 | + } | |
| 496 | + | |
| 497 | + $files_list = null; | |
| 498 | +        if (!is_null($files)) { // download selected files | |
| 499 | + $files_list = json_decode($files); | |
| 500 | + // in case we get only a single file | |
| 501 | +            if ($files_list === null) { | |
| 502 | + $files_list = [$files]; | |
| 503 | + } | |
| 504 | + // Just in case $files is a single int like '1234' | |
| 505 | +            if (!is_array($files_list)) { | |
| 506 | + $files_list = [$files_list]; | |
| 507 | + } | |
| 508 | + } | |
| 509 | + | |
| 510 | + $userFolder = $this->rootFolder->getUserFolder($share->getShareOwner()); | |
| 511 | + $originalSharePath = $userFolder->getRelativePath($share->getNode()->getPath()); | |
| 512 | + | |
| 513 | +        if (!$this->validateShare($share)) { | |
| 514 | + throw new NotFoundException(); | |
| 515 | + } | |
| 516 | + | |
| 517 | + // Single file share | |
| 518 | +        if ($share->getNode() instanceof \OCP\Files\File) { | |
| 519 | + // Single file download | |
| 520 | + $this->singleFileDownloaded($share, $share->getNode()); | |
| 521 | + } | |
| 522 | + // Directory share | |
| 523 | +        else { | |
| 524 | + /** @var \OCP\Files\Folder $node */ | |
| 525 | + $node = $share->getNode(); | |
| 526 | + | |
| 527 | + // Try to get the path | |
| 528 | +            if ($path !== '') { | |
| 529 | +                try { | |
| 530 | + $node = $node->get($path); | |
| 531 | +                } catch (NotFoundException $e) { | |
| 532 | + $this->emitAccessShareHook($share, 404, 'Share not found'); | |
| 533 | + return new NotFoundResponse(); | |
| 534 | + } | |
| 535 | + } | |
| 536 | + | |
| 537 | + $originalSharePath = $userFolder->getRelativePath($node->getPath()); | |
| 538 | + | |
| 539 | +            if ($node instanceof \OCP\Files\File) { | |
| 540 | + // Single file download | |
| 541 | + $this->singleFileDownloaded($share, $share->getNode()); | |
| 542 | +            } else if (!empty($files_list)) { | |
| 543 | + $this->fileListDownloaded($share, $files_list, $node); | |
| 544 | +            } else { | |
| 545 | + // The folder is downloaded | |
| 546 | + $this->singleFileDownloaded($share, $share->getNode()); | |
| 547 | + } | |
| 548 | + } | |
| 549 | + | |
| 550 | + /* FIXME: We should do this all nicely in OCP */ | |
| 551 | + OC_Util::tearDownFS(); | |
| 552 | + OC_Util::setupFS($share->getShareOwner()); | |
| 553 | + | |
| 554 | + /** | |
| 555 | + * this sets a cookie to be able to recognize the start of the download | |
| 556 | + * the content must not be longer than 32 characters and must only contain | |
| 557 | + * alphanumeric characters | |
| 558 | + */ | |
| 559 | + if (!empty($downloadStartSecret) | |
| 560 | + && !isset($downloadStartSecret[32]) | |
| 561 | +            && preg_match('!^[a-zA-Z0-9]+$!', $downloadStartSecret) === 1) { | |
| 562 | + | |
| 563 | + // FIXME: set on the response once we use an actual app framework response | |
| 564 | +            setcookie('ocDownloadStarted', $downloadStartSecret, time() + 20, '/'); | |
| 565 | + } | |
| 566 | + | |
| 567 | + $this->emitAccessShareHook($share); | |
| 568 | + | |
| 569 | + $server_params = array( 'head' => $this->request->getMethod() === 'HEAD' ); | |
| 570 | + | |
| 571 | + /** | |
| 572 | + * Http range requests support | |
| 573 | + */ | |
| 574 | +        if (isset($_SERVER['HTTP_RANGE'])) { | |
| 575 | +            $server_params['range'] = $this->request->getHeader('Range'); | |
| 576 | + } | |
| 577 | + | |
| 578 | + // download selected files | |
| 579 | +        if (!is_null($files) && $files !== '') { | |
| 580 | + // FIXME: The exit is required here because otherwise the AppFramework is trying to add headers as well | |
| 581 | + // after dispatching the request which results in a "Cannot modify header information" notice. | |
| 582 | + OC_Files::get($originalSharePath, $files_list, $server_params); | |
| 583 | + exit(); | |
| 584 | +        } else { | |
| 585 | + // FIXME: The exit is required here because otherwise the AppFramework is trying to add headers as well | |
| 586 | + // after dispatching the request which results in a "Cannot modify header information" notice. | |
| 587 | + OC_Files::get(dirname($originalSharePath), basename($originalSharePath), $server_params); | |
| 588 | + exit(); | |
| 589 | + } | |
| 590 | + } | |
| 591 | + | |
| 592 | + /** | |
| 593 | + * create activity for every downloaded file | |
| 594 | + * | |
| 595 | + * @param Share\IShare $share | |
| 596 | + * @param array $files_list | |
| 597 | + * @param \OCP\Files\Folder $node | |
| 598 | + */ | |
| 599 | +    protected function fileListDownloaded(Share\IShare $share, array $files_list, \OCP\Files\Folder $node) { | |
| 600 | +        foreach ($files_list as $file) { | |
| 601 | + $subNode = $node->get($file); | |
| 602 | + $this->singleFileDownloaded($share, $subNode); | |
| 603 | + } | |
| 604 | + | |
| 605 | + } | |
| 606 | + | |
| 607 | + /** | |
| 608 | + * create activity if a single file was downloaded from a link share | |
| 609 | + * | |
| 610 | + * @param Share\IShare $share | |
| 611 | + */ | |
| 612 | +    protected function singleFileDownloaded(Share\IShare $share, \OCP\Files\Node $node) { | |
| 613 | + | |
| 614 | + $fileId = $node->getId(); | |
| 615 | + | |
| 616 | + $userFolder = $this->rootFolder->getUserFolder($share->getSharedBy()); | |
| 617 | + $userNodeList = $userFolder->getById($fileId); | |
| 618 | + $userNode = $userNodeList[0]; | |
| 619 | + $ownerFolder = $this->rootFolder->getUserFolder($share->getShareOwner()); | |
| 620 | + $userPath = $userFolder->getRelativePath($userNode->getPath()); | |
| 621 | + $ownerPath = $ownerFolder->getRelativePath($node->getPath()); | |
| 622 | + | |
| 623 | + $parameters = [$userPath]; | |
| 624 | + | |
| 625 | +        if ($share->getShareType() === \OCP\Share::SHARE_TYPE_EMAIL) { | |
| 626 | +            if ($node instanceof \OCP\Files\File) { | |
| 627 | + $subject = Downloads::SUBJECT_SHARED_FILE_BY_EMAIL_DOWNLOADED; | |
| 628 | +            } else { | |
| 629 | + $subject = Downloads::SUBJECT_SHARED_FOLDER_BY_EMAIL_DOWNLOADED; | |
| 630 | + } | |
| 631 | + $parameters[] = $share->getSharedWith(); | |
| 632 | +        } else { | |
| 633 | +            if ($node instanceof \OCP\Files\File) { | |
| 634 | + $subject = Downloads::SUBJECT_PUBLIC_SHARED_FILE_DOWNLOADED; | |
| 635 | +            } else { | |
| 636 | + $subject = Downloads::SUBJECT_PUBLIC_SHARED_FOLDER_DOWNLOADED; | |
| 637 | + } | |
| 638 | + } | |
| 639 | + | |
| 640 | + $this->publishActivity($subject, $parameters, $share->getSharedBy(), $fileId, $userPath); | |
| 641 | + | |
| 642 | +        if ($share->getShareOwner() !== $share->getSharedBy()) { | |
| 643 | + $parameters[0] = $ownerPath; | |
| 644 | + $this->publishActivity($subject, $parameters, $share->getShareOwner(), $fileId, $ownerPath); | |
| 645 | + } | |
| 646 | + } | |
| 647 | + | |
| 648 | + /** | |
| 649 | + * publish activity | |
| 650 | + * | |
| 651 | + * @param string $subject | |
| 652 | + * @param array $parameters | |
| 653 | + * @param string $affectedUser | |
| 654 | + * @param int $fileId | |
| 655 | + * @param string $filePath | |
| 656 | + */ | |
| 657 | + protected function publishActivity($subject, | |
| 658 | + array $parameters, | |
| 659 | + $affectedUser, | |
| 660 | + $fileId, | |
| 661 | +                                        $filePath) { | |
| 662 | + | |
| 663 | + $event = $this->activityManager->generateEvent(); | |
| 664 | +        $event->setApp('files_sharing') | |
| 665 | +            ->setType('public_links') | |
| 666 | + ->setSubject($subject, $parameters) | |
| 667 | + ->setAffectedUser($affectedUser) | |
| 668 | +            ->setObject('files', $fileId, $filePath); | |
| 669 | + $this->activityManager->publish($event); | |
| 670 | + } | |
| 671 | 671 | |
| 672 | 672 | |
| 673 | 673 | } | 
| @@ -156,7 +156,7 @@ discard block | ||
| 156 | 156 |  	public function showAuthenticate($token) { | 
| 157 | 157 | $share = $this->shareManager->getShareByToken($token); | 
| 158 | 158 | |
| 159 | -		if($this->linkShareAuth($share)) { | |
| 159 | +		if ($this->linkShareAuth($share)) { | |
| 160 | 160 |  			return new RedirectResponse($this->urlGenerator->linkToRoute('files_sharing.sharecontroller.showShare', array('token' => $token))); | 
| 161 | 161 | } | 
| 162 | 162 | |
| @@ -218,15 +218,15 @@ discard block | ||
| 218 | 218 |  		if ($password !== null) { | 
| 219 | 219 |  			if ($this->shareManager->checkPassword($share, $password)) { | 
| 220 | 220 | $this->session->regenerateId(true, true); | 
| 221 | -				$this->session->set('public_link_authenticated', (string)$share->getId()); | |
| 221 | +				$this->session->set('public_link_authenticated', (string) $share->getId()); | |
| 222 | 222 |  			} else { | 
| 223 | 223 | $this->emitAccessShareHook($share, 403, 'Wrong password'); | 
| 224 | 224 | return false; | 
| 225 | 225 | } | 
| 226 | 226 |  		} else { | 
| 227 | 227 | // not authenticated ? | 
| 228 | -			if ( ! $this->session->exists('public_link_authenticated') | |
| 229 | -				|| $this->session->get('public_link_authenticated') !== (string)$share->getId()) { | |
| 228 | +			if (!$this->session->exists('public_link_authenticated') | |
| 229 | +				|| $this->session->get('public_link_authenticated') !== (string) $share->getId()) { | |
| 230 | 230 | return false; | 
| 231 | 231 | } | 
| 232 | 232 | } | 
| @@ -247,7 +247,7 @@ discard block | ||
| 247 | 247 | $itemType = $itemSource = $uidOwner = ''; | 
| 248 | 248 | $token = $share; | 
| 249 | 249 | $exception = null; | 
| 250 | -		if($share instanceof \OCP\Share\IShare) { | |
| 250 | +		if ($share instanceof \OCP\Share\IShare) { | |
| 251 | 251 |  			try { | 
| 252 | 252 | $token = $share->getToken(); | 
| 253 | 253 | $uidOwner = $share->getSharedBy(); | 
| @@ -266,7 +266,7 @@ discard block | ||
| 266 | 266 | 'errorCode' => $errorCode, | 
| 267 | 267 | 'errorMessage' => $errorMessage, | 
| 268 | 268 | ]); | 
| 269 | -		if(!is_null($exception)) { | |
| 269 | +		if (!is_null($exception)) { | |
| 270 | 270 | throw $exception; | 
| 271 | 271 | } | 
| 272 | 272 | } | 
| @@ -392,7 +392,7 @@ discard block | ||
| 392 | 392 | $shareTmpl['previewURL'] = $shareTmpl['downloadURL']; | 
| 393 | 393 | $ogPreview = ''; | 
| 394 | 394 |  		if ($shareTmpl['previewSupported']) { | 
| 395 | - $shareTmpl['previewImage'] = $this->urlGenerator->linkToRouteAbsolute( 'files_sharing.PublicPreview.getPreview', | |
| 395 | +			$shareTmpl['previewImage'] = $this->urlGenerator->linkToRouteAbsolute('files_sharing.PublicPreview.getPreview', | |
| 396 | 396 | ['x' => 200, 'y' => 200, 'file' => $shareTmpl['directory_path'], 't' => $shareTmpl['dirToken']]); | 
| 397 | 397 | $ogPreview = $shareTmpl['previewImage']; | 
| 398 | 398 | |
| @@ -440,7 +440,7 @@ discard block | ||
| 440 | 440 | |
| 441 | 441 | // OpenGraph Support: http://ogp.me/ | 
| 442 | 442 |  		\OCP\Util::addHeader('meta', ['property' => "og:title", 'content' => $shareTmpl['filename']]); | 
| 443 | -		\OCP\Util::addHeader('meta', ['property' => "og:description", 'content' => $this->defaults->getName() . ($this->defaults->getSlogan() !== '' ? ' - ' . $this->defaults->getSlogan() : '')]); | |
| 443 | +		\OCP\Util::addHeader('meta', ['property' => "og:description", 'content' => $this->defaults->getName().($this->defaults->getSlogan() !== '' ? ' - '.$this->defaults->getSlogan() : '')]); | |
| 444 | 444 |  		\OCP\Util::addHeader('meta', ['property' => "og:site_name", 'content' => $this->defaults->getName()]); | 
| 445 | 445 |  		\OCP\Util::addHeader('meta', ['property' => "og:url", 'content' => $shareTmpl['shareUrl']]); | 
| 446 | 446 |  		\OCP\Util::addHeader('meta', ['property' => "og:type", 'content' => "object"]); | 
| @@ -484,7 +484,7 @@ discard block | ||
| 484 | 484 | |
| 485 | 485 | $share = $this->shareManager->getShareByToken($token); | 
| 486 | 486 | |
| 487 | -		if(!($share->getPermissions() & \OCP\Constants::PERMISSION_READ)) { | |
| 487 | +		if (!($share->getPermissions() & \OCP\Constants::PERMISSION_READ)) { | |
| 488 | 488 |  			return new \OCP\AppFramework\Http\DataResponse('Share is read-only'); | 
| 489 | 489 | } | 
| 490 | 490 | |
| @@ -566,7 +566,7 @@ discard block | ||
| 566 | 566 | |
| 567 | 567 | $this->emitAccessShareHook($share); | 
| 568 | 568 | |
| 569 | - $server_params = array( 'head' => $this->request->getMethod() === 'HEAD' ); | |
| 569 | +		$server_params = array('head' => $this->request->getMethod() === 'HEAD'); | |
| 570 | 570 | |
| 571 | 571 | /** | 
| 572 | 572 | * Http range requests support | 
| @@ -32,8 +32,6 @@ | ||
| 32 | 32 | |
| 33 | 33 | use OC\Authentication\Exceptions\InvalidTokenException; | 
| 34 | 34 | use OC\Authentication\Token\IProvider; | 
| 35 | -use OC\SystemConfig; | |
| 36 | -use OCP\IConfig; | |
| 37 | 35 | use OCP\Session\Exceptions\SessionNotAvailableException; | 
| 38 | 36 | |
| 39 | 37 | /** | 
| @@ -44,170 +44,170 @@ | ||
| 44 | 44 | * @package OC\Session | 
| 45 | 45 | */ | 
| 46 | 46 |  class Internal extends Session { | 
| 47 | - /** | |
| 48 | - * @param string $name | |
| 49 | - * @throws \Exception | |
| 50 | - */ | |
| 51 | -	public function __construct(string $name) { | |
| 52 | - set_error_handler([$this, 'trapError']); | |
| 53 | -		$this->invoke('session_name', [$name]); | |
| 54 | -		try { | |
| 55 | -			$this->invoke('session_start'); | |
| 56 | -		} catch (\Exception $e) { | |
| 57 | -			setcookie($this->invoke('session_name'), null, -1, \OC::$WEBROOT ?: '/'); | |
| 58 | - } | |
| 59 | - restore_error_handler(); | |
| 60 | -		if (!isset($_SESSION)) { | |
| 61 | -			throw new \Exception('Failed to start session'); | |
| 62 | - } | |
| 63 | - } | |
| 64 | - | |
| 65 | - /** | |
| 66 | - * @param string $key | |
| 67 | - * @param integer $value | |
| 68 | - */ | |
| 69 | -	public function set(string $key, $value) { | |
| 70 | - $this->validateSession(); | |
| 71 | - $_SESSION[$key] = $value; | |
| 72 | - } | |
| 73 | - | |
| 74 | - /** | |
| 75 | - * @param string $key | |
| 76 | - * @return mixed | |
| 77 | - */ | |
| 78 | -	public function get(string $key) { | |
| 79 | -		if (!$this->exists($key)) { | |
| 80 | - return null; | |
| 81 | - } | |
| 82 | - return $_SESSION[$key]; | |
| 83 | - } | |
| 84 | - | |
| 85 | - /** | |
| 86 | - * @param string $key | |
| 87 | - * @return bool | |
| 88 | - */ | |
| 89 | -	public function exists(string $key): bool { | |
| 90 | - return isset($_SESSION[$key]); | |
| 91 | - } | |
| 92 | - | |
| 93 | - /** | |
| 94 | - * @param string $key | |
| 95 | - */ | |
| 96 | -	public function remove(string $key) { | |
| 97 | -		if (isset($_SESSION[$key])) { | |
| 98 | - unset($_SESSION[$key]); | |
| 99 | - } | |
| 100 | - } | |
| 101 | - | |
| 102 | -	public function clear() { | |
| 103 | -		$this->invoke('session_unset'); | |
| 104 | - $this->regenerateId(); | |
| 105 | -		$this->invoke('session_start', [], true); | |
| 106 | - $_SESSION = []; | |
| 107 | - } | |
| 108 | - | |
| 109 | -	public function close() { | |
| 110 | -		$this->invoke('session_write_close'); | |
| 111 | - parent::close(); | |
| 112 | - } | |
| 113 | - | |
| 114 | - /** | |
| 115 | - * Wrapper around session_regenerate_id | |
| 116 | - * | |
| 117 | - * @param bool $deleteOldSession Whether to delete the old associated session file or not. | |
| 118 | - * @param bool $updateToken Wheater to update the associated auth token | |
| 119 | - * @return void | |
| 120 | - */ | |
| 121 | -	public function regenerateId(bool $deleteOldSession = true, bool $updateToken = false) { | |
| 122 | - $oldId = null; | |
| 123 | - | |
| 124 | -		if ($updateToken) { | |
| 125 | - // Get the old id to update the token | |
| 126 | -			try { | |
| 127 | - $oldId = $this->getId(); | |
| 128 | -			} catch (SessionNotAvailableException $e) { | |
| 129 | - // We can't update a token if there is no previous id | |
| 130 | - $updateToken = false; | |
| 131 | - } | |
| 132 | - } | |
| 133 | - | |
| 134 | -		try { | |
| 135 | - @session_regenerate_id($deleteOldSession); | |
| 136 | -		} catch (\Error $e) { | |
| 137 | - $this->trapError($e->getCode(), $e->getMessage()); | |
| 138 | - } | |
| 139 | - | |
| 140 | -		if ($updateToken) { | |
| 141 | - // Get the new id to update the token | |
| 142 | - $newId = $this->getId(); | |
| 143 | - | |
| 144 | - /** @var IProvider $tokenProvider */ | |
| 145 | - $tokenProvider = \OC::$server->query(IProvider::class); | |
| 146 | - | |
| 147 | -			try { | |
| 148 | - $tokenProvider->renewSessionToken($oldId, $newId); | |
| 149 | -			} catch (InvalidTokenException $e) { | |
| 150 | - // Just ignore | |
| 151 | - } | |
| 152 | - } | |
| 153 | - } | |
| 154 | - | |
| 155 | - /** | |
| 156 | - * Wrapper around session_id | |
| 157 | - * | |
| 158 | - * @return string | |
| 159 | - * @throws SessionNotAvailableException | |
| 160 | - * @since 9.1.0 | |
| 161 | - */ | |
| 162 | -	public function getId(): string { | |
| 163 | -		$id = $this->invoke('session_id', [], true); | |
| 164 | -		if ($id === '') { | |
| 165 | - throw new SessionNotAvailableException(); | |
| 166 | - } | |
| 167 | - return $id; | |
| 168 | - } | |
| 169 | - | |
| 170 | - /** | |
| 171 | - * @throws \Exception | |
| 172 | - */ | |
| 173 | -	public function reopen() { | |
| 174 | -		throw new \Exception('The session cannot be reopened - reopen() is ony to be used in unit testing.'); | |
| 175 | - } | |
| 176 | - | |
| 177 | - /** | |
| 178 | - * @param int $errorNumber | |
| 179 | - * @param string $errorString | |
| 180 | - * @throws \ErrorException | |
| 181 | - */ | |
| 182 | -	public function trapError(int $errorNumber, string $errorString) { | |
| 183 | - throw new \ErrorException($errorString); | |
| 184 | - } | |
| 185 | - | |
| 186 | - /** | |
| 187 | - * @throws \Exception | |
| 188 | - */ | |
| 189 | -	private function validateSession() { | |
| 190 | -		if ($this->sessionClosed) { | |
| 191 | -			throw new SessionNotAvailableException('Session has been closed - no further changes to the session are allowed'); | |
| 192 | - } | |
| 193 | - } | |
| 194 | - | |
| 195 | - /** | |
| 196 | - * @param string $functionName the full session_* function name | |
| 197 | - * @param array $parameters | |
| 198 | - * @param bool $silence whether to suppress warnings | |
| 199 | - * @throws \ErrorException via trapError | |
| 200 | - * @return mixed | |
| 201 | - */ | |
| 202 | -	private function invoke(string $functionName, array $parameters = [], bool $silence = false) { | |
| 203 | -		try { | |
| 204 | -			if($silence) { | |
| 205 | - return @call_user_func_array($functionName, $parameters); | |
| 206 | -			} else { | |
| 207 | - return call_user_func_array($functionName, $parameters); | |
| 208 | - } | |
| 209 | -		} catch(\Error $e) { | |
| 210 | - $this->trapError($e->getCode(), $e->getMessage()); | |
| 211 | - } | |
| 212 | - } | |
| 47 | + /** | |
| 48 | + * @param string $name | |
| 49 | + * @throws \Exception | |
| 50 | + */ | |
| 51 | +    public function __construct(string $name) { | |
| 52 | + set_error_handler([$this, 'trapError']); | |
| 53 | +        $this->invoke('session_name', [$name]); | |
| 54 | +        try { | |
| 55 | +            $this->invoke('session_start'); | |
| 56 | +        } catch (\Exception $e) { | |
| 57 | +            setcookie($this->invoke('session_name'), null, -1, \OC::$WEBROOT ?: '/'); | |
| 58 | + } | |
| 59 | + restore_error_handler(); | |
| 60 | +        if (!isset($_SESSION)) { | |
| 61 | +            throw new \Exception('Failed to start session'); | |
| 62 | + } | |
| 63 | + } | |
| 64 | + | |
| 65 | + /** | |
| 66 | + * @param string $key | |
| 67 | + * @param integer $value | |
| 68 | + */ | |
| 69 | +    public function set(string $key, $value) { | |
| 70 | + $this->validateSession(); | |
| 71 | + $_SESSION[$key] = $value; | |
| 72 | + } | |
| 73 | + | |
| 74 | + /** | |
| 75 | + * @param string $key | |
| 76 | + * @return mixed | |
| 77 | + */ | |
| 78 | +    public function get(string $key) { | |
| 79 | +        if (!$this->exists($key)) { | |
| 80 | + return null; | |
| 81 | + } | |
| 82 | + return $_SESSION[$key]; | |
| 83 | + } | |
| 84 | + | |
| 85 | + /** | |
| 86 | + * @param string $key | |
| 87 | + * @return bool | |
| 88 | + */ | |
| 89 | +    public function exists(string $key): bool { | |
| 90 | + return isset($_SESSION[$key]); | |
| 91 | + } | |
| 92 | + | |
| 93 | + /** | |
| 94 | + * @param string $key | |
| 95 | + */ | |
| 96 | +    public function remove(string $key) { | |
| 97 | +        if (isset($_SESSION[$key])) { | |
| 98 | + unset($_SESSION[$key]); | |
| 99 | + } | |
| 100 | + } | |
| 101 | + | |
| 102 | +    public function clear() { | |
| 103 | +        $this->invoke('session_unset'); | |
| 104 | + $this->regenerateId(); | |
| 105 | +        $this->invoke('session_start', [], true); | |
| 106 | + $_SESSION = []; | |
| 107 | + } | |
| 108 | + | |
| 109 | +    public function close() { | |
| 110 | +        $this->invoke('session_write_close'); | |
| 111 | + parent::close(); | |
| 112 | + } | |
| 113 | + | |
| 114 | + /** | |
| 115 | + * Wrapper around session_regenerate_id | |
| 116 | + * | |
| 117 | + * @param bool $deleteOldSession Whether to delete the old associated session file or not. | |
| 118 | + * @param bool $updateToken Wheater to update the associated auth token | |
| 119 | + * @return void | |
| 120 | + */ | |
| 121 | +    public function regenerateId(bool $deleteOldSession = true, bool $updateToken = false) { | |
| 122 | + $oldId = null; | |
| 123 | + | |
| 124 | +        if ($updateToken) { | |
| 125 | + // Get the old id to update the token | |
| 126 | +            try { | |
| 127 | + $oldId = $this->getId(); | |
| 128 | +            } catch (SessionNotAvailableException $e) { | |
| 129 | + // We can't update a token if there is no previous id | |
| 130 | + $updateToken = false; | |
| 131 | + } | |
| 132 | + } | |
| 133 | + | |
| 134 | +        try { | |
| 135 | + @session_regenerate_id($deleteOldSession); | |
| 136 | +        } catch (\Error $e) { | |
| 137 | + $this->trapError($e->getCode(), $e->getMessage()); | |
| 138 | + } | |
| 139 | + | |
| 140 | +        if ($updateToken) { | |
| 141 | + // Get the new id to update the token | |
| 142 | + $newId = $this->getId(); | |
| 143 | + | |
| 144 | + /** @var IProvider $tokenProvider */ | |
| 145 | + $tokenProvider = \OC::$server->query(IProvider::class); | |
| 146 | + | |
| 147 | +            try { | |
| 148 | + $tokenProvider->renewSessionToken($oldId, $newId); | |
| 149 | +            } catch (InvalidTokenException $e) { | |
| 150 | + // Just ignore | |
| 151 | + } | |
| 152 | + } | |
| 153 | + } | |
| 154 | + | |
| 155 | + /** | |
| 156 | + * Wrapper around session_id | |
| 157 | + * | |
| 158 | + * @return string | |
| 159 | + * @throws SessionNotAvailableException | |
| 160 | + * @since 9.1.0 | |
| 161 | + */ | |
| 162 | +    public function getId(): string { | |
| 163 | +        $id = $this->invoke('session_id', [], true); | |
| 164 | +        if ($id === '') { | |
| 165 | + throw new SessionNotAvailableException(); | |
| 166 | + } | |
| 167 | + return $id; | |
| 168 | + } | |
| 169 | + | |
| 170 | + /** | |
| 171 | + * @throws \Exception | |
| 172 | + */ | |
| 173 | +    public function reopen() { | |
| 174 | +        throw new \Exception('The session cannot be reopened - reopen() is ony to be used in unit testing.'); | |
| 175 | + } | |
| 176 | + | |
| 177 | + /** | |
| 178 | + * @param int $errorNumber | |
| 179 | + * @param string $errorString | |
| 180 | + * @throws \ErrorException | |
| 181 | + */ | |
| 182 | +    public function trapError(int $errorNumber, string $errorString) { | |
| 183 | + throw new \ErrorException($errorString); | |
| 184 | + } | |
| 185 | + | |
| 186 | + /** | |
| 187 | + * @throws \Exception | |
| 188 | + */ | |
| 189 | +    private function validateSession() { | |
| 190 | +        if ($this->sessionClosed) { | |
| 191 | +            throw new SessionNotAvailableException('Session has been closed - no further changes to the session are allowed'); | |
| 192 | + } | |
| 193 | + } | |
| 194 | + | |
| 195 | + /** | |
| 196 | + * @param string $functionName the full session_* function name | |
| 197 | + * @param array $parameters | |
| 198 | + * @param bool $silence whether to suppress warnings | |
| 199 | + * @throws \ErrorException via trapError | |
| 200 | + * @return mixed | |
| 201 | + */ | |
| 202 | +    private function invoke(string $functionName, array $parameters = [], bool $silence = false) { | |
| 203 | +        try { | |
| 204 | +            if($silence) { | |
| 205 | + return @call_user_func_array($functionName, $parameters); | |
| 206 | +            } else { | |
| 207 | + return call_user_func_array($functionName, $parameters); | |
| 208 | + } | |
| 209 | +        } catch(\Error $e) { | |
| 210 | + $this->trapError($e->getCode(), $e->getMessage()); | |
| 211 | + } | |
| 212 | + } | |
| 213 | 213 | } | 
| @@ -45,69 +45,69 @@ | ||
| 45 | 45 | */ | 
| 46 | 46 |  interface ISession { | 
| 47 | 47 | |
| 48 | - /** | |
| 49 | - * Set a value in the session | |
| 50 | - * | |
| 51 | - * @param string $key | |
| 52 | - * @param mixed $value | |
| 53 | - * @since 6.0.0 | |
| 54 | - */ | |
| 55 | - public function set(string $key, $value); | |
| 48 | + /** | |
| 49 | + * Set a value in the session | |
| 50 | + * | |
| 51 | + * @param string $key | |
| 52 | + * @param mixed $value | |
| 53 | + * @since 6.0.0 | |
| 54 | + */ | |
| 55 | + public function set(string $key, $value); | |
| 56 | 56 | |
| 57 | - /** | |
| 58 | - * Get a value from the session | |
| 59 | - * | |
| 60 | - * @param string $key | |
| 61 | - * @return mixed should return null if $key does not exist | |
| 62 | - * @since 6.0.0 | |
| 63 | - */ | |
| 64 | - public function get(string $key); | |
| 57 | + /** | |
| 58 | + * Get a value from the session | |
| 59 | + * | |
| 60 | + * @param string $key | |
| 61 | + * @return mixed should return null if $key does not exist | |
| 62 | + * @since 6.0.0 | |
| 63 | + */ | |
| 64 | + public function get(string $key); | |
| 65 | 65 | |
| 66 | - /** | |
| 67 | - * Check if a named key exists in the session | |
| 68 | - * | |
| 69 | - * @param string $key | |
| 70 | - * @return bool | |
| 71 | - * @since 6.0.0 | |
| 72 | - */ | |
| 73 | - public function exists(string $key): bool; | |
| 66 | + /** | |
| 67 | + * Check if a named key exists in the session | |
| 68 | + * | |
| 69 | + * @param string $key | |
| 70 | + * @return bool | |
| 71 | + * @since 6.0.0 | |
| 72 | + */ | |
| 73 | + public function exists(string $key): bool; | |
| 74 | 74 | |
| 75 | - /** | |
| 76 | - * Remove a $key/$value pair from the session | |
| 77 | - * | |
| 78 | - * @param string $key | |
| 79 | - * @since 6.0.0 | |
| 80 | - */ | |
| 81 | - public function remove(string $key); | |
| 75 | + /** | |
| 76 | + * Remove a $key/$value pair from the session | |
| 77 | + * | |
| 78 | + * @param string $key | |
| 79 | + * @since 6.0.0 | |
| 80 | + */ | |
| 81 | + public function remove(string $key); | |
| 82 | 82 | |
| 83 | - /** | |
| 84 | - * Reset and recreate the session | |
| 85 | - * @since 6.0.0 | |
| 86 | - */ | |
| 87 | - public function clear(); | |
| 83 | + /** | |
| 84 | + * Reset and recreate the session | |
| 85 | + * @since 6.0.0 | |
| 86 | + */ | |
| 87 | + public function clear(); | |
| 88 | 88 | |
| 89 | - /** | |
| 90 | - * Close the session and release the lock | |
| 91 | - * @since 7.0.0 | |
| 92 | - */ | |
| 93 | - public function close(); | |
| 89 | + /** | |
| 90 | + * Close the session and release the lock | |
| 91 | + * @since 7.0.0 | |
| 92 | + */ | |
| 93 | + public function close(); | |
| 94 | 94 | |
| 95 | - /** | |
| 96 | - * Wrapper around session_regenerate_id | |
| 97 | - * | |
| 98 | - * @param bool $deleteOldSession Whether to delete the old associated session file or not. | |
| 99 | - * @param bool $updateToken Wheater to update the associated auth token | |
| 100 | - * @return void | |
| 101 | - * @since 9.0.0, $updateToken added in 14.0.0 | |
| 102 | - */ | |
| 103 | - public function regenerateId(bool $deleteOldSession = true, bool $updateToken = false); | |
| 95 | + /** | |
| 96 | + * Wrapper around session_regenerate_id | |
| 97 | + * | |
| 98 | + * @param bool $deleteOldSession Whether to delete the old associated session file or not. | |
| 99 | + * @param bool $updateToken Wheater to update the associated auth token | |
| 100 | + * @return void | |
| 101 | + * @since 9.0.0, $updateToken added in 14.0.0 | |
| 102 | + */ | |
| 103 | + public function regenerateId(bool $deleteOldSession = true, bool $updateToken = false); | |
| 104 | 104 | |
| 105 | - /** | |
| 106 | - * Wrapper around session_id | |
| 107 | - * | |
| 108 | - * @return string | |
| 109 | - * @throws SessionNotAvailableException | |
| 110 | - * @since 9.1.0 | |
| 111 | - */ | |
| 112 | - public function getId(): string; | |
| 105 | + /** | |
| 106 | + * Wrapper around session_id | |
| 107 | + * | |
| 108 | + * @return string | |
| 109 | + * @throws SessionNotAvailableException | |
| 110 | + * @since 9.1.0 | |
| 111 | + */ | |
| 112 | + public function getId(): string; | |
| 113 | 113 | } |