Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions apps/files_sharing/composer/composer/autoload_classmap.php
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@
'OCA\\Files_Sharing\\Listener\\ShareInteractionListener' => $baseDir . '/../lib/Listener/ShareInteractionListener.php',
'OCA\\Files_Sharing\\Listener\\SharesUpdatedListener' => $baseDir . '/../lib/Listener/SharesUpdatedListener.php',
'OCA\\Files_Sharing\\Listener\\UserAddedToGroupListener' => $baseDir . '/../lib/Listener/UserAddedToGroupListener.php',
'OCA\\Files_Sharing\\Listener\\UserHomeSetupListener' => $baseDir . '/../lib/Listener/UserHomeSetupListener.php',
'OCA\\Files_Sharing\\Listener\\UserShareAcceptanceListener' => $baseDir . '/../lib/Listener/UserShareAcceptanceListener.php',
'OCA\\Files_Sharing\\Middleware\\OCSShareAPIMiddleware' => $baseDir . '/../lib/Middleware/OCSShareAPIMiddleware.php',
'OCA\\Files_Sharing\\Middleware\\ShareInfoMiddleware' => $baseDir . '/../lib/Middleware/ShareInfoMiddleware.php',
Expand All @@ -98,6 +99,7 @@
'OCA\\Files_Sharing\\Settings\\Personal' => $baseDir . '/../lib/Settings/Personal.php',
'OCA\\Files_Sharing\\ShareBackend\\File' => $baseDir . '/../lib/ShareBackend/File.php',
'OCA\\Files_Sharing\\ShareBackend\\Folder' => $baseDir . '/../lib/ShareBackend/Folder.php',
'OCA\\Files_Sharing\\ShareRecipientUpdater' => $baseDir . '/../lib/ShareRecipientUpdater.php',
'OCA\\Files_Sharing\\ShareTargetValidator' => $baseDir . '/../lib/ShareTargetValidator.php',
'OCA\\Files_Sharing\\SharedMount' => $baseDir . '/../lib/SharedMount.php',
'OCA\\Files_Sharing\\SharedStorage' => $baseDir . '/../lib/SharedStorage.php',
Expand Down
2 changes: 2 additions & 0 deletions apps/files_sharing/composer/composer/autoload_static.php
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ class ComposerStaticInitFiles_Sharing
'OCA\\Files_Sharing\\Listener\\ShareInteractionListener' => __DIR__ . '/..' . '/../lib/Listener/ShareInteractionListener.php',
'OCA\\Files_Sharing\\Listener\\SharesUpdatedListener' => __DIR__ . '/..' . '/../lib/Listener/SharesUpdatedListener.php',
'OCA\\Files_Sharing\\Listener\\UserAddedToGroupListener' => __DIR__ . '/..' . '/../lib/Listener/UserAddedToGroupListener.php',
'OCA\\Files_Sharing\\Listener\\UserHomeSetupListener' => __DIR__ . '/..' . '/../lib/Listener/UserHomeSetupListener.php',
'OCA\\Files_Sharing\\Listener\\UserShareAcceptanceListener' => __DIR__ . '/..' . '/../lib/Listener/UserShareAcceptanceListener.php',
'OCA\\Files_Sharing\\Middleware\\OCSShareAPIMiddleware' => __DIR__ . '/..' . '/../lib/Middleware/OCSShareAPIMiddleware.php',
'OCA\\Files_Sharing\\Middleware\\ShareInfoMiddleware' => __DIR__ . '/..' . '/../lib/Middleware/ShareInfoMiddleware.php',
Expand All @@ -113,6 +114,7 @@ class ComposerStaticInitFiles_Sharing
'OCA\\Files_Sharing\\Settings\\Personal' => __DIR__ . '/..' . '/../lib/Settings/Personal.php',
'OCA\\Files_Sharing\\ShareBackend\\File' => __DIR__ . '/..' . '/../lib/ShareBackend/File.php',
'OCA\\Files_Sharing\\ShareBackend\\Folder' => __DIR__ . '/..' . '/../lib/ShareBackend/Folder.php',
'OCA\\Files_Sharing\\ShareRecipientUpdater' => __DIR__ . '/..' . '/../lib/ShareRecipientUpdater.php',
'OCA\\Files_Sharing\\ShareTargetValidator' => __DIR__ . '/..' . '/../lib/ShareTargetValidator.php',
'OCA\\Files_Sharing\\SharedMount' => __DIR__ . '/..' . '/../lib/SharedMount.php',
'OCA\\Files_Sharing\\SharedStorage' => __DIR__ . '/..' . '/../lib/SharedStorage.php',
Expand Down
3 changes: 3 additions & 0 deletions apps/files_sharing/lib/AppInfo/Application.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
use OCA\Files_Sharing\Listener\ShareInteractionListener;
use OCA\Files_Sharing\Listener\SharesUpdatedListener;
use OCA\Files_Sharing\Listener\UserAddedToGroupListener;
use OCA\Files_Sharing\Listener\UserHomeSetupListener;
use OCA\Files_Sharing\Listener\UserShareAcceptanceListener;
use OCA\Files_Sharing\Middleware\OCSShareAPIMiddleware;
use OCA\Files_Sharing\Middleware\ShareInfoMiddleware;
Expand All @@ -48,6 +49,7 @@
use OCP\Files\Events\BeforeDirectFileDownloadEvent;
use OCP\Files\Events\BeforeZipCreatedEvent;
use OCP\Files\Events\Node\BeforeNodeReadEvent;
use OCP\Files\Events\UserHomeSetupEvent;
use OCP\Group\Events\GroupChangedEvent;
use OCP\Group\Events\GroupDeletedEvent;
use OCP\Group\Events\UserAddedEvent;
Expand Down Expand Up @@ -121,6 +123,7 @@ function () use ($c) {
$context->registerEventListener(UserAddedEvent::class, SharesUpdatedListener::class);
$context->registerEventListener(UserRemovedEvent::class, SharesUpdatedListener::class);
$context->registerEventListener(UserShareAccessUpdatedEvent::class, SharesUpdatedListener::class);
$context->registerEventListener(UserHomeSetupEvent::class, UserHomeSetupListener::class);

$context->registerConfigLexicon(ConfigLexicon::class);
}
Expand Down
10 changes: 9 additions & 1 deletion apps/files_sharing/lib/Config/ConfigLexicon.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ class ConfigLexicon implements ILexicon {
public const SHOW_FEDERATED_AS_INTERNAL = 'show_federated_shares_as_internal';
public const SHOW_FEDERATED_TO_TRUSTED_AS_INTERNAL = 'show_federated_shares_to_trusted_servers_as_internal';
public const EXCLUDE_RESHARE_FROM_EDIT = 'shareapi_exclude_reshare_from_edit';
public const UPDATE_SINGLE_CUTOFF = 'update_single_cutoff';
public const UPDATE_ALL_CUTOFF = 'update_all_cutoff';
public const USER_NEEDS_SHARE_REFRESH = 'user_needs_share_refresh';

public function getStrictness(): Strictness {
return Strictness::IGNORE;
Expand All @@ -34,10 +37,15 @@ public function getAppConfigs(): array {
new Entry(self::SHOW_FEDERATED_AS_INTERNAL, ValueType::BOOL, false, 'shows federated shares as internal shares', true),
new Entry(self::SHOW_FEDERATED_TO_TRUSTED_AS_INTERNAL, ValueType::BOOL, false, 'shows federated shares to trusted servers as internal shares', true),
new Entry(self::EXCLUDE_RESHARE_FROM_EDIT, ValueType::BOOL, false, 'Exclude reshare permission from "Allow editing" bundled permissions'),

new Entry(self::UPDATE_SINGLE_CUTOFF, ValueType::INT, 10, 'For how many users do we update the share date immediately for single-share updates'),
new Entry(self::UPDATE_ALL_CUTOFF, ValueType::INT, 3, 'For how many users do we update the share date immediately for all-share updates'),
];
}

public function getUserConfigs(): array {
return [];
return [
new Entry(self::USER_NEEDS_SHARE_REFRESH, ValueType::BOOL, false, 'whether a user needs to have the receiving share data refreshed for possible changes'),
];
}
}
86 changes: 48 additions & 38 deletions apps/files_sharing/lib/Listener/SharesUpdatedListener.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,16 @@

