owncloud/core

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

Summary

Maintainability
D
2 days
Test Coverage
<?php
/**
 * @author Viktar Dubiniuk <dubiniuk@owncloud.com>
 *
 * @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 OCA\FederatedFileSharing\Address;
use OCA\FederatedFileSharing\AddressHandler;
use OCA\FederatedFileSharing\Middleware\OcmMiddleware;
use OCA\FederatedFileSharing\Ocm\Exception\BadRequestException;
use OCA\FederatedFileSharing\Ocm\Exception\NotImplementedException;
use OCA\FederatedFileSharing\Ocm\Notification\FileNotification;
use OCP\AppFramework\Http\JSONResponse;
use OCA\FederatedFileSharing\FedShareManager;
use OCA\FederatedFileSharing\Ocm\Exception\OcmException;
use OCP\AppFramework\Controller;
use OCP\AppFramework\Http;
use OCP\IConfig;
use OCP\ILogger;
use OCP\IRequest;
use OCP\IURLGenerator;
use OCP\IUserManager;
use OCP\Share\Exceptions\ShareNotFound;

/**
 * Class OcmController
 *
 * @package OCA\FederatedFileSharing\Controller
 */
class OcmController extends Controller {
    public const API_VERSION = '1.0-proposal1';

    /**
     * @var OcmMiddleware
     */
    private $ocmMiddleware;

    /**
     * @var IURLGenerator
     */
    protected $urlGenerator;

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

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

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

    /**
     * @var ILogger
     */
    protected $logger;

    /** @var IConfig */
    private $config;

    /**
     * OcmController constructor.
     *
     * @param string $appName
     * @param IRequest $request
     * @param OcmMiddleware $ocmMiddleware
     * @param IURLGenerator $urlGenerator
     * @param IUserManager $userManager
     * @param AddressHandler $addressHandler
     * @param FedShareManager $fedShareManager
     * @param ILogger $logger
     * @param IConfig $config
     */
    public function __construct(
        $appName,
        IRequest $request,
        OcmMiddleware $ocmMiddleware,
        IURLGenerator $urlGenerator,
        IUserManager $userManager,
        AddressHandler $addressHandler,
        FedShareManager $fedShareManager,
        ILogger $logger,
        IConfig $config
    ) {
        parent::__construct($appName, $request);

        $this->ocmMiddleware = $ocmMiddleware;
        $this->urlGenerator = $urlGenerator;
        $this->userManager = $userManager;
        $this->addressHandler = $addressHandler;
        $this->fedShareManager = $fedShareManager;
        $this->logger = $logger;
        $this->config = $config;
    }

    /**
     * @NoCSRFRequired
     * @PublicPage
     *
     * EndPoint discovery
     * Responds to /ocm-provider/ requests
     *
     * @return array
     */
    public function discovery() {
        $endPoint = $this->urlGenerator->linkToRouteAbsolute(
            "{$this->appName}.ocm.index"
        );
        return [
            'enabled' => true,
            'apiVersion' => self::API_VERSION,
            'endPoint' => \rtrim($endPoint, '/'),
            'shareTypes' => [
                [
                    'name' => FileNotification::RESOURCE_TYPE_FILE,
                    'protocols' => $this->getProtocols()
                ]
            ]
        ];
    }

