Skip to content

Commit

Permalink
feat(files_sharing): allow not deleting expiring shares
Browse files Browse the repository at this point in the history
Signed-off-by: John Molakvoæ (skjnldsv) <[email protected]>
  • Loading branch information
skjnldsv committed Sep 19, 2024
1 parent 34445ea commit 276151e
Show file tree
Hide file tree
Showing 12 changed files with 376 additions and 25 deletions.
8 changes: 8 additions & 0 deletions apps/files_sharing/appinfo/routes.php
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,14 @@
'url' => '/api/v1/deletedshares/{id}',
'verb' => 'POST',
],
/*
* Expired Shares
*/
[
'name' => 'ExpiredShareAPI#index',
'url' => '/api/v1/expiredshares',
'verb' => 'GET',
],
/*
* OCS Sharee API
*/
Expand Down
1 change: 1 addition & 0 deletions apps/files_sharing/composer/composer/autoload_classmap.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
'OCA\\Files_Sharing\\Command\\ExiprationNotification' => $baseDir . '/../lib/Command/ExiprationNotification.php',
'OCA\\Files_Sharing\\Controller\\AcceptController' => $baseDir . '/../lib/Controller/AcceptController.php',
'OCA\\Files_Sharing\\Controller\\DeletedShareAPIController' => $baseDir . '/../lib/Controller/DeletedShareAPIController.php',
'OCA\\Files_Sharing\\Controller\\ExpiredShareAPIController' => $baseDir . '/../lib/Controller/ExpiredShareAPIController.php',
'OCA\\Files_Sharing\\Controller\\ExternalSharesController' => $baseDir . '/../lib/Controller/ExternalSharesController.php',
'OCA\\Files_Sharing\\Controller\\PublicPreviewController' => $baseDir . '/../lib/Controller/PublicPreviewController.php',
'OCA\\Files_Sharing\\Controller\\RemoteController' => $baseDir . '/../lib/Controller/RemoteController.php',
Expand Down
1 change: 1 addition & 0 deletions apps/files_sharing/composer/composer/autoload_static.php
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ class ComposerStaticInitFiles_Sharing
'OCA\\Files_Sharing\\Command\\ExiprationNotification' => __DIR__ . '/..' . '/../lib/Command/ExiprationNotification.php',
'OCA\\Files_Sharing\\Controller\\AcceptController' => __DIR__ . '/..' . '/../lib/Controller/AcceptController.php',
'OCA\\Files_Sharing\\Controller\\DeletedShareAPIController' => __DIR__ . '/..' . '/../lib/Controller/DeletedShareAPIController.php',
'OCA\\Files_Sharing\\Controller\\ExpiredShareAPIController' => __DIR__ . '/..' . '/../lib/Controller/ExpiredShareAPIController.php',
'OCA\\Files_Sharing\\Controller\\ExternalSharesController' => __DIR__ . '/..' . '/../lib/Controller/ExternalSharesController.php',
'OCA\\Files_Sharing\\Controller\\PublicPreviewController' => __DIR__ . '/..' . '/../lib/Controller/PublicPreviewController.php',
'OCA\\Files_Sharing\\Controller\\RemoteController' => __DIR__ . '/..' . '/../lib/Controller/RemoteController.php',
Expand Down
235 changes: 235 additions & 0 deletions apps/files_sharing/lib/Controller/ExpiredShareAPIController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,235 @@
<?php

declare(strict_types=1);

/**
* SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCA\Files_Sharing\Controller;

use OCA\Files_Sharing\ResponseDefinitions;
use OCP\App\IAppManager;
use OCP\AppFramework\Http;
use OCP\AppFramework\Http\Attribute\NoAdminRequired;
use OCP\AppFramework\Http\DataResponse;
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\IManager as ShareManager;
use OCP\Share\IShare;

/**
* @psalm-import-type Files_SharingDeletedShare from ResponseDefinitions
*/
class ExpiredShareAPIController 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 */

Check notice

Code scanning / Psalm

DeprecatedInterface Note

Interface OCP\IServerContainer is marked as deprecated
private $serverContainer;

public function __construct(string $appName,
IRequest $request,
ShareManager $shareManager,
string $UserId,
IUserManager $userManager,
IGroupManager $groupManager,
IRootFolder $rootFolder,
IAppManager $appManager,
IServerContainer $serverContainer) {

Check notice

Code scanning / Psalm

DeprecatedInterface Note

Interface OCP\IServerContainer is marked as deprecated
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(),

Check notice

Code scanning / Psalm

PossiblyNullReference Note

Cannot call method getDisplayName on possibly null value
'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(),

Check notice

Code scanning / Psalm

PossiblyNullReference Note

Cannot call method getDisplayName on possibly null value
'path' => $share->getTarget(),
];
$userFolder = $this->rootFolder->getUserFolder($share->getSharedBy());
$node = $userFolder->getFirstNodeById($share->getNodeId());
if (!$node) {
// fallback to guessing the path
$node = $userFolder->get($share->getTarget());
if ($node === null || $share->getTarget() === '') {

Check notice

Code scanning / Psalm

DocblockTypeContradiction Note

OCP\Files\Node does not contain null
throw new NotFoundException();
}
}

