Code Coverage
 
Classes and Traits
Functions and Methods
Lines
Total
0.00% covered (danger)
0.00%
0 / 1
90.00% covered (success)
90.00%
9 / 10
CRAP
95.00% covered (success)
95.00%
57 / 60
EnvCheckMiddleware
0.00% covered (danger)
0.00%
0 / 1
90.00% covered (success)
90.00%
9 / 10
24
95.00% covered (success)
95.00%
57 / 60
 __construct
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
7 / 7
 beforeController
100.00% covered (success)
100.00%
1 / 1
3
100.00% covered (success)
100.00%
7 / 7
 validateAndSetTokenBasedEnv
100.00% covered (success)
100.00%
1 / 1
2
100.00% covered (success)
100.00%
9 / 9
 getShare
100.00% covered (success)
100.00%
1 / 1
2
100.00% covered (success)
100.00%
7 / 7
 checkShareIsValid
100.00% covered (success)
100.00%
1 / 1
3
100.00% covered (success)
100.00%
5 / 5
 checkItemType
100.00% covered (success)
100.00%
1 / 1
2
100.00% covered (success)
100.00%
4 / 4
 checkAuthorisation
100.00% covered (success)
100.00%
1 / 1
3
100.00% covered (success)
100.00%
6 / 6
 authenticate
0.00% covered (danger)
0.00%
0 / 1
2.50
50.00% covered (warning)
50.00%
3 / 6
 checkPassword
100.00% covered (success)
100.00%
1 / 1
3
100.00% covered (success)
100.00%
5 / 5
 checkSession
100.00% covered (success)
100.00%
1 / 1
3
100.00% covered (success)
100.00%
4 / 4
<?php
/**
 * 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 2014-2016
 * @copyright Bernhard Posselt 2012-2015
 * @copyright Authors of \OCA\Files_Sharing\Helper 2014-2016
 */
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);
        }
    }
}