owncloud/core

View on GitHub
apps/federatedfilesharing/lib/Controller/RequestHandlerController.php

Summary

Maintainability
C
1 day
Test Coverage
<?php
/**
 * @author Arthur Schiwon <blizzz@arthur-schiwon.de>
 * @author Björn Schießle <bjoern@schiessle.org>
 * @author Joas Schilling <coding@schilljs.com>
 * @author Lukas Reschke <lukas@statuscode.ch>
 * @author Morris Jobke <hey@morrisjobke.de>
 * @author Thomas Müller <thomas.mueller@tmit.eu>
 *
 * @copyright Copyright (c) 2018, ownCloud GmbH
 * @license AGPL-3.0
 *
 * This code is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License, version 3,
 * as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License, version 3,
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
 *
 */

namespace OCA\FederatedFileSharing\Controller;

use OC\Files\View;
use OC\OCS\Result;
use OCA\FederatedFileSharing\Address;
use OCA\FederatedFileSharing\AddressHandler;
use OCA\FederatedFileSharing\FedShareManager;
use OCA\FederatedFileSharing\Middleware\OcmMiddleware;
use OCA\FederatedFileSharing\Ocm\Exception\BadRequestException;
use OCA\FederatedFileSharing\Ocm\Exception\ForbiddenException;
use OCA\FederatedFileSharing\Ocm\Exception\NotImplementedException;
use OCA\FederatedFileSharing\Ocm\Exception\OcmException;
use OCP\AppFramework\Http;
use OCP\AppFramework\OCSController;
use OCP\Files\InvalidPathException;
use OCP\IRequest;
use OCP\IUserManager;

/**
 * Class RequestHandlerController
 *
 * Handles OCS Request to the federated share API
 *
 * @package OCA\FederatedFileSharing\API
 */
class RequestHandlerController extends OCSController {
    /** @var OcmMiddleware */
    private $ocmMiddleware;

    /** @var IUserManager */
    private $userManager;

    /** @var AddressHandler */
    private $addressHandler;

    /** @var  FedShareManager */
    private $fedShareManager;

    /**
     * Server2Server constructor.
     *
     * @param string $appName
     * @param IRequest $request
     * @param OcmMiddleware $ocmMiddleware
     * @param IUserManager $userManager
     * @param AddressHandler $addressHandler
     * @param FedShareManager $fedShareManager
     */
    public function __construct(
        $appName,
        IRequest $request,
        OcmMiddleware $ocmMiddleware,
        IUserManager $userManager,
        AddressHandler $addressHandler,
        FedShareManager $fedShareManager
    ) {
        parent::__construct($appName, $request);

        $this->ocmMiddleware = $ocmMiddleware;
        $this->userManager = $userManager;
        $this->addressHandler = $addressHandler;
        $this->fedShareManager = $fedShareManager;
    }