$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;
}

/**
* Get a list of all expired shares
*
* @return DataResponse<Http::STATUS_OK, Files_SharingDeletedShare[], array{}>
*
* 200: Deleted shares returned
*/
#[NoAdminRequired]
public function index(): DataResponse {
$groupShares = $this->shareManager->getExpiredShares($this->userId, IShare::TYPE_GROUP, null, -1, 0);
$roomShares = $this->shareManager->getExpiredShares($this->userId, IShare::TYPE_ROOM, null, -1, 0);
$deckShares = $this->shareManager->getExpiredShares($this->userId, IShare::TYPE_DECK, null, -1, 0);
$sciencemeshShares = $this->shareManager->getExpiredShares($this->userId, IShare::TYPE_SCIENCEMESH, null, -1, 0);
$linkShares = $this->shareManager->getExpiredShares($this->userId, IShare::TYPE_LINK, null, -1, 0);
$userShares = $this->shareManager->getExpiredShares($this->userId, IShare::TYPE_USER, null, -1, 0);
$emailsShares = $this->shareManager->getExpiredShares($this->userId, IShare::TYPE_EMAIL, null, -1, 0);
$circlesShares = $this->shareManager->getExpiredShares($this->userId, IShare::TYPE_CIRCLE, null, -1, 0);
$remoteShares = $this->shareManager->getExpiredShares($this->userId, IShare::TYPE_REMOTE, null, -1, 0);

$shares = array_merge($groupShares, $roomShares, $deckShares, $sciencemeshShares, $linkShares, $userShares, $emailsShares, $circlesShares, $remoteShares);

$shares = array_map(function (IShare $share) {
return $this->formatShare($share);
}, $shares);

return new DataResponse($shares);
}

/**
* 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();

Check notice

Code scanning / Psalm

DeprecatedClass Note

OCP\AppFramework\QueryException is marked deprecated
}

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();

Check notice

Code scanning / Psalm

DeprecatedClass Note

OCP\AppFramework\QueryException is marked deprecated
}

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();

Check notice

Code scanning / Psalm

DeprecatedClass Note

OCP\AppFramework\QueryException is marked deprecated
}

return $this->serverContainer->get('\OCA\ScienceMesh\Sharing\ShareAPIHelper');
}
}
28 changes: 15 additions & 13 deletions apps/files_sharing/lib/ExpireSharesJob.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,25 +9,24 @@
use OCP\AppFramework\Utility\ITimeFactory;
use OCP\BackgroundJob\IJob;
use OCP\BackgroundJob\TimedJob;
use OCP\IAppConfig;
use OCP\IDBConnection;
use OCP\Share\Exceptions\ShareNotFound;
use OCP\Share\IManager;
use OCP\Share\IShare;
use Psr\Log\LoggerInterface;

/**
* Delete all shares that are expired
*/
class ExpireSharesJob extends TimedJob {

/** @var IManager */
private $shareManager;

/** @var IDBConnection */
private $db;

public function __construct(ITimeFactory $time, IManager $shareManager, IDBConnection $db) {
$this->shareManager = $shareManager;
$this->db = $db;
public function __construct(
ITimeFactory $time,
private IManager $shareManager,
private IDBConnection $db,
private IAppConfig $config,
private LoggerInterface $logger) {

parent::__construct($time);

Expand All @@ -43,13 +42,16 @@ public function __construct(ITimeFactory $time, IManager $shareManager, IDBConne
* @param array $argument unused argument
*/
public function run($argument) {
//Current time
if ($this->config->getValueString('core', 'shareapi_delete_on_expire', 'yes') !== 'yes') {
$this->logger->info('Share deletion on expiration is disabled');
return;
}

// Current time
$now = new \DateTime();
$now = $now->format('Y-m-d H:i:s');

/*
* Expire file link shares only (for now)
*/
// Expire file link shares only (for now)
$qb = $this->db->getQueryBuilder();
$qb->select('id', 'share_type')
->from('share')
Expand Down
7 changes: 6 additions & 1 deletion apps/files_sharing/src/files_actions/sharingStatusAction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,22 @@ import { action as sidebarAction } from '../../../files/src/actions/sidebarActio
import { generateAvatarSvg } from '../utils/AccountIcon'

import './sharingStatusAction.scss'
import { expiredSharesViewId } from '../files_views/shares'

const isExternal = (node: Node) => {
return node.attributes.remote_id !== undefined
}

export const action = new FileAction({
id: 'sharing-status',
displayName(nodes: Node[]) {
displayName(nodes: Node[], view: View) {
const node = nodes[0]
const shareTypes = Object.values(node?.attributes?.['share-types'] || {}).flat() as number[]

if (view.id === expiredSharesViewId) {
return t('files_sharing', 'Expired')
}

if (shareTypes.length > 0
|| (node.owner !== getCurrentUser()?.uid || isExternal(node))) {
return t('files_sharing', 'Shared')
Expand Down
Loading

0 comments on commit 276151e

Please sign in to comment.