Compare commits
30 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 3caa1467b1 | |||
| 2c2335b8b4 | |||
| d6eade0119 | |||
| 8d1cb50048 | |||
| b4b328cf61 | |||
| e47195a334 | |||
| 4d00f49757 | |||
| 83d795dd18 | |||
| a6b9483a5f | |||
| 636345bac8 | |||
| 79d4953e64 | |||
| ca8050b94e | |||
| 47b08a07d8 | |||
| 77c070bc93 | |||
| 09c9241b30 | |||
| 711bd2bc6d | |||
| 2a81cba978 | |||
| 6df490942c | |||
| 0b8e7bb4f0 | |||
| 7e264ba58e | |||
| 64f319ab4e | |||
| ec0ed788fa | |||
| 67d1fac6f6 | |||
| af98eed523 | |||
| bfac9e7023 | |||
| 237d5156b6 | |||
| 40c39270c0 | |||
| f5b18dd7fd | |||
| f885d7292f | |||
| e4244c5fc8 |
@@ -23,3 +23,7 @@
|
||||
- [ ] [Backports requested](https://github.com/nextcloud/backportbot/#usage) where applicable (ex: critical bugfixes)
|
||||
- [ ] [Labels added](https://github.com/nextcloud/server/labels) where applicable (ex: bug/enhancement, `3. to review`, feature component)
|
||||
- [ ] [Milestone added](https://github.com/nextcloud/server/milestones) for target branch/version (ex: 32.x for `stable32`)
|
||||
|
||||
## AI (if applicable)
|
||||
|
||||
- [ ] The content of this PR was partly or fully generated using AI
|
||||
|
||||
@@ -77,6 +77,7 @@ return array(
|
||||
'OCA\\DAV\\CalDAV\\Federation\\FederatedCalendarFactory' => $baseDir . '/../lib/CalDAV/Federation/FederatedCalendarFactory.php',
|
||||
'OCA\\DAV\\CalDAV\\Federation\\FederatedCalendarImpl' => $baseDir . '/../lib/CalDAV/Federation/FederatedCalendarImpl.php',
|
||||
'OCA\\DAV\\CalDAV\\Federation\\FederatedCalendarMapper' => $baseDir . '/../lib/CalDAV/Federation/FederatedCalendarMapper.php',
|
||||
'OCA\\DAV\\CalDAV\\Federation\\FederatedCalendarObject' => $baseDir . '/../lib/CalDAV/Federation/FederatedCalendarObject.php',
|
||||
'OCA\\DAV\\CalDAV\\Federation\\FederatedCalendarSyncService' => $baseDir . '/../lib/CalDAV/Federation/FederatedCalendarSyncService.php',
|
||||
'OCA\\DAV\\CalDAV\\Federation\\FederationSharingService' => $baseDir . '/../lib/CalDAV/Federation/FederationSharingService.php',
|
||||
'OCA\\DAV\\CalDAV\\Federation\\Protocol\\CalendarFederationProtocolV1' => $baseDir . '/../lib/CalDAV/Federation/Protocol/CalendarFederationProtocolV1.php',
|
||||
|
||||
@@ -92,6 +92,7 @@ class ComposerStaticInitDAV
|
||||
'OCA\\DAV\\CalDAV\\Federation\\FederatedCalendarFactory' => __DIR__ . '/..' . '/../lib/CalDAV/Federation/FederatedCalendarFactory.php',
|
||||
'OCA\\DAV\\CalDAV\\Federation\\FederatedCalendarImpl' => __DIR__ . '/..' . '/../lib/CalDAV/Federation/FederatedCalendarImpl.php',
|
||||
'OCA\\DAV\\CalDAV\\Federation\\FederatedCalendarMapper' => __DIR__ . '/..' . '/../lib/CalDAV/Federation/FederatedCalendarMapper.php',
|
||||
'OCA\\DAV\\CalDAV\\Federation\\FederatedCalendarObject' => __DIR__ . '/..' . '/../lib/CalDAV/Federation/FederatedCalendarObject.php',
|
||||
'OCA\\DAV\\CalDAV\\Federation\\FederatedCalendarSyncService' => __DIR__ . '/..' . '/../lib/CalDAV/Federation/FederatedCalendarSyncService.php',
|
||||
'OCA\\DAV\\CalDAV\\Federation\\FederationSharingService' => __DIR__ . '/..' . '/../lib/CalDAV/Federation/FederationSharingService.php',
|
||||
'OCA\\DAV\\CalDAV\\Federation\\Protocol\\CalendarFederationProtocolV1' => __DIR__ . '/..' . '/../lib/CalDAV/Federation/Protocol/CalendarFederationProtocolV1.php',
|
||||
|
||||
@@ -43,9 +43,9 @@ class CalendarProvider implements ICalendarProvider {
|
||||
});
|
||||
}
|
||||
|
||||
$additionalProperties = $this->getAdditionalPropertiesForCalendars($calendarInfos);
|
||||
$iCalendars = [];
|
||||
|
||||
$additionalProperties = $this->getAdditionalPropertiesForCalendars($calendarInfos);
|
||||
foreach ($calendarInfos as $calendarInfo) {
|
||||
$user = str_replace('principals/users/', '', $calendarInfo['principaluri']);
|
||||
$path = 'calendars/' . $user . '/' . $calendarInfo['uri'];
|
||||
@@ -60,9 +60,7 @@ class CalendarProvider implements ICalendarProvider {
|
||||
);
|
||||
}
|
||||
|
||||
$additionalFederatedProps = $this->getAdditionalPropertiesForCalendars(
|
||||
$federatedCalendarInfos,
|
||||
);
|
||||
$additionalFederatedProps = $this->getAdditionalPropertiesForCalendars($federatedCalendarInfos);
|
||||
foreach ($federatedCalendarInfos as $calendarInfo) {
|
||||
$user = str_replace('principals/users/', '', $calendarInfo['principaluri']);
|
||||
$path = 'calendars/' . $user . '/' . $calendarInfo['uri'];
|
||||
|
||||
@@ -104,9 +104,10 @@ class CalendarFederationProvider implements ICloudFederationProvider {
|
||||
);
|
||||
}
|
||||
|
||||
// TODO: implement read-write sharing
|
||||
// convert access to permissions
|
||||
$permissions = match ($access) {
|
||||
DavSharingBackend::ACCESS_READ => Constants::PERMISSION_READ,
|
||||
DavSharingBackend::ACCESS_READ_WRITE => Constants::PERMISSION_READ | Constants::PERMISSION_CREATE | Constants::PERMISSION_UPDATE | Constants::PERMISSION_DELETE,
|
||||
default => throw new ProviderCouldNotAddShareException(
|
||||
"Unsupported access value: $access",
|
||||
'',
|
||||
@@ -122,20 +123,27 @@ class CalendarFederationProvider implements ICloudFederationProvider {
|
||||
$sharedWithPrincipal = 'principals/users/' . $share->getShareWith();
|
||||
|
||||
// Delete existing incoming federated share first
|
||||
$this->federatedCalendarMapper->deleteByUri($sharedWithPrincipal, $calendarUri);
|
||||
$calendar = $this->federatedCalendarMapper->findByUri($sharedWithPrincipal, $calendarUri);
|
||||
|
||||
$calendar = new FederatedCalendarEntity();
|
||||
$calendar->setPrincipaluri($sharedWithPrincipal);
|
||||
$calendar->setUri($calendarUri);
|
||||
$calendar->setRemoteUrl($calendarUrl);
|
||||
$calendar->setDisplayName($displayName);
|
||||
$calendar->setColor($color);
|
||||
$calendar->setToken($share->getShareSecret());
|
||||
$calendar->setSharedBy($share->getSharedBy());
|
||||
$calendar->setSharedByDisplayName($share->getSharedByDisplayName());
|
||||
$calendar->setPermissions($permissions);
|
||||
$calendar->setComponents($components);
|
||||
$calendar = $this->federatedCalendarMapper->insert($calendar);
|
||||
if ($calendar === null) {
|
||||
$calendar = new FederatedCalendarEntity();
|
||||
$calendar->setPrincipaluri($sharedWithPrincipal);
|
||||
$calendar->setUri($calendarUri);
|
||||
$calendar->setRemoteUrl($calendarUrl);
|
||||
$calendar->setDisplayName($displayName);
|
||||
$calendar->setColor($color);
|
||||
$calendar->setToken($share->getShareSecret());
|
||||
$calendar->setSharedBy($share->getSharedBy());
|
||||
$calendar->setSharedByDisplayName($share->getSharedByDisplayName());
|
||||
$calendar->setPermissions($permissions);
|
||||
$calendar->setComponents($components);
|
||||
$calendar = $this->federatedCalendarMapper->insert($calendar);
|
||||
} else {
|
||||
$calendar->setToken($share->getShareSecret());
|
||||
$calendar->setPermissions($permissions);
|
||||
$calendar->setComponents($components);
|
||||
$this->federatedCalendarMapper->update($calendar);
|
||||
}
|
||||
|
||||
$this->jobList->add(FederatedCalendarSyncJob::class, [
|
||||
FederatedCalendarSyncJob::ARGUMENT_ID => $calendar->getId(),
|
||||
|
||||
@@ -10,29 +10,289 @@ declare(strict_types=1);
|
||||
namespace OCA\DAV\CalDAV\Federation;
|
||||
|
||||
use OCA\DAV\CalDAV\CalDavBackend;
|
||||
use OCA\DAV\CalDAV\Calendar;
|
||||
use OCP\IConfig;
|
||||
use OCP\IL10N;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Sabre\CalDAV\Backend;
|
||||
use OCP\Constants;
|
||||
use Sabre\CalDAV\ICalendar;
|
||||
use Sabre\CalDAV\Plugin;
|
||||
use Sabre\CalDAV\Xml\Property\SupportedCalendarComponentSet;
|
||||
use Sabre\DAV\Exception\MethodNotAllowed;
|
||||
use Sabre\DAV\Exception\NotFound;
|
||||
use Sabre\DAV\IMultiGet;
|
||||
use Sabre\DAV\INode;
|
||||
use Sabre\DAV\IProperties;
|
||||
use Sabre\DAV\PropPatch;
|
||||
|
||||
class FederatedCalendar implements ICalendar, IProperties, IMultiGet {
|
||||
|
||||
private const CALENDAR_TYPE = CalDavBackend::CALENDAR_TYPE_FEDERATED;
|
||||
private const DAV_PROPERTY_CALENDAR_LABEL = '{DAV:}displayname';
|
||||
private const DAV_PROPERTY_CALENDAR_COLOR = '{http://apple.com/ns/ical/}calendar-color';
|
||||
|
||||
private string $principalUri;
|
||||
private string $calendarUri;
|
||||
private ?array $calendarACL = null;
|
||||
private FederatedCalendarEntity $federationInfo;
|
||||
|
||||
class FederatedCalendar extends Calendar {
|
||||
public function __construct(
|
||||
Backend\BackendInterface $caldavBackend,
|
||||
$calendarInfo,
|
||||
IL10N $l10n,
|
||||
IConfig $config,
|
||||
LoggerInterface $logger,
|
||||
private readonly FederatedCalendarMapper $federatedCalendarMapper,
|
||||
private readonly FederatedCalendarSyncService $federatedCalendarService,
|
||||
private readonly CalDavBackend $caldavBackend,
|
||||
$calendarInfo,
|
||||
) {
|
||||
parent::__construct($caldavBackend, $calendarInfo, $l10n, $config, $logger);
|
||||
$this->principalUri = $calendarInfo['principaluri'];
|
||||
$this->calendarUri = $calendarInfo['uri'];
|
||||
$this->federationInfo = $federatedCalendarMapper->findByUri($this->principalUri, $this->calendarUri);
|
||||
}
|
||||
|
||||
public function delete() {
|
||||
$this->federatedCalendarMapper->deleteById($this->getResourceId());
|
||||
public function getResourceId(): int {
|
||||
return $this->federationInfo->getId();
|
||||
}
|
||||
|
||||
public function getName(): string {
|
||||
return $this->federationInfo->getUri();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name Name of the file
|
||||
*/
|
||||
public function setName($name): void {
|
||||
throw new MethodNotAllowed('Renaming federated calendars is not allowed');
|
||||
}
|
||||
|
||||
protected function getCalendarType(): int {
|
||||
return CalDavBackend::CALENDAR_TYPE_FEDERATED;
|
||||
return self::CALENDAR_TYPE;
|
||||
}
|
||||
|
||||
public function getPrincipalURI(): string {
|
||||
return $this->federationInfo->getPrincipaluri();
|
||||
}
|
||||
|
||||
public function getOwner(): ?string {
|
||||
return $this->federationInfo->getSharedByPrincipal();
|
||||
}
|
||||
|
||||
public function getGroup(): ?string {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<array-key, mixed>
|
||||
*/
|
||||
public function getACL(): array {
|
||||
if ($this->calendarACL !== null) {
|
||||
return $this->calendarACL;
|
||||
}
|
||||
|
||||
$permissions = $this->federationInfo->getPermissions();
|
||||
// default permission
|
||||
$acl = [
|
||||
// read object permission
|
||||
[
|
||||
'privilege' => '{DAV:}read',
|
||||
'principal' => $this->principalUri,
|
||||
'protected' => true,
|
||||
],
|
||||
// read acl permission
|
||||
[
|
||||
'privilege' => '{DAV:}read-acl',
|
||||
'principal' => $this->principalUri,
|
||||
'protected' => true,
|
||||
],
|
||||
// write properties permission (calendar name, color)
|
||||
[
|
||||
'privilege' => '{DAV:}write-properties',
|
||||
'principal' => $this->principalUri,
|
||||
'protected' => true,
|
||||
],
|
||||
];
|
||||
// create permission
|
||||
if ($permissions & Constants::PERMISSION_CREATE) {
|
||||
$acl[] = [
|
||||
'privilege' => '{DAV:}bind',
|
||||
'principal' => $this->principalUri,
|
||||
'protected' => true,
|
||||
];
|
||||
}
|
||||
// update permission
|
||||
if ($permissions & Constants::PERMISSION_UPDATE) {
|
||||
$acl[] = [
|
||||
'privilege' => '{DAV:}write-content',
|
||||
'principal' => $this->principalUri,
|
||||
'protected' => true,
|
||||
];
|
||||
}
|
||||
// delete permission
|
||||
if ($permissions & Constants::PERMISSION_DELETE) {
|
||||
$acl[] = [
|
||||
'privilege' => '{DAV:}unbind',
|
||||
'principal' => $this->principalUri,
|
||||
'protected' => true,
|
||||
];
|
||||
}
|
||||
|
||||
// cache the calculated ACL for later use
|
||||
$this->calendarACL = $acl;
|
||||
|
||||
return $acl;
|
||||
}
|
||||
|
||||
public function setACL(array $acl): void {
|
||||
throw new MethodNotAllowed('Changing ACLs on federated calendars is not allowed');
|
||||
}
|
||||
|
||||
public function getSupportedPrivilegeSet(): ?array {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed> properties array, with property name as key
|
||||
*/
|
||||
public function getProperties($properties): array {
|
||||
return [
|
||||
self::DAV_PROPERTY_CALENDAR_LABEL => $this->federationInfo->getDisplayName(),
|
||||
self::DAV_PROPERTY_CALENDAR_COLOR => $this->federationInfo->getColor(),
|
||||
'{' . Plugin::NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet(explode(',', $this->federationInfo->getComponents())),
|
||||
];
|
||||
}
|
||||
|
||||
public function propPatch(PropPatch $propPatch): void {
|
||||
$mutations = $propPatch->getMutations();
|
||||
if (count($mutations) > 0) {
|
||||
// evaluate if name was changed
|
||||
if (isset($mutations[self::DAV_PROPERTY_CALENDAR_LABEL])) {
|
||||
$this->federationInfo->setDisplayName($mutations[self::DAV_PROPERTY_CALENDAR_LABEL]);
|
||||
$propPatch->setResultCode(self::DAV_PROPERTY_CALENDAR_LABEL, 200);
|
||||
}
|
||||
// evaluate if color was changed
|
||||
if (isset($mutations[self::DAV_PROPERTY_CALENDAR_COLOR])) {
|
||||
$this->federationInfo->setColor($mutations[self::DAV_PROPERTY_CALENDAR_COLOR]);
|
||||
$propPatch->setResultCode(self::DAV_PROPERTY_CALENDAR_COLOR, 200);
|
||||
}
|
||||
$this->federatedCalendarMapper->update($this->federationInfo);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public function getChildACL(): array {
|
||||
return $this->getACL();
|
||||
}
|
||||
|
||||
public function getLastModified(): ?int {
|
||||
return $this->federationInfo->getLastSync();
|
||||
}
|
||||
|
||||
public function delete(): void {
|
||||
$this->federatedCalendarMapper->deleteById($this->getResourceId());
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name Name of the file
|
||||
*/
|
||||
public function createDirectory($name): void {
|
||||
throw new MethodNotAllowed('Creating nested collection is not allowed');
|
||||
}
|
||||
|
||||
public function calendarQuery(array $filters): array {
|
||||
$uris = $this->caldavBackend->calendarQuery($this->federationInfo->getId(), $filters, $this->getCalendarType());
|
||||
return $uris;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name Name of the file
|
||||
*/
|
||||
public function getChild($name): INode {
|
||||
$obj = $this->caldavBackend->getCalendarObject($this->federationInfo->getId(), $name, $this->getCalendarType());
|
||||
|
||||
if ($obj === null) {
|
||||
throw new NotFound('Calendar object not found');
|
||||
}
|
||||
|
||||
return new FederatedCalendarObject($this, $obj);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<INode>
|
||||
*/
|
||||
public function getChildren(): array {
|
||||
$objs = $this->caldavBackend->getCalendarObjects($this->federationInfo->getId(), $this->getCalendarType());
|
||||
|
||||
$children = [];
|
||||
foreach ($objs as $obj) {
|
||||
$children[] = new FederatedCalendarObject($this, $obj);
|
||||
}
|
||||
|
||||
return $children;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string> $paths Names of the files
|
||||
*
|
||||
* @return array<INode>
|
||||
*/
|
||||
public function getMultipleChildren(array $paths): array {
|
||||
$objs = $this->caldavBackend->getMultipleCalendarObjects($this->federationInfo->getId(), $paths, $this->getCalendarType());
|
||||
|
||||
$children = [];
|
||||
foreach ($objs as $obj) {
|
||||
$children[] = new FederatedCalendarObject($this, $obj);
|
||||
}
|
||||
|
||||
return $children;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name Name of the file
|
||||
*/
|
||||
public function childExists($name): bool {
|
||||
$obj = $this->caldavBackend->getCalendarObject($this->federationInfo->getId(), $name, $this->getCalendarType());
|
||||
return $obj !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name Name of the file
|
||||
* @param resource|string $data Initial payload
|
||||
*/
|
||||
public function createFile($name, $data = null): string {
|
||||
if (is_resource($data)) {
|
||||
$data = stream_get_contents($data);
|
||||
}
|
||||
|
||||
// Create on remote server first
|
||||
$etag = $this->federatedCalendarService->createCalendarObject($this->federationInfo, $name, $data);
|
||||
|
||||
if (empty($etag)) {
|
||||
throw new \Exception('Failed to create calendar object on remote server');
|
||||
}
|
||||
|
||||
// Then store locally
|
||||
return $this->caldavBackend->createCalendarObject($this->federationInfo->getId(), $name, $data, $this->getCalendarType());
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name Name of the file
|
||||
* @param resource|string $data Initial payload
|
||||
*/
|
||||
public function updateFile($name, $data): string {
|
||||
if (is_resource($data)) {
|
||||
$data = stream_get_contents($data);
|
||||
}
|
||||
|
||||
// Update remote calendar first
|
||||
$etag = $this->federatedCalendarService->updateCalendarObject($this->federationInfo, $name, $data);
|
||||
|
||||
if (empty($etag)) {
|
||||
throw new \Exception('Failed to update calendar object on remote server');
|
||||
}
|
||||
|
||||
// Then update locally
|
||||
return $this->caldavBackend->updateCalendarObject($this->federationInfo->getId(), $name, $data, $this->getCalendarType());
|
||||
}
|
||||
|
||||
public function deleteFile(string $name): void {
|
||||
// Delete from remote server first
|
||||
$this->federatedCalendarService->deleteCalendarObject($this->federationInfo, $name);
|
||||
|
||||
// Then delete locally
|
||||
$this->caldavBackend->deleteCalendarObject($this->federationInfo->getId(), $name, $this->getCalendarType());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -94,8 +94,8 @@ class FederatedCalendarEntity extends Entity {
|
||||
'{' . \Sabre\CalDAV\Plugin::NS_CALENDARSERVER . '}getctag' => $this->getSyncTokenForSabre(),
|
||||
'{' . \Sabre\CalDAV\Plugin::NS_CALDAV . '}supported-calendar-component-set' => $this->getSupportedCalendarComponentSet(),
|
||||
'{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}owner-principal' => $this->getSharedByPrincipal(),
|
||||
// TODO: implement read-write sharing
|
||||
'{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}read-only' => 1
|
||||
'{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}read-only' => ($this->getPermissions() & \OCP\Constants::PERMISSION_UPDATE) === 0 ? 1 : 0,
|
||||
'{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}permissions' => $this->getPermissions(),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,34 +9,23 @@ declare(strict_types=1);
|
||||
|
||||
namespace OCA\DAV\CalDAV\Federation;
|
||||
|
||||
use OCA\DAV\AppInfo\Application;
|
||||
use OCA\DAV\CalDAV\CalDavBackend;
|
||||
use OCP\IConfig;
|
||||
use OCP\IL10N;
|
||||
use OCP\L10N\IFactory as IL10NFactory;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
class FederatedCalendarFactory {
|
||||
private readonly IL10N $l10n;
|
||||
|
||||
public function __construct(
|
||||
private readonly CalDavBackend $caldavBackend,
|
||||
private readonly IConfig $config,
|
||||
private readonly LoggerInterface $logger,
|
||||
private readonly FederatedCalendarMapper $federatedCalendarMapper,
|
||||
IL10NFactory $l10nFactory,
|
||||
private readonly FederatedCalendarSyncService $federatedCalendarService,
|
||||
private readonly CalDavBackend $caldavBackend,
|
||||
) {
|
||||
$this->l10n = $l10nFactory->get(Application::APP_ID);
|
||||
}
|
||||
|
||||
public function createFederatedCalendar(array $calendarInfo): FederatedCalendar {
|
||||
return new FederatedCalendar(
|
||||
$this->federatedCalendarMapper,
|
||||
$this->federatedCalendarService,
|
||||
$this->caldavBackend,
|
||||
$calendarInfo,
|
||||
$this->l10n,
|
||||
$this->config,
|
||||
$this->logger,
|
||||
$this->federatedCalendarMapper,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,8 +51,7 @@ class FederatedCalendarImpl implements ICalendar, ICalendarIsShared, ICalendarIs
|
||||
}
|
||||
|
||||
public function getPermissions(): int {
|
||||
// TODO: implement read-write sharing
|
||||
return Constants::PERMISSION_READ;
|
||||
return $this->calendarInfo['{http://owncloud.org/ns}permissions'] ?? Constants::PERMISSION_READ;
|
||||
}
|
||||
|
||||
public function isDeleted(): bool {
|
||||
@@ -64,7 +63,8 @@ class FederatedCalendarImpl implements ICalendar, ICalendarIsShared, ICalendarIs
|
||||
}
|
||||
|
||||
public function isWritable(): bool {
|
||||
return false;
|
||||
$permissions = $this->getPermissions();
|
||||
return ($permissions & Constants::PERMISSION_UPDATE) !== 0;
|
||||
}
|
||||
|
||||
public function isEnabled(): bool {
|
||||
|
||||
@@ -0,0 +1,107 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
namespace OCA\DAV\CalDAV\Federation;
|
||||
|
||||
use Sabre\CalDAV\ICalendarObject;
|
||||
use Sabre\DAV\Exception\MethodNotAllowed;
|
||||
use Sabre\DAVACL\IACL;
|
||||
|
||||
class FederatedCalendarObject implements ICalendarObject, IACL {
|
||||
|
||||
public function __construct(
|
||||
protected FederatedCalendar $calendarObject,
|
||||
protected $objectData,
|
||||
) {
|
||||
}
|
||||
|
||||
public function getName(): string {
|
||||
return $this->objectData['uri'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name Name of the file
|
||||
*/
|
||||
public function setName($name) {
|
||||
throw new \Exception('Not implemented');
|
||||
}
|
||||
|
||||
public function get(): string {
|
||||
return $this->objectData['calendardata'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param resource|string $data contents of the file
|
||||
*/
|
||||
public function put($data): string {
|
||||
|
||||
$etag = $this->calendarObject->updateFile($this->objectData['uri'], $data);
|
||||
$this->objectData['calendardata'] = $data;
|
||||
$this->objectData['etag'] = $etag;
|
||||
|
||||
return $etag;
|
||||
}
|
||||
|
||||
public function delete(): void {
|
||||
$this->calendarObject->deleteFile($this->objectData['uri']);
|
||||
}
|
||||
|
||||
public function getContentType(): ?string {
|
||||
$mime = 'text/calendar; charset=utf-8';
|
||||
if (isset($this->objectData['component']) && $this->objectData['component']) {
|
||||
$mime .= '; component=' . $this->objectData['component'];
|
||||
}
|
||||
|
||||
return $mime;
|
||||
}
|
||||
|
||||
public function getETag(): string {
|
||||
if (isset($this->objectData['etag'])) {
|
||||
return $this->objectData['etag'];
|
||||
} else {
|
||||
return '"' . md5($this->get()) . '"';
|
||||
}
|
||||
}
|
||||
|
||||
public function getLastModified(): int {
|
||||
return $this->objectData['lastmodified'];
|
||||
}
|
||||
|
||||
public function getSize(): int {
|
||||
if (isset($this->objectData['size'])) {
|
||||
return $this->objectData['size'];
|
||||
} else {
|
||||
return strlen($this->get());
|
||||
}
|
||||
}
|
||||
|
||||
public function getOwner(): ?string {
|
||||
return $this->calendarObject->getPrincipalURI();
|
||||
}
|
||||
|
||||
public function getGroup(): ?string {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<array-key, mixed>
|
||||
*/
|
||||
public function getACL(): array {
|
||||
return $this->calendarObject->getACL();
|
||||
}
|
||||
|
||||
public function setACL(array $acl): void {
|
||||
throw new MethodNotAllowed('Changing ACLs on federated events is not allowed');
|
||||
}
|
||||
|
||||
public function getSupportedPrivilegeSet(): ?array {
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -9,20 +9,52 @@ declare(strict_types=1);
|
||||
|
||||
namespace OCA\DAV\CalDAV\Federation;
|
||||
|
||||
use OCA\DAV\CalDAV\SyncService as CalDavSyncService;
|
||||
use OCA\DAV\CalDAV\CalDavBackend;
|
||||
use OCA\DAV\Service\ASyncService;
|
||||
use OCP\AppFramework\Db\TTransactional;
|
||||
use OCP\AppFramework\Http;
|
||||
use OCP\Federation\ICloudIdManager;
|
||||
use OCP\Http\Client\IClientService;
|
||||
use OCP\IConfig;
|
||||
use OCP\IDBConnection;
|
||||
use Psr\Http\Client\ClientExceptionInterface;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
class FederatedCalendarSyncService {
|
||||
class FederatedCalendarSyncService extends ASyncService {
|
||||
use TTransactional;
|
||||
|
||||
private const SYNC_TOKEN_PREFIX = 'http://sabre.io/ns/sync/';
|
||||
|
||||
public function __construct(
|
||||
IClientService $clientService,
|
||||
IConfig $config,
|
||||
private readonly FederatedCalendarMapper $federatedCalendarMapper,
|
||||
private readonly LoggerInterface $logger,
|
||||
private readonly CalDavSyncService $syncService,
|
||||
private readonly CalDavBackend $backend,
|
||||
private readonly IDBConnection $dbConnection,
|
||||
private readonly ICloudIdManager $cloudIdManager,
|
||||
) {
|
||||
parent::__construct($clientService, $config);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract and encode credentials from a federated calendar entity.
|
||||
*
|
||||
* @return array{username: string, remoteUrl: string, token: string}
|
||||
*/
|
||||
private function getCredentials(FederatedCalendarEntity $calendar): array {
|
||||
[,, $sharedWith] = explode('/', $calendar->getPrincipaluri());
|
||||
$calDavUser = $this->cloudIdManager->getCloudId($sharedWith, null)->getId();
|
||||
|
||||
// Need to encode the cloud id as it might contain a colon which is not allowed in basic
|
||||
// auth according to RFC 7617
|
||||
$calDavUser = base64_encode($calDavUser);
|
||||
|
||||
return [
|
||||
'username' => $calDavUser,
|
||||
'remoteUrl' => $calendar->getRemoteUrl(),
|
||||
'token' => $calendar->getToken(),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -31,29 +63,77 @@ class FederatedCalendarSyncService {
|
||||
* @throws ClientExceptionInterface If syncing the calendar fails.
|
||||
*/
|
||||
public function syncOne(FederatedCalendarEntity $calendar): int {
|
||||
[,, $sharedWith] = explode('/', $calendar->getPrincipaluri());
|
||||
$calDavUser = $this->cloudIdManager->getCloudId($sharedWith, null)->getId();
|
||||
$remoteUrl = $calendar->getRemoteUrl();
|
||||
$credentials = $this->getCredentials($calendar);
|
||||
$syncToken = $calendar->getSyncTokenForSabre();
|
||||
|
||||
// Need to encode the cloud id as it might contain a colon which is not allowed in basic
|
||||
// auth according to RFC 7617
|
||||
$calDavUser = base64_encode($calDavUser);
|
||||
try {
|
||||
$response = $this->requestSyncReport(
|
||||
$credentials['remoteUrl'],
|
||||
$credentials['username'],
|
||||
$credentials['token'],
|
||||
$syncToken,
|
||||
);
|
||||
} catch (ClientExceptionInterface $ex) {
|
||||
if ($ex->getCode() === Http::STATUS_UNAUTHORIZED) {
|
||||
// Remote server revoked access to the calendar => remove it
|
||||
$this->federatedCalendarMapper->delete($calendar);
|
||||
$this->logger->warning("Authorization failed, remove federated calendar: {$credentials['remoteUrl']}", [
|
||||
'app' => 'dav',
|
||||
]);
|
||||
return 0;
|
||||
}
|
||||
$this->logger->error('Client exception:', ['app' => 'dav', 'exception' => $ex]);
|
||||
throw $ex;
|
||||
}
|
||||
|
||||
$syncResponse = $this->syncService->syncRemoteCalendar(
|
||||
$remoteUrl,
|
||||
$calDavUser,
|
||||
$calendar->getToken(),
|
||||
$syncToken,
|
||||
$calendar,
|
||||
);
|
||||
// Process changes from remote
|
||||
$downloadedEvents = 0;
|
||||
foreach ($response['response'] as $resource => $status) {
|
||||
$objectUri = basename($resource);
|
||||
if (isset($status[200])) {
|
||||
// Object created or updated
|
||||
$absoluteUrl = $this->prepareUri($credentials['remoteUrl'], $resource);
|
||||
$calendarData = $this->download($absoluteUrl, $credentials['username'], $credentials['token']);
|
||||
$this->atomic(function () use ($calendar, $objectUri, $calendarData): void {
|
||||
$existingObject = $this->backend->getCalendarObject(
|
||||
$calendar->getId(),
|
||||
$objectUri,
|
||||
CalDavBackend::CALENDAR_TYPE_FEDERATED
|
||||
);
|
||||
if (!$existingObject) {
|
||||
$this->backend->createCalendarObject(
|
||||
$calendar->getId(),
|
||||
$objectUri,
|
||||
$calendarData,
|
||||
CalDavBackend::CALENDAR_TYPE_FEDERATED
|
||||
);
|
||||
} else {
|
||||
$this->backend->updateCalendarObject(
|
||||
$calendar->getId(),
|
||||
$objectUri,
|
||||
$calendarData,
|
||||
CalDavBackend::CALENDAR_TYPE_FEDERATED
|
||||
);
|
||||
}
|
||||
}, $this->dbConnection);
|
||||
$downloadedEvents++;
|
||||
} else {
|
||||
// Object deleted
|
||||
$this->backend->deleteCalendarObject(
|
||||
$calendar->getId(),
|
||||
$objectUri,
|
||||
CalDavBackend::CALENDAR_TYPE_FEDERATED,
|
||||
true
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
$newSyncToken = $syncResponse->getSyncToken();
|
||||
$newSyncToken = $response['token'];
|
||||
|
||||
// Check sync token format and extract the actual sync token integer
|
||||
$matches = [];
|
||||
if (!preg_match('/^http:\/\/sabre\.io\/ns\/sync\/([0-9]+)$/', $newSyncToken, $matches)) {
|
||||
$this->logger->error("Failed to sync federated calendar at $remoteUrl: New sync token has unexpected format: $newSyncToken", [
|
||||
$this->logger->error("Failed to sync federated calendar at {$credentials['remoteUrl']}: New sync token has unexpected format: $newSyncToken", [
|
||||
'calendar' => $calendar->toCalendarInfo(),
|
||||
'newSyncToken' => $newSyncToken,
|
||||
]);
|
||||
@@ -67,10 +147,58 @@ class FederatedCalendarSyncService {
|
||||
$newSyncToken,
|
||||
);
|
||||
} else {
|
||||
$this->logger->debug("Sync Token for $remoteUrl unchanged from previous sync");
|
||||
$this->logger->debug("Sync Token for {$credentials['remoteUrl']} unchanged from previous sync");
|
||||
$this->federatedCalendarMapper->updateSyncTime($calendar->getId());
|
||||
}
|
||||
|
||||
return $syncResponse->getDownloadedEvents();
|
||||
return $downloadedEvents;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a calendar object on the remote server.
|
||||
*
|
||||
* @throws ClientExceptionInterface If the remote request fails.
|
||||
*/
|
||||
public function createCalendarObject(FederatedCalendarEntity $calendar, string $name, string $data): string {
|
||||
$credentials = $this->getCredentials($calendar);
|
||||
$objectUrl = $this->prepareUri($credentials['remoteUrl'], $name);
|
||||
|
||||
return $this->requestPut(
|
||||
$objectUrl,
|
||||
$credentials['username'],
|
||||
$credentials['token'],
|
||||
$data,
|
||||
'text/calendar; charset=utf-8'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a calendar object on the remote server.
|
||||
*
|
||||
* @throws ClientExceptionInterface If the remote request fails.
|
||||
*/
|
||||
public function updateCalendarObject(FederatedCalendarEntity $calendar, string $name, string $data): string {
|
||||
$credentials = $this->getCredentials($calendar);
|
||||
$objectUrl = $this->prepareUri($credentials['remoteUrl'], $name);
|
||||
|
||||
return $this->requestPut(
|
||||
$objectUrl,
|
||||
$credentials['username'],
|
||||
$credentials['token'],
|
||||
$data,
|
||||
'text/calendar; charset=utf-8'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a calendar object on the remote server.
|
||||
*
|
||||
* @throws ClientExceptionInterface If the remote request fails.
|
||||
*/
|
||||
public function deleteCalendarObject(FederatedCalendarEntity $calendar, string $name): void {
|
||||
$credentials = $this->getCredentials($calendar);
|
||||
$objectUrl = $this->prepareUri($credentials['remoteUrl'], $name);
|
||||
|
||||
$this->requestDelete($objectUrl, $credentials['username'], $credentials['token']);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ use OCA\DAV\CalDAV\Calendar;
|
||||
use OCA\DAV\CalDAV\CalendarHome;
|
||||
use OCA\DAV\CalDAV\CalendarObject;
|
||||
use OCA\DAV\CalDAV\DefaultCalendarValidator;
|
||||
use OCA\DAV\CalDAV\Federation\FederatedCalendar;
|
||||
use OCA\DAV\CalDAV\TipBroker;
|
||||
use OCP\IConfig;
|
||||
use Psr\Log\LoggerInterface;
|
||||
@@ -173,8 +174,15 @@ class Plugin extends \Sabre\CalDAV\Schedule\Plugin {
|
||||
return;
|
||||
}
|
||||
|
||||
/** @var Calendar $calendarNode */
|
||||
/** @var Calendar&ICalendar $calendarNode */
|
||||
$calendarNode = $this->server->tree->getNodeForPath($calendarPath);
|
||||
|
||||
// abort if calendar is federated
|
||||
if ($calendarNode instanceof FederatedCalendar) {
|
||||
$this->logger->debug('Not processing scheduling for federated calendar at path: ' . $calendarPath);
|
||||
return;
|
||||
}
|
||||
|
||||
// extract addresses for owner
|
||||
$addresses = $this->getAddressesForPrincipal($calendarNode->getOwner());
|
||||
// determine if request is from a sharee
|
||||
|
||||
@@ -15,7 +15,6 @@ use OCP\Files\Node;
|
||||
use OCP\Files\NotFoundException;
|
||||
use OCP\Files\Storage\ISharedStorage;
|
||||
use OCP\IUserSession;
|
||||
use OCP\PaginationParameters;
|
||||
use OCP\Share\IManager;
|
||||
use OCP\Share\IShare;
|
||||
use Sabre\DAV\Exception\NotFound;
|
||||
@@ -80,9 +79,10 @@ class SharesPlugin extends \Sabre\DAV\ServerPlugin {
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list<IShare>
|
||||
* @param Node $node
|
||||
* @return IShare[]
|
||||
*/
|
||||
private function getShare(Node $node, PaginationParameters $paginationParameters): array {
|
||||
private function getShare(Node $node): array {
|
||||
$result = [];
|
||||
$requestedShareTypes = [
|
||||
IShare::TYPE_USER,
|
||||
@@ -95,23 +95,26 @@ class SharesPlugin extends \Sabre\DAV\ServerPlugin {
|
||||
IShare::TYPE_DECK,
|
||||
];
|
||||
|
||||
$result[] = $this->shareManager->getAllSharesBy(
|
||||
$this->userId,
|
||||
$node,
|
||||
$paginationParameters,
|
||||
false,
|
||||
);
|
||||
|
||||
// Also check for shares where the user is the recipient
|
||||
try {
|
||||
$result[] = $this->shareManager->getAllSharedWith(
|
||||
foreach ($requestedShareTypes as $requestedShareType) {
|
||||
$result[] = $this->shareManager->getSharesBy(
|
||||
$this->userId,
|
||||
$requestedShareTypes,
|
||||
$requestedShareType,
|
||||
$node,
|
||||
$paginationParameters,
|
||||
false,
|
||||
-1
|
||||
);
|
||||
} catch (BackendError $e) {
|
||||
// ignore
|
||||
|
||||
// Also check for shares where the user is the recipient
|
||||
try {
|
||||
$result[] = $this->shareManager->getSharedWith(
|
||||
$this->userId,
|
||||
$requestedShareType,
|
||||
$node,
|
||||
-1
|
||||
);
|
||||
} catch (BackendError $e) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
return array_merge(...$result);
|
||||
@@ -152,7 +155,7 @@ class SharesPlugin extends \Sabre\DAV\ServerPlugin {
|
||||
return [];
|
||||
}
|
||||
|
||||
$shares = $this->getShare($node, new PaginationParameters(limit: null));
|
||||
$shares = $this->getShare($node);
|
||||
$this->cachedShares[$sabreNode->getId()] = $shares;
|
||||
return $shares;
|
||||
}
|
||||
@@ -232,9 +235,9 @@ class SharesPlugin extends \Sabre\DAV\ServerPlugin {
|
||||
return true;
|
||||
}
|
||||
|
||||
$targetShares = $this->getShare($targetNode->getNode(), new PaginationParameters(limit: null));
|
||||
$targetShares = $this->getShare($targetNode->getNode());
|
||||
if (empty($targetShares)) {
|
||||
// Target is not a share so no re-sharing in-progress
|
||||
// Target is not a share so no re-sharing inprogress
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -250,7 +253,7 @@ class SharesPlugin extends \Sabre\DAV\ServerPlugin {
|
||||
}
|
||||
}
|
||||
|
||||
// if the share recipient is allowed to delete from the share, they are allowed to move the file out of the share
|
||||
// if the share recipient is allow to delete from the share, they are allowed to move the file out of the share
|
||||
// the user moving the file out of the share to their home storage would give them share permissions and allow moving into the share
|
||||
//
|
||||
// since the 2-step move is allowed, we also allow both steps at once
|
||||
|
||||
@@ -191,4 +191,70 @@ abstract class ASyncService {
|
||||
rtrim($responseUri, '/'),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Push data to the remote server via HTTP PUT.
|
||||
* Used for creating or updating CalDAV/CardDAV objects.
|
||||
*
|
||||
* @param string $url The absolute URL to PUT to
|
||||
* @param string $username The username for authentication
|
||||
* @param string $token The authentication token/password
|
||||
* @param string $data The data to upload
|
||||
* @param string $contentType The Content-Type header (e.g., 'text/calendar' or 'text/vcard')
|
||||
*
|
||||
* @return string The ETag returned by the server
|
||||
*/
|
||||
protected function requestPut(
|
||||
string $url,
|
||||
string $username,
|
||||
string $token,
|
||||
string $data,
|
||||
string $contentType = 'text/calendar; charset=utf-8',
|
||||
): string {
|
||||
$client = $this->getClient();
|
||||
|
||||
$options = [
|
||||
'auth' => [$username, $token],
|
||||
'body' => $data,
|
||||
'headers' => [
|
||||
'Content-Type' => $contentType,
|
||||
],
|
||||
'verify' => !$this->config->getSystemValue(
|
||||
'sharing.federation.allowSelfSignedCertificates',
|
||||
false,
|
||||
),
|
||||
];
|
||||
|
||||
$response = $client->put($url, $options);
|
||||
|
||||
// Extract and return the ETag from the response
|
||||
$etag = $response->getHeader('ETag');
|
||||
return $etag;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a resource from the remote server via HTTP DELETE.
|
||||
* Used for deleting CalDAV/CardDAV objects.
|
||||
*
|
||||
* @param string $url The absolute URL to DELETE
|
||||
* @param string $username The username for authentication
|
||||
* @param string $token The authentication token/password
|
||||
*/
|
||||
protected function requestDelete(
|
||||
string $url,
|
||||
string $username,
|
||||
string $token,
|
||||
): void {
|
||||
$client = $this->getClient();
|
||||
|
||||
$options = [
|
||||
'auth' => [$username, $token],
|
||||
'verify' => !$this->config->getSystemValue(
|
||||
'sharing.federation.allowSelfSignedCertificates',
|
||||
false,
|
||||
),
|
||||
];
|
||||
|
||||
$client->delete($url, $options);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -92,11 +92,12 @@ class CalendarFederationProviderTest extends TestCase {
|
||||
->willReturn(true);
|
||||
|
||||
$this->federatedCalendarMapper->expects(self::once())
|
||||
->method('deleteByUri')
|
||||
->method('findByUri')
|
||||
->with(
|
||||
'principals/users/sharee1',
|
||||
'ae4b8ab904076fff2b955ea21b1a0d92',
|
||||
);
|
||||
)
|
||||
->willReturn(null);
|
||||
|
||||
$this->federatedCalendarMapper->expects(self::once())
|
||||
->method('insert')
|
||||
@@ -123,6 +124,68 @@ class CalendarFederationProviderTest extends TestCase {
|
||||
$this->assertEquals(10, $this->calendarFederationProvider->shareReceived($share));
|
||||
}
|
||||
|
||||
public function testShareReceivedWithExistingCalendar(): void {
|
||||
$share = $this->createMock(ICloudFederationShare::class);
|
||||
$share->method('getShareType')
|
||||
->willReturn('user');
|
||||
$share->method('getProtocol')
|
||||
->willReturn([
|
||||
'version' => 'v1',
|
||||
'url' => 'https://nextcloud.remote/remote.php/dav/remote-calendars/abcdef123/cal1_shared_by_user1',
|
||||
'displayName' => 'Calendar 1',
|
||||
'color' => '#ff0000',
|
||||
'access' => 3,
|
||||
'components' => 'VEVENT,VTODO',
|
||||
]);
|
||||
$share->method('getShareWith')
|
||||
->willReturn('sharee1');
|
||||
$share->method('getShareSecret')
|
||||
->willReturn('new-token');
|
||||
$share->method('getSharedBy')
|
||||
->willReturn('user1@nextcloud.remote');
|
||||
$share->method('getSharedByDisplayName')
|
||||
->willReturn('User 1');
|
||||
|
||||
$this->calendarFederationConfig->expects(self::once())
|
||||
->method('isFederationEnabled')
|
||||
->willReturn(true);
|
||||
|
||||
$existingCalendar = new FederatedCalendarEntity();
|
||||
$existingCalendar->setId(10);
|
||||
$existingCalendar->setPrincipaluri('principals/users/sharee1');
|
||||
$existingCalendar->setUri('ae4b8ab904076fff2b955ea21b1a0d92');
|
||||
$existingCalendar->setRemoteUrl('https://nextcloud.remote/remote.php/dav/remote-calendars/abcdef123/cal1_shared_by_user1');
|
||||
$existingCalendar->setToken('old-token');
|
||||
$existingCalendar->setPermissions(1);
|
||||
$existingCalendar->setComponents('VEVENT');
|
||||
|
||||
$this->federatedCalendarMapper->expects(self::once())
|
||||
->method('findByUri')
|
||||
->with(
|
||||
'principals/users/sharee1',
|
||||
'ae4b8ab904076fff2b955ea21b1a0d92',
|
||||
)
|
||||
->willReturn($existingCalendar);
|
||||
|
||||
$this->federatedCalendarMapper->expects(self::never())
|
||||
->method('insert');
|
||||
|
||||
$this->federatedCalendarMapper->expects(self::once())
|
||||
->method('update')
|
||||
->willReturnCallback(function (FederatedCalendarEntity $calendar) {
|
||||
$this->assertEquals('new-token', $calendar->getToken());
|
||||
$this->assertEquals(1, $calendar->getPermissions());
|
||||
$this->assertEquals('VEVENT,VTODO', $calendar->getComponents());
|
||||
return $calendar;
|
||||
});
|
||||
|
||||
$this->jobList->expects(self::once())
|
||||
->method('add')
|
||||
->with(FederatedCalendarSyncJob::class, ['id' => 10]);
|
||||
|
||||
$this->assertEquals(10, $this->calendarFederationProvider->shareReceived($share));
|
||||
}
|
||||
|
||||
public function testShareReceivedWithInvalidProtocolVersion(): void {
|
||||
$share = $this->createMock(ICloudFederationShare::class);
|
||||
$share->method('getShareType')
|
||||
@@ -270,7 +333,7 @@ class CalendarFederationProviderTest extends TestCase {
|
||||
$this->calendarFederationProvider->shareReceived($share);
|
||||
}
|
||||
|
||||
public function testShareReceivedWithUnsupportedAccess(): void {
|
||||
public function testShareReceivedWithReadWriteAccess(): void {
|
||||
$share = $this->createMock(ICloudFederationShare::class);
|
||||
$share->method('getShareType')
|
||||
->willReturn('user');
|
||||
@@ -296,6 +359,65 @@ class CalendarFederationProviderTest extends TestCase {
|
||||
->method('isFederationEnabled')
|
||||
->willReturn(true);
|
||||
|
||||
$this->federatedCalendarMapper->expects(self::once())
|
||||
->method('findByUri')
|
||||
->with(
|
||||
'principals/users/sharee1',
|
||||
'ae4b8ab904076fff2b955ea21b1a0d92',
|
||||
)
|
||||
->willReturn(null);
|
||||
|
||||
$this->federatedCalendarMapper->expects(self::once())
|
||||
->method('insert')
|
||||
->willReturnCallback(function (FederatedCalendarEntity $calendar) {
|
||||
$this->assertEquals('principals/users/sharee1', $calendar->getPrincipaluri());
|
||||
$this->assertEquals('ae4b8ab904076fff2b955ea21b1a0d92', $calendar->getUri());
|
||||
$this->assertEquals('https://nextcloud.remote/remote.php/dav/remote-calendars/abcdef123/cal1_shared_by_user1', $calendar->getRemoteUrl());
|
||||
$this->assertEquals('Calendar 1', $calendar->getDisplayName());
|
||||
$this->assertEquals('#ff0000', $calendar->getColor());
|
||||
$this->assertEquals('token', $calendar->getToken());
|
||||
$this->assertEquals('user1@nextcloud.remote', $calendar->getSharedBy());
|
||||
$this->assertEquals('User 1', $calendar->getSharedByDisplayName());
|
||||
$this->assertEquals(15, $calendar->getPermissions()); // READ | CREATE | UPDATE | DELETE
|
||||
$this->assertEquals('VEVENT,VTODO', $calendar->getComponents());
|
||||
|
||||
$calendar->setId(10);
|
||||
return $calendar;
|
||||
});
|
||||
|
||||
$this->jobList->expects(self::once())
|
||||
->method('add')
|
||||
->with(FederatedCalendarSyncJob::class, ['id' => 10]);
|
||||
|
||||
$this->assertEquals(10, $this->calendarFederationProvider->shareReceived($share));
|
||||
}
|
||||
|
||||
public function testShareReceivedWithUnsupportedAccess(): void {
|
||||
$share = $this->createMock(ICloudFederationShare::class);
|
||||
$share->method('getShareType')
|
||||
->willReturn('user');
|
||||
$share->method('getProtocol')
|
||||
->willReturn([
|
||||
'version' => 'v1',
|
||||
'url' => 'https://nextcloud.remote/remote.php/dav/remote-calendars/abcdef123/cal1_shared_by_user1',
|
||||
'displayName' => 'Calendar 1',
|
||||
'color' => '#ff0000',
|
||||
'access' => 999, // Invalid access value
|
||||
'components' => 'VEVENT,VTODO',
|
||||
]);
|
||||
$share->method('getShareWith')
|
||||
->willReturn('sharee1');
|
||||
$share->method('getShareSecret')
|
||||
->willReturn('token');
|
||||
$share->method('getSharedBy')
|
||||
->willReturn('user1@nextcloud.remote');
|
||||
$share->method('getSharedByDisplayName')
|
||||
->willReturn('User 1');
|
||||
|
||||
$this->calendarFederationConfig->expects(self::once())
|
||||
->method('isFederationEnabled')
|
||||
->willReturn(true);
|
||||
|
||||
$this->federatedCalendarMapper->expects(self::never())
|
||||
->method('insert');
|
||||
$this->jobList->expects(self::never())
|
||||
|
||||
@@ -9,13 +9,17 @@ declare(strict_types=1);
|
||||
|
||||
namespace OCA\DAV\Tests\unit\CalDAV\Federation;
|
||||
|
||||
use OCA\DAV\CalDAV\CalDavBackend;
|
||||
use OCA\DAV\CalDAV\Federation\FederatedCalendarEntity;
|
||||
use OCA\DAV\CalDAV\Federation\FederatedCalendarMapper;
|
||||
use OCA\DAV\CalDAV\Federation\FederatedCalendarSyncService;
|
||||
use OCA\DAV\CalDAV\SyncService as CalDavSyncService;
|
||||
use OCA\DAV\CalDAV\SyncServiceResult;
|
||||
use OCP\Federation\ICloudId;
|
||||
use OCP\Federation\ICloudIdManager;
|
||||
use OCP\Http\Client\IClient;
|
||||
use OCP\Http\Client\IClientService;
|
||||
use OCP\Http\Client\IResponse;
|
||||
use OCP\IConfig;
|
||||
use OCP\IDBConnection;
|
||||
use PHPUnit\Framework\Attributes\DataProvider;
|
||||
use PHPUnit\Framework\MockObject\MockObject;
|
||||
use Psr\Log\LoggerInterface;
|
||||
@@ -26,21 +30,30 @@ class FederatedCalendarSyncServiceTest extends TestCase {
|
||||
|
||||
private FederatedCalendarMapper&MockObject $federatedCalendarMapper;
|
||||
private LoggerInterface&MockObject $logger;
|
||||
private CalDavSyncService&MockObject $calDavSyncService;
|
||||
private CalDavBackend&MockObject $backend;
|
||||
private IDBConnection&MockObject $dbConnection;
|
||||
private ICloudIdManager&MockObject $cloudIdManager;
|
||||
private IClientService&MockObject $clientService;
|
||||
private IConfig&MockObject $config;
|
||||
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
$this->federatedCalendarMapper = $this->createMock(FederatedCalendarMapper::class);
|
||||
$this->logger = $this->createMock(LoggerInterface::class);
|
||||
$this->calDavSyncService = $this->createMock(CalDavSyncService::class);
|
||||
$this->backend = $this->createMock(CalDavBackend::class);
|
||||
$this->dbConnection = $this->createMock(IDBConnection::class);
|
||||
$this->cloudIdManager = $this->createMock(ICloudIdManager::class);
|
||||
$this->clientService = $this->createMock(IClientService::class);
|
||||
$this->config = $this->createMock(IConfig::class);
|
||||
|
||||
$this->federatedCalendarSyncService = new FederatedCalendarSyncService(
|
||||
$this->clientService,
|
||||
$this->config,
|
||||
$this->federatedCalendarMapper,
|
||||
$this->logger,
|
||||
$this->calDavSyncService,
|
||||
$this->backend,
|
||||
$this->dbConnection,
|
||||
$this->cloudIdManager,
|
||||
);
|
||||
}
|
||||
@@ -61,16 +74,24 @@ class FederatedCalendarSyncServiceTest extends TestCase {
|
||||
->with('user1')
|
||||
->willReturn($cloudId);
|
||||
|
||||
$this->calDavSyncService->expects(self::once())
|
||||
->method('syncRemoteCalendar')
|
||||
->with(
|
||||
'https://remote.tld/remote.php/dav/remote-calendars/abcdef123/cal1_shared_by_user2',
|
||||
'dXNlcjFAbmV4dGNsb3VkLnRlc3Rpbmc=',
|
||||
'token',
|
||||
'http://sabre.io/ns/sync/100',
|
||||
$calendar,
|
||||
)
|
||||
->willReturn(new SyncServiceResult('http://sabre.io/ns/sync/101', 10));
|
||||
// Mock HTTP client for sync report
|
||||
$client = $this->createMock(IClient::class);
|
||||
$response = $this->createMock(IResponse::class);
|
||||
$response->method('getBody')
|
||||
->willReturn('<?xml version="1.0"?><d:multistatus xmlns:d="DAV:"><d:sync-token>http://sabre.io/ns/sync/101</d:sync-token></d:multistatus>');
|
||||
|
||||
$client->expects(self::once())
|
||||
->method('request')
|
||||
->with('REPORT', 'https://remote.tld/remote.php/dav/remote-calendars/abcdef123/cal1_shared_by_user2', self::anything())
|
||||
->willReturn($response);
|
||||
|
||||
$this->clientService->method('newClient')
|
||||
->willReturn($client);
|
||||
|
||||
$this->config->method('getSystemValueInt')
|
||||
->willReturn(30);
|
||||
$this->config->method('getSystemValue')
|
||||
->willReturn(false);
|
||||
|
||||
$this->federatedCalendarMapper->expects(self::once())
|
||||
->method('updateSyncTokenAndTime')
|
||||
@@ -78,7 +99,7 @@ class FederatedCalendarSyncServiceTest extends TestCase {
|
||||
$this->federatedCalendarMapper->expects(self::never())
|
||||
->method('updateSyncTime');
|
||||
|
||||
$this->assertEquals(10, $this->federatedCalendarSyncService->syncOne($calendar));
|
||||
$this->assertEquals(0, $this->federatedCalendarSyncService->syncOne($calendar));
|
||||
}
|
||||
|
||||
public function testSyncOneUnchanged(): void {
|
||||
@@ -97,16 +118,24 @@ class FederatedCalendarSyncServiceTest extends TestCase {
|
||||
->with('user1')
|
||||
->willReturn($cloudId);
|
||||
|
||||
$this->calDavSyncService->expects(self::once())
|
||||
->method('syncRemoteCalendar')
|
||||
->with(
|
||||
'https://remote.tld/remote.php/dav/remote-calendars/abcdef123/cal1_shared_by_user2',
|
||||
'dXNlcjFAbmV4dGNsb3VkLnRlc3Rpbmc=',
|
||||
'token',
|
||||
'http://sabre.io/ns/sync/100',
|
||||
$calendar,
|
||||
)
|
||||
->willReturn(new SyncServiceResult('http://sabre.io/ns/sync/100', 0));
|
||||
// Mock HTTP client for sync report
|
||||
$client = $this->createMock(IClient::class);
|
||||
$response = $this->createMock(IResponse::class);
|
||||
$response->method('getBody')
|
||||
->willReturn('<?xml version="1.0"?><d:multistatus xmlns:d="DAV:"><d:sync-token>http://sabre.io/ns/sync/100</d:sync-token></d:multistatus>');
|
||||
|
||||
$client->expects(self::once())
|
||||
->method('request')
|
||||
->with('REPORT', 'https://remote.tld/remote.php/dav/remote-calendars/abcdef123/cal1_shared_by_user2', self::anything())
|
||||
->willReturn($response);
|
||||
|
||||
$this->clientService->method('newClient')
|
||||
->willReturn($client);
|
||||
|
||||
$this->config->method('getSystemValueInt')
|
||||
->willReturn(30);
|
||||
$this->config->method('getSystemValue')
|
||||
->willReturn(false);
|
||||
|
||||
$this->federatedCalendarMapper->expects(self::never())
|
||||
->method('updateSyncTokenAndTime');
|
||||
@@ -143,16 +172,24 @@ class FederatedCalendarSyncServiceTest extends TestCase {
|
||||
->with('user1')
|
||||
->willReturn($cloudId);
|
||||
|
||||
$this->calDavSyncService->expects(self::once())
|
||||
->method('syncRemoteCalendar')
|
||||
->with(
|
||||
'https://remote.tld/remote.php/dav/remote-calendars/abcdef123/cal1_shared_by_user2',
|
||||
'dXNlcjFAbmV4dGNsb3VkLnRlc3Rpbmc=',
|
||||
'token',
|
||||
'http://sabre.io/ns/sync/100',
|
||||
$calendar,
|
||||
)
|
||||
->willReturn(new SyncServiceResult($syncToken, 10));
|
||||
// Mock HTTP client for sync report with unexpected token format
|
||||
$client = $this->createMock(IClient::class);
|
||||
$response = $this->createMock(IResponse::class);
|
||||
$response->method('getBody')
|
||||
->willReturn('<?xml version="1.0"?><d:multistatus xmlns:d="DAV:"><d:sync-token>' . $syncToken . '</d:sync-token></d:multistatus>');
|
||||
|
||||
$client->expects(self::once())
|
||||
->method('request')
|
||||
->with('REPORT', 'https://remote.tld/remote.php/dav/remote-calendars/abcdef123/cal1_shared_by_user2', self::anything())
|
||||
->willReturn($response);
|
||||
|
||||
$this->clientService->method('newClient')
|
||||
->willReturn($client);
|
||||
|
||||
$this->config->method('getSystemValueInt')
|
||||
->willReturn(30);
|
||||
$this->config->method('getSystemValue')
|
||||
->willReturn(false);
|
||||
|
||||
$this->federatedCalendarMapper->expects(self::never())
|
||||
->method('updateSyncTokenAndTime');
|
||||
|
||||
@@ -0,0 +1,404 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
namespace OCA\DAV\Tests\unit\CalDAV\Federation;
|
||||
|
||||
use OCA\DAV\CalDAV\CalDavBackend;
|
||||
use OCA\DAV\CalDAV\Federation\FederatedCalendar;
|
||||
use OCA\DAV\CalDAV\Federation\FederatedCalendarEntity;
|
||||
use OCA\DAV\CalDAV\Federation\FederatedCalendarMapper;
|
||||
use OCA\DAV\CalDAV\Federation\FederatedCalendarObject;
|
||||
use OCA\DAV\CalDAV\Federation\FederatedCalendarSyncService;
|
||||
use OCP\Constants;
|
||||
use PHPUnit\Framework\MockObject\MockObject;
|
||||
use Sabre\DAV\Exception\MethodNotAllowed;
|
||||
use Sabre\DAV\Exception\NotFound;
|
||||
use Sabre\DAV\PropPatch;
|
||||
use Test\TestCase;
|
||||
|
||||
class FederatedCalendarTest extends TestCase {
|
||||
private FederatedCalendar $federatedCalendar;
|
||||
|
||||
private FederatedCalendarMapper&MockObject $federatedCalendarMapper;
|
||||
private FederatedCalendarSyncService&MockObject $federatedCalendarService;
|
||||
private CalDavBackend&MockObject $caldavBackend;
|
||||
private FederatedCalendarEntity $federationInfo;
|
||||
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
$this->federatedCalendarMapper = $this->createMock(FederatedCalendarMapper::class);
|
||||
$this->federatedCalendarService = $this->createMock(FederatedCalendarSyncService::class);
|
||||
$this->caldavBackend = $this->createMock(CalDavBackend::class);
|
||||
|
||||
$this->federationInfo = new FederatedCalendarEntity();
|
||||
$this->federationInfo->setId(10);
|
||||
$this->federationInfo->setPrincipaluri('principals/users/user1');
|
||||
$this->federationInfo->setUri('calendar-uri');
|
||||
$this->federationInfo->setDisplayName('Federated Calendar');
|
||||
$this->federationInfo->setColor('#ff0000');
|
||||
$this->federationInfo->setSharedBy('user2@nextcloud.remote');
|
||||
$this->federationInfo->setSharedByDisplayName('User 2');
|
||||
$this->federationInfo->setPermissions(Constants::PERMISSION_READ);
|
||||
$this->federationInfo->setLastSync(1234567890);
|
||||
|
||||
$this->federatedCalendarMapper->method('findByUri')
|
||||
->with('principals/users/user1', 'calendar-uri')
|
||||
->willReturn($this->federationInfo);
|
||||
|
||||
$calendarInfo = [
|
||||
'principaluri' => 'principals/users/user1',
|
||||
'id' => 10,
|
||||
'uri' => 'calendar-uri',
|
||||
];
|
||||
|
||||
$this->federatedCalendar = new FederatedCalendar(
|
||||
$this->federatedCalendarMapper,
|
||||
$this->federatedCalendarService,
|
||||
$this->caldavBackend,
|
||||
$calendarInfo,
|
||||
);
|
||||
}
|
||||
|
||||
public function testGetResourceId(): void {
|
||||
$this->assertEquals(10, $this->federatedCalendar->getResourceId());
|
||||
}
|
||||
|
||||
public function testGetName(): void {
|
||||
$this->assertEquals('calendar-uri', $this->federatedCalendar->getName());
|
||||
}
|
||||
|
||||
public function testSetName(): void {
|
||||
$this->expectException(MethodNotAllowed::class);
|
||||
$this->expectExceptionMessage('Renaming federated calendars is not allowed');
|
||||
$this->federatedCalendar->setName('new-name');
|
||||
}
|
||||
|
||||
public function testGetPrincipalURI(): void {
|
||||
$this->assertEquals('principals/users/user1', $this->federatedCalendar->getPrincipalURI());
|
||||
}
|
||||
|
||||
public function testGetOwner(): void {
|
||||
$expected = 'principals/remote-users/' . base64_encode('user2@nextcloud.remote');
|
||||
$this->assertEquals($expected, $this->federatedCalendar->getOwner());
|
||||
}
|
||||
|
||||
public function testGetGroup(): void {
|
||||
$this->assertNull($this->federatedCalendar->getGroup());
|
||||
}
|
||||
|
||||
public function testGetACLWithReadOnlyPermissions(): void {
|
||||
$this->federationInfo->setPermissions(Constants::PERMISSION_READ);
|
||||
|
||||
$acl = $this->federatedCalendar->getACL();
|
||||
|
||||
$this->assertCount(3, $acl);
|
||||
// Check basic read permissions
|
||||
$this->assertEquals('{DAV:}read', $acl[0]['privilege']);
|
||||
$this->assertTrue($acl[0]['protected']);
|
||||
$this->assertEquals('{DAV:}read-acl', $acl[1]['privilege']);
|
||||
$this->assertTrue($acl[1]['protected']);
|
||||
$this->assertEquals('{DAV:}write-properties', $acl[2]['privilege']);
|
||||
$this->assertTrue($acl[2]['protected']);
|
||||
}
|
||||
|
||||
public function testGetACLWithCreatePermission(): void {
|
||||
$this->federationInfo->setPermissions(Constants::PERMISSION_READ | Constants::PERMISSION_CREATE);
|
||||
|
||||
$acl = $this->federatedCalendar->getACL();
|
||||
|
||||
$this->assertCount(4, $acl);
|
||||
// Check that create permission is added
|
||||
$privileges = array_column($acl, 'privilege');
|
||||
$this->assertContains('{DAV:}bind', $privileges);
|
||||
}
|
||||
|
||||
public function testGetACLWithUpdatePermission(): void {
|
||||
$this->federationInfo->setPermissions(Constants::PERMISSION_READ | Constants::PERMISSION_UPDATE);
|
||||
|
||||
$acl = $this->federatedCalendar->getACL();
|
||||
|
||||
$this->assertCount(4, $acl);
|
||||
// Check that update permission is added (write-content, not write-properties which is already in base ACL)
|
||||
$privileges = array_column($acl, 'privilege');
|
||||
$this->assertContains('{DAV:}write-content', $privileges);
|
||||
}
|
||||
|
||||
public function testGetACLWithDeletePermission(): void {
|
||||
$this->federationInfo->setPermissions(Constants::PERMISSION_READ | Constants::PERMISSION_DELETE);
|
||||
|
||||
$acl = $this->federatedCalendar->getACL();
|
||||
|
||||
$this->assertCount(4, $acl);
|
||||
// Check that delete permission is added
|
||||
$privileges = array_column($acl, 'privilege');
|
||||
$this->assertContains('{DAV:}unbind', $privileges);
|
||||
}
|
||||
|
||||
public function testGetACLWithAllPermissions(): void {
|
||||
$this->federationInfo->setPermissions(
|
||||
Constants::PERMISSION_READ
|
||||
| Constants::PERMISSION_CREATE
|
||||
| Constants::PERMISSION_UPDATE
|
||||
| Constants::PERMISSION_DELETE
|
||||
);
|
||||
|
||||
$acl = $this->federatedCalendar->getACL();
|
||||
|
||||
$this->assertCount(6, $acl);
|
||||
$privileges = array_column($acl, 'privilege');
|
||||
$this->assertContains('{DAV:}read', $privileges);
|
||||
$this->assertContains('{DAV:}bind', $privileges);
|
||||
$this->assertContains('{DAV:}write-content', $privileges);
|
||||
$this->assertContains('{DAV:}write-properties', $privileges);
|
||||
$this->assertContains('{DAV:}unbind', $privileges);
|
||||
}
|
||||
|
||||
public function testSetACL(): void {
|
||||
$this->expectException(MethodNotAllowed::class);
|
||||
$this->expectExceptionMessage('Changing ACLs on federated calendars is not allowed');
|
||||
$this->federatedCalendar->setACL([]);
|
||||
}
|
||||
|
||||
public function testGetSupportedPrivilegeSet(): void {
|
||||
$this->assertNull($this->federatedCalendar->getSupportedPrivilegeSet());
|
||||
}
|
||||
|
||||
public function testGetProperties(): void {
|
||||
$properties = $this->federatedCalendar->getProperties([
|
||||
'{DAV:}displayname',
|
||||
'{http://apple.com/ns/ical/}calendar-color',
|
||||
]);
|
||||
|
||||
$this->assertEquals('Federated Calendar', $properties['{DAV:}displayname']);
|
||||
$this->assertEquals('#ff0000', $properties['{http://apple.com/ns/ical/}calendar-color']);
|
||||
}
|
||||
|
||||
public function testPropPatchWithDisplayName(): void {
|
||||
$propPatch = $this->createMock(PropPatch::class);
|
||||
$propPatch->method('getMutations')
|
||||
->willReturn([
|
||||
'{DAV:}displayname' => 'New Calendar Name',
|
||||
]);
|
||||
|
||||
$this->federatedCalendarMapper->expects(self::once())
|
||||
->method('update')
|
||||
->willReturnCallback(function (FederatedCalendarEntity $entity) {
|
||||
$this->assertEquals('New Calendar Name', $entity->getDisplayName());
|
||||
return $entity;
|
||||
});
|
||||
|
||||
$propPatch->expects(self::once())
|
||||
->method('setResultCode')
|
||||
->with('{DAV:}displayname', 200);
|
||||
|
||||
$this->federatedCalendar->propPatch($propPatch);
|
||||
}
|
||||
|
||||
public function testPropPatchWithColor(): void {
|
||||
$propPatch = $this->createMock(PropPatch::class);
|
||||
$propPatch->method('getMutations')
|
||||
->willReturn([
|
||||
'{http://apple.com/ns/ical/}calendar-color' => '#00ff00',
|
||||
]);
|
||||
|
||||
$this->federatedCalendarMapper->expects(self::once())
|
||||
->method('update')
|
||||
->willReturnCallback(function (FederatedCalendarEntity $entity) {
|
||||
$this->assertEquals('#00ff00', $entity->getColor());
|
||||
return $entity;
|
||||
});
|
||||
|
||||
$propPatch->expects(self::once())
|
||||
->method('setResultCode')
|
||||
->with('{http://apple.com/ns/ical/}calendar-color', 200);
|
||||
|
||||
$this->federatedCalendar->propPatch($propPatch);
|
||||
}
|
||||
|
||||
public function testPropPatchWithNoMutations(): void {
|
||||
$propPatch = $this->createMock(PropPatch::class);
|
||||
$propPatch->method('getMutations')
|
||||
->willReturn([]);
|
||||
|
||||
$this->federatedCalendarMapper->expects(self::never())
|
||||
->method('update');
|
||||
|
||||
$propPatch->expects(self::never())
|
||||
->method('handle');
|
||||
|
||||
$this->federatedCalendar->propPatch($propPatch);
|
||||
}
|
||||
|
||||
public function testGetChildACL(): void {
|
||||
$this->assertEquals($this->federatedCalendar->getACL(), $this->federatedCalendar->getChildACL());
|
||||
}
|
||||
|
||||
public function testGetLastModified(): void {
|
||||
$this->assertEquals(1234567890, $this->federatedCalendar->getLastModified());
|
||||
}
|
||||
|
||||
public function testDelete(): void {
|
||||
$this->federatedCalendarMapper->expects(self::once())
|
||||
->method('deleteById')
|
||||
->with(10);
|
||||
|
||||
$this->federatedCalendar->delete();
|
||||
}
|
||||
|
||||
public function testCreateDirectory(): void {
|
||||
$this->expectException(MethodNotAllowed::class);
|
||||
$this->expectExceptionMessage('Creating nested collection is not allowed');
|
||||
$this->federatedCalendar->createDirectory('test');
|
||||
}
|
||||
|
||||
public function testCalendarQuery(): void {
|
||||
$filters = ['comp-filter' => ['name' => 'VEVENT']];
|
||||
$expectedUris = ['event1.ics', 'event2.ics'];
|
||||
|
||||
$this->caldavBackend->expects(self::once())
|
||||
->method('calendarQuery')
|
||||
->with(10, $filters, 2) // 2 is CALENDAR_TYPE_FEDERATED
|
||||
->willReturn($expectedUris);
|
||||
|
||||
$result = $this->federatedCalendar->calendarQuery($filters);
|
||||
$this->assertEquals($expectedUris, $result);
|
||||
}
|
||||
|
||||
public function testGetChild(): void {
|
||||
$objectData = [
|
||||
'id' => 1,
|
||||
'uri' => 'event1.ics',
|
||||
'calendardata' => 'BEGIN:VCALENDAR...',
|
||||
];
|
||||
|
||||
$this->caldavBackend->expects(self::once())
|
||||
->method('getCalendarObject')
|
||||
->with(10, 'event1.ics', 2) // 2 is CALENDAR_TYPE_FEDERATED
|
||||
->willReturn($objectData);
|
||||
|
||||
$child = $this->federatedCalendar->getChild('event1.ics');
|
||||
$this->assertInstanceOf(FederatedCalendarObject::class, $child);
|
||||
}
|
||||
|
||||
public function testGetChildNotFound(): void {
|
||||
$this->caldavBackend->expects(self::once())
|
||||
->method('getCalendarObject')
|
||||
->with(10, 'nonexistent.ics', 2)
|
||||
->willReturn(null);
|
||||
|
||||
$this->expectException(NotFound::class);
|
||||
$this->federatedCalendar->getChild('nonexistent.ics');
|
||||
}
|
||||
|
||||
public function testGetChildren(): void {
|
||||
$objects = [
|
||||
['id' => 1, 'uri' => 'event1.ics', 'calendardata' => 'BEGIN:VCALENDAR...'],
|
||||
['id' => 2, 'uri' => 'event2.ics', 'calendardata' => 'BEGIN:VCALENDAR...'],
|
||||
];
|
||||
|
||||
$this->caldavBackend->expects(self::once())
|
||||
->method('getCalendarObjects')
|
||||
->with(10, 2) // 2 is CALENDAR_TYPE_FEDERATED
|
||||
->willReturn($objects);
|
||||
|
||||
$children = $this->federatedCalendar->getChildren();
|
||||
$this->assertCount(2, $children);
|
||||
$this->assertInstanceOf(FederatedCalendarObject::class, $children[0]);
|
||||
$this->assertInstanceOf(FederatedCalendarObject::class, $children[1]);
|
||||
}
|
||||
|
||||
public function testGetMultipleChildren(): void {
|
||||
$paths = ['event1.ics', 'event2.ics'];
|
||||
$objects = [
|
||||
['id' => 1, 'uri' => 'event1.ics', 'calendardata' => 'BEGIN:VCALENDAR...'],
|
||||
['id' => 2, 'uri' => 'event2.ics', 'calendardata' => 'BEGIN:VCALENDAR...'],
|
||||
];
|
||||
|
||||
$this->caldavBackend->expects(self::once())
|
||||
->method('getMultipleCalendarObjects')
|
||||
->with(10, $paths, 2) // 2 is CALENDAR_TYPE_FEDERATED
|
||||
->willReturn($objects);
|
||||
|
||||
$children = $this->federatedCalendar->getMultipleChildren($paths);
|
||||
$this->assertCount(2, $children);
|
||||
$this->assertInstanceOf(FederatedCalendarObject::class, $children[0]);
|
||||
$this->assertInstanceOf(FederatedCalendarObject::class, $children[1]);
|
||||
}
|
||||
|
||||
public function testChildExists(): void {
|
||||
$this->caldavBackend->expects(self::once())
|
||||
->method('getCalendarObject')
|
||||
->with(10, 'event1.ics', 2)
|
||||
->willReturn(['id' => 1, 'uri' => 'event1.ics']);
|
||||
|
||||
$result = $this->federatedCalendar->childExists('event1.ics');
|
||||
$this->assertTrue($result);
|
||||
}
|
||||
|
||||
public function testChildNotExists(): void {
|
||||
$this->caldavBackend->expects(self::once())
|
||||
->method('getCalendarObject')
|
||||
->with(10, 'nonexistent.ics', 2)
|
||||
->willReturn(null);
|
||||
|
||||
$result = $this->federatedCalendar->childExists('nonexistent.ics');
|
||||
$this->assertFalse($result);
|
||||
}
|
||||
|
||||
public function testCreateFile(): void {
|
||||
$calendarData = 'BEGIN:VCALENDAR...END:VCALENDAR';
|
||||
$remoteEtag = '"remote-etag-123"';
|
||||
$localEtag = '"local-etag-456"';
|
||||
|
||||
$this->federatedCalendarService->expects(self::once())
|
||||
->method('createCalendarObject')
|
||||
->with($this->federationInfo, 'event1.ics', $calendarData)
|
||||
->willReturn($remoteEtag);
|
||||
|
||||
$this->caldavBackend->expects(self::once())
|
||||
->method('createCalendarObject')
|
||||
->with(10, 'event1.ics', $calendarData, 2)
|
||||
->willReturn($localEtag);
|
||||
|
||||
$result = $this->federatedCalendar->createFile('event1.ics', $calendarData);
|
||||
$this->assertEquals($localEtag, $result);
|
||||
}
|
||||
|
||||
public function testUpdateFile(): void {
|
||||
$calendarData = 'BEGIN:VCALENDAR...UPDATED...END:VCALENDAR';
|
||||
$remoteEtag = '"remote-etag-updated"';
|
||||
$localEtag = '"local-etag-updated"';
|
||||
|
||||
$this->federatedCalendarService->expects(self::once())
|
||||
->method('updateCalendarObject')
|
||||
->with($this->federationInfo, 'event1.ics', $calendarData)
|
||||
->willReturn($remoteEtag);
|
||||
|
||||
$this->caldavBackend->expects(self::once())
|
||||
->method('updateCalendarObject')
|
||||
->with(10, 'event1.ics', $calendarData, 2)
|
||||
->willReturn($localEtag);
|
||||
|
||||
$result = $this->federatedCalendar->updateFile('event1.ics', $calendarData);
|
||||
$this->assertEquals($localEtag, $result);
|
||||
}
|
||||
|
||||
public function testDeleteFile(): void {
|
||||
$this->federatedCalendarService->expects(self::once())
|
||||
->method('deleteCalendarObject')
|
||||
->with($this->federationInfo, 'event1.ics');
|
||||
|
||||
$this->caldavBackend->expects(self::once())
|
||||
->method('deleteCalendarObject')
|
||||
->with(10, 'event1.ics', 2);
|
||||
|
||||
$this->federatedCalendar->deleteFile('event1.ics');
|
||||
}
|
||||
}
|
||||
@@ -87,11 +87,12 @@ class SharesPluginTest extends \Test\TestCase {
|
||||
});
|
||||
|
||||
$this->shareManager->expects($this->any())
|
||||
->method('getAllSharedWith')
|
||||
->method('getSharedWith')
|
||||
->with(
|
||||
$this->equalTo('user1'),
|
||||
$this->anything(),
|
||||
$this->equalTo($node),
|
||||
$this->equalTo(-1)
|
||||
)
|
||||
->willReturn([]);
|
||||
|
||||
@@ -182,11 +183,12 @@ class SharesPluginTest extends \Test\TestCase {
|
||||
});
|
||||
|
||||
$this->shareManager->expects($this->any())
|
||||
->method('getAllSharedWith')
|
||||
->method('getSharedWith')
|
||||
->with(
|
||||
$this->equalTo('user1'),
|
||||
$this->anything(),
|
||||
$this->equalTo($node),
|
||||
$this->equalTo(-1)
|
||||
)
|
||||
->willReturn([]);
|
||||
|
||||
|
||||
@@ -545,7 +545,7 @@ class Crypt {
|
||||
$options,
|
||||
$iv);
|
||||
|
||||
if ($plainContent) {
|
||||
if ($plainContent !== false) {
|
||||
return $plainContent;
|
||||
} else {
|
||||
throw new DecryptionFailedException('Encryption library: Decryption (symmetric) of content failed: ' . openssl_error_string());
|
||||
|
||||
@@ -24,38 +24,42 @@ use OCP\IL10N;
|
||||
use OCP\IUserManager;
|
||||
use OCP\Share\Exceptions\GenericShareException;
|
||||
use OCP\Share\Exceptions\ShareNotFound;
|
||||
use OCP\Share\ICreateShareProvider;
|
||||
use OCP\Share\IShare;
|
||||
use OCP\Share\IShareProvider;
|
||||
use OCP\Share\IShareProviderSupportsAllSharesInFolder;
|
||||
use Override;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Symfony\Component\Console\Exception\LogicException;
|
||||
|
||||
/**
|
||||
* Class FederatedShareProvider
|
||||
*
|
||||
* @package OCA\FederatedFileSharing
|
||||
*/
|
||||
class FederatedShareProvider implements IShareProvider, IShareProviderSupportsAllSharesInFolder, ICreateShareProvider {
|
||||
private string $externalShareTable = 'share_external';
|
||||
class FederatedShareProvider implements IShareProvider, IShareProviderSupportsAllSharesInFolder {
|
||||
public const SHARE_TYPE_REMOTE = 6;
|
||||
|
||||
/** @var list<IShare::TYPE_*> list of supported share types */
|
||||
private array $supportedShareType = [IShare::TYPE_REMOTE_GROUP, IShare::TYPE_REMOTE, IShare::TYPE_CIRCLE];
|
||||
/** @var string */
|
||||
private $externalShareTable = 'share_external';
|
||||
|
||||
/** @var array list of supported share types */
|
||||
private $supportedShareType = [IShare::TYPE_REMOTE_GROUP, IShare::TYPE_REMOTE, IShare::TYPE_CIRCLE];
|
||||
|
||||
/**
|
||||
* DefaultShareProvider constructor.
|
||||
*/
|
||||
public function __construct(
|
||||
private readonly IDBConnection $dbConnection,
|
||||
private readonly AddressHandler $addressHandler,
|
||||
private readonly Notifications $notifications,
|
||||
private readonly TokenHandler $tokenHandler,
|
||||
private readonly IL10N $l,
|
||||
private readonly IRootFolder $rootFolder,
|
||||
private readonly IConfig $config,
|
||||
private readonly IUserManager $userManager,
|
||||
private readonly ICloudIdManager $cloudIdManager,
|
||||
private readonly \OCP\GlobalScale\IConfig $gsConfig,
|
||||
private readonly ICloudFederationProviderManager $cloudFederationProviderManager,
|
||||
private readonly LoggerInterface $logger,
|
||||
private IDBConnection $dbConnection,
|
||||
private AddressHandler $addressHandler,
|
||||
private Notifications $notifications,
|
||||
private TokenHandler $tokenHandler,
|
||||
private IL10N $l,
|
||||
private IRootFolder $rootFolder,
|
||||
private IConfig $config,
|
||||
private IUserManager $userManager,
|
||||
private ICloudIdManager $cloudIdManager,
|
||||
private \OCP\GlobalScale\IConfig $gsConfig,
|
||||
private ICloudFederationProviderManager $cloudFederationProviderManager,
|
||||
private LoggerInterface $logger,
|
||||
) {
|
||||
}
|
||||
|
||||
@@ -64,16 +68,6 @@ class FederatedShareProvider implements IShareProvider, IShareProviderSupportsAl
|
||||
return 'ocFederatedSharing';
|
||||
}
|
||||
|
||||
#[Override]
|
||||
public function getShareTypes(): array {
|
||||
return $this->supportedShareType;
|
||||
}
|
||||
|
||||
#[Override]
|
||||
public function getTokenShareTypes(): array {
|
||||
return $this->supportedShareType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Share a path
|
||||
*
|
||||
@@ -166,7 +160,7 @@ class FederatedShareProvider implements IShareProvider, IShareProviderSupportsAl
|
||||
}
|
||||
|
||||
$data = $this->getRawShare($shareId);
|
||||
return $this->createShare($data);
|
||||
return $this->createShareObject($data);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -430,7 +424,7 @@ class FederatedShareProvider implements IShareProvider, IShareProviderSupportsAl
|
||||
|
||||
$cursor = $qb->executeQuery();
|
||||
while ($data = $cursor->fetchAssociative()) {
|
||||
$children[] = $this->createShare($data);
|
||||
$children[] = $this->createShareObject($data);
|
||||
}
|
||||
$cursor->closeCursor();
|
||||
|
||||
@@ -577,25 +571,104 @@ class FederatedShareProvider implements IShareProvider, IShareProviderSupportsAl
|
||||
$cursor = $qb->executeQuery();
|
||||
$shares = [];
|
||||
while ($data = $cursor->fetchAssociative()) {
|
||||
$shares[$data['fileid']][] = $this->createShare($data);
|
||||
$shares[$data['fileid']][] = $this->createShareObject($data);
|
||||
}
|
||||
$cursor->closeCursor();
|
||||
|
||||
return $shares;
|
||||
}
|
||||
|
||||
#[Override]
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function getSharesBy($userId, $shareType, $node, $reshares, $limit, $offset) {
|
||||
throw new LogicException('Is no longer used');
|
||||
$qb = $this->dbConnection->getQueryBuilder();
|
||||
$qb->select('*')
|
||||
->from('share');
|
||||
|
||||
$qb->andWhere($qb->expr()->eq('share_type', $qb->createNamedParameter($shareType)));
|
||||
|
||||
/**
|
||||
* Reshares for this user are shares where they are the owner.
|
||||
*/
|
||||
if ($reshares === false) {
|
||||
//Special case for old shares created via the web UI
|
||||
$or1 = $qb->expr()->andX(
|
||||
$qb->expr()->eq('uid_owner', $qb->createNamedParameter($userId)),
|
||||
$qb->expr()->isNull('uid_initiator')
|
||||
);
|
||||
|
||||
$qb->andWhere(
|
||||
$qb->expr()->orX(
|
||||
$qb->expr()->eq('uid_initiator', $qb->createNamedParameter($userId)),
|
||||
$or1
|
||||
)
|
||||
);
|
||||
} else {
|
||||
$qb->andWhere(
|
||||
$qb->expr()->orX(
|
||||
$qb->expr()->eq('uid_owner', $qb->createNamedParameter($userId)),
|
||||
$qb->expr()->eq('uid_initiator', $qb->createNamedParameter($userId))
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if ($node !== null) {
|
||||
$qb->andWhere($qb->expr()->eq('file_source', $qb->createNamedParameter($node->getId())));
|
||||
}
|
||||
|
||||
if ($limit !== -1) {
|
||||
$qb->setMaxResults($limit);
|
||||
}
|
||||
|
||||
$qb->setFirstResult($offset);
|
||||
$qb->orderBy('id');
|
||||
|
||||
$cursor = $qb->executeQuery();
|
||||
$shares = [];
|
||||
while ($data = $cursor->fetchAssociative()) {
|
||||
$shares[] = $this->createShareObject($data);
|
||||
}
|
||||
$cursor->closeCursor();
|
||||
|
||||
return $shares;
|
||||
}
|
||||
|
||||
#[Override]
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function getShareById($id, $recipientId = null) {
|
||||
throw new LogicException('Is no longer used');
|
||||
$qb = $this->dbConnection->getQueryBuilder();
|
||||
|
||||
$qb->select('*')
|
||||
->from('share')
|
||||
->where($qb->expr()->eq('id', $qb->createNamedParameter($id)))
|
||||
->andWhere($qb->expr()->in('share_type', $qb->createNamedParameter($this->supportedShareType, IQueryBuilder::PARAM_INT_ARRAY)));
|
||||
|
||||
$cursor = $qb->executeQuery();
|
||||
$data = $cursor->fetchAssociative();
|
||||
$cursor->closeCursor();
|
||||
|
||||
if ($data === false) {
|
||||
throw new ShareNotFound('Can not find share with ID: ' . $id);
|
||||
}
|
||||
|
||||
try {
|
||||
$share = $this->createShareObject($data);
|
||||
} catch (InvalidShare $e) {
|
||||
throw new ShareNotFound();
|
||||
}
|
||||
|
||||
return $share;
|
||||
}
|
||||
|
||||
#[Override]
|
||||
public function getSharesByPath(Node $path): array {
|
||||
/**
|
||||
* Get shares for a given path
|
||||
*
|
||||
* @param Node $path
|
||||
* @return IShare[]
|
||||
*/
|
||||
public function getSharesByPath(Node $path) {
|
||||
$qb = $this->dbConnection->getQueryBuilder();
|
||||
|
||||
// get federated user shares
|
||||
@@ -607,15 +680,17 @@ class FederatedShareProvider implements IShareProvider, IShareProviderSupportsAl
|
||||
|
||||
$shares = [];
|
||||
while ($data = $cursor->fetchAssociative()) {
|
||||
$shares[] = $this->createShare($data);
|
||||
$shares[] = $this->createShareObject($data);
|
||||
}
|
||||
$cursor->closeCursor();
|
||||
|
||||
return $shares;
|
||||
}
|
||||
|
||||
#[Override]
|
||||
public function getSharedWith(string $userId, int $shareType, ?Node $node, int $limit, int $offset): array {
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function getSharedWith($userId, $shareType, $node, $limit, $offset) {
|
||||
/** @var IShare[] $shares */
|
||||
$shares = [];
|
||||
|
||||
@@ -644,7 +719,7 @@ class FederatedShareProvider implements IShareProvider, IShareProviderSupportsAl
|
||||
$cursor = $qb->executeQuery();
|
||||
|
||||
while ($data = $cursor->fetchAssociative()) {
|
||||
$shares[] = $this->createShare($data);
|
||||
$shares[] = $this->createShareObject($data);
|
||||
}
|
||||
$cursor->closeCursor();
|
||||
|
||||
@@ -668,7 +743,13 @@ class FederatedShareProvider implements IShareProvider, IShareProviderSupportsAl
|
||||
throw new ShareNotFound('Share not found', $this->l->t('Could not find share'));
|
||||
}
|
||||
|
||||
return $this->createShare($data);
|
||||
try {
|
||||
$share = $this->createShareObject($data);
|
||||
} catch (InvalidShare $e) {
|
||||
throw new ShareNotFound('Share not found', $this->l->t('Could not find share'));
|
||||
}
|
||||
|
||||
return $share;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -696,8 +777,15 @@ class FederatedShareProvider implements IShareProvider, IShareProviderSupportsAl
|
||||
return $data;
|
||||
}
|
||||
|
||||
#[Override]
|
||||
public function createShare(array $data): IShare {
|
||||
/**
|
||||
* Create a share object from an database row
|
||||
*
|
||||
* @param array $data
|
||||
* @return IShare
|
||||
* @throws InvalidShare
|
||||
* @throws ShareNotFound
|
||||
*/
|
||||
private function createShareObject($data): IShare {
|
||||
$share = new Share($this->rootFolder, $this->userManager);
|
||||
$share->setId((string)$data['id'])
|
||||
->setShareType((int)$data['share_type'])
|
||||
@@ -950,8 +1038,25 @@ class FederatedShareProvider implements IShareProvider, IShareProviderSupportsAl
|
||||
return ['remote' => $remote];
|
||||
}
|
||||
|
||||
#[Override]
|
||||
public function getAllShares(): iterable {
|
||||
throw new \LogicException('getAllShare in DefaultShareProvider should no longer be used');
|
||||
$qb = $this->dbConnection->getQueryBuilder();
|
||||
|
||||
$qb->select('*')
|
||||
->from('share')
|
||||
->where($qb->expr()->in('share_type', $qb->createNamedParameter([IShare::TYPE_REMOTE_GROUP, IShare::TYPE_REMOTE], IQueryBuilder::PARAM_INT_ARRAY)));
|
||||
|
||||
$cursor = $qb->executeQuery();
|
||||
while ($data = $cursor->fetchAssociative()) {
|
||||
try {
|
||||
$share = $this->createShareObject($data);
|
||||
} catch (InvalidShare $e) {
|
||||
continue;
|
||||
} catch (ShareNotFound $e) {
|
||||
continue;
|
||||
}
|
||||
|
||||
yield $share;
|
||||
}
|
||||
$cursor->closeCursor();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,7 +31,6 @@ use OCP\Files\NotFoundException;
|
||||
use OCP\IUser;
|
||||
use OCP\IUserManager;
|
||||
use OCP\L10N\IFactory;
|
||||
use OCP\PaginationParameters;
|
||||
use OCP\Server;
|
||||
use OCP\Share\Events\ShareTransferredEvent;
|
||||
use OCP\Share\IManager as IShareManager;
|
||||
@@ -411,19 +410,16 @@ class OwnershipTransferService {
|
||||
$progress = new ProgressBar($output);
|
||||
$normalizedPath = Filesystem::normalizePath($path);
|
||||
|
||||
$maxShareId = null;
|
||||
$offset = 0;
|
||||
while (true) {
|
||||
/** @var list<IShare> $sharePage */
|
||||
$sharePage = $this->shareManager->getAllSharedWith($sourceUid, [IShare::TYPE_USER], null, new PaginationParameters(limit: 50, maxId: $maxShareId));
|
||||
$sharePage = $this->shareManager->getSharedWith($sourceUid, IShare::TYPE_USER, null, 50, $offset);
|
||||
$progress->advance(count($sharePage));
|
||||
if (empty($sharePage)) {
|
||||
break;
|
||||
}
|
||||
|
||||
$maxShareId = end($sharePage)->getId();
|
||||
|
||||
if ($path !== null && $path !== "$sourceUid/files") {
|
||||
$sharePage = array_filter($sharePage, static function (IShare $share) use ($sourceUid, $normalizedPath): bool {
|
||||
$sharePage = array_filter($sharePage, static function (IShare $share) use ($sourceUid, $normalizedPath) {
|
||||
try {
|
||||
return str_starts_with(Filesystem::normalizePath($sourceUid . '/files' . $share->getTarget() . '/', false), $normalizedPath . '/');
|
||||
} catch (Exception) {
|
||||
@@ -435,6 +431,8 @@ class OwnershipTransferService {
|
||||
foreach ($sharePage as $share) {
|
||||
$shares[$share->getNodeId()] = $share;
|
||||
}
|
||||
|
||||
$offset += 50;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -95,7 +95,7 @@ class Backends extends Base {
|
||||
*/
|
||||
private function formatConfiguration(array $parameters): array {
|
||||
$configuration = array_filter($parameters, function (DefinitionParameter $parameter) {
|
||||
return $parameter->isFlagSet(DefinitionParameter::FLAG_HIDDEN);
|
||||
return !$parameter->isFlagSet(DefinitionParameter::FLAG_HIDDEN);
|
||||
});
|
||||
return array_map(function (DefinitionParameter $parameter) {
|
||||
return $parameter->getTypeName();
|
||||
|
||||
@@ -270,8 +270,8 @@ class AmazonS3 extends Common {
|
||||
$connection->deleteObjects([
|
||||
'Bucket' => $this->bucket,
|
||||
'Delete' => [
|
||||
'Quiet' => true,
|
||||
'Objects' => array_map(fn (array $object) => [
|
||||
'ETag' => $object['ETag'],
|
||||
'Key' => $object['Key'],
|
||||
], $objects['Contents'])
|
||||
]
|
||||
|
||||
@@ -233,7 +233,7 @@ OC.L10N.register(
|
||||
"Create a new share link" : "Креирајте нов линк за споделување",
|
||||
"Quick share options, the current selected is \"{selectedOption}\"" : "Опции за брзо споделување за , тековната избрана е \"{selectedOption}\"",
|
||||
"View only" : "Само за гледање",
|
||||
"Can edit" : "Може да се уредува",
|
||||
"Can edit" : "Може да уредува",
|
||||
"Custom permissions" : "Прилагодени дозволи",
|
||||
"Resharing is not allowed" : "Повторно споделување не е дозволено",
|
||||
"Name or email …" : "Име или е-пошта …",
|
||||
|
||||
@@ -231,7 +231,7 @@
|
||||
"Create a new share link" : "Креирајте нов линк за споделување",
|
||||
"Quick share options, the current selected is \"{selectedOption}\"" : "Опции за брзо споделување за , тековната избрана е \"{selectedOption}\"",
|
||||
"View only" : "Само за гледање",
|
||||
"Can edit" : "Може да се уредува",
|
||||
"Can edit" : "Може да уредува",
|
||||
"Custom permissions" : "Прилагодени дозволи",
|
||||
"Resharing is not allowed" : "Повторно споделување не е дозволено",
|
||||
"Name or email …" : "Име или е-пошта …",
|
||||
|
||||
@@ -155,7 +155,7 @@ class Application extends App implements IBootstrap {
|
||||
// notifications api to accept incoming user shares
|
||||
$dispatcher->addListener(ShareCreatedEvent::class, function (ShareCreatedEvent $event): void {
|
||||
/** @var Listener $listener */
|
||||
$listener = $this->getContainer()->get(Listener::class);
|
||||
$listener = $this->getContainer()->query(Listener::class);
|
||||
$listener->shareNotification($event);
|
||||
});
|
||||
$dispatcher->addListener(IGroup::class . '::postAddUser', function ($event): void {
|
||||
@@ -163,7 +163,7 @@ class Application extends App implements IBootstrap {
|
||||
return;
|
||||
}
|
||||
/** @var Listener $listener */
|
||||
$listener = $this->getContainer()->get(Listener::class);
|
||||
$listener = $this->getContainer()->query(Listener::class);
|
||||
$listener->userAddedToGroup($event);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -23,7 +23,6 @@ use OCP\Files\NotFoundException;
|
||||
use OCP\IGroupManager;
|
||||
use OCP\IRequest;
|
||||
use OCP\IUserManager;
|
||||
use OCP\PaginationParameters;
|
||||
use OCP\Server;
|
||||
use OCP\Share\Exceptions\GenericShareException;
|
||||
use OCP\Share\Exceptions\ShareNotFound;
|
||||
@@ -136,9 +135,13 @@ class DeletedShareAPIController extends OCSController {
|
||||
*/
|
||||
#[NoAdminRequired]
|
||||
public function index(): DataResponse {
|
||||
$shares = $this->shareManager->getAllDeletedSharedWith($this->userId, [IShare::TYPE_GROUP, IShare::TYPE_CIRCLE, IShare::TYPE_ROOM, IShare::TYPE_DECK], null, new PaginationParameters(limit: null));
|
||||
$groupShares = $this->shareManager->getDeletedSharedWith($this->userId, IShare::TYPE_GROUP, null, -1, 0);
|
||||
$teamShares = $this->shareManager->getDeletedSharedWith($this->userId, IShare::TYPE_CIRCLE, null, -1, 0);
|
||||
$roomShares = $this->shareManager->getDeletedSharedWith($this->userId, IShare::TYPE_ROOM, null, -1, 0);
|
||||
$deckShares = $this->shareManager->getDeletedSharedWith($this->userId, IShare::TYPE_DECK, null, -1, 0);
|
||||
|
||||
$shares = array_map(fn (IShare $share): array => $this->formatShare($share), $shares);
|
||||
$shares = array_merge($groupShares, $teamShares, $roomShares, $deckShares);
|
||||
$shares = array_values(array_map(fn (IShare $share): array => $this->formatShare($share), $shares));
|
||||
|
||||
return new DataResponse($shares);
|
||||
}
|
||||
|
||||
@@ -57,7 +57,6 @@ use OCP\Lock\ILockingProvider;
|
||||
use OCP\Lock\LockedException;
|
||||
use OCP\Mail\IEmailValidator;
|
||||
use OCP\Mail\IMailer;
|
||||
use OCP\PaginationParameters;
|
||||
use OCP\Server;
|
||||
use OCP\Share\Exceptions\GenericShareException;
|
||||
use OCP\Share\Exceptions\ShareNotFound;
|
||||
@@ -846,11 +845,20 @@ class ShareAPIController extends OCSController {
|
||||
* @return list<Files_SharingShare>
|
||||
*/
|
||||
private function getSharedWithMe($node, bool $includeTags): array {
|
||||
$shareTypes = [IShare::TYPE_USER, IShare::TYPE_GROUP, IShare::TYPE_CIRCLE, IShare::TYPE_ROOM, IShare::TYPE_DECK];
|
||||
$shares = $this->shareManager->getAllSharedWith($this->userId, $shareTypes, $node, new PaginationParameters(limit: null), ignoreWithSelf: true);
|
||||
$userShares = $this->shareManager->getSharedWith($this->userId, IShare::TYPE_USER, $node, -1, 0);
|
||||
$groupShares = $this->shareManager->getSharedWith($this->userId, IShare::TYPE_GROUP, $node, -1, 0);
|
||||
$circleShares = $this->shareManager->getSharedWith($this->userId, IShare::TYPE_CIRCLE, $node, -1, 0);
|
||||
$roomShares = $this->shareManager->getSharedWith($this->userId, IShare::TYPE_ROOM, $node, -1, 0);
|
||||
$deckShares = $this->shareManager->getSharedWith($this->userId, IShare::TYPE_DECK, $node, -1, 0);
|
||||
|
||||
$shares = array_merge($userShares, $groupShares, $circleShares, $roomShares, $deckShares);
|
||||
|
||||
$filteredShares = array_filter($shares, function (IShare $share) {
|
||||
return $share->getShareOwner() !== $this->userId && $share->getSharedBy() !== $this->userId;
|
||||
});
|
||||
|
||||
$formatted = [];
|
||||
foreach ($shares as $share) {
|
||||
foreach ($filteredShares as $share) {
|
||||
if ($this->canAccessShare($share)) {
|
||||
try {
|
||||
$formatted[] = $this->formatShare($share);
|
||||
@@ -879,30 +887,38 @@ class ShareAPIController extends OCSController {
|
||||
throw new OCSBadRequestException($this->l->t('Not a directory'));
|
||||
}
|
||||
|
||||
$nodes = $folder->getDirectoryListing();
|
||||
|
||||
/** @var IShare[] $shares */
|
||||
$shares = array_reduce($nodes, function ($carry, $node) {
|
||||
$carry = array_merge($carry, $this->getAllShares($node, true));
|
||||
return $carry;
|
||||
}, []);
|
||||
|
||||
// filter out duplicate shares
|
||||
$known = [];
|
||||
|
||||
$formatted = $miniFormatted = [];
|
||||
$resharingRight = false;
|
||||
$known = [];
|
||||
foreach ($folder->getDirectoryListing() as $node) {
|
||||
foreach ($this->getAllShares($node, true) as $share) {
|
||||
if (in_array($share->getId(), $known) || $share->getSharedWith() === $this->userId) {
|
||||
continue;
|
||||
}
|
||||
foreach ($shares as $share) {
|
||||
if (in_array($share->getId(), $known) || $share->getSharedWith() === $this->userId) {
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
$format = $this->formatShare($share);
|
||||
try {
|
||||
$format = $this->formatShare($share);
|
||||
|
||||
$known[] = $share->getId();
|
||||
$formatted[] = $format;
|
||||
if ($share->getSharedBy() === $this->userId) {
|
||||
$miniFormatted[] = $format;
|
||||
}
|
||||
if (!$resharingRight && $this->shareProviderResharingRights($this->userId, $share, $folder)) {
|
||||
$resharingRight = true;
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
//Ignore this share
|
||||
$known[] = $share->getId();
|
||||
$formatted[] = $format;
|
||||
if ($share->getSharedBy() === $this->userId) {
|
||||
$miniFormatted[] = $format;
|
||||
}
|
||||
if (!$resharingRight && $this->shareProviderResharingRights($this->userId, $share, $folder)) {
|
||||
$resharingRight = true;
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
//Ignore this share
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1408,15 +1424,17 @@ class ShareAPIController extends OCSController {
|
||||
IShare::TYPE_GROUP
|
||||
];
|
||||
|
||||
$shares = $this->shareManager->getAllSharedWith($this->userId, $shareTypes, null, new PaginationParameters(limit: null));
|
||||
foreach ($shareTypes as $shareType) {
|
||||
$shares = $this->shareManager->getSharedWith($this->userId, $shareType, null, -1, 0);
|
||||
|
||||
foreach ($shares as $share) {
|
||||
if ($share->getStatus() === IShare::STATUS_PENDING || $share->getStatus() === IShare::STATUS_REJECTED) {
|
||||
$pendingShares[] = $share;
|
||||
foreach ($shares as $share) {
|
||||
if ($share->getStatus() === IShare::STATUS_PENDING || $share->getStatus() === IShare::STATUS_REJECTED) {
|
||||
$pendingShares[] = $share;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$result = array_values(array_filter(array_map(function (IShare $share): ?array {
|
||||
$result = array_values(array_filter(array_map(function (IShare $share) {
|
||||
$userFolder = $this->rootFolder->getUserFolder($share->getSharedBy());
|
||||
$node = $userFolder->getFirstNodeById($share->getNodeId());
|
||||
if (!$node) {
|
||||
@@ -1435,7 +1453,7 @@ class ShareAPIController extends OCSController {
|
||||
} catch (NotFoundException $e) {
|
||||
return null;
|
||||
}
|
||||
}, $pendingShares), function (?array $entry): bool {
|
||||
}, $pendingShares), function ($entry) {
|
||||
return $entry !== null;
|
||||
}));
|
||||
|
||||
@@ -1957,10 +1975,39 @@ class ShareAPIController extends OCSController {
|
||||
*
|
||||
* @param Node|null $path
|
||||
* @param boolean $reshares
|
||||
* @return list<IShare>
|
||||
* @return IShare[]
|
||||
*/
|
||||
private function getAllShares(?Node $path = null, bool $reshares = false): array {
|
||||
return $this->shareManager->getAllSharesBy($this->userId, $path, new PaginationParameters(limit: null), $reshares);
|
||||
private function getAllShares(?Node $path = null, bool $reshares = false) {
|
||||
// Get all shares
|
||||
$userShares = $this->shareManager->getSharesBy($this->userId, IShare::TYPE_USER, $path, $reshares, -1, 0);
|
||||
$groupShares = $this->shareManager->getSharesBy($this->userId, IShare::TYPE_GROUP, $path, $reshares, -1, 0);
|
||||
$linkShares = $this->shareManager->getSharesBy($this->userId, IShare::TYPE_LINK, $path, $reshares, -1, 0);
|
||||
|
||||
// EMAIL SHARES
|
||||
$mailShares = $this->shareManager->getSharesBy($this->userId, IShare::TYPE_EMAIL, $path, $reshares, -1, 0);
|
||||
|
||||
// TEAM SHARES
|
||||
$circleShares = $this->shareManager->getSharesBy($this->userId, IShare::TYPE_CIRCLE, $path, $reshares, -1, 0);
|
||||
|
||||
// TALK SHARES
|
||||
$roomShares = $this->shareManager->getSharesBy($this->userId, IShare::TYPE_ROOM, $path, $reshares, -1, 0);
|
||||
|
||||
// DECK SHARES
|
||||
$deckShares = $this->shareManager->getSharesBy($this->userId, IShare::TYPE_DECK, $path, $reshares, -1, 0);
|
||||
|
||||
// FEDERATION
|
||||
if ($this->shareManager->outgoingServer2ServerSharesAllowed()) {
|
||||
$federatedShares = $this->shareManager->getSharesBy($this->userId, IShare::TYPE_REMOTE, $path, $reshares, -1, 0);
|
||||
} else {
|
||||
$federatedShares = [];
|
||||
}
|
||||
if ($this->shareManager->outgoingServer2ServerGroupSharesAllowed()) {
|
||||
$federatedGroupShares = $this->shareManager->getSharesBy($this->userId, IShare::TYPE_REMOTE_GROUP, $path, $reshares, -1, 0);
|
||||
} else {
|
||||
$federatedGroupShares = [];
|
||||
}
|
||||
|
||||
return array_merge($userShares, $groupShares, $linkShares, $mailShares, $circleShares, $roomShares, $deckShares, $federatedShares, $federatedGroupShares);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -13,7 +13,6 @@ use OCP\EventDispatcher\Event;
|
||||
use OCP\EventDispatcher\IEventListener;
|
||||
use OCP\Group\Events\UserAddedEvent;
|
||||
use OCP\IConfig;
|
||||
use OCP\PaginationParameters;
|
||||
use OCP\Share\IManager;
|
||||
use OCP\Share\IShare;
|
||||
|
||||
@@ -40,7 +39,7 @@ class UserAddedToGroupListener implements IEventListener {
|
||||
}
|
||||
|
||||
// Get all group shares this user has access to now to filter later
|
||||
$shares = $this->shareManager->getAllSharedWith($user->getUID(), [IShare::TYPE_GROUP], null, new PaginationParameters());
|
||||
$shares = $this->shareManager->getSharedWith($user->getUID(), IShare::TYPE_GROUP, null, -1);
|
||||
|
||||
foreach ($shares as $share) {
|
||||
// If this is not the new group we can skip it
|
||||
|
||||
@@ -21,7 +21,6 @@ use OCP\Files\Storage\IStorageFactory;
|
||||
use OCP\ICacheFactory;
|
||||
use OCP\IConfig;
|
||||
use OCP\IUser;
|
||||
use OCP\PaginationParameters;
|
||||
use OCP\Share\IAttributes;
|
||||
use OCP\Share\IManager;
|
||||
use OCP\Share\IShare;
|
||||
@@ -53,10 +52,15 @@ class MountProvider implements IMountProvider, IAuthoritativeMountProvider, IPar
|
||||
*/
|
||||
public function getSuperSharesForUser(IUser $user, array $excludeShares = []): array {
|
||||
$userId = $user->getUID();
|
||||
$shareTypes = [IShare::TYPE_USER, IShare::TYPE_GROUP, IShare::TYPE_CIRCLE, IShare::TYPE_ROOM, IShare::TYPE_DECK];
|
||||
$shares = $this->mergeIterables(
|
||||
$this->shareManager->getSharedWith($userId, IShare::TYPE_USER, null, -1),
|
||||
$this->shareManager->getSharedWith($userId, IShare::TYPE_GROUP, null, -1),
|
||||
$this->shareManager->getSharedWith($userId, IShare::TYPE_CIRCLE, null, -1),
|
||||
$this->shareManager->getSharedWith($userId, IShare::TYPE_ROOM, null, -1),
|
||||
$this->shareManager->getSharedWith($userId, IShare::TYPE_DECK, null, -1),
|
||||
);
|
||||
|
||||
$excludeShareIds = array_map(fn (IShare $share) => $share->getFullId(), $excludeShares);
|
||||
$shares = $this->shareManager->getAllSharedWith($userId, $shareTypes, null, new PaginationParameters(limit: null), ignoreWithSelf: true);
|
||||
$shares = $this->filterShares($shares, $userId, $excludeShareIds);
|
||||
return $this->buildSuperShares($shares, $user);
|
||||
}
|
||||
|
||||
@@ -13,7 +13,6 @@ use OCP\IGroupManager;
|
||||
use OCP\IUser;
|
||||
use OCP\Notification\IManager as INotificationManager;
|
||||
use OCP\Notification\INotification;
|
||||
use OCP\PaginationParameters;
|
||||
use OCP\Share\Events\ShareCreatedEvent;
|
||||
use OCP\Share\IManager as IShareManager;
|
||||
use OCP\Share\IShare;
|
||||
@@ -61,17 +60,12 @@ class Listener {
|
||||
/** @var IUser $user */
|
||||
$user = $event->getArgument('user');
|
||||
|
||||
$sinceShareId = null;
|
||||
$paginationParameters = new PaginationParameters(
|
||||
limit: 50,
|
||||
maxId: null,
|
||||
);
|
||||
$offset = 0;
|
||||
while (true) {
|
||||
$shares = $this->shareManager->getAllSharedWith($user->getUID(), [IShare::TYPE_GROUP], null, $paginationParameters);
|
||||
$shares = $this->shareManager->getSharedWith($user->getUID(), IShare::TYPE_GROUP, null, 50, $offset);
|
||||
if (empty($shares)) {
|
||||
break;
|
||||
}
|
||||
$paginationParameters->maxId = end($shares)->getId();
|
||||
|
||||
foreach ($shares as $share) {
|
||||
if ($share->getSharedWith() !== $group->getGID()) {
|
||||
@@ -88,6 +82,7 @@ class Listener {
|
||||
->setUser($user->getUID());
|
||||
$this->notificationManager->notify($notification);
|
||||
}
|
||||
$offset += 50;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -18,7 +18,6 @@ use OCP\Files\Mount\IMountManager;
|
||||
use OCP\IAppConfig;
|
||||
use OCP\IConfig;
|
||||
use OCP\IDateTimeZone;
|
||||
use OCP\IDBConnection;
|
||||
use OCP\IGroupManager;
|
||||
use OCP\IUserManager;
|
||||
use OCP\IUserSession;
|
||||
@@ -93,7 +92,6 @@ class CapabilitiesTest extends \Test\TestCase {
|
||||
$this->createMock(ShareDisableChecker::class),
|
||||
$this->createMock(IDateTimeZone::class),
|
||||
$appConfig,
|
||||
$this->createMock(IDBConnection::class),
|
||||
);
|
||||
|
||||
$cap = new Capabilities($config, $appConfig, $shareManager, $appManager);
|
||||
|
||||
@@ -16,11 +16,9 @@ use OCP\IConfig;
|
||||
use OCP\IL10N;
|
||||
use OCP\IURLGenerator;
|
||||
use OCP\Share\IManager;
|
||||
use PHPUnit\Framework\Attributes\Group;
|
||||
use PHPUnit\Framework\MockObject\MockObject;
|
||||
use Test\TestCase;
|
||||
|
||||
#[Group(name: 'DB')]
|
||||
class SharingTest extends TestCase {
|
||||
private Sharing $admin;
|
||||
|
||||
|
||||
@@ -38,9 +38,7 @@ use OCP\Share\IManager as IShareManager;
|
||||
use OCP\Share\IShare;
|
||||
use OCP\Share\IShareProviderWithNotification;
|
||||
use OCP\Util;
|
||||
use Override;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Symfony\Component\Console\Exception\LogicException;
|
||||
|
||||
/**
|
||||
* Class ShareByMail
|
||||
@@ -48,42 +46,41 @@ use Symfony\Component\Console\Exception\LogicException;
|
||||
* @package OCA\ShareByMail
|
||||
*/
|
||||
class ShareByMailProvider extends DefaultShareProvider implements IShareProviderWithNotification {
|
||||
public function __construct(
|
||||
private readonly IConfig $config,
|
||||
private readonly IDBConnection $dbConnection,
|
||||
private readonly ISecureRandom $secureRandom,
|
||||
private readonly IUserManager $userManager,
|
||||
private readonly IRootFolder $rootFolder,
|
||||
private readonly IL10N $l,
|
||||
private readonly LoggerInterface $logger,
|
||||
private readonly IMailer $mailer,
|
||||
private readonly IURLGenerator $urlGenerator,
|
||||
private readonly IManager $activityManager,
|
||||
private readonly SettingsManager $settingsManager,
|
||||
private readonly Defaults $defaults,
|
||||
private readonly IHasher $hasher,
|
||||
private readonly IEventDispatcher $eventDispatcher,
|
||||
private readonly IShareManager $shareManager,
|
||||
private readonly IEmailValidator $emailValidator,
|
||||
) {
|
||||
}
|
||||
|
||||
#[Override]
|
||||
/**
|
||||
* Return the identifier of this provider.
|
||||
*
|
||||
* @return string Containing only [a-zA-Z0-9]
|
||||
*/
|
||||
public function identifier(): string {
|
||||
return 'ocMailShare';
|
||||
}
|
||||
|
||||
#[Override]
|
||||
public function getShareTypes(): array {
|
||||
return [IShare::TYPE_EMAIL];
|
||||
public function __construct(
|
||||
private IConfig $config,
|
||||
private IDBConnection $dbConnection,
|
||||
private ISecureRandom $secureRandom,
|
||||
private IUserManager $userManager,
|
||||
private IRootFolder $rootFolder,
|
||||
private IL10N $l,
|
||||
private LoggerInterface $logger,
|
||||
private IMailer $mailer,
|
||||
private IURLGenerator $urlGenerator,
|
||||
private IManager $activityManager,
|
||||
private SettingsManager $settingsManager,
|
||||
private Defaults $defaults,
|
||||
private IHasher $hasher,
|
||||
private IEventDispatcher $eventDispatcher,
|
||||
private IShareManager $shareManager,
|
||||
private IEmailValidator $emailValidator,
|
||||
) {
|
||||
}
|
||||
|
||||
#[Override]
|
||||
public function getTokenShareTypes(): array {
|
||||
return [IShare::TYPE_EMAIL];
|
||||
}
|
||||
|
||||
#[Override]
|
||||
/**
|
||||
* Share a path
|
||||
*
|
||||
* @throws ShareNotFound
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function create(IShare $share): IShare {
|
||||
$shareWith = $share->getSharedWith();
|
||||
// Check if file is not already shared with the given email,
|
||||
@@ -803,17 +800,97 @@ class ShareByMailProvider extends DefaultShareProvider implements IShareProvider
|
||||
throw new GenericShareException('not implemented');
|
||||
}
|
||||
|
||||
#[Override]
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function getSharesBy($userId, $shareType, $node, $reshares, $limit, $offset): array {
|
||||
throw new LogicException('Is no longer used');
|
||||
$qb = $this->dbConnection->getQueryBuilder();
|
||||
$qb->select('*')
|
||||
->from('share');
|
||||
|
||||
$qb->andWhere($qb->expr()->eq('share_type', $qb->createNamedParameter(IShare::TYPE_EMAIL)));
|
||||
|
||||
/**
|
||||
* Reshares for this user are shares where they are the owner.
|
||||
*/
|
||||
if ($reshares === false) {
|
||||
//Special case for old shares created via the web UI
|
||||
$or1 = $qb->expr()->andX(
|
||||
$qb->expr()->eq('uid_owner', $qb->createNamedParameter($userId)),
|
||||
$qb->expr()->isNull('uid_initiator')
|
||||
);
|
||||
|
||||
$qb->andWhere(
|
||||
$qb->expr()->orX(
|
||||
$qb->expr()->eq('uid_initiator', $qb->createNamedParameter($userId)),
|
||||
$or1
|
||||
)
|
||||
);
|
||||
} elseif ($node === null) {
|
||||
$qb->andWhere(
|
||||
$qb->expr()->orX(
|
||||
$qb->expr()->eq('uid_owner', $qb->createNamedParameter($userId)),
|
||||
$qb->expr()->eq('uid_initiator', $qb->createNamedParameter($userId))
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if ($node !== null) {
|
||||
$qb->andWhere($qb->expr()->eq('file_source', $qb->createNamedParameter($node->getId())));
|
||||
}
|
||||
|
||||
if ($limit !== -1) {
|
||||
$qb->setMaxResults($limit);
|
||||
}
|
||||
|
||||
$qb->setFirstResult($offset);
|
||||
$qb->orderBy('id');
|
||||
|
||||
$cursor = $qb->executeQuery();
|
||||
$shares = [];
|
||||
while ($data = $cursor->fetchAssociative()) {
|
||||
$shares[] = $this->createShareObject($data);
|
||||
}
|
||||
$cursor->closeCursor();
|
||||
|
||||
return $shares;
|
||||
}
|
||||
|
||||
#[Override]
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function getShareById($id, $recipientId = null): IShare {
|
||||
throw new LogicException('Is no longer used');
|
||||
$qb = $this->dbConnection->getQueryBuilder();
|
||||
|
||||
$qb->select('*')
|
||||
->from('share')
|
||||
->where($qb->expr()->eq('id', $qb->createNamedParameter($id)))
|
||||
->andWhere($qb->expr()->eq('share_type', $qb->createNamedParameter(IShare::TYPE_EMAIL)));
|
||||
|
||||
$cursor = $qb->executeQuery();
|
||||
$data = $cursor->fetchAssociative();
|
||||
$cursor->closeCursor();
|
||||
|
||||
if ($data === false) {
|
||||
throw new ShareNotFound();
|
||||
}
|
||||
|
||||
$data['id'] = (string)$data['id'];
|
||||
|
||||
try {
|
||||
$share = $this->createShareObject($data);
|
||||
} catch (InvalidShare $e) {
|
||||
throw new ShareNotFound();
|
||||
}
|
||||
|
||||
return $share;
|
||||
}
|
||||
|
||||
#[Override]
|
||||
/**
|
||||
* Get shares for a given path
|
||||
*
|
||||
* @return IShare[]
|
||||
*/
|
||||
public function getSharesByPath(Node $path): array {
|
||||
$qb = $this->dbConnection->getQueryBuilder();
|
||||
|
||||
@@ -833,7 +910,9 @@ class ShareByMailProvider extends DefaultShareProvider implements IShareProvider
|
||||
return $shares;
|
||||
}
|
||||
|
||||
#[Override]
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function getSharedWith($userId, $shareType, $node, $limit, $offset): array {
|
||||
/** @var IShare[] $shares */
|
||||
$shares = [];
|
||||
@@ -871,9 +950,35 @@ class ShareByMailProvider extends DefaultShareProvider implements IShareProvider
|
||||
return $shares;
|
||||
}
|
||||
|
||||
#[Override]
|
||||
public function getShareByToken(string $token): never {
|
||||
throw new LogicException('Is no longer used');
|
||||
/**
|
||||
* Get a share by token
|
||||
*
|
||||
* @throws ShareNotFound
|
||||
*/
|
||||
public function getShareByToken($token): IShare {
|
||||
$qb = $this->dbConnection->getQueryBuilder();
|
||||
|
||||
$cursor = $qb->select('*')
|
||||
->from('share')
|
||||
->where($qb->expr()->eq('share_type', $qb->createNamedParameter(IShare::TYPE_EMAIL)))
|
||||
->andWhere($qb->expr()->eq('token', $qb->createNamedParameter($token)))
|
||||
->executeQuery();
|
||||
|
||||
$data = $cursor->fetchAssociative();
|
||||
|
||||
if ($data === false) {
|
||||
throw new ShareNotFound('Share not found', $this->l->t('Could not find share'));
|
||||
}
|
||||
|
||||
$data['id'] = (string)$data['id'];
|
||||
|
||||
try {
|
||||
$share = $this->createShareObject($data);
|
||||
} catch (InvalidShare $e) {
|
||||
throw new ShareNotFound('Share not found', $this->l->t('Could not find share'));
|
||||
}
|
||||
|
||||
return $share;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1100,7 +1205,29 @@ class ShareByMailProvider extends DefaultShareProvider implements IShareProvider
|
||||
}
|
||||
|
||||
public function getAllShares(): iterable {
|
||||
throw new \LogicException('getAllShare in DefaultShareProvider should no longer be used');
|
||||
$qb = $this->dbConnection->getQueryBuilder();
|
||||
|
||||
$qb->select('*')
|
||||
->from('share')
|
||||
->where(
|
||||
$qb->expr()->orX(
|
||||
$qb->expr()->eq('share_type', $qb->createNamedParameter(IShare::TYPE_EMAIL))
|
||||
)
|
||||
);
|
||||
|
||||
$cursor = $qb->executeQuery();
|
||||
while ($data = $cursor->fetchAssociative()) {
|
||||
try {
|
||||
$share = $this->createShareObject($data);
|
||||
} catch (InvalidShare $e) {
|
||||
continue;
|
||||
} catch (ShareNotFound $e) {
|
||||
continue;
|
||||
}
|
||||
|
||||
yield $share;
|
||||
}
|
||||
$cursor->closeCursor();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Generated
+21
-21
@@ -6730,9 +6730,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/asn1.js/node_modules/bn.js": {
|
||||
"version": "4.12.2",
|
||||
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.2.tgz",
|
||||
"integrity": "sha512-n4DSx829VRTRByMRGdjQ9iqsN0Bh4OolPsFnaZBLcbi8iXcB+kJ9s7EnRt4wILZNV3kPLHkRVfOc/HvhC3ovDw==",
|
||||
"version": "4.12.3",
|
||||
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.3.tgz",
|
||||
"integrity": "sha512-fGTi3gxV/23FTYdAoUtLYp6qySe2KE3teyZitipKNRuVYcBkoP/bB3guXN/XVKUe9mxCHXnc9C4ocyz8OmgN0g==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/assert": {
|
||||
@@ -7084,9 +7084,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/bn.js": {
|
||||
"version": "5.2.2",
|
||||
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.2.tgz",
|
||||
"integrity": "sha512-v2YAxEmKaBLahNwE1mjp4WON6huMNeuDvagFZW+ASCuA/ku0bXR9hSMw0XpiqMoA3+rmnyck/tPRSFQkoC9Cuw==",
|
||||
"version": "5.2.3",
|
||||
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.3.tgz",
|
||||
"integrity": "sha512-EAcmnPkxpntVL+DS7bO1zhcZNvCkxqtkd0ZY53h06GNQ3DEkkGZ/gKgmDv6DdZQGj9BgfSPKtJJ7Dp1GPP8f7w==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/body-parser": {
|
||||
@@ -8114,9 +8114,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/create-ecdh/node_modules/bn.js": {
|
||||
"version": "4.12.2",
|
||||
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.2.tgz",
|
||||
"integrity": "sha512-n4DSx829VRTRByMRGdjQ9iqsN0Bh4OolPsFnaZBLcbi8iXcB+kJ9s7EnRt4wILZNV3kPLHkRVfOc/HvhC3ovDw==",
|
||||
"version": "4.12.3",
|
||||
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.3.tgz",
|
||||
"integrity": "sha512-fGTi3gxV/23FTYdAoUtLYp6qySe2KE3teyZitipKNRuVYcBkoP/bB3guXN/XVKUe9mxCHXnc9C4ocyz8OmgN0g==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/create-hash": {
|
||||
@@ -8690,9 +8690,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/diffie-hellman/node_modules/bn.js": {
|
||||
"version": "4.12.2",
|
||||
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.2.tgz",
|
||||
"integrity": "sha512-n4DSx829VRTRByMRGdjQ9iqsN0Bh4OolPsFnaZBLcbi8iXcB+kJ9s7EnRt4wILZNV3kPLHkRVfOc/HvhC3ovDw==",
|
||||
"version": "4.12.3",
|
||||
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.3.tgz",
|
||||
"integrity": "sha512-fGTi3gxV/23FTYdAoUtLYp6qySe2KE3teyZitipKNRuVYcBkoP/bB3guXN/XVKUe9mxCHXnc9C4ocyz8OmgN0g==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/dijkstrajs": {
|
||||
@@ -8889,9 +8889,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/elliptic/node_modules/bn.js": {
|
||||
"version": "4.12.2",
|
||||
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.2.tgz",
|
||||
"integrity": "sha512-n4DSx829VRTRByMRGdjQ9iqsN0Bh4OolPsFnaZBLcbi8iXcB+kJ9s7EnRt4wILZNV3kPLHkRVfOc/HvhC3ovDw==",
|
||||
"version": "4.12.3",
|
||||
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.3.tgz",
|
||||
"integrity": "sha512-fGTi3gxV/23FTYdAoUtLYp6qySe2KE3teyZitipKNRuVYcBkoP/bB3guXN/XVKUe9mxCHXnc9C4ocyz8OmgN0g==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/emoji-mart-vue-fast": {
|
||||
@@ -13028,9 +13028,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/miller-rabin/node_modules/bn.js": {
|
||||
"version": "4.12.2",
|
||||
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.2.tgz",
|
||||
"integrity": "sha512-n4DSx829VRTRByMRGdjQ9iqsN0Bh4OolPsFnaZBLcbi8iXcB+kJ9s7EnRt4wILZNV3kPLHkRVfOc/HvhC3ovDw==",
|
||||
"version": "4.12.3",
|
||||
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.3.tgz",
|
||||
"integrity": "sha512-fGTi3gxV/23FTYdAoUtLYp6qySe2KE3teyZitipKNRuVYcBkoP/bB3guXN/XVKUe9mxCHXnc9C4ocyz8OmgN0g==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/mime": {
|
||||
@@ -14750,9 +14750,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/public-encrypt/node_modules/bn.js": {
|
||||
"version": "4.12.2",
|
||||
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.2.tgz",
|
||||
"integrity": "sha512-n4DSx829VRTRByMRGdjQ9iqsN0Bh4OolPsFnaZBLcbi8iXcB+kJ9s7EnRt4wILZNV3kPLHkRVfOc/HvhC3ovDw==",
|
||||
"version": "4.12.3",
|
||||
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.3.tgz",
|
||||
"integrity": "sha512-fGTi3gxV/23FTYdAoUtLYp6qySe2KE3teyZitipKNRuVYcBkoP/bB3guXN/XVKUe9mxCHXnc9C4ocyz8OmgN0g==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/punycode": {
|
||||
|
||||
@@ -21,6 +21,38 @@ $qb->selectColumnsDistinct('f');
|
||||
$qb->selectAlias('g', 'h');
|
||||
$qb->selectAlias($qb->func()->lower('i'), 'j');
|
||||
|
||||
$qb
|
||||
->setParameter('k', 'l')
|
||||
->setParameters([])
|
||||
->setFirstResult(0)
|
||||
->setMaxResults(0)
|
||||
->delete()
|
||||
->update()
|
||||
->insert()
|
||||
->from('m')
|
||||
->join('n', 'o', 'p')
|
||||
->innerJoin('q', 'r', 's')
|
||||
->leftJoin('t', 'u', 'v')
|
||||
->rightJoin('w', 'x', 'y')
|
||||
->set('z', '1')
|
||||
->where()
|
||||
->andWhere()
|
||||
->orWhere()
|
||||
->groupBy()
|
||||
->addGroupBy()
|
||||
->setValue('2', '3')
|
||||
->values([])
|
||||
->having()
|
||||
->andHaving()
|
||||
->orHaving()
|
||||
->orderBy('4')
|
||||
->addOrderBy('5')
|
||||
->resetQueryParts()
|
||||
->resetQueryPart('6')
|
||||
->hintShardKey('7', '8')
|
||||
->runAcrossAllShards()
|
||||
->forUpdate();
|
||||
|
||||
/** @psalm-check-type-exact $result = \OCP\DB\IResult<'a'|'b'|'c'|'d'|'e'|'f'|'h'|'j'> */
|
||||
$result = $qb->executeQuery();
|
||||
|
||||
|
||||
@@ -82,7 +82,7 @@ class StatusCommand extends Command implements CompletionAwareInterface {
|
||||
|
||||
$numExecutedUnavailableMigrations = count($executedUnavailableMigrations);
|
||||
$numNewMigrations = count(array_diff(array_keys($availableMigrations), $executedMigrations));
|
||||
$pending = $ms->describeMigrationStep('lastest');
|
||||
$pending = $ms->describeMigrationStep();
|
||||
|
||||
$infos = [
|
||||
'App' => $ms->getApp(),
|
||||
|
||||
@@ -43,6 +43,12 @@ class ResetPassword extends Base {
|
||||
InputOption::VALUE_NONE,
|
||||
'read password from environment variable NC_PASS/OC_PASS'
|
||||
)
|
||||
->addOption(
|
||||
'no-password',
|
||||
null,
|
||||
InputOption::VALUE_NONE,
|
||||
'Sets the password to blank'
|
||||
)
|
||||
;
|
||||
}
|
||||
|
||||
@@ -76,22 +82,32 @@ class ResetPassword extends Base {
|
||||
}
|
||||
}
|
||||
|
||||
$question = new Question('Enter a new password: ');
|
||||
$question->setHidden(true);
|
||||
$password = $helper->ask($input, $output, $question);
|
||||
if ($input->getOption('no-password')) {
|
||||
$question = new ConfirmationQuestion('Are you sure you want to clear the password for ' . $username . '?');
|
||||
|
||||
if ($password === null) {
|
||||
$output->writeln('<error>Password cannot be empty!</error>');
|
||||
return 1;
|
||||
}
|
||||
if (!$helper->ask($input, $output, $question)) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
$question = new Question('Confirm the new password: ');
|
||||
$question->setHidden(true);
|
||||
$confirm = $helper->ask($input, $output, $question);
|
||||
$password = '';
|
||||
} else {
|
||||
$question = new Question('Enter a new password: ');
|
||||
$question->setHidden(true);
|
||||
$password = $helper->ask($input, $output, $question);
|
||||
|
||||
if ($password !== $confirm) {
|
||||
$output->writeln('<error>Passwords did not match!</error>');
|
||||
return 1;
|
||||
if ($password === null) {
|
||||
$output->writeln('<error>Password cannot be empty!</error>');
|
||||
return 1;
|
||||
}
|
||||
|
||||
$question = new Question('Confirm the new password: ');
|
||||
$question->setHidden(true);
|
||||
$confirm = $helper->ask($input, $output, $question);
|
||||
|
||||
if ($password !== $confirm) {
|
||||
$output->writeln('<error>Passwords did not match!</error>');
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$output->writeln('<error>Interactive input or --password-from-env is needed for entering a new password!</error>');
|
||||
|
||||
+4
-3
@@ -766,9 +766,10 @@ class OC {
|
||||
self::checkConfig();
|
||||
self::checkInstalled($systemConfig);
|
||||
|
||||
self::addSecurityHeaders();
|
||||
|
||||
self::performSameSiteCookieProtection($config);
|
||||
if (!self::$CLI) {
|
||||
self::addSecurityHeaders();
|
||||
self::performSameSiteCookieProtection($config);
|
||||
}
|
||||
|
||||
if (!defined('OC_CONSOLE')) {
|
||||
$eventLogger->start('check_server', 'Run a few configuration checks');
|
||||
|
||||
@@ -800,14 +800,24 @@ class ObjectStoreStorage extends Common implements IChunkedFileWrite {
|
||||
$this->getCache()->update($stat['fileid'], $stat);
|
||||
}
|
||||
} catch (S3MultipartUploadException|S3Exception $e) {
|
||||
$this->objectStore->abortMultipartUpload($urn, $writeToken);
|
||||
$this->logger->error(
|
||||
'Could not complete multipart upload ' . $urn . ' with uploadId ' . $writeToken,
|
||||
'Unable to complete multipart upload for "' . $urn . '" (uploadId: "' . $writeToken . '")',
|
||||
[
|
||||
'app' => 'objectstore',
|
||||
'exception' => $e,
|
||||
]
|
||||
);
|
||||
try {
|
||||
$this->objectStore->abortMultipartUpload($urn, $writeToken);
|
||||
} catch (S3Exception $e) {
|
||||
$this->logger->error(
|
||||
'Unable to abort multipart upload for "' . $urn . '" (uploadId: "' . $writeToken . '") after completion error',
|
||||
[
|
||||
'app' => 'objectstore',
|
||||
'exception' => $e,
|
||||
]
|
||||
);
|
||||
}
|
||||
throw new GenericFileException('Could not write chunked file');
|
||||
}
|
||||
return $size;
|
||||
|
||||
@@ -145,7 +145,7 @@ class Factory implements IFactory {
|
||||
if ($lang === null) {
|
||||
return null;
|
||||
}
|
||||
$lang = preg_replace('/[^a-zA-Z0-9.;,=-]/', '', $lang);
|
||||
$lang = preg_replace('/[^a-zA-Z0-9.;,=_-]/', '', $lang);
|
||||
return str_replace('..', '', $lang);
|
||||
}
|
||||
|
||||
|
||||
@@ -199,14 +199,19 @@ class Repair implements IOutput {
|
||||
* @return list<IRepairStep>
|
||||
*/
|
||||
public static function getExpensiveRepairSteps(): array {
|
||||
return [
|
||||
$expensiveSteps = [
|
||||
Server::get(OldGroupMembershipShares::class),
|
||||
Server::get(RemoveBrokenProperties::class),
|
||||
Server::get(RepairMimeTypes::class),
|
||||
Server::get(DeleteSchedulingObjects::class),
|
||||
Server::get(RemoveObjectProperties::class),
|
||||
Server::get(CleanupShareTarget::class),
|
||||
];
|
||||
|
||||
if (class_exists(CleanupShareTarget::class)) {
|
||||
$expensiveSteps[] = Server::get(CleanupShareTarget::class);
|
||||
}
|
||||
|
||||
return $expensiveSteps;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -37,7 +37,6 @@ use OCP\Mail\IMailer;
|
||||
use OCP\Server;
|
||||
use OCP\Share\Exceptions\ShareNotFound;
|
||||
use OCP\Share\IAttributes;
|
||||
use OCP\Share\ICreateShareProvider;
|
||||
use OCP\Share\IManager;
|
||||
use OCP\Share\IPartialShareProvider;
|
||||
use OCP\Share\IShare;
|
||||
@@ -46,7 +45,6 @@ use OCP\Share\IShareProviderSupportsAccept;
|
||||
use OCP\Share\IShareProviderSupportsAllSharesInFolder;
|
||||
use OCP\Share\IShareProviderWithNotification;
|
||||
use OCP\Util;
|
||||
use Override;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use function str_starts_with;
|
||||
use function strlen;
|
||||
@@ -61,41 +59,41 @@ class DefaultShareProvider implements
|
||||
IShareProviderSupportsAccept,
|
||||
IShareProviderSupportsAllSharesInFolder,
|
||||
IShareProviderGetUsers,
|
||||
IPartialShareProvider,
|
||||
ICreateShareProvider {
|
||||
IPartialShareProvider {
|
||||
public function __construct(
|
||||
private readonly IDBConnection $dbConn,
|
||||
private readonly IUserManager $userManager,
|
||||
private readonly IGroupManager $groupManager,
|
||||
private readonly IRootFolder $rootFolder,
|
||||
private readonly IMailer $mailer,
|
||||
private readonly Defaults $defaults,
|
||||
private readonly IFactory $l10nFactory,
|
||||
private readonly IURLGenerator $urlGenerator,
|
||||
private readonly ITimeFactory $timeFactory,
|
||||
private readonly LoggerInterface $logger,
|
||||
private readonly IManager $shareManager,
|
||||
private readonly IConfig $config,
|
||||
private IDBConnection $dbConn,
|
||||
private IUserManager $userManager,
|
||||
private IGroupManager $groupManager,
|
||||
private IRootFolder $rootFolder,
|
||||
private IMailer $mailer,
|
||||
private Defaults $defaults,
|
||||
private IFactory $l10nFactory,
|
||||
private IURLGenerator $urlGenerator,
|
||||
private ITimeFactory $timeFactory,
|
||||
private LoggerInterface $logger,
|
||||
private IManager $shareManager,
|
||||
private IConfig $config,
|
||||
) {
|
||||
}
|
||||
|
||||
#[Override]
|
||||
public function identifier(): string {
|
||||
/**
|
||||
* Return the identifier of this provider.
|
||||
*
|
||||
* @return string Containing only [a-zA-Z0-9]
|
||||
*/
|
||||
public function identifier() {
|
||||
return 'ocinternal';
|
||||
}
|
||||
|
||||
#[Override]
|
||||
public function getShareTypes(): array {
|
||||
return [IShare::TYPE_USER, IShare::TYPE_GROUP, IShare::TYPE_LINK];
|
||||
}
|
||||
|
||||
#[Override]
|
||||
public function getTokenShareTypes(): array {
|
||||
return [IShare::TYPE_LINK];
|
||||
}
|
||||
|
||||
#[Override]
|
||||
public function create(IShare $share): IShare {
|
||||
/**
|
||||
* Share a path
|
||||
*
|
||||
* @param IShare $share
|
||||
* @return IShare The share object
|
||||
* @throws ShareNotFound
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function create(IShare $share) {
|
||||
$qb = $this->dbConn->getQueryBuilder();
|
||||
|
||||
$qb->insert('share');
|
||||
@@ -205,8 +203,16 @@ class DefaultShareProvider implements
|
||||
return $share;
|
||||
}
|
||||
|
||||
#[Override]
|
||||
public function update(IShare $share): IShare {
|
||||
/**
|
||||
* Update a share
|
||||
*
|
||||
* @param IShare $share
|
||||
* @return IShare The share object
|
||||
* @throws ShareNotFound
|
||||
* @throws InvalidPathException
|
||||
* @throws NotFoundException
|
||||
*/
|
||||
public function update(IShare $share) {
|
||||
$originalShare = $this->getShareById($share->getId());
|
||||
|
||||
$shareAttributes = $this->formatShareAttributes($share->getAttributes());
|
||||
@@ -303,7 +309,14 @@ class DefaultShareProvider implements
|
||||
return $share;
|
||||
}
|
||||
|
||||
#[Override]
|
||||
/**
|
||||
* Accept a share.
|
||||
*
|
||||
* @param IShare $share
|
||||
* @param string $recipient
|
||||
* @return IShare The share object
|
||||
* @since 9.0.0
|
||||
*/
|
||||
public function acceptShare(IShare $share, string $recipient): IShare {
|
||||
if ($share->getShareType() === IShare::TYPE_GROUP) {
|
||||
$group = $this->groupManager->get($share->getSharedWith());
|
||||
@@ -387,7 +400,11 @@ class DefaultShareProvider implements
|
||||
return $children;
|
||||
}
|
||||
|
||||
#[Override]
|
||||
/**
|
||||
* Delete a share
|
||||
*
|
||||
* @param IShare $share
|
||||
*/
|
||||
public function delete(IShare $share) {
|
||||
$qb = $this->dbConn->getQueryBuilder();
|
||||
$qb->delete('share')
|
||||
@@ -679,7 +696,9 @@ class DefaultShareProvider implements
|
||||
return $shares;
|
||||
}
|
||||
|
||||
#[Override]
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function getSharesBy($userId, $shareType, $node, $reshares, $limit, $offset) {
|
||||
$qb = $this->dbConn->getQueryBuilder();
|
||||
$qb->select('*')
|
||||
@@ -725,8 +744,10 @@ class DefaultShareProvider implements
|
||||
return $shares;
|
||||
}
|
||||
|
||||
#[Override]
|
||||
public function getShareById(string $id, $recipientId = null): IShare {
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function getShareById($id, $recipientId = null) {
|
||||
$qb = $this->dbConn->getQueryBuilder();
|
||||
|
||||
$qb->select('*')
|
||||
@@ -769,9 +790,10 @@ class DefaultShareProvider implements
|
||||
/**
|
||||
* Get shares for a given path
|
||||
*
|
||||
* @return list<IShare>
|
||||
* @param Node $path
|
||||
* @return IShare[]
|
||||
*/
|
||||
public function getSharesByPath(Node $path): array {
|
||||
public function getSharesByPath(Node $path) {
|
||||
$qb = $this->dbConn->getQueryBuilder();
|
||||
|
||||
$cursor = $qb->select('*')
|
||||
@@ -1022,13 +1044,46 @@ class DefaultShareProvider implements
|
||||
return $shares;
|
||||
}
|
||||
|
||||
#[Override]
|
||||
public function getShareByToken(string $token): never {
|
||||
throw new \LogicException('Should no longer be called directly, instead use IManager::getShareByToken');
|
||||
/**
|
||||
* Get a share by token
|
||||
*
|
||||
* @param string $token
|
||||
* @return IShare
|
||||
* @throws ShareNotFound
|
||||
*/
|
||||
public function getShareByToken($token) {
|
||||
$qb = $this->dbConn->getQueryBuilder();
|
||||
|
||||
$cursor = $qb->select('*')
|
||||
->from('share')
|
||||
->where($qb->expr()->eq('share_type', $qb->createNamedParameter(IShare::TYPE_LINK)))
|
||||
->andWhere($qb->expr()->eq('token', $qb->createNamedParameter($token)))
|
||||
->andWhere($qb->expr()->in('item_type', $qb->createNamedParameter(['file', 'folder'], IQueryBuilder::PARAM_STR_ARRAY)))
|
||||
->executeQuery();
|
||||
|
||||
$data = $cursor->fetch();
|
||||
|
||||
if ($data === false) {
|
||||
throw new ShareNotFound();
|
||||
}
|
||||
|
||||
try {
|
||||
$share = $this->createShare($data);
|
||||
} catch (InvalidShare $e) {
|
||||
throw new ShareNotFound();
|
||||
}
|
||||
|
||||
return $share;
|
||||
}
|
||||
|
||||
#[Override]
|
||||
public function createShare(array $data): IShare {
|
||||
/**
|
||||
* Create a share object from a database row
|
||||
*
|
||||
* @param mixed[] $data
|
||||
* @return IShare
|
||||
* @throws InvalidShare
|
||||
*/
|
||||
private function createShare($data) {
|
||||
$share = new Share($this->rootFolder, $this->userManager);
|
||||
$share->setId($data['id'])
|
||||
->setShareType((int)$data['share_type'])
|
||||
@@ -1664,9 +1719,24 @@ class DefaultShareProvider implements
|
||||
}
|
||||
}
|
||||
|
||||
#[Override]
|
||||
public function getAllShares(): iterable {
|
||||
throw new \LogicException('getAllShare in DefaultShareProvider should no longer be used');
|
||||
$qb = $this->dbConn->getQueryBuilder();
|
||||
|
||||
$qb->select('*')
|
||||
->from('share')
|
||||
->where($qb->expr()->in('share_type', $qb->createNamedParameter([IShare::TYPE_USER, IShare::TYPE_GROUP, IShare::TYPE_LINK], IQueryBuilder::PARAM_INT_ARRAY)));
|
||||
|
||||
$cursor = $qb->executeQuery();
|
||||
while ($data = $cursor->fetch()) {
|
||||
try {
|
||||
$share = $this->createShare($data);
|
||||
} catch (InvalidShare $e) {
|
||||
continue;
|
||||
}
|
||||
|
||||
yield $share;
|
||||
}
|
||||
$cursor->closeCursor();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+45
-348
@@ -19,7 +19,6 @@ use OCA\Files_Sharing\AppInfo\Application;
|
||||
use OCA\Files_Sharing\SharedStorage;
|
||||
use OCA\ShareByMail\ShareByMailProvider;
|
||||
use OCP\Constants;
|
||||
use OCP\DB\QueryBuilder\IQueryBuilder;
|
||||
use OCP\EventDispatcher\Event;
|
||||
use OCP\EventDispatcher\IEventDispatcher;
|
||||
use OCP\Files\File;
|
||||
@@ -33,14 +32,12 @@ use OCP\HintException;
|
||||
use OCP\IAppConfig;
|
||||
use OCP\IConfig;
|
||||
use OCP\IDateTimeZone;
|
||||
use OCP\IDBConnection;
|
||||
use OCP\IGroupManager;
|
||||
use OCP\IL10N;
|
||||
use OCP\IUser;
|
||||
use OCP\IUserManager;
|
||||
use OCP\IUserSession;
|
||||
use OCP\L10N\IFactory;
|
||||
use OCP\PaginationParameters;
|
||||
use OCP\Security\Events\ValidatePasswordPolicyEvent;
|
||||
use OCP\Security\IHasher;
|
||||
use OCP\Security\ISecureRandom;
|
||||
@@ -56,7 +53,6 @@ use OCP\Share\Exceptions\AlreadySharedException;
|
||||
use OCP\Share\Exceptions\GenericShareException;
|
||||
use OCP\Share\Exceptions\ShareNotFound;
|
||||
use OCP\Share\Exceptions\ShareTokenException;
|
||||
use OCP\Share\ICreateShareProvider;
|
||||
use OCP\Share\IManager;
|
||||
use OCP\Share\IPartialShareProvider;
|
||||
use OCP\Share\IProviderFactory;
|
||||
@@ -94,7 +90,6 @@ class Manager implements IManager {
|
||||
private ShareDisableChecker $shareDisableChecker,
|
||||
private IDateTimeZone $dateTimeZone,
|
||||
private IAppConfig $appConfig,
|
||||
private IDBConnection $connection,
|
||||
) {
|
||||
$this->l = $this->l10nFactory->get('lib');
|
||||
// The constructor of LegacyHooks registers the listeners of share events
|
||||
@@ -1044,50 +1039,14 @@ class Manager implements IManager {
|
||||
IShare::TYPE_EMAIL,
|
||||
];
|
||||
|
||||
// Figure out which users has some shares with which providers
|
||||
$qb = $this->connection->getQueryBuilder();
|
||||
$qb->select('uid_initiator', 'share_type')
|
||||
->from('share')
|
||||
->andWhere($qb->expr()->in('item_type', $qb->createNamedParameter(['file', 'folder'], IQueryBuilder::PARAM_STR_ARRAY)))
|
||||
->andWhere($qb->expr()->in('share_type', $qb->createNamedParameter($shareTypes, IQueryBuilder::PARAM_INT_ARRAY)))
|
||||
->andWhere(
|
||||
$qb->expr()->orX(
|
||||
$qb->expr()->in('uid_initiator', $qb->createNamedParameter($userIds, IQueryBuilder::PARAM_STR_ARRAY)),
|
||||
// Special case for old shares created via the web UI
|
||||
$qb->expr()->andX(
|
||||
$qb->expr()->in('uid_owner', $qb->createNamedParameter($userIds, IQueryBuilder::PARAM_STR_ARRAY)),
|
||||
$qb->expr()->isNull('uid_initiator')
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
if (!$node instanceof Folder) {
|
||||
$qb->andWhere($qb->expr()->eq('file_source', $qb->createNamedParameter($node->getId(), IQueryBuilder::PARAM_INT)));
|
||||
}
|
||||
|
||||
$qb->orderBy('id');
|
||||
|
||||
$cursor = $qb->executeQuery();
|
||||
$rawShares = [];
|
||||
while ($data = $cursor->fetch()) {
|
||||
if (!isset($rawShares[$data['uid_initiator']])) {
|
||||
$rawShares[$data['uid_initiator']] = [];
|
||||
}
|
||||
if (!in_array($data['share_type'], $rawShares[$data['uid_initiator']], true)) {
|
||||
$rawShares[$data['uid_initiator']][] = $data['share_type'];
|
||||
}
|
||||
}
|
||||
$cursor->closeCursor();
|
||||
|
||||
foreach ($rawShares as $userId => $shareTypes) {
|
||||
foreach ($userIds as $userId) {
|
||||
foreach ($shareTypes as $shareType) {
|
||||
try {
|
||||
$provider = $this->factory->getProviderForType($shareType);
|
||||
} catch (ProviderException) {
|
||||
} catch (ProviderException $e) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
if ($node instanceof Folder) {
|
||||
/* We need to get all shares by this user to get subshares */
|
||||
$shares = $provider->getSharesBy($userId, $shareType, null, false, -1, 0);
|
||||
@@ -1230,95 +1189,11 @@ class Manager implements IManager {
|
||||
return $shares;
|
||||
}
|
||||
|
||||
#[Override]
|
||||
public function getAllSharesBy(string $userId, ?Node $node = null, PaginationParameters $paginationParameters, bool $reshares = false, bool $onlyValid = true): array {
|
||||
if ($node !== null && !$node instanceof File && !$node instanceof Folder) {
|
||||
throw new \InvalidArgumentException($this->l->t('Invalid path'));
|
||||
}
|
||||
|
||||
// Get all shares from the providers supporting createShare
|
||||
$providers = $this->factory->getAllProviders();
|
||||
$createShareProviders = array_filter($providers, fn (IShareProvider $provider) => $provider instanceof ICreateShareProvider);
|
||||
$shareTypes = array_unique(array_merge(...array_map(fn (ICreateShareProvider $provider): array => $provider->getTokenShareTypes(), $createShareProviders)));
|
||||
|
||||
if ($node?->getMountPoint() instanceof IShareOwnerlessMount) {
|
||||
return $this->getSharesByPath($node, $shareTypes, $paginationParameters);
|
||||
}
|
||||
|
||||
$qb = $this->connection->getQueryBuilder();
|
||||
$qb->select('*')
|
||||
->from('share')
|
||||
->andWhere($qb->expr()->in('share_type', $qb->createNamedParameter($shareTypes, IQueryBuilder::PARAM_INT_ARRAY)));
|
||||
|
||||
/**
|
||||
* Reshares for this user are shares where they are the owner.
|
||||
*/
|
||||
if ($reshares === false) {
|
||||
//Special case for old shares created via the web UI
|
||||
$or1 = $qb->expr()->andX(
|
||||
$qb->expr()->eq('uid_owner', $qb->createNamedParameter($userId)),
|
||||
$qb->expr()->isNull('uid_initiator')
|
||||
);
|
||||
|
||||
$qb->andWhere(
|
||||
$qb->expr()->orX(
|
||||
$qb->expr()->eq('uid_initiator', $qb->createNamedParameter($userId)),
|
||||
$or1
|
||||
)
|
||||
);
|
||||
} elseif ($node === null) {
|
||||
$qb->andWhere(
|
||||
$qb->expr()->orX(
|
||||
$qb->expr()->eq('uid_owner', $qb->createNamedParameter($userId)),
|
||||
$qb->expr()->eq('uid_initiator', $qb->createNamedParameter($userId))
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if ($node !== null) {
|
||||
$qb->andWhere($qb->expr()->eq('file_source', $qb->createNamedParameter($node->getId())));
|
||||
}
|
||||
|
||||
$paginationParameters->fillQuery($qb, 'id');
|
||||
|
||||
$cursor = $qb->executeQuery();
|
||||
while ($data = $cursor->fetchAssociative()) {
|
||||
$provider = $this->factory->getProviderForType((int)$data['share_type']);
|
||||
if ($provider instanceof ICreateShareProvider) {
|
||||
throw new \LogicException('Share type ' . $data['share_type'] . " doesn't have a corresponding ICreateShareProvider.");
|
||||
}
|
||||
$share = $provider->createShare($data);
|
||||
try {
|
||||
$this->checkShare($share, $added);
|
||||
} catch (ShareNotFound $e) {
|
||||
// Ignore since this basically means the share is deleted
|
||||
continue;
|
||||
}
|
||||
}
|
||||
$cursor->closeCursor();
|
||||
|
||||
// Get all the other shares from the providers not supporting createShare
|
||||
$shares = [];
|
||||
foreach ([IShare::TYPE_USER, IShare::TYPE_GROUP, IShare::TYPE_EMAIL, IShare::TYPE_CIRCLE, IShare::TYPE_ROOM, IShare::TYPE_DECK] as $shareType) {
|
||||
if (!in_array($shareType, $shareTypes)) {
|
||||
$shares = array_merge($shares, $this->getSharesBy($userId, $shareType, $node, $reshares, -1, 0));
|
||||
}
|
||||
}
|
||||
|
||||
// FEDERATION
|
||||
if ($this->outgoingServer2ServerSharesAllowed() && !in_array(IShare::TYPE_REMOTE, $shareTypes)) {
|
||||
$shares = array_merge($shares, $this->getSharesBy($userId, IShare::TYPE_REMOTE, $node, $reshares, -1, 0));
|
||||
}
|
||||
if ($this->outgoingServer2ServerGroupSharesAllowed() && !in_array(IShare::TYPE_REMOTE_GROUP, $shareTypes)) {
|
||||
$shares = array_merge($shares, $this->getSharesBy($userId, IShare::TYPE_REMOTE_GROUP, $node, $reshares, -1, 0));
|
||||
}
|
||||
|
||||
return $shares;
|
||||
}
|
||||
|
||||
#[Override]
|
||||
public function getSharesBy(string $userId, int $shareType, ?Node $path = null, bool $reshares = false, int $limit = 50, int $offset = 0, bool $onlyValid = true): array {
|
||||
if ($path !== null && !($path instanceof File) && !($path instanceof Folder)) {
|
||||
if ($path !== null
|
||||
&& !($path instanceof File)
|
||||
&& !($path instanceof Folder)) {
|
||||
throw new \InvalidArgumentException($this->l->t('Invalid path'));
|
||||
}
|
||||
|
||||
@@ -1391,70 +1266,8 @@ class Manager implements IManager {
|
||||
}
|
||||
}
|
||||
|
||||
return $shares2;
|
||||
}
|
||||
$shares = $shares2;
|
||||
|
||||
#[Override]
|
||||
public function getAllSharedWith(string $userId, array $shareTypes, ?Node $node, PaginationParameters $paginationParameters, bool $ignoreWithSelf = false): array {
|
||||
$shareTypes = [];
|
||||
$noCreateShareProvider = [];
|
||||
foreach ($this->factory->getAllProviders() as $provider) {
|
||||
if ($provider instanceof ICreateShareProvider) {
|
||||
$shareTypes = array_merge($provider->getShareTypes(), $shareTypes);
|
||||
} else {
|
||||
$noCreateShareProvider[] = $provider;
|
||||
}
|
||||
}
|
||||
$shareTypes = array_unique($shareTypes);
|
||||
|
||||
// Get shares directly with this user
|
||||
$qb = $this->connection->getQueryBuilder();
|
||||
$qb->select('*')
|
||||
->from('share');
|
||||
|
||||
$qb->where($qb->expr()->in('share_type', $qb->createNamedParameter($shareTypes, IQueryBuilder::PARAM_INT_ARRAY)));
|
||||
$qb->andWhere($qb->expr()->eq('share_with', $qb->createNamedParameter($userId)));
|
||||
|
||||
if ($node !== null) {
|
||||
$qb->andWhere($qb->expr()->eq('file_source', $qb->createNamedParameter($node->getId())));
|
||||
}
|
||||
|
||||
if ($ignoreWithSelf) {
|
||||
$qb->andWhere($qb->expr()->neq('uid_owner', $qb->createNamedParameter($userId, IQueryBuilder::PARAM_STR)));
|
||||
$qb->andWhere($qb->expr()->neq('uid_initiator', $qb->createNamedParameter($userId, IQueryBuilder::PARAM_STR)));
|
||||
}
|
||||
|
||||
$paginationParameters->fillQuery($qb, 'id');
|
||||
|
||||
$result = $qb->executeQuery();
|
||||
$shares = [];
|
||||
foreach ($result->fetchAssociative() as $data) {
|
||||
try {
|
||||
$provider = $this->factory->getProviderForType($data['share_type']);
|
||||
} catch (ProviderException $e) {
|
||||
continue;
|
||||
}
|
||||
if (!$provider instanceof ICreateShareProvider) {
|
||||
throw new \LogicException($this->l->t('Invalid provider'));
|
||||
}
|
||||
$shares[] = $provider->createShare($data);
|
||||
}
|
||||
|
||||
// Legacy for providers what don't support ICreateShareProvider
|
||||
foreach ($noCreateShareProvider as $shareType) {
|
||||
// TODO fix pagination
|
||||
$unverifiedShares = $provider->getSharedWith($userId, $shareType, $node, 0, 0);
|
||||
|
||||
// remove all shares which are already expired
|
||||
foreach ($unverifiedShares as $key => $share) {
|
||||
try {
|
||||
$this->checkShare($share);
|
||||
} catch (ShareNotFound $e) {
|
||||
unset($shares[$key]);
|
||||
}
|
||||
$shares[] = $share;
|
||||
}
|
||||
}
|
||||
return $shares;
|
||||
}
|
||||
|
||||
@@ -1480,7 +1293,9 @@ class Manager implements IManager {
|
||||
return $shares;
|
||||
}
|
||||
|
||||
#[Override]
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function getSharedWithByPath(string $userId, int $shareType, string $path, bool $forChildren, int $limit = 50, int $offset = 0): iterable {
|
||||
try {
|
||||
$provider = $this->factory->getProviderForType($shareType);
|
||||
@@ -1520,7 +1335,6 @@ class Manager implements IManager {
|
||||
|
||||
#[Override]
|
||||
public function getDeletedSharedWith(string $userId, int $shareType, ?Node $node = null, int $limit = 50, int $offset = 0): array {
|
||||
// TODO: Remove, it is no longer used.
|
||||
$shares = $this->getSharedWith($userId, $shareType, $node, $limit, $offset);
|
||||
|
||||
// Only get shares deleted shares and where the owner still exists
|
||||
@@ -1529,16 +1343,11 @@ class Manager implements IManager {
|
||||
}
|
||||
|
||||
#[Override]
|
||||
public function getAllDeletedSharedWith(string $userId, array $shareTypes, ?Node $node = null, PaginationParameters $paginationParameters): array {
|
||||
$shares = $this->getAllSharedWith($userId, $shareTypes, $node, $paginationParameters);
|
||||
public function getShareById($id, $recipient = null, bool $onlyValid = true): IShare {
|
||||
if ($id === null) {
|
||||
throw new ShareNotFound();
|
||||
}
|
||||
|
||||
// Only get shares deleted shares and where the owner still exists
|
||||
return array_filter($shares, fn (IShare $share): bool => $share->getPermissions() === 0
|
||||
&& $this->userManager->userExists($share->getShareOwner()));
|
||||
}
|
||||
|
||||
#[Override]
|
||||
public function getShareById(string $id, ?string $recipient = null, bool $onlyValid = true): IShare {
|
||||
[$providerId, $id] = $this->splitFullId($id);
|
||||
|
||||
try {
|
||||
@@ -1547,31 +1356,6 @@ class Manager implements IManager {
|
||||
throw new ShareNotFound();
|
||||
}
|
||||
|
||||
if ($provider instanceof ICreateShareProvider) {
|
||||
$qb = $this->connection->getQueryBuilder();
|
||||
|
||||
$qb->select('*')
|
||||
->from('share')
|
||||
->where($qb->expr()->eq('id', $qb->createNamedParameter($id)))
|
||||
->andWhere($qb->expr()->in('share_type', $qb->createNamedParameter($provider->getShareTypes(), IQueryBuilder::PARAM_INT_ARRAY)));
|
||||
|
||||
$cursor = $qb->executeQuery();
|
||||
$data = $cursor->fetchAssociative();
|
||||
$cursor->closeCursor();
|
||||
|
||||
if ($data === false) {
|
||||
throw new ShareNotFound('Can not find share with ID: ' . $id);
|
||||
}
|
||||
|
||||
$share = $provider->createShare($data);
|
||||
|
||||
if ($onlyValid) {
|
||||
$this->checkShare($share);
|
||||
}
|
||||
|
||||
return $share;
|
||||
}
|
||||
|
||||
$share = $provider->getShareById($id, $recipient);
|
||||
|
||||
if ($onlyValid) {
|
||||
@@ -1581,87 +1365,24 @@ class Manager implements IManager {
|
||||
return $share;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param list<IShare::TYPE_*> $shareTypes
|
||||
* @return list<IShare>
|
||||
*/
|
||||
private function getSharesByPath(Node $path, array $shareTypes, PaginationParameters $paginationParameters): array {
|
||||
$qb = $this->connection->getQueryBuilder();
|
||||
|
||||
$qb = $qb->select('*')
|
||||
->from('share')
|
||||
->andWhere($qb->expr()->eq('file_source', $qb->createNamedParameter($path->getId())))
|
||||
->andWhere($qb->expr()->in('share_type', $qb->createNamedParameter($shareTypes, IQueryBuilder::PARAM_INT_ARRAY)));
|
||||
$paginationParameters->fillQuery($qb, 'id');
|
||||
$cursor = $qb->executeQuery();
|
||||
|
||||
$shares = [];
|
||||
while ($data = $cursor->fetchAssociative()) {
|
||||
$provider = $this->factory->getProvider((int)$data['share_type']);
|
||||
if ($provider instanceof ICreateShareProvider) {
|
||||
$shares[] = $provider->createShare($data);
|
||||
} else {
|
||||
$shares[] = $provider->getShareById((int)$data['id']);
|
||||
}
|
||||
}
|
||||
$cursor->closeCursor();
|
||||
return $shares;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{?IShare, list<IShare::TYPE_*>}
|
||||
*/
|
||||
private function getShareByTokenOptimized(string $token): array {
|
||||
$providers = $this->factory->getAllProviders();
|
||||
// Get all shares from the providers supporting createShare
|
||||
$createShareProviders = array_filter($providers, fn (IShareProvider $provider) => $provider instanceof ICreateShareProvider);
|
||||
$shareTypes = array_unique(array_merge(...array_map(fn (ICreateShareProvider $provider): array => $provider->getTokenShareTypes(), $createShareProviders)));
|
||||
if (!$this->appConfig->getValueBool('core', 'shareapi_allow_links', true)) {
|
||||
$shareTypes = array_filter($shareTypes, fn (int $shareType): bool => $shareType !== IShare::TYPE_LINK);
|
||||
}
|
||||
$qb = $this->connection->getQueryBuilder();
|
||||
$result = $qb->select('*')
|
||||
->from('share')
|
||||
->where($qb->expr()->in('share_type', $qb->createNamedParameter($shareTypes)))
|
||||
->andWhere($qb->expr()->eq('token', $qb->createNamedParameter($token)))
|
||||
->andWhere($qb->expr()->in('item_type', $qb->createNamedParameter(['file', 'folder'], IQueryBuilder::PARAM_STR_ARRAY)))
|
||||
->executeQuery();
|
||||
|
||||
$data = $result->fetch();
|
||||
if ($data === false) {
|
||||
return [null, $shareTypes];
|
||||
}
|
||||
$provider = $this->factory->getProviderForType((int)$data['share_type']);
|
||||
if (!$provider instanceof ICreateShareProvider) {
|
||||
throw new \LogicException('Share type ' . $data['share_type'] . " doesn't have a corresponding ICreateShareProvider.");
|
||||
}
|
||||
$data['id'] = (string)$data['id'];
|
||||
$share = $provider->createShare($data);
|
||||
return [$share, $shareTypes];
|
||||
}
|
||||
|
||||
#[Override]
|
||||
public function getShareByToken(string $token): IShare {
|
||||
// tokens cannot be valid local usernames
|
||||
if ($this->userManager->userExists($token)) {
|
||||
throw new ShareNotFound();
|
||||
}
|
||||
|
||||
[$share, $testedShareTypes] = $this->getShareByTokenOptimized($token);
|
||||
if ($share !== null) {
|
||||
return $share;
|
||||
}
|
||||
|
||||
if (!in_array(IShare::TYPE_LINK, $testedShareTypes) && $this->appConfig->getValueBool('core', 'shareapi_allow_links', true)) {
|
||||
try {
|
||||
$share = null;
|
||||
try {
|
||||
if ($this->config->getAppValue('core', 'shareapi_allow_links', 'yes') === 'yes') {
|
||||
$provider = $this->factory->getProviderForType(IShare::TYPE_LINK);
|
||||
$share = $provider->getShareByToken($token);
|
||||
} catch (ProviderException|ShareNotFound) {
|
||||
}
|
||||
} catch (ProviderException|ShareNotFound) {
|
||||
}
|
||||
|
||||
|
||||
// If it is not a link share try to fetch a federated share by token
|
||||
if ($share === null && !in_array(IShare::TYPE_REMOTE, $testedShareTypes)) {
|
||||
if ($share === null) {
|
||||
try {
|
||||
$provider = $this->factory->getProviderForType(IShare::TYPE_REMOTE);
|
||||
$share = $provider->getShareByToken($token);
|
||||
@@ -1670,7 +1391,7 @@ class Manager implements IManager {
|
||||
}
|
||||
|
||||
// If it is not a link share try to fetch a mail share by token
|
||||
if ($share === null && !in_array(IShare::TYPE_REMOTE, $testedShareTypes) && $this->shareProviderExists(IShare::TYPE_EMAIL)) {
|
||||
if ($share === null && $this->shareProviderExists(IShare::TYPE_EMAIL)) {
|
||||
try {
|
||||
$provider = $this->factory->getProviderForType(IShare::TYPE_EMAIL);
|
||||
$share = $provider->getShareByToken($token);
|
||||
@@ -1678,7 +1399,7 @@ class Manager implements IManager {
|
||||
}
|
||||
}
|
||||
|
||||
if ($share === null && !in_array(IShare::TYPE_REMOTE, $testedShareTypes) && $this->shareProviderExists(IShare::TYPE_CIRCLE)) {
|
||||
if ($share === null && $this->shareProviderExists(IShare::TYPE_CIRCLE)) {
|
||||
try {
|
||||
$provider = $this->factory->getProviderForType(IShare::TYPE_CIRCLE);
|
||||
$share = $provider->getShareByToken($token);
|
||||
@@ -1686,7 +1407,7 @@ class Manager implements IManager {
|
||||
}
|
||||
}
|
||||
|
||||
if ($share === null && !in_array(IShare::TYPE_REMOTE, $testedShareTypes) && $this->shareProviderExists(IShare::TYPE_ROOM)) {
|
||||
if ($share === null && $this->shareProviderExists(IShare::TYPE_ROOM)) {
|
||||
try {
|
||||
$provider = $this->factory->getProviderForType(IShare::TYPE_ROOM);
|
||||
$share = $provider->getShareByToken($token);
|
||||
@@ -1917,21 +1638,21 @@ class Manager implements IManager {
|
||||
|
||||
#[Override]
|
||||
public function shareApiEnabled(): bool {
|
||||
return $this->appConfig->getValueBool('core', 'shareapi_enabled', true);
|
||||
return $this->config->getAppValue('core', 'shareapi_enabled', 'yes') === 'yes';
|
||||
}
|
||||
|
||||
#[Override]
|
||||
public function shareApiAllowLinks(?IUser $user = null): bool {
|
||||
if (!$this->appConfig->getValueBool('core', 'shareapi_allow_links', true)) {
|
||||
if ($this->config->getAppValue('core', 'shareapi_allow_links', 'yes') !== 'yes') {
|
||||
return false;
|
||||
}
|
||||
|
||||
$user = $user ?? $this->userSession->getUser();
|
||||
if ($user) {
|
||||
$excludedGroups = $this->appConfig->getValueArray('core', 'shareapi_allow_links_exclude_groups');
|
||||
$excludedGroups = json_decode($this->config->getAppValue('core', 'shareapi_allow_links_exclude_groups', '[]'));
|
||||
if ($excludedGroups) {
|
||||
$userGroups = $this->groupManager->getUserGroupIds($user);
|
||||
return !array_intersect($excludedGroups, $userGroups);
|
||||
return !(bool)array_intersect($excludedGroups, $userGroups);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1950,12 +1671,13 @@ class Manager implements IManager {
|
||||
|
||||
#[Override]
|
||||
public function shareApiLinkEnforcePassword(bool $checkGroupMembership = true): bool {
|
||||
$excludedGroups = $this->appConfig->getValueArray('core', 'shareapi_enforce_links_password_excluded_groups');
|
||||
if ($excludedGroups !== [] && $checkGroupMembership) {
|
||||
$excludedGroups = $this->config->getAppValue('core', 'shareapi_enforce_links_password_excluded_groups', '');
|
||||
if ($excludedGroups !== '' && $checkGroupMembership) {
|
||||
$excludedGroups = json_decode($excludedGroups);
|
||||
$user = $this->userSession->getUser();
|
||||
if ($user) {
|
||||
$userGroups = $this->groupManager->getUserGroupIds($user);
|
||||
if (array_intersect($excludedGroups, $userGroups)) {
|
||||
if ((bool)array_intersect($excludedGroups, $userGroups)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -1976,49 +1698,49 @@ class Manager implements IManager {
|
||||
|
||||
#[Override]
|
||||
public function shareApiLinkDefaultExpireDays(): int {
|
||||
return $this->appConfig->getValueInt('core', 'shareapi_expire_after_n_days', 7);
|
||||
return (int)$this->config->getAppValue('core', 'shareapi_expire_after_n_days', '7');
|
||||
}
|
||||
|
||||
#[Override]
|
||||
public function shareApiInternalDefaultExpireDate(): bool {
|
||||
return $this->appConfig->getValueBool('core', 'shareapi_default_internal_expire_date');
|
||||
return $this->config->getAppValue('core', 'shareapi_default_internal_expire_date', 'no') === 'yes';
|
||||
}
|
||||
|
||||
#[Override]
|
||||
public function shareApiRemoteDefaultExpireDate(): bool {
|
||||
return $this->appConfig->getValueBool('core', 'shareapi_default_remote_expire_date');
|
||||
return $this->config->getAppValue('core', 'shareapi_default_remote_expire_date', 'no') === 'yes';
|
||||
}
|
||||
|
||||
#[Override]
|
||||
public function shareApiInternalDefaultExpireDateEnforced(): bool {
|
||||
return $this->shareApiInternalDefaultExpireDate()
|
||||
&& $this->appConfig->getValueBool('core', 'shareapi_enforce_internal_expire_date');
|
||||
&& $this->config->getAppValue('core', 'shareapi_enforce_internal_expire_date', 'no') === 'yes';
|
||||
}
|
||||
|
||||
#[Override]
|
||||
public function shareApiRemoteDefaultExpireDateEnforced(): bool {
|
||||
return $this->shareApiRemoteDefaultExpireDate()
|
||||
&& $this->appConfig->getValueBool('core', 'shareapi_enforce_remote_expire_date');
|
||||
&& $this->config->getAppValue('core', 'shareapi_enforce_remote_expire_date', 'no') === 'yes';
|
||||
}
|
||||
|
||||
#[Override]
|
||||
public function shareApiInternalDefaultExpireDays(): int {
|
||||
return $this->appConfig->getValueInt('core', 'shareapi_internal_expire_after_n_days', 7);
|
||||
return (int)$this->config->getAppValue('core', 'shareapi_internal_expire_after_n_days', '7');
|
||||
}
|
||||
|
||||
#[Override]
|
||||
public function shareApiRemoteDefaultExpireDays(): int {
|
||||
return $this->appConfig->getValueInt('core', 'shareapi_remote_expire_after_n_days', 7);
|
||||
return (int)$this->config->getAppValue('core', 'shareapi_remote_expire_after_n_days', '7');
|
||||
}
|
||||
|
||||
#[Override]
|
||||
public function shareApiLinkAllowPublicUpload(): bool {
|
||||
return $this->appConfig->getValueBool('core', 'shareapi_allow_public_upload', true);
|
||||
return $this->config->getAppValue('core', 'shareapi_allow_public_upload', 'yes') === 'yes';
|
||||
}
|
||||
|
||||
#[Override]
|
||||
public function shareWithGroupMembersOnly(): bool {
|
||||
return $this->appConfig->getValueBool('core', 'shareapi_only_share_with_group_members');
|
||||
return $this->config->getAppValue('core', 'shareapi_only_share_with_group_members', 'no') === 'yes';
|
||||
}
|
||||
|
||||
#[Override]
|
||||
@@ -2032,29 +1754,29 @@ class Manager implements IManager {
|
||||
|
||||
#[Override]
|
||||
public function allowGroupSharing(): bool {
|
||||
return $this->appConfig->getValueBool('core', 'shareapi_allow_group_sharing', true);
|
||||
return $this->config->getAppValue('core', 'shareapi_allow_group_sharing', 'yes') === 'yes';
|
||||
}
|
||||
|
||||
#[Override]
|
||||
public function allowEnumeration(): bool {
|
||||
return $this->appConfig->getValueBool('core', 'shareapi_allow_share_dialog_user_enumeration', true);
|
||||
return $this->config->getAppValue('core', 'shareapi_allow_share_dialog_user_enumeration', 'yes') === 'yes';
|
||||
}
|
||||
|
||||
#[Override]
|
||||
public function limitEnumerationToGroups(): bool {
|
||||
return $this->allowEnumeration()
|
||||
&& $this->appConfig->getValueBool('core', 'shareapi_restrict_user_enumeration_to_group');
|
||||
&& $this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_to_group', 'no') === 'yes';
|
||||
}
|
||||
|
||||
#[Override]
|
||||
public function limitEnumerationToPhone(): bool {
|
||||
return $this->allowEnumeration()
|
||||
&& $this->appConfig->getValueBool('core', 'shareapi_restrict_user_enumeration_to_phone');
|
||||
&& $this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_to_phone', 'no') === 'yes';
|
||||
}
|
||||
|
||||
#[Override]
|
||||
public function allowEnumerationFullMatch(): bool {
|
||||
return $this->appConfig->getValueBool('core', 'shareapi_restrict_user_enumeration_full_match', true);
|
||||
return $this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_full_match', 'yes') === 'yes';
|
||||
}
|
||||
|
||||
#[Override]
|
||||
@@ -2157,32 +1879,7 @@ class Manager implements IManager {
|
||||
public function getAllShares(): iterable {
|
||||
$providers = $this->factory->getAllProviders();
|
||||
|
||||
// Get all shares from the providers supporting createShare
|
||||
$createShareProviders = array_filter($providers, fn (IShareProvider $provider) => $provider instanceof ICreateShareProvider);
|
||||
$shareTypes = array_unique(array_merge(...array_map(fn (ICreateShareProvider $provider): array => $provider->getShareTypes(), $createShareProviders)));
|
||||
$qb = $this->connection->getQueryBuilder();
|
||||
$result = $qb->select('*')
|
||||
->from('share')
|
||||
->where($qb->expr()->in('share_type', $qb->createNamedParameter($shareTypes, IQueryBuilder::PARAM_INT_ARRAY)))
|
||||
->executeQuery();
|
||||
/** @var array<IShare::TYPE*, IShareProvider> $providers */
|
||||
$providers = [];
|
||||
foreach ($result->iterateAssociative() as $row) {
|
||||
if (!isset($providers[$row['share_type']])) {
|
||||
$providers[$row['share_type']] = $this->factory->getProviderForType($row['share_type']);
|
||||
}
|
||||
|
||||
$provider = $providers[$row['share_type']];
|
||||
if (!$provider instanceof ICreateShareProvider) {
|
||||
throw new \LogicException('Share type ' . $row['share_type'] . " doesn't have a corresponding ICreateShareProvider.");
|
||||
}
|
||||
|
||||
yield $provider->createShare($row);
|
||||
}
|
||||
|
||||
// Get all shares from the other providers
|
||||
$noCreateShareProviders = array_filter($providers, fn (IShareProvider $provider) => !$provider instanceof ICreateShareProvider);
|
||||
foreach ($noCreateShareProviders as $provider) {
|
||||
foreach ($providers as $provider) {
|
||||
yield from $provider->getAllShares();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
@@ -12,28 +14,27 @@ use OCP\Files\NotFoundException;
|
||||
use OCP\Files\NotPermittedException;
|
||||
use OCP\Share\IManager;
|
||||
use OCP\Share\IShareHelper;
|
||||
use Override;
|
||||
|
||||
class ShareHelper implements IShareHelper {
|
||||
public function __construct(
|
||||
private IManager $shareManager,
|
||||
private readonly IManager $shareManager,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Node $node
|
||||
* @return array [ users => [Mapping $uid => $pathForUser], remotes => [Mapping $cloudId => $pathToMountRoot]]
|
||||
*/
|
||||
public function getPathsForAccessList(Node $node) {
|
||||
#[Override]
|
||||
public function getPathsForAccessList(Node $node): array {
|
||||
$result = [
|
||||
'users' => [],
|
||||
'remotes' => [],
|
||||
];
|
||||
|
||||
$accessList = $this->shareManager->getAccessList($node, true, true);
|
||||
if (!empty($accessList['users'])) {
|
||||
if (isset($accessList['users']) && $accessList['users'] !== []) {
|
||||
$result['users'] = $this->getPathsForUsers($node, $accessList['users']);
|
||||
}
|
||||
if (!empty($accessList['remote'])) {
|
||||
|
||||
if (isset($accessList['remote']) && $accessList['remote'] !== []) {
|
||||
$result['remotes'] = $this->getPathsForRemotes($node, $accessList['remote']);
|
||||
}
|
||||
|
||||
@@ -60,20 +61,20 @@ class ShareHelper implements IShareHelper {
|
||||
* 'test3' => '/cat',
|
||||
* ],
|
||||
*
|
||||
* @param Node $node
|
||||
* @param array[] $users
|
||||
* @return array
|
||||
* @param non-empty-array<string, array{node_id: int, node_path: string}> $users
|
||||
* @return array<string, string>
|
||||
*/
|
||||
protected function getPathsForUsers(Node $node, array $users) {
|
||||
/** @var array[] $byId */
|
||||
protected function getPathsForUsers(Node $node, array $users): array {
|
||||
/** @var array<int, array<string, string>> $byId */
|
||||
$byId = [];
|
||||
/** @var array[] $results */
|
||||
/** @var array<string, string> $results */
|
||||
$results = [];
|
||||
|
||||
foreach ($users as $uid => $info) {
|
||||
if (!isset($byId[$info['node_id']])) {
|
||||
$byId[$info['node_id']] = [];
|
||||
}
|
||||
|
||||
$byId[$info['node_id']][$uid] = $info['node_path'];
|
||||
}
|
||||
|
||||
@@ -82,15 +83,14 @@ class ShareHelper implements IShareHelper {
|
||||
foreach ($byId[$node->getId()] as $uid => $path) {
|
||||
$results[$uid] = $path;
|
||||
}
|
||||
|
||||
unset($byId[$node->getId()]);
|
||||
}
|
||||
} catch (NotFoundException $e) {
|
||||
return $results;
|
||||
} catch (InvalidPathException $e) {
|
||||
} catch (NotFoundException|InvalidPathException) {
|
||||
return $results;
|
||||
}
|
||||
|
||||
if (empty($byId)) {
|
||||
if ($byId === []) {
|
||||
return $results;
|
||||
}
|
||||
|
||||
@@ -98,22 +98,18 @@ class ShareHelper implements IShareHelper {
|
||||
$appendix = '/' . $node->getName();
|
||||
while (!empty($byId)) {
|
||||
try {
|
||||
/** @var Node $item */
|
||||
$item = $item->getParent();
|
||||
|
||||
if (!empty($byId[$item->getId()])) {
|
||||
if ($byId[$item->getId()] !== []) {
|
||||
foreach ($byId[$item->getId()] as $uid => $path) {
|
||||
$results[$uid] = $path . $appendix;
|
||||
}
|
||||
|
||||
unset($byId[$item->getId()]);
|
||||
}
|
||||
|
||||
$appendix = '/' . $item->getName() . $appendix;
|
||||
} catch (NotFoundException $e) {
|
||||
return $results;
|
||||
} catch (InvalidPathException $e) {
|
||||
return $results;
|
||||
} catch (NotPermittedException $e) {
|
||||
} catch (NotFoundException|InvalidPathException|NotPermittedException) {
|
||||
return $results;
|
||||
}
|
||||
}
|
||||
@@ -141,27 +137,27 @@ class ShareHelper implements IShareHelper {
|
||||
* 'test3' => ['token' => 't3', 'node_path' => '/SixTeen/TwentyThree/FortyTwo'],
|
||||
* ],
|
||||
*
|
||||
* @param Node $node
|
||||
* @param array[] $remotes
|
||||
* @return array
|
||||
* @param non-empty-array<string, array{node_id: int, token: string}> $remotes
|
||||
* @return array<string, array{token: string, node_path: string}>
|
||||
*/
|
||||
protected function getPathsForRemotes(Node $node, array $remotes) {
|
||||
/** @var array[] $byId */
|
||||
protected function getPathsForRemotes(Node $node, array $remotes): array {
|
||||
/** @var array<int, array<string, string>> $byId */
|
||||
$byId = [];
|
||||
/** @var array[] $results */
|
||||
/** @var array<string, array{token: string, node_path: string}> $results */
|
||||
$results = [];
|
||||
|
||||
foreach ($remotes as $cloudId => $info) {
|
||||
if (!isset($byId[$info['node_id']])) {
|
||||
$byId[$info['node_id']] = [];
|
||||
}
|
||||
|
||||
$byId[$info['node_id']][$cloudId] = $info['token'];
|
||||
}
|
||||
|
||||
$item = $node;
|
||||
while (!empty($byId)) {
|
||||
try {
|
||||
if (!empty($byId[$item->getId()])) {
|
||||
if ($byId[$item->getId()] !== []) {
|
||||
$path = $this->getMountedPath($item);
|
||||
foreach ($byId[$item->getId()] as $uid => $token) {
|
||||
$results[$uid] = [
|
||||
@@ -169,16 +165,12 @@ class ShareHelper implements IShareHelper {
|
||||
'token' => $token,
|
||||
];
|
||||
}
|
||||
|
||||
unset($byId[$item->getId()]);
|
||||
}
|
||||
|
||||
/** @var Node $item */
|
||||
$item = $item->getParent();
|
||||
} catch (NotFoundException $e) {
|
||||
return $results;
|
||||
} catch (InvalidPathException $e) {
|
||||
return $results;
|
||||
} catch (NotPermittedException $e) {
|
||||
} catch (NotFoundException|InvalidPathException|NotPermittedException) {
|
||||
return $results;
|
||||
}
|
||||
}
|
||||
@@ -186,11 +178,7 @@ class ShareHelper implements IShareHelper {
|
||||
return $results;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Node $node
|
||||
* @return string
|
||||
*/
|
||||
protected function getMountedPath(Node $node) {
|
||||
protected function getMountedPath(Node $node): string {
|
||||
$path = $node->getPath();
|
||||
$sections = explode('/', $path, 4);
|
||||
return '/' . $sections[3];
|
||||
|
||||
@@ -37,7 +37,9 @@ interface ITypedQueryBuilder extends IQueryBuilder {
|
||||
* @template NewS of string
|
||||
* @param NewS ...$columns The columns to select. They are not allowed to contain table names or aliases, or asterisks. Use {@see self::selectAlias()} for that.
|
||||
* @psalm-this-out self<S|NewS>
|
||||
* @return $this
|
||||
* @since 34.0.0
|
||||
* @note Psalm has a bug that prevents inferring the correct type in chained calls: https://github.com/vimeo/psalm/issues/8803. Convert the chained calls to standalone calls or switch to PHPStan, which suffered the same bug in the past, but fixed it in 2.1.5: https://github.com/phpstan/phpstan/issues/8439
|
||||
*/
|
||||
public function selectColumns(string ...$columns): self;
|
||||
|
||||
@@ -52,7 +54,9 @@ interface ITypedQueryBuilder extends IQueryBuilder {
|
||||
* @template NewS of string
|
||||
* @param NewS ...$columns The columns to select distinct. They are not allowed to contain table names or aliases, or asterisks. Use {@see self::selectAlias()} for that.
|
||||
* @psalm-this-out self<S|NewS>
|
||||
* @return $this
|
||||
* @since 34.0.0
|
||||
* @note Psalm has a bug that prevents inferring the correct type in chained calls: https://github.com/vimeo/psalm/issues/8803. Convert the chained calls to standalone calls or switch to PHPStan, which suffered the same bug in the past, but fixed it in 2.1.5: https://github.com/phpstan/phpstan/issues/8439
|
||||
*/
|
||||
public function selectColumnsDistinct(string ...$columns): self;
|
||||
|
||||
@@ -69,8 +73,244 @@ interface ITypedQueryBuilder extends IQueryBuilder {
|
||||
* @template NewS of string
|
||||
* @param NewS $alias
|
||||
* @psalm-this-out self<S|NewS>
|
||||
* @psalm-suppress LessSpecificImplementedReturnType
|
||||
* @return $this
|
||||
* @note Psalm has a bug that prevents inferring the correct type in chained calls: https://github.com/vimeo/psalm/issues/8803. Convert the chained calls to standalone calls or switch to PHPStan, which suffered the same bug in the past, but fixed it in 2.1.5: https://github.com/phpstan/phpstan/issues/8439
|
||||
*/
|
||||
#[Override]
|
||||
public function selectAlias($select, $alias): self;
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
* @return $this
|
||||
* @psalm-suppress MissingParamType
|
||||
*/
|
||||
#[Override]
|
||||
public function setParameter($key, $value, $type = null);
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
* @return $this
|
||||
*/
|
||||
#[Override]
|
||||
public function setParameters(array $params, array $types = []);
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
* @return $this
|
||||
* @psalm-suppress MissingParamType
|
||||
*/
|
||||
#[Override]
|
||||
public function setFirstResult($firstResult);
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
* @return $this
|
||||
* @psalm-suppress MissingParamType
|
||||
*/
|
||||
#[Override]
|
||||
public function setMaxResults($maxResults);
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
* @return $this
|
||||
* @psalm-suppress MissingParamType
|
||||
*/
|
||||
#[Override]
|
||||
public function delete($delete = null, $alias = null);
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
* @return $this
|
||||
* @psalm-suppress MissingParamType
|
||||
*/
|
||||
#[Override]
|
||||
public function update($update = null, $alias = null);
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
* @return $this
|
||||
* @psalm-suppress MissingParamType
|
||||
*/
|
||||
#[Override]
|
||||
public function insert($insert = null);
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
* @return $this
|
||||
* @psalm-suppress MissingParamType
|
||||
*/
|
||||
#[Override]
|
||||
public function from($from, $alias = null);
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
* @return $this
|
||||
* @psalm-suppress MissingParamType
|
||||
*/
|
||||
#[Override]
|
||||
public function join($fromAlias, $join, $alias, $condition = null);
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
* @return $this
|
||||
* @psalm-suppress MissingParamType
|
||||
*/
|
||||
#[Override]
|
||||
public function innerJoin($fromAlias, $join, $alias, $condition = null);
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
* @return $this
|
||||
* @psalm-suppress MissingParamType
|
||||
*/
|
||||
#[Override]
|
||||
public function leftJoin($fromAlias, $join, $alias, $condition = null);
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
* @return $this
|
||||
* @psalm-suppress MissingParamType
|
||||
*/
|
||||
#[Override]
|
||||
public function rightJoin($fromAlias, $join, $alias, $condition = null);
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
* @return $this
|
||||
* @psalm-suppress MissingParamType
|
||||
*/
|
||||
#[Override]
|
||||
public function set($key, $value);
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
* @return $this
|
||||
* @psalm-suppress MissingParamType
|
||||
*/
|
||||
#[Override]
|
||||
public function where(...$predicates);
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
* @return $this
|
||||
* @psalm-suppress MissingParamType
|
||||
*/
|
||||
#[Override]
|
||||
public function andWhere(...$where);
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
* @return $this
|
||||
* @psalm-suppress MissingParamType
|
||||
*/
|
||||
#[Override]
|
||||
public function orWhere(...$where);
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
* @return $this
|
||||
* @psalm-suppress MissingParamType
|
||||
*/
|
||||
#[Override]
|
||||
public function groupBy(...$groupBys);
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
* @return $this
|
||||
* @psalm-suppress MissingParamType
|
||||
*/
|
||||
#[Override]
|
||||
public function addGroupBy(...$groupBy);
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
* @return $this
|
||||
* @psalm-suppress MissingParamType
|
||||
*/
|
||||
#[Override]
|
||||
public function setValue($column, $value);
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
* @return $this
|
||||
*/
|
||||
#[Override]
|
||||
public function values(array $values);
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
* @return $this
|
||||
* @psalm-suppress MissingParamType
|
||||
*/
|
||||
#[Override]
|
||||
public function having(...$having);
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
* @return $this
|
||||
* @psalm-suppress MissingParamType
|
||||
*/
|
||||
#[Override]
|
||||
public function andHaving(...$having);
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
* @return $this
|
||||
* @psalm-suppress MissingParamType
|
||||
*/
|
||||
#[Override]
|
||||
public function orHaving(...$having);
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
* @return $this
|
||||
* @psalm-suppress MissingParamType
|
||||
*/
|
||||
#[Override]
|
||||
public function orderBy($sort, $order = null);
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
* @return $this
|
||||
* @psalm-suppress MissingParamType
|
||||
*/
|
||||
#[Override]
|
||||
public function addOrderBy($sort, $order = null);
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
* @return $this
|
||||
* @psalm-suppress MissingParamType
|
||||
*/
|
||||
#[Override]
|
||||
public function resetQueryParts($queryPartNames = null);
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
* @return $this
|
||||
* @psalm-suppress MissingParamType
|
||||
*/
|
||||
#[Override]
|
||||
public function resetQueryPart($queryPartName);
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
* @return $this
|
||||
*/
|
||||
#[Override]
|
||||
public function hintShardKey(string $column, mixed $value, bool $overwrite = false): self;
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
* @return $this
|
||||
*/
|
||||
#[Override]
|
||||
public function runAcrossAllShards(): self;
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
* @return $this
|
||||
*/
|
||||
#[Override]
|
||||
public function forUpdate(ConflictResolutionMode $conflictResolutionMode = ConflictResolutionMode::Ordinary): self;
|
||||
}
|
||||
|
||||
@@ -1,73 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH
|
||||
* SPDX-FileContributor: Carl Schwan
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
namespace OCP;
|
||||
|
||||
use OCP\AppFramework\Attribute\Consumable;
|
||||
use OCP\DB\QueryBuilder\IQueryBuilder;
|
||||
|
||||
/**
|
||||
* Since 34.0.0
|
||||
* @see https://docs.joinmastodon.org/api/guidelines/#pagination
|
||||
*/
|
||||
#[Consumable(since: '34.0.0')]
|
||||
class PaginationParameters {
|
||||
/**
|
||||
* Pagination parameters.
|
||||
*
|
||||
* You can use $minId with $maxId and $maxId with $sinceId.
|
||||
*
|
||||
* @param ?int $limit The maximum number of results to return. Set to null to
|
||||
* return all results.
|
||||
* @param ?non-empty-string $maxId All results returned will be lesser than this ID. In effect, sets an upper bound on results.
|
||||
* @param ?non-empty-string $minId Returns results immediately greater than this ID. In effect, sets a cursor at this ID and paginates forward.
|
||||
* @param ?non-empty-string $sinceId All results returned will be greater than this ID. In effect, sets a lower bound on results.
|
||||
*/
|
||||
public function __construct(
|
||||
public ?int $limit = 100,
|
||||
public ?string $maxId = null,
|
||||
public ?string $minId = null,
|
||||
public ?string $sinceId = null,
|
||||
) {
|
||||
if ($minId !== null && $sinceId !== null) {
|
||||
throw new \InvalidArgumentException("minId and sinceId can't be defined togeter");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add pagination condition to the query builder based on the configured pagination settings.
|
||||
*/
|
||||
public function fillQuery(IQueryBuilder $qb, string $idColumn): IQueryBuilder {
|
||||
// This logic is inspired by
|
||||
// https://github.com/mastodon/mastodon/blob/main/app/models/concerns/paginable.rb#L7
|
||||
|
||||
if ($this->minId !== null) {
|
||||
// paginate by min id (last entries added in the table last)
|
||||
$qb->andWhere($qb->expr()->gt($idColumn, $this->minId));
|
||||
if ($this->maxId !== null) {
|
||||
$qb->andWhere($qb->expr()->lt($idColumn, $this->maxId));
|
||||
}
|
||||
$qb->orderBy($idColumn, 'ASC');
|
||||
$qb->setMaxResults($this->limit);
|
||||
return $qb;
|
||||
} else {
|
||||
// paginate by max id (last entries added in the table first)
|
||||
if ($this->maxId !== null) {
|
||||
$qb->andWhere($qb->expr()->lt($idColumn, $this->maxId));
|
||||
}
|
||||
if ($this->sinceId !== null) {
|
||||
$qb->andWhere($qb->expr()->gt($idColumn, $this->sinceId));
|
||||
}
|
||||
$qb->orderBy($idColumn, 'DESC');
|
||||
$qb->setMaxResults($this->limit);
|
||||
return $qb;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH
|
||||
* SPDX-FileContributor: Carl Schwan
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
namespace OCP\Share;
|
||||
|
||||
/**
|
||||
* Interface IShareProviderSupportsAccept
|
||||
*
|
||||
* This interface allows to define IShareProvider that can list users for share with the getUsersForShare method,
|
||||
* which is available since Nextcloud 17.
|
||||
*
|
||||
* @since 33.0.0
|
||||
*/
|
||||
interface ICreateShareProvider extends IShareProvider {
|
||||
/**
|
||||
* Fill a share with additional information from the raw row of the database.
|
||||
*
|
||||
* @param array<string, mixed> $data
|
||||
* @return IShare $share
|
||||
* @since 34.0.0
|
||||
*/
|
||||
public function createShare(array $data): IShare;
|
||||
|
||||
/**
|
||||
* @return list<IShare::TYPE_*>
|
||||
* @since 34.0.0
|
||||
*/
|
||||
public function getShareTypes(): array;
|
||||
|
||||
/**
|
||||
* @return list<IShare::TYPE_*>
|
||||
* @since 34.0.0
|
||||
*/
|
||||
public function getTokenShareTypes(): array;
|
||||
}
|
||||
@@ -12,7 +12,6 @@ use OCP\Files\Folder;
|
||||
use OCP\Files\Node;
|
||||
|
||||
use OCP\IUser;
|
||||
use OCP\PaginationParameters;
|
||||
use OCP\Share\Exceptions\GenericShareException;
|
||||
use OCP\Share\Exceptions\ShareNotFound;
|
||||
use OCP\Share\Exceptions\ShareTokenException;
|
||||
@@ -116,23 +115,11 @@ interface IManager {
|
||||
* @param int $limit The maximum number of returned results, -1 for all results
|
||||
* @param int $offset
|
||||
* @param bool $onlyValid Only returns valid shares, invalid shares will be deleted automatically and are not returned
|
||||
* @return list<IShare>
|
||||
* @return IShare[]
|
||||
* @since 9.0.0
|
||||
*/
|
||||
public function getSharesBy(string $userId, int $shareType, ?Node $path = null, bool $reshares = false, int $limit = 50, int $offset = 0, bool $onlyValid = true): array;
|
||||
|
||||
/**
|
||||
* Get all shares shared by (initiated) by the provided user.
|
||||
*
|
||||
* @param string $userId
|
||||
* @param Node|null $path
|
||||
* @param bool $reshares
|
||||
* @param bool $onlyValid Only returns valid shares, invalid shares will be deleted automatically and are not returned
|
||||
* @return list<IShare>
|
||||
* @since 34.0.0
|
||||
*/
|
||||
public function getAllSharesBy(string $userId, ?Node $node, PaginationParameters $paginationParameters, bool $reshares = false, bool $onlyValid = true): array;
|
||||
|
||||
/**
|
||||
* Get shares shared with $user.
|
||||
* Filter by $node if provided
|
||||
@@ -144,19 +131,9 @@ interface IManager {
|
||||
* @param int $offset
|
||||
* @return IShare[]
|
||||
* @since 9.0.0
|
||||
* @deprecated 34.0.0 Use getAllSharedWith instead, it's more efficient
|
||||
*/
|
||||
public function getSharedWith(string $userId, int $shareType, ?Node $node = null, int $limit = 50, int $offset = 0): array;
|
||||
|
||||
/**
|
||||
* @param string $userId
|
||||
* @param list<IShare::TYPE_*> $shareTypes
|
||||
* @param Node|null $node
|
||||
* @return list<IShare>
|
||||
* @since 34.0.0
|
||||
*/
|
||||
public function getAllSharedWith(string $userId, array $shareTypes, ?Node $node, PaginationParameters $paginationParameters, bool $ignoreWithSelf = false): array;
|
||||
|
||||
/**
|
||||
* Get shares shared with a $user filtering by $path.
|
||||
*
|
||||
@@ -175,23 +152,11 @@ interface IManager {
|
||||
*
|
||||
* @param IShare::TYPE_* $shareType
|
||||
* @param int $limit The maximum number of shares returned, -1 for all
|
||||
* @return list<IShare>
|
||||
* @return IShare[]
|
||||
* @since 14.0.0
|
||||
* @deprecated 34.0.0 Use getAllDeletedSharedWith instead
|
||||
*/
|
||||
public function getDeletedSharedWith(string $userId, int $shareType, ?Node $node = null, int $limit = 50, int $offset = 0): array;
|
||||
|
||||
/**
|
||||
* Get all the deleted shares shared with $user.
|
||||
*
|
||||
* Additionally filter by $node if provided
|
||||
*
|
||||
* @param list<IShare::TYPE_*> $shareTypes
|
||||
* @return list<IShare>
|
||||
* @since 34.0.0
|
||||
*/
|
||||
public function getAllDeletedSharedWith(string $userId, array $shareTypes, ?Node $node = null, PaginationParameters $paginationParameters): array;
|
||||
|
||||
/**
|
||||
* Retrieve a share by the share id.
|
||||
* If the recipient is set make sure to retrieve the file for that user.
|
||||
@@ -293,9 +258,9 @@ interface IManager {
|
||||
* @return ($currentAccess is true
|
||||
* ? array{
|
||||
* users?: array<string, array{node_id: int, node_path: string}>,
|
||||
* remote?: array<string, array{node_id: int, node_path: string}>,
|
||||
* remote?: array<string, array{node_id: int, token: string}>,
|
||||
* public?: bool,
|
||||
* mail?: array<string, array{node_id: int, node_path: string}>
|
||||
* mail?: array<string, array{node_id: int, token: string}>
|
||||
* }
|
||||
* : array{users?: list<string>, remote?: bool, public?: bool, mail?: list<string>})
|
||||
* @since 12.0.0
|
||||
|
||||
@@ -1,23 +1,26 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
namespace OCP\Share;
|
||||
|
||||
use OCP\AppFramework\Attribute\Consumable;
|
||||
use OCP\Files\Node;
|
||||
|
||||
/**
|
||||
* Interface IShareHelper
|
||||
*
|
||||
* @since 12
|
||||
* @since 12.0.0
|
||||
*/
|
||||
#[Consumable(since: '12.0.0')]
|
||||
interface IShareHelper {
|
||||
/**
|
||||
* @param Node $node
|
||||
* @return array [ users => [Mapping $uid => $pathForUser], remotes => [Mapping $cloudId => $pathToMountRoot]]
|
||||
* @return array{users: array<string, string>, remotes: array<string, array{token: string, node_path: string}>} [ users => [Mapping $uid => $pathForUser], remotes => [Mapping $cloudId => $pathToMountRoot]]
|
||||
* @since 12
|
||||
*/
|
||||
public function getPathsForAccessList(Node $node);
|
||||
public function getPathsForAccessList(Node $node): array;
|
||||
}
|
||||
|
||||
@@ -129,7 +129,7 @@ interface IShareProvider {
|
||||
* Get shares for a given path
|
||||
*
|
||||
* @param Node $path
|
||||
* @return list<\OCP\Share\IShare>
|
||||
* @return \OCP\Share\IShare[]
|
||||
* @since 9.0.0
|
||||
*/
|
||||
public function getSharesByPath(Node $path);
|
||||
@@ -138,14 +138,14 @@ interface IShareProvider {
|
||||
* Get shared with the given user
|
||||
*
|
||||
* @param string $userId get shares where this user is the recipient
|
||||
* @param IShare::TYPE_* $shareType
|
||||
* @param int $shareType
|
||||
* @param Node|null $node
|
||||
* @param int $limit The max number of entries returned, -1 for all
|
||||
* @param int $offset
|
||||
* @return \OCP\Share\IShare[]
|
||||
* @since 9.0.0
|
||||
*/
|
||||
public function getSharedWith(string $userId, int $shareType, ?Node $node, int $limit, int $offset);
|
||||
public function getSharedWith($userId, $shareType, $node, $limit, $offset);
|
||||
|
||||
/**
|
||||
* Get a share by token
|
||||
@@ -206,7 +206,6 @@ interface IShareProvider {
|
||||
*
|
||||
* @return iterable<IShare>
|
||||
* @since 18.0.0
|
||||
* @deprecated 34.0.0 This is no longer needed when implementing ICreateShareProvider
|
||||
*/
|
||||
public function getAllShares(): iterable;
|
||||
|
||||
|
||||
@@ -28,6 +28,8 @@
|
||||
<file name="build/psalm/ITypedQueryBuilderTest.php"/>
|
||||
<file name="lib/private/DB/QueryBuilder/TypedQueryBuilder.php"/>
|
||||
<file name="lib/public/DB/QueryBuilder/ITypedQueryBuilder.php"/>
|
||||
<file name="lib/private/Share20/ShareHelper.php"/>
|
||||
<file name="lib/public/Share/IShareHelper.php"/>
|
||||
<ignoreFiles>
|
||||
<directory name="apps/**/composer"/>
|
||||
<directory name="apps/**/tests"/>
|
||||
|
||||
@@ -95,6 +95,7 @@ class FactoryTest extends TestCase {
|
||||
return [
|
||||
'null shortcut' => [null, null],
|
||||
'default language' => ['de', 'de'],
|
||||
'regional language' => ['de_DE', 'de_DE'],
|
||||
'malicious language' => ['de/../fr', 'defr'],
|
||||
'request language' => ['kab;q=0.8,ka;q=0.7,de;q=0.6', 'kab;q=0.8,ka;q=0.7,de;q=0.6'],
|
||||
];
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
namespace Test\Share;
|
||||
|
||||
use OC\Share20\Manager;
|
||||
use OCP\PaginationParameters;
|
||||
use OCP\Server;
|
||||
use OCP\Share\IShare;
|
||||
use OCP\Share_Backend;
|
||||
@@ -40,7 +39,10 @@ class Backend implements Share_Backend {
|
||||
|
||||
|
||||
$shareManager = Server::get(Manager::class);
|
||||
$shares = $shareManager->getAllSharedWith($shareWith, [IShare::TYPE_USER, IShare::TYPE_GROUP], null, new PaginationParameters(limit: null));
|
||||
$shares = array_merge(
|
||||
$shareManager->getSharedWith($shareWith, IShare::TYPE_USER),
|
||||
$shareManager->getSharedWith($shareWith, IShare::TYPE_GROUP),
|
||||
);
|
||||
|
||||
$knownTargets = [];
|
||||
foreach ($shares as $share) {
|
||||
|
||||
@@ -9,6 +9,7 @@ namespace Test\Share20;
|
||||
|
||||
use OC\EventDispatcher\EventDispatcher;
|
||||
use OC\Share20\LegacyHooks;
|
||||
use OC\Share20\Manager;
|
||||
use OCP\Constants;
|
||||
use OCP\EventDispatcher\IEventDispatcher;
|
||||
use OCP\Files\Cache\ICacheEntry;
|
||||
@@ -23,7 +24,6 @@ use OCP\Share\Events\ShareDeletedFromSelfEvent;
|
||||
use OCP\Share\IManager as IShareManager;
|
||||
use OCP\Share\IShare;
|
||||
use OCP\Util;
|
||||
use PHPUnit\Framework\Attributes\Group;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Test\TestCase;
|
||||
|
||||
@@ -40,11 +40,15 @@ class Dummy {
|
||||
}
|
||||
}
|
||||
|
||||
#[Group(name: 'DB')]
|
||||
class LegacyHooksTest extends TestCase {
|
||||
private LegacyHooks $hooks;
|
||||
private IEventDispatcher $eventDispatcher;
|
||||
private IShareManager $manager;
|
||||
/** @var LegacyHooks */
|
||||
private $hooks;
|
||||
|
||||
/** @var IEventDispatcher */
|
||||
private $eventDispatcher;
|
||||
|
||||
/** @var Manager */
|
||||
private $manager;
|
||||
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
@@ -18,9 +18,6 @@ use OC\Share20\Manager;
|
||||
use OC\Share20\Share;
|
||||
use OC\Share20\ShareDisableChecker;
|
||||
use OCP\Constants;
|
||||
use OCP\DB\IResult;
|
||||
use OCP\DB\QueryBuilder\IExpressionBuilder;
|
||||
use OCP\DB\QueryBuilder\IQueryBuilder;
|
||||
use OCP\EventDispatcher\Event;
|
||||
use OCP\EventDispatcher\IEventDispatcher;
|
||||
use OCP\Files\File;
|
||||
@@ -36,7 +33,6 @@ use OCP\HintException;
|
||||
use OCP\IAppConfig;
|
||||
use OCP\IConfig;
|
||||
use OCP\IDateTimeZone;
|
||||
use OCP\IDBConnection;
|
||||
use OCP\IGroup;
|
||||
use OCP\IGroupManager;
|
||||
use OCP\IL10N;
|
||||
@@ -63,7 +59,6 @@ use OCP\Share\IShareProvider;
|
||||
use OCP\Share\IShareProviderSupportsAllSharesInFolder;
|
||||
use OCP\Util;
|
||||
use PHPUnit\Framework\Attributes\DataProvider;
|
||||
use PHPUnit\Framework\Attributes\Group;
|
||||
use PHPUnit\Framework\MockObject\MockBuilder;
|
||||
use PHPUnit\Framework\MockObject\MockObject;
|
||||
use Psr\Log\LoggerInterface;
|
||||
@@ -80,7 +75,7 @@ class DummyShareManagerListener {
|
||||
*
|
||||
* @package Test\Share20
|
||||
*/
|
||||
#[Group(name: 'DB')]
|
||||
#[\PHPUnit\Framework\Attributes\Group('DB')]
|
||||
class ManagerTest extends \Test\TestCase {
|
||||
protected Manager $manager;
|
||||
protected LoggerInterface&MockObject $logger;
|
||||
@@ -102,7 +97,6 @@ class ManagerTest extends \Test\TestCase {
|
||||
private DateTimeZone $timezone;
|
||||
protected IDateTimeZone&MockObject $dateTimeZone;
|
||||
protected IAppConfig&MockObject $appConfig;
|
||||
protected IDBConnection&MockObject $connection;
|
||||
|
||||
protected function setUp(): void {
|
||||
$this->logger = $this->createMock(LoggerInterface::class);
|
||||
@@ -116,7 +110,6 @@ class ManagerTest extends \Test\TestCase {
|
||||
$this->dispatcher = $this->createMock(IEventDispatcher::class);
|
||||
$this->userSession = $this->createMock(IUserSession::class);
|
||||
$this->knownUserService = $this->createMock(KnownUserService::class);
|
||||
$this->connection = $this->createMock(IDBConnection::class);
|
||||
|
||||
$this->shareDisabledChecker = new ShareDisableChecker($this->config, $this->userManager, $this->groupManager);
|
||||
$this->dateTimeZone = $this->createMock(IDateTimeZone::class);
|
||||
@@ -164,7 +157,6 @@ class ManagerTest extends \Test\TestCase {
|
||||
$this->shareDisabledChecker,
|
||||
$this->dateTimeZone,
|
||||
$this->appConfig,
|
||||
$this->connection,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -190,7 +182,6 @@ class ManagerTest extends \Test\TestCase {
|
||||
$this->shareDisabledChecker,
|
||||
$this->dateTimeZone,
|
||||
$this->appConfig,
|
||||
$this->connection,
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -495,26 +486,6 @@ class ManagerTest extends \Test\TestCase {
|
||||
|
||||
$manager->expects($this->exactly(1))->method('updateShare')->with($reShare)->willReturn($reShare);
|
||||
|
||||
$qb = $this->createMock(IQueryBuilder::class);
|
||||
$result = $this->createMock(IResult::class);
|
||||
$qb->method('select')
|
||||
->willReturn($qb);
|
||||
$qb->method('from')
|
||||
->willReturn($qb);
|
||||
$qb->method('andWhere')
|
||||
->willReturn($qb);
|
||||
$qb->method('expr')
|
||||
->willReturn($this->createMock(IExpressionBuilder::class));
|
||||
$qb->method('executeQuery')
|
||||
->willReturn($result);
|
||||
$this->connection->method('getQueryBuilder')
|
||||
->willReturn($qb);
|
||||
$result->method('fetch')
|
||||
->willReturnOnConsecutiveCalls(
|
||||
['uid_initiator' => 'userB', 'share_type' => IShare::TYPE_USER],
|
||||
false,
|
||||
);
|
||||
|
||||
self::invokePrivate($manager, 'promoteReshares', [$share]);
|
||||
}
|
||||
|
||||
@@ -575,26 +546,6 @@ class ManagerTest extends \Test\TestCase {
|
||||
return $expected;
|
||||
});
|
||||
|
||||
$qb = $this->createMock(IQueryBuilder::class);
|
||||
$result = $this->createMock(IResult::class);
|
||||
$qb->method('select')
|
||||
->willReturn($qb);
|
||||
$qb->method('from')
|
||||
->willReturn($qb);
|
||||
$qb->method('andWhere')
|
||||
->willReturn($qb);
|
||||
$qb->method('expr')
|
||||
->willReturn($this->createMock(IExpressionBuilder::class));
|
||||
$qb->method('executeQuery')
|
||||
->willReturn($result);
|
||||
$this->connection->method('getQueryBuilder')
|
||||
->willReturn($qb);
|
||||
$result->method('fetch')
|
||||
->willReturnOnConsecutiveCalls(
|
||||
['uid_initiator' => 'userB', 'share_type' => IShare::TYPE_USER],
|
||||
false,
|
||||
);
|
||||
|
||||
self::invokePrivate($manager, 'promoteReshares', [$share]);
|
||||
}
|
||||
|
||||
@@ -623,26 +574,6 @@ class ManagerTest extends \Test\TestCase {
|
||||
/* No share is promoted because generalCreateChecks does not throw */
|
||||
$manager->expects($this->never())->method('updateShare');
|
||||
|
||||
$qb = $this->createMock(IQueryBuilder::class);
|
||||
$result = $this->createMock(IResult::class);
|
||||
$qb->method('select')
|
||||
->willReturn($qb);
|
||||
$qb->method('from')
|
||||
->willReturn($qb);
|
||||
$qb->method('andWhere')
|
||||
->willReturn($qb);
|
||||
$qb->method('expr')
|
||||
->willReturn($this->createMock(IExpressionBuilder::class));
|
||||
$qb->method('executeQuery')
|
||||
->willReturn($result);
|
||||
$this->connection->method('getQueryBuilder')
|
||||
->willReturn($qb);
|
||||
$result->method('fetch')
|
||||
->willReturnOnConsecutiveCalls(
|
||||
['uid_initiator' => 'userB', 'share_type' => IShare::TYPE_USER],
|
||||
false,
|
||||
);
|
||||
|
||||
self::invokePrivate($manager, 'promoteReshares', [$share]);
|
||||
}
|
||||
|
||||
@@ -710,27 +641,6 @@ class ManagerTest extends \Test\TestCase {
|
||||
return $expected;
|
||||
});
|
||||
|
||||
$qb = $this->createMock(IQueryBuilder::class);
|
||||
$result = $this->createMock(IResult::class);
|
||||
$qb->method('select')
|
||||
->willReturn($qb);
|
||||
$qb->method('from')
|
||||
->willReturn($qb);
|
||||
$qb->method('andWhere')
|
||||
->willReturn($qb);
|
||||
$qb->method('expr')
|
||||
->willReturn($this->createMock(IExpressionBuilder::class));
|
||||
$qb->method('executeQuery')
|
||||
->willReturn($result);
|
||||
$this->connection->method('getQueryBuilder')
|
||||
->willReturn($qb);
|
||||
$result->method('fetch')
|
||||
->willReturnOnConsecutiveCalls(
|
||||
['uid_initiator' => 'userB', 'share_type' => IShare::TYPE_USER],
|
||||
['uid_initiator' => 'userC', 'share_type' => IShare::TYPE_USER],
|
||||
false,
|
||||
);
|
||||
|
||||
self::invokePrivate($manager, 'promoteReshares', [$share]);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user