chore(files_sharing): refactor ShareAPIController shared methods

Signed-off-by: John Molakvoæ (skjnldsv) <skjnldsv@protonmail.com>
This commit is contained in:
John Molakvoæ (skjnldsv) 2024-09-20 12:04:41 +02:00 committed by skjnldsv
parent 06069e56ee
commit fc3ae5ca4b
26 changed files with 884 additions and 1109 deletions

View file

@ -35,6 +35,7 @@ return array(
'OCA\\Files_Sharing\\Controller\\RemoteController' => $baseDir . '/../lib/Controller/RemoteController.php',
'OCA\\Files_Sharing\\Controller\\SettingsController' => $baseDir . '/../lib/Controller/SettingsController.php',
'OCA\\Files_Sharing\\Controller\\ShareAPIController' => $baseDir . '/../lib/Controller/ShareAPIController.php',
'OCA\\Files_Sharing\\Controller\\ShareApiControllerFactory' => $baseDir . '/../lib/Controller/ShareApiControllerFactory.php',
'OCA\\Files_Sharing\\Controller\\ShareController' => $baseDir . '/../lib/Controller/ShareController.php',
'OCA\\Files_Sharing\\Controller\\ShareInfoController' => $baseDir . '/../lib/Controller/ShareInfoController.php',
'OCA\\Files_Sharing\\Controller\\ShareesAPIController' => $baseDir . '/../lib/Controller/ShareesAPIController.php',

View file

@ -50,6 +50,7 @@ class ComposerStaticInitFiles_Sharing
'OCA\\Files_Sharing\\Controller\\RemoteController' => __DIR__ . '/..' . '/../lib/Controller/RemoteController.php',
'OCA\\Files_Sharing\\Controller\\SettingsController' => __DIR__ . '/..' . '/../lib/Controller/SettingsController.php',
'OCA\\Files_Sharing\\Controller\\ShareAPIController' => __DIR__ . '/..' . '/../lib/Controller/ShareAPIController.php',
'OCA\\Files_Sharing\\Controller\\ShareApiControllerFactory' => __DIR__ . '/..' . '/../lib/Controller/ShareApiControllerFactory.php',
'OCA\\Files_Sharing\\Controller\\ShareController' => __DIR__ . '/..' . '/../lib/Controller/ShareController.php',
'OCA\\Files_Sharing\\Controller\\ShareInfoController' => __DIR__ . '/..' . '/../lib/Controller/ShareInfoController.php',
'OCA\\Files_Sharing\\Controller\\ShareesAPIController' => __DIR__ . '/..' . '/../lib/Controller/ShareesAPIController.php',

View file

@ -15,154 +15,66 @@ use OCP\AppFramework\Http\Attribute\NoAdminRequired;
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\Folder;
use OCP\Files\IRootFolder;
use OCP\Files\NotFoundException;
use OCP\IDateTimeZone;
use OCP\IGroupManager;
use OCP\IL10N;
use OCP\IPreview;
use OCP\IRequest;
use OCP\IServerContainer;
use OCP\IURLGenerator;
use OCP\IUserManager;
use OCP\Share\Exceptions\GenericShareException;
use OCP\Share\Exceptions\ShareNotFound;
use OCP\Share\IManager as ShareManager;
use OCP\Share\IShare;
use OCP\UserStatus\IManager as UserStatusManager;
use Psr\Container\ContainerInterface;
use Psr\Log\LoggerInterface;
/**
* @psalm-import-type Files_SharingDeletedShare from ResponseDefinitions
* @psalm-import-type Files_SharingShare 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,
class DeletedShareAPIController extends ShareApiControllerFactory {
public function __construct(
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 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;
protected ShareManager $shareManager,
protected ?string $userId,
protected IUserManager $userManager,
protected IGroupManager $groupManager,
protected IRootFolder $rootFolder,
protected IAppManager $appManager,
protected ContainerInterface $serverContainer,
protected UserStatusManager $userStatusManager,
protected IPreview $previewManager,
protected IDateTimeZone $dateTimeZone,
protected IURLGenerator $urlGenerator,
protected IL10N $l,
protected LoggerInterface $logger,
) {
parent::__construct(
$request,
$shareManager,
$userId,
$userManager,
$groupManager,
$rootFolder,
$appManager,
$serverContainer,
$userStatusManager,
$previewManager,
$dateTimeZone,
$urlGenerator,
$l,
$logger,
);
$this->isDeletedShareController = true;
}
/**
* Get a list of all deleted shares
*
* @return DataResponse<Http::STATUS_OK, Files_SharingDeletedShare[], array{}>
* @return DataResponse<Http::STATUS_OK, Files_SharingShare[], array{}>
*
* 200: Deleted shares returned
*/
@ -212,55 +124,4 @@ class DeletedShareAPIController extends OCSController {
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');
}
}

View file

