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