    /**
     * @NoCSRFRequired
     * @PublicPage
     *
     * create a new share
     *
     * @return Result
     */
    public function createShare() {
        try {
            $this->ocmMiddleware->assertIncomingSharingEnabled();
            $remote = $this->request->getParam('remote', null);
            $token = $this->request->getParam('token', null);
            $name = $this->request->getParam('name', null);
            $owner = $this->request->getParam('owner', null);
            $sharedBy = $this->request->getParam('sharedBy', null);
            $shareWith = $this->request->getParam('shareWith', null);
            $remoteId = $this->request->getParam('remoteId', null);
            $sharedByFederatedId = $this->request->getParam(
                'sharedByFederatedId',
                null
            );

            if (\strlen($token) > 128) {
                throw new BadRequestException('Token too long');
            }

            $ownerFederatedId = $this->request->getParam('ownerFederatedId', null);
            $this->ocmMiddleware->assertNotNull(
                [
                    'remote' => $remote,
                    'token' => $token,
                    'name' => $name,
                    'owner' => $owner,
                    'remoteId' => $remoteId,
                    'shareWith' => $shareWith
                ]
            );

            if (!$this->isFileNameValid($name)) {
                throw new BadRequestException(
                    'The mountpoint name contains invalid characters.'
                );
            }
            // FIXME this should be a method in the user management instead
            \OCP\Util::writeLog('federatedfilesharing', 'shareWith before, ' . $shareWith, \OCP\Util::DEBUG);
            $handled = false;
            // the $handled var will be sent as reference so the listeners can use it as a flag
            // in order to know if the event has been processed already or not.
            \OCP\Util::emitHook(
                '\OCA\Files_Sharing\API\Server2Server',
                'preLoginNameUsedAsUserName',
                ['uid' => &$shareWith, 'hasBeenHandled' => &$handled]
            );
            \OCP\Util::writeLog('federatedfilesharing', 'shareWith after, ' . $shareWith, \OCP\Util::DEBUG);
            if (!$this->userManager->userExists($shareWith)) {
                throw new BadRequestException('User does not exist');
            }

            $ownerAddress = $ownerFederatedId === null
                ? new Address("{$owner}@{$remote}")
                : new Address($ownerFederatedId);
            // if the owner of the share and the initiator are the same user
            // we also complete the federated share ID for the initiator
            if ($sharedByFederatedId === null && $owner === $sharedBy) {
                $sharedByFederatedId = $ownerAddress->getCloudId();
            }

            $sharedByAddress = new Address($sharedByFederatedId);

            $this->fedShareManager->createShare(
                $ownerAddress,
                $sharedByAddress,
                $shareWith,
                $remoteId,
                $name,
                $token
            );
        } catch (OcmException $e) {
            return new Result(
                null,
                $e->getHttpStatusCode(),
                $e->getMessage()
            );
        } catch (\Exception $e) {
            \OCP\Util::writeLog(
                'federatedfilesharing',
                'server can not add federated share, ' . $e->getMessage(),
                \OCP\Util::ERROR
            );
            return new Result(
                null,
                Http::STATUS_INTERNAL_SERVER_ERROR,
                'internal server error, was not able to add share from ' . $remote
            );
        }
        return new Result();
    }

    /**
     * @NoCSRFRequired
     * @PublicPage
     *
     * create re-share on behalf of another user
     *
     * @param int $id
     *
     * @return Result
     */
    public function reShare($id) {
        $token = $this->request->getParam('token', null);
        $shareWith = $this->request->getParam('shareWith', null);
        $permission = $this->request->getParam('permission', null);
        $remoteId = $this->request->getParam('remoteId', null);

        try {
            $this->ocmMiddleware->assertNotNull(
                [
                    'id' => $id,
                    'token' => $token,
                    'shareWith' => $shareWith,
                    'permission'  => $permission,
                    'remoteId' => $remoteId
                ]
            );
            $permission = $this->ocmMiddleware->normalizePermissions(
                (int) $permission
            );
            $remoteId = (int) $remoteId;
            $share = $this->ocmMiddleware->getValidShare($id, $token);

            // don't allow to share a file back to the owner
            $owner = $share->getShareOwner();
            $ownerAddress = $this->addressHandler->getLocalUserFederatedAddress($owner);
            $shareWithAddress = new Address($shareWith);
            $this->ocmMiddleware->assertNotSameUser($ownerAddress, $shareWithAddress);

            $this->ocmMiddleware->assertSharingPermissionSet($share);
            $result = $this->fedShareManager->reShare(
                $share,
                $remoteId,
                $shareWith,
                $permission
            );
        } catch (OcmException $e) {
            return new Result(
                null,
                $e->getHttpStatusCode(),
                $e->getMessage()
            );
        } catch (\Exception $e) {
            return new Result(null, Http::STATUS_BAD_REQUEST);
        }

        return new Result(
            [
                'token' => $result->getToken(),
                'remoteId' => $result->getId()
            ]
        );
    }

