diff --git a/apps/files/tests/Sharing/Source/NodeShareSourceTypeTest.php b/apps/files/tests/Sharing/Source/NodeShareSourceTypeTest.php index 3311775b4cc7d..31d699361cfd0 100644 --- a/apps/files/tests/Sharing/Source/NodeShareSourceTypeTest.php +++ b/apps/files/tests/Sharing/Source/NodeShareSourceTypeTest.php @@ -14,7 +14,6 @@ use OC\Files\Filesystem; use OC\User\Database; use OCA\Files\Sharing\Source\NodeShareSourceType; -use OCA\Sharing\SharingBackend; use OCP\EventDispatcher\IEventDispatcher; use OCP\Files\IRootFolder; use OCP\Files\Node; @@ -95,7 +94,6 @@ public function testGetSourceIcon(): void { public function testDelete(): void { $registry = Server::get(ISharingRegistry::class); $registry->clear(); - $registry->registerSharingBackend(Server::get(SharingBackend::class)); $registry->registerSourceType($this->sourceType); $accessContext = new ShareAccessContext(currentUser: $this->user1); diff --git a/apps/sharebymail/lib/ShareByMailProvider.php b/apps/sharebymail/lib/ShareByMailProvider.php index 1f9ee46ebc7ec..2aa239b0b241d 100644 --- a/apps/sharebymail/lib/ShareByMailProvider.php +++ b/apps/sharebymail/lib/ShareByMailProvider.php @@ -224,7 +224,9 @@ protected function publishActivity(string $subject, array $parameters, string $a * @throws \Exception */ protected function createMailShare(IShare $share): string { - $share->setToken($this->generateToken()); + if ($share->getToken() === '') { + $share->setToken($this->generateToken()); + } return $this->addShareToDB( $share->getNodeId(), $share->getNodeType(), diff --git a/apps/sharebymail/tests/ShareByMailProviderTest.php b/apps/sharebymail/tests/ShareByMailProviderTest.php index 300bf7c94d0ec..2369aa544c9cd 100644 --- a/apps/sharebymail/tests/ShareByMailProviderTest.php +++ b/apps/sharebymail/tests/ShareByMailProviderTest.php @@ -670,7 +670,7 @@ public function testCreateFailed(): void { } public function testCreateMailShare(): void { - $this->share->expects($this->any())->method('getToken')->willReturn('token'); + $this->share->expects($this->any())->method('getToken')->willReturn(''); $this->share->expects($this->once())->method('setToken')->with('token'); $this->share->expects($this->any())->method('getSharedBy')->willReturn('validby@valid.com'); $this->share->expects($this->any())->method('getSharedWith')->willReturn('validwith@valid.com'); diff --git a/apps/sharing/composer/composer/autoload_classmap.php b/apps/sharing/composer/composer/autoload_classmap.php index 163ae995691ba..0d6e87b5cfd0d 100644 --- a/apps/sharing/composer/composer/autoload_classmap.php +++ b/apps/sharing/composer/composer/autoload_classmap.php @@ -27,5 +27,4 @@ 'OCA\\Sharing\\Middleware\\ShareApiEnabledMiddleware' => $baseDir . '/../lib/Middleware/ShareApiEnabledMiddleware.php', 'OCA\\Sharing\\Migration\\Version1000Date20250929161325' => $baseDir . '/../lib/Migration/Version1000Date20250929161325.php', 'OCA\\Sharing\\ResponseDefinitions' => $baseDir . '/../lib/ResponseDefinitions.php', - 'OCA\\Sharing\\SharingBackend' => $baseDir . '/../lib/SharingBackend.php', ); diff --git a/apps/sharing/composer/composer/autoload_static.php b/apps/sharing/composer/composer/autoload_static.php index 00ae4e33b5d40..22a3d83d9e4d0 100644 --- a/apps/sharing/composer/composer/autoload_static.php +++ b/apps/sharing/composer/composer/autoload_static.php @@ -42,7 +42,6 @@ class ComposerStaticInitSharing 'OCA\\Sharing\\Middleware\\ShareApiEnabledMiddleware' => __DIR__ . '/..' . '/../lib/Middleware/ShareApiEnabledMiddleware.php', 'OCA\\Sharing\\Migration\\Version1000Date20250929161325' => __DIR__ . '/..' . '/../lib/Migration/Version1000Date20250929161325.php', 'OCA\\Sharing\\ResponseDefinitions' => __DIR__ . '/..' . '/../lib/ResponseDefinitions.php', - 'OCA\\Sharing\\SharingBackend' => __DIR__ . '/..' . '/../lib/SharingBackend.php', ); public static function getInitializer(ClassLoader $loader) diff --git a/apps/sharing/lib/AppInfo/Application.php b/apps/sharing/lib/AppInfo/Application.php index 0eec114787d09..680e125b05bcc 100644 --- a/apps/sharing/lib/AppInfo/Application.php +++ b/apps/sharing/lib/AppInfo/Application.php @@ -9,15 +9,12 @@ namespace OCA\Sharing\AppInfo; -use NCU\Sharing\ISharingRegistry; use OCA\Sharing\Capabilities; use OCA\Sharing\Middleware\ShareApiEnabledMiddleware; -use OCA\Sharing\SharingBackend; use OCP\AppFramework\App; use OCP\AppFramework\Bootstrap\IBootContext; use OCP\AppFramework\Bootstrap\IBootstrap; use OCP\AppFramework\Bootstrap\IRegistrationContext; -use OCP\Server; final class Application extends App implements IBootstrap { public const string APP_ID = 'sharing'; @@ -30,9 +27,6 @@ public function __construct(array $urlParams = []) { public function register(IRegistrationContext $context): void { $context->registerCapability(Capabilities::class); $context->registerMiddleware(ShareApiEnabledMiddleware::class); - - $registry = Server::get(ISharingRegistry::class); - $registry->registerSharingBackend(Server::get(SharingBackend::class)); } #[\Override] diff --git a/lib/composer/composer/autoload_classmap.php b/lib/composer/composer/autoload_classmap.php index c58f6a0ed305d..79825014fdffc 100644 --- a/lib/composer/composer/autoload_classmap.php +++ b/lib/composer/composer/autoload_classmap.php @@ -2296,6 +2296,8 @@ 'OC\\Share20\\UserDeletedListener' => $baseDir . '/lib/private/Share20/UserDeletedListener.php', 'OC\\Share20\\UserRemovedListener' => $baseDir . '/lib/private/Share20/UserRemovedListener.php', 'OC\\Share\\Constants' => $baseDir . '/lib/private/Share/Constants.php', + 'OC\\Sharing\\ISharingLegacyBackend' => $baseDir . '/lib/private/Sharing/ISharingLegacyBackend.php', + 'OC\\Sharing\\SharingBackend' => $baseDir . '/lib/private/Sharing/SharingBackend.php', 'OC\\Sharing\\SharingManager' => $baseDir . '/lib/private/Sharing/SharingManager.php', 'OC\\Sharing\\SharingRegistry' => $baseDir . '/lib/private/Sharing/SharingRegistry.php', 'OC\\Snowflake\\APCuSequence' => $baseDir . '/lib/private/Snowflake/APCuSequence.php', diff --git a/lib/composer/composer/autoload_static.php b/lib/composer/composer/autoload_static.php index 32e4ffee59a32..260349ab1fdac 100644 --- a/lib/composer/composer/autoload_static.php +++ b/lib/composer/composer/autoload_static.php @@ -2337,6 +2337,8 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2 'OC\\Share20\\UserDeletedListener' => __DIR__ . '/../../..' . '/lib/private/Share20/UserDeletedListener.php', 'OC\\Share20\\UserRemovedListener' => __DIR__ . '/../../..' . '/lib/private/Share20/UserRemovedListener.php', 'OC\\Share\\Constants' => __DIR__ . '/../../..' . '/lib/private/Share/Constants.php', + 'OC\\Sharing\\ISharingLegacyBackend' => __DIR__ . '/../../..' . '/lib/private/Sharing/ISharingLegacyBackend.php', + 'OC\\Sharing\\SharingBackend' => __DIR__ . '/../../..' . '/lib/private/Sharing/SharingBackend.php', 'OC\\Sharing\\SharingManager' => __DIR__ . '/../../..' . '/lib/private/Sharing/SharingManager.php', 'OC\\Sharing\\SharingRegistry' => __DIR__ . '/../../..' . '/lib/private/Sharing/SharingRegistry.php', 'OC\\Snowflake\\APCuSequence' => __DIR__ . '/../../..' . '/lib/private/Snowflake/APCuSequence.php', diff --git a/lib/private/Share20/Manager.php b/lib/private/Share20/Manager.php index 74199ff5ca362..f1b8dbc4ce97e 100644 --- a/lib/private/Share20/Manager.php +++ b/lib/private/Share20/Manager.php @@ -268,7 +268,7 @@ protected function generalChecks(IShare $share): void { throw new GenericShareException($isRestricted, code: 403); } } catch (\Exception $exception) { - throw new GenericShareException($exception->getMessage(), $exception instanceof HintException ? $exception->getHint() : '', code: 403); + throw new GenericShareException($exception->getMessage(), $exception instanceof HintException ? $exception->getHint() : '', code: 403, previous: $exception); } } } @@ -567,9 +567,11 @@ public function createShare(IShare $share): IShare { || $share->getShareType() === IShare::TYPE_EMAIL) { $this->setLinkParent($share); - $token = $this->generateToken(); - // Set the unique token - $share->setToken($token); + if ($share->getToken() === '') { + $token = $this->generateToken(); + // Set the unique token + $share->setToken($token); + } // Verify the expiration date $share = $this->validateExpirationDateLink($share); diff --git a/lib/private/Share20/Share.php b/lib/private/Share20/Share.php index 3f8d3d97b21ef..d88710e4e191f 100644 --- a/lib/private/Share20/Share.php +++ b/lib/private/Share20/Share.php @@ -31,7 +31,7 @@ class Share implements IShare { private $fileId; /** @var string */ private $nodeType; - /** @var int */ + /** @var IShare::TYPE_* */ private $shareType; /** @var string */ private $sharedWith; @@ -44,11 +44,11 @@ class Share implements IShare { private $sharedBy; /** @var string */ private $shareOwner; - /** @var int */ + /** @var int-mask-of */ private $permissions; /** @var IAttributes */ private $attributes; - /** @var int */ + /** @var self::STATUS_* */ private $status; /** @var string */ private $note = ''; @@ -60,7 +60,7 @@ class Share implements IShare { /** @var bool */ private $sendPasswordByTalk = false; /** @var string */ - private $token; + private $token = ''; private ?int $parent = null; /** @var string */ private $target; @@ -358,7 +358,7 @@ public function setStatus(int $status): IShare { * @inheritdoc */ #[\Override] - public function getStatus(): int { + public function getStatus(): ?int { return $this->status; } diff --git a/lib/private/Sharing/ISharingLegacyBackend.php b/lib/private/Sharing/ISharingLegacyBackend.php new file mode 100644 index 0000000000000..5aa9f45271e75 --- /dev/null +++ b/lib/private/Sharing/ISharingLegacyBackend.php @@ -0,0 +1,59 @@ +> + */ + public function getCompatibleSourceTypes(): array; + + /** + * @return list> + */ + public function getCompatibleRecipientTypes(): array; + + /** + * Update a share. + */ + public function updateShare(Share $share): void; + + /** + * Delete a share. + * + * @throws ShareNotFoundException + */ + public function deleteShare(string $id): void; + + /** + * Get a share. + * + * @throws ShareNotFoundException + */ + public function getShare(ShareAccessContext $accessContext, string $id): Share; + + /** + * Get multiple shares. + * + * @param ?class-string $filterSourceTypeClass + * @param ?positive-int $limit + * @return list + */ + public function getShares(ShareAccessContext $accessContext, ?string $filterSourceTypeClass, ?string $filterSourceTypeValue, ?string $lastShareID, ?int $limit): array; +} diff --git a/apps/sharing/lib/SharingBackend.php b/lib/private/Sharing/SharingBackend.php similarity index 91% rename from apps/sharing/lib/SharingBackend.php rename to lib/private/Sharing/SharingBackend.php index aa196bc00ac50..5d6cb5d4017d2 100644 --- a/apps/sharing/lib/SharingBackend.php +++ b/lib/private/Sharing/SharingBackend.php @@ -7,7 +7,7 @@ declare(strict_types=1); -namespace OCA\Sharing; +namespace OC\Sharing; use Exception; use NCU\Sharing\Exception\ShareInvalidException; @@ -36,7 +36,6 @@ use OCP\IUser; use OCP\IUserManager; use OCP\L10N\IFactory; -use OCP\Snowflake\ISnowflakeGenerator; use RuntimeException; // TODO: Add mapping table for class names in sources, recipients, permissions and properties @@ -51,7 +50,6 @@ public function __construct( IFactory $factory, private IDBConnection $connection, private IUserManager $userManager, - private ISnowflakeGenerator $snowflakeGenerator, private IAppConfig $appConfig, private ISharingRegistry $registry, private ISharingManager $manager, @@ -60,10 +58,7 @@ public function __construct( } #[\Override] - public function createShare(ShareUser $owner): string { - $id = $this->snowflakeGenerator->nextId(); - $lastUpdated = $this->manager->generateTimestamp(); - + public function createShare(string $id, ShareUser $owner, int $lastUpdated): void { $qb = $this->connection->getQueryBuilder(); $qb ->insert('sharing_share') @@ -75,15 +70,14 @@ public function createShare(ShareUser $owner): string { 'state' => $qb->createNamedParameter(ShareState::Draft->value), ]) ->executeStatement(); - - return $id; } #[\Override] - public function onOwnerDeleted(ShareUser $owner): void { + public function onOwnerDeleted(ShareUser $owner): array { $qb = $this->connection->getQueryBuilder(); $qb - ->delete('sharing_share') + ->selectDistinct('id') + ->from('sharing_share') ->where($qb->expr()->eq('owner_user_id', $qb->createNamedParameter($owner->userId))); if ($owner->instance === null) { $qb->andWhere($qb->expr()->isNull('owner_instance')); @@ -91,7 +85,25 @@ public function onOwnerDeleted(ShareUser $owner): void { $qb->andWhere($qb->expr()->eq('owner_instance', $qb->createNamedParameter($owner->instance))); } - $qb->executeStatement(); + $result = $qb->executeQuery(); + + /** @var list $ids */ + $ids = $result->fetchFirstColumn(); + if ($ids === []) { + return []; + } + + $ids = array_map(static fn (string|int $id): string => (string)$id, $ids); + + foreach (array_chunk($ids, 1000) as $chunk) { + $qb = $this->connection->getQueryBuilder(); + $qb + ->delete('sharing_share') + ->where($qb->expr()->in('id', $qb->createNamedParameter($chunk, IQueryBuilder::PARAM_STR_ARRAY))) + ->executeStatement(); + } + + return $ids; } #[\Override] @@ -172,6 +184,10 @@ public function onSourceDeleted(ShareSource $source): array { #[\Override] public function addShareRecipient(string $id, ShareRecipient $recipient): void { + if ($recipient->secret === null) { + throw new RuntimeException('The secret must not be null.'); + } + if (!$recipient->initiator instanceof ShareUser) { throw new RuntimeException('The initiator must not be null.'); } @@ -184,7 +200,7 @@ public function addShareRecipient(string $id, ShareRecipient $recipient): void { 'recipient_class' => $qb->createNamedParameter($recipient->class), 'recipient_value' => $qb->createNamedParameter($recipient->value), 'recipient_instance' => $qb->createNamedParameter($recipient->instance), - 'recipient_secret' => $qb->createNamedParameter($this->manager->generateSecret()), + 'recipient_secret' => $qb->createNamedParameter($recipient->secret), 'initiator_user_id' => $qb->createNamedParameter($recipient->initiator->userId), 'initiator_instance' => $qb->createNamedParameter($recipient->initiator->instance), ]; @@ -324,6 +340,35 @@ public function updateShareRecipientSecret(string $id, ShareRecipient $recipient } } + #[\Override] + public function createShareProperty(string $id, ShareProperty $property): void { + $value = $property->value; + + $propertyType = $this->registry->getPropertyTypes()[$property->class]; + + if ($propertyType instanceof ISharePropertyTypeModifyValue) { + $value = $propertyType->modifyValueOnSave(null, $property->value); + } + + try { + $qb = $this->connection->getQueryBuilder(); + $qb + ->insert('sharing_share_properties') + ->values([ + 'share_id' => $qb->createNamedParameter($id), + 'property_class' => $qb->createNamedParameter($property->class), + 'property_value' => $qb->createNamedParameter($value), + ]) + ->executeStatement(); + } catch (\OCP\DB\Exception $exception) { + if ($exception->getReason() === \OCP\DB\Exception::REASON_UNIQUE_CONSTRAINT_VIOLATION) { + throw new RuntimeException('The property already exists: ' . $property->class, $exception->getCode(), $exception); + } + + throw $exception; + } + } + #[\Override] public function updateShareProperty(string $id, ShareProperty $property): void { $value = $property->value; @@ -359,6 +404,27 @@ public function updateShareProperty(string $id, ShareProperty $property): void { } } + #[\Override] + public function createSharePermission(string $id, SharePermission $permission): void { + try { + $qb = $this->connection->getQueryBuilder(); + $qb + ->insert('sharing_share_permissions') + ->values([ + 'share_id' => $qb->createNamedParameter($id), + 'permission_class' => $qb->createNamedParameter($permission->class), + 'permission_enabled' => $qb->createNamedParameter($permission->enabled, IQueryBuilder::PARAM_BOOL), + ]) + ->executeStatement(); + } catch (\OCP\DB\Exception $exception) { + if ($exception->getReason() === \OCP\DB\Exception::REASON_UNIQUE_CONSTRAINT_VIOLATION) { + throw new RuntimeException('The permission already exists: ' . $permission->class, $exception->getCode(), $exception); + } + + throw $exception; + } + } + #[\Override] public function updateSharePermission(string $id, SharePermission $permission): void { $qb = $this->connection->getQueryBuilder(); @@ -589,8 +655,6 @@ private function list(ShareAccessContext $accessContext, ?string $filterShareID, $result = $qb->executeQuery(); $rows = $result->fetchAll(); foreach ($rows as $row) { - /** @var non-empty-string $id */ - $id = (string)$row['id']; /** @var non-empty-string $ownerUserId */ $ownerUserId = $row['owner_user_id']; /** @var ?non-empty-string $ownerInstance */ @@ -601,6 +665,8 @@ private function list(ShareAccessContext $accessContext, ?string $filterShareID, continue; } + /** @var non-empty-string $id */ + $id = (string)$row['id']; /** @var non-negative-int $lastUpdated */ $lastUpdated = (int)$row['last_updated']; /** @var string $state */ @@ -916,79 +982,23 @@ private function list(ShareAccessContext $accessContext, ?string $filterShareID, } foreach (array_keys($shares) as $id) { - foreach ($registryPropertyTypes as $propertyTypeClass => $propertyType) { + foreach (array_keys($registryPropertyTypes) as $propertyTypeClass) { $share = $shares[$id]; if ( !isset($share->properties[$propertyTypeClass]) && isset($shareSourceTypeClasses[$id], $shareRecipientTypeClasses[$id]) && array_intersect($registryPropertyTypeCompatibleSourceTypeClasses[$propertyTypeClass], array_keys($shareSourceTypeClasses[$id])) !== [] && array_intersect($registryPropertyTypeCompatibleRecipientTypeClasses[$propertyTypeClass], array_keys($shareRecipientTypeClasses[$id])) !== []) { - $value = $propertyType->getDefaultValue($share); - - $timestamp = $this->manager->generateTimestamp(); - $this->setLastUpdated([(string)$id], $timestamp); - - $qb = $this->connection->getQueryBuilder(); - $qb - ->insert('sharing_share_properties') - ->values([ - 'share_id' => $qb->createNamedParameter($id), - 'property_class' => $qb->createNamedParameter($propertyTypeClass), - 'property_value' => $qb->createNamedParameter($propertyType instanceof ISharePropertyTypeModifyValue ? $propertyType->modifyValueOnSave(null, $value) : $value), - ]) - ->executeStatement(); - - $properties = $share->properties; - $properties[$propertyTypeClass] = new ShareProperty($propertyTypeClass, $value); - - $shares[$id] = new Share( - $share->id, - $share->owner, - $timestamp, - $share->state, - $share->sources, - $share->recipients, - $properties, - $share->permissions, - ); + $shares[$id] = $this->manager->createSharePropertyDefaultValue($shares[$id], $propertyTypeClass); } } } - $permissionTypes = $this->registry->getPermissionTypes(); foreach (array_keys($shares) as $id) { foreach (array_keys($shareCompatiblePermissionTypeClasses[$id]) as $permissionTypeClass) { $share = $shares[$id]; if (!isset($share->permissions[$permissionTypeClass])) { - $permissionType = $permissionTypes[$permissionTypeClass]; - $enabled = $permissionType->isEnabledByDefault(); - - $timestamp = $this->manager->generateTimestamp(); - $this->setLastUpdated([(string)$id], $timestamp); - - $qb = $this->connection->getQueryBuilder(); - $qb - ->insert('sharing_share_permissions') - ->values([ - 'share_id' => $qb->createNamedParameter($id), - 'permission_class' => $qb->createNamedParameter($permissionTypeClass), - 'permission_enabled' => $qb->createNamedParameter($enabled, IQueryBuilder::PARAM_BOOL), - ]) - ->executeStatement(); - - $permissions = $share->permissions; - $permissions[$permissionTypeClass] = new SharePermission($permissionTypeClass, $enabled); - - $shares[$id] = new Share( - $share->id, - $share->owner, - $timestamp, - $share->state, - $share->sources, - $share->recipients, - $share->properties, - $permissions, - ); + $shares[$id] = $this->manager->createSharePermissionDefaultValue($shares[$id], $permissionTypeClass); } } } diff --git a/lib/private/Sharing/SharingManager.php b/lib/private/Sharing/SharingManager.php index e4d77626b2075..76593a8f4bc0b 100644 --- a/lib/private/Sharing/SharingManager.php +++ b/lib/private/Sharing/SharingManager.php @@ -28,12 +28,10 @@ use NCU\Sharing\ShareUser; use NCU\Sharing\Source\ShareSource; use OC\Core\Sharing\Permission\ReshareSharePermissionType; -use OCA\Sharing\SharingBackend; use OCP\EventDispatcher\Event; use OCP\EventDispatcher\IEventDispatcher; use OCP\EventDispatcher\IEventListener; -use OCP\ICache; -use OCP\ICacheFactory; +use OCP\IAppConfig; use OCP\IDBConnection; use OCP\IL10N; use OCP\Interaction\Actions\ShareAction; @@ -42,6 +40,7 @@ use OCP\IUserManager; use OCP\L10N\IFactory; use OCP\Security\ISecureRandom; +use OCP\Snowflake\ISnowflakeGenerator; use OCP\User\Events\BeforeUserDeletedEvent; use Random\Randomizer; use RuntimeException; @@ -59,31 +58,31 @@ final readonly class SharingManager implements ISharingManager, IEventListener { private Randomizer $randomizer; - private ICache $backendCache; - private IL10N $l10n; + private ISharingBackend $backend; + public function __construct( - ICacheFactory $cacheFactory, IEventDispatcher $eventDispatcher, private IUserManager $userManager, private IFactory $l10nFactory, + private ISnowflakeGenerator $snowflakeGenerator, private IDBConnection $dbConnection, private ISharingRegistry $registry, + IAppConfig $appConfig, ) { $this->randomizer = new Randomizer(); - - if ($cacheFactory->isAvailable()) { - $this->backendCache = $cacheFactory->createDistributed('sharing'); - } elseif ($cacheFactory->isLocalCacheAvailable()) { - $this->backendCache = $cacheFactory->createLocal('sharing'); - } else { - $this->backendCache = $cacheFactory->createInMemory(); - } + $this->l10n = $l10nFactory->get('sharing'); + $this->backend = new SharingBackend( + $l10nFactory, + $dbConnection, + $userManager, + $appConfig, + $registry, + $this, + ); $eventDispatcher->addServiceListener(BeforeUserDeletedEvent::class, self::class); - - $this->l10n = $this->l10nFactory->get('sharing'); } #[\Override] @@ -158,9 +157,11 @@ public function createShare(ShareAccessContext $accessContext): string { $this->assertInTransaction(); - $backend = $this->getBackend(null); - $id = $backend->createShare(new ShareUser($currentUser->getUID(), null)); - $this->backendCache->set($id, $backend::class); + $id = $this->snowflakeGenerator->nextId(); + $lastUpdated = $this->generateTimestamp(); + $this->backend->createShare($id, new ShareUser($currentUser->getUID(), null), $lastUpdated); + + $this->processShareUpdates([$id]); return $id; } @@ -175,8 +176,13 @@ public function onOwnerDeleted(ShareAccessContext $accessContext, ShareUser $own // No need to update the last updated timestamp, because the share will be deleted anyway. - foreach ($this->registry->getSharingBackends() as $backend) { - $backend->onOwnerDeleted($owner); + $ids = $this->backend->onOwnerDeleted($owner); + + $legacyBackend = $this->registry->getLegacyBackend(); + if ($legacyBackend instanceof ISharingLegacyBackend) { + foreach ($ids as $id) { + $legacyBackend->deleteShare($id); + } } } @@ -184,28 +190,28 @@ public function onOwnerDeleted(ShareAccessContext $accessContext, ShareUser $own public function updateShareState(ShareAccessContext $accessContext, string $id, ShareState $state): void { $this->assertInTransaction(); - $backend = $this->getBackend($id); - $backend->setLastUpdated([$id], $this->generateTimestamp()); + $this->backend->setLastUpdated([$id], $this->generateTimestamp()); - $owner = $backend->getShareOwner($id); + $owner = $this->backend->getShareOwner($id); $this->validateShareOwnerOperation($accessContext, $owner); if ($state === ShareState::Active) { - $share = $this->getShare($accessContext, $id, $backend); + $share = $this->getShare($accessContext, $id); $this->assertShareCanBeActive($share); } - $backend->updateShareState($id, $state); + $this->backend->updateShareState($id, $state); + + $this->processShareUpdates([$id]); } #[\Override] public function addShareSource(ShareAccessContext $accessContext, string $id, ShareSource $source): void { $this->assertInTransaction(); - $backend = $this->getBackend($id); - $backend->setLastUpdated([$id], $this->generateTimestamp()); + $this->backend->setLastUpdated([$id], $this->generateTimestamp()); - $owner = $backend->getShareOwner($id); + $owner = $this->backend->getShareOwner($id); $this->validateShareOwnerOperation($accessContext, $owner); if (($sourceType = $this->registry->getSourceTypes()[$source->class] ?? null) === null) { @@ -216,29 +222,42 @@ public function addShareSource(ShareAccessContext $accessContext, string $id, Sh throw new ShareInvalidException('Invalid source: ' . $source->value . ' ' . $source->class, $this->l10n->t('The source does not exist.')); } + $share = $this->getShare($accessContext, $id); + $sources = $share->sources; + $sources[] = $source; + $share = new Share( + $share->id, + $share->owner, + $share->lastUpdated, + $share->state, + $sources, + $share->recipients, + $share->properties, + $share->permissions, + ); + if (!$accessContext->overrideChecks) { - $share = $this->getShare($accessContext, $id, $backend); - $sources = $share->sources; - $sources[] = $source; - $this->validateInteraction($accessContext, $owner, $sources, $share->getEnabledPermissions(), $share->recipients); + $this->validateInteraction($accessContext, $share); } - $backend->addShareSource($id, $source); + $this->backend->addShareSource($id, $source); + + // The modified share object has to be used instead of fetching the share again, because it would trigger the insertion of default values prematurely. + $this->processShareUpdates([$share]); } #[\Override] public function removeShareSource(ShareAccessContext $accessContext, string $id, ShareSource $source): void { $this->assertInTransaction(); - $backend = $this->getBackend($id); - $backend->setLastUpdated([$id], $this->generateTimestamp()); + $this->backend->setLastUpdated([$id], $this->generateTimestamp()); - $owner = $backend->getShareOwner($id); + $owner = $this->backend->getShareOwner($id); $this->validateShareOwnerOperation($accessContext, $owner); - $backend->removeShareSource($id, $source); + $this->backend->removeShareSource($id, $source); - $this->makeSharesDraftIfNeeded([$id]); + $this->processShareUpdates([$id]); } #[\Override] @@ -251,18 +270,14 @@ public function onSourceDeleted(ShareAccessContext $accessContext, ShareSource $ $timestamp = $this->generateTimestamp(); - $ids = []; - foreach ($this->registry->getSharingBackends() as $backend) { - $updatedIds = $backend->onSourceDeleted($source); - if ($updatedIds === []) { - continue; - } - - $backend->setLastUpdated($updatedIds, $timestamp); - $ids[] = $updatedIds; + $updatedIds = $this->backend->onSourceDeleted($source); + if ($updatedIds === []) { + return; } - $this->makeSharesDraftIfNeeded(array_merge(...$ids)); + $this->backend->setLastUpdated($updatedIds, $timestamp); + + $this->processShareUpdates($updatedIds); } #[\Override] @@ -273,16 +288,15 @@ public function addShareRecipient(ShareAccessContext $accessContext, string $id, $this->assertInTransaction(); - $backend = $this->getBackend($id); - $backend->setLastUpdated([$id], $this->generateTimestamp()); + $this->backend->setLastUpdated([$id], $this->generateTimestamp()); - $owner = $backend->getShareOwner($id); + $owner = $this->backend->getShareOwner($id); try { $this->validateShareOwnerOperation($accessContext, $owner); $share = null; } catch (ShareOperationForbiddenException) { - $share = $this->getShare($accessContext, $id, $backend); + $share = $this->getShare($accessContext, $id); $this->validatePermission($share, ReshareSharePermissionType::class); } @@ -294,22 +308,41 @@ public function addShareRecipient(ShareAccessContext $accessContext, string $id, throw new ShareInvalidException('Invalid recipient: ' . $recipient->value . ' ' . $recipient->class . ' ' . ($recipient->instance ?? 'local'), $this->l10n->t('The recipient does not exist.')); } - if (!$accessContext->overrideChecks) { - $share ??= $this->getShare($accessContext, $id, $backend); - $recipients = $share->recipients; - $recipients[] = $recipient; - $this->validateInteraction($accessContext, $owner, $share->sources, $share->getEnabledPermissions(), $recipients); - } - - $recipient = new ShareRecipient( - $recipient->class, - $recipient->value, - $recipient->instance, - $recipient->secret, - new ShareUser($currentUser->getUID(), null), + $share ??= $this->getShare($accessContext, $id); + $recipients = $share->recipients; + $recipients[] = $recipient; + $share = new Share( + $share->id, + $share->owner, + $share->lastUpdated, + $share->state, + $share->sources, + $recipients, + $share->properties, + $share->permissions, ); - $backend->addShareRecipient($id, $recipient); + if (!$accessContext->overrideChecks) { + $this->validateInteraction($accessContext, $share); + } + + if ($recipient->secret === null || !$recipient->initiator instanceof ShareUser) { + $secret = $recipient->secret ?? $this->generateSecret(); + $initiator = $recipient->initiator ?? new ShareUser($currentUser->getUID(), null); + + $recipient = new ShareRecipient( + $recipient->class, + $recipient->value, + $recipient->instance, + $secret, + $initiator, + ); + } + + $this->backend->addShareRecipient($id, $recipient); + + // The modified share object has to be used instead of fetching the share again, because it would trigger the insertion of default values prematurely. + $this->processShareUpdates([$share]); } #[\Override] @@ -318,22 +351,21 @@ public function removeShareRecipient(ShareAccessContext $accessContext, string $ $this->assertInTransaction(); - $backend = $this->getBackend($id); - $backend->setLastUpdated([$id], $this->generateTimestamp()); + $this->backend->setLastUpdated([$id], $this->generateTimestamp()); - $owner = $backend->getShareOwner($id); + $owner = $this->backend->getShareOwner($id); try { $this->validateShareOwnerOperation($accessContext, $owner); } catch (ShareOperationForbiddenException) { - $share = $this->getShare($accessContext, $id, $backend); + $share = $this->getShare($accessContext, $id); // This does not allow removing own recipients. A user can only reject a share, but not remove it for the recipient. $this->validateReshareOperation($accessContext, $share, $recipient); } - $backend->removeShareRecipient($id, $recipient); + $this->backend->removeShareRecipient($id, $recipient); - $this->makeSharesDraftIfNeeded([$id]); + $this->processShareUpdates([$id]); } #[\Override] @@ -346,18 +378,14 @@ public function onRecipientDeleted(ShareAccessContext $accessContext, ShareRecip $timestamp = $this->generateTimestamp(); - $ids = []; - foreach ($this->registry->getSharingBackends() as $backend) { - $updatedIds = $backend->onRecipientDeleted($recipient); - if ($updatedIds === []) { - continue; - } - - $backend->setLastUpdated($updatedIds, $timestamp); - $ids[] = $updatedIds; + $updatedIds = $this->backend->onRecipientDeleted($recipient); + if ($updatedIds === []) { + return; } - $this->makeSharesDraftIfNeeded(array_merge(...$ids)); + $this->backend->setLastUpdated($updatedIds, $timestamp); + + $this->processShareUpdates($updatedIds); } #[\Override] @@ -370,31 +398,28 @@ public function onInitiatorDeleted(ShareAccessContext $accessContext, ShareUser $timestamp = $this->generateTimestamp(); - foreach ($this->registry->getSharingBackends() as $backend) { - $updatedIds = $backend->onInitiatorDeleted($initiator); - if ($updatedIds === []) { - continue; - } - - $backend->setLastUpdated($updatedIds, $timestamp); + $updatedIds = $this->backend->onInitiatorDeleted($initiator); + if ($updatedIds === []) { + return; } - // No need to make shares draft, because promoting a reshare to the owner doesn't remove recipients. + $this->backend->setLastUpdated($updatedIds, $timestamp); + + $this->processShareUpdates($updatedIds); } #[\Override] public function updateShareRecipientSecret(ShareAccessContext $accessContext, string $id, ShareRecipient $recipient, string $secret): void { $this->assertInTransaction(); - $backend = $this->getBackend($id); - $backend->setLastUpdated([$id], $this->generateTimestamp()); + $this->backend->setLastUpdated([$id], $this->generateTimestamp()); - $owner = $backend->getShareOwner($id); + $owner = $this->backend->getShareOwner($id); try { $this->validateShareOwnerOperation($accessContext, $owner); } catch (ShareOperationForbiddenException) { - $share = $this->getShare($accessContext, $id, $backend); + $share = $this->getShare($accessContext, $id); $this->validateReshareOperation($accessContext, $share, $recipient); } @@ -410,17 +435,52 @@ public function updateShareRecipientSecret(ShareAccessContext $accessContext, st throw new ShareInvalidException('Invalid secret: ' . $secret, $this->l10n->t('The value must be alphanumeric, 1 to 32 characters long and may contain dashes.')); } - $backend->updateShareRecipientSecret($id, $recipient, $secret); + $this->backend->updateShareRecipientSecret($id, $recipient, $secret); + + $this->processShareUpdates([$id]); + } + + #[\Override] + public function createSharePropertyDefaultValue(Share $share, string $propertyTypeClass): Share { + $this->assertInTransaction(); + + $timestamp = $this->generateTimestamp(); + $this->backend->setLastUpdated([$share->id], $timestamp); + + if (($propertyType = $this->registry->getPropertyTypes()[$propertyTypeClass] ?? null) === null) { + throw new RuntimeException('The property is not registered: ' . $propertyTypeClass); + } + + $property = new ShareProperty($propertyTypeClass, $propertyType->getDefaultValue($share)); + + $this->backend->createShareProperty($share->id, $property); + + $properties = $share->properties; + $properties[$propertyTypeClass] = $property; + + $share = new Share( + $share->id, + $share->owner, + $timestamp, + $share->state, + $share->sources, + $share->recipients, + $properties, + $share->permissions, + ); + + [$share] = $this->processShareUpdates([$share]); + + return $share; } #[\Override] public function updateShareProperty(ShareAccessContext $accessContext, string $id, ShareProperty $property): void { $this->assertInTransaction(); - $backend = $this->getBackend($id); - $backend->setLastUpdated([$id], $this->generateTimestamp()); + $this->backend->setLastUpdated([$id], $this->generateTimestamp()); - $owner = $backend->getShareOwner($id); + $owner = $this->backend->getShareOwner($id); $this->validateShareOwnerOperation($accessContext, $owner); if (($propertyType = $this->registry->getPropertyTypes()[$property->class] ?? null) === null) { @@ -434,94 +494,130 @@ public function updateShareProperty(ShareAccessContext $accessContext, string $i } } - $backend->updateShareProperty($id, $property); + $this->backend->updateShareProperty($id, $property); + + $this->processShareUpdates([$id]); + } + + #[\Override] + public function createSharePermissionDefaultValue(Share $share, string $permissionTypeClass): Share { + $this->assertInTransaction(); + + $timestamp = $this->generateTimestamp(); + $this->backend->setLastUpdated([$share->id], $timestamp); + + if (($permissionType = $this->registry->getPermissionTypes()[$permissionTypeClass] ?? null) === null) { + throw new RuntimeException('The permission is not registered: ' . $permissionTypeClass); + } + + $permission = new SharePermission($permissionTypeClass, $permissionType->isEnabledByDefault()); + + $this->backend->createSharePermission($share->id, $permission); + + $permissions = $share->permissions; + $permissions[$permissionTypeClass] = $permission; + + $share = new Share( + $share->id, + $share->owner, + $timestamp, + $share->state, + $share->sources, + $share->recipients, + $share->properties, + $permissions, + ); + + [$share] = $this->processShareUpdates([$share]); - $this->makeSharesDraftIfNeeded([$id]); + return $share; } #[\Override] public function updateSharePermission(ShareAccessContext $accessContext, string $id, SharePermission $permission): void { $this->assertInTransaction(); - $backend = $this->getBackend($id); - $backend->setLastUpdated([$id], $this->generateTimestamp()); + $this->backend->setLastUpdated([$id], $this->generateTimestamp()); - $owner = $backend->getShareOwner($id); + $owner = $this->backend->getShareOwner($id); $this->validateShareOwnerOperation($accessContext, $owner); if (!isset($this->registry->getPermissionTypes()[$permission->class])) { throw new RuntimeException('The permission type is not registered: ' . $permission->class); } + $share = $this->getShare($accessContext, $id); + $permissions = $share->permissions; + $permissions[$permission->class] = $permission; + $share = new Share( + $share->id, + $share->owner, + $share->lastUpdated, + $share->state, + $share->sources, + $share->recipients, + $share->properties, + $permissions, + ); + if (!$accessContext->overrideChecks) { - $share = $this->getShare($accessContext, $id, $backend); - $permissions = $share->permissions; - $permissions[$permission->class] = $permission; - $this->validateInteraction($accessContext, $owner, $share->sources, array_filter($permissions, static fn (SharePermission $permission): bool => $permission->enabled), $share->recipients); + $this->validateInteraction($accessContext, $share); } - $backend->updateSharePermission($id, $permission); + $this->backend->updateSharePermission($id, $permission); - $this->makeSharesDraftIfNeeded([$id]); + // The modified share object has to be used instead of fetching the share again, because it would trigger the insertion of default values prematurely. + $this->processShareUpdates([$share]); } #[\Override] public function selectSharePermissionPreset(ShareAccessContext $accessContext, string $id, string $permissionPresetClass): void { $this->assertInTransaction(); - $backend = $this->getBackend($id); - $backend->setLastUpdated([$id], $this->generateTimestamp()); + $this->backend->setLastUpdated([$id], $this->generateTimestamp()); - $owner = $backend->getShareOwner($id); + $owner = $this->backend->getShareOwner($id); $this->validateShareOwnerOperation($accessContext, $owner); if (($this->registry->getPermissionPresetCompatiblePermissionTypeClasses()[$permissionPresetClass] ?? null) === null) { throw new RuntimeException('The permission preset is not registered: ' . $permissionPresetClass); } - $backend->selectSharePermissionPreset($id, $permissionPresetClass); + $this->backend->selectSharePermissionPreset($id, $permissionPresetClass); + + $this->processShareUpdates([$id]); } #[\Override] public function deleteShare(ShareAccessContext $accessContext, string $id): void { $this->assertInTransaction(); - $backend = $this->getBackend($id); - $owner = $backend->getShareOwner($id); + $owner = $this->backend->getShareOwner($id); // No need to update the last updated timestamp, because the share will be deleted anyway. $this->validateShareOwnerOperation($accessContext, $owner); - $backend->deleteShare($id); + $this->backend->deleteShare($id); + + $legacyBackend = $this->registry->getLegacyBackend(); + if ($legacyBackend instanceof ISharingLegacyBackend) { + $legacyBackend->deleteShare($id); + } } #[\Override] - public function getShare(ShareAccessContext $accessContext, string $id, ?ISharingBackend $backend = null): Share { + public function getShare(ShareAccessContext $accessContext, string $id): Share { $this->assertInTransaction(); - return ($backend ?? $this->getBackend($id))->getShare($accessContext, $id); + return $this->backend->getShare($accessContext, $id); } #[\Override] public function getShares(ShareAccessContext $accessContext, ?string $filterSourceTypeClass, ?string $filterSourceTypeValue, ?string $lastShareID, ?int $limit): array { $this->assertInTransaction(); - $shares = []; - - // TODO: Deal with more results than limit? - foreach ($this->registry->getSharingBackends() as $backend) { - $backendShares = $backend->getShares($accessContext, $filterSourceTypeClass, $filterSourceTypeValue, $lastShareID, $limit); - $shares[] = $backendShares; - - foreach ($backendShares as $share) { - if ($this->backendCache->get($share->id) === null) { - $this->backendCache->set($share->id, $backend::class); - } - } - } - - return array_merge(...$shares); + return $this->backend->getShares($accessContext, $filterSourceTypeClass, $filterSourceTypeValue, $lastShareID, $limit); } #[\Override] @@ -545,39 +641,6 @@ private function assertInTransaction(): void { } } - private function getBackend(?string $id): ISharingBackend { - $availableBackends = $this->registry->getSharingBackends(); - if ($availableBackends === []) { - throw new RuntimeException('No sharing backends registered'); - } - - $selectedBackend = null; - if ($id === null) { - // For new shares we only use the backend from the sharing app. - $selectedBackend = $availableBackends[SharingBackend::class] ?? null; - } else { - /** @var ?class-string $cachedBackendClass */ - $cachedBackendClass = $this->backendCache->get($id); - if ($cachedBackendClass !== null) { - $selectedBackend = $availableBackends[$cachedBackendClass] ?? null; - } else { - foreach ($availableBackends as $backend) { - if ($backend->hasShare($id)) { - $selectedBackend = $backend; - $this->backendCache->set($id, $backend::class); - break; - } - } - } - } - - if ($selectedBackend === null) { - throw new RuntimeException('No sharing backend selected'); - } - - return $selectedBackend; - } - // TODO: Support IShareOwnerlessMount /** @@ -598,6 +661,7 @@ private function validateShareOwnerOperation(ShareAccessContext $accessContext, * @throws ShareOperationForbiddenException */ private function validatePermission(Share $share, string $permissionTypeClass): void { + // TODO: Only fetch permisions if ((($permission = $share->permissions[$permissionTypeClass] ?? null) !== null) && $permission->enabled) { return; } @@ -611,6 +675,7 @@ private function validatePermission(Share $share, string $permissionTypeClass): private function validateReshareOperation(ShareAccessContext $accessContext, Share $share, ShareRecipient $recipient): void { $this->validatePermission($share, ReshareSharePermissionType::class); + // TODO: Only fetch recipients foreach ($share->recipients as $shareRecipient) { if ( $recipient->class === $shareRecipient->class @@ -627,20 +692,17 @@ private function validateReshareOperation(ShareAccessContext $accessContext, Sha } /** - * @param list $sources - * @param array, SharePermission> $enabledPermissions - * @param list $recipients * @throws ShareInvalidException */ - private function validateInteraction(ShareAccessContext $accessContext, ShareUser $owner, array $sources, array $enabledPermissions, array $recipients): void { - $action = new ShareAction(null, array_values(array_map(static fn (SharePermission $permission): string => $permission->class, $enabledPermissions))); + private function validateInteraction(ShareAccessContext $accessContext, Share $share): void { + $action = new ShareAction(null, array_values(array_map(static fn (SharePermission $permission): string => $permission->class, $share->getEnabledPermissions()))); $usersToCheck = []; - if ($owner->instance === null && ($ownerUser = $this->userManager->get($owner->userId)) !== null) { + if ($share->owner->instance === null && ($ownerUser = $this->userManager->get($share->owner->userId)) !== null) { $usersToCheck[] = $ownerUser; } - if ($accessContext->currentUser instanceof IUser && !$owner->isCurrentUser($accessContext)) { + if ($accessContext->currentUser instanceof IUser && !$share->owner->isCurrentUser($accessContext)) { $usersToCheck[] = $accessContext->currentUser; } @@ -648,7 +710,7 @@ private function validateInteraction(ShareAccessContext $accessContext, ShareUse $sourceTypes = $this->registry->getSourceTypes(); $receivers = []; - foreach ($recipients as $recipient) { + foreach ($share->recipients as $recipient) { if (($recipientType = $recipientTypes[$recipient->class] ?? null) === null) { throw new RuntimeException('The recipient type is not registered: ' . $recipient->class); } @@ -662,7 +724,7 @@ private function validateInteraction(ShareAccessContext $accessContext, ShareUse foreach ($usersToCheck as $userToCheck) { $resources = []; - foreach ($sources as $source) { + foreach ($share->sources as $source) { if (($sourceType = $sourceTypes[$source->class] ?? null) === null) { throw new RuntimeException('The source type is not registered: ' . $source->class); } @@ -708,21 +770,60 @@ private function assertShareCanBeActive(Share $share): void { } /** - * @param list $ids + * @param non-empty-list $sharesOrIds + * @return non-empty-list */ - private function makeSharesDraftIfNeeded(array $ids): void { - foreach ($ids as $id) { - $backend = $this->getBackend($id); - $share = $backend->getShare(new ShareAccessContext(overrideChecks: true), $id); - if ($share->state !== ShareState::Active) { - continue; + private function processShareUpdates(array $sharesOrIds): array { + $shares = []; + + foreach ($sharesOrIds as $shareOrId) { + if ($shareOrId instanceof Share) { + $share = $shareOrId; + } else { + $share = $this->backend->getShare(new ShareAccessContext(overrideChecks: true), $shareOrId); } - try { - $this->assertShareCanBeActive($share); - } catch (ShareInvalidException) { - $backend->updateShareState($id, ShareState::Draft); + if ($share->state === ShareState::Active) { + try { + $this->assertShareCanBeActive($share); + } catch (ShareInvalidException) { + $this->backend->updateShareState($share->id, ShareState::Draft); + + $share = new Share( + $share->id, + $share->owner, + $share->lastUpdated, + ShareState::Draft, + $share->sources, + $share->recipients, + $share->properties, + $share->permissions, + ); + } + } + + $legacyBackend = $this->registry->getLegacyBackend(); + if ($legacyBackend instanceof ISharingLegacyBackend) { + $compatibleSourceTypes = array_fill_keys($legacyBackend->getCompatibleSourceTypes(), true); + foreach ($share->sources as $source) { + if (!isset($compatibleSourceTypes[$source->class])) { + throw new RuntimeException('The legacy backend ' . $legacyBackend::class . ' does not support this source type: ' . $source->class); + } + } + + $compatibleRecipientTypes = array_fill_keys($legacyBackend->getCompatibleRecipientTypes(), true); + foreach ($share->recipients as $recipient) { + if (!isset($compatibleRecipientTypes[$recipient->class])) { + throw new RuntimeException('The legacy backend ' . $legacyBackend::class . ' does not support this recipient type: ' . $recipient->class); + } + } + + $legacyBackend->updateShare($share); } + + $shares[] = $share; } + + return $shares; } } diff --git a/lib/private/Sharing/SharingRegistry.php b/lib/private/Sharing/SharingRegistry.php index 92d415270f884..85647e18f2452 100644 --- a/lib/private/Sharing/SharingRegistry.php +++ b/lib/private/Sharing/SharingRegistry.php @@ -9,7 +9,6 @@ namespace OC\Sharing; -use NCU\Sharing\ISharingBackend; use NCU\Sharing\ISharingRegistry; use NCU\Sharing\Permission\ISharePermissionPreset; use NCU\Sharing\Permission\ISharePermissionType; @@ -20,10 +19,7 @@ // TODO: Maybe add validate method to run all checks before using the manager final class SharingRegistry implements ISharingRegistry { - /** - * @var array, ISharingBackend> - */ - private array $backends = []; + private ?ISharingLegacyBackend $legacyBackend = null; /** @var array, IShareSourceType> */ private array $sourceTypes = []; @@ -63,7 +59,7 @@ final class SharingRegistry implements ISharingRegistry { #[\Override] public function clear(): void { - $this->backends = []; + $this->legacyBackend = null; $this->sourceTypes = []; $this->recipientTypes = []; $this->propertyTypes = []; @@ -79,22 +75,17 @@ public function clear(): void { } #[\Override] - public function registerSharingBackend(ISharingBackend $backend): void { - $class = $backend::class; - - if (isset($this->backends[$class])) { - throw new RuntimeException('Sharing backend ' . $class . ' is already registered'); + public function registerLegacyBackend(ISharingLegacyBackend $legacyBackend): void { + if ($this->legacyBackend instanceof ISharingLegacyBackend) { + throw new RuntimeException('A sharing legacy backend is already registered'); } - $this->backends[$class] = $backend; + $this->legacyBackend = $legacyBackend; } - /** - * @return array, ISharingBackend> - */ #[\Override] - public function getSharingBackends(): array { - return $this->backends; + public function getLegacyBackend(): ?ISharingLegacyBackend { + return $this->legacyBackend; } #[\Override] diff --git a/lib/public/Share/IShare.php b/lib/public/Share/IShare.php index 1323299bc5137..5e5647f52aa10 100644 --- a/lib/public/Share/IShare.php +++ b/lib/public/Share/IShare.php @@ -9,9 +9,8 @@ namespace OCP\Share; use OCP\AppFramework\Attribute\Consumable; +use OCP\Constants; use OCP\Files\Cache\ICacheEntry; -use OCP\Files\File; -use OCP\Files\Folder; use OCP\Files\Node; use OCP\Files\NotFoundException; use OCP\Share\Exceptions\IllegalIDChangeException; @@ -209,7 +208,7 @@ public function getNodeType(); /** * Set the shareType * - * @param int $shareType + * @param self::TYPE_* $shareType * @return \OCP\Share\IShare The modified object * @since 9.0.0 */ @@ -218,7 +217,7 @@ public function setShareType($shareType); /** * Get the shareType * - * @return int + * @return self::TYPE_* * @since 9.0.0 */ public function getShareType(); @@ -276,9 +275,8 @@ public function getSharedWithAvatar(); /** * Set the permissions. - * See \OCP\Constants::PERMISSION_* * - * @param int $permissions + * @param int-mask-of $permissions * @return IShare The modified object * @since 9.0.0 */ @@ -286,9 +284,8 @@ public function setPermissions($permissions); /** * Get the share permissions - * See \OCP\Constants::PERMISSION_* * - * @return int + * @return int-mask-of * @since 9.0.0 */ public function getPermissions(); @@ -320,9 +317,8 @@ public function getAttributes(): ?IAttributes; /** * Set the accepted status - * See self::STATUS_* * - * @param int $status + * @param self::STATUS_* $status * @return IShare The modified object * @since 18.0.0 */ @@ -330,12 +326,11 @@ public function setStatus(int $status): IShare; /** * Get the accepted status - * See self::STATUS_* * - * @return int + * @return ?self::STATUS_* * @since 18.0.0 */ - public function getStatus(): int; + public function getStatus(): ?int; /** * Attach a note to a share diff --git a/lib/unstable/Sharing/ISharingBackend.php b/lib/unstable/Sharing/ISharingBackend.php index 2ae085b8e6233..a4cb1401dd9cc 100644 --- a/lib/unstable/Sharing/ISharingBackend.php +++ b/lib/unstable/Sharing/ISharingBackend.php @@ -17,27 +17,27 @@ use NCU\Sharing\Recipient\ShareRecipient; use NCU\Sharing\Source\IShareSourceType; use NCU\Sharing\Source\ShareSource; -use OCP\AppFramework\Attribute\Implementable; +use OCP\AppFramework\Attribute\Consumable; -// TODO: Add ability to start and commit a transaction /** * @experimental 35.0.0 */ -#[Implementable(since: '35.0.0')] +#[Consumable(since: '35.0.0')] interface ISharingBackend { /** * Create a new share. * * @experimental 35.0.0 */ - public function createShare(ShareUser $owner): string; + public function createShare(string $id, ShareUser $owner, int $lastUpdated): void; /** * Perform all updates when the owner was deleted. * + * @return list * @experimental 35.0.0 */ - public function onOwnerDeleted(ShareUser $owner): void; + public function onOwnerDeleted(ShareUser $owner): array; /** * Update the state of a share. @@ -113,6 +113,14 @@ public function onInitiatorDeleted(ShareUser $initiator): array; */ public function updateShareRecipientSecret(string $id, ShareRecipient $recipient, string $secret): void; + /** + * Insert a property for a share. + * + * @throws ShareNotFoundException + * @experimental 35.0.0 + */ + public function createShareProperty(string $id, ShareProperty $property): void; + /** * Update a property of a share. * @@ -121,6 +129,14 @@ public function updateShareRecipientSecret(string $id, ShareRecipient $recipient */ public function updateShareProperty(string $id, ShareProperty $property): void; + /** + * Insert a permission for a share. + * + * @throws ShareNotFoundException + * @experimental 35.0.0 + */ + public function createSharePermission(string $id, SharePermission $permission): void; + /** * Update a permission of a share. * diff --git a/lib/unstable/Sharing/ISharingManager.php b/lib/unstable/Sharing/ISharingManager.php index ebbe7b6059c4f..21128680aaefc 100644 --- a/lib/unstable/Sharing/ISharingManager.php +++ b/lib/unstable/Sharing/ISharingManager.php @@ -13,7 +13,9 @@ use NCU\Sharing\Exception\ShareNotFoundException; use NCU\Sharing\Exception\ShareOperationForbiddenException; use NCU\Sharing\Permission\ISharePermissionPreset; +use NCU\Sharing\Permission\ISharePermissionType; use NCU\Sharing\Permission\SharePermission; +use NCU\Sharing\Property\ISharePropertyType; use NCU\Sharing\Property\ShareProperty; use NCU\Sharing\Recipient\IShareRecipientType; use NCU\Sharing\Recipient\ShareRecipient; @@ -148,6 +150,13 @@ public function onInitiatorDeleted(ShareAccessContext $accessContext, ShareUser */ public function updateShareRecipientSecret(ShareAccessContext $accessContext, string $id, ShareRecipient $recipient, string $secret): void; + /** + * @param class-string $propertyTypeClass + * @throws ShareNotFoundException + * @experimental 35.0.0 + */ + public function createSharePropertyDefaultValue(Share $share, string $propertyTypeClass): Share; + /** * Update a property of a share. * @@ -158,6 +167,13 @@ public function updateShareRecipientSecret(ShareAccessContext $accessContext, st */ public function updateShareProperty(ShareAccessContext $accessContext, string $id, ShareProperty $property): void; + /** + * @param class-string $permissionTypeClass + * @throws ShareNotFoundException + * @experimental 35.0.0 + */ + public function createSharePermissionDefaultValue(Share $share, string $permissionTypeClass): Share; + /** * Update a permission of a share. * diff --git a/lib/unstable/Sharing/ISharingRegistry.php b/lib/unstable/Sharing/ISharingRegistry.php index 81fda046e17e2..a1a352da84582 100644 --- a/lib/unstable/Sharing/ISharingRegistry.php +++ b/lib/unstable/Sharing/ISharingRegistry.php @@ -14,6 +14,7 @@ use NCU\Sharing\Property\ISharePropertyType; use NCU\Sharing\Recipient\IShareRecipientType; use NCU\Sharing\Source\IShareSourceType; +use OC\Sharing\ISharingLegacyBackend; use OCP\AppFramework\Attribute\Consumable; /** @@ -29,13 +30,12 @@ public function clear(): void; /** * @experimental 35.0.0 */ - public function registerSharingBackend(ISharingBackend $backend): void; + public function registerLegacyBackend(ISharingLegacyBackend $legacyBackend): void; /** - * @return array, ISharingBackend> * @experimental 35.0.0 */ - public function getSharingBackends(): array; + public function getLegacyBackend(): ?ISharingLegacyBackend; /** * @experimental 35.0.0 diff --git a/tests/Core/Sharing/Recipient/GroupShareRecipientTypeTest.php b/tests/Core/Sharing/Recipient/GroupShareRecipientTypeTest.php index 954e5835359a6..abfd54bc2a847 100644 --- a/tests/Core/Sharing/Recipient/GroupShareRecipientTypeTest.php +++ b/tests/Core/Sharing/Recipient/GroupShareRecipientTypeTest.php @@ -15,7 +15,6 @@ use NCU\Sharing\ShareAccessContext; use OC\Core\Sharing\Recipient\GroupShareRecipientType; use OC\Group\Database; -use OCA\Sharing\SharingBackend; use OCP\EventDispatcher\IEventDispatcher; use OCP\IDBConnection; use OCP\IGroup; @@ -132,7 +131,6 @@ public function testSearchRecipients(): void { public function testDelete(): void { $registry = Server::get(ISharingRegistry::class); $registry->clear(); - $registry->registerSharingBackend(Server::get(SharingBackend::class)); $registry->registerRecipientType($this->recipientType); $accessContext = new ShareAccessContext(currentUser: $this->user1); diff --git a/tests/Core/Sharing/Recipient/UserShareRecipientTypeTest.php b/tests/Core/Sharing/Recipient/UserShareRecipientTypeTest.php index 4cc6f232ea7c9..453c0ecd12ac4 100644 --- a/tests/Core/Sharing/Recipient/UserShareRecipientTypeTest.php +++ b/tests/Core/Sharing/Recipient/UserShareRecipientTypeTest.php @@ -15,7 +15,6 @@ use NCU\Sharing\ShareAccessContext; use OC\Core\Sharing\Recipient\UserShareRecipientType; use OC\User\Database; -use OCA\Sharing\SharingBackend; use OCP\EventDispatcher\IEventDispatcher; use OCP\IDBConnection; use OCP\IUser; @@ -136,7 +135,6 @@ public function testSearchRecipients(): void { public function testDelete(): void { $registry = Server::get(ISharingRegistry::class); $registry->clear(); - $registry->registerSharingBackend(Server::get(SharingBackend::class)); $registry->registerRecipientType($this->recipientType); $accessContext = new ShareAccessContext(currentUser: $this->user1); diff --git a/tests/lib/Sharing/AbstractSharingManagerTests.php b/tests/lib/Sharing/AbstractSharingManagerTests.php index dddd33f6ed008..8f4522dc86ff2 100644 --- a/tests/lib/Sharing/AbstractSharingManagerTests.php +++ b/tests/lib/Sharing/AbstractSharingManagerTests.php @@ -19,7 +19,6 @@ use NCU\Sharing\ShareState; use NCU\Sharing\Source\ShareSource; use OC\Core\Sharing\Permission\ReshareSharePermissionType; -use OCA\Sharing\SharingBackend; use OCP\EventDispatcher\IEventDispatcher; use OCP\HintException; use OCP\IDBConnection; @@ -113,7 +112,6 @@ public function setUp(): void { $this->registry = Server::get(ISharingRegistry::class); $this->registry->clear(); - $this->registry->registerSharingBackend(Server::get(SharingBackend::class)); $this->registry->registerSourceType(new TestShareSourceType1(['source1' => 'Source 1'])); $this->registry->registerSourceType(new TestShareSourceType2(['source2' => 'Source 2'])); $this->registry->registerRecipientType(new TestShareRecipientType1( @@ -1718,7 +1716,6 @@ public function testUpdateSharePermissionInteractionRestricted(): void { public function testSelectSharePermissionPreset(): void { $this->registry->clear(); - $this->registry->registerSharingBackend(Server::get(SharingBackend::class)); $this->registry->registerPermissionPreset(new TestSharePermissionPreset1()); $this->registry->registerPermissionPreset(new TestSharePermissionPreset2()); $this->registry->registerPermissionType(null, new TestSharePermissionType1()); @@ -1918,7 +1915,6 @@ public function testSelectSharePermissionPreset(): void { public function testSelectSharePermissionPresetCompatible(): void { $this->registry->clear(); - $this->registry->registerSharingBackend(Server::get(SharingBackend::class)); $this->registry->registerSourceType(new TestShareSourceType1(['source1' => 'Source 1'])); $this->registry->registerPermissionPreset(new TestSharePermissionPreset1()); $this->registry->registerPermissionPreset(new TestSharePermissionPreset2()); @@ -2990,7 +2986,6 @@ public static function dataGetShareWithPublicSecret(): array { #[DataProvider('dataGetShareWithPublicSecret')] public function testGetShareWithPublicSecret(bool $isSecretPublic): void { $this->registry->clear(); - $this->registry->registerSharingBackend(Server::get(SharingBackend::class)); $this->registry->registerRecipientType(new TestShareRecipientType1( [ 'recipient1' => 'Recipient 1', @@ -3067,7 +3062,6 @@ public function testGetShareWithPublicSecret(bool $isSecretPublic): void { public function testGetShareWithSecret(): void { $this->registry->clear(); - $this->registry->registerSharingBackend(Server::get(SharingBackend::class)); $this->registry->registerSourceType(new TestShareSourceType1(['source1' => 'Source'])); $this->registry->registerRecipientType(new TestShareRecipientTypePublicSecret( [ @@ -3146,7 +3140,6 @@ public function testGetShareWithSecret(): void { public function testGetShareUniqueDisplayNames(): void { $this->registry->clear(); - $this->registry->registerSharingBackend(Server::get(SharingBackend::class)); $this->registry->registerSourceType(new TestShareSourceType1(['source1' => 'Source'])); $this->registry->registerSourceType(new TestShareSourceType2(['source2' => 'Source', 'source3' => 'Other'])); $this->registry->registerRecipientType(new TestShareRecipientType1(['recipient1' => 'Recipient'], [], []));