owncloud/core

View on GitHub
apps/federatedfilesharing/lib/FedShareManager.php

Summary

Maintainability
C
1 day
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;

use OCA\FederatedFileSharing\Ocm\Permissions;
use OCA\Files_Sharing\Activity;
use OCP\Activity\IManager as ActivityManager;
use OCP\Files\NotFoundException;
use OCP\IUserManager;
use OCP\Notification\IManager as NotificationManager;
use OCP\Share\IShare;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\EventDispatcher\GenericEvent;

/**
 * Class FedShareManager holds the share logic
 *
 * @package OCA\FederatedFileSharing
 */
class FedShareManager {
    public const ACTION_URL = 'ocs/v1.php/apps/files_sharing/api/v1/remote_shares/pending/';

    /**
     * @var FederatedShareProvider
     */
    private $federatedShareProvider;

    /**
     * @var Notifications
     */
    private $notifications;

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

    /**
     * @var ActivityManager
     */
    private $activityManager;

    /**
     * @var NotificationManager
     */
    private $notificationManager;

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

    /**
     * @var EventDispatcherInterface
     */
    private $eventDispatcher;

    /**
     * FedShareManager constructor.
     *
     * @param FederatedShareProvider $federatedShareProvider
     * @param Notifications $notifications
     * @param IUserManager $userManager
     * @param ActivityManager $activityManager
     * @param NotificationManager $notificationManager
     * @param AddressHandler $addressHandler
     * @param EventDispatcherInterface $eventDispatcher
     */
    public function __construct(
        FederatedShareProvider $federatedShareProvider,
        Notifications $notifications,
        IUserManager $userManager,
        ActivityManager $activityManager,
        NotificationManager $notificationManager,
        AddressHandler $addressHandler,
        EventDispatcherInterface $eventDispatcher
    ) {
        $this->federatedShareProvider = $federatedShareProvider;
        $this->notifications = $notifications;
        $this->userManager = $userManager;
        $this->activityManager = $activityManager;
        $this->notificationManager = $notificationManager;
        $this->addressHandler = $addressHandler;
        $this->eventDispatcher = $eventDispatcher;
    }

    /**
     * Create an incoming share
     *
     * @param Address $ownerAddress
     * @param Address $sharedByAddress
     * @param string $shareWith
     * @param string $remoteId
     * @param string $name
     * @param string $token
     *
     * @return void
     */
    public function createShare(
        Address $ownerAddress,
        Address $sharedByAddress,
        $shareWith,
        $remoteId,
        $name,
        $token
    ) {
        $owner = $ownerAddress->getUserId();
        $remote = $ownerAddress->getOrigin();
        $shareId = $this->federatedShareProvider->addShare(
            $remote,
            $token,
            $name,
            $owner,
            $shareWith,
            $remoteId
        );

        $this->eventDispatcher->dispatch(
            new GenericEvent(
                null,
                [
                    'name' => $name,
                    'targetuser' => $sharedByAddress->getCloudId(),
                    'owner' => $owner,
                    'sharewith' => $shareWith,
                    'sharedby' => $sharedByAddress->getUserId(),
                    'remoteid' => $remoteId
                ]
            ),
            '\OCA\FederatedFileSharing::remote_shareReceived'
        );
        $this->publishActivity(
            $shareWith,
            Activity::SUBJECT_REMOTE_SHARE_RECEIVED,
            [$ownerAddress->getCloudId(), \trim($name, '/'), ['shareId' => $shareId]],
            'files',
            '',
            '',
            ''
        );
        $link = $this->getActionLink($shareId);
        $params = [
            $ownerAddress->getCloudId(),
            $sharedByAddress->getCloudId(),
            \trim($name, '/')
        ];
        if (!$this->federatedShareProvider->getAccepted($remote, $shareWith)) {
            $notification = $this->createNotification($shareWith);
            $notification->setDateTime(new \DateTime())
                ->setObject('remote_share', $shareId)
                ->setSubject('remote_share', $params)
                ->setMessage('remote_share', $params);
            $declineAction = $notification->createAction();
            $declineAction->setLabel('decline')
                ->setLink($link, 'DELETE');
            $notification->addAction($declineAction);
            $acceptAction = $notification->createAction();
            $acceptAction->setLabel('accept')
                ->setLink($link, 'POST');
            $notification->addAction($acceptAction);
            $this->notificationManager->notify($notification);
        }
    }