    /**
     * @NoCSRFRequired
     * @PublicPage
     *
     * accept server-to-server share
     *
     * @param int $id
     *
     * @return Result
     */
    public function acceptShare($id) {
        try {
            $this->ocmMiddleware->assertOutgoingSharingEnabled();
            $token = $this->request->getParam('token', null);
            $share = $this->ocmMiddleware->getValidShare($id, $token);
            $this->fedShareManager->acceptShare($share);
        } catch (BadRequestException $e) {
            return new Result(
                null,
                Http::STATUS_GONE,
                $e->getMessage()
            );
        } catch (ForbiddenException $e) {
            return new Result(
                null,
                Http::STATUS_FORBIDDEN,
                $e->getMessage()
            );
        } catch (NotImplementedException $e) {
            return new Result(
                null,
                Http::STATUS_SERVICE_UNAVAILABLE,
                'Server does not support federated cloud sharing'
            );
        }
        return new Result();
    }

    /**
     * @NoCSRFRequired
     * @PublicPage
     *
     * decline server-to-server share
     *
     * @param int $id
     *
     * @return Result
     */
    public function declineShare($id) {
        try {
            $token = $this->request->getParam('token', null);
            $this->ocmMiddleware->assertOutgoingSharingEnabled();
            $share = $this->ocmMiddleware->getValidShare($id, $token);
            $this->fedShareManager->declineShare($share);
        } catch (BadRequestException $e) {
            return new Result(
                null,
                Http::STATUS_GONE,
                $e->getMessage()
            );
        } catch (ForbiddenException $e) {
            return new Result(
                null,
                Http::STATUS_FORBIDDEN,
                $e->getMessage()
            );
        } catch (NotImplementedException $e) {
            return new Result(
                null,
                Http::STATUS_SERVICE_UNAVAILABLE,
                'Server does not support federated cloud sharing'
            );
        }

        return new Result();
    }

    /**
     * @NoCSRFRequired
     * @PublicPage
     *
     * remove server-to-server share if it was unshared by the owner
     *
     * @param int $id
     *
     * @return Result
     */
    public function unshare($id) {
        try {
            $this->ocmMiddleware->assertOutgoingSharingEnabled();
            $token = $this->request->getParam('token', null);
            if ($token && $id) {
                $this->fedShareManager->unshare($id, $token);
            }
        } catch (NotImplementedException $e) {
            return new Result(
                null,
                Http::STATUS_SERVICE_UNAVAILABLE,
                'Server does not support federated cloud sharing'
            );
        } catch (\Exception $e) {
            // pass
        }
        return new Result();
    }

    /**
     * @NoCSRFRequired
     * @PublicPage
     *
     * federated share was revoked, either by the owner or the re-sharer
     *
     * @param int $id
     *
     * @return Result
     */
    public function revoke($id) {
        try {
            $token = $this->request->getParam('token', null);
            $share = $this->ocmMiddleware->getValidShare($id, $token);
            $this->fedShareManager->undoReshare($share);
        } catch (\Exception $e) {
            return new Result(null, Http::STATUS_BAD_REQUEST);
        }

        return new Result();
    }

    /**
     * @NoCSRFRequired
     * @PublicPage
     *
     * update share information to keep federated re-shares in sync
     *
     * @param int $id
     *
     * @return Result
     */
    public function updatePermissions($id) {
        try {
            $permissions = $this->request->getParam('permissions', null);
            $token = $this->request->getParam('token', null);
            $share = $this->ocmMiddleware->getValidShare($id, $token);

            // we need to prohibit the permission update if it's not a re-share
            if (!$this->fedShareManager->isFederatedReShare($share)) {
                throw new \Exception("Updating the permissions is only possible on a federated re-share");
            }

            $validPermission = \ctype_digit((string)$permissions);
            if (!$validPermission) {
                throw new \Exception("Not allowed to update the permissions");
            }
            $permissions = $this->ocmMiddleware->normalizePermissions(
                (int) $permissions
            );
            $this->fedShareManager->updatePermissions($share, $permissions);
        } catch (\Exception $e) {
            return new Result(null, Http::STATUS_BAD_REQUEST);
        }

        return new Result();
    }

    private function isFileNameValid(?string $name): bool {
        if ($name === null) {
            return false;
        }
        $v = new View();
        try {
            # new shares will show up in user home - therefore we test with /
            $v->verifyPath('/', $name);
        } catch (InvalidPathException $e) {
            return false;
        }
        return true;
    }
}