apps/federatedfilesharing/lib/Controller/OcmController.php
<?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);
}
}