    /**
     * @param IShare $share
     * @param string $remoteId
     * @param string $shareWith
     * @param int|null $permissions - null for OCM 1.0-proposal1
     *
     * @return IShare
     *
     * @throws \OCP\Share\Exceptions\ShareNotFound
     */
    public function reShare(IShare $share, $remoteId, $shareWith, $permissions = null) {
        if ($permissions !== null) {
            $share->setPermissions($share->getPermissions() & $permissions);
        }
        // the recipient of the initial share is now the initiator for the re-share
        $share->setSharedBy($share->getSharedWith());
        $share->setSharedWith($shareWith);
        $result = $this->federatedShareProvider->create($share);
        $this->federatedShareProvider->storeRemoteId(
            (int)$result->getId(),
            $remoteId
        );
        return $result;
    }

    /**
     *
     *
     * @param IShare $share
     *
     * @throws \OCP\Files\InvalidPathException
     * @throws \OCP\Files\NotFoundException
     */
    public function acceptShare(IShare $share) {
        $uid = $this->getCorrectUid($share);
        $fileId = $share->getNode()->getId();
        list($file, $link) = $this->getFile($uid, $fileId);
        $this->publishActivity(
            $uid,
            Activity::SUBJECT_REMOTE_SHARE_ACCEPTED,
            [$share->getSharedWith(), \basename($file)],
            'files',
            $fileId,
            $file,
            $link
        );
        $this->notifyRemote($share, [$this->notifications, 'sendAcceptShare']);
    }

    /**
     * Delete declined share and create a activity
     *
     * @param IShare $share
     *
     * @throws \OCP\Files\InvalidPathException
     * @throws \OCP\Files\NotFoundException
     */
    public function declineShare(IShare $share) {
        $this->notifyRemote($share, [$this->notifications, 'sendDeclineShare']);
        $uid = $this->getCorrectUid($share);
        $fileId = $share->getNode()->getId();
        $this->federatedShareProvider->removeShareFromTable($share);
        list($file, $link) = $this->getFile($uid, $fileId);
        $this->publishActivity(
            $uid,
            Activity::SUBJECT_REMOTE_SHARE_DECLINED,
            [$share->getSharedWith(), \basename($file)],
            'files',
            $fileId,
            $file,
            $link
        );
    }

    /**
     * Unshare an item from self
     *
     * @param int $id
     * @param string $token
     *
     * @return void
     */
    public function unshare($id, $token) {
        $shareRow = $this->federatedShareProvider->unshare($id, $token);
        if ($shareRow === false) {
            return;
        }
        $ownerAddress = new Address($shareRow['owner'] . '@' . $shareRow['remote']);
        $mountpoint = $shareRow['mountpoint'];
        $user = $shareRow['user'];
        if ($shareRow['accepted']) {
            $path = \trim($mountpoint, '/');
        } else {
            $path = \trim($shareRow['name'], '/');
        }
        $notification = $this->createNotification($user);
        $notification->setObject('remote_share', (int) $shareRow['id']);
        $this->notificationManager->markProcessed($notification);
        $this->publishActivity(
            $user,
            Activity::SUBJECT_REMOTE_SHARE_UNSHARED,
            [$ownerAddress->getCloudId(), $path],
            'files',
            '',
            '',
            ''
        );
    }

    /**
     * @param IShare $share
     *
     * @return void
     */
    public function undoReshare(IShare $share) {
        $this->federatedShareProvider->removeShareFromTable($share);
    }

    /**
     * Update permissions
     *
     * @param IShare $share
     * @param string[] $ocmPermissions as ['read', 'write', 'share']
     *
     * @return void
     */
    public function updateOcmPermissions(IShare $share, $ocmPermissions) {
        $permissions = Permissions::toOcPermissions($ocmPermissions);
        $this->updatePermissions($share, $permissions);
    }