@ -13,151 +13,63 @@ 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\IDateTimeZone;
use OCP\IGroupManager;
use OCP\IL10N;
use OCP\IPreview;
use OCP\IRequest;
use OCP\IServerContainer;
use OCP\IURLGenerator;
use OCP\IUserManager;
use OCP\Share\IManager as ShareManager;
use OCP\Share\IShare;
use OCP\UserStatus\IManager as UserStatusManager;
use Psr\Container\ContainerInterface;
use Psr\Log\LoggerInterface;
/**
* @psalm-import-type Files_SharingDeletedShare from ResponseDefinitions
* @psalm-import-type Files_SharingShare from ResponseDefinitions
*/
class ExpiredShareAPIController extends OCSController {
class ExpiredShareAPIController extends ShareApiControllerFactory {
/** @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,
public function __construct(
IRequest $request,
ShareManager $shareManager,
string $UserId,
?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;
ContainerInterface $serverContainer,
UserStatusManager $userStatusManager,
IPreview $previewManager,
IDateTimeZone $dateTimeZone,
IURLGenerator $urlGenerator,
IL10N $l,
LoggerInterface $logger,
) {
parent::__construct(
$request,
$shareManager,
$userId,
$userManager,
$groupManager,
$rootFolder,
$appManager,
$serverContainer,
$userStatusManager,
$previewManager,
$dateTimeZone,
$urlGenerator,
$l,
$logger,
);
}
/**
* Get a list of all expired shares
*
* @return DataResponse<Http::STATUS_OK, Files_SharingDeletedShare[], array{}>
* @return DataResponse<Http::STATUS_OK, Files_SharingShare[], array{}>
*
* 200: Deleted shares returned
*/
@ -181,55 +93,4 @@ class ExpiredShareAPIController extends OCSController {
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');
}
}

View file

@ -18,7 +18,6 @@ use OCA\Files_Sharing\Exceptions\SharingRightsException;
use OCA\Files_Sharing\External\Storage;
use OCA\Files_Sharing\ResponseDefinitions;
use OCA\Files_Sharing\SharedStorage;
use OCA\GlobalSiteSelector\Service\SlaveService;
use OCP\App\IAppManager;
use OCP\AppFramework\Http;
use OCP\AppFramework\Http\Attribute\NoAdminRequired;
@ -28,7 +27,6 @@ use OCP\AppFramework\OCS\OCSBadRequestException;
use OCP\AppFramework\OCS\OCSException;
use OCP\AppFramework\OCS\OCSForbiddenException;
use OCP\AppFramework\OCS\OCSNotFoundException;
use OCP\AppFramework\OCSController;
use OCP\AppFramework\QueryException;
use OCP\Constants;
use OCP\Files\File;
@ -48,7 +46,6 @@ use OCP\IUserManager;
use OCP\Lock\ILockingProvider;
use OCP\Lock\LockedException;
use OCP\Mail\IMailer;
use OCP\Server;
use OCP\Share\Exceptions\GenericShareException;
use OCP\Share\Exceptions\ShareNotFound;
use OCP\Share\IManager;
@ -56,7 +53,6 @@ use OCP\Share\IProviderFactory;
use OCP\Share\IShare;
use OCP\Share\IShareProviderWithNotification;
use OCP\UserStatus\IManager as IUserStatusManager;
use Psr\Container\ContainerExceptionInterface;
use Psr\Container\ContainerInterface;
use Psr\Log\LoggerInterface;
@ -65,388 +61,47 @@ use Psr\Log\LoggerInterface;
*
* @psalm-import-type Files_SharingShare from ResponseDefinitions
*/
class ShareAPIController extends OCSController {
class ShareAPIController extends ShareApiControllerFactory {
private ?Node $lockedNode = null;
private string $currentUser;
/**
* Share20OCS constructor.
*/
public function __construct(
string $appName,
IRequest $request,
private IManager $shareManager,
private IGroupManager $groupManager,
private IUserManager $userManager,
private IRootFolder $rootFolder,
private IURLGenerator $urlGenerator,
private IL10N $l,
private IConfig $config,
private IAppManager $appManager,
private ContainerInterface $serverContainer,
private IUserStatusManager $userStatusManager,
private IPreview $previewManager,
private IDateTimeZone $dateTimeZone,
private LoggerInterface $logger,
private IProviderFactory $factory,
private IMailer $mailer,
?string $userId = null,
protected IManager $shareManager,
protected IGroupManager $groupManager,
protected IUserManager $userManager,
protected IRootFolder $rootFolder,
protected IURLGenerator $urlGenerator,
protected IL10N $l,
protected IConfig $config,
protected IAppManager $appManager,
protected ContainerInterface $serverContainer,
protected IUserStatusManager $userStatusManager,
protected IPreview $previewManager,
protected IDateTimeZone $dateTimeZone,
protected LoggerInterface $logger,
protected IProviderFactory $factory,
protected IMailer $mailer,
string $userId,
) {
parent::__construct($appName, $request);
$this->currentUser = $userId;
parent::__construct(
$request,
$shareManager,
$userId,
$userManager,
$groupManager,
$rootFolder,
$appManager,
$serverContainer,
$userStatusManager,
$previewManager,
$dateTimeZone,
$urlGenerator,
$l,
$logger,
);
}
/**
* Convert an IShare to an array for OCS output
*
* @param \OCP\Share\IShare $share
* @param Node|null $recipientNode
* @return Files_SharingShare
* @throws NotFoundException In case the node can't be resolved.
*
* @suppress PhanUndeclaredClassMethod
*/
protected function formatShare(IShare $share, ?Node $recipientNode = null): array {
$sharedBy = $this->userManager->get($share->getSharedBy());
$shareOwner = $this->userManager->get($share->getShareOwner());
$isOwnShare = false;
if ($shareOwner !== null) {
$isOwnShare = $shareOwner->getUID() === $this->currentUser;
}
$result = [
'id' => $share->getId(),
'share_type' => $share->getShareType(),
'uid_owner' => $share->getSharedBy(),
'displayname_owner' => $sharedBy !== null ? $sharedBy->getDisplayName() : $share->getSharedBy(),
// recipient permissions
'permissions' => $share->getPermissions(),
// current user permissions on this share
'can_edit' => $this->canEditShare($share),
'can_delete' => $this->canDeleteShare($share),
'stime' => $share->getShareTime()->getTimestamp(),
'parent' => null,
'expiration' => null,
'token' => null,
'uid_file_owner' => $share->getShareOwner(),
'note' => $share->getNote(),
'label' => $share->getLabel(),
'displayname_file_owner' => $shareOwner !== null ? $shareOwner->getDisplayName() : $share->getShareOwner(),
];
$userFolder = $this->rootFolder->getUserFolder($this->currentUser);
if ($recipientNode) {
$node = $recipientNode;
} else {
$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 Folder) {
$result['item_type'] = 'folder';
} else {
$result['item_type'] = 'file';
}
// Get the original node permission if the share owner is the current user
if ($isOwnShare) {
$result['item_permissions'] = $node->getPermissions();
}
// If we're on the recipient side, the node permissions
// are bound to the share permissions. So we need to
// adjust the permissions to the share permissions if necessary.
if (!$isOwnShare) {
$result['item_permissions'] = $share->getPermissions();
// For some reason, single files share are forbidden to have the delete permission
// since we have custom methods to check those, let's adjust straight away.
// DAV permissions does not have that issue though.
if ($this->canDeleteShare($share) || $this->canDeleteShareFromSelf($share)) {
$result['item_permissions'] |= Constants::PERMISSION_DELETE;
}
if ($this->canEditShare($share)) {
$result['item_permissions'] |= Constants::PERMISSION_UPDATE;
}
}
// See MOUNT_ROOT_PROPERTYNAME dav property
$result['is-mount-root'] = $node->getInternalPath() === '';
$result['mount-type'] = $node->getMountPoint()->getMountType();
$result['mimetype'] = $node->getMimetype();
$result['has_preview'] = $this->previewManager->isAvailable($node);
$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) {
$expiration->setTimezone($this->dateTimeZone->getTimeZone());
$result['expiration'] = $expiration->format('Y-m-d 00:00:00');
}
if ($share->getShareType() === IShare::TYPE_USER) {
$sharedWith = $this->userManager->get($share->getSharedWith());
$result['share_with'] = $share->getSharedWith();
$result['share_with_displayname'] = $sharedWith !== null ? $sharedWith->getDisplayName() : $share->getSharedWith();
$result['share_with_displayname_unique'] = $sharedWith !== null ? (
!empty($sharedWith->getSystemEMailAddress()) ? $sharedWith->getSystemEMailAddress() : $sharedWith->getUID()
) : $share->getSharedWith();
$userStatuses = $this->userStatusManager->getUserStatuses([$share->getSharedWith()]);
$userStatus = array_shift($userStatuses);
if ($userStatus) {
$result['status'] = [
'status' => $userStatus->getStatus(),
'message' => $userStatus->getMessage(),
'icon' => $userStatus->getIcon(),
'clearAt' => $userStatus->getClearAt()
? (int)$userStatus->getClearAt()->format('U')
: null,
];
}
} elseif ($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_LINK) {
// "share_with" and "share_with_displayname" for passwords of link
// shares was deprecated in Nextcloud 15, use "password" instead.
$result['share_with'] = $share->getPassword();
$result['share_with_displayname'] = '(' . $this->l->t('Shared link') . ')';
$result['password'] = $share->getPassword();
$result['send_password_by_talk'] = $share->getSendPasswordByTalk();
$result['token'] = $share->getToken();
$result['url'] = $this->urlGenerator->linkToRouteAbsolute('files_sharing.sharecontroller.showShare', ['token' => $share->getToken()]);
} elseif ($share->getShareType() === IShare::TYPE_REMOTE) {
$result['share_with'] = $share->getSharedWith();
$result['share_with_displayname'] = $this->getCachedFederatedDisplayName($share->getSharedWith());
$result['token'] = $share->getToken();
} elseif ($share->getShareType() === IShare::TYPE_REMOTE_GROUP) {
$result['share_with'] = $share->getSharedWith();
$result['share_with_displayname'] = $this->getDisplayNameFromAddressBook($share->getSharedWith(), 'CLOUD');
$result['token'] = $share->getToken();
} elseif ($share->getShareType() === IShare::TYPE_EMAIL) {
$result['share_with'] = $share->getSharedWith();
$result['password'] = $share->getPassword();
$result['password_expiration_time'] = $share->getPasswordExpirationTime() !== null ? $share->getPasswordExpirationTime()->format(\DateTime::ATOM) : null;
$result['send_password_by_talk'] = $share->getSendPasswordByTalk();
$result['share_with_displayname'] = $this->getDisplayNameFromAddressBook($share->getSharedWith(), 'EMAIL');
$result['token'] = $share->getToken();
} elseif ($share->getShareType() === IShare::TYPE_CIRCLE) {
// getSharedWith() returns either "name (type, owner)" or
// "name (type, owner) [id]", depending on the Teams app version.
$hasCircleId = (substr($share->getSharedWith(), -1) === ']');
$result['share_with_displayname'] = $share->getSharedWithDisplayName();
if (empty($result['share_with_displayname'])) {
$displayNameLength = ($hasCircleId ? strrpos($share->getSharedWith(), ' ') : strlen($share->getSharedWith()));
$result['share_with_displayname'] = substr($share->getSharedWith(), 0, $displayNameLength);
}
$result['share_with_avatar'] = $share->getSharedWithAvatar();
$shareWithStart = ($hasCircleId ? strrpos($share->getSharedWith(), '[') + 1 : 0);
$shareWithLength = ($hasCircleId ? -1 : strpos($share->getSharedWith(), ' '));
if ($shareWithLength === false) {
$result['share_with'] = substr($share->getSharedWith(), $shareWithStart);
} else {
$result['share_with'] = substr($share->getSharedWith(), $shareWithStart, $shareWithLength);
}
} elseif ($share->getShareType() === IShare::TYPE_ROOM) {
$result['share_with'] = $share->getSharedWith();
$result['share_with_displayname'] = '';
try {
/** @var array{share_with_displayname: string, share_with_link: string, share_with?: string, token?: string} $roomShare */
$roomShare = $this->getRoomShareHelper()->formatShare($share);
$result = array_merge($result, $roomShare);
} catch (QueryException $e) {
}
} elseif ($share->getShareType() === IShare::TYPE_DECK) {
$result['share_with'] = $share->getSharedWith();
$result['share_with_displayname'] = '';
try {
/** @var array{share_with: string, share_with_displayname: string, share_with_link: string} $deckShare */
$deckShare = $this->getDeckShareHelper()->formatShare($share);
$result = array_merge($result, $deckShare);
} catch (QueryException $e) {
}
} elseif ($share->getShareType() === IShare::TYPE_SCIENCEMESH) {
$result['share_with'] = $share->getSharedWith();
$result['share_with_displayname'] = '';
try {
/** @var array{share_with: string, share_with_displayname: string, token: string} $scienceMeshShare */
$scienceMeshShare = $this->getSciencemeshShareHelper()->formatShare($share);
$result = array_merge($result, $scienceMeshShare);
} catch (QueryException $e) {
}
}
$result['mail_send'] = $share->getMailSend() ? 1 : 0;
$result['hide_download'] = $share->getHideDownload() ? 1 : 0;
$result['attributes'] = null;
if ($attributes = $share->getAttributes()) {
$result['attributes'] = (string)\json_encode($attributes->toArray());
}
return $result;
}
/**
* Check if one of the users address books knows the exact property, if
* not we return the full name.
*
* @param string $query
* @param string $property
* @return string
*/
private function getDisplayNameFromAddressBook(string $query, string $property): string {
// FIXME: If we inject the contacts manager it gets initialized before any address books are registered
try {
$result = \OC::$server->getContactsManager()->search($query, [$property], [
'limit' => 1,
'enumeration' => false,
'strict_search' => true,
]);
} catch (Exception $e) {
$this->logger->error(
$e->getMessage(),
['exception' => $e]
);
return $query;
}
foreach ($result as $r) {
foreach ($r[$property] as $value) {
if ($value === $query && $r['FN']) {
return $r['FN'];
}
}
}
return $query;
}
/**
* @param array $shares
* @param array|null $updatedDisplayName
*
* @return array
*/
private function fixMissingDisplayName(array $shares, ?array $updatedDisplayName = null): array {
$userIds = $updated = [];
foreach ($shares as $share) {
// share is federated and share have no display name yet
if ($share['share_type'] === IShare::TYPE_REMOTE
&& ($share['share_with'] ?? '') !== ''
&& ($share['share_with_displayname'] ?? '') === '') {
$userIds[] = $userId = $share['share_with'];
if ($updatedDisplayName !== null && array_key_exists($userId, $updatedDisplayName)) {
$share['share_with_displayname'] = $updatedDisplayName[$userId];
}
}
// prepping userIds with displayName to be updated
$updated[] = $share;
}
// if $updatedDisplayName is not null, it means we should have already fixed displayNames of the shares
if ($updatedDisplayName !== null) {
return $updated;
}
// get displayName for the generated list of userId with no displayName
$displayNames = $this->retrieveFederatedDisplayName($userIds);
// if no displayName are updated, we exit
if (empty($displayNames)) {
return $updated;
}
// let's fix missing display name and returns all shares
return $this->fixMissingDisplayName($shares, $displayNames);
}
/**
* get displayName of a list of userIds from the lookup-server; through the globalsiteselector app.
* returns an array with userIds as keys and displayName as values.
*
* @param array $userIds
* @param bool $cacheOnly - do not reach LUS, get data from cache.
*
* @return array
* @throws ContainerExceptionInterface
*/
private function retrieveFederatedDisplayName(array $userIds, bool $cacheOnly = false): array {
// check if gss is enabled and available
if (count($userIds) === 0
|| !$this->appManager->isInstalled('globalsiteselector')
|| !class_exists('\OCA\GlobalSiteSelector\Service\SlaveService')) {
return [];
}
try {
$slaveService = Server::get(SlaveService::class);
} catch (\Throwable $e) {
$this->logger->error(
$e->getMessage(),
['exception' => $e]
);
return [];
}
return $slaveService->getUsersDisplayName($userIds, $cacheOnly);
}
/**
* retrieve displayName from cache if available (should be used on federated shares)
* if not available in cache/lus, try for get from address-book, else returns empty string.
*
* @param string $userId
* @param bool $cacheOnly if true will not reach the lus but will only get data from cache
*
* @return string
*/
private function getCachedFederatedDisplayName(string $userId, bool $cacheOnly = true): string {
$details = $this->retrieveFederatedDisplayName([$userId], $cacheOnly);
if (array_key_exists($userId, $details)) {
return $details[$userId];
}
$displayName = $this->getDisplayNameFromAddressBook($userId, 'CLOUD');
return ($displayName === $userId) ? '' : $displayName;
}
/**
* Get a specific share by id
*
@ -1528,150 +1183,6 @@ class ShareAPIController extends OCSController {
return false;
}
/**
* Does the user have edit permission on the share
*
* @param \OCP\Share\IShare $share the share to check
* @return boolean
*/
protected function canEditShare(IShare $share): bool {
// A file with permissions 0 can't be accessed by us. So Don't show it
if ($share->getPermissions() === 0) {
return false;
}
// The owner of the file and the creator of the share
// can always edit the share
if ($share->getShareOwner() === $this->currentUser ||
$share->getSharedBy() === $this->currentUser
) {
return true;
}
//! we do NOT support some kind of `admin` in groups.
//! You cannot edit shares shared to a group you're
//! a member of if you're not the share owner or the file owner!
return false;
}
/**
* Does the user have delete permission on the share
*
* @param \OCP\Share\IShare $share the share to check
* @return boolean
*/
protected function canDeleteShare(IShare $share): bool {
// A file with permissions 0 can't be accessed by us. So Don't show it
if ($share->getPermissions() === 0) {
return false;
}
// if the user is the recipient, i can unshare
// the share with self
if ($share->getShareType() === IShare::TYPE_USER &&
$share->getSharedWith() === $this->currentUser
) {
return true;
}
// The owner of the file and the creator of the share
// can always delete the share
if ($share->getShareOwner() === $this->currentUser ||
$share->getSharedBy() === $this->currentUser
) {
return true;
}
return false;
}
/**
* Does the user have delete permission on the share
* This differs from the canDeleteShare function as it only
* remove the share for the current user. It does NOT
* completely delete the share but only the mount point.
* It can then be restored from the deleted shares section.
*
* @param \OCP\Share\IShare $share the share to check
* @return boolean
*
* @suppress PhanUndeclaredClassMethod
*/
protected function canDeleteShareFromSelf(IShare $share): bool {
if ($share->getShareType() !== IShare::TYPE_GROUP &&
$share->getShareType() !== IShare::TYPE_ROOM &&
$share->getShareType() !== IShare::TYPE_DECK &&
$share->getShareType() !== IShare::TYPE_SCIENCEMESH
) {
return false;
}
if ($share->getShareOwner() === $this->currentUser ||
$share->getSharedBy() === $this->currentUser
) {
// Delete the whole share, not just for self
return false;
}
// If in the recipient group, you can delete the share from self
if ($share->getShareType() === IShare::TYPE_GROUP) {
$sharedWith = $this->groupManager->get($share->getSharedWith());
$user = $this->userManager->get($this->currentUser);
if ($user !== null && $sharedWith !== null && $sharedWith->inGroup($user)) {
return true;
}
}
if ($share->getShareType() === IShare::TYPE_ROOM) {
try {
return $this->getRoomShareHelper()->canAccessShare($share, $this->currentUser);
} catch (QueryException $e) {
return false;
}
}
if ($share->getShareType() === IShare::TYPE_DECK) {
try {
return $this->getDeckShareHelper()->canAccessShare($share, $this->currentUser);
} catch (QueryException $e) {
return false;
}
}
if ($share->getShareType() === IShare::TYPE_SCIENCEMESH) {
try {
return $this->getSciencemeshShareHelper()->canAccessShare($share, $this->currentUser);
} catch (QueryException $e) {
return false;
}
}
return false;
}
/**
* Make sure that the passed date is valid ISO 8601
* So YYYY-MM-DD
* If not throw an exception
*
* @param string $expireDate
*
* @throws \Exception
* @return \DateTime
*/
private function parseDate(string $expireDate): \DateTime {
try {
$date = new \DateTime(trim($expireDate, '"'), $this->dateTimeZone->getTimeZone());
// Make sure it expires at midnight in owner timezone
$date->setTime(0, 0, 0);
} catch (\Exception $e) {
throw new \Exception($this->l->t('Invalid date. Format must be YYYY-MM-DD'));
}
return $date;
}
/**
* Since we have multiple providers but the OCS Share API v1 does
* not support this we need to check all backends.
@ -1764,57 +1275,6 @@ class ShareAPIController extends OCSController {
}
}
/**
* Returns the helper of ShareAPIController 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\ShareAPIController
* @throws QueryException
*/
private function getRoomShareHelper() {
if (!$this->appManager->isEnabledForUser('spreed')) {
throw new QueryException();
}
return $this->serverContainer->get('\OCA\Talk\Share\Helper\ShareAPIController');
}
/**
* Returns the helper of ShareAPIHelper 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 ShareAPIHelper 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');
}
/**
* @param string $viewer
* @param Node $node

View file

@ -0,0 +1,628 @@
<?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\AppInfo\Application;
use OCA\Files_Sharing\ResponseDefinitions;
use OCP\App\IAppManager;
use OCP\AppFramework\OCSController;
use OCP\AppFramework\QueryException;
use OCP\Constants;
use OCP\Files\Folder;
use OCP\Files\IRootFolder;
use OCP\Files\Node;
use OCP\Files\NotFoundException;
use OCP\IDateTimeZone;
use OCP\IGroupManager;
use OCP\IL10N;
use OCP\IPreview;
use OCP\IRequest;
use OCP\IURLGenerator;
use OCP\IUserManager;
use OCP\Server;
use OCP\Share\IManager as ShareManager;
use OCP\Share\IShare;
use OCP\UserStatus\IManager as UserStatusManager;
use Psr\Container\ContainerExceptionInterface;
use Psr\Container\ContainerInterface;
use Psr\Log\LoggerInterface;
/**
* @psalm-import-type Files_SharingShare from ResponseDefinitions
*/
abstract class ShareApiControllerFactory extends OCSController {
protected ?string $currentUser;
public bool $isDeletedShareController = false;
public function __construct(
IRequest $request,
protected ShareManager $shareManager,
protected ?string $userId,
protected IUserManager $userManager,
protected IGroupManager $groupManager,
protected IRootFolder $rootFolder,
protected IAppManager $appManager,
protected ContainerInterface $serverContainer,
protected UserStatusManager $userStatusManager,
protected IPreview $previewManager,
protected IDateTimeZone $dateTimeZone,
protected IURLGenerator $urlGenerator,
protected IL10N $l,
protected LoggerInterface $logger,
) {
parent::__construct(Application::APP_ID, $request);
$this->currentUser = $userId;
}
/**
* Convert an IShare to an array for OCS output
*
* @param \OCP\Share\IShare $share
* @param Node|null $recipientNode
* @return Files_SharingShare
* @throws NotFoundException In case the node can't be resolved.
*
* @suppress PhanUndeclaredClassMethod
*/
public function formatShare(IShare $share, ?Node $recipientNode = null): array {
$sharedBy = $this->userManager->get($share->getSharedBy());
$shareOwner = $this->userManager->get($share->getShareOwner());
$isOwnShare = false;
if ($shareOwner !== null) {
$isOwnShare = $shareOwner->getUID() === $this->currentUser;
}
$result = [
'id' => $share->getId(),
'share_type' => $share->getShareType(),
'uid_owner' => $share->getSharedBy(),
'displayname_owner' => $sharedBy !== null ? $sharedBy->getDisplayName() : $share->getSharedBy(),
// recipient permissions
'permissions' => $share->getPermissions(),
// current user permissions on this share
'can_edit' => $this->canEditShare($share),
'can_delete' => $this->canDeleteShare($share),
'stime' => $share->getShareTime()->getTimestamp(),
'parent' => null,
'expiration' => null,
'token' => null,
'uid_file_owner' => $share->getShareOwner(),
'note' => $share->getNote(),
'label' => $share->getLabel(),
'displayname_file_owner' => $shareOwner !== null ? $shareOwner->getDisplayName() : $share->getShareOwner(),
];
$userFolder = $this->rootFolder->getUserFolder($this->isDeletedShareController ? $share->getSharedBy() : $this->currentUser);
if ($recipientNode) {
$node = $recipientNode;
} else {
$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 Folder) {
$result['item_type'] = 'folder';
} else {
$result['item_type'] = 'file';
}
// Get the original node permission if the share owner is the current user
if ($isOwnShare) {
$result['item_permissions'] = $node->getPermissions();
}
// If we're on the recipient side, the node permissions
// are bound to the share permissions. So we need to
// adjust the permissions to the share permissions if necessary.
if (!$isOwnShare) {
$result['item_permissions'] = $share->getPermissions();
// For some reason, single files share are forbidden to have the delete permission
// since we have custom methods to check those, let's adjust straight away.
// DAV permissions does not have that issue though.
if ($this->canDeleteShare($share) || $this->canDeleteShareFromSelf($share)) {
$result['item_permissions'] |= Constants::PERMISSION_DELETE;
}
if ($this->canEditShare($share)) {
$result['item_permissions'] |= Constants::PERMISSION_UPDATE;
}
}
// See MOUNT_ROOT_PROPERTYNAME dav property
$result['is-mount-root'] = $node->getInternalPath() === '';
$result['mount-type'] = $node->getMountPoint()->getMountType();
$result['mimetype'] = $node->getMimetype();
$result['has_preview'] = $this->previewManager->isAvailable($node);
$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) {
$expiration->setTimezone($this->dateTimeZone->getTimeZone());
$result['expiration'] = $expiration->format('Y-m-d 00:00:00');
}
if ($share->getShareType() === IShare::TYPE_USER) {
$sharedWith = $this->userManager->get($share->getSharedWith());
$result['share_with'] = $share->getSharedWith();
$result['share_with_displayname'] = $sharedWith !== null ? $sharedWith->getDisplayName() : $share->getSharedWith();
$result['share_with_displayname_unique'] = $sharedWith !== null ? (
!empty($sharedWith->getSystemEMailAddress()) ? $sharedWith->getSystemEMailAddress() : $sharedWith->getUID()
) : $share->getSharedWith();
$userStatuses = $this->userStatusManager->getUserStatuses([$share->getSharedWith()]);
$userStatus = array_shift($userStatuses);
if ($userStatus) {
$result['status'] = [
'status' => $userStatus->getStatus(),
'message' => $userStatus->getMessage(),
'icon' => $userStatus->getIcon(),
'clearAt' => $userStatus->getClearAt()
? (int)$userStatus->getClearAt()->format('U')
: null,
];
}
} elseif ($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_LINK) {
// "share_with" and "share_with_displayname" for passwords of link
// shares was deprecated in Nextcloud 15, use "password" instead.
$result['share_with'] = $share->getPassword();
$result['share_with_displayname'] = '(' . $this->l->t('Shared link') . ')';
$result['password'] = $share->getPassword();
$result['send_password_by_talk'] = $share->getSendPasswordByTalk();
$result['token'] = $share->getToken();
$result['url'] = $this->urlGenerator->linkToRouteAbsolute('files_sharing.sharecontroller.showShare', ['token' => $share->getToken()]);
} elseif ($share->getShareType() === IShare::TYPE_REMOTE) {
$result['share_with'] = $share->getSharedWith();
$result['share_with_displayname'] = $this->getCachedFederatedDisplayName($share->getSharedWith());
$result['token'] = $share->getToken();
} elseif ($share->getShareType() === IShare::TYPE_REMOTE_GROUP) {
$result['share_with'] = $share->getSharedWith();
$result['share_with_displayname'] = $this->getDisplayNameFromAddressBook($share->getSharedWith(), 'CLOUD');
$result['token'] = $share->getToken();
} elseif ($share->getShareType() === IShare::TYPE_EMAIL) {
$result['share_with'] = $share->getSharedWith();
$result['password'] = $share->getPassword();
$result['password_expiration_time'] = $share->getPasswordExpirationTime() !== null ? $share->getPasswordExpirationTime()->format(\DateTime::ATOM) : null;
$result['send_password_by_talk'] = $share->getSendPasswordByTalk();
$result['share_with_displayname'] = $this->getDisplayNameFromAddressBook($share->getSharedWith(), 'EMAIL');
$result['token'] = $share->getToken();
} elseif ($share->getShareType() === IShare::TYPE_CIRCLE) {
// getSharedWith() returns either "name (type, owner)" or
// "name (type, owner) [id]", depending on the Teams app version.
$hasCircleId = (substr($share->getSharedWith(), -1) === ']');
$result['share_with_displayname'] = $share->getSharedWithDisplayName();
if (empty($result['share_with_displayname'])) {
$displayNameLength = ($hasCircleId ? strrpos($share->getSharedWith(), ' ') : strlen($share->getSharedWith()));
$result['share_with_displayname'] = substr($share->getSharedWith(), 0, $displayNameLength);
}
$result['share_with_avatar'] = $share->getSharedWithAvatar();
$shareWithStart = ($hasCircleId ? strrpos($share->getSharedWith(), '[') + 1 : 0);
$shareWithLength = ($hasCircleId ? -1 : strpos($share->getSharedWith(), ' '));
if ($shareWithLength === false) {
$result['share_with'] = substr($share->getSharedWith(), $shareWithStart);
} else {
$result['share_with'] = substr($share->getSharedWith(), $shareWithStart, $shareWithLength);
}
} elseif ($share->getShareType() === IShare::TYPE_ROOM) {
$result['share_with'] = $share->getSharedWith();
$result['share_with_displayname'] = '';
try {
$roomShareHelper = $this->isDeletedShareController
? $this->getDeletedRoomShareHelper()
: $this->getRoomShareHelper();
/** @var array{share_with_displayname: string, share_with_link: string, share_with?: string, token?: string} $roomShare */
$roomShare = $roomShareHelper->formatShare($share);
$result = array_merge($result, $roomShare);
} catch (QueryException $e) {
}
} elseif ($share->getShareType() === IShare::TYPE_DECK) {
$result['share_with'] = $share->getSharedWith();
$result['share_with_displayname'] = '';
try {
/** @var array{share_with: string, share_with_displayname: string, share_with_link: string} $deckShare */
$deckShare = $this->getDeckShareHelper()->formatShare($share);
$result = array_merge($result, $deckShare);
} catch (QueryException $e) {
}
} elseif ($share->getShareType() === IShare::TYPE_SCIENCEMESH) {
$result['share_with'] = $share->getSharedWith();
$result['share_with_displayname'] = '';
try {
/** @var array{share_with: string, share_with_displayname: string, token: string} $scienceMeshShare */
$scienceMeshShare = $this->getSciencemeshShareHelper()->formatShare($share);
$result = array_merge($result, $scienceMeshShare);
} catch (QueryException $e) {
}
}
$result['mail_send'] = $share->getMailSend() ? 1 : 0;
$result['hide_download'] = $share->getHideDownload() ? 1 : 0;
$result['attributes'] = null;
if ($attributes = $share->getAttributes()) {
$result['attributes'] = (string)\json_encode($attributes->toArray());
}
return $result;
}
/**
* Returns the helper of ShareAPIController 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\ShareAPIController
* @throws QueryException
*/
public function getRoomShareHelper() {
if (!$this->appManager->isEnabledForUser('spreed')) {
throw new QueryException();
}
return $this->serverContainer->get('\OCA\Talk\Share\Helper\ShareAPIController');
}
/**
* 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
*/
public function getDeletedRoomShareHelper() {
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
*/
public 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
*/
public function getSciencemeshShareHelper() {
if (!$this->appManager->isEnabledForUser('sciencemesh')) {
throw new QueryException();
}
return $this->serverContainer->get('\OCA\ScienceMesh\Sharing\ShareAPIHelper');
}
/**
* Does the user have edit permission on the share
*
* @param \OCP\Share\IShare $share the share to check
* @return boolean
*/
public function canEditShare(IShare $share): bool {
// A file with permissions 0 can't be accessed by us. So Don't show it
if ($share->getPermissions() === 0) {
return false;
}
// The owner of the file and the creator of the share
// can always edit the share
if ($share->getShareOwner() === $this->currentUser ||
$share->getSharedBy() === $this->currentUser
) {
return true;
}
//! we do NOT support some kind of `admin` in groups.
//! You cannot edit shares shared to a group you're
//! a member of if you're not the share owner or the file owner!
return false;
}
/**
* Does the user have delete permission on the share
*
* @param \OCP\Share\IShare $share the share to check
* @return boolean
*/
public function canDeleteShare(IShare $share): bool {
// A file with permissions 0 can't be accessed by us. So Don't show it
if ($share->getPermissions() === 0) {
return false;
}
// if the user is the recipient, i can unshare
// the share with self
if ($share->getShareType() === IShare::TYPE_USER &&
$share->getSharedWith() === $this->currentUser
) {
return true;
}
// The owner of the file and the creator of the share
// can always delete the share
if ($share->getShareOwner() === $this->currentUser ||
$share->getSharedBy() === $this->currentUser
) {
return true;
}
return false;
}
/**
* Does the user have delete permission on the share
* This differs from the canDeleteShare function as it only
* remove the share for the current user. It does NOT
* completely delete the share but only the mount point.
* It can then be restored from the deleted shares section.
*
* @param \OCP\Share\IShare $share the share to check
* @return boolean
*
* @suppress PhanUndeclaredClassMethod
*/
public function canDeleteShareFromSelf(IShare $share): bool {
if ($share->getShareType() !== IShare::TYPE_GROUP &&
$share->getShareType() !== IShare::TYPE_ROOM &&
$share->getShareType() !== IShare::TYPE_DECK &&
$share->getShareType() !== IShare::TYPE_SCIENCEMESH
) {
return false;
}
if ($share->getShareOwner() === $this->currentUser ||
$share->getSharedBy() === $this->currentUser
) {
// Delete the whole share, not just for self
return false;
}
// If in the recipient group, you can delete the share from self
if ($share->getShareType() === IShare::TYPE_GROUP) {
$sharedWith = $this->groupManager->get($share->getSharedWith());
$user = $this->userManager->get($this->currentUser);
if ($user !== null && $sharedWith !== null && $sharedWith->inGroup($user)) {
return true;
}
}
if ($share->getShareType() === IShare::TYPE_ROOM) {
try {
return $this->getRoomShareHelper()->canAccessShare($share, $this->currentUser);
} catch (QueryException $e) {
return false;
}
}
if ($share->getShareType() === IShare::TYPE_DECK) {
try {
return $this->getDeckShareHelper()->canAccessShare($share, $this->currentUser);
} catch (QueryException $e) {
return false;
}
}
if ($share->getShareType() === IShare::TYPE_SCIENCEMESH) {
try {
return $this->getSciencemeshShareHelper()->canAccessShare($share, $this->currentUser);
} catch (QueryException $e) {
return false;
}
}
return false;
}
/**
* Make sure that the passed date is valid ISO 8601
* So YYYY-MM-DD
* If not throw an exception
*
* @param string $expireDate
*
* @throws \Exception
* @return \DateTime
*/
public function parseDate(string $expireDate): \DateTime {
try {
$date = new \DateTime(trim($expireDate, '"'), $this->dateTimeZone->getTimeZone());
// Make sure it expires at midnight in owner timezone
$date->setTime(0, 0, 0);
} catch (\Exception $e) {
throw new \Exception($this->l->t('Invalid date. Format must be YYYY-MM-DD'));
}
return $date;
}
/**
* retrieve displayName from cache if available (should be used on federated shares)
* if not available in cache/lus, try for get from address-book, else returns empty string.
*
* @param string $userId
* @param bool $cacheOnly if true will not reach the lus but will only get data from cache
*
* @return string
*/
public function getCachedFederatedDisplayName(string $userId, bool $cacheOnly = true): string {
$details = $this->retrieveFederatedDisplayName([$userId], $cacheOnly);
if (array_key_exists($userId, $details)) {
return $details[$userId];
}
$displayName = $this->getDisplayNameFromAddressBook($userId, 'CLOUD');
return ($displayName === $userId) ? '' : $displayName;
}
/**
* Check if one of the users address books knows the exact property, if
* not we return the full name.
*
* @param string $query
* @param string $property
* @return string
*/
public function getDisplayNameFromAddressBook(string $query, string $property): string {
// FIXME: If we inject the contacts manager it gets initialized before any address books are registered
try {
$result = \OC::$server->getContactsManager()->search($query, [$property], [
'limit' => 1,
'enumeration' => false,
'strict_search' => true,
]);
} catch (\Exception $e) {
$this->logger->error(
$e->getMessage(),
['exception' => $e]
);
return $query;
}
foreach ($result as $r) {
foreach ($r[$property] as $value) {
if ($value === $query && $r['FN']) {
return $r['FN'];
}
}
}
return $query;
}
/**
* @param array $shares
* @param array|null $updatedDisplayName
*
* @return array
*/
public function fixMissingDisplayName(array $shares, ?array $updatedDisplayName = null): array {
$userIds = $updated = [];
foreach ($shares as $share) {
// share is federated and share have no display name yet
if ($share['share_type'] === IShare::TYPE_REMOTE
&& ($share['share_with'] ?? '') !== ''
&& ($share['share_with_displayname'] ?? '') === '') {
$userIds[] = $userId = $share['share_with'];
if ($updatedDisplayName !== null && array_key_exists($userId, $updatedDisplayName)) {
$share['share_with_displayname'] = $updatedDisplayName[$userId];
}
}
// prepping userIds with displayName to be updated
$updated[] = $share;
}
// if $updatedDisplayName is not null, it means we should have already fixed displayNames of the shares
if ($updatedDisplayName !== null) {
return $updated;
}
// get displayName for the generated list of userId with no displayName
$displayNames = $this->retrieveFederatedDisplayName($userIds);
// if no displayName are updated, we exit
if (empty($displayNames)) {
return $updated;
}
// let's fix missing display name and returns all shares
return $this->fixMissingDisplayName($shares, $displayNames);
}
/**
* get displayName of a list of userIds from the lookup-server; through the globalsiteselector app.
* returns an array with userIds as keys and displayName as values.
*
* @param array $userIds
* @param bool $cacheOnly - do not reach LUS, get data from cache.
*
* @return array
* @throws ContainerExceptionInterface
*/
public function retrieveFederatedDisplayName(array $userIds, bool $cacheOnly = false): array {
// check if gss is enabled and available
if (count($userIds) === 0
|| !$this->appManager->isInstalled('globalsiteselector')
|| !$this->appManager->isEnabledForUser('globalsiteselector')
|| !class_exists('\OCA\GlobalSiteSelector\Service\SlaveService')) {
return [];
}
try {
/** @var \OCA\GlobalSiteSelector\Service\SlaveService $slaveService */
$slaveService = $this->serverContainer->get('\OCA\GlobalSiteSelector\Service\SlaveService');
} catch (\Throwable $e) {
$this->logger->error(
$e->getMessage(),
['exception' => $e]
);
return [];
}
return $slaveService->getUsersDisplayName($userIds, $cacheOnly);
}
}

View file

@ -24,8 +24,9 @@ class ExpireSharesJob extends TimedJob {
ITimeFactory $time,
private IManager $shareManager,
private IDBConnection $db,
private IAppConfig $config,
private LoggerInterface $logger) {
private IAppConfig $appConfig,
private LoggerInterface $logger,
) {
parent::__construct($time);
@ -41,7 +42,7 @@ class ExpireSharesJob extends TimedJob {
* @param array $argument unused argument
*/
public function run($argument) {
if ($this->config->getValueString('core', 'shareapi_delete_on_expire', 'yes') === 'no') {
if ($this->appConfig->getValueString('core', 'shareapi_delete_on_expire', 'yes') === 'no') {
$this->logger->info('Share deletion on expiration is disabled');
return;
}

View file

@ -56,29 +56,6 @@ namespace OCA\Files_Sharing;
* url?: string,
* }
*
* @psalm-type Files_SharingDeletedShare = array{
* id: string,
* share_type: int,
* uid_owner: string,
* displayname_owner: string,
* permissions: int,
* stime: int,
* uid_file_owner: string,
* displayname_file_owner: string,
* path: string,
* item_type: string,
* mimetype: string,
* storage: int,
* item_source: int,
* file_source: int,
* file_parent: int,
* file_target: int,
* expiration: string|null,
* share_with: string|null,
* share_with_displayname: string|null,
* share_with_link: string|null,
* }
*
* @psalm-type Files_SharingRemoteShare = array{
* accepted: bool,
* file_id: int|null,

View file

@ -244,105 +244,6 @@
}
}
},
"DeletedShare": {
"type": "object",
"required": [
"id",
"share_type",
"uid_owner",
"displayname_owner",
"permissions",
"stime",
"uid_file_owner",
"displayname_file_owner",
"path",
"item_type",
"mimetype",
"storage",
"item_source",
"file_source",
"file_parent",
"file_target",
"expiration",
"share_with",
"share_with_displayname",
"share_with_link"
],
"properties": {
"id": {
"type": "string"
},
"share_type": {
"type": "integer",
"format": "int64"
},
"uid_owner": {
"type": "string"
},
"displayname_owner": {
"type": "string"
},
"permissions": {
"type": "integer",
"format": "int64"
},
"stime": {
"type": "integer",
"format": "int64"
},
"uid_file_owner": {
"type": "string"
},
"displayname_file_owner": {
"type": "string"
},
"path": {
"type": "string"
},
"item_type": {
"type": "string"
},
"mimetype": {
"type": "string"
},
"storage": {
"type": "integer",
"format": "int64"
},
"item_source": {
"type": "integer",
"format": "int64"
},
"file_source": {
"type": "integer",
"format": "int64"
},
"file_parent": {
"type": "integer",
"format": "int64"
},
"file_target": {
"type": "integer",
"format": "int64"
},
"expiration": {
"type": "string",
"nullable": true
},
"share_with": {
"type": "string",
"nullable": true
},
"share_with_displayname": {
"type": "string",
"nullable": true
},
"share_with_link": {
"type": "string",
"nullable": true
}
}
},
"Lookup": {
"type": "object",
"required": [
@ -2891,7 +2792,7 @@
"data": {
"type": "array",
"items": {
"$ref": "#/components/schemas/DeletedShare"
"$ref": "#/components/schemas/Share"
}
}
}
@ -3000,6 +2901,70 @@
}
}
},
"/ocs/v2.php/apps/files_sharing/api/v1/expiredshares": {
"get": {
"operationId": "expired_shareapi-index",
"summary": "Get a list of all expired shares",
"tags": [
"expired_shareapi"
],
"security": [
{
"bearer_auth": []
},
{
"basic_auth": []
}
],
"parameters": [
{
"name": "OCS-APIRequest",
"in": "header",
"description": "Required to be true for the API request to pass",
"required": true,
"schema": {
"type": "boolean",
"default": true
}
}
],
"responses": {
"200": {
"description": "Deleted shares returned",
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"ocs"
],
"properties": {
"ocs": {
"type": "object",
"required": [
"meta",
"data"
],
"properties": {
"meta": {
"$ref": "#/components/schemas/OCSMeta"
},
"data": {
"type": "array",
"items": {
"$ref": "#/components/schemas/Share"
}
}
}
}
}
}
}
}
}
}
}
},
"/ocs/v2.php/apps/files_sharing/api/v1/sharees": {
"get": {
"operationId": "shareesapi-search",

View file

@ -34,12 +34,12 @@ describe('Sharing views definition', () => {
const shareOverviewView = Navigation.views.find(view => view.id === 'shareoverview') as View
const sharesChildViews = Navigation.views.filter(view => view.parent === 'shareoverview') as View[]
expect(Navigation.register).toHaveBeenCalledTimes(7)
expect(Navigation.register).toHaveBeenCalledTimes(8)
// one main view and no children
expect(Navigation.views.length).toBe(7)
expect(Navigation.views.length).toBe(8)
expect(shareOverviewView).toBeDefined()
expect(sharesChildViews.length).toBe(6)
expect(sharesChildViews.length).toBe(7)
expect(shareOverviewView?.id).toBe('shareoverview')
expect(shareOverviewView?.name).toBe('Shares')
@ -55,6 +55,7 @@ describe('Sharing views definition', () => {
{ id: 'sharinglinks', name: 'Shared by link' },
{ id: 'filerequest', name: 'File requests' },
{ id: 'deletedshares', name: 'Deleted shares' },
{ id: 'expiredshares', name: 'Expired shares', columns: 1 },
{ id: 'pendingshares', name: 'Pending shares' },
]
@ -67,7 +68,7 @@ describe('Sharing views definition', () => {
expect(view?.emptyCaption).toBeDefined()
expect(view?.icon).match(/<svg.+<\/svg>/)
expect(view?.order).toBe(index + 1)
expect(view?.columns).toStrictEqual([])
expect(view?.columns).toHaveLength(dataProvider[index].columns || 0)
expect(view?.getContents).toBeDefined()
})
})
@ -98,7 +99,7 @@ describe('Sharing views contents', () => {
})
registerSharingViews()
expect(Navigation.views.length).toBe(7)
expect(Navigation.views.length).toBe(8)
Navigation.views.forEach(async (view: View) => {
const content = await view.getContents('/')
expect(content.contents).toStrictEqual([])

View file

@ -46,7 +46,7 @@ describe('SharingService methods definitions', () => {
})
test('Shared with you', async () => {
await getContents(true, false, false, false, [])
await getContents(true, false, false, false, false, [])
expect(axios.get).toHaveBeenCalledTimes(2)
expect(axios.get).toHaveBeenNthCalledWith(1, 'http://nextcloud.local/ocs/v2.php/apps/files_sharing/api/v1/shares', {
@ -69,7 +69,7 @@ describe('SharingService methods definitions', () => {
})
test('Shared with others', async () => {
await getContents(false, true, false, false, [])
await getContents(false, true, false, false, false, [])
expect(axios.get).toHaveBeenCalledTimes(1)
expect(axios.get).toHaveBeenCalledWith('http://nextcloud.local/ocs/v2.php/apps/files_sharing/api/v1/shares', {
@ -84,7 +84,7 @@ describe('SharingService methods definitions', () => {
})
test('Pending shares', async () => {
await getContents(false, false, true, false, [])
await getContents(false, false, true, false, false, [])
expect(axios.get).toHaveBeenCalledTimes(2)
expect(axios.get).toHaveBeenNthCalledWith(1, 'http://nextcloud.local/ocs/v2.php/apps/files_sharing/api/v1/shares/pending', {
@ -106,7 +106,7 @@ describe('SharingService methods definitions', () => {
})
test('Deleted shares', async () => {
await getContents(false, true, false, false, [])
await getContents(false, true, false, false, false, [])
expect(axios.get).toHaveBeenCalledTimes(1)
expect(axios.get).toHaveBeenCalledWith('http://nextcloud.local/ocs/v2.php/apps/files_sharing/api/v1/shares', {
@ -120,9 +120,23 @@ describe('SharingService methods definitions', () => {
})
})
test('Expired shares', async () => {
await getContents(false, false, false, false, true, [])
expect(axios.get).toHaveBeenCalledTimes(1)
expect(axios.get).toHaveBeenCalledWith('http://nextcloud.local/ocs/v2.php/apps/files_sharing/api/v1/expiredshares', {
headers: {
'Content-Type': 'application/json',
},
params: {
include_tags: true,
},
})
})
test('Unknown owner', async () => {
vi.spyOn(auth, 'getCurrentUser').mockReturnValue(null)
const results = await getContents(false, true, false, false, [])
const results = await getContents(false, true, false, false, false, [])
expect(results.folder.owner).toEqual(null)
})
@ -170,7 +184,7 @@ describe('SharingService filtering', () => {
})
test('Shared with others filtering', async () => {
const shares = await getContents(false, true, false, false, [ShareType.User])
const shares = await getContents(false, true, false, false, false, [ShareType.User])
expect(axios.get).toHaveBeenCalledTimes(1)
expect(shares.contents).toHaveLength(1)
@ -179,7 +193,7 @@ describe('SharingService filtering', () => {
})
test('Shared with others filtering empty', async () => {
const shares = await getContents(false, true, false, false, [ShareType.Link])
const shares = await getContents(false, true, false, false, false, [ShareType.Link])
expect(axios.get).toHaveBeenCalledTimes(1)
expect(shares.contents).toHaveLength(0)

View file

@ -108,7 +108,6 @@ class ApiTest extends TestCase {
$dateTimeZone->method('getTimeZone')->willReturn(new \DateTimeZone(date_default_timezone_get()));
return new ShareAPIController(
self::APP_NAME,
$this->getMockBuilder(IRequest::class)->getMock(),
$this->shareManager,
\OC::$server->getGroupManager(),

View file

@ -108,7 +108,6 @@ class ShareAPIControllerTest extends TestCase {
$this->mailer = $this->createMock(IMailer::class);
$this->ocs = new ShareAPIController(
$this->appName,
$this->request,
$this->shareManager,
$this->groupManager,
@ -135,7 +134,6 @@ class ShareAPIControllerTest extends TestCase {
private function mockFormatShare() {
return $this->getMockBuilder(ShareAPIController::class)
->setConstructorArgs([
$this->appName,
$this->request,
$this->shareManager,
$this->groupManager,
@ -752,7 +750,6 @@ class ShareAPIControllerTest extends TestCase {
/** @var ShareAPIController|\PHPUnit\Framework\MockObject\MockObject $ocs */
$ocs = $this->getMockBuilder(ShareAPIController::class)
->setConstructorArgs([
$this->appName,
$this->request,
$this->shareManager,
$this->groupManager,
@ -1386,7 +1383,6 @@ class ShareAPIControllerTest extends TestCase {
/** @var ShareAPIController $ocs */
$ocs = $this->getMockBuilder(ShareAPIController::class)
->setConstructorArgs([
$this->appName,
$this->request,
$this->shareManager,
$this->groupManager,
@ -1729,7 +1725,6 @@ class ShareAPIControllerTest extends TestCase {
/** @var ShareAPIController $ocs */
$ocs = $this->getMockBuilder(ShareAPIController::class)
->setConstructorArgs([
$this->appName,
$this->request,
$this->shareManager,
$this->groupManager,
@ -1827,7 +1822,6 @@ class ShareAPIControllerTest extends TestCase {
/** @var ShareAPIController|\PHPUnit\Framework\MockObject\MockObject $ocs */
$ocs = $this->getMockBuilder(ShareAPIController::class)
->setConstructorArgs([
$this->appName,
$this->request,
$this->shareManager,
$this->groupManager,
@ -2249,7 +2243,6 @@ class ShareAPIControllerTest extends TestCase {
/** @var ShareAPIController $ocs */
$ocs = $this->getMockBuilder(ShareAPIController::class)
->setConstructorArgs([
$this->appName,
$this->request,
$this->shareManager,
$this->groupManager,
@ -2319,7 +2312,6 @@ class ShareAPIControllerTest extends TestCase {
/** @var ShareAPIController $ocs */
$ocs = $this->getMockBuilder(ShareAPIController::class)
->setConstructorArgs([
$this->appName,
$this->request,
$this->shareManager,
$this->groupManager,
@ -2562,7 +2554,6 @@ class ShareAPIControllerTest extends TestCase {
/** @var ShareAPIController|\PHPUnit\Framework\MockObject\MockObject $ocs */
$ocs = $this->getMockBuilder(ShareAPIController::class)
->setConstructorArgs([
$this->appName,
$this->request,
$this->shareManager,
$this->groupManager,

View file

@ -83,10 +83,10 @@ class Sharing implements IDelegatedSettings {
}
/**
* Helper function to retrive boolean values from human readable strings ('yes' / 'no')
* Helper function to retrieve boolean values from human readable strings ('yes' / 'no')
*/
private function getHumanBooleanConfig(string $app, string $key, bool $default = false): bool {
return $this->config->getAppValue($app, $key, $default ? 'yes' : 'no') === 'yes';
return $this->config->getAppValue($app, $key, $default ? 'yes' : 'no') !== 'no';
}
/**

View file

@ -93,6 +93,7 @@ class SharingTest extends TestCase {
['core', 'shareapi_enforce_remote_expire_date', 'no', 'no'],
['core', 'shareapi_enforce_links_password_excluded_groups', '', ''],
['core', 'shareapi_only_share_with_group_members_exclude_group_list', '', '[]'],
['core', 'deleteOnExpire', 'yes', 'yes'],
]);
$this->shareManager->method('shareWithGroupMembersOnly')
->willReturn(false);
@ -139,6 +140,7 @@ class SharingTest extends TestCase {
'onlyShareWithGroupMembersExcludeGroupList' => [],
'enforceLinksPasswordExcludedGroups' => [],
'enforceLinksPasswordExcludedGroupsEnabled' => false,
'deleteOnExpire' => true
]
],
);
@ -232,6 +234,7 @@ class SharingTest extends TestCase {
'onlyShareWithGroupMembersExcludeGroupList' => [],
'enforceLinksPasswordExcludedGroups' => [],
'enforceLinksPasswordExcludedGroupsEnabled' => false,
'deleteOnExpire' => true,
]
],
);

View file

@ -874,12 +874,6 @@
<code><![CDATA[Circles]]></code>
<code><![CDATA[Circles]]></code>
</UndefinedClass>
<UndefinedDocblockClass>
<code><![CDATA[$this->getRoomShareHelper()]]></code>
<code><![CDATA[$this->getRoomShareHelper()]]></code>
<code><![CDATA[$this->getRoomShareHelper()]]></code>
<code><![CDATA[\OCA\Talk\Share\Helper\ShareAPIController]]></code>
</UndefinedDocblockClass>
</file>
<file src="apps/files_sharing/lib/External/Manager.php">
<LessSpecificReturnStatement>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -29,6 +29,7 @@ SPDX-FileCopyrightText: John-David Dalton <john.david.dalton@gmail.com> (http://
SPDX-FileCopyrightText: John Molakvoæ (skjnldsv) <skjnldsv@protonmail.com>
SPDX-FileCopyrightText: Jeff Sagal <sagalbot@gmail.com>
SPDX-FileCopyrightText: Jacob Clevenger<https://github.com/wheatjs>
SPDX-FileCopyrightText: Iskren Ivov Chernev <iskren.chernev@gmail.com> (https://github.com/ichernev)
SPDX-FileCopyrightText: Hypercontext
SPDX-FileCopyrightText: Guillaume Chau <guillaume.b.chau@gmail.com>
SPDX-FileCopyrightText: GitHub Inc.
@ -37,6 +38,7 @@ SPDX-FileCopyrightText: Evan You
SPDX-FileCopyrightText: Eugene Sharygin <eush77@gmail.com>
SPDX-FileCopyrightText: Eric Norris (https://github.com/ericnorris)
SPDX-FileCopyrightText: Dr.-Ing. Mario Heiderich, Cure53 <mario@cure53.de> (https://cure53.de/)
SPDX-FileCopyrightText: Denis Pushkarev
SPDX-FileCopyrightText: David Clark
SPDX-FileCopyrightText: Christoph Wurst <christoph@winzerhof-wurst.at>
SPDX-FileCopyrightText: Christoph Wurst
@ -93,6 +95,12 @@ This file is generated from multiple sources. Included packages:
- @nextcloud/logger
- version: 3.0.2
- license: GPL-3.0-or-later
- @nextcloud/router
- version: 2.2.1
- license: GPL-3.0-or-later
- @nextcloud/moment
- version: 1.3.1
- license: GPL-3.0-or-later
- @nextcloud/paths
- version: 2.2.1
- license: GPL-3.0-or-later
@ -129,6 +137,9 @@ This file is generated from multiple sources. Included packages:
- charenc
- version: 0.0.2
- license: BSD-3-Clause
- core-js
- version: 3.38.1
- license: MIT
- crypt
- version: 0.0.2
- license: BSD-3-Clause
@ -171,6 +182,9 @@ This file is generated from multiple sources. Included packages:
- md5
- version: 2.3.0
- license: BSD-3-Clause
- moment
- version: 2.30.1
- license: MIT
- node-gettext
- version: 3.0.0
- license: MIT

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -1143,7 +1143,7 @@ class Manager implements IManager {
/**
* @inheritdoc
*/
public function getSharesBy($userId, $shareType, $path = null, $reshares = false, $limit = 50, $offset = 0, $expired = false) {
public function getSharesBy($userId, $shareType, $path = null, $reshares = false, $limit = 50, $offset = 0, $allowExpired = false) {
if ($path !== null &&
!($path instanceof \OCP\Files\File) &&
!($path instanceof \OCP\Files\Folder)) {
@ -1158,18 +1158,17 @@ class Manager implements IManager {
$shares = $provider->getSharesBy($userId, $shareType, $path, $reshares, $limit, $offset);
/*
/**
* Work around so we don't return expired shares but still follow
* proper pagination.
*/
$shares2 = [];
while (true) {
$added = 0;
foreach ($shares as $share) {
try {
$this->checkShare($share, $expired);
$this->checkShare($share, $allowExpired);
} catch (ShareNotFound $e) {
// Ignore since this basically means the share is deleted
continue;
@ -1216,7 +1215,7 @@ class Manager implements IManager {
/**
* @inheritdoc
*/
public function getSharedWith($userId, $shareType, $node = null, $limit = 50, $offset = 0, $expired = false) {
public function getSharedWith($userId, $shareType, $node = null, $limit = 50, $offset = 0, $allowExpired = false) {
try {
$provider = $this->factory->getProviderForType($shareType);
} catch (ProviderException $e) {
@ -1228,7 +1227,7 @@ class Manager implements IManager {
// remove all shares which are already expired
foreach ($shares as $key => $share) {
try {
$this->checkShare($share, $expired);
$this->checkShare($share, $allowExpired);
} catch (ShareNotFound $e) {
unset($shares[$key]);
}
@ -1270,7 +1269,7 @@ class Manager implements IManager {
/**
* @inheritdoc
*/
public function getShareById($id, $recipient = null, $expired = false) {
public function getShareById($id, $recipient = null, $allowExpired = false) {
if ($id === null) {
throw new ShareNotFound();
}
@ -1285,7 +1284,7 @@ class Manager implements IManager {
$share = $provider->getShareById($id, $recipient);
$this->checkShare($share, $expired);
$this->checkShare($share, $allowExpired);
return $share;
}
@ -1311,7 +1310,7 @@ class Manager implements IManager {
*
* @throws ShareNotFound
*/
public function getShareByToken($token, $expired = false) {
public function getShareByToken($token, $allowExpired = false) {
// tokens cannot be valid local user names
if ($this->userManager->userExists($token)) {
throw new ShareNotFound();
@ -1369,7 +1368,7 @@ class Manager implements IManager {
throw new ShareNotFound($this->l->t('The requested share does not exist anymore'));
}
$this->checkShare($share, $expired);
$this->checkShare($share, $allowExpired);
/*
* Reduce the permissions for link or email shares if public upload is not enabled
@ -1385,6 +1384,8 @@ class Manager implements IManager {
/**
* Check expire date and disabled owner
*
* @param IShare $share
* @param bool $allowExpired will throw if share is expired and $allowExpired is false
* @throws ShareNotFound
*/
protected function checkShare(IShare $share, $allowExpired = false): void {

View file

@ -127,10 +127,12 @@ interface IManager {
* @param bool $reshares
* @param int $limit The maximum number of returned results, -1 for all results
* @param int $offset
* @param bool $allowExpired also return expired shares
* @return IShare[]
* @since 9.0.0
* @since 31.0.0 Added $allowExpired parameter
*/
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, $allowExpired = false);
/**
* Get shares shared with $user.

View file

@ -147,8 +147,9 @@
<errorLevel type="suppress">
<!-- Helper classes for sharing API integration from other apps -->
<referencedClass name="OCA\Deck\Sharing\ShareAPIHelper" />
<referencedClass name="OCA\Talk\Share\Helper\DeletedShareAPIController" />
<referencedClass name="OCA\GlobalSiteSelector\Service\SlaveService"/>
<referencedClass name="OCA\Talk\Share\Helper\DeletedShareAPIController" />
<referencedClass name="OCA\Talk\Share\Helper\ShareAPIController" />
</errorLevel>
</UndefinedDocblockClass>
<AmbiguousConstantInheritance>

View file

@ -2951,13 +2951,13 @@ class ManagerTest extends \Test\TestCase {
public function testGetShareByTokenExpired(): void {
$this->expectException(\OCP\Share\Exceptions\ShareNotFound::class);
$this->expectExceptionMessage('The requested share does not exist anymore');
$this->expectExceptionMessage('The requested share has expired');
$this->config
->expects($this->once())
->expects($this->exactly(2))
->method('getAppValue')
->with('core', 'shareapi_allow_links', 'yes')
->willReturn('yes');
->withConsecutive(['core', 'shareapi_allow_links', 'yes'], ['core', 'shareapi_delete_on_expire', 'yes'])
->willReturnOnConsecutiveCalls('yes', 'yes');
$this->l->expects($this->once())
->method('t')