    /**
     * @NoCSRFRequired
     * @PublicPage
     *
     * @param string $shareWith identifier of the user or group
     *                             to share the resource with
     * @param string $name name of the shared resource
     * @param string $description share description (optional)
     * @param string $providerId Identifier of the resource at the provider side
     * @param string $owner identifier of the user that owns the resource
     * @param string $ownerDisplayName display name of the owner
     * @param string $sender Provider specific identifier of the user that wants
     *                            to share the resource
     * @param string $senderDisplayName Display name of the user that wants
     *                                     to share the resource
     * @param string $shareType Share type ('user' is supported atm)
     * @param string $resourceType only 'file' is supported atm
     * @param array $protocol
     *         [
     *             'name' => (string) protocol name. Only 'webdav' is supported atm,
     *             'options' => [
     *                 protocol specific options
     *                 only `webdav` options are supported atm
     *                 e.g. `uri`,    `access_token`, `password`, `permissions` etc.
     *
     *                 For backward compatibility the webdav protocol will use
     *                 the 'sharedSecret" as username and password
     *             ]
     *
     * @return JSONResponse
     */
    public function createShare(
        $shareWith,
        $name,
        $description,
        $providerId,
        $owner,
        $ownerDisplayName,
        $sender,
        $senderDisplayName,
        $shareType,
        $resourceType,
        $protocol
    ) {
        // Allow other apps to overwrite the behaviour of this endpoint
        $controllerClass = $this->config->getSystemValue('sharing.ocmController');
        if (($controllerClass !== '') && ($controllerClass !== null)) {
            $controller = \OC::$server->query($controllerClass);
            return $controller->createShare(
                $shareWith,
                $name,
                $description,
                $providerId,
                $owner,
                $ownerDisplayName,
                $sender,
                $senderDisplayName,
                $shareType,
                $resourceType,
                $protocol
            );
        }
        try {
            $this->ocmMiddleware->assertIncomingSharingEnabled();
            $this->ocmMiddleware->assertNotNull(
                [
                    'shareWith' => $shareWith,
                    'name' => $name,
                    'providerId' => $providerId,
                    'owner' => $owner,
                    'shareType' => $shareType,
                    'resourceType' => $resourceType
                ]
            );
            if (!\is_array($protocol)
                || !isset($protocol['name'])
                || !isset($protocol['options'])
                || !\is_array($protocol['options'])
                || !isset($protocol['options']['sharedSecret'])
            ) {
                throw new BadRequestException(
                    'server can not add federated share, missing parameter'
                );
            }
            if (!\OCP\Util::isValidFileName($name)) {
                throw new BadRequestException(
                    'The mountpoint name contains invalid characters.'
                );
            }

            if ($this->isSupportedProtocol($protocol['name']) === false) {
                throw new NotImplementedException(
                    "Protocol {$protocol['name']} is not supported"
                );
            }

            if ($this->isSupportedShareType($shareType) === false) {
                throw new NotImplementedException(
                    "ShareType {$shareType} is not supported"
                );
            }

            if ($this->isSupportedResourceType($resourceType) === false) {
                throw new NotImplementedException(
                    "ResourceType {$resourceType} is not supported"
                );
            }

            $shareWithAddress = new Address($shareWith);
            $localShareWith = $shareWithAddress->toLocalUid();
            if (!$this->userManager->userExists($localShareWith)) {
                throw new BadRequestException("User $localShareWith does not exist");
            }

            $ownerAddress = new Address($owner, $ownerDisplayName);
            $sharedByAddress = new Address($sender, $senderDisplayName);

            $this->fedShareManager->createShare(
                $ownerAddress,
                $sharedByAddress,
                $localShareWith,
                $providerId,
                $name,
                $protocol['options']['sharedSecret']
            );
        } catch (OcmException $e) {
            return new JSONResponse(
                ['message' => $e->getMessage()],
                $e->getHttpStatusCode()
            );
        } catch (\Exception $e) {
            $this->logger->error(
                "server can not add federated share, {$e->getMessage()}",
                ['app' => 'federatefilesharing']
            );
            return new JSONResponse(
                [
                    'message' => "internal server error, was not able to add share from {$owner}"
                ],
                Http::STATUS_INTERNAL_SERVER_ERROR
            );
        }
        return new JSONResponse(
            [],
            Http::STATUS_CREATED
        );
    }