    /**
     * Update permissions
     *
     * @param IShare $share
     * @param int $permissions
     *
     * @return void
     */
    public function updatePermissions(IShare $share, int $permissions): void {
        # permissions can only be reduced but not upgraded
        if (!Permissions::isNewPermissionHigher($share->getPermissions(), $permissions)) {
            $share->setPermissions($permissions);
            $this->federatedShareProvider->update($share);
        }
    }

    /**
     * Check if a federated share was re-shared with another federated server.
     *
     * @param IShare $share
     * @return bool
     * @throws NotFoundException
     */
    public function isFederatedReShare(IShare $share) {
        // get all federated shares on this file
        $shares = $this->federatedShareProvider->getSharesByPath($share->getNode());

        foreach ($shares as $matchingShare) {
            // if the share initiator (sharedBy) received a share for this file
            // in the past, this is a re-share
            if ($share->getSharedBy() === $matchingShare->getSharedWith()) {
                return true;
            }
        }

        return false;
    }

    /**
     * @param IShare $share
     * @param callable $callback
     *
     * @throws \OCP\Share\Exceptions\ShareNotFound
     * @throws \OC\HintException
     */
    protected function notifyRemote($share, $callback) {
        if ($share->getShareOwner() !== $share->getSharedBy()) {
            try {
                list(, $remote) = $this->addressHandler->splitUserRemote(
                    $share->getSharedBy()
                );
                $remoteId = $this->federatedShareProvider->getRemoteId($share);
                $callback($remote, $remoteId, $share->getToken());
            } catch (\Exception $e) {
                // expected fail if sender is a local user
            }
        }
    }

    /**
     * Publish a new activity
     *
     * @param string $affectedUser
     * @param string $subject
     * @param array $subjectParams
     * @param string $objectType
     * @param int $objectId
     * @param string $objectName
     * @param string $link
     *
     * @return void
     */
    protected function publishActivity(
        $affectedUser,
        $subject,
        $subjectParams,
        $objectType,
        $objectId,
        $objectName,
        $link
    ) {
        $event = $this->activityManager->generateEvent();
        $event->setApp(Activity::FILES_SHARING_APP)
            ->setType(Activity::TYPE_REMOTE_SHARE)
            ->setAffectedUser($affectedUser)
            ->setSubject($subject, $subjectParams)
            ->setObject($objectType, $objectId, $objectName)
            ->setLink($link);
        $this->activityManager->publish($event);
    }

    /**
     * Get a new notification
     *
     * @param string $uid
     *
     * @return \OCP\Notification\INotification
     */
    protected function createNotification($uid) {
        $notification = $this->notificationManager->createNotification();
        $notification->setApp('files_sharing');
        $notification->setUser($uid);
        return $notification;
    }

    /**
     * @param int $shareId
     * @return string
     */
    protected function getActionLink($shareId) {
        $urlGenerator = \OC::$server->getURLGenerator();
        return $urlGenerator->linkTo('', self::ACTION_URL . $shareId);
    }

    /**
     * Get file
     *
     * @param string $user
     * @param int $fileSource
     *
     * @return array with internal path of the file and a absolute link to it
     */
    protected function getFile($user, $fileSource) {
        \OC_Util::setupFS($user);

        try {
            $file = \OC\Files\Filesystem::getPath($fileSource);
        } catch (NotFoundException $e) {
            $file = null;
        }
        // FIXME:  use permalink here, see ViewController for reference
        $args = \OC\Files\Filesystem::is_dir($file)
            ? ['dir' => $file]
            : ['dir' => \dirname($file), 'scrollto' => $file];
        $link = \OCP\Util::linkToAbsolute('files', 'index.php', $args);

        return [$file, $link];
    }

    /**
     * Check if we are the initiator or the owner of a re-share
     * and return the correct UID
     *
     * @param IShare $share
     *
     * @return string
     */
    protected function getCorrectUid(IShare $share) {
        if ($this->userManager->userExists($share->getShareOwner())) {
            return $share->getShareOwner();
        }

        return $share->getSharedBy();
    }
}