diff --git a/apps/files/lib/Service/OwnershipTransferService.php b/apps/files/lib/Service/OwnershipTransferService.php
index a624df4a92458..57b50e73d22b0 100644
--- a/apps/files/lib/Service/OwnershipTransferService.php
+++ b/apps/files/lib/Service/OwnershipTransferService.php
@@ -442,6 +442,9 @@ private function restoreShares(
}
} catch (\OCP\Files\NotFoundException $e) {
$output->writeln('Share with id ' . $share->getId() . ' points at deleted file, skipping');
+ } catch (\OCP\Share\Exceptions\GenericShareException $e) {
+ $output->writeln('Share with id ' . $share->getId() . ' is broken, deleting');
+ $this->shareManager->deleteShare($share);
} catch (\Throwable $e) {
$output->writeln('Could not restore share with id ' . $share->getId() . ':' . $e->getMessage() . ' : ' . $e->getTraceAsString() . '');
}
diff --git a/apps/files_sharing/appinfo/info.xml b/apps/files_sharing/appinfo/info.xml
index 68b0e0eb81e0e..210757f9bee37 100644
--- a/apps/files_sharing/appinfo/info.xml
+++ b/apps/files_sharing/appinfo/info.xml
@@ -48,6 +48,7 @@ Turning the feature off removes shared files and folders on the server for all s
OCA\Files_Sharing\Command\CleanupRemoteStorages
OCA\Files_Sharing\Command\ExiprationNotification
OCA\Files_Sharing\Command\DeleteOrphanShares
+ OCA\Files_Sharing\Command\FixBrokenShares
diff --git a/apps/files_sharing/composer/composer/ClassLoader.php b/apps/files_sharing/composer/composer/ClassLoader.php
index 7824d8f7eafe8..a72151c77c8eb 100644
--- a/apps/files_sharing/composer/composer/ClassLoader.php
+++ b/apps/files_sharing/composer/composer/ClassLoader.php
@@ -45,34 +45,35 @@ class ClassLoader
/** @var \Closure(string):void */
private static $includeFile;
- /** @var string|null */
+ /** @var ?string */
private $vendorDir;
// PSR-4
/**
- * @var array>
+ * @var array[]
+ * @psalm-var array>
*/
private $prefixLengthsPsr4 = array();
/**
- * @var array>
+ * @var array[]
+ * @psalm-var array>
*/
private $prefixDirsPsr4 = array();
/**
- * @var list
+ * @var array[]
+ * @psalm-var array
*/
private $fallbackDirsPsr4 = array();
// PSR-0
/**
- * List of PSR-0 prefixes
- *
- * Structured as array('F (first letter)' => array('Foo\Bar (full prefix)' => array('path', 'path2')))
- *
- * @var array>>
+ * @var array[]
+ * @psalm-var array>
*/
private $prefixesPsr0 = array();
/**
- * @var list
+ * @var array[]
+ * @psalm-var array
*/
private $fallbackDirsPsr0 = array();
@@ -80,7 +81,8 @@ class ClassLoader
private $useIncludePath = false;
/**
- * @var array
+ * @var string[]
+ * @psalm-var array
*/
private $classMap = array();
@@ -88,20 +90,21 @@ class ClassLoader
private $classMapAuthoritative = false;
/**
- * @var array
+ * @var bool[]
+ * @psalm-var array
*/
private $missingClasses = array();
- /** @var string|null */
+ /** @var ?string */
private $apcuPrefix;
/**
- * @var array
+ * @var self[]
*/
private static $registeredLoaders = array();
/**
- * @param string|null $vendorDir
+ * @param ?string $vendorDir
*/
public function __construct($vendorDir = null)
{
@@ -110,7 +113,7 @@ public function __construct($vendorDir = null)
}
/**
- * @return array>
+ * @return string[]
*/
public function getPrefixes()
{
@@ -122,7 +125,8 @@ public function getPrefixes()
}
/**
- * @return array>
+ * @return array[]
+ * @psalm-return array>
*/
public function getPrefixesPsr4()
{
@@ -130,7 +134,8 @@ public function getPrefixesPsr4()
}
/**
- * @return list
+ * @return array[]
+ * @psalm-return array
*/
public function getFallbackDirs()
{
@@ -138,7 +143,8 @@ public function getFallbackDirs()
}
/**
- * @return list
+ * @return array[]
+ * @psalm-return array
*/
public function getFallbackDirsPsr4()
{
@@ -146,7 +152,8 @@ public function getFallbackDirsPsr4()
}
/**
- * @return array Array of classname => path
+ * @return string[] Array of classname => path
+ * @psalm-return array
*/
public function getClassMap()
{
@@ -154,7 +161,8 @@ public function getClassMap()
}
/**
- * @param array $classMap Class to filename map
+ * @param string[] $classMap Class to filename map
+ * @psalm-param array $classMap
*
* @return void
*/
@@ -171,25 +179,24 @@ public function addClassMap(array $classMap)
* Registers a set of PSR-0 directories for a given prefix, either
* appending or prepending to the ones previously set for this prefix.
*
- * @param string $prefix The prefix
- * @param list|string $paths The PSR-0 root directories
- * @param bool $prepend Whether to prepend the directories
+ * @param string $prefix The prefix
+ * @param string[]|string $paths The PSR-0 root directories
+ * @param bool $prepend Whether to prepend the directories
*
* @return void
*/
public function add($prefix, $paths, $prepend = false)
{
- $paths = (array) $paths;
if (!$prefix) {
if ($prepend) {
$this->fallbackDirsPsr0 = array_merge(
- $paths,
+ (array) $paths,
$this->fallbackDirsPsr0
);
} else {
$this->fallbackDirsPsr0 = array_merge(
$this->fallbackDirsPsr0,
- $paths
+ (array) $paths
);
}
@@ -198,19 +205,19 @@ public function add($prefix, $paths, $prepend = false)
$first = $prefix[0];
if (!isset($this->prefixesPsr0[$first][$prefix])) {
- $this->prefixesPsr0[$first][$prefix] = $paths;
+ $this->prefixesPsr0[$first][$prefix] = (array) $paths;
return;
}
if ($prepend) {
$this->prefixesPsr0[$first][$prefix] = array_merge(
- $paths,
+ (array) $paths,
$this->prefixesPsr0[$first][$prefix]
);
} else {
$this->prefixesPsr0[$first][$prefix] = array_merge(
$this->prefixesPsr0[$first][$prefix],
- $paths
+ (array) $paths
);
}
}
@@ -219,9 +226,9 @@ public function add($prefix, $paths, $prepend = false)
* Registers a set of PSR-4 directories for a given namespace, either
* appending or prepending to the ones previously set for this namespace.
*
- * @param string $prefix The prefix/namespace, with trailing '\\'
- * @param list|string $paths The PSR-4 base directories
- * @param bool $prepend Whether to prepend the directories
+ * @param string $prefix The prefix/namespace, with trailing '\\'
+ * @param string[]|string $paths The PSR-4 base directories
+ * @param bool $prepend Whether to prepend the directories
*
* @throws \InvalidArgumentException
*
@@ -229,18 +236,17 @@ public function add($prefix, $paths, $prepend = false)
*/
public function addPsr4($prefix, $paths, $prepend = false)
{
- $paths = (array) $paths;
if (!$prefix) {
// Register directories for the root namespace.
if ($prepend) {
$this->fallbackDirsPsr4 = array_merge(
- $paths,
+ (array) $paths,
$this->fallbackDirsPsr4
);
} else {
$this->fallbackDirsPsr4 = array_merge(
$this->fallbackDirsPsr4,
- $paths
+ (array) $paths
);
}
} elseif (!isset($this->prefixDirsPsr4[$prefix])) {
@@ -250,18 +256,18 @@ public function addPsr4($prefix, $paths, $prepend = false)
throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
}
$this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
- $this->prefixDirsPsr4[$prefix] = $paths;
+ $this->prefixDirsPsr4[$prefix] = (array) $paths;
} elseif ($prepend) {
// Prepend directories for an already registered namespace.
$this->prefixDirsPsr4[$prefix] = array_merge(
- $paths,
+ (array) $paths,
$this->prefixDirsPsr4[$prefix]
);
} else {
// Append directories for an already registered namespace.
$this->prefixDirsPsr4[$prefix] = array_merge(
$this->prefixDirsPsr4[$prefix],
- $paths
+ (array) $paths
);
}
}
@@ -270,8 +276,8 @@ public function addPsr4($prefix, $paths, $prepend = false)
* Registers a set of PSR-0 directories for a given prefix,
* replacing any others previously set for this prefix.
*
- * @param string $prefix The prefix
- * @param list|string $paths The PSR-0 base directories
+ * @param string $prefix The prefix
+ * @param string[]|string $paths The PSR-0 base directories
*
* @return void
*/
@@ -288,8 +294,8 @@ public function set($prefix, $paths)
* Registers a set of PSR-4 directories for a given namespace,
* replacing any others previously set for this namespace.
*
- * @param string $prefix The prefix/namespace, with trailing '\\'
- * @param list|string $paths The PSR-4 base directories
+ * @param string $prefix The prefix/namespace, with trailing '\\'
+ * @param string[]|string $paths The PSR-4 base directories
*
* @throws \InvalidArgumentException
*
@@ -475,9 +481,9 @@ public function findFile($class)
}
/**
- * Returns the currently registered loaders keyed by their corresponding vendor directories.
+ * Returns the currently registered loaders indexed by their corresponding vendor directories.
*
- * @return array
+ * @return self[]
*/
public static function getRegisteredLoaders()
{
diff --git a/apps/files_sharing/composer/composer/autoload_classmap.php b/apps/files_sharing/composer/composer/autoload_classmap.php
index b7a931a622895..cbb366a3c19b1 100644
--- a/apps/files_sharing/composer/composer/autoload_classmap.php
+++ b/apps/files_sharing/composer/composer/autoload_classmap.php
@@ -26,6 +26,7 @@
'OCA\\Files_Sharing\\Command\\CleanupRemoteStorages' => $baseDir . '/../lib/Command/CleanupRemoteStorages.php',
'OCA\\Files_Sharing\\Command\\DeleteOrphanShares' => $baseDir . '/../lib/Command/DeleteOrphanShares.php',
'OCA\\Files_Sharing\\Command\\ExiprationNotification' => $baseDir . '/../lib/Command/ExiprationNotification.php',
+ 'OCA\\Files_Sharing\\Command\\FixBrokenShares' => $baseDir . '/../lib/Command/FixBrokenShares.php',
'OCA\\Files_Sharing\\Controller\\AcceptController' => $baseDir . '/../lib/Controller/AcceptController.php',
'OCA\\Files_Sharing\\Controller\\DeletedShareAPIController' => $baseDir . '/../lib/Controller/DeletedShareAPIController.php',
'OCA\\Files_Sharing\\Controller\\ExternalSharesController' => $baseDir . '/../lib/Controller/ExternalSharesController.php',
diff --git a/apps/files_sharing/composer/composer/autoload_static.php b/apps/files_sharing/composer/composer/autoload_static.php
index 70dc7be7cdf69..62f193436e36d 100644
--- a/apps/files_sharing/composer/composer/autoload_static.php
+++ b/apps/files_sharing/composer/composer/autoload_static.php
@@ -41,6 +41,7 @@ class ComposerStaticInitFiles_Sharing
'OCA\\Files_Sharing\\Command\\CleanupRemoteStorages' => __DIR__ . '/..' . '/../lib/Command/CleanupRemoteStorages.php',
'OCA\\Files_Sharing\\Command\\DeleteOrphanShares' => __DIR__ . '/..' . '/../lib/Command/DeleteOrphanShares.php',
'OCA\\Files_Sharing\\Command\\ExiprationNotification' => __DIR__ . '/..' . '/../lib/Command/ExiprationNotification.php',
+ 'OCA\\Files_Sharing\\Command\\FixBrokenShares' => __DIR__ . '/..' . '/../lib/Command/FixBrokenShares.php',
'OCA\\Files_Sharing\\Controller\\AcceptController' => __DIR__ . '/..' . '/../lib/Controller/AcceptController.php',
'OCA\\Files_Sharing\\Controller\\DeletedShareAPIController' => __DIR__ . '/..' . '/../lib/Controller/DeletedShareAPIController.php',
'OCA\\Files_Sharing\\Controller\\ExternalSharesController' => __DIR__ . '/..' . '/../lib/Controller/ExternalSharesController.php',
diff --git a/apps/files_sharing/lib/Command/FixBrokenShares.php b/apps/files_sharing/lib/Command/FixBrokenShares.php
new file mode 100644
index 0000000000000..e9b21a10e311d
--- /dev/null
+++ b/apps/files_sharing/lib/Command/FixBrokenShares.php
@@ -0,0 +1,59 @@
+setName('sharing:fix-broken-shares')
+ ->setDescription('Fix broken shares after transfer ownership')
+ ->addOption(
+ 'dry-run',
+ null,
+ InputOption::VALUE_NONE,
+ 'only show which shares would be updated'
+ );
+ }
+
+ public function execute(InputInterface $input, OutputInterface $output): int {
+ $shares = $this->orphanHelper->getAllShares();
+ $dryRun = $input->getOption('dry-run');
+
+ foreach ($shares as $share) {
+ if ($this->orphanHelper->isShareValid($share['owner'], $share['fileid']) || !$this->orphanHelper->fileExists($share['fileid'])) {
+ continue;
+ }
+
+ $owner = $this->orphanHelper->findOwner($share['fileid']);
+
+ if ($owner !== null) {
+ if ($dryRun) {
+ $output->writeln("Share {$share['id']} can be updated to owner $owner");
+ } else {
+ $this->orphanHelper->updateShareOwner($share['id'], $owner);
+ $output->writeln("Share {$share['id']} updated to owner $owner");
+ }
+ }
+ }
+
+ return static::SUCCESS;
+ }
+}
diff --git a/apps/files_sharing/lib/OrphanHelper.php b/apps/files_sharing/lib/OrphanHelper.php
index 94fe4f0831838..cbf594db16692 100644
--- a/apps/files_sharing/lib/OrphanHelper.php
+++ b/apps/files_sharing/lib/OrphanHelper.php
@@ -10,19 +10,23 @@
use OC\User\NoUserException;
use OCP\DB\QueryBuilder\IQueryBuilder;
+use OCP\Files\Config\IUserMountCache;
use OCP\Files\IRootFolder;
use OCP\IDBConnection;
class OrphanHelper {
private IDBConnection $connection;
private IRootFolder $rootFolder;
+ private IUserMountCache $userMountCache;
public function __construct(
IDBConnection $connection,
- IRootFolder $rootFolder
+ IRootFolder $rootFolder,
+ IUserMountCache $userMountCache
) {
$this->connection = $connection;
$this->rootFolder = $rootFolder;
+ $this->userMountCache = $userMountCache;
}
public function isShareValid(string $owner, int $fileId): bool {
@@ -73,4 +77,26 @@ public function getAllShares() {
];
}
}
+
+ public function findOwner(int $fileId): ?string {
+ $mounts = $this->userMountCache->getMountsForFileId($fileId);
+ if (!$mounts) {
+ return null;
+ }
+ foreach ($mounts as $mount) {
+ // Only the mount of owner has the internal path value
+ if ($mount->getInternalPath()) {
+ return $mount->getUser()->getUID();
+ }
+ }
+ return null;
+ }
+
+ public function updateShareOwner(int $shareId, string $owner): void {
+ $query = $this->connection->getQueryBuilder();
+ $query->update('share')
+ ->set('uid_owner', $query->createNamedParameter($owner))
+ ->where($query->expr()->eq('id', $query->createNamedParameter($shareId, IQueryBuilder::PARAM_INT)));
+ $query->executeStatement();
+ }
}
diff --git a/apps/files_sharing/tests/EtagPropagationTest.php b/apps/files_sharing/tests/EtagPropagationTest.php
index 3f9ddfc413dc7..327726acc9774 100644
--- a/apps/files_sharing/tests/EtagPropagationTest.php
+++ b/apps/files_sharing/tests/EtagPropagationTest.php
@@ -277,7 +277,8 @@ public function testOwnerUnshares() {
self::TEST_FILES_SHARING_API_USER2,
]);
- $this->assertAllUnchanged();
+ $this->assertEtagsNotChanged([self::TEST_FILES_SHARING_API_USER1, self::TEST_FILES_SHARING_API_USER2, self::TEST_FILES_SHARING_API_USER3]);
+ $this->assertEtagsChanged([self::TEST_FILES_SHARING_API_USER4]);
}
public function testOwnerUnsharesFlatReshares() {
diff --git a/lib/private/Share20/Manager.php b/lib/private/Share20/Manager.php
index e76f4586dfded..64ca7e218334c 100644
--- a/lib/private/Share20/Manager.php
+++ b/lib/private/Share20/Manager.php
@@ -17,6 +17,7 @@
use OCP\Files\IRootFolder;
use OCP\Files\Mount\IMountManager;
use OCP\Files\Node;
+use OCP\Files\NotFoundException;
use OCP\HintException;
use OCP\IConfig;
use OCP\IDateTimeZone;
@@ -1016,6 +1017,110 @@ protected function deleteChildren(IShare $share) {
return $deletedShares;
}
+ public function deleteReshare(IShare $share) {
+ // Skip if node not found
+ try {
+ $node = $share->getNode();
+ } catch (NotFoundException) {
+ return;
+ }
+
+ // If the sharedWith user/group has another shares, we don't delete the shares by this user/group
+ if ($share->getShareType() === IShare::TYPE_USER && $this->hasNodeAccess($share->getSharedWith(), $node)) {
+ return;
+ }
+
+ // Delete re-share records (shared by "share with user") inside folder
+ if ($share->getNodeType() === 'folder' && $share->getShareType() === IShare::TYPE_USER) {
+ $sharesInFolder = $this->getSharesInFolder($share->getSharedWith(), $node, true);
+
+ foreach ($sharesInFolder as $shares) {
+ foreach ($shares as $child) {
+ if ($this->hasNodeAccess($share->getSharedWith(), $child->getNode())) {
+ continue;
+ }
+ $this->deleteShare($child);
+ }
+ }
+ }
+
+ $shareTypes = [
+ IShare::TYPE_GROUP,
+ IShare::TYPE_USER,
+ IShare::TYPE_LINK,
+ IShare::TYPE_REMOTE,
+ IShare::TYPE_EMAIL
+ ];
+
+ // Delete re-share records which shared by "share with user"
+ if ($share->getShareType() === IShare::TYPE_USER || $share->getShareType() === IShare::TYPE_USERGROUP) {
+ foreach ($shareTypes as $shareType) {
+ $provider = $this->factory->getProviderForType($shareType);
+ $shares = $provider->getSharesBy($share->getSharedWith(), $shareType, $node, false, -1, 0);
+ foreach ($shares as $child) {
+ if ($this->hasNodeAccess($share->getSharedWith(), $child->getNode())) {
+ continue;
+ }
+ $this->deleteShare($child);
+ }
+ }
+ }
+
+ // Delete re-share records which shared by users in "share with group"
+ if ($share->getShareType() === IShare::TYPE_GROUP) {
+ $group = $this->groupManager->get($share->getSharedWith());
+ $users = $group->getUsers();
+
+ foreach ($users as $user) {
+ // Skip if share owner is member of shared group
+ if ($user->getUID() === $share->getShareOwner()) {
+ continue;
+ }
+
+ // If the user has another shares, we don't delete the shares by this user
+ if ($this->hasNodeAccess($user->getUID(), $node)) {
+ continue;
+ }
+
+ if ($share->getNodeType() === 'folder') {
+ $sharesInFolder = $this->getSharesInFolder($user->getUID(), $node, true);
+
+ foreach ($sharesInFolder as $shares) {
+ foreach ($shares as $child) {
+ if ($this->hasNodeAccess($user->getUID(), $child->getNode())) {
+ continue;
+ }
+ $this->deleteShare($child);
+ }
+ }
+ }
+
+ foreach ($shareTypes as $shareType) {
+ $provider = $this->factory->getProviderForType($shareType);
+ $shares = $provider->getSharesBy($user->getUID(), $shareType, $node, false, -1, 0);
+ foreach ($shares as $child) {
+ $this->deleteShare($child);
+ }
+ }
+ }
+ }
+ }
+
+ public function hasNodeAccess($userId, Node $node) {
+ $otherShares = $this->getSharedWith($userId, IShare::TYPE_USER, $node, -1);
+
+ foreach ($otherShares as $share) {
+ try {
+ $this->generalCreateChecks($share);
+ return true;
+ } catch (GenericShareException $e) {
+ return false;
+ }
+ }
+
+ return false;
+ }
+
/**
* Delete a share
*
@@ -1039,6 +1144,9 @@ public function deleteShare(IShare $share) {
$provider = $this->factory->getProviderForType($share->getShareType());
$provider->delete($share);
+ // Delete shares that shared by the "share with user/group"
+ $this->deleteReshare($share);
+
$this->dispatcher->dispatchTyped(new ShareDeletedEvent($share));
}
diff --git a/tests/lib/Share20/ManagerTest.php b/tests/lib/Share20/ManagerTest.php
index 15ecc839451a4..28d18845f8d88 100644
--- a/tests/lib/Share20/ManagerTest.php
+++ b/tests/lib/Share20/ManagerTest.php
@@ -227,7 +227,7 @@ public function dataTestDelete() {
*/
public function testDelete($shareType, $sharedWith) {
$manager = $this->createManagerMock()
- ->setMethods(['getShareById', 'deleteChildren'])
+ ->setMethods(['getShareById', 'deleteChildren', 'deleteReshare'])
->getMock();
$manager->method('deleteChildren')->willReturn([]);
@@ -245,6 +245,7 @@ public function testDelete($shareType, $sharedWith) {
->setTarget('myTarget');
$manager->expects($this->once())->method('deleteChildren')->with($share);
+ $manager->expects($this->once())->method('deleteReshare')->with($share);
$this->defaultProvider
->expects($this->once())
@@ -269,7 +270,7 @@ public function testDelete($shareType, $sharedWith) {
public function testDeleteLazyShare() {
$manager = $this->createManagerMock()
- ->setMethods(['getShareById', 'deleteChildren'])
+ ->setMethods(['getShareById', 'deleteChildren', 'deleteReshare'])
->getMock();
$manager->method('deleteChildren')->willReturn([]);
@@ -288,6 +289,7 @@ public function testDeleteLazyShare() {
$this->rootFolder->expects($this->never())->method($this->anything());
$manager->expects($this->once())->method('deleteChildren')->with($share);
+ $manager->expects($this->once())->method('deleteReshare')->with($share);
$this->defaultProvider
->expects($this->once())
@@ -312,7 +314,7 @@ public function testDeleteLazyShare() {
public function testDeleteNested() {
$manager = $this->createManagerMock()
- ->setMethods(['getShareById'])
+ ->setMethods(['getShareById', 'deleteReshare'])
->getMock();
$path = $this->createMock(File::class);
@@ -469,6 +471,122 @@ public function testDeleteChildren() {
$this->assertSame($shares, $result);
}
+ public function testDeleteReshareWhenUserHasOneShare() {
+ $manager = $this->createManagerMock()
+ ->setMethods(['deleteShare', 'getSharesInFolder', 'getSharedWith', 'hasNodeAccess'])
+ ->getMock();
+ $manager->method('hasNodeAccess')->willReturn(false);
+
+ $folder = $this->createMock(Folder::class);
+ $folder->method('getParent')->willReturn(null);
+
+ $share = $this->createMock(IShare::class);
+ $share->method('getShareType')->willReturn(IShare::TYPE_USER);
+ $share->method('getNodeType')->willReturn('folder');
+ $share->method('getSharedWith')->willReturn('UserB');
+ $share->method('getNode')->willReturn($folder);
+
+ $reShare = $this->createMock(IShare::class);
+ $reShare->method('getSharedBy')->willReturn('UserB');
+ $reShare->method('getNode')->willReturn($folder);
+
+ $reShareInSubFolder = $this->createMock(IShare::class);
+ $reShareInSubFolder->method('getSharedBy')->willReturn('UserB');
+
+ $manager->method('getSharedWith')->willReturn([]);
+ $manager->method('getSharesInFolder')->willReturn([$reShareInSubFolder]);
+
+ $this->defaultProvider->method('getSharesBy')
+ ->willReturn([$reShare]);
+
+ $manager->expects($this->atLeast(2))->method('deleteShare')->withConsecutive([$reShare], [$reShareInSubFolder]);
+
+ $manager->deleteReshare($share);
+ }
+
+ public function testDeleteReshareWhenUserHasAnotherShare() {
+ $manager = $this->createManagerMock()
+ ->setMethods(['deleteShare', 'getSharesInFolder', 'getSharedWith', 'hasNodeAccess'])
+ ->getMock();
+ $manager->method('hasNodeAccess')->willReturn(true);
+
+ $folder = $this->createMock(Folder::class);
+ $folder->method('getParent')->willReturn(null);
+
+ $share = $this->createMock(IShare::class);
+ $share->method('getShareType')->willReturn(IShare::TYPE_USER);
+ $share->method('getNodeType')->willReturn('folder');
+ $share->method('getSharedWith')->willReturn('UserB');
+ $share->method('getNode')->willReturn($folder);
+
+ $reShare = $this->createMock(IShare::class);
+ $reShare->method('getShareType')->willReturn(IShare::TYPE_USER);
+ $reShare->method('getNodeType')->willReturn('folder');
+ $reShare->method('getSharedBy')->willReturn('UserB');
+ $reShare->method('getNode')->willReturn($folder);
+
+ $manager->method('getSharedWith')
+ ->with('UserB', IShare::TYPE_GROUP, $folder, -1, 0)
+ ->willReturn([1]);
+
+ $manager->expects($this->never())->method('deleteShare');
+
+ $manager->deleteReshare($share);
+ }
+
+ public function testDeleteReshareOfUsersInGroupShare() {
+ $manager = $this->createManagerMock()
+ ->setMethods(['deleteShare', 'getSharesInFolder', 'getSharedWith', 'hasNodeAccess'])
+ ->getMock();
+ $manager->method('hasNodeAccess')->willReturn(false);
+
+ $parentFolder = $this->createMock(Folder::class);
+ $parentFolder->method('getParent')->willReturn(null);
+
+ $folder = $this->createMock(Folder::class);
+ $folder->method('getParent')->willReturn($parentFolder);
+
+ $userA = $this->createMock(IUser::class);
+ $userA->method('getUID')->willReturn('userA');
+
+ $share = $this->createMock(IShare::class);
+ $share->method('getShareType')->willReturn(IShare::TYPE_GROUP);
+ $share->method('getNodeType')->willReturn('folder');
+ $share->method('getSharedWith')->willReturn('Group');
+ $share->method('getNode')->willReturn($folder);
+ $share->method('getShareOwner')->willReturn($userA);
+
+ $reShare1 = $this->createMock(IShare::class);
+ $reShare1->method('getShareType')->willReturn(IShare::TYPE_USER);
+ $reShare1->method('getNodeType')->willReturn('folder');
+ $reShare1->method('getSharedBy')->willReturn('UserB');
+ $reShare1->method('getNode')->willReturn($folder);
+
+ $reShare2 = $this->createMock(IShare::class);
+ $reShare2->method('getShareType')->willReturn(IShare::TYPE_USER);
+ $reShare2->method('getNodeType')->willReturn('folder');
+ $reShare2->method('getSharedBy')->willReturn('UserC');
+ $reShare2->method('getNode')->willReturn($folder);
+
+ $userB = $this->createMock(IUser::class);
+ $userB->method('getUID')->willReturn('userB');
+ $userC = $this->createMock(IUser::class);
+ $userC->method('getUID')->willReturn('userC');
+ $group = $this->createMock(IGroup::class);
+ $group->method('getUsers')->willReturn([$userB, $userC]);
+ $this->groupManager->method('get')->with('Group')->willReturn($group);
+
+ $this->defaultProvider->method('getSharesBy')
+ ->willReturn([]);
+
+ $manager->method('getSharedWith')->willReturn([]);
+ $manager->expects($this->exactly(2))->method('getSharesInFolder')->willReturnOnConsecutiveCalls([[$reShare1]], [[$reShare2]]);
+
+ $manager->expects($this->exactly(2))->method('deleteShare')->withConsecutive([$reShare1], [$reShare2]);
+
+ $manager->deleteReshare($share);
+ }
+
public function testGetShareById() {
$share = $this->createMock(IShare::class);