migrate therinaldos.com data
Build & Deploy to DigitalOcean Space / build (push) Failing after 2m38s

This commit is contained in:
2024-05-05 15:50:45 -04:00
commit ef1ff240d4
23182 changed files with 3801898 additions and 0 deletions
@@ -0,0 +1,87 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2020, Roeland Jago Douma <roeland@famdouma.nl>
*
* @author Roeland Jago Douma <roeland@famdouma.nl>
* @author Kate Döen <kate.doeen@nextcloud.com>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* 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
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Files_Sharing\Controller;
use OCA\Files_Sharing\AppInfo\Application;
use OCP\AppFramework\Controller;
use OCP\AppFramework\Http\Attribute\OpenAPI;
use OCP\AppFramework\Http\NotFoundResponse;
use OCP\AppFramework\Http\RedirectResponse;
use OCP\AppFramework\Http\Response;
use OCP\IRequest;
use OCP\IURLGenerator;
use OCP\IUserSession;
use OCP\Share\Exceptions\ShareNotFound;
use OCP\Share\IManager as ShareManager;
#[OpenAPI(scope: OpenAPI::SCOPE_IGNORE)]
class AcceptController extends Controller {
/** @var ShareManager */
private $shareManager;
/** @var IUserSession */
private $userSession;
/** @var IURLGenerator */
private $urlGenerator;
public function __construct(IRequest $request, ShareManager $shareManager, IUserSession $userSession, IURLGenerator $urlGenerator) {
parent::__construct(Application::APP_ID, $request);
$this->shareManager = $shareManager;
$this->userSession = $userSession;
$this->urlGenerator = $urlGenerator;
}
/**
* @NoAdminRequired
* @NoCSRFRequired
*/
public function accept(string $shareId): Response {
try {
$share = $this->shareManager->getShareById($shareId);
} catch (ShareNotFound $e) {
return new NotFoundResponse();
}
$user = $this->userSession->getUser();
if ($user === null) {
return new NotFoundResponse();
}
try {
$share = $this->shareManager->acceptShare($share, $user->getUID());
} catch (\Exception $e) {
// Just ignore
}
$url = $this->urlGenerator->linkToRouteAbsolute('files.viewcontroller.showFile', ['fileid' => $share->getNode()->getId()]);
return new RedirectResponse($url);
}
}
@@ -0,0 +1,291 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2016 Roeland Jago Douma <roeland@famdouma.nl>
*
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
* @author Daniel Calviño Sánchez <danxuliu@gmail.com>
* @author Joas Schilling <coding@schilljs.com>
* @author John Molakvoæ <skjnldsv@protonmail.com>
* @author Julius Härtl <jus@bitgrid.net>
* @author Roeland Jago Douma <roeland@famdouma.nl>
* @author Kate Döen <kate.doeen@nextcloud.com>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* 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
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Files_Sharing\Controller;
use OCA\Files_Sharing\ResponseDefinitions;
use OCP\App\IAppManager;
use OCP\AppFramework\Http;
use OCP\AppFramework\Http\DataResponse;
use OCP\AppFramework\OCS\OCSException;
use OCP\AppFramework\OCS\OCSNotFoundException;
use OCP\AppFramework\OCSController;
use OCP\AppFramework\QueryException;
use OCP\Files\IRootFolder;
use OCP\Files\NotFoundException;
use OCP\IGroupManager;
use OCP\IRequest;
use OCP\IServerContainer;
use OCP\IUserManager;
use OCP\Share\Exceptions\GenericShareException;
use OCP\Share\Exceptions\ShareNotFound;
use OCP\Share\IManager as ShareManager;
use OCP\Share\IShare;
/**
* @psalm-import-type Files_SharingDeletedShare from ResponseDefinitions
*/
class DeletedShareAPIController extends OCSController {
/** @var ShareManager */
private $shareManager;
/** @var string */
private $userId;
/** @var IUserManager */
private $userManager;
/** @var IGroupManager */
private $groupManager;
/** @var IRootFolder */
private $rootFolder;
/** @var IAppManager */
private $appManager;
/** @var IServerContainer */
private $serverContainer;
public function __construct(string $appName,
IRequest $request,
ShareManager $shareManager,
string $UserId,
IUserManager $userManager,
IGroupManager $groupManager,
IRootFolder $rootFolder,
IAppManager $appManager,
IServerContainer $serverContainer) {
parent::__construct($appName, $request);
$this->shareManager = $shareManager;
$this->userId = $UserId;
$this->userManager = $userManager;
$this->groupManager = $groupManager;
$this->rootFolder = $rootFolder;
$this->appManager = $appManager;
$this->serverContainer = $serverContainer;
}
/**
* @suppress PhanUndeclaredClassMethod
*
* @return Files_SharingDeletedShare
*/
private function formatShare(IShare $share): array {
$result = [
'id' => $share->getFullId(),
'share_type' => $share->getShareType(),
'uid_owner' => $share->getSharedBy(),
'displayname_owner' => $this->userManager->get($share->getSharedBy())->getDisplayName(),
'permissions' => 0,
'stime' => $share->getShareTime()->getTimestamp(),
'parent' => null,
'expiration' => null,
'token' => null,
'uid_file_owner' => $share->getShareOwner(),
'displayname_file_owner' => $this->userManager->get($share->getShareOwner())->getDisplayName(),
'path' => $share->getTarget(),
];
$userFolder = $this->rootFolder->getUserFolder($share->getSharedBy());
$nodes = $userFolder->getById($share->getNodeId());
if (empty($nodes)) {
// fallback to guessing the path
$node = $userFolder->get($share->getTarget());
if ($node === null || $share->getTarget() === '') {
throw new NotFoundException();
}
} else {
$node = $nodes[0];
}
$result['path'] = $userFolder->getRelativePath($node->getPath());
if ($node instanceof \OCP\Files\Folder) {
$result['item_type'] = 'folder';
} else {
$result['item_type'] = 'file';
}
$result['mimetype'] = $node->getMimetype();
$result['storage_id'] = $node->getStorage()->getId();
$result['storage'] = $node->getStorage()->getCache()->getNumericStorageId();
$result['item_source'] = $node->getId();
$result['file_source'] = $node->getId();
$result['file_parent'] = $node->getParent()->getId();
$result['file_target'] = $share->getTarget();
$result['item_size'] = $node->getSize();
$result['item_mtime'] = $node->getMTime();
$expiration = $share->getExpirationDate();
if ($expiration !== null) {
$result['expiration'] = $expiration->format('Y-m-d 00:00:00');
}
if ($share->getShareType() === IShare::TYPE_GROUP) {
$group = $this->groupManager->get($share->getSharedWith());
$result['share_with'] = $share->getSharedWith();
$result['share_with_displayname'] = $group !== null ? $group->getDisplayName() : $share->getSharedWith();
} elseif ($share->getShareType() === IShare::TYPE_ROOM) {
$result['share_with'] = $share->getSharedWith();
$result['share_with_displayname'] = '';
try {
$result = array_merge($result, $this->getRoomShareHelper()->formatShare($share));
} catch (QueryException $e) {
}
} elseif ($share->getShareType() === IShare::TYPE_DECK) {
$result['share_with'] = $share->getSharedWith();
$result['share_with_displayname'] = '';
try {
$result = array_merge($result, $this->getDeckShareHelper()->formatShare($share));
} catch (QueryException $e) {
}
} elseif ($share->getShareType() === IShare::TYPE_SCIENCEMESH) {
$result['share_with'] = $share->getSharedWith();
$result['share_with_displayname'] = '';
try {
$result = array_merge($result, $this->getSciencemeshShareHelper()->formatShare($share));
} catch (QueryException $e) {
}
}
return $result;
}
/**
* @NoAdminRequired
*
* Get a list of all deleted shares
*
* @return DataResponse<Http::STATUS_OK, Files_SharingDeletedShare[], array{}>
*
* 200: Deleted shares returned
*/
public function index(): DataResponse {
$groupShares = $this->shareManager->getDeletedSharedWith($this->userId, IShare::TYPE_GROUP, null, -1, 0);
$roomShares = $this->shareManager->getDeletedSharedWith($this->userId, IShare::TYPE_ROOM, null, -1, 0);
$deckShares = $this->shareManager->getDeletedSharedWith($this->userId, IShare::TYPE_DECK, null, -1, 0);
$sciencemeshShares = $this->shareManager->getDeletedSharedWith($this->userId, IShare::TYPE_SCIENCEMESH, null, -1, 0);
$shares = array_merge($groupShares, $roomShares, $deckShares, $sciencemeshShares);
$shares = array_map(function (IShare $share) {
return $this->formatShare($share);
}, $shares);
return new DataResponse($shares);
}
/**
* @NoAdminRequired
*
* Undelete a deleted share
*
* @param string $id ID of the share
* @return DataResponse<Http::STATUS_OK, array<empty>, array{}>
* @throws OCSException
* @throws OCSNotFoundException Share not found
*
* 200: Share undeleted successfully
*/
public function undelete(string $id): DataResponse {
try {
$share = $this->shareManager->getShareById($id, $this->userId);
} catch (ShareNotFound $e) {
throw new OCSNotFoundException('Share not found');
}
if ($share->getPermissions() !== 0) {
throw new OCSNotFoundException('No deleted share found');
}
try {
$this->shareManager->restoreShare($share, $this->userId);
} catch (GenericShareException $e) {
throw new OCSException('Something went wrong');
}
return new DataResponse([]);
}
/**
* Returns the helper of DeletedShareAPIController for room shares.
*
* If the Talk application is not enabled or the helper is not available
* a QueryException is thrown instead.
*
* @return \OCA\Talk\Share\Helper\DeletedShareAPIController
* @throws QueryException
*/
private function getRoomShareHelper() {
if (!$this->appManager->isEnabledForUser('spreed')) {
throw new QueryException();
}
return $this->serverContainer->get('\OCA\Talk\Share\Helper\DeletedShareAPIController');
}
/**
* Returns the helper of DeletedShareAPIHelper for deck shares.
*
* If the Deck application is not enabled or the helper is not available
* a QueryException is thrown instead.
*
* @return \OCA\Deck\Sharing\ShareAPIHelper
* @throws QueryException
*/
private function getDeckShareHelper() {
if (!$this->appManager->isEnabledForUser('deck')) {
throw new QueryException();
}
return $this->serverContainer->get('\OCA\Deck\Sharing\ShareAPIHelper');
}
/**
* Returns the helper of DeletedShareAPIHelper for sciencemesh shares.
*
* If the sciencemesh application is not enabled or the helper is not available
* a QueryException is thrown instead.
*
* @return \OCA\Deck\Sharing\ShareAPIHelper
* @throws QueryException
*/
private function getSciencemeshShareHelper() {
if (!$this->appManager->isEnabledForUser('sciencemesh')) {
throw new QueryException();
}
return $this->serverContainer->get('\OCA\ScienceMesh\Sharing\ShareAPIHelper');
}
}
@@ -0,0 +1,143 @@
<?php
/**
* @copyright Copyright (c) 2016, ownCloud, Inc.
*
* @author Björn Schießle <bjoern@schiessle.org>
* @author Joas Schilling <coding@schilljs.com>
* @author Lukas Reschke <lukas@statuscode.ch>
* @author Robin Appelman <robin@icewind.nl>
* @author Roeland Jago Douma <roeland@famdouma.nl>
*
* @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\Files_Sharing\Controller;
use OCP\AppFramework\Controller;
use OCP\AppFramework\Http\DataResponse;
use OCP\AppFramework\Http\JSONResponse;
use OCP\Http\Client\IClientService;
use OCP\IConfig;
use OCP\IRequest;
/**
* Class ExternalSharesController
*
* @package OCA\Files_Sharing\Controller
*/
class ExternalSharesController extends Controller {
public function __construct(
string $appName,
IRequest $request,
private \OCA\Files_Sharing\External\Manager $externalManager,
private IClientService $clientService,
private IConfig $config,
) {
parent::__construct($appName, $request);
}
/**
* @NoAdminRequired
* @NoOutgoingFederatedSharingRequired
*
* @return JSONResponse
*/
public function index() {
return new JSONResponse($this->externalManager->getOpenShares());
}
/**
* @NoAdminRequired
* @NoOutgoingFederatedSharingRequired
*
* @param int $id
* @return JSONResponse
*/
public function create($id) {
$this->externalManager->acceptShare($id);
return new JSONResponse();
}
/**
* @NoAdminRequired
* @NoOutgoingFederatedSharingRequired
*
* @param integer $id
* @return JSONResponse
*/
public function destroy($id) {
$this->externalManager->declineShare($id);
return new JSONResponse();
}
/**
* Test whether the specified remote is accessible
*
* @param string $remote
* @param bool $checkVersion
* @return bool
*/
protected function testUrl($remote, $checkVersion = false) {
try {
$client = $this->clientService->newClient();
$response = json_decode($client->get(
$remote,
[
'timeout' => 3,
'connect_timeout' => 3,
'verify' => !$this->config->getSystemValueBool('sharing.federation.allowSelfSignedCertificates', false),
]
)->getBody());
if ($checkVersion) {
return !empty($response->version) && version_compare($response->version, '7.0.0', '>=');
} else {
return is_object($response);
}
} catch (\Exception $e) {
return false;
}
}
/**
* @PublicPage
* @NoOutgoingFederatedSharingRequired
* @NoIncomingFederatedSharingRequired
*
* @param string $remote
* @return DataResponse
*/
public function testRemote($remote) {
if (str_contains($remote, '#') || str_contains($remote, '?') || str_contains($remote, ';')) {
return new DataResponse(false);
}
if (
$this->testUrl('https://' . $remote . '/ocm-provider/') ||
$this->testUrl('https://' . $remote . '/ocm-provider/index.php') ||
$this->testUrl('https://' . $remote . '/status.php', true)
) {
return new DataResponse('https');
} elseif (
$this->testUrl('http://' . $remote . '/ocm-provider/') ||
$this->testUrl('http://' . $remote . '/ocm-provider/index.php') ||
$this->testUrl('http://' . $remote . '/status.php', true)
) {
return new DataResponse('http');
} else {
return new DataResponse(false);
}
}
}
@@ -0,0 +1,205 @@
<?php
/**
* @copyright Copyright (c) 2016, Roeland Jago Douma <roeland@famdouma.nl>
*
* @author Julius Härtl <jus@bitgrid.net>
* @author Morris Jobke <hey@morrisjobke.de>
* @author Roeland Jago Douma <roeland@famdouma.nl>
* @author Kate Döen <kate.doeen@nextcloud.com>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* 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
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Files_Sharing\Controller;
use OCP\AppFramework\Http;
use OCP\AppFramework\Http\DataResponse;
use OCP\AppFramework\Http\FileDisplayResponse;
use OCP\AppFramework\PublicShareController;
use OCP\Constants;
use OCP\Files\Folder;
use OCP\Files\NotFoundException;
use OCP\IPreview;
use OCP\IRequest;
use OCP\ISession;
use OCP\Share\Exceptions\ShareNotFound;
use OCP\Share\IManager as ShareManager;
use OCP\Share\IShare;
class PublicPreviewController extends PublicShareController {
/** @var ShareManager */
private $shareManager;
/** @var IPreview */
private $previewManager;
/** @var IShare */
private $share;
public function __construct(string $appName,
IRequest $request,
ShareManager $shareManger,
ISession $session,
IPreview $previewManager) {
parent::__construct($appName, $request, $session);
$this->shareManager = $shareManger;
$this->previewManager = $previewManager;
}
protected function getPasswordHash(): string {
return $this->share->getPassword();
}
public function isValidToken(): bool {
try {
$this->share = $this->shareManager->getShareByToken($this->getToken());
return true;
} catch (ShareNotFound $e) {
return false;
}
}
protected function isPasswordProtected(): bool {
return $this->share->getPassword() !== null;
}
/**
* @PublicPage
* @NoCSRFRequired
*
* Get a preview for a shared file
*
* @param string $token Token of the share
* @param string $file File in the share
* @param int $x Width of the preview
* @param int $y Height of the preview
* @param bool $a Whether to not crop the preview
* @return FileDisplayResponse<Http::STATUS_OK, array{Content-Type: string}>|DataResponse<Http::STATUS_BAD_REQUEST|Http::STATUS_FORBIDDEN|Http::STATUS_NOT_FOUND, array<empty>, array{}>
*
* 200: Preview returned
* 400: Getting preview is not possible
* 403: Getting preview is not allowed
* 404: Share or preview not found
*/
public function getPreview(
string $token,
string $file = '',
int $x = 32,
int $y = 32,
$a = false
) {
if ($token === '' || $x === 0 || $y === 0) {
return new DataResponse([], Http::STATUS_BAD_REQUEST);
}
try {
$share = $this->shareManager->getShareByToken($token);
} catch (ShareNotFound $e) {
return new DataResponse([], Http::STATUS_NOT_FOUND);
}
if (($share->getPermissions() & Constants::PERMISSION_READ) === 0) {
return new DataResponse([], Http::STATUS_FORBIDDEN);
}
$attributes = $share->getAttributes();
if ($attributes !== null && $attributes->getAttribute('permissions', 'download') === false) {
return new DataResponse([], Http::STATUS_FORBIDDEN);
}
try {
$node = $share->getNode();
if ($node instanceof Folder) {
$file = $node->get($file);
} else {
$file = $node;
}
$f = $this->previewManager->getPreview($file, $x, $y, !$a);
$response = new FileDisplayResponse($f, Http::STATUS_OK, ['Content-Type' => $f->getMimeType()]);
$response->cacheFor(3600 * 24);
return $response;
} catch (NotFoundException $e) {
return new DataResponse([], Http::STATUS_NOT_FOUND);
} catch (\InvalidArgumentException $e) {
return new DataResponse([], Http::STATUS_BAD_REQUEST);
}
}
/**
* @PublicPage
* @NoCSRFRequired
* @NoSameSiteCookieRequired
*
* Get a direct link preview for a shared file
*
* @param string $token Token of the share
* @return FileDisplayResponse<Http::STATUS_OK, array{Content-Type: string}>|DataResponse<Http::STATUS_BAD_REQUEST|Http::STATUS_FORBIDDEN|Http::STATUS_NOT_FOUND, array<empty>, array{}>
*
* 200: Preview returned
* 400: Getting preview is not possible
* 403: Getting preview is not allowed
* 404: Share or preview not found
*/
public function directLink(string $token) {
// No token no image
if ($token === '') {
return new DataResponse([], Http::STATUS_BAD_REQUEST);
}
// No share no image
try {
$share = $this->shareManager->getShareByToken($token);
} catch (ShareNotFound $e) {
return new DataResponse([], Http::STATUS_NOT_FOUND);
}
// No permissions no image
if (($share->getPermissions() & Constants::PERMISSION_READ) === 0) {
return new DataResponse([], Http::STATUS_FORBIDDEN);
}
// Password protected shares have no direct link!
if ($share->getPassword() !== null) {
return new DataResponse([], Http::STATUS_FORBIDDEN);
}
$attributes = $share->getAttributes();
if ($attributes !== null && $attributes->getAttribute('permissions', 'download') === false) {
return new DataResponse([], Http::STATUS_FORBIDDEN);
}
try {
$node = $share->getNode();
if ($node instanceof Folder) {
// Direct link only works for single files
return new DataResponse([], Http::STATUS_BAD_REQUEST);
}
$f = $this->previewManager->getPreview($node, -1, -1, false);
$response = new FileDisplayResponse($f, Http::STATUS_OK, ['Content-Type' => $f->getMimeType()]);
$response->cacheFor(3600 * 24);
return $response;
} catch (NotFoundException $e) {
return new DataResponse([], Http::STATUS_NOT_FOUND);
} catch (\InvalidArgumentException $e) {
return new DataResponse([], Http::STATUS_BAD_REQUEST);
}
}
}
@@ -0,0 +1,202 @@
<?php
/**
* @copyright Copyright (c) 2016, ownCloud, Inc.
*
* @author Bjoern Schiessle <bjoern@schiessle.org>
* @author Joas Schilling <coding@schilljs.com>
* @author Roeland Jago Douma <roeland@famdouma.nl>
* @author Kate Döen <kate.doeen@nextcloud.com>
*
* @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\Files_Sharing\Controller;
use OCA\Files_Sharing\External\Manager;
use OCA\Files_Sharing\ResponseDefinitions;
use OCP\AppFramework\Http;
use OCP\AppFramework\Http\DataResponse;
use OCP\AppFramework\OCS\OCSForbiddenException;
use OCP\AppFramework\OCS\OCSNotFoundException;
use OCP\AppFramework\OCSController;
use OCP\IRequest;
use Psr\Log\LoggerInterface;
/**
* @psalm-import-type Files_SharingRemoteShare from ResponseDefinitions
*/
class RemoteController extends OCSController {
/**
* @NoAdminRequired
*
* Remote constructor.
*
* @param string $appName
* @param IRequest $request
* @param Manager $externalManager
*/
public function __construct(
$appName,
IRequest $request,
private Manager $externalManager,
private LoggerInterface $logger,
) {
parent::__construct($appName, $request);
}
/**
* @NoAdminRequired
*
* Get list of pending remote shares
*
* @return DataResponse<Http::STATUS_OK, Files_SharingRemoteShare[], array{}>
*
* 200: Pending remote shares returned
*/
public function getOpenShares() {
return new DataResponse($this->externalManager->getOpenShares());
}
/**
* @NoAdminRequired
*
* Accept a remote share
*
* @param int $id ID of the share
* @return DataResponse<Http::STATUS_OK, array<empty>, array{}>
* @throws OCSNotFoundException Share not found
*
* 200: Share accepted successfully
*/
public function acceptShare($id) {
if ($this->externalManager->acceptShare($id)) {
return new DataResponse();
}
$this->logger->error('Could not accept federated share with id: ' . $id,
['app' => 'files_sharing']);
throw new OCSNotFoundException('wrong share ID, share does not exist.');
}
/**
* @NoAdminRequired
*
* Decline a remote share
*
* @param int $id ID of the share
* @return DataResponse<Http::STATUS_OK, array<empty>, array{}>
* @throws OCSNotFoundException Share not found
*
* 200: Share declined successfully
*/
public function declineShare($id) {
if ($this->externalManager->declineShare($id)) {
return new DataResponse();
}
// Make sure the user has no notification for something that does not exist anymore.
$this->externalManager->processNotification($id);
throw new OCSNotFoundException('wrong share ID, share does not exist.');
}
/**
* @param array $share Share with info from the share_external table
* @return array enriched share info with data from the filecache
*/
private static function extendShareInfo($share) {
$view = new \OC\Files\View('/' . \OC_User::getUser() . '/files/');
$info = $view->getFileInfo($share['mountpoint']);
if ($info === false) {
return $share;
}
$share['mimetype'] = $info->getMimetype();
$share['mtime'] = $info->getMTime();
$share['permissions'] = $info->getPermissions();
$share['type'] = $info->getType();
$share['file_id'] = $info->getId();
return $share;
}
/**
* @NoAdminRequired
*
* Get a list of accepted remote shares
*
* @return DataResponse<Http::STATUS_OK, Files_SharingRemoteShare[], array{}>
*
* 200: Accepted remote shares returned
*/
public function getShares() {
$shares = $this->externalManager->getAcceptedShares();
$shares = array_map('self::extendShareInfo', $shares);
return new DataResponse($shares);
}
/**
* @NoAdminRequired
*
* Get info of a remote share
*
* @param int $id ID of the share
* @return DataResponse<Http::STATUS_OK, Files_SharingRemoteShare, array{}>
* @throws OCSNotFoundException Share not found
*
* 200: Share returned
*/
public function getShare($id) {
$shareInfo = $this->externalManager->getShare($id);
if ($shareInfo === false) {
throw new OCSNotFoundException('share does not exist');
} else {
$shareInfo = self::extendShareInfo($shareInfo);
return new DataResponse($shareInfo);
}
}
/**
* @NoAdminRequired
*
* Unshare a remote share
*
* @param int $id ID of the share
* @return DataResponse<Http::STATUS_OK, array<empty>, array{}>
* @throws OCSNotFoundException Share not found
* @throws OCSForbiddenException Unsharing is not possible
*
* 200: Share unshared successfully
*/
public function unshare($id) {
$shareInfo = $this->externalManager->getShare($id);
if ($shareInfo === false) {
throw new OCSNotFoundException('Share does not exist');
}
$mountPoint = '/' . \OC_User::getUser() . '/files' . $shareInfo['mountpoint'];
if ($this->externalManager->removeShare($mountPoint) === true) {
return new DataResponse();
} else {
throw new OCSForbiddenException('Could not unshare');
}
}
}
@@ -0,0 +1,75 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2019, Roeland Jago Douma <roeland@famdouma.nl>
*
* @author Roeland Jago Douma <roeland@famdouma.nl>
* @author Hinrich Mahler <nextcloud@mahlerhome.de>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* 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
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Files_Sharing\Controller;
use OCA\Files_Sharing\AppInfo\Application;
use OCP\AppFramework\Controller;
use OCP\AppFramework\Http\JSONResponse;
use OCP\IConfig;
use OCP\IRequest;
class SettingsController extends Controller {
/** @var IConfig */
private $config;
/** @var string */
private $userId;
public function __construct(IRequest $request,
IConfig $config,
string $userId) {
parent::__construct(Application::APP_ID, $request);
$this->config = $config;
$this->userId = $userId;
}
/**
* @NoAdminRequired
*/
public function setDefaultAccept(bool $accept): JSONResponse {
$this->config->setUserValue($this->userId, Application::APP_ID, 'default_accept', $accept ? 'yes' : 'no');
return new JSONResponse();
}
/**
* @NoAdminRequired
*/
public function setUserShareFolder(string $shareFolder): JSONResponse {
$this->config->setUserValue($this->userId, Application::APP_ID, 'share_folder', $shareFolder);
return new JSONResponse();
}
/**
* @NoAdminRequired
*/
public function resetUserShareFolder(): JSONResponse {
$this->config->deleteUserValue($this->userId, Application::APP_ID, 'share_folder');
return new JSONResponse();
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,584 @@
<?php
/**
* @copyright Copyright (c) 2016, ownCloud, Inc.
*
* @author Arthur Schiwon <blizzz@arthur-schiwon.de>
* @author Bjoern Schiessle <bjoern@schiessle.org>
* @author Björn Schießle <bjoern@schiessle.org>
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
* @author Daniel Calviño Sánchez <danxuliu@gmail.com>
* @author Georg Ehrke <oc.list@georgehrke.com>
* @author j3l11234 <297259024@qq.com>
* @author Joas Schilling <coding@schilljs.com>
* @author John Molakvoæ <skjnldsv@protonmail.com>
* @author Jonas Sulzer <jonas@violoncello.ch>
* @author Julius Härtl <jus@bitgrid.net>
* @author Lukas Reschke <lukas@statuscode.ch>
* @author MartB <mart.b@outlook.de>
* @author Maxence Lange <maxence@pontapreta.net>
* @author Michael Weimann <mail@michael-weimann.eu>
* @author Morris Jobke <hey@morrisjobke.de>
* @author Piotr Filiciak <piotr@filiciak.pl>
* @author Robin Appelman <robin@icewind.nl>
* @author Roeland Jago Douma <roeland@famdouma.nl>
* @author Sascha Sambale <mastixmc@gmail.com>
* @author Thomas Müller <thomas.mueller@tmit.eu>
* @author Vincent Petry <vincent@nextcloud.com>
* @author Kate Döen <kate.doeen@nextcloud.com>
*
* @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\Files_Sharing\Controller;
use OC\Security\CSP\ContentSecurityPolicy;
use OC_Files;
use OC_Util;
use OCA\FederatedFileSharing\FederatedShareProvider;
use OCA\Files_Sharing\Activity\Providers\Downloads;
use OCA\Files_Sharing\Event\BeforeTemplateRenderedEvent;
use OCA\Files_Sharing\Event\ShareLinkAccessedEvent;
use OCP\Accounts\IAccountManager;
use OCP\AppFramework\AuthPublicShareController;
use OCP\AppFramework\Http\Attribute\OpenAPI;
use OCP\AppFramework\Http\NotFoundResponse;
use OCP\AppFramework\Http\TemplateResponse;
use OCP\Defaults;
use OCP\EventDispatcher\IEventDispatcher;
use OCP\Files\Folder;
use OCP\Files\IRootFolder;
use OCP\Files\NotFoundException;
use OCP\IConfig;
use OCP\IL10N;
use OCP\IPreview;
use OCP\IRequest;
use OCP\ISession;
use OCP\IURLGenerator;
use OCP\IUserManager;
use OCP\Security\ISecureRandom;
use OCP\Share;
use OCP\Share\Exceptions\ShareNotFound;
use OCP\Share\IManager as ShareManager;
use OCP\Share\IPublicShareTemplateFactory;
use OCP\Share\IShare;
use OCP\Template;
/**
* @package OCA\Files_Sharing\Controllers
*/
#[OpenAPI(scope: OpenAPI::SCOPE_IGNORE)]
class ShareController extends AuthPublicShareController {
protected ?Share\IShare $share = null;
public const SHARE_ACCESS = 'access';
public const SHARE_AUTH = 'auth';
public const SHARE_DOWNLOAD = 'download';
public function __construct(
string $appName,
IRequest $request,
protected IConfig $config,
IURLGenerator $urlGenerator,
protected IUserManager $userManager,
protected \OCP\Activity\IManager $activityManager,
protected ShareManager $shareManager,
ISession $session,
protected IPreview $previewManager,
protected IRootFolder $rootFolder,
protected FederatedShareProvider $federatedShareProvider,
protected IAccountManager $accountManager,
protected IEventDispatcher $eventDispatcher,
protected IL10N $l10n,
protected ISecureRandom $secureRandom,
protected Defaults $defaults,
private IPublicShareTemplateFactory $publicShareTemplateFactory,
) {
parent::__construct($appName, $request, $session, $urlGenerator);
}
/**
* @PublicPage
* @NoCSRFRequired
*
* Show the authentication page
* The form has to submit to the authenticate method route
*/
public function showAuthenticate(): TemplateResponse {
$templateParameters = ['share' => $this->share];
$this->eventDispatcher->dispatchTyped(new BeforeTemplateRenderedEvent($this->share, BeforeTemplateRenderedEvent::SCOPE_PUBLIC_SHARE_AUTH));
$response = new TemplateResponse('core', 'publicshareauth', $templateParameters, 'guest');
if ($this->share->getSendPasswordByTalk()) {
$csp = new ContentSecurityPolicy();
$csp->addAllowedConnectDomain('*');
$csp->addAllowedMediaDomain('blob:');
$response->setContentSecurityPolicy($csp);
}
return $response;
}
/**
* The template to show when authentication failed
*/
protected function showAuthFailed(): TemplateResponse {
$templateParameters = ['share' => $this->share, 'wrongpw' => true];
$this->eventDispatcher->dispatchTyped(new BeforeTemplateRenderedEvent($this->share, BeforeTemplateRenderedEvent::SCOPE_PUBLIC_SHARE_AUTH));
$response = new TemplateResponse('core', 'publicshareauth', $templateParameters, 'guest');
if ($this->share->getSendPasswordByTalk()) {
$csp = new ContentSecurityPolicy();
$csp->addAllowedConnectDomain('*');
$csp->addAllowedMediaDomain('blob:');
$response->setContentSecurityPolicy($csp);
}
return $response;
}
/**
* The template to show after user identification
*/
protected function showIdentificationResult(bool $success = false): TemplateResponse {
$templateParameters = ['share' => $this->share, 'identityOk' => $success];
$this->eventDispatcher->dispatchTyped(new BeforeTemplateRenderedEvent($this->share, BeforeTemplateRenderedEvent::SCOPE_PUBLIC_SHARE_AUTH));
$response = new TemplateResponse('core', 'publicshareauth', $templateParameters, 'guest');
if ($this->share->getSendPasswordByTalk()) {
$csp = new ContentSecurityPolicy();
$csp->addAllowedConnectDomain('*');
$csp->addAllowedMediaDomain('blob:');
$response->setContentSecurityPolicy($csp);
}
return $response;
}
/**
* Validate the identity token of a public share
*
* @param ?string $identityToken
* @return bool
*/
protected function validateIdentity(?string $identityToken = null): bool {
if ($this->share->getShareType() !== IShare::TYPE_EMAIL) {
return false;
}
if ($identityToken === null || $this->share->getSharedWith() === null) {
return false;
}
return $identityToken === $this->share->getSharedWith();
}
/**
* Generates a password for the share, respecting any password policy defined
*/
protected function generatePassword(): void {
$event = new \OCP\Security\Events\GenerateSecurePasswordEvent();
$this->eventDispatcher->dispatchTyped($event);
$password = $event->getPassword() ?? $this->secureRandom->generate(20);
$this->share->setPassword($password);
$this->shareManager->updateShare($this->share);
}
protected function verifyPassword(string $password): bool {
return $this->shareManager->checkPassword($this->share, $password);
}
protected function getPasswordHash(): string {
return $this->share->getPassword();
}
public function isValidToken(): bool {
try {
$this->share = $this->shareManager->getShareByToken($this->getToken());
} catch (ShareNotFound $e) {
return false;
}
return true;
}
protected function isPasswordProtected(): bool {
return $this->share->getPassword() !== null;
}
protected function authSucceeded() {
// For share this was always set so it is still used in other apps
$this->session->set('public_link_authenticated', (string)$this->share->getId());
}
protected function authFailed() {
$this->emitAccessShareHook($this->share, 403, 'Wrong password');
$this->emitShareAccessEvent($this->share, self::SHARE_AUTH, 403, 'Wrong password');
}
/**
* throws hooks when a share is attempted to be accessed
*
* @param \OCP\Share\IShare|string $share the Share instance if available,
* otherwise token
* @param int $errorCode
* @param string $errorMessage
*
* @throws \OCP\HintException
* @throws \OC\ServerNotAvailableException
*
* @deprecated use OCP\Files_Sharing\Event\ShareLinkAccessedEvent
*/
protected function emitAccessShareHook($share, int $errorCode = 200, string $errorMessage = '') {
$itemType = $itemSource = $uidOwner = '';
$token = $share;
$exception = null;
if ($share instanceof \OCP\Share\IShare) {
try {
$token = $share->getToken();
$uidOwner = $share->getSharedBy();
$itemType = $share->getNodeType();
$itemSource = $share->getNodeId();
} catch (\Exception $e) {
// we log what we know and pass on the exception afterwards
$exception = $e;
}
}
\OC_Hook::emit(Share::class, 'share_link_access', [
'itemType' => $itemType,
'itemSource' => $itemSource,
'uidOwner' => $uidOwner,
'token' => $token,
'errorCode' => $errorCode,
'errorMessage' => $errorMessage
]);
if (!is_null($exception)) {
throw $exception;
}
}
/**
* Emit a ShareLinkAccessedEvent event when a share is accessed, downloaded, auth...
*/
protected function emitShareAccessEvent(IShare $share, string $step = '', int $errorCode = 200, string $errorMessage = ''): void {
if ($step !== self::SHARE_ACCESS &&
$step !== self::SHARE_AUTH &&
$step !== self::SHARE_DOWNLOAD) {
return;
}
$this->eventDispatcher->dispatchTyped(new ShareLinkAccessedEvent($share, $step, $errorCode, $errorMessage));
}
/**
* Validate the permissions of the share
*
* @param Share\IShare $share
* @return bool
*/
private function validateShare(\OCP\Share\IShare $share) {
// If the owner is disabled no access to the link is granted
$owner = $this->userManager->get($share->getShareOwner());
if ($owner === null || !$owner->isEnabled()) {
return false;
}
// If the initiator of the share is disabled no access is granted
$initiator = $this->userManager->get($share->getSharedBy());
if ($initiator === null || !$initiator->isEnabled()) {
return false;
}
return $share->getNode()->isReadable() && $share->getNode()->isShareable();
}
/**
* @PublicPage
* @NoCSRFRequired
*
*
* @param string $path
* @return TemplateResponse
* @throws NotFoundException
* @throws \Exception
*/
public function showShare($path = ''): TemplateResponse {
\OC_User::setIncognitoMode(true);
// Check whether share exists
try {
$share = $this->shareManager->getShareByToken($this->getToken());
} catch (ShareNotFound $e) {
// The share does not exists, we do not emit an ShareLinkAccessedEvent
$this->emitAccessShareHook($this->getToken(), 404, 'Share not found');
throw new NotFoundException();
}
if (!$this->validateShare($share)) {
throw new NotFoundException();
}
$shareNode = $share->getNode();
try {
$templateProvider = $this->publicShareTemplateFactory->getProvider($share);
$response = $templateProvider->renderPage($share, $this->getToken(), $path);
} catch (NotFoundException $e) {
$this->emitAccessShareHook($share, 404, 'Share not found');
$this->emitShareAccessEvent($share, ShareController::SHARE_ACCESS, 404, 'Share not found');
throw new NotFoundException();
}
// We can't get the path of a file share
try {
if ($shareNode instanceof \OCP\Files\File && $path !== '') {
$this->emitAccessShareHook($share, 404, 'Share not found');
$this->emitShareAccessEvent($share, self::SHARE_ACCESS, 404, 'Share not found');
throw new NotFoundException();
}
} catch (\Exception $e) {
$this->emitAccessShareHook($share, 404, 'Share not found');
$this->emitShareAccessEvent($share, self::SHARE_ACCESS, 404, 'Share not found');
throw $e;
}
$this->emitAccessShareHook($share);
$this->emitShareAccessEvent($share, self::SHARE_ACCESS);
return $response;
}
/**
* @PublicPage
* @NoCSRFRequired
* @NoSameSiteCookieRequired
*
* @param string $token
* @param string $files
* @param string $path
* @param string $downloadStartSecret
* @return void|\OCP\AppFramework\Http\Response
* @throws NotFoundException
*/
public function downloadShare($token, $files = null, $path = '', $downloadStartSecret = '') {
\OC_User::setIncognitoMode(true);
$share = $this->shareManager->getShareByToken($token);
if (!($share->getPermissions() & \OCP\Constants::PERMISSION_READ)) {
return new \OCP\AppFramework\Http\DataResponse('Share has no read permission');
}
$files_list = null;
if (!is_null($files)) { // download selected files
$files_list = json_decode($files);
// in case we get only a single file
if ($files_list === null) {
$files_list = [$files];
}
// Just in case $files is a single int like '1234'
if (!is_array($files_list)) {
$files_list = [$files_list];
}
}
if (!$this->validateShare($share)) {
throw new NotFoundException();
}
$userFolder = $this->rootFolder->getUserFolder($share->getShareOwner());
$originalSharePath = $userFolder->getRelativePath($share->getNode()->getPath());
// Single file share
if ($share->getNode() instanceof \OCP\Files\File) {
// Single file download
$this->singleFileDownloaded($share, $share->getNode());
}
// Directory share
else {
/** @var \OCP\Files\Folder $node */
$node = $share->getNode();
// Try to get the path
if ($path !== '') {
try {
$node = $node->get($path);
} catch (NotFoundException $e) {
$this->emitAccessShareHook($share, 404, 'Share not found');
$this->emitShareAccessEvent($share, self::SHARE_DOWNLOAD, 404, 'Share not found');
return new NotFoundResponse();
}
}
$originalSharePath = $userFolder->getRelativePath($node->getPath());
if ($node instanceof \OCP\Files\File) {
// Single file download
$this->singleFileDownloaded($share, $share->getNode());
} else {
try {
if (!empty($files_list)) {
$this->fileListDownloaded($share, $files_list, $node);
} else {
// The folder is downloaded
$this->singleFileDownloaded($share, $share->getNode());
}
} catch (NotFoundException $e) {
return new NotFoundResponse();
}
}
}
/* FIXME: We should do this all nicely in OCP */
OC_Util::tearDownFS();
OC_Util::setupFS($share->getShareOwner());
/**
* this sets a cookie to be able to recognize the start of the download
* the content must not be longer than 32 characters and must only contain
* alphanumeric characters
*/
if (!empty($downloadStartSecret)
&& !isset($downloadStartSecret[32])
&& preg_match('!^[a-zA-Z0-9]+$!', $downloadStartSecret) === 1) {
// FIXME: set on the response once we use an actual app framework response
setcookie('ocDownloadStarted', $downloadStartSecret, time() + 20, '/');
}
$this->emitAccessShareHook($share);
$this->emitShareAccessEvent($share, self::SHARE_DOWNLOAD);
$server_params = [ 'head' => $this->request->getMethod() === 'HEAD' ];
/**
* Http range requests support
*/
if (isset($_SERVER['HTTP_RANGE'])) {
$server_params['range'] = $this->request->getHeader('Range');
}
// download selected files
if (!is_null($files) && $files !== '') {
// FIXME: The exit is required here because otherwise the AppFramework is trying to add headers as well
// after dispatching the request which results in a "Cannot modify header information" notice.
OC_Files::get($originalSharePath, $files_list, $server_params);
exit();
} else {
// FIXME: The exit is required here because otherwise the AppFramework is trying to add headers as well
// after dispatching the request which results in a "Cannot modify header information" notice.
OC_Files::get(dirname($originalSharePath), basename($originalSharePath), $server_params);
exit();
}
}
/**
* create activity for every downloaded file
*
* @param Share\IShare $share
* @param array $files_list
* @param \OCP\Files\Folder $node
* @throws NotFoundException when trying to download a folder or multiple files of a "hide download" share
*/
protected function fileListDownloaded(Share\IShare $share, array $files_list, \OCP\Files\Folder $node) {
if ($share->getHideDownload() && count($files_list) > 1) {
throw new NotFoundException('Downloading more than 1 file');
}
foreach ($files_list as $file) {
$subNode = $node->get($file);
$this->singleFileDownloaded($share, $subNode);
}
}
/**
* create activity if a single file was downloaded from a link share
*
* @param Share\IShare $share
* @throws NotFoundException when trying to download a folder of a "hide download" share
*/
protected function singleFileDownloaded(Share\IShare $share, \OCP\Files\Node $node) {
if ($share->getHideDownload() && $node instanceof Folder) {
throw new NotFoundException('Downloading a folder');
}
$fileId = $node->getId();
$userFolder = $this->rootFolder->getUserFolder($share->getSharedBy());
$userNodeList = $userFolder->getById($fileId);
$userNode = $userNodeList[0];
$ownerFolder = $this->rootFolder->getUserFolder($share->getShareOwner());
$userPath = $userFolder->getRelativePath($userNode->getPath());
$ownerPath = $ownerFolder->getRelativePath($node->getPath());
$remoteAddress = $this->request->getRemoteAddress();
$dateTime = new \DateTime();
$dateTime = $dateTime->format('Y-m-d H');
$remoteAddressHash = md5($dateTime . '-' . $remoteAddress);
$parameters = [$userPath];
if ($share->getShareType() === IShare::TYPE_EMAIL) {
if ($node instanceof \OCP\Files\File) {
$subject = Downloads::SUBJECT_SHARED_FILE_BY_EMAIL_DOWNLOADED;
} else {
$subject = Downloads::SUBJECT_SHARED_FOLDER_BY_EMAIL_DOWNLOADED;
}
$parameters[] = $share->getSharedWith();
} else {
if ($node instanceof \OCP\Files\File) {
$subject = Downloads::SUBJECT_PUBLIC_SHARED_FILE_DOWNLOADED;
$parameters[] = $remoteAddressHash;
} else {
$subject = Downloads::SUBJECT_PUBLIC_SHARED_FOLDER_DOWNLOADED;
$parameters[] = $remoteAddressHash;
}
}
$this->publishActivity($subject, $parameters, $share->getSharedBy(), $fileId, $userPath);
if ($share->getShareOwner() !== $share->getSharedBy()) {
$parameters[0] = $ownerPath;
$this->publishActivity($subject, $parameters, $share->getShareOwner(), $fileId, $ownerPath);
}
}
/**
* publish activity
*
* @param string $subject
* @param array $parameters
* @param string $affectedUser
* @param int $fileId
* @param string $filePath
*/
protected function publishActivity($subject,
array $parameters,
$affectedUser,
$fileId,
$filePath) {
$event = $this->activityManager->generateEvent();
$event->setApp('files_sharing')
->setType('public_links')
->setSubject($subject, $parameters)
->setAffectedUser($affectedUser)
->setObject('files', $fileId, $filePath);
$this->activityManager->publish($event);
}
}
@@ -0,0 +1,171 @@
<?php
/**
* @copyright Copyright (c) 2016 Roeland Jago Douma <roeland@famdouma.nl>
*
* @author Morris Jobke <hey@morrisjobke.de>
* @author Roeland Jago Douma <roeland@famdouma.nl>
* @author Kate Döen <kate.doeen@nextcloud.com>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* 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
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Files_Sharing\Controller;
use OCA\Files_External\NotFoundException;
use OCA\Files_Sharing\ResponseDefinitions;
use OCP\AppFramework\ApiController;
use OCP\AppFramework\Http;
use OCP\AppFramework\Http\JSONResponse;
use OCP\Constants;
use OCP\Files\File;
use OCP\Files\Folder;
use OCP\Files\Node;
use OCP\IRequest;
use OCP\Share\Exceptions\ShareNotFound;
use OCP\Share\IManager;
/**
* @psalm-import-type Files_SharingShareInfo from ResponseDefinitions
*/
class ShareInfoController extends ApiController {
/** @var IManager */
private $shareManager;
/**
* ShareInfoController constructor.
*
* @param string $appName
* @param IRequest $request
* @param IManager $shareManager
*/
public function __construct(string $appName,
IRequest $request,
IManager $shareManager) {
parent::__construct($appName, $request);
$this->shareManager = $shareManager;
}
/**
* @PublicPage
* @NoCSRFRequired
* @BruteForceProtection(action=shareinfo)
*
* Get the info about a share
*
* @param string $t Token of the share
* @param string|null $password Password of the share
* @param string|null $dir Subdirectory to get info about
* @param int $depth Maximum depth to get info about
* @return JSONResponse<Http::STATUS_OK, Files_SharingShareInfo, array{}>|JSONResponse<Http::STATUS_FORBIDDEN|Http::STATUS_NOT_FOUND, array<empty>, array{}>
*
* 200: Share info returned
* 403: Getting share info is not allowed
* 404: Share not found
*/
public function info(string $t, ?string $password = null, ?string $dir = null, int $depth = -1): JSONResponse {
try {
$share = $this->shareManager->getShareByToken($t);
} catch (ShareNotFound $e) {
$response = new JSONResponse([], Http::STATUS_NOT_FOUND);
$response->throttle(['token' => $t]);
return $response;
}
if ($share->getPassword() && !$this->shareManager->checkPassword($share, $password)) {
$response = new JSONResponse([], Http::STATUS_FORBIDDEN);
$response->throttle(['token' => $t]);
return $response;
}
if (!($share->getPermissions() & Constants::PERMISSION_READ)) {
$response = new JSONResponse([], Http::STATUS_FORBIDDEN);
$response->throttle(['token' => $t]);
return $response;
}
$permissionMask = $share->getPermissions();
$node = $share->getNode();
if ($dir !== null && $node instanceof Folder) {
try {
$node = $node->get($dir);
} catch (NotFoundException $e) {
}
}
return new JSONResponse($this->parseNode($node, $permissionMask, $depth));
}
/**
* @return Files_SharingShareInfo
*/
private function parseNode(Node $node, int $permissionMask, int $depth): array {
if ($node instanceof File) {
return $this->parseFile($node, $permissionMask);
}
/** @var Folder $node */
return $this->parseFolder($node, $permissionMask, $depth);
}
/**
* @return Files_SharingShareInfo
*/
private function parseFile(File $file, int $permissionMask): array {
return $this->format($file, $permissionMask);
}
/**
* @return Files_SharingShareInfo
*/
private function parseFolder(Folder $folder, int $permissionMask, int $depth): array {
$data = $this->format($folder, $permissionMask);
if ($depth === 0) {
return $data;
}
$data['children'] = [];
$nodes = $folder->getDirectoryListing();
foreach ($nodes as $node) {
$data['children'][] = $this->parseNode($node, $permissionMask, $depth <= -1 ? -1 : $depth - 1);
}
return $data;
}
/**
* @return Files_SharingShareInfo
*/
private function format(Node $node, int $permissionMask): array {
$entry = [];
$entry['id'] = $node->getId();
$entry['parentId'] = $node->getParent()->getId();
$entry['mtime'] = $node->getMTime();
$entry['name'] = $node->getName();
$entry['permissions'] = $node->getPermissions() & $permissionMask;
$entry['mimetype'] = $node->getMimetype();
$entry['size'] = $node->getSize();
$entry['type'] = $node->getType();
$entry['etag'] = $node->getEtag();
return $entry;
}
}
@@ -0,0 +1,452 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2016, ownCloud, Inc.
*
* @author Arthur Schiwon <blizzz@arthur-schiwon.de>
* @author Bjoern Schiessle <bjoern@schiessle.org>
* @author Björn Schießle <bjoern@schiessle.org>
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
* @author Daniel Calviño Sánchez <danxuliu@gmail.com>
* @author Daniel Kesselberg <mail@danielkesselberg.de>
* @author J0WI <J0WI@users.noreply.github.com>
* @author Joas Schilling <coding@schilljs.com>
* @author John Molakvoæ <skjnldsv@protonmail.com>
* @author Julius Härtl <jus@bitgrid.net>
* @author Maxence Lange <maxence@nextcloud.com>
* @author Morris Jobke <hey@morrisjobke.de>
* @author Robin Appelman <robin@icewind.nl>
* @author Roeland Jago Douma <roeland@famdouma.nl>
* @author Kate Döen <kate.doeen@nextcloud.com>
*
* @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\Files_Sharing\Controller;
use Generator;
use OC\Collaboration\Collaborators\SearchResult;
use OCA\Files_Sharing\ResponseDefinitions;
use OCP\AppFramework\Http;
use OCP\AppFramework\Http\DataResponse;
use OCP\AppFramework\OCS\OCSBadRequestException;
use OCP\AppFramework\OCSController;
use OCP\Collaboration\Collaborators\ISearch;
use OCP\Collaboration\Collaborators\ISearchResult;
use OCP\Collaboration\Collaborators\SearchResultType;
use OCP\Constants;
use OCP\IConfig;
use OCP\IRequest;
use OCP\IURLGenerator;
use OCP\Share\IManager;
use OCP\Share\IShare;
use function array_slice;
use function array_values;
use function usort;
/**
* @psalm-import-type Files_SharingShareesSearchResult from ResponseDefinitions
* @psalm-import-type Files_SharingShareesRecommendedResult from ResponseDefinitions
*/
class ShareesAPIController extends OCSController {
/** @var string */
protected $userId;
/** @var IConfig */
protected $config;
/** @var IURLGenerator */
protected $urlGenerator;
/** @var IManager */
protected $shareManager;
/** @var int */
protected $offset = 0;
/** @var int */
protected $limit = 10;
/** @var Files_SharingShareesSearchResult */
protected $result = [
'exact' => [
'users' => [],
'groups' => [],
'remotes' => [],
'remote_groups' => [],
'emails' => [],
'circles' => [],
'rooms' => [],
],
'users' => [],
'groups' => [],
'remotes' => [],
'remote_groups' => [],
'emails' => [],
'lookup' => [],
'circles' => [],
'rooms' => [],
'lookupEnabled' => false,
];
protected $reachedEndFor = [];
/** @var ISearch */
private $collaboratorSearch;
/**
* @param string $UserId
* @param string $appName
* @param IRequest $request
* @param IConfig $config
* @param IURLGenerator $urlGenerator
* @param IManager $shareManager
* @param ISearch $collaboratorSearch
*/
public function __construct(
$UserId,
string $appName,
IRequest $request,
IConfig $config,
IURLGenerator $urlGenerator,
IManager $shareManager,
ISearch $collaboratorSearch
) {
parent::__construct($appName, $request);
$this->userId = $UserId;
$this->config = $config;
$this->urlGenerator = $urlGenerator;
$this->shareManager = $shareManager;
$this->collaboratorSearch = $collaboratorSearch;
}
/**
* @NoAdminRequired
*
* Search for sharees
*
* @param string $search Text to search for
* @param string|null $itemType Limit to specific item types
* @param int $page Page offset for searching
* @param int $perPage Limit amount of search results per page
* @param int|int[]|null $shareType Limit to specific share types
* @param bool $lookup If a global lookup should be performed too
* @return DataResponse<Http::STATUS_OK, Files_SharingShareesSearchResult, array{Link?: string}>
* @throws OCSBadRequestException Invalid search parameters
*
* 200: Sharees search result returned
*/
public function search(string $search = '', string $itemType = null, int $page = 1, int $perPage = 200, $shareType = null, bool $lookup = false): DataResponse {
// only search for string larger than a given threshold
$threshold = $this->config->getSystemValueInt('sharing.minSearchStringLength', 0);
if (strlen($search) < $threshold) {
return new DataResponse($this->result);
}
// never return more than the max. number of results configured in the config.php
$maxResults = $this->config->getSystemValueInt('sharing.maxAutocompleteResults', Constants::SHARING_MAX_AUTOCOMPLETE_RESULTS_DEFAULT);
if ($maxResults > 0) {
$perPage = min($perPage, $maxResults);
}
if ($perPage <= 0) {
throw new OCSBadRequestException('Invalid perPage argument');
}
if ($page <= 0) {
throw new OCSBadRequestException('Invalid page');
}
$shareTypes = [
IShare::TYPE_USER,
];
if ($itemType === null) {
throw new OCSBadRequestException('Missing itemType');
} elseif ($itemType === 'file' || $itemType === 'folder') {
if ($this->shareManager->allowGroupSharing()) {
$shareTypes[] = IShare::TYPE_GROUP;
}
if ($this->isRemoteSharingAllowed($itemType)) {
$shareTypes[] = IShare::TYPE_REMOTE;
}
if ($this->isRemoteGroupSharingAllowed($itemType)) {
$shareTypes[] = IShare::TYPE_REMOTE_GROUP;
}
if ($this->shareManager->shareProviderExists(IShare::TYPE_EMAIL)) {
$shareTypes[] = IShare::TYPE_EMAIL;
}
if ($this->shareManager->shareProviderExists(IShare::TYPE_ROOM)) {
$shareTypes[] = IShare::TYPE_ROOM;
}
if ($this->shareManager->shareProviderExists(IShare::TYPE_SCIENCEMESH)) {
$shareTypes[] = IShare::TYPE_SCIENCEMESH;
}
} else {
if ($this->shareManager->allowGroupSharing()) {
$shareTypes[] = IShare::TYPE_GROUP;
}
$shareTypes[] = IShare::TYPE_EMAIL;
}
// FIXME: DI
if (\OC::$server->getAppManager()->isEnabledForUser('circles') && class_exists('\OCA\Circles\ShareByCircleProvider')) {
$shareTypes[] = IShare::TYPE_CIRCLE;
}
if ($this->shareManager->shareProviderExists(IShare::TYPE_SCIENCEMESH)) {
$shareTypes[] = IShare::TYPE_SCIENCEMESH;
}
if ($shareType !== null && is_array($shareType)) {
$shareTypes = array_intersect($shareTypes, $shareType);
} elseif (is_numeric($shareType)) {
$shareTypes = array_intersect($shareTypes, [(int) $shareType]);
}
sort($shareTypes);
$this->limit = $perPage;
$this->offset = $perPage * ($page - 1);
// In global scale mode we always search the loogup server
if ($this->config->getSystemValueBool('gs.enabled', false)) {
$lookup = true;
$this->result['lookupEnabled'] = true;
} else {
$this->result['lookupEnabled'] = $this->config->getAppValue('files_sharing', 'lookupServerEnabled', 'yes') === 'yes';
}
[$result, $hasMoreResults] = $this->collaboratorSearch->search($search, $shareTypes, $lookup, $this->limit, $this->offset);
// extra treatment for 'exact' subarray, with a single merge expected keys might be lost
if (isset($result['exact'])) {
$result['exact'] = array_merge($this->result['exact'], $result['exact']);
}
$this->result = array_merge($this->result, $result);
$response = new DataResponse($this->result);
if ($hasMoreResults) {
$response->setHeaders(['Link' => $this->getPaginationLink($page, [
'search' => $search,
'itemType' => $itemType,
'shareType' => $shareTypes,
'perPage' => $perPage,
])]);
}
return $response;
}
/**
* @param string $user
* @param int $shareType
*
* @return Generator<array<string>>
*/
private function getAllShareesByType(string $user, int $shareType): Generator {
$offset = 0;
$pageSize = 50;
while (count($page = $this->shareManager->getSharesBy(
$user,
$shareType,
null,
false,
$pageSize,
$offset
))) {
foreach ($page as $share) {
yield [$share->getSharedWith(), $share->getSharedWithDisplayName() ?? $share->getSharedWith()];
}
$offset += $pageSize;
}
}
private function sortShareesByFrequency(array $sharees): array {
usort($sharees, function (array $s1, array $s2): int {
return $s2['count'] - $s1['count'];
});
return $sharees;
}
private $searchResultTypeMap = [
IShare::TYPE_USER => 'users',
IShare::TYPE_GROUP => 'groups',
IShare::TYPE_REMOTE => 'remotes',
IShare::TYPE_REMOTE_GROUP => 'remote_groups',
IShare::TYPE_EMAIL => 'emails',
];
private function getAllSharees(string $user, array $shareTypes): ISearchResult {
$result = [];
foreach ($shareTypes as $shareType) {
$sharees = $this->getAllShareesByType($user, $shareType);
$shareTypeResults = [];
foreach ($sharees as [$sharee, $displayname]) {
if (!isset($this->searchResultTypeMap[$shareType])) {
continue;
}
if (!isset($shareTypeResults[$sharee])) {
$shareTypeResults[$sharee] = [
'count' => 1,
'label' => $displayname,
'value' => [
'shareType' => $shareType,
'shareWith' => $sharee,
],
];
} else {
$shareTypeResults[$sharee]['count']++;
}
}
$result = array_merge($result, array_values($shareTypeResults));
}
$top5 = array_slice(
$this->sortShareesByFrequency($result),
0,
5
);
$searchResult = new SearchResult();
foreach ($this->searchResultTypeMap as $int => $str) {
$searchResult->addResultSet(new SearchResultType($str), [], []);
foreach ($top5 as $x) {
if ($x['value']['shareType'] === $int) {
$searchResult->addResultSet(new SearchResultType($str), [], [$x]);
}
}
}
return $searchResult;
}
/**
* @NoAdminRequired
*
* Find recommended sharees
*
* @param string $itemType Limit to specific item types
* @param int|int[]|null $shareType Limit to specific share types
* @return DataResponse<Http::STATUS_OK, Files_SharingShareesRecommendedResult, array{}>
*
* 200: Recommended sharees returned
*/
public function findRecommended(string $itemType, $shareType = null): DataResponse {
$shareTypes = [
IShare::TYPE_USER,
];
if ($itemType === 'file' || $itemType === 'folder') {
if ($this->shareManager->allowGroupSharing()) {
$shareTypes[] = IShare::TYPE_GROUP;
}
if ($this->isRemoteSharingAllowed($itemType)) {
$shareTypes[] = IShare::TYPE_REMOTE;
}
if ($this->isRemoteGroupSharingAllowed($itemType)) {
$shareTypes[] = IShare::TYPE_REMOTE_GROUP;
}
if ($this->shareManager->shareProviderExists(IShare::TYPE_EMAIL)) {
$shareTypes[] = IShare::TYPE_EMAIL;
}
if ($this->shareManager->shareProviderExists(IShare::TYPE_ROOM)) {
$shareTypes[] = IShare::TYPE_ROOM;
}
} else {
$shareTypes[] = IShare::TYPE_GROUP;
$shareTypes[] = IShare::TYPE_EMAIL;
}
// FIXME: DI
if (\OC::$server->getAppManager()->isEnabledForUser('circles') && class_exists('\OCA\Circles\ShareByCircleProvider')) {
$shareTypes[] = IShare::TYPE_CIRCLE;
}
if (isset($_GET['shareType']) && is_array($_GET['shareType'])) {
$shareTypes = array_intersect($shareTypes, $_GET['shareType']);
sort($shareTypes);
} elseif (is_numeric($shareType)) {
$shareTypes = array_intersect($shareTypes, [(int) $shareType]);
sort($shareTypes);
}
return new DataResponse(
$this->getAllSharees($this->userId, $shareTypes)->asArray()
);
}
/**
* Method to get out the static call for better testing
*
* @param string $itemType
* @return bool
*/
protected function isRemoteSharingAllowed(string $itemType): bool {
try {
// FIXME: static foo makes unit testing unnecessarily difficult
$backend = \OC\Share\Share::getBackend($itemType);
return $backend->isShareTypeAllowed(IShare::TYPE_REMOTE);
} catch (\Exception $e) {
return false;
}
}
protected function isRemoteGroupSharingAllowed(string $itemType): bool {
try {
// FIXME: static foo makes unit testing unnecessarily difficult
$backend = \OC\Share\Share::getBackend($itemType);
return $backend->isShareTypeAllowed(IShare::TYPE_REMOTE_GROUP);
} catch (\Exception $e) {
return false;
}
}
/**
* Generates a bunch of pagination links for the current page
*
* @param int $page Current page
* @param array $params Parameters for the URL
* @return string
*/
protected function getPaginationLink(int $page, array $params): string {
if ($this->isV2()) {
$url = $this->urlGenerator->getAbsoluteURL('/ocs/v2.php/apps/files_sharing/api/v1/sharees') . '?';
} else {
$url = $this->urlGenerator->getAbsoluteURL('/ocs/v1.php/apps/files_sharing/api/v1/sharees') . '?';
}
$params['page'] = $page + 1;
return '<' . $url . http_build_query($params) . '>; rel="next"';
}
/**
* @return bool
*/
protected function isV2(): bool {
return $this->request->getScriptName() === '/ocs/v2.php';
}
}