    /**
     * @NoCSRFRequired
     * @PublicPage
     *
     * @param string $notificationType notification type (SHARE_REMOVED, etc)
     * @param string $resourceType only 'file' is supported atm
     * @param string $providerId Identifier of the resource at the provider side
     * @param array $notification
     *         [
     *             optional additional parameters, depending on the notification
     *                 and the resource type
     *         ]
     *
     * @return JSONResponse
     */
    public function processNotification(
        $notificationType,
        $resourceType,
        $providerId,
        $notification
    ) {
        // Allow other apps to overwrite the behaviour of this endpoint
        $controllerClass = $this->config->getSystemValue('sharing.ocmController');
        if (($controllerClass !== '') && ($controllerClass !== null)) {
            $controller = \OC::$server->query($controllerClass);
            return $controller->processNotification(
                $notificationType,
                $resourceType,
                $providerId,
                $notification
            );
        }
        try {
            if (!\is_array($notification)) {
                throw new BadRequestException(
                    'server can not add federated share, missing parameter'
                );
            }

            $notification = \array_merge(
                ['sharedSecret' => null],
                $notification
            );

            $this->ocmMiddleware->assertNotNull(
                [
                    'notificationType' => $notificationType,
                    'resourceType' => $resourceType,
                    'providerId' => $providerId,
                    'sharedSecret' => $notification['sharedSecret']
                ]
            );

            if ($this->isSupportedResourceType($resourceType) === false) {
                throw new NotImplementedException(
                    "ResourceType {$resourceType} is not supported"
                );
            }

            switch ($notificationType) {
                case FileNotification::NOTIFICATION_TYPE_SHARE_ACCEPTED:
                    $this->ocmMiddleware->assertOutgoingSharingEnabled();
                    $share = $this->ocmMiddleware->getValidShare(
                        $providerId,
                        $notification['sharedSecret']
                    );
                    $this->fedShareManager->acceptShare($share);
                    break;
                case FileNotification::NOTIFICATION_TYPE_SHARE_DECLINED:
                    $this->ocmMiddleware->assertOutgoingSharingEnabled();
                    $share = $this->ocmMiddleware->getValidShare(
                        $providerId,
                        $notification['sharedSecret']
                    );
                    $this->fedShareManager->declineShare($share);
                    break;
                case FileNotification::NOTIFICATION_TYPE_REQUEST_RESHARE:
                    $this->ocmMiddleware->assertOutgoingSharingEnabled();
                    $this->ocmMiddleware->assertNotNull(
                        [
                            'shareWith' => $notification['shareWith'],
                            'senderId' => $notification['senderId'],
                        ]
                    );
                    $share = $this->ocmMiddleware->getValidShare(
                        $providerId,
                        $notification['sharedSecret']
                    );

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

                    $reShare = $this->fedShareManager->reShare(
                        $share,
                        $notification['senderId'],
                        $notification['shareWith']
                    );
                    return new JSONResponse(
                        [
                            'sharedSecret' => $reShare->getToken(),
                            'providerId' => $reShare->getId()
                        ],
                        Http::STATUS_CREATED
                    );
                    break;
                case FileNotification::NOTIFICATION_TYPE_RESHARE_CHANGE_PERMISSION:
                    $this->ocmMiddleware->assertNotNull(
                        [
                            'permission' => $notification['permission']
                        ]
                    );
                    $share = $this->ocmMiddleware->getValidShare(
                        $providerId,
                        $notification['sharedSecret']
                    );
                    $this->fedShareManager->updateOcmPermissions(
                        $share,
                        $notification['permission']
                    );
                    break;
                case FileNotification::NOTIFICATION_TYPE_SHARE_UNSHARED:
                    $this->fedShareManager->unshare(
                        $providerId,
                        $notification['sharedSecret']
                    );
                    break;
                case FileNotification::NOTIFICATION_TYPE_RESHARE_UNDO:
                    // Stub. Let it fallback to the prev endpoint for now
                    return new JSONResponse(
                        ['message' => "Notification of type {$notificationType} is not supported"],
                        Http::STATUS_NOT_IMPLEMENTED
                    );
                default:
                    return new JSONResponse(
                        ['message' => "Notification of type {$notificationType} is not supported"],
                        Http::STATUS_NOT_IMPLEMENTED
                    );
            }
        } catch (OcmException $e) {
            return new JSONResponse(
                ['message' => $e->getMessage()],
                $e->getHttpStatusCode()
            );
        } catch (\Exception $e) {
            $this->logger->error(
                "server can not process notification, {$e->getMessage()}",
                ['app' => 'federatefilesharing']
            );
            return new JSONResponse(
                [
                    'message' => "internal server error, was not able to process notification"
                ],
                Http::STATUS_INTERNAL_SERVER_ERROR
            );
        }
        return new JSONResponse(
            [],
            Http::STATUS_CREATED
        );
    }

    /**
     * Get list of supported protocols
     *
     * @return array
     */
    protected function getProtocols() {
        return [
            'webdav' => '/public.php/webdav/'
        ];
    }

    /**
     * @param string $resourceType
     *
     * @return bool
     */
    protected function isSupportedResourceType($resourceType) {
        return $resourceType === FileNotification::RESOURCE_TYPE_FILE;
    }

    /**
     * @param string $shareType
     * @return bool
     */
    protected function isSupportedShareType($shareType) {
        // TODO: make it a constant
        return $shareType === 'user';
    }

    /**
     * @param string $protocolName
     * @return bool
     */
    protected function isSupportedProtocol($protocolName) {
        $supportedProtocols = $this->getProtocols();
        $supportedProtocolNames = \array_keys($supportedProtocols);
        return \in_array($protocolName, $supportedProtocolNames);
    }
}