mirror of
https://github.com/nextcloud/server.git
synced 2026-06-11 09:42:09 -04:00
feat(files_sharing): allow not deleting expiring shares
Signed-off-by: John Molakvoæ (skjnldsv) <skjnldsv@protonmail.com>
This commit is contained in:
parent
cd3dc1719b
commit
06069e56ee
14 changed files with 394 additions and 33 deletions
|
|
@ -139,6 +139,14 @@ return [
|
|||
'url' => '/api/v1/deletedshares/{id}',
|
||||
'verb' => 'POST',
|
||||
],
|
||||
/*
|
||||
* Expired Shares
|
||||
*/
|
||||
[
|
||||
'name' => 'ExpiredShareAPI#index',
|
||||
'url' => '/api/v1/expiredshares',
|
||||
'verb' => 'GET',
|
||||
],
|
||||
/*
|
||||
* OCS Sharee API
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ return array(
|
|||
'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',
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
|
|
|
|||
235
apps/files_sharing/lib/Controller/ExpiredShareAPIController.php
Normal file
235
apps/files_sharing/lib/Controller/ExpiredShareAPIController.php
Normal file
|
|
@ -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 */
|
||||
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());
|
||||
$node = $userFolder->getFirstNodeById($share->getNodeId());
|
||||
if (!$node) {
|
||||
// fallback to guessing the path
|
||||
$node = $userFolder->get($share->getTarget());
|
||||
if ($node === null || $share->getTarget() === '') {
|
||||
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();
|
||||
}
|
||||
|
||||
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');
|
||||
}
|
||||
}
|
||||
|
|
@ -8,25 +8,24 @@ namespace OCA\Files_Sharing;
|
|||
|
||||
use OCP\AppFramework\Utility\ITimeFactory;
|
||||
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);
|
||||
|
||||
|
|
@ -42,13 +41,16 @@ class ExpireSharesJob extends TimedJob {
|
|||
* @param array $argument unused argument
|
||||
*/
|
||||
public function run($argument) {
|
||||
//Current time
|
||||
if ($this->config->getValueString('core', 'shareapi_delete_on_expire', 'yes') === 'no') {
|
||||
$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')
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ import LinkSvg from '@mdi/svg/svg/link.svg?raw'
|
|||
import CircleSvg from '../../../../core/img/apps/circles.svg?raw'
|
||||
|
||||
import { action as sidebarAction } from '../../../files/src/actions/sidebarAction'
|
||||
import { expiredSharesViewId } from '../files_views/shares'
|
||||
import { generateAvatarSvg } from '../utils/AccountIcon'
|
||||
|
||||
import './sharingStatusAction.scss'
|
||||
|
|
@ -24,10 +25,14 @@ const isExternal = (node: Node) => {
|
|||
|
||||
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')
|
||||
|
|
|
|||
|
|
@ -3,8 +3,11 @@
|
|||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
import { translate as t } from '@nextcloud/l10n'
|
||||
import { View, getNavigation } from '@nextcloud/files'
|
||||
import { Column, View, getNavigation } from '@nextcloud/files'
|
||||
import { ShareType } from '@nextcloud/sharing'
|
||||
import moment from '@nextcloud/moment'
|
||||
|
||||
import AccountArrowLeftSvg from '@mdi/svg/svg/account-arrow-left.svg?raw'
|
||||
import AccountClockSvg from '@mdi/svg/svg/account-clock.svg?raw'
|
||||
import AccountGroupSvg from '@mdi/svg/svg/account-group.svg?raw'
|
||||
import AccountPlusSvg from '@mdi/svg/svg/account-plus.svg?raw'
|
||||
|
|
@ -22,6 +25,7 @@ export const sharingByLinksViewId = 'sharinglinks'
|
|||
export const deletedSharesViewId = 'deletedshares'
|
||||
export const pendingSharesViewId = 'pendingshares'
|
||||
export const fileRequestViewId = 'filerequest'
|
||||
export const expiredSharesViewId = 'expiredshares'
|
||||
|
||||
export default () => {
|
||||
const Navigation = getNavigation()
|
||||
|
|
@ -89,7 +93,7 @@ export default () => {
|
|||
|
||||
columns: [],
|
||||
|
||||
getContents: () => getContents(false, true, false, false, [ShareType.Link]),
|
||||
getContents: () => getContents(false, true, false, false, false, [ShareType.Link]),
|
||||
}))
|
||||
|
||||
Navigation.register(new View({
|
||||
|
|
@ -106,7 +110,7 @@ export default () => {
|
|||
|
||||
columns: [],
|
||||
|
||||
getContents: () => getContents(false, true, false, false, [ShareType.Link, ShareType.Email])
|
||||
getContents: () => getContents(false, true, false, false, false, [ShareType.Link, ShareType.Email])
|
||||
.then(({ folder, contents }) => {
|
||||
return {
|
||||
folder,
|
||||
|
|
@ -132,6 +136,46 @@ export default () => {
|
|||
getContents: () => getContents(false, false, false, true),
|
||||
}))
|
||||
|
||||
Navigation.register(new View({
|
||||
id: expiredSharesViewId,
|
||||
name: t('files_sharing', 'Expired shares'),
|
||||
caption: t('files_sharing', 'List of shares that expired.'),
|
||||
|
||||
emptyTitle: t('files_sharing', 'No expired shares'),
|
||||
emptyCaption: t('files_sharing', 'Shares that have expired will show up here'),
|
||||
|
||||
icon: AccountClockSvg,
|
||||
order: 6,
|
||||
parent: sharesViewId,
|
||||
|
||||
columns: [
|
||||
new Column({
|
||||
id: 'expired',
|
||||
title: t('files_sharing', 'Expired'),
|
||||
render(node) {
|
||||
const expirationTime = node.attributes?.expiration
|
||||
const span = document.createElement('span')
|
||||
if (expirationTime) {
|
||||
span.title = moment.unix(expirationTime).format('LLL')
|
||||
span.textContent = moment.unix(expirationTime).fromNow()
|
||||
return span
|
||||
}
|
||||
|
||||
// Unknown expiration time
|
||||
span.textContent = t('files_sharing', 'A long time ago')
|
||||
return span
|
||||
},
|
||||
sort(nodeA, nodeB) {
|
||||
const expirationTimeA = nodeA.attributes?.expiration || 0
|
||||
const expirationTimeB = nodeB.attributes?.expiration || 0
|
||||
return expirationTimeB - expirationTimeA
|
||||
},
|
||||
}),
|
||||
],
|
||||
|
||||
getContents: () => getContents(false, false, false, false, true),
|
||||
}))
|
||||
|
||||
Navigation.register(new View({
|
||||
id: pendingSharesViewId,
|
||||
name: t('files_sharing', 'Pending shares'),
|
||||
|
|
@ -140,8 +184,8 @@ export default () => {
|
|||
emptyTitle: t('files_sharing', 'No pending shares'),
|
||||
emptyCaption: t('files_sharing', 'Shares you have received but not approved will show up here'),
|
||||
|
||||
icon: AccountClockSvg,
|
||||
order: 6,
|
||||
icon: AccountArrowLeftSvg,
|
||||
order: 7,
|
||||
parent: sharesViewId,
|
||||
|
||||
columns: [],
|
||||
|
|
|
|||
|
|
@ -59,11 +59,16 @@ const ocsEntryToNode = async function(ocsEntry: any): Promise<Folder | File | nu
|
|||
const source = `${davRemoteURL}${davRootPath}/${path.replace(/^\/+/, '')}`
|
||||
|
||||
let mtime = ocsEntry.item_mtime ? new Date((ocsEntry.item_mtime) * 1000) : undefined
|
||||
|
||||
// Prefer share time if more recent than item mtime
|
||||
if (ocsEntry?.stime > (ocsEntry?.item_mtime || 0)) {
|
||||
mtime = new Date((ocsEntry.stime) * 1000)
|
||||
}
|
||||
|
||||
const expiration = ocsEntry?.expiration
|
||||
? new Date(ocsEntry?.expiration)?.getTime() / 1000
|
||||
: undefined
|
||||
|
||||
return new Node({
|
||||
id: fileid,
|
||||
source,
|
||||
|
|
@ -75,6 +80,7 @@ const ocsEntryToNode = async function(ocsEntry: any): Promise<Folder | File | nu
|
|||
root: davRootPath,
|
||||
attributes: {
|
||||
...ocsEntry,
|
||||
expiration,
|
||||
'has-preview': hasPreview,
|
||||
// Also check the sharingStatusAction.ts code
|
||||
'owner-id': ocsEntry?.uid_owner,
|
||||
|
|
@ -149,6 +155,16 @@ const getDeletedShares = function(): AxiosPromise<OCSResponse<any>> {
|
|||
})
|
||||
}
|
||||
|
||||
const getExpiredShares = function(): AxiosPromise<OCSResponse<any>> {
|
||||
const url = generateOcsUrl('apps/files_sharing/api/v1/expiredshares')
|
||||
return axios.get(url, {
|
||||
headers,
|
||||
params: {
|
||||
include_tags: true,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a file request is enabled
|
||||
* @param attributes the share attributes json-encoded array
|
||||
|
|
@ -180,7 +196,7 @@ const groupBy = function(nodes: (Folder | File)[], key: string) {
|
|||
}, {})) as (Folder | File)[][]
|
||||
}
|
||||
|
||||
export const getContents = async (sharedWithYou = true, sharedWithOthers = true, pendingShares = false, deletedshares = false, filterTypes: number[] = []): Promise<ContentsWithRoot> => {
|
||||
export const getContents = async (sharedWithYou = true, sharedWithOthers = true, pendingShares = false, deletedshares = false, expiredShares = false, filterTypes: number[] = []): Promise<ContentsWithRoot> => {
|
||||
const promises = [] as AxiosPromise<OCSResponse<any>>[]
|
||||
|
||||
if (sharedWithYou) {
|
||||
|
|
@ -195,6 +211,9 @@ export const getContents = async (sharedWithYou = true, sharedWithOthers = true,
|
|||
if (deletedshares) {
|
||||
promises.push(getDeletedShares())
|
||||
}
|
||||
if (expiredShares) {
|
||||
promises.push(getExpiredShares())
|
||||
}
|
||||
|
||||
const responses = await Promise.all(promises)
|
||||
const data = responses.map((response) => response.data.ocs.data).flat()
|
||||
|
|
|
|||
|
|
@ -9,8 +9,10 @@ namespace OCA\Files_Sharing\Tests;
|
|||
use OCA\Files_Sharing\ExpireSharesJob;
|
||||
use OCP\AppFramework\Utility\ITimeFactory;
|
||||
use OCP\Constants;
|
||||
use OCP\IAppConfig;
|
||||
use OCP\Share\IManager;
|
||||
use OCP\Share\IShare;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
/**
|
||||
* Class ExpireSharesJobTest
|
||||
|
|
@ -49,7 +51,14 @@ class ExpireSharesJobTest extends \Test\TestCase {
|
|||
|
||||
\OC::registerShareHooks(\OC::$server->getSystemConfig());
|
||||
|
||||
$this->job = new ExpireSharesJob(\OC::$server->get(ITimeFactory::class), \OC::$server->get(IManager::class), $this->connection);
|
||||
$this->job = new ExpireSharesJob(
|
||||
\OC::$server->get(ITimeFactory::class),
|
||||
\OC::$server->get(IManager::class),
|
||||
$this->connection,
|
||||
\OC::$server->get(IAppConfig::class),
|
||||
\OC::$server->get(LoggerInterface::class),
|
||||
|
||||
);
|
||||
}
|
||||
|
||||
protected function tearDown(): void {
|
||||
|
|
|
|||
|
|
@ -71,6 +71,7 @@ class Sharing implements IDelegatedSettings {
|
|||
'defaultRemoteExpireDate' => $this->getHumanBooleanConfig('core', 'shareapi_default_remote_expire_date'),
|
||||
'remoteExpireAfterNDays' => $this->config->getAppValue('core', 'shareapi_remote_expire_after_n_days', '7'),
|
||||
'enforceRemoteExpireDate' => $this->getHumanBooleanConfig('core', 'shareapi_enforce_remote_expire_date'),
|
||||
'deleteOnExpire' => $this->getHumanBooleanConfig('core', 'shareapi_delete_on_expire', true),
|
||||
];
|
||||
|
||||
$this->initialState->provideInitialState('sharingAppEnabled', $this->appManager->isEnabledForUser('files_sharing'));
|
||||
|
|
|
|||
|
|
@ -141,6 +141,12 @@
|
|||
:placeholder="t('settings', 'Expire shares after x days')"
|
||||
:value.sync="settings.expireAfterNDays" />
|
||||
</fieldset>
|
||||
|
||||
<NcCheckboxRadioSwitch type="switch"
|
||||
aria-controls="settings-sharing-api-expiration-delete"
|
||||
:checked.sync="settings.deleteOnExpire">
|
||||
{{ t('settings', 'Delete shares on expiration') }}
|
||||
</NcCheckboxRadioSwitch>
|
||||
</div>
|
||||
|
||||
<div v-show="settings.enabled" id="settings-sharing-privary-related" class="sharing__section">
|
||||
|
|
@ -240,6 +246,7 @@ interface IShareSettings {
|
|||
defaultRemoteExpireDate: boolean
|
||||
remoteExpireAfterNDays: string
|
||||
enforceRemoteExpireDate: boolean
|
||||
deleteOnExpire: boolean
|
||||
}
|
||||
|
||||
export default defineComponent({
|
||||
|
|
|
|||
|
|
@ -1143,7 +1143,7 @@ class Manager implements IManager {
|
|||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function getSharesBy($userId, $shareType, $path = null, $reshares = false, $limit = 50, $offset = 0) {
|
||||
public function getSharesBy($userId, $shareType, $path = null, $reshares = false, $limit = 50, $offset = 0, $expired = false) {
|
||||
if ($path !== null &&
|
||||
!($path instanceof \OCP\Files\File) &&
|
||||
!($path instanceof \OCP\Files\Folder)) {
|
||||
|
|
@ -1169,7 +1169,7 @@ class Manager implements IManager {
|
|||
$added = 0;
|
||||
foreach ($shares as $share) {
|
||||
try {
|
||||
$this->checkShare($share);
|
||||
$this->checkShare($share, $expired);
|
||||
} catch (ShareNotFound $e) {
|
||||
// Ignore since this basically means the share is deleted
|
||||
continue;
|
||||
|
|
@ -1216,7 +1216,7 @@ class Manager implements IManager {
|
|||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function getSharedWith($userId, $shareType, $node = null, $limit = 50, $offset = 0) {
|
||||
public function getSharedWith($userId, $shareType, $node = null, $limit = 50, $offset = 0, $expired = false) {
|
||||
try {
|
||||
$provider = $this->factory->getProviderForType($shareType);
|
||||
} catch (ProviderException $e) {
|
||||
|
|
@ -1228,7 +1228,7 @@ class Manager implements IManager {
|
|||
// remove all shares which are already expired
|
||||
foreach ($shares as $key => $share) {
|
||||
try {
|
||||
$this->checkShare($share);
|
||||
$this->checkShare($share, $expired);
|
||||
} catch (ShareNotFound $e) {
|
||||
unset($shares[$key]);
|
||||
}
|
||||
|
|
@ -1259,7 +1259,18 @@ class Manager implements IManager {
|
|||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function getShareById($id, $recipient = null) {
|
||||
public function getExpiredShares($userId, $shareType, ?Node $path = null, $limit = 50, $offset = 0) {
|
||||
$shares = $this->getSharesBy($userId, $shareType, $path, false, $limit, $offset, true);
|
||||
|
||||
return array_filter($shares, function (IShare $share) {
|
||||
return $share->isExpired();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function getShareById($id, $recipient = null, $expired = false) {
|
||||
if ($id === null) {
|
||||
throw new ShareNotFound();
|
||||
}
|
||||
|
|
@ -1274,7 +1285,7 @@ class Manager implements IManager {
|
|||
|
||||
$share = $provider->getShareById($id, $recipient);
|
||||
|
||||
$this->checkShare($share);
|
||||
$this->checkShare($share, $expired);
|
||||
|
||||
return $share;
|
||||
}
|
||||
|
|
@ -1300,7 +1311,7 @@ class Manager implements IManager {
|
|||
*
|
||||
* @throws ShareNotFound
|
||||
*/
|
||||
public function getShareByToken($token) {
|
||||
public function getShareByToken($token, $expired = false) {
|
||||
// tokens cannot be valid local user names
|
||||
if ($this->userManager->userExists($token)) {
|
||||
throw new ShareNotFound();
|
||||
|
|
@ -1358,7 +1369,7 @@ class Manager implements IManager {
|
|||
throw new ShareNotFound($this->l->t('The requested share does not exist anymore'));
|
||||
}
|
||||
|
||||
$this->checkShare($share);
|
||||
$this->checkShare($share, $expired);
|
||||
|
||||
/*
|
||||
* Reduce the permissions for link or email shares if public upload is not enabled
|
||||
|
|
@ -1376,11 +1387,14 @@ class Manager implements IManager {
|
|||
*
|
||||
* @throws ShareNotFound
|
||||
*/
|
||||
protected function checkShare(IShare $share): void {
|
||||
if ($share->isExpired()) {
|
||||
$this->deleteShare($share);
|
||||
throw new ShareNotFound($this->l->t('The requested share does not exist anymore'));
|
||||
protected function checkShare(IShare $share, $allowExpired = false): void {
|
||||
if ($share->isExpired() && !$allowExpired) {
|
||||
if ($this->config->getAppValue('core', 'shareapi_delete_on_expire', 'yes') !== 'no') {
|
||||
$this->deleteShare($share);
|
||||
}
|
||||
throw new ShareNotFound($this->l->t('The requested share has expired'));
|
||||
}
|
||||
|
||||
if ($this->config->getAppValue('files_sharing', 'hide_disabled_user_shares', 'no') === 'yes') {
|
||||
$uids = array_unique([$share->getShareOwner(),$share->getSharedBy()]);
|
||||
foreach ($uids as $uid) {
|
||||
|
|
|
|||
|
|
@ -160,6 +160,20 @@ interface IManager {
|
|||
*/
|
||||
public function getDeletedSharedWith($userId, $shareType, $node = null, $limit = 50, $offset = 0);
|
||||
|
||||
/**
|
||||
* Get expired shares created by $user.
|
||||
* Filter by $node if provided
|
||||
*
|
||||
* @param string $userId
|
||||
* @param int $shareType
|
||||
* @param Node|null $path
|
||||
* @param int $limit The maximum number of shares returned, -1 for all
|
||||
* @param int $offset
|
||||
* @return IShare[]
|
||||
* @since 31.0.0
|
||||
*/
|
||||
public function getExpiredShares($userId, $shareType, ?Node $path = null, $limit = 50, $offset = 0);
|
||||
|
||||
/**
|
||||
* Retrieve a share by the share id.
|
||||
* If the recipient is set make sure to retrieve the file for that user.
|
||||
|
|
|
|||
|
|
@ -2720,6 +2720,7 @@ class ManagerTest extends \Test\TestCase {
|
|||
* deleted (as they are evaluated). but share 8 should still be there.
|
||||
*/
|
||||
public function testGetSharesByExpiredLinkShares(): void {
|
||||
/** @var IManager|\PHPUnit\Framework\MockObject\MockObject */
|
||||
$manager = $this->createManagerMock()
|
||||
->setMethods(['deleteShare'])
|
||||
->getMock();
|
||||
|
|
|
|||
Loading…
Reference in a new issue