nextcloud-gallery/lib/Middleware/EnvCheckMiddleware.php

299 lines
7.7 KiB
PHP

<?php
/**
* Nextcloud - Gallery
*
* This file is licensed under the Affero General Public License version 3 or
* later. See the COPYING file.
*
* @author Olivier Paroz <galleryapps@oparoz.com>
* @author Bernhard Posselt <dev@bernhard-posselt.com>
* @author Authors of \OCA\Files_Sharing\Helper
*
* @copyright Olivier Paroz 2017
* @copyright Bernhard Posselt 2017
* @copyright Authors of \OCA\Files_Sharing\Helper 2017
*/
namespace OCA\Gallery\Middleware;
use OCP\IRequest;
use OCP\IURLGenerator;
use OCP\ISession;
use OCP\ILogger;
use OCP\Share;
use OCP\Share\IShare;
use OCP\Share\Exceptions\ShareNotFound;
use OCP\Security\IHasher;
use OCP\AppFramework\Http;
use OCP\AppFramework\Utility\IControllerMethodReflector;
use OCA\Gallery\Environment\Environment;
use OCP\Share\IManager;
/**
* Checks that we have a valid token linked to a valid resource and that the
* user is authorised to access it
*
* Once all checks have been passed, the environment is ready to use
*
* @package OCA\Gallery\Middleware
*/
class EnvCheckMiddleware extends CheckMiddleware {
/** @var IHasher */
private $hasher;
/** @var ISession */
private $session;
/** @var Environment */
private $environment;
/** @var IControllerMethodReflector */
protected $reflector;
/** @var IManager */
protected $shareManager;
/***
* Constructor
*
* @param string $appName
* @param IRequest $request
* @param IHasher $hasher
* @param ISession $session
* @param Environment $environment
* @param IControllerMethodReflector $reflector
* @param IURLGenerator $urlGenerator
* @param ILogger $logger
* @param IManager $shareManager
*/
public function __construct(
$appName,
IRequest $request,
IHasher $hasher,
ISession $session,
Environment $environment,
IControllerMethodReflector $reflector,
IURLGenerator $urlGenerator,
IManager $shareManager,
ILogger $logger
) {
parent::__construct(
$appName,
$request,
$urlGenerator,
$logger
);
$this->hasher = $hasher;
$this->session = $session;
$this->environment = $environment;
$this->reflector = $reflector;
$this->shareManager = $shareManager;
}
/**
* Checks that we have a valid token linked to a valid resource and that the
* user is authorised to access it
*
* Inspects the controller method annotations and if PublicPage is found
* it checks that we have a token and an optional password giving access to a valid resource.
* Once that's done, the environment is setup so that our services can find the resources they
* need.
*
* The checks are not performed on "guest" pages and the environment is not setup. Typical
* guest pages are anonymous error ages
*
* @inheritDoc
*/
public function beforeController($controller, $methodName) {
if ($this->reflector->hasAnnotation('Guest')) {
return;
}
$isPublicPage = $this->reflector->hasAnnotation('PublicPage');
if ($isPublicPage) {
$this->validateAndSetTokenBasedEnv();
} else {
$this->environment->setStandardEnv();
}
}
/**
* Checks that we have a token and an optional password giving access to a
* valid resource. Sets the token based environment after that
*
* @throws CheckException
*/
private function validateAndSetTokenBasedEnv() {
$token = $this->request->getParam('token');
if (!$token) {
throw new CheckException(
"Can't access a public resource without a token", Http::STATUS_NOT_FOUND
);
} else {
$share = $this->getShare($token);
$password = $this->request->getParam('password');
// Let's see if the user needs to provide a password
$this->checkAuthorisation($share, $password);
$this->environment->setTokenBasedEnv($share);
}
}
/**
* Validates a token to make sure its linked to a valid resource
*
* Uses Share 2.0
*
* @fixme setIncognitoMode in 8.1 https://github.com/owncloud/core/pull/12912
*
* @param string $token
*
* @throws CheckException
* @return IShare
*/
private function getShare($token) {
// Allows a logged in user to access public links
\OC_User::setIncognitoMode(true);
try {
$share = $this->shareManager->getShareByToken($token);
} catch (ShareNotFound $e) {
throw new CheckException($e->getMessage(), Http::STATUS_NOT_FOUND);
}
$this->checkShareIsValid($share, $token);
$this->checkItemType($share);
return $share;
}
/**
* Makes sure that the token contains all the information that we need
*
* @param IShare $share
* @param string $token
*
* @throws CheckException
*/
private function checkShareIsValid($share, $token) {
if ($share->getShareOwner() === null
|| $share->getTarget() === null
) {
$message =
'Passed token seems to be valid, but it does not contain all necessary information . ("'
. $token . '")';
throw new CheckException($message, Http::STATUS_NOT_FOUND);
}
}
/**
* Makes sure an item type was set for that token
*
* @param IShare $share
*
* @throws CheckException
*/
private function checkItemType($share) {
if ($share->getNodeType() === null) {
$message = 'No item type set for share id: ' . $share->getId();
throw new CheckException($message, Http::STATUS_NOT_FOUND);
}
}
/**
* Checks if a password is required or if the one supplied is working
*
* @param IShare $share
* @param string|null $password optional password
*
* @throws CheckException
*/
private function checkAuthorisation($share, $password) {
$passwordRequired = $share->getPassword();
if (isset($passwordRequired)) {
if ($password !== null) {
$this->authenticate($share, $password);
} else {
$this->checkSession($share);
}
}
}
/**
* Authenticate link item with the given password
* or with the session if no password was given.
*
* @param IShare $share
* @param string $password
*
* @return bool true if authorized, an exception is raised otherwise
*
* @throws CheckException
*/
private function authenticate($share, $password) {
if ((int)$share->getShareType() === Share::SHARE_TYPE_LINK) {
$this->checkPassword($share, $password);
} else {
throw new CheckException(
'Unknown share type ' . $share->getShareType() . ' for share id '
. $share->getId(), Http::STATUS_NOT_FOUND
);
}
return true;
}
/**
* Validates the given password
*
* @fixme @LukasReschke says: Migrate old hashes to new hash format
* Due to the fact that there is no reasonable functionality to update the password
* of an existing share no migration is yet performed there.
* The only possibility is to update the existing share which will result in a new
* share ID and is a major hack.
*
* In the future the migration should be performed once there is a proper method
* to update the share's password. (for example `$share->updatePassword($password)`
*
* @link https://github.com/owncloud/core/issues/10671
*
* @param IShare $share
* @param string $password
*
* @throws CheckException
*/
private function checkPassword($share, $password) {
$newHash = '';
if ($this->shareManager->checkPassword($share, $password)) {
// Save item id in session for future requests
$this->session->set('public_link_authenticated', (string)$share->getId());
// @codeCoverageIgnoreStart
if (!empty($newHash)) {
// For future use
}
// @codeCoverageIgnoreEnd
} else {
throw new CheckException("Wrong password", Http::STATUS_UNAUTHORIZED);
}
}
/**
* Makes sure the user is already properly authenticated when a password is required and none
* was provided
*
* @param IShare $share
*
* @throws CheckException
*/
private function checkSession($share) {
// Not authenticated ?
if (!$this->session->exists('public_link_authenticated')
|| $this->session->get('public_link_authenticated') !== (string)$share->getId()
) {
throw new CheckException("Missing password", Http::STATUS_UNAUTHORIZED);
}
}
}