namespace OCA\Files_Sharing\Listener;

use OCA\Files_Sharing\AppInfo\Application;
use OCA\Files_Sharing\Config\ConfigLexicon;
use OCA\Files_Sharing\Event\UserShareAccessUpdatedEvent;
use OCA\Files_Sharing\MountProvider;
use OCA\Files_Sharing\ShareTargetValidator;
use OCA\Files_Sharing\ShareRecipientUpdater;
use OCP\Config\IUserConfig;
use OCP\EventDispatcher\Event;
use OCP\EventDispatcher\IEventListener;
use OCP\Files\Config\ICachedMountInfo;
use OCP\Files\Config\IUserMountCache;
use OCP\Group\Events\UserAddedEvent;
use OCP\Group\Events\UserRemovedEvent;
use OCP\IAppConfig;
use OCP\IUser;
use OCP\Share\Events\BeforeShareDeletedEvent;
use OCP\Share\Events\ShareCreatedEvent;
Expand All @@ -29,57 +30,66 @@
* @template-implements IEventListener<UserAddedEvent|UserRemovedEvent|ShareCreatedEvent|ShareTransferredEvent|BeforeShareDeletedEvent|UserShareAccessUpdatedEvent>
*/
class SharesUpdatedListener implements IEventListener {
private array $inUpdate = [];
/**
* for how many users do we update the share date immediately,
* before just marking the other users when we know the relevant share
*/
private int $cutOffMarkAllSingleShare;
/**
* for how many users do we update the share date immediately,
* before just marking the other users when the relevant shares are unknown
*/
private int $cutOffMarkAllShares ;

private int $updatedCount = 0;

public function __construct(
private readonly IManager $shareManager,
private readonly IUserMountCache $userMountCache,
private readonly MountProvider $shareMountProvider,
private readonly ShareTargetValidator $shareTargetValidator,
private readonly ShareRecipientUpdater $shareUpdater,
private readonly IUserConfig $userConfig,
IAppConfig $appConfig,
) {
$this->cutOffMarkAllSingleShare = $appConfig->getValueInt(Application::APP_ID, ConfigLexicon::UPDATE_SINGLE_CUTOFF, 10);
$this->cutOffMarkAllShares = $appConfig->getValueInt(Application::APP_ID, ConfigLexicon::UPDATE_ALL_CUTOFF, 3);
}

public function handle(Event $event): void {
if ($event instanceof UserShareAccessUpdatedEvent) {
foreach ($event->getUsers() as $user) {
$this->updateForUser($user);
$this->updateOrMarkUser($user);
}
}
if ($event instanceof UserAddedEvent || $event instanceof UserRemovedEvent) {
$this->updateForUser($event->getUser());
$this->updateOrMarkUser($event->getUser());
}
if (
$event instanceof ShareCreatedEvent
|| $event instanceof BeforeShareDeletedEvent
|| $event instanceof ShareTransferredEvent
) {
foreach ($this->shareManager->getUsersForShare($event->getShare()) as $user) {
$this->updateForUser($user);
if ($event instanceof ShareCreatedEvent || $event instanceof ShareTransferredEvent) {
$share = $event->getShare();
$shareTarget = $share->getTarget();
foreach ($this->shareManager->getUsersForShare($share) as $user) {
if ($share->getSharedBy() !== $user->getUID()) {
$this->updatedCount++;
if ($this->updatedCount <= $this->cutOffMarkAllSingleShare) {
$this->shareUpdater->updateForShare($user, $share);
// Share target validation might have changed the target, restore it for the next user
$share->setTarget($shareTarget);
} else {
$this->markUserForRefresh($user);
}
}
}
}
}

private function updateForUser(IUser $user): void {
// prevent recursion
if (isset($this->inUpdate[$user->getUID()])) {
return;
}
$this->inUpdate[$user->getUID()] = true;

$cachedMounts = $this->userMountCache->getMountsForUser($user);
$mountPoints = array_map(fn (ICachedMountInfo $mount) => $mount->getMountPoint(), $cachedMounts);
$mountsByPath = array_combine($mountPoints, $cachedMounts);

$shares = $this->shareMountProvider->getSuperSharesForUser($user);

foreach ($shares as &$share) {
[$parentShare, $groupedShares] = $share;
$mountPoint = '/' . $user->getUID() . '/files/' . trim($parentShare->getTarget(), '/') . '/';
$mountKey = $parentShare->getNodeId() . '::' . $mountPoint;
if (!isset($cachedMounts[$mountKey])) {
$this->shareTargetValidator->verifyMountPoint($user, $parentShare, $mountsByPath, $groupedShares);
}
private function updateOrMarkUser(IUser $user): void {
$this->updatedCount++;
if ($this->updatedCount <= $this->cutOffMarkAllShares) {
$this->shareUpdater->updateForUser($user);
} else {
$this->markUserForRefresh($user);
}
}

unset($this->inUpdate[$user->getUID()]);
private function markUserForRefresh(IUser $user): void {
$this->userConfig->setValueBool($user->getUID(), Application::APP_ID, ConfigLexicon::USER_NEEDS_SHARE_REFRESH, true);
}
}
44 changes: 44 additions & 0 deletions apps/files_sharing/lib/Listener/UserHomeSetupListener.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<?php

declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

namespace OCA\Files_Sharing\Listener;

use OCA\Files_Sharing\AppInfo\Application;
use OCA\Files_Sharing\Config\ConfigLexicon;
use OCA\Files_Sharing\ShareRecipientUpdater;
use OCP\Config\IUserConfig;
use OCP\EventDispatcher\Event;
use OCP\EventDispatcher\IEventListener;
use OCP\Files\Events\UserHomeSetupEvent;

/**
* Listen to the users filesystem setup being started, to perform any receiving share
* work that was postponed.
*
* @template-implements IEventListener<UserHomeSetupEvent>
*/
class UserHomeSetupListener implements IEventListener {
public function __construct(
private readonly ShareRecipientUpdater $updater,
private readonly IUserConfig $userConfig,
) {
}

public function handle(Event $event): void {
if (!$event instanceof UserHomeSetupEvent) {
return;
}

$user = $event->getUser();
if ($this->userConfig->getValueBool($user->getUID(), Application::APP_ID, ConfigLexicon::USER_NEEDS_SHARE_REFRESH)) {
$this->updater->updateForUser($user);
$this->userConfig->setValueBool($user->getUID(), Application::APP_ID, ConfigLexicon::USER_NEEDS_SHARE_REFRESH, false);
}
}

}
64 changes: 64 additions & 0 deletions apps/files_sharing/lib/ShareRecipientUpdater.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
<?php

declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

namespace OCA\Files_Sharing;

use OCP\Files\Config\ICachedMountInfo;
use OCP\Files\Config\IUserMountCache;
use OCP\IUser;
use OCP\Share\IShare;

class ShareRecipientUpdater {
private array $inUpdate = [];

public function __construct(
private readonly IUserMountCache $userMountCache,
private readonly MountProvider $shareMountProvider,
private readonly ShareTargetValidator $shareTargetValidator,
) {
}

/**
* Validate all received shares for a user
*/
public function updateForUser(IUser $user): void {
// prevent recursion
if (isset($this->inUpdate[$user->getUID()])) {
return;
}
$this->inUpdate[$user->getUID()] = true;

$cachedMounts = $this->userMountCache->getMountsForUser($user);
$mountPoints = array_map(fn (ICachedMountInfo $mount) => $mount->getMountPoint(), $cachedMounts);
$mountsByPath = array_combine($mountPoints, $cachedMounts);

$shares = $this->shareMountProvider->getSuperSharesForUser($user);

foreach ($shares as &$share) {
[$parentShare, $groupedShares] = $share;
$mountPoint = '/' . $user->getUID() . '/files/' . trim($parentShare->getTarget(), '/') . '/';
$mountKey = $parentShare->getNodeId() . '::' . $mountPoint;
if (!isset($cachedMounts[$mountKey])) {
$this->shareTargetValidator->verifyMountPoint($user, $parentShare, $mountsByPath, $groupedShares);
}
}

unset($this->inUpdate[$user->getUID()]);
}

/**
* Validate a single received share for a user
*/
public function updateForShare(IUser $user, IShare $share): void {
$cachedMounts = $this->userMountCache->getMountsForUser($user);
$mountPoints = array_map(fn (ICachedMountInfo $mount) => $mount->getMountPoint(), $cachedMounts);
$mountsByPath = array_combine($mountPoints, $cachedMounts);

$this->shareTargetValidator->verifyMountPoint($user, $share, $mountsByPath, [$share]);
}
}
2 changes: 2 additions & 0 deletions build/integration/features/bootstrap/SharingContext.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ protected function resetAppConfigs() {
$this->deleteServerConfig('core', 'shareapi_allow_federation_on_public_shares');
$this->deleteServerConfig('files_sharing', 'outgoing_server2server_share_enabled');
$this->deleteServerConfig('core', 'shareapi_allow_view_without_download');
$this->deleteServerConfig('files_sharing', 'update_single_cutoff');
$this->deleteServerConfig('files_sharing', 'update_all_cutoff');

$this->runOcc(['config:system:delete', 'share_folder']);
}
Expand Down
44 changes: 44 additions & 0 deletions build/integration/sharing_features/sharing-v1-part2.feature
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,50 @@ Feature: sharing
| share_with | user2 |
| share_with_displayname | user2 |

Scenario: getting all shares of a file with a received share after revoking the resharing rights with delayed share check
Given user "user0" exists
And parameter "update_single_cutoff" of app "files_sharing" is set to "0"
And parameter "update_all_cutoff" of app "files_sharing" is set to "0"
And user "user1" exists
And user "user2" exists
And file "textfile0.txt" of user "user1" is shared with user "user0"
And user "user0" accepts last share
And Updating last share with
| permissions | 1 |
And file "textfile0.txt" of user "user1" is shared with user "user2"
When As an "user0"
And sending "GET" to "/apps/files_sharing/api/v1/shares?reshares=true&path=/textfile0 (2).txt"
Then the list of returned shares has 1 shares
And share 0 is returned with
| share_type | 0 |
| uid_owner | user1 |
| displayname_owner | user1 |
| path | /textfile0 (2).txt |
| item_type | file |
| mimetype | text/plain |
| storage_id | shared::/textfile0 (2).txt |
| file_target | /textfile0.txt |
| share_with | user2 |
| share_with_displayname | user2 |
# After user2 does an FS setup the share is renamed
When As an "user2"
And Downloading file "/textfile0 (2).txt" with range "bytes=10-18"
Then Downloaded content should be "test text"
When As an "user0"
And sending "GET" to "/apps/files_sharing/api/v1/shares?reshares=true&path=/textfile0 (2).txt"
Then the list of returned shares has 1 shares
And share 0 is returned with
| share_type | 0 |
| uid_owner | user1 |
| displayname_owner | user1 |
| path | /textfile0 (2).txt |
| item_type | file |
| mimetype | text/plain |
| storage_id | shared::/textfile0 (2).txt |
| file_target | /textfile0 (2).txt |
| share_with | user2 |
| share_with_displayname | user2 |

Scenario: getting all shares of a file with a received share also reshared after revoking the resharing rights
Given user "user0" exists
And user "user1" exists
Expand Down
1 change: 1 addition & 0 deletions lib/composer/composer/autoload_classmap.php
Original file line number Diff line number Diff line change
Expand Up @@ -470,6 +470,7 @@
'OCP\\Files\\Events\\Node\\NodeRenamedEvent' => $baseDir . '/lib/public/Files/Events/Node/NodeRenamedEvent.php',
'OCP\\Files\\Events\\Node\\NodeTouchedEvent' => $baseDir . '/lib/public/Files/Events/Node/NodeTouchedEvent.php',
'OCP\\Files\\Events\\Node\\NodeWrittenEvent' => $baseDir . '/lib/public/Files/Events/Node/NodeWrittenEvent.php',
'OCP\\Files\\Events\\UserHomeSetupEvent' => $baseDir . '/lib/public/Files/Events/UserHomeSetupEvent.php',
'OCP\\Files\\File' => $baseDir . '/lib/public/Files/File.php',
'OCP\\Files\\FileInfo' => $baseDir . '/lib/public/Files/FileInfo.php',
'OCP\\Files\\FileNameTooLongException' => $baseDir . '/lib/public/Files/FileNameTooLongException.php',
Expand Down
1 change: 1 addition & 0 deletions lib/composer/composer/autoload_static.php
Original file line number Diff line number Diff line change
Expand Up @@ -511,6 +511,7 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2
'OCP\\Files\\Events\\Node\\NodeRenamedEvent' => __DIR__ . '/../../..' . '/lib/public/Files/Events/Node/NodeRenamedEvent.php',
'OCP\\Files\\Events\\Node\\NodeTouchedEvent' => __DIR__ . '/../../..' . '/lib/public/Files/Events/Node/NodeTouchedEvent.php',
'OCP\\Files\\Events\\Node\\NodeWrittenEvent' => __DIR__ . '/../../..' . '/lib/public/Files/Events/Node/NodeWrittenEvent.php',
'OCP\\Files\\Events\\UserHomeSetupEvent' => __DIR__ . '/../../..' . '/lib/public/Files/Events/UserHomeSetupEvent.php',
'OCP\\Files\\File' => __DIR__ . '/../../..' . '/lib/public/Files/File.php',
'OCP\\Files\\FileInfo' => __DIR__ . '/../../..' . '/lib/public/Files/FileInfo.php',
'OCP\\Files\\FileNameTooLongException' => __DIR__ . '/../../..' . '/lib/public/Files/FileNameTooLongException.php',
Expand Down
Loading
Loading