diff --git a/.github/workflows/phpunit-mysql.yml b/.github/workflows/phpunit-mysql.yml
index cecc22e1c0005..20b2f3a52747e 100644
--- a/.github/workflows/phpunit-mysql.yml
+++ b/.github/workflows/phpunit-mysql.yml
@@ -89,7 +89,7 @@ jobs:
submodules: true
- name: Set up php ${{ matrix.php-versions }}
- uses: shivammathur/setup-php@4bd44f22a98a19e0950cbad5f31095157cc9621b # v2
+ uses: shivammathur/setup-php@c665c7a15b5295c2488ac8a87af9cb806cd72198 # v2
with:
php-version: ${{ matrix.php-versions }}
# https://docs.nextcloud.com/server/stable/admin_manual/installation/source_installation.html#prerequisites-for-manual-installation
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 e1abddb3a6450..87c935dcbab82 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 5d2fb3bac2a47..c77a2af82b278 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 75eeb082b2fba..1b52ecfce46c2 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;
@@ -360,7 +361,7 @@ protected function validateExpirationDateLink(IShare $share) {
if ($expirationDate !== null) {
$expirationDate->setTimezone($this->dateTimeZone->getTimeZone());
$expirationDate->setTime(0, 0, 0);
-
+
$date = new \DateTime('now', $this->dateTimeZone->getTimeZone());
$date->setTime(0, 0, 0);
if ($date >= $expirationDate) {
@@ -376,24 +377,24 @@ protected function validateExpirationDateLink(IShare $share) {
} catch (\UnexpectedValueException $e) {
// This is a new share
}
-
+
if ($fullId === null && $expirationDate === null && $this->shareApiLinkDefaultExpireDate()) {
$expirationDate = new \DateTime('now', $this->dateTimeZone->getTimeZone());
$expirationDate->setTime(0, 0, 0);
-
+
$days = (int)$this->config->getAppValue('core', 'link_defaultExpDays', (string)$this->shareApiLinkDefaultExpireDays());
if ($days > $this->shareApiLinkDefaultExpireDays()) {
$days = $this->shareApiLinkDefaultExpireDays();
}
$expirationDate->add(new \DateInterval('P' . $days . 'D'));
}
-
+
// If we enforce the expiration date check that is does not exceed
if ($isEnforced) {
if (empty($expirationDate)) {
throw new \InvalidArgumentException('Expiration date is enforced');
}
-
+
$date = new \DateTime('now', $this->dateTimeZone->getTimeZone());
$date->setTime(0, 0, 0);
$date->add(new \DateInterval('P' . $this->shareApiLinkDefaultExpireDays() . 'D'));
@@ -1117,6 +1118,108 @@ 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 user has another shares, we don't delete the shares by this user
+ if ($share->getShareType() === IShare::TYPE_USER) {
+ $groupShares = $this->getSharedWith($share->getSharedWith(), IShare::TYPE_GROUP, $node, -1, 0);
+
+ if (count($groupShares) !== 0) {
+ return;
+ }
+
+ // Check shares of parent folders
+ try {
+ $parentNode = $node->getParent();
+ while ($parentNode) {
+ $groupShares = $this->getSharedWith($share->getSharedWith(), IShare::TYPE_GROUP, $parentNode, -1, 0);
+ $userShares = $this->getSharedWith($share->getSharedWith(), IShare::TYPE_USER, $parentNode, -1, 0);
+
+ if (count($groupShares) !== 0 || count($userShares) !== 0) {
+ return;
+ }
+
+ $parentNode = $parentNode->getParent();
+ }
+ } catch (NotFoundException) {
+ }
+ }
+
+ // 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) {
+ $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) {
+ $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) {
+ if ($user->getUID() === $share->getShareOwner()) {
+ continue;
+ }
+
+ $anotherShares = $this->getSharedWith($user->getUID(), IShare::TYPE_USER, $node, -1, 0);
+ $groupShares = $this->getSharedWith($user->getUID(), IShare::TYPE_GROUP, $node, -1, 0);
+
+ // If the user has another shares, we don't delete the shares by this user
+ if (count($anotherShares) !== 0 || count($groupShares) > 1) {
+ continue;
+ }
+
+ if ($share->getNodeType() === 'folder') {
+ $sharesInFolder = $this->getSharesInFolder($user->getUID(), $node, true);
+
+ foreach ($sharesInFolder as $shares) {
+ foreach ($shares as $child) {
+ $this->deleteShare($child);
+ }
+ }
+ } else {
+ 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);
+ }
+ }
+ }
+ }
+ }
+ }
+
/**
* Delete a share
*
@@ -1133,6 +1236,9 @@ public function deleteShare(IShare $share) {
$this->dispatcher->dispatchTyped(new BeforeShareDeletedEvent($share));
+ // Delete shares that shared by the "share with user/group"
+ $this->deleteReshare($share);
+
// Get all children and delete them as well
$this->deleteChildren($share);
diff --git a/tests/lib/Share20/ManagerTest.php b/tests/lib/Share20/ManagerTest.php
index 15ecc839451a4..b4f61ba4b2498 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,149 @@ public function testDeleteChildren() {
$this->assertSame($shares, $result);
}
+ public function testDeleteReshareWhenUserHasOneShare() {
+ $manager = $this->createManagerMock()
+ ->setMethods(['deleteShare', 'getSharesInFolder', 'getSharedWith'])
+ ->getMock();
+
+ $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');
+
+ $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'])
+ ->getMock();
+
+ $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 testDeleteReshareWhenUserHasAnotherShareFromParentFolder() {
+ $manager = $this->createManagerMock()
+ ->setMethods(['deleteShare', 'getSharesInFolder', 'getSharedWith'])
+ ->getMock();
+
+ $parentFolder = $this->createMock(Folder::class);
+ $parentFolder->method('getParent')->willReturn(null);
+
+ $folder = $this->createMock(Folder::class);
+ $folder->method('getParent')->willReturn($parentFolder);
+
+ $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->expects($this->exactly(3))->method('getSharedWith')->willReturnOnConsecutiveCalls([], [1], []);
+ $manager->method('getSharesInFolder')->willReturn([]);
+
+ $this->defaultProvider->method('getSharesBy')
+ ->willReturn([$reShare]);
+
+ $manager->expects($this->never())->method('deleteShare');
+
+ $manager->deleteReshare($share);
+ }
+
+ public function testDeleteReshareOfUsersInGroupShare() {
+ $manager = $this->createManagerMock()
+ ->setMethods(['deleteShare', 'getSharesInFolder', 'getSharedWith'])
+ ->getMock();
+
+ $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);
+
+ $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);