Merge pull request #713 from nextcloud/stable10_474
[Stable 10] Group shares with same source and target
This commit is contained in:
commit
7321baa108
|
@ -75,16 +75,20 @@ class MountProvider implements IMountProvider {
|
|||
return $share->getPermissions() > 0 && $share->getShareOwner() !== $user->getUID();
|
||||
});
|
||||
|
||||
$mounts = [];
|
||||
foreach ($shares as $share) {
|
||||
$superShares = $this->buildSuperShares($shares, $user);
|
||||
|
||||
$mounts = [];
|
||||
foreach ($superShares as $share) {
|
||||
try {
|
||||
$mounts[] = new SharedMount(
|
||||
'\OC\Files\Storage\Shared',
|
||||
$mounts,
|
||||
[
|
||||
'user' => $user->getUID(),
|
||||
'newShare' => $share,
|
||||
// parent share
|
||||
'superShare' => $share[0],
|
||||
// children/component of the superShare
|
||||
'groupedShares' => $share[1],
|
||||
],
|
||||
$storageFactory
|
||||
);
|
||||
|
@ -97,4 +101,84 @@ class MountProvider implements IMountProvider {
|
|||
// array_filter removes the null values from the array
|
||||
return array_filter($mounts);
|
||||
}
|
||||
|
||||
/**
|
||||
* Groups shares by path (nodeId) and target path
|
||||
*
|
||||
* @param \OCP\Share\IShare[] $shares
|
||||
* @return \OCP\Share\IShare[][] array of grouped shares, each element in the
|
||||
* array is a group which itself is an array of shares
|
||||
*/
|
||||
private function groupShares(array $shares) {
|
||||
$tmp = [];
|
||||
|
||||
foreach ($shares as $share) {
|
||||
if (!isset($tmp[$share->getNodeId()])) {
|
||||
$tmp[$share->getNodeId()] = [];
|
||||
}
|
||||
$tmp[$share->getNodeId()][] = $share;
|
||||
}
|
||||
|
||||
$result = [];
|
||||
// sort by stime, the super share will be based on the least recent share
|
||||
foreach ($tmp as &$tmp2) {
|
||||
@usort($tmp2, function($a, $b) {
|
||||
if ($a->getShareTime() < $b->getShareTime()) {
|
||||
return -1;
|
||||
}
|
||||
return 1;
|
||||
});
|
||||
$result[] = $tmp2;
|
||||
}
|
||||
|
||||
return array_values($result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build super shares (virtual share) by grouping them by node id and target,
|
||||
* then for each group compute the super share and return it along with the matching
|
||||
* grouped shares. The most permissive permissions are used based on the permissions
|
||||
* of all shares within the group.
|
||||
*
|
||||
* @param \OCP\Share\IShare[] $allShares
|
||||
* @param \OCP\IUser $user user
|
||||
* @return array Tuple of [superShare, groupedShares]
|
||||
*/
|
||||
private function buildSuperShares(array $allShares, \OCP\IUser $user) {
|
||||
$result = [];
|
||||
|
||||
$groupedShares = $this->groupShares($allShares);
|
||||
|
||||
/** @var \OCP\Share\IShare[] $shares */
|
||||
foreach ($groupedShares as $shares) {
|
||||
if (count($shares) === 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$superShare = $this->shareManager->newShare();
|
||||
|
||||
// compute super share based on first entry of the group
|
||||
$superShare->setId($shares[0]->getId())
|
||||
->setShareOwner($shares[0]->getShareOwner())
|
||||
->setNodeId($shares[0]->getNodeId())
|
||||
->setTarget($shares[0]->getTarget());
|
||||
|
||||
// use most permissive permissions
|
||||
$permissions = 0;
|
||||
foreach ($shares as $share) {
|
||||
$permissions |= $share->getPermissions();
|
||||
if ($share->getTarget() !== $superShare->getTarget()) {
|
||||
// adjust target, for database consistency
|
||||
$share->setTarget($superShare->getTarget());
|
||||
$this->shareManager->moveShare($share, $user->getUID());
|
||||
}
|
||||
}
|
||||
|
||||
$superShare->setPermissions($permissions);
|
||||
|
||||
$result[] = [$superShare, $shares];
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -52,7 +52,10 @@ class SharedMount extends MountPoint implements MoveableMount {
|
|||
private $user;
|
||||
|
||||
/** @var \OCP\Share\IShare */
|
||||
private $share;
|
||||
private $superShare;
|
||||
|
||||
/** @var \OCP\Share\IShare[] */
|
||||
private $groupedShares;
|
||||
|
||||
/**
|
||||
* @param string $storage
|
||||
|
@ -63,10 +66,13 @@ class SharedMount extends MountPoint implements MoveableMount {
|
|||
public function __construct($storage, array $mountpoints, $arguments = null, $loader = null) {
|
||||
$this->user = $arguments['user'];
|
||||
$this->recipientView = new View('/' . $this->user . '/files');
|
||||
$this->share = $arguments['newShare'];
|
||||
$newMountPoint = $this->verifyMountPoint($this->share, $mountpoints);
|
||||
|
||||
$this->superShare = $arguments['superShare'];
|
||||
$this->groupedShares = $arguments['groupedShares'];
|
||||
|
||||
$newMountPoint = $this->verifyMountPoint($this->superShare, $mountpoints);
|
||||
$absMountPoint = '/' . $this->user . '/files' . $newMountPoint;
|
||||
$arguments['ownerView'] = new View('/' . $this->share->getShareOwner() . '/files');
|
||||
$arguments['ownerView'] = new View('/' . $this->superShare->getShareOwner() . '/files');
|
||||
parent::__construct($storage, $absMountPoint, $arguments, $loader);
|
||||
}
|
||||
|
||||
|
@ -108,7 +114,11 @@ class SharedMount extends MountPoint implements MoveableMount {
|
|||
*/
|
||||
private function updateFileTarget($newPath, &$share) {
|
||||
$share->setTarget($newPath);
|
||||
\OC::$server->getShareManager()->moveShare($share, $this->user);
|
||||
|
||||
foreach ($this->groupedShares as $share) {
|
||||
$share->setTarget($newPath);
|
||||
\OC::$server->getShareManager()->moveShare($share, $this->user);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -214,7 +224,7 @@ class SharedMount extends MountPoint implements MoveableMount {
|
|||
* @return \OCP\Share\IShare
|
||||
*/
|
||||
public function getShare() {
|
||||
return $this->share;
|
||||
return $this->superShare;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -223,6 +233,6 @@ class SharedMount extends MountPoint implements MoveableMount {
|
|||
* @return int
|
||||
*/
|
||||
public function getStorageRootId() {
|
||||
return $this->share->getNodeId();
|
||||
return $this->getShare()->getNodeId();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -43,11 +43,11 @@ use OCP\Lock\ILockingProvider;
|
|||
* Convert target path to source path and pass the function call to the correct storage provider
|
||||
*/
|
||||
class Shared extends \OC\Files\Storage\Wrapper\Jail implements ISharedStorage {
|
||||
|
||||
private $share; // the shared resource
|
||||
|
||||
/** @var \OCP\Share\IShare */
|
||||
private $newShare;
|
||||
private $superShare;
|
||||
|
||||
/** @var \OCP\Share\IShare[] */
|
||||
private $groupedShares;
|
||||
|
||||
/**
|
||||
* @var \OC\Files\View
|
||||
|
@ -77,11 +77,14 @@ class Shared extends \OC\Files\Storage\Wrapper\Jail implements ISharedStorage {
|
|||
public function __construct($arguments) {
|
||||
$this->ownerView = $arguments['ownerView'];
|
||||
$this->logger = \OC::$server->getLogger();
|
||||
$this->newShare = $arguments['newShare'];
|
||||
|
||||
$this->superShare = $arguments['superShare'];
|
||||
$this->groupedShares = $arguments['groupedShares'];
|
||||
|
||||
$this->user = $arguments['user'];
|
||||
|
||||
Filesystem::initMountPoints($this->newShare->getShareOwner());
|
||||
$sourcePath = $this->ownerView->getPath($this->newShare->getNodeId());
|
||||
Filesystem::initMountPoints($this->superShare->getShareOwner());
|
||||
$sourcePath = $this->ownerView->getPath($this->superShare->getNodeId());
|
||||
list($storage, $internalPath) = $this->ownerView->resolvePath($sourcePath);
|
||||
|
||||
parent::__construct([
|
||||
|
@ -96,8 +99,8 @@ class Shared extends \OC\Files\Storage\Wrapper\Jail implements ISharedStorage {
|
|||
}
|
||||
$this->initialized = true;
|
||||
try {
|
||||
Filesystem::initMountPoints($this->newShare->getShareOwner());
|
||||
$sourcePath = $this->ownerView->getPath($this->newShare->getNodeId());
|
||||
Filesystem::initMountPoints($this->superShare->getShareOwner());
|
||||
$sourcePath = $this->ownerView->getPath($this->superShare->getNodeId());
|
||||
list($this->sourceStorage, $sourceInternalPath) = $this->ownerView->resolvePath($sourcePath);
|
||||
$this->sourceRootInfo = $this->sourceStorage->getCache()->get($sourceInternalPath);
|
||||
} catch (\Exception $e) {
|
||||
|
@ -105,6 +108,13 @@ class Shared extends \OC\Files\Storage\Wrapper\Jail implements ISharedStorage {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getShareId() {
|
||||
return $this->superShare->getId();
|
||||
}
|
||||
|
||||
private function isValid() {
|
||||
$this->init();
|
||||
return $this->sourceRootInfo && ($this->sourceRootInfo->getPermissions() & Constants::PERMISSION_SHARE) === Constants::PERMISSION_SHARE;
|
||||
|
@ -119,15 +129,6 @@ class Shared extends \OC\Files\Storage\Wrapper\Jail implements ISharedStorage {
|
|||
return 'shared::' . $this->getMountPoint();
|
||||
}
|
||||
|
||||
/**
|
||||
* get file cache of the shared item source
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getSourceId() {
|
||||
return $this->newShare->getNodeId();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the permissions granted for a shared file
|
||||
*
|
||||
|
@ -138,7 +139,7 @@ class Shared extends \OC\Files\Storage\Wrapper\Jail implements ISharedStorage {
|
|||
if (!$this->isValid()) {
|
||||
return 0;
|
||||
}
|
||||
$permissions = $this->newShare->getPermissions();
|
||||
$permissions = $this->superShare->getPermissions();
|
||||
// part files and the mount point always have delete permissions
|
||||
if ($target === '' || pathinfo($target, PATHINFO_EXTENSION) === 'part') {
|
||||
$permissions |= \OCP\Constants::PERMISSION_DELETE;
|
||||
|
@ -260,30 +261,18 @@ class Shared extends \OC\Files\Storage\Wrapper\Jail implements ISharedStorage {
|
|||
* @return string
|
||||
*/
|
||||
public function getMountPoint() {
|
||||
return $this->newShare->getTarget();
|
||||
return $this->superShare->getTarget();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $path
|
||||
*/
|
||||
public function setMountPoint($path) {
|
||||
$this->newShare->setTarget($path);
|
||||
}
|
||||
$this->superShare->setTarget($path);
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getShareType() {
|
||||
return $this->newShare->getShareType();
|
||||
}
|
||||
|
||||
/**
|
||||
* get share ID
|
||||
*
|
||||
* @return integer unique share ID
|
||||
*/
|
||||
public function getShareId() {
|
||||
return $this->newShare->getId();
|
||||
foreach ($this->groupedShares as $share) {
|
||||
$share->setTarget($path);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -292,14 +281,14 @@ class Shared extends \OC\Files\Storage\Wrapper\Jail implements ISharedStorage {
|
|||
* @return string
|
||||
*/
|
||||
public function getSharedFrom() {
|
||||
return $this->newShare->getShareOwner();
|
||||
return $this->superShare->getShareOwner();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \OCP\Share\IShare
|
||||
*/
|
||||
public function getShare() {
|
||||
return $this->newShare;
|
||||
return $this->superShare;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -308,7 +297,7 @@ class Shared extends \OC\Files\Storage\Wrapper\Jail implements ISharedStorage {
|
|||
* @return string
|
||||
*/
|
||||
public function getItemType() {
|
||||
return $this->newShare->getNodeType();
|
||||
return $this->superShare->getNodeType();
|
||||
}
|
||||
|
||||
public function getCache($path = '', $storage = null) {
|
||||
|
@ -337,7 +326,7 @@ class Shared extends \OC\Files\Storage\Wrapper\Jail implements ISharedStorage {
|
|||
}
|
||||
|
||||
public function getOwner($path) {
|
||||
return $this->newShare->getShareOwner();
|
||||
return $this->superShare->getShareOwner();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -346,7 +335,9 @@ class Shared extends \OC\Files\Storage\Wrapper\Jail implements ISharedStorage {
|
|||
* @return bool
|
||||
*/
|
||||
public function unshareStorage() {
|
||||
\OC::$server->getShareManager()->deleteFromSelf($this->newShare, $this->user);
|
||||
foreach ($this->groupedShares as $share) {
|
||||
\OC::$server->getShareManager()->deleteFromSelf($share, $this->user);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -362,7 +353,7 @@ class Shared extends \OC\Files\Storage\Wrapper\Jail implements ISharedStorage {
|
|||
$targetStorage->acquireLock($targetInternalPath, $type, $provider);
|
||||
// lock the parent folders of the owner when locking the share as recipient
|
||||
if ($path === '') {
|
||||
$sourcePath = $this->ownerView->getPath($this->newShare->getNodeId());
|
||||
$sourcePath = $this->ownerView->getPath($this->superShare->getNodeId());
|
||||
$this->ownerView->lockFile(dirname($sourcePath), ILockingProvider::LOCK_SHARED, true);
|
||||
}
|
||||
}
|
||||
|
@ -378,7 +369,7 @@ class Shared extends \OC\Files\Storage\Wrapper\Jail implements ISharedStorage {
|
|||
$targetStorage->releaseLock($targetInternalPath, $type, $provider);
|
||||
// unlock the parent folders of the owner when unlocking the share as recipient
|
||||
if ($path === '') {
|
||||
$sourcePath = $this->ownerView->getPath($this->newShare->getNodeId());
|
||||
$sourcePath = $this->ownerView->getPath($this->superShare->getNodeId());
|
||||
$this->ownerView->unlockFile(dirname($sourcePath), ILockingProvider::LOCK_SHARED, true);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -68,51 +68,224 @@ class MountProviderTest extends \Test\TestCase {
|
|||
$this->provider = new MountProvider($this->config, $this->shareManager, $this->logger);
|
||||
}
|
||||
|
||||
private function makeMockShare($id, $nodeId, $owner = 'user2', $target = null, $permissions = 31) {
|
||||
$share = $this->getMock('\OCP\Share\IShare');
|
||||
$share->expects($this->any())
|
||||
->method('getPermissions')
|
||||
->will($this->returnValue($permissions));
|
||||
$share->expects($this->any())
|
||||
->method('getShareOwner')
|
||||
->will($this->returnValue($owner));
|
||||
$share->expects($this->any())
|
||||
->method('getTarget')
|
||||
->will($this->returnValue($target));
|
||||
$share->expects($this->any())
|
||||
->method('getId')
|
||||
->will($this->returnValue($id));
|
||||
$share->expects($this->any())
|
||||
->method('getNodeId')
|
||||
->will($this->returnValue($nodeId));
|
||||
$share->expects($this->any())
|
||||
->method('getShareTime')
|
||||
->will($this->returnValue(
|
||||
// compute share time based on id, simulating share order
|
||||
new \DateTime('@' . (1469193980 + 1000 * $id))
|
||||
));
|
||||
return $share;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests excluding shares from the current view. This includes:
|
||||
* - shares that were opted out of (permissions === 0)
|
||||
* - shares with a group in which the owner is already in
|
||||
*/
|
||||
public function testExcludeShares() {
|
||||
/** @var IShare | \PHPUnit_Framework_MockObject_MockObject $share1 */
|
||||
$share1 = $this->getMockBuilder('\OCP\Share\IShare')->getMock();
|
||||
$share1->expects($this->once())
|
||||
->method('getPermissions')
|
||||
->will($this->returnValue(0));
|
||||
|
||||
$share2 = $this->getMockBuilder('\OCP\Share\IShare')->getMock();
|
||||
$share2->expects($this->once())
|
||||
->method('getPermissions')
|
||||
->will($this->returnValue(31));
|
||||
$share2->expects($this->any())
|
||||
->method('getShareOwner')
|
||||
->will($this->returnValue('user2'));
|
||||
$share2->expects($this->any())
|
||||
->method('getTarget')
|
||||
->will($this->returnValue('/share2'));
|
||||
|
||||
$share3 = $this->getMockBuilder('\OCP\Share\IShare')->getMock();
|
||||
$share3->expects($this->once())
|
||||
->method('getPermissions')
|
||||
->will($this->returnValue(0));
|
||||
|
||||
/** @var IShare | \PHPUnit_Framework_MockObject_MockObject $share4 */
|
||||
$share4 = $this->getMockBuilder('\OCP\Share\IShare')->getMock();
|
||||
$share4->expects($this->once())
|
||||
->method('getPermissions')
|
||||
->will($this->returnValue(31));
|
||||
$share4->expects($this->any())
|
||||
->method('getShareOwner')
|
||||
->will($this->returnValue('user2'));
|
||||
$share4->expects($this->any())
|
||||
->method('getTarget')
|
||||
->will($this->returnValue('/share4'));
|
||||
|
||||
$share5 = $this->getMockBuilder('\OCP\Share\IShare')->getMock();
|
||||
$share5->expects($this->once())
|
||||
->method('getPermissions')
|
||||
->will($this->returnValue(31));
|
||||
$share5->expects($this->any())
|
||||
->method('getShareOwner')
|
||||
$rootFolder = $this->getMock('\OCP\Files\IRootFolder');
|
||||
$userManager = $this->getMock('\OCP\IUserManager');
|
||||
$userShares = [
|
||||
$this->makeMockShare(1, 100, 'user2', '/share2', 0),
|
||||
$this->makeMockShare(2, 100, 'user2', '/share2', 31),
|
||||
];
|
||||
$groupShares = [
|
||||
$this->makeMockShare(3, 100, 'user2', '/share2', 0),
|
||||
$this->makeMockShare(4, 101, 'user2', '/share4', 31),
|
||||
$this->makeMockShare(5, 100, 'user1', '/share4', 31),
|
||||
];
|
||||
$this->user->expects($this->any())
|
||||
->method('getUID')
|
||||
->will($this->returnValue('user1'));
|
||||
$this->shareManager->expects($this->at(0))
|
||||
->method('getSharedWith')
|
||||
->with('user1', \OCP\Share::SHARE_TYPE_USER)
|
||||
->will($this->returnValue($userShares));
|
||||
$this->shareManager->expects($this->at(1))
|
||||
->method('getSharedWith')
|
||||
->with('user1', \OCP\Share::SHARE_TYPE_GROUP, null, -1)
|
||||
->will($this->returnValue($groupShares));
|
||||
$this->shareManager->expects($this->any())
|
||||
->method('newShare')
|
||||
->will($this->returnCallback(function() use ($rootFolder, $userManager) {
|
||||
return new \OC\Share20\Share($rootFolder, $userManager);
|
||||
}));
|
||||
$mounts = $this->provider->getMountsForUser($this->user, $this->loader);
|
||||
$this->assertCount(2, $mounts);
|
||||
$this->assertInstanceOf('OCA\Files_Sharing\SharedMount', $mounts[0]);
|
||||
$this->assertInstanceOf('OCA\Files_Sharing\SharedMount', $mounts[1]);
|
||||
$mountedShare1 = $mounts[0]->getShare();
|
||||
$this->assertEquals('2', $mountedShare1->getId());
|
||||
$this->assertEquals('user2', $mountedShare1->getShareOwner());
|
||||
$this->assertEquals(100, $mountedShare1->getNodeId());
|
||||
$this->assertEquals('/share2', $mountedShare1->getTarget());
|
||||
$this->assertEquals(31, $mountedShare1->getPermissions());
|
||||
$mountedShare2 = $mounts[1]->getShare();
|
||||
$this->assertEquals('4', $mountedShare2->getId());
|
||||
$this->assertEquals('user2', $mountedShare2->getShareOwner());
|
||||
$this->assertEquals(101, $mountedShare2->getNodeId());
|
||||
$this->assertEquals('/share4', $mountedShare2->getTarget());
|
||||
$this->assertEquals(31, $mountedShare2->getPermissions());
|
||||
}
|
||||
|
||||
$userShares = [$share1, $share2];
|
||||
$groupShares = [$share3, $share4, $share5];
|
||||
public function mergeSharesDataProvider() {
|
||||
// note: the user in the specs here is the shareOwner not recipient
|
||||
// the recipient is always "user1"
|
||||
return [
|
||||
// #0: share as outsider with "group1" and "user1" with same permissions
|
||||
[
|
||||
[
|
||||
[1, 100, 'user2', '/share2', 31],
|
||||
],
|
||||
[
|
||||
[2, 100, 'user2', '/share2', 31],
|
||||
],
|
||||
[
|
||||
// combined, user share has higher priority
|
||||
['1', 100, 'user2', '/share2', 31],
|
||||
],
|
||||
],
|
||||
// #1: share as outsider with "group1" and "user1" with different permissions
|
||||
[
|
||||
[
|
||||
[1, 100, 'user2', '/share', 31],
|
||||
],
|
||||
[
|
||||
[2, 100, 'user2', '/share', 15],
|
||||
],
|
||||
[
|
||||
// use highest permissions
|
||||
['1', 100, 'user2', '/share', 31],
|
||||
],
|
||||
],
|
||||
// #2: share as outsider with "group1" and "group2" with same permissions
|
||||
[
|
||||
[
|
||||
],
|
||||
[
|
||||
[1, 100, 'user2', '/share', 31],
|
||||
[2, 100, 'user2', '/share', 31],
|
||||
],
|
||||
[
|
||||
// combined, first group share has higher priority
|
||||
['1', 100, 'user2', '/share', 31],
|
||||
],
|
||||
],
|
||||
// #3: share as outsider with "group1" and "group2" with different permissions
|
||||
[
|
||||
[
|
||||
],
|
||||
[
|
||||
[1, 100, 'user2', '/share', 31],
|
||||
[2, 100, 'user2', '/share', 15],
|
||||
],
|
||||
[
|
||||
// use higher permissions
|
||||
['1', 100, 'user2', '/share', 31],
|
||||
],
|
||||
],
|
||||
// #4: share as insider with "group1"
|
||||
[
|
||||
[
|
||||
],
|
||||
[
|
||||
[1, 100, 'user1', '/share', 31],
|
||||
],
|
||||
[
|
||||
// no received share since "user1" is the sharer/owner
|
||||
],
|
||||
],
|
||||
// #5: share as insider with "group1" and "group2" with different permissions
|
||||
[
|
||||
[
|
||||
],
|
||||
[
|
||||
[1, 100, 'user1', '/share', 31],
|
||||
[2, 100, 'user1', '/share', 15],
|
||||
],
|
||||
[
|
||||
// no received share since "user1" is the sharer/owner
|
||||
],
|
||||
],
|
||||
// #6: share as outside with "group1", recipient opted out
|
||||
[
|
||||
[
|
||||
],
|
||||
[
|
||||
[1, 100, 'user2', '/share', 0],
|
||||
],
|
||||
[
|
||||
// no received share since "user1" opted out
|
||||
],
|
||||
],
|
||||
// #7: share as outsider with "group1" and "user1" where recipient renamed in between
|
||||
[
|
||||
[
|
||||
[1, 100, 'user2', '/share2-renamed', 31],
|
||||
],
|
||||
[
|
||||
[2, 100, 'user2', '/share2', 31],
|
||||
],
|
||||
[
|
||||
// use target of least recent share
|
||||
['1', 100, 'user2', '/share2-renamed', 31],
|
||||
],
|
||||
],
|
||||
// #8: share as outsider with "group1" and "user1" where recipient renamed in between
|
||||
[
|
||||
[
|
||||
[2, 100, 'user2', '/share2', 31],
|
||||
],
|
||||
[
|
||||
[1, 100, 'user2', '/share2-renamed', 31],
|
||||
],
|
||||
[
|
||||
// use target of least recent share
|
||||
['1', 100, 'user2', '/share2-renamed', 31],
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests merging shares.
|
||||
*
|
||||
* Happens when sharing the same entry to a user through multiple ways,
|
||||
* like several groups and also direct shares at the same time.
|
||||
*
|
||||
* @dataProvider mergeSharesDataProvider
|
||||
*
|
||||
* @param array $userShares array of user share specs
|
||||
* @param array $groupShares array of group share specs
|
||||
* @param array $expectedShares array of expected supershare specs
|
||||
*/
|
||||
public function testMergeShares($userShares, $groupShares, $expectedShares) {
|
||||
$rootFolder = $this->getMock('\OCP\Files\IRootFolder');
|
||||
$userManager = $this->getMock('\OCP\IUserManager');
|
||||
|
||||
$userShares = array_map(function($shareSpec) {
|
||||
return $this->makeMockShare($shareSpec[0], $shareSpec[1], $shareSpec[2], $shareSpec[3], $shareSpec[4]);
|
||||
}, $userShares);
|
||||
$groupShares = array_map(function($shareSpec) {
|
||||
return $this->makeMockShare($shareSpec[0], $shareSpec[1], $shareSpec[2], $shareSpec[3], $shareSpec[4]);
|
||||
}, $groupShares);
|
||||
|
||||
$this->user->expects($this->any())
|
||||
->method('getUID')
|
||||
|
@ -126,17 +299,29 @@ class MountProviderTest extends \Test\TestCase {
|
|||
->method('getSharedWith')
|
||||
->with('user1', \OCP\Share::SHARE_TYPE_GROUP, null, -1)
|
||||
->will($this->returnValue($groupShares));
|
||||
$this->shareManager->expects($this->any())
|
||||
->method('newShare')
|
||||
->will($this->returnCallback(function() use ($rootFolder, $userManager) {
|
||||
return new \OC\Share20\Share($rootFolder, $userManager);
|
||||
}));
|
||||
|
||||
$mounts = $this->provider->getMountsForUser($this->user, $this->loader);
|
||||
|
||||
$this->assertCount(2, $mounts);
|
||||
$this->assertSharedMount($share1, $mounts[0]);
|
||||
$this->assertSharedMount($share4, $mounts[1]);
|
||||
}
|
||||
$this->assertCount(count($expectedShares), $mounts);
|
||||
|
||||
private function assertSharedMount(IShare $share, IMountPoint $mount) {
|
||||
$this->assertInstanceOf('OCA\Files_Sharing\SharedMount', $mount);
|
||||
$this->assertEquals($share, $mount->getShare());
|
||||
foreach ($mounts as $index => $mount) {
|
||||
$expectedShare = $expectedShares[$index];
|
||||
$this->assertInstanceOf('OCA\Files_Sharing\SharedMount', $mount);
|
||||
|
||||
// supershare
|
||||
$share = $mount->getShare();
|
||||
|
||||
$this->assertEquals($expectedShare[0], $share->getId());
|
||||
$this->assertEquals($expectedShare[1], $share->getNodeId());
|
||||
$this->assertEquals($expectedShare[2], $share->getShareOwner());
|
||||
$this->assertEquals($expectedShare[3], $share->getTarget());
|
||||
$this->assertEquals($expectedShare[4], $share->getPermissions());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -309,10 +309,10 @@ trait Sharing {
|
|||
PHPUnit_Framework_Assert::assertEquals(False, $this->isFieldInResponse('share_with', "$user"));
|
||||
}
|
||||
|
||||
public function isUserOrGroupInSharedData($userOrGroup){
|
||||
public function isUserOrGroupInSharedData($userOrGroup, $permissions = null){
|
||||
$data = $this->response->xml()->data[0];
|
||||
foreach($data as $element) {
|
||||
if ($element->share_with == $userOrGroup){
|
||||
if ($element->share_with == $userOrGroup && ($permissions === null || $permissions == $element->permissions)){
|
||||
return True;
|
||||
}
|
||||
}
|
||||
|
@ -320,13 +320,13 @@ trait Sharing {
|
|||
}
|
||||
|
||||
/**
|
||||
* @Given /^file "([^"]*)" of user "([^"]*)" is shared with user "([^"]*)"$/
|
||||
* @Given /^(file|folder|entry) "([^"]*)" of user "([^"]*)" is shared with user "([^"]*)"( with permissions ([\d]*))?$/
|
||||
*
|
||||
* @param string $filepath
|
||||
* @param string $user1
|
||||
* @param string $user2
|
||||
*/
|
||||
public function assureFileIsShared($filepath, $user1, $user2){
|
||||
public function assureFileIsShared($entry, $filepath, $user1, $user2, $withPerms = null, $permissions = null){
|
||||
$fullUrl = $this->baseUrl . "v{$this->apiVersion}.php/apps/files_sharing/api/v{$this->sharingApiVersion}/shares" . "?path=$filepath";
|
||||
$client = new Client();
|
||||
$options = [];
|
||||
|
@ -336,23 +336,23 @@ trait Sharing {
|
|||
$options['auth'] = [$user1, $this->regularUser];
|
||||
}
|
||||
$this->response = $client->get($fullUrl, $options);
|
||||
if ($this->isUserOrGroupInSharedData($user2)){
|
||||
if ($this->isUserOrGroupInSharedData($user2, $permissions)){
|
||||
return;
|
||||
} else {
|
||||
$this->createShare($user1, $filepath, 0, $user2, null, null, null);
|
||||
$this->createShare($user1, $filepath, 0, $user2, null, null, $permissions);
|
||||
}
|
||||
$this->response = $client->get($fullUrl, $options);
|
||||
PHPUnit_Framework_Assert::assertEquals(True, $this->isUserOrGroupInSharedData($user2));
|
||||
PHPUnit_Framework_Assert::assertEquals(True, $this->isUserOrGroupInSharedData($user2, $permissions));
|
||||
}
|
||||
|
||||
/**
|
||||
* @Given /^file "([^"]*)" of user "([^"]*)" is shared with group "([^"]*)"$/
|
||||
* @Given /^(file|folder|entry) "([^"]*)" of user "([^"]*)" is shared with group "([^"]*)"( with permissions ([\d]*))?$/
|
||||
*
|
||||
* @param string $filepath
|
||||
* @param string $user
|
||||
* @param string $group
|
||||
*/
|
||||
public function assureFileIsSharedWithGroup($filepath, $user, $group){
|
||||
public function assureFileIsSharedWithGroup($entry, $filepath, $user, $group, $withPerms = null, $permissions = null){
|
||||
$fullUrl = $this->baseUrl . "v{$this->apiVersion}.php/apps/files_sharing/api/v{$this->sharingApiVersion}/shares" . "?path=$filepath";
|
||||
$client = new Client();
|
||||
$options = [];
|
||||
|
@ -362,13 +362,13 @@ trait Sharing {
|
|||
$options['auth'] = [$user, $this->regularUser];
|
||||
}
|
||||
$this->response = $client->get($fullUrl, $options);
|
||||
if ($this->isUserOrGroupInSharedData($group)){
|
||||
if ($this->isUserOrGroupInSharedData($group, $permissions)){
|
||||
return;
|
||||
} else {
|
||||
$this->createShare($user, $filepath, 1, $group, null, null, null);
|
||||
$this->createShare($user, $filepath, 1, $group, null, null, $permissions);
|
||||
}
|
||||
$this->response = $client->get($fullUrl, $options);
|
||||
PHPUnit_Framework_Assert::assertEquals(True, $this->isUserOrGroupInSharedData($group));
|
||||
PHPUnit_Framework_Assert::assertEquals(True, $this->isUserOrGroupInSharedData($group, $permissions));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -87,12 +87,12 @@ trait WebDav {
|
|||
}
|
||||
|
||||
/**
|
||||
* @Given /^User "([^"]*)" moved file "([^"]*)" to "([^"]*)"$/
|
||||
* @Given /^User "([^"]*)" moved (file|folder|entry) "([^"]*)" to "([^"]*)"$/
|
||||
* @param string $user
|
||||
* @param string $fileSource
|
||||
* @param string $fileDestination
|
||||
*/
|
||||
public function userMovedFile($user, $fileSource, $fileDestination){
|
||||
public function userMovedFile($user, $entry, $fileSource, $fileDestination){
|
||||
$fullUrl = substr($this->baseUrl, 0, -4) . $this->davPath;
|
||||
$headers['Destination'] = $fullUrl . $fileDestination;
|
||||
$this->response = $this->makeDavRequest($user, "MOVE", $fileSource, $headers);
|
||||
|
@ -100,12 +100,12 @@ trait WebDav {
|
|||
}
|
||||
|
||||
/**
|
||||
* @When /^User "([^"]*)" moves file "([^"]*)" to "([^"]*)"$/
|
||||
* @When /^User "([^"]*)" moves (file|folder|entry) "([^"]*)" to "([^"]*)"$/
|
||||
* @param string $user
|
||||
* @param string $fileSource
|
||||
* @param string $fileDestination
|
||||
*/
|
||||
public function userMovesFile($user, $fileSource, $fileDestination){
|
||||
public function userMovesFile($user, $entry, $fileSource, $fileDestination){
|
||||
$fullUrl = substr($this->baseUrl, 0, -4) . $this->davPath;
|
||||
$headers['Destination'] = $fullUrl . $fileDestination;
|
||||
$this->response = $this->makeDavRequest($user, "MOVE", $fileSource, $headers);
|
||||
|
@ -259,6 +259,32 @@ trait WebDav {
|
|||
$this->response = $this->listFolder($user, $path, 0, $properties);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then /^as "([^"]*)" the (file|folder|entry) "([^"]*)" does not exist$/
|
||||
* @param string $user
|
||||
* @param string $path
|
||||
* @param \Behat\Gherkin\Node\TableNode|null $propertiesTable
|
||||
*/
|
||||
public function asTheFileOrFolderDoesNotExist($user, $entry, $path) {
|
||||
$client = $this->getSabreClient($user);
|
||||
$response = $client->request('HEAD', $this->makeSabrePath($path));
|
||||
if ($response['statusCode'] !== 404) {
|
||||
throw new \Exception($entry . ' "' . $path . '" expected to not exist (status code ' . $response['statusCode'] . ', expected 404)');
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then /^as "([^"]*)" the (file|folder|entry) "([^"]*)" exists$/
|
||||
* @param string $user
|
||||
* @param string $path
|
||||
* @param \Behat\Gherkin\Node\TableNode|null $propertiesTable
|
||||
*/
|
||||
public function asTheFileOrFolderExists($user, $entry, $path) {
|
||||
$this->response = $this->listFolder($user, $path, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then the single response should contain a property :key with value :value
|
||||
* @param string $key
|
||||
|
@ -327,9 +353,25 @@ trait WebDav {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
/*Returns the elements of a propfind, $folderDepth requires 1 to see elements without children*/
|
||||
public function listFolder($user, $path, $folderDepth, $properties = null){
|
||||
$client = $this->getSabreClient($user);
|
||||
if (!$properties) {
|
||||
$properties = [
|
||||
'{DAV:}getetag'
|
||||
];
|
||||
}
|
||||
|
||||
$response = $client->propfind($this->makeSabrePath($path), $properties, $folderDepth);
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
public function makeSabrePath($path) {
|
||||
return $this->encodePath($this->davPath . '/' . ltrim($path, '/'));
|
||||
}
|
||||
|
||||
public function getSabreClient($user) {
|
||||
$fullUrl = substr($this->baseUrl, 0, -4);
|
||||
|
||||
$settings = array(
|
||||
|
@ -343,17 +385,7 @@ trait WebDav {
|
|||
$settings['password'] = $this->regularUser;
|
||||
}
|
||||
|
||||
$client = new SClient($settings);
|
||||
|
||||
if (!$properties) {
|
||||
$properties = [
|
||||
'{DAV:}getetag'
|
||||
];
|
||||
}
|
||||
|
||||
$response = $client->propfind($this->davPath . '/' . ltrim($path, '/'), $properties, $folderDepth);
|
||||
|
||||
return $response;
|
||||
return new SClient($settings);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -493,6 +525,17 @@ trait WebDav {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* URL encodes the given path but keeps the slashes
|
||||
*
|
||||
* @param string $path to encode
|
||||
* @return string encoded path
|
||||
*/
|
||||
private function encodePath($path) {
|
||||
// slashes need to stay
|
||||
return str_replace('%2F', '/', rawurlencode($path));
|
||||
}
|
||||
|
||||
/**
|
||||
* @When user :user favorites element :path
|
||||
*/
|
||||
|
|
|
@ -775,3 +775,146 @@ Feature: sharing
|
|||
And Deleting last share
|
||||
Then the OCS status code should be "404"
|
||||
And the HTTP status code should be "200"
|
||||
|
||||
Scenario: Merging shares for recipient when shared from outside with group and member
|
||||
Given As an "admin"
|
||||
And user "user0" exists
|
||||
And user "user1" exists
|
||||
And group "group1" exists
|
||||
And user "user1" belongs to group "group1"
|
||||
And user "user0" created a folder "merge-test-outside"
|
||||
When folder "merge-test-outside" of user "user0" is shared with group "group1"
|
||||
And folder "merge-test-outside" of user "user0" is shared with user "user1"
|
||||
Then as "user1" the folder "merge-test-outside" exists
|
||||
And as "user1" the folder "merge-test-outside (2)" does not exist
|
||||
|
||||
Scenario: Merging shares for recipient when shared from outside with group and member with different permissions
|
||||
Given As an "admin"
|
||||
And user "user0" exists
|
||||
And user "user1" exists
|
||||
And group "group1" exists
|
||||
And user "user1" belongs to group "group1"
|
||||
And user "user0" created a folder "merge-test-outside-perms"
|
||||
When folder "merge-test-outside-perms" of user "user0" is shared with group "group1" with permissions 1
|
||||
And folder "merge-test-outside-perms" of user "user0" is shared with user "user1" with permissions 31
|
||||
Then as "user1" gets properties of folder "merge-test-outside-perms" with
|
||||
|{http://owncloud.org/ns}permissions|
|
||||
And the single response should contain a property "{http://owncloud.org/ns}permissions" with value "SRDNVCK"
|
||||
And as "user1" the folder "merge-test-outside-perms (2)" does not exist
|
||||
|
||||
Scenario: Merging shares for recipient when shared from outside with two groups
|
||||
Given As an "admin"
|
||||
And user "user0" exists
|
||||
And user "user1" exists
|
||||
And group "group1" exists
|
||||
And group "group2" exists
|
||||
And user "user1" belongs to group "group1"
|
||||
And user "user1" belongs to group "group2"
|
||||
And user "user0" created a folder "merge-test-outside-twogroups"
|
||||
When folder "merge-test-outside-twogroups" of user "user0" is shared with group "group1"
|
||||
And folder "merge-test-outside-twogroups" of user "user0" is shared with group "group2"
|
||||
Then as "user1" the folder "merge-test-outside-twogroups" exists
|
||||
And as "user1" the folder "merge-test-outside-twogroups (2)" does not exist
|
||||
|
||||
Scenario: Merging shares for recipient when shared from outside with two groups with different permissions
|
||||
Given As an "admin"
|
||||
And user "user0" exists
|
||||
And user "user1" exists
|
||||
And group "group1" exists
|
||||
And group "group2" exists
|
||||
And user "user1" belongs to group "group1"
|
||||
And user "user1" belongs to group "group2"
|
||||
And user "user0" created a folder "merge-test-outside-twogroups-perms"
|
||||
When folder "merge-test-outside-twogroups-perms" of user "user0" is shared with group "group1" with permissions 1
|
||||
And folder "merge-test-outside-twogroups-perms" of user "user0" is shared with group "group2" with permissions 31
|
||||
Then as "user1" gets properties of folder "merge-test-outside-twogroups-perms" with
|
||||
|{http://owncloud.org/ns}permissions|
|
||||
And the single response should contain a property "{http://owncloud.org/ns}permissions" with value "SRDNVCK"
|
||||
And as "user1" the folder "merge-test-outside-twogroups-perms (2)" does not exist
|
||||
|
||||
Scenario: Merging shares for recipient when shared from outside with two groups and member
|
||||
Given As an "admin"
|
||||
And user "user0" exists
|
||||
And user "user1" exists
|
||||
And group "group1" exists
|
||||
And group "group2" exists
|
||||
And user "user1" belongs to group "group1"
|
||||
And user "user1" belongs to group "group2"
|
||||
And user "user0" created a folder "merge-test-outside-twogroups-member-perms"
|
||||
When folder "merge-test-outside-twogroups-member-perms" of user "user0" is shared with group "group1" with permissions 1
|
||||
And folder "merge-test-outside-twogroups-member-perms" of user "user0" is shared with group "group2" with permissions 31
|
||||
And folder "merge-test-outside-twogroups-member-perms" of user "user0" is shared with user "user1" with permissions 1
|
||||
Then as "user1" gets properties of folder "merge-test-outside-twogroups-member-perms" with
|
||||
|{http://owncloud.org/ns}permissions|
|
||||
And the single response should contain a property "{http://owncloud.org/ns}permissions" with value "SRDNVCK"
|
||||
And as "user1" the folder "merge-test-outside-twogroups-member-perms (2)" does not exist
|
||||
|
||||
Scenario: Merging shares for recipient when shared from inside with group
|
||||
Given As an "admin"
|
||||
And user "user0" exists
|
||||
And group "group1" exists
|
||||
And user "user0" belongs to group "group1"
|
||||
And user "user0" created a folder "merge-test-inside-group"
|
||||
When folder "/merge-test-inside-group" of user "user0" is shared with group "group1"
|
||||
Then as "user0" the folder "merge-test-inside-group" exists
|
||||
And as "user0" the folder "merge-test-inside-group (2)" does not exist
|
||||
|
||||
Scenario: Merging shares for recipient when shared from inside with two groups
|
||||
Given As an "admin"
|
||||
And user "user0" exists
|
||||
And group "group1" exists
|
||||
And group "group2" exists
|
||||
And user "user0" belongs to group "group1"
|
||||
And user "user0" belongs to group "group2"
|
||||
And user "user0" created a folder "merge-test-inside-twogroups"
|
||||
When folder "merge-test-inside-twogroups" of user "user0" is shared with group "group1"
|
||||
And folder "merge-test-inside-twogroups" of user "user0" is shared with group "group2"
|
||||
Then as "user0" the folder "merge-test-inside-twogroups" exists
|
||||
And as "user0" the folder "merge-test-inside-twogroups (2)" does not exist
|
||||
And as "user0" the folder "merge-test-inside-twogroups (3)" does not exist
|
||||
|
||||
Scenario: Merging shares for recipient when shared from inside with group with less permissions
|
||||
Given As an "admin"
|
||||
And user "user0" exists
|
||||
And group "group1" exists
|
||||
And group "group2" exists
|
||||
And user "user0" belongs to group "group1"
|
||||
And user "user0" belongs to group "group2"
|
||||
And user "user0" created a folder "merge-test-inside-twogroups-perms"
|
||||
When folder "merge-test-inside-twogroups-perms" of user "user0" is shared with group "group1"
|
||||
And folder "merge-test-inside-twogroups-perms" of user "user0" is shared with group "group2"
|
||||
Then as "user0" gets properties of folder "merge-test-inside-twogroups-perms" with
|
||||
|{http://owncloud.org/ns}permissions|
|
||||
And the single response should contain a property "{http://owncloud.org/ns}permissions" with value "RDNVCK"
|
||||
And as "user0" the folder "merge-test-inside-twogroups-perms (2)" does not exist
|
||||
And as "user0" the folder "merge-test-inside-twogroups-perms (3)" does not exist
|
||||
|
||||
Scenario: Merging shares for recipient when shared from outside with group then user and recipient renames in between
|
||||
Given As an "admin"
|
||||
And user "user0" exists
|
||||
And user "user1" exists
|
||||
And group "group1" exists
|
||||
And user "user1" belongs to group "group1"
|
||||
And user "user0" created a folder "merge-test-outside-groups-renamebeforesecondshare"
|
||||
When folder "merge-test-outside-groups-renamebeforesecondshare" of user "user0" is shared with group "group1"
|
||||
And User "user1" moved folder "/merge-test-outside-groups-renamebeforesecondshare" to "/merge-test-outside-groups-renamebeforesecondshare-renamed"
|
||||
And folder "merge-test-outside-groups-renamebeforesecondshare" of user "user0" is shared with user "user1"
|
||||
Then as "user1" gets properties of folder "merge-test-outside-groups-renamebeforesecondshare-renamed" with
|
||||
|{http://owncloud.org/ns}permissions|
|
||||
And the single response should contain a property "{http://owncloud.org/ns}permissions" with value "SRDNVCK"
|
||||
And as "user1" the folder "merge-test-outside-groups-renamebeforesecondshare" does not exist
|
||||
|
||||
# Scenario: Merging shares for recipient when shared from outside with user then group and recipient renames in between
|
||||
# Given As an "admin"
|
||||
# And user "user0" exists
|
||||
# And user "user1" exists
|
||||
# And group "group1" exists
|
||||
# And user "user1" belongs to group "group1"
|
||||
# And user "user0" created a folder "merge-test-outside-groups-renamebeforesecondshare"
|
||||
# When folder "merge-test-outside-groups-renamebeforesecondshare" of user "user0" is shared with user "user1"
|
||||
# And User "user1" moved folder "/merge-test-outside-groups-renamebeforesecondshare" to "/merge-test-outside-groups-renamebeforesecondshare-renamed"
|
||||
# And folder "merge-test-outside-groups-renamebeforesecondshare" of user "user0" is shared with group "group1"
|
||||
# Then as "user1" gets properties of folder "merge-test-outside-groups-renamebeforesecondshare-renamed" with
|
||||
# |{http://owncloud.org/ns}permissions|
|
||||
# And the single response should contain a property "{http://owncloud.org/ns}permissions" with value "SRDNVCK"
|
||||
# And as "user1" the folder "merge-test-outside-groups-renamebeforesecondshare" does not exist
|
||||
|
|
|
@ -598,6 +598,33 @@
|
|||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Group reshares into a single super share element.
|
||||
* Does this by finding the most precise share and
|
||||
* combines the permissions to be the most permissive.
|
||||
*
|
||||
* @param {Array} reshares
|
||||
* @return {Object} reshare
|
||||
*/
|
||||
_groupReshares: function(reshares) {
|
||||
if (!reshares || !reshares.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var superShare = reshares.shift();
|
||||
var combinedPermissions = superShare.permissions;
|
||||
_.each(reshares, function(reshare) {
|
||||
// use share have higher priority than group share
|
||||
if (reshare.share_type === OC.Share.SHARE_TYPE_USER && superShare.share_type === OC.Share.SHARE_TYPE_GROUP) {
|
||||
superShare = reshare;
|
||||
}
|
||||
combinedPermissions |= reshare.permissions;
|
||||
});
|
||||
|
||||
superShare.permissions = combinedPermissions;
|
||||
return superShare;
|
||||
},
|
||||
|
||||
fetch: function() {
|
||||
var model = this;
|
||||
this.trigger('request', this);
|
||||
|
@ -615,7 +642,7 @@
|
|||
|
||||
var reshare = false;
|
||||
if (data2[0].ocs.data.length) {
|
||||
reshare = data2[0].ocs.data[0];
|
||||
reshare = model._groupReshares(data2[0].ocs.data);
|
||||
}
|
||||
|
||||
model.set(model.parse({
|
||||
|
|
|
@ -181,6 +181,48 @@ describe('OC.Share.ShareItemModel', function() {
|
|||
|
||||
// TODO: check more attributes
|
||||
});
|
||||
it('groups reshare info into a single item', function() {
|
||||
/* jshint camelcase: false */
|
||||
fetchReshareDeferred.resolve(makeOcsResponse([
|
||||
{
|
||||
id: '1',
|
||||
share_type: OC.Share.SHARE_TYPE_USER,
|
||||
uid_owner: 'owner',
|
||||
displayname_owner: 'Owner',
|
||||
share_with: 'root',
|
||||
permissions: 1
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
share_type: OC.Share.SHARE_TYPE_GROUP,
|
||||
uid_owner: 'owner',
|
||||
displayname_owner: 'Owner',
|
||||
share_with: 'group1',
|
||||
permissions: 15
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
share_type: OC.Share.SHARE_TYPE_GROUP,
|
||||
uid_owner: 'owner',
|
||||
displayname_owner: 'Owner',
|
||||
share_with: 'group1',
|
||||
permissions: 17
|
||||
}
|
||||
]));
|
||||
fetchSharesDeferred.resolve(makeOcsResponse([]));
|
||||
|
||||
OC.currentUser = 'root';
|
||||
|
||||
model.fetch();
|
||||
|
||||
var reshare = model.get('reshare');
|
||||
// max permissions
|
||||
expect(reshare.permissions).toEqual(31);
|
||||
// user share has higher priority
|
||||
expect(reshare.share_type).toEqual(OC.Share.SHARE_TYPE_USER);
|
||||
expect(reshare.share_with).toEqual('root');
|
||||
expect(reshare.id).toEqual('1');
|
||||
});
|
||||
it('does not parse link share when for a different file', function() {
|
||||
/* jshint camelcase: false */
|
||||
fetchReshareDeferred.resolve(makeOcsResponse([]));
|
||||
|
|
|
@ -49,6 +49,7 @@ use OC\Repair\RepairMimeTypes;
|
|||
use OC\Repair\SearchLuceneTables;
|
||||
use OC\Repair\UpdateOutdatedOcsIds;
|
||||
use OC\Repair\RepairInvalidShares;
|
||||
use OC\Repair\RepairUnmergedShares;
|
||||
use OCP\AppFramework\QueryException;
|
||||
use OCP\Migration\IOutput;
|
||||
use OCP\Migration\IRepairStep;
|
||||
|
@ -140,6 +141,12 @@ class Repair implements IOutput{
|
|||
new RemoveOldShares(\OC::$server->getDatabaseConnection()),
|
||||
new AvatarPermissions(\OC::$server->getDatabaseConnection()),
|
||||
new RemoveRootShares(\OC::$server->getDatabaseConnection(), \OC::$server->getUserManager(), \OC::$server->getLazyRootFolder()),
|
||||
new RepairUnmergedShares(
|
||||
\OC::$server->getConfig(),
|
||||
\OC::$server->getDatabaseConnection(),
|
||||
\OC::$server->getUserManager(),
|
||||
\OC::$server->getGroupManager()
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,328 @@
|
|||
<?php
|
||||
/**
|
||||
* @author Vincent Petry <pvince81@owncloud.com>
|
||||
*
|
||||
* @copyright Copyright (c) 2016, ownCloud, Inc.
|
||||
* @license AGPL-3.0
|
||||
*
|
||||
* This code is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License, version 3,
|
||||
* as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License, version 3,
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
*/
|
||||
|
||||
namespace OC\Repair;
|
||||
|
||||
use OCP\Migration\IOutput;
|
||||
use OCP\Migration\IRepairStep;
|
||||
use OC\Share\Constants;
|
||||
use OCP\DB\QueryBuilder\IQueryBuilder;
|
||||
use OCP\IConfig;
|
||||
use OCP\IDBConnection;
|
||||
use OCP\IUserManager;
|
||||
use OCP\IUser;
|
||||
use OCP\IGroupManager;
|
||||
use OC\Share20\DefaultShareProvider;
|
||||
|
||||
/**
|
||||
* Repairs shares for which the received folder was not properly deduplicated.
|
||||
*
|
||||
* An unmerged share can for example happen when sharing a folder with the same
|
||||
* user through multiple ways, like several groups and also directly, additionally
|
||||
* to group shares. Since 9.0.0 these would create duplicate entries "folder (2)",
|
||||
* one for every share. This repair step rearranges them so they only appear as a single
|
||||
* folder.
|
||||
*/
|
||||
class RepairUnmergedShares implements IRepairStep {
|
||||
|
||||
/** @var \OCP\IConfig */
|
||||
protected $config;
|
||||
|
||||
/** @var \OCP\IDBConnection */
|
||||
protected $connection;
|
||||
|
||||
/** @var IUserManager */
|
||||
protected $userManager;
|
||||
|
||||
/** @var IGroupManager */
|
||||
protected $groupManager;
|
||||
|
||||
/** @var IQueryBuilder */
|
||||
private $queryGetSharesWithUsers;
|
||||
|
||||
/** @var IQueryBuilder */
|
||||
private $queryUpdateSharePermissionsAndTarget;
|
||||
|
||||
/** @var IQueryBuilder */
|
||||
private $queryUpdateShareInBatch;
|
||||
|
||||
/**
|
||||
* @param \OCP\IConfig $config
|
||||
* @param \OCP\IDBConnection $connection
|
||||
*/
|
||||
public function __construct(
|
||||
IConfig $config,
|
||||
IDBConnection $connection,
|
||||
IUserManager $userManager,
|
||||
IGroupManager $groupManager
|
||||
) {
|
||||
$this->connection = $connection;
|
||||
$this->config = $config;
|
||||
$this->userManager = $userManager;
|
||||
$this->groupManager = $groupManager;
|
||||
}
|
||||
|
||||
public function getName() {
|
||||
return 'Repair unmerged shares';
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds prepared queries for reuse
|
||||
*/
|
||||
private function buildPreparedQueries() {
|
||||
/**
|
||||
* Retrieve shares for a given user/group and share type
|
||||
*/
|
||||
$query = $this->connection->getQueryBuilder();
|
||||
$query
|
||||
->select('item_source', 'id', 'file_target', 'permissions', 'parent', 'share_type')
|
||||
->from('share')
|
||||
->where($query->expr()->eq('share_type', $query->createParameter('shareType')))
|
||||
->andWhere($query->expr()->in('share_with', $query->createParameter('shareWiths')))
|
||||
->andWhere($query->expr()->in('item_type', $query->createParameter('itemTypes')))
|
||||
->orderBy('item_source', 'ASC')
|
||||
->addOrderBy('stime', 'ASC');
|
||||
|
||||
$this->queryGetSharesWithUsers = $query;
|
||||
|
||||
/**
|
||||
* Updates the file_target to the given value for all given share ids.
|
||||
*
|
||||
* This updates several shares in bulk which is faster than individually.
|
||||
*/
|
||||
$query = $this->connection->getQueryBuilder();
|
||||
$query->update('share')
|
||||
->set('file_target', $query->createParameter('file_target'))
|
||||
->where($query->expr()->in('id', $query->createParameter('ids')));
|
||||
|
||||
$this->queryUpdateShareInBatch = $query;
|
||||
|
||||
/**
|
||||
* Updates the share permissions and target path of a single share.
|
||||
*/
|
||||
$query = $this->connection->getQueryBuilder();
|
||||
$query->update('share')
|
||||
->set('permissions', $query->createParameter('permissions'))
|
||||
->set('file_target', $query->createParameter('file_target'))
|
||||
->where($query->expr()->eq('id', $query->createParameter('shareid')));
|
||||
|
||||
$this->queryUpdateSharePermissionsAndTarget = $query;
|
||||
|
||||
}
|
||||
|
||||
private function getSharesWithUser($shareType, $shareWiths) {
|
||||
$groupedShares = [];
|
||||
|
||||
$query = $this->queryGetSharesWithUsers;
|
||||
$query->setParameter('shareWiths', $shareWiths, IQueryBuilder::PARAM_STR_ARRAY);
|
||||
$query->setParameter('shareType', $shareType);
|
||||
$query->setParameter('itemTypes', ['file', 'folder'], IQueryBuilder::PARAM_STR_ARRAY);
|
||||
|
||||
$shares = $query->execute()->fetchAll();
|
||||
|
||||
// group by item_source
|
||||
foreach ($shares as $share) {
|
||||
if (!isset($groupedShares[$share['item_source']])) {
|
||||
$groupedShares[$share['item_source']] = [];
|
||||
}
|
||||
$groupedShares[$share['item_source']][] = $share;
|
||||
}
|
||||
return $groupedShares;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fix the given received share represented by the set of group shares
|
||||
* and matching sub shares
|
||||
*
|
||||
* @param array $groupShares group share entries
|
||||
* @param array $subShares sub share entries
|
||||
*
|
||||
* @return boolean false if the share was not repaired, true if it was
|
||||
*/
|
||||
private function fixThisShare($groupShares, $subShares) {
|
||||
if (empty($subShares)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$groupSharesById = [];
|
||||
foreach ($groupShares as $groupShare) {
|
||||
$groupSharesById[$groupShare['id']] = $groupShare;
|
||||
}
|
||||
|
||||
if ($this->isThisShareValid($groupSharesById, $subShares)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$targetPath = $groupShares[0]['file_target'];
|
||||
|
||||
// check whether the user opted out completely of all subshares
|
||||
$optedOut = true;
|
||||
foreach ($subShares as $subShare) {
|
||||
if ((int)$subShare['permissions'] !== 0) {
|
||||
$optedOut = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$shareIds = [];
|
||||
foreach ($subShares as $subShare) {
|
||||
// only if the user deleted some subshares but not all, adjust the permissions of that subshare
|
||||
if (!$optedOut && (int)$subShare['permissions'] === 0 && (int)$subShare['share_type'] === DefaultShareProvider::SHARE_TYPE_USERGROUP) {
|
||||
// set permissions from parent group share
|
||||
$permissions = $groupSharesById[$subShare['parent']]['permissions'];
|
||||
|
||||
// fix permissions and target directly
|
||||
$query = $this->queryUpdateSharePermissionsAndTarget;
|
||||
$query->setParameter('shareid', $subShare['id']);
|
||||
$query->setParameter('file_target', $targetPath);
|
||||
$query->setParameter('permissions', $permissions);
|
||||
$query->execute();
|
||||
} else {
|
||||
// gather share ids for bulk target update
|
||||
if ($subShare['file_target'] !== $targetPath) {
|
||||
$shareIds[] = (int)$subShare['id'];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($shareIds)) {
|
||||
$query = $this->queryUpdateShareInBatch;
|
||||
$query->setParameter('ids', $shareIds, IQueryBuilder::PARAM_INT_ARRAY);
|
||||
$query->setParameter('file_target', $targetPath);
|
||||
$query->execute();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the number of group shares is balanced with the child subshares.
|
||||
* If all group shares have exactly one subshare, and the target of every subshare
|
||||
* is the same, then the share is valid.
|
||||
* If however there is a group share entry that has no matching subshare, it means
|
||||
* we're in the bogus situation and the whole share must be repaired
|
||||
*
|
||||
* @param array $groupSharesById
|
||||
* @param array $subShares
|
||||
*
|
||||
* @return true if the share is valid, false if it needs repair
|
||||
*/
|
||||
private function isThisShareValid($groupSharesById, $subShares) {
|
||||
$foundTargets = [];
|
||||
|
||||
// every group share needs to have exactly one matching subshare
|
||||
foreach ($subShares as $subShare) {
|
||||
$foundTargets[$subShare['file_target']] = true;
|
||||
if (count($foundTargets) > 1) {
|
||||
// not all the same target path value => invalid
|
||||
return false;
|
||||
}
|
||||
if (isset($groupSharesById[$subShare['parent']])) {
|
||||
// remove it from the list as we found it
|
||||
unset($groupSharesById[$subShare['parent']]);
|
||||
}
|
||||
}
|
||||
|
||||
// if we found one subshare per group entry, the set will be empty.
|
||||
// If not empty, it means that one of the group shares did not have
|
||||
// a matching subshare entry.
|
||||
return empty($groupSharesById);
|
||||
}
|
||||
|
||||
/**
|
||||
* Detect unmerged received shares and merge them properly
|
||||
*/
|
||||
private function fixUnmergedShares(IOutput $out, IUser $user) {
|
||||
$groups = $this->groupManager->getUserGroupIds($user);
|
||||
if (empty($groups)) {
|
||||
// user is in no groups, so can't have received group shares
|
||||
return;
|
||||
}
|
||||
|
||||
// get all subshares grouped by item source
|
||||
$subSharesByItemSource = $this->getSharesWithUser(DefaultShareProvider::SHARE_TYPE_USERGROUP, [$user->getUID()]);
|
||||
|
||||
// because sometimes one wants to give the user more permissions than the group share
|
||||
$userSharesByItemSource = $this->getSharesWithUser(Constants::SHARE_TYPE_USER, [$user->getUID()]);
|
||||
|
||||
if (empty($subSharesByItemSource) && empty($userSharesByItemSource)) {
|
||||
// nothing to repair for this user, no need to do extra queries
|
||||
return;
|
||||
}
|
||||
|
||||
$groupSharesByItemSource = $this->getSharesWithUser(Constants::SHARE_TYPE_GROUP, $groups);
|
||||
if (empty($groupSharesByItemSource) && empty($userSharesByItemSource)) {
|
||||
// nothing to repair for this user
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($groupSharesByItemSource as $itemSource => $groupShares) {
|
||||
$subShares = [];
|
||||
if (isset($subSharesByItemSource[$itemSource])) {
|
||||
$subShares = $subSharesByItemSource[$itemSource];
|
||||
}
|
||||
|
||||
if (isset($userSharesByItemSource[$itemSource])) {
|
||||
// add it to the subshares to get a similar treatment
|
||||
$subShares = array_merge($subShares, $userSharesByItemSource[$itemSource]);
|
||||
}
|
||||
|
||||
$this->fixThisShare($groupShares, $subShares);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Count all the users
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
private function countUsers() {
|
||||
$allCount = $this->userManager->countUsers();
|
||||
|
||||
$totalCount = 0;
|
||||
foreach ($allCount as $backend => $count) {
|
||||
$totalCount += $count;
|
||||
}
|
||||
|
||||
return $totalCount;
|
||||
}
|
||||
|
||||
public function run(IOutput $output) {
|
||||
$ocVersionFromBeforeUpdate = $this->config->getSystemValue('version', '0.0.0');
|
||||
if (version_compare($ocVersionFromBeforeUpdate, '9.1.0.16', '<')) {
|
||||
// this situation was only possible between 9.0.0 and 9.0.3 included
|
||||
|
||||
$function = function(IUser $user) use ($output) {
|
||||
$this->fixUnmergedShares($output, $user);
|
||||
$output->advance();
|
||||
};
|
||||
|
||||
$this->buildPreparedQueries();
|
||||
|
||||
$userCount = $this->countUsers();
|
||||
$output->startProgress($userCount);
|
||||
|
||||
$this->userManager->callForAllUsers($function);
|
||||
|
||||
$output->finishProgress();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,449 @@
|
|||
<?php
|
||||
/**
|
||||
* @author Vincent Petry <pvince81@owncloud.com>
|
||||
*
|
||||
* @copyright Copyright (c) 2016, ownCloud, Inc.
|
||||
* @license AGPL-3.0
|
||||
*
|
||||
* This code is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License, version 3,
|
||||
* as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License, version 3,
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
*/
|
||||
|
||||
namespace Test\Repair;
|
||||
|
||||
|
||||
use OC\Repair\RepairUnmergedShares;
|
||||
use OC\Share\Constants;
|
||||
use OCP\Migration\IOutput;
|
||||
use OCP\Migration\IRepairStep;
|
||||
use Test\TestCase;
|
||||
use OC\Share20\DefaultShareProvider;
|
||||
|
||||
/**
|
||||
* Tests for repairing invalid shares
|
||||
*
|
||||
* @group DB
|
||||
*
|
||||
* @see \OC\Repair\RepairUnmergedShares
|
||||
*/
|
||||
class RepairUnmergedSharesTest extends TestCase {
|
||||
|
||||
/** @var IRepairStep */
|
||||
private $repair;
|
||||
|
||||
/** @var \OCP\IDBConnection */
|
||||
private $connection;
|
||||
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
$config = $this->getMockBuilder('OCP\IConfig')
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
$config->expects($this->any())
|
||||
->method('getSystemValue')
|
||||
->with('version')
|
||||
->will($this->returnValue('9.0.3.0'));
|
||||
|
||||
$this->connection = \OC::$server->getDatabaseConnection();
|
||||
$this->deleteAllShares();
|
||||
|
||||
$user1 = $this->getMock('\OCP\IUser');
|
||||
$user1->expects($this->any())
|
||||
->method('getUID')
|
||||
->will($this->returnValue('user1'));
|
||||
|
||||
$user2 = $this->getMock('\OCP\IUser');
|
||||
$user2->expects($this->any())
|
||||
->method('getUID')
|
||||
->will($this->returnValue('user2'));
|
||||
|
||||
$users = [$user1, $user2];
|
||||
|
||||
$groupManager = $this->getMock('\OCP\IGroupManager');
|
||||
$groupManager->expects($this->any())
|
||||
->method('getUserGroupIds')
|
||||
->will($this->returnValueMap([
|
||||
// owner
|
||||
[$user1, ['samegroup1', 'samegroup2']],
|
||||
// recipient
|
||||
[$user2, ['recipientgroup1', 'recipientgroup2']],
|
||||
]));
|
||||
|
||||
$userManager = $this->getMock('\OCP\IUserManager');
|
||||
$userManager->expects($this->once())
|
||||
->method('countUsers')
|
||||
->will($this->returnValue([2]));
|
||||
$userManager->expects($this->once())
|
||||
->method('callForAllUsers')
|
||||
->will($this->returnCallback(function(\Closure $closure) use ($users) {
|
||||
foreach ($users as $user) {
|
||||
$closure($user);
|
||||
}
|
||||
}));
|
||||
|
||||
/** @var \OCP\IConfig $config */
|
||||
$this->repair = new RepairUnmergedShares($config, $this->connection, $userManager, $groupManager);
|
||||
}
|
||||
|
||||
protected function tearDown() {
|
||||
$this->deleteAllShares();
|
||||
|
||||
parent::tearDown();
|
||||
}
|
||||
|
||||
protected function deleteAllShares() {
|
||||
$qb = $this->connection->getQueryBuilder();
|
||||
$qb->delete('share')->execute();
|
||||
}
|
||||
|
||||
private function createShare($type, $sourceId, $recipient, $targetName, $permissions, $parentId = null) {
|
||||
$qb = $this->connection->getQueryBuilder();
|
||||
$values = [
|
||||
'share_type' => $qb->expr()->literal($type),
|
||||
'share_with' => $qb->expr()->literal($recipient),
|
||||
'uid_owner' => $qb->expr()->literal('user1'),
|
||||
'item_type' => $qb->expr()->literal('folder'),
|
||||
'item_source' => $qb->expr()->literal($sourceId),
|
||||
'item_target' => $qb->expr()->literal('/' . $sourceId),
|
||||
'file_source' => $qb->expr()->literal($sourceId),
|
||||
'file_target' => $qb->expr()->literal($targetName),
|
||||
'permissions' => $qb->expr()->literal($permissions),
|
||||
'stime' => $qb->expr()->literal(time()),
|
||||
];
|
||||
if ($parentId !== null) {
|
||||
$values['parent'] = $qb->expr()->literal($parentId);
|
||||
}
|
||||
$qb->insert('share')
|
||||
->values($values)
|
||||
->execute();
|
||||
|
||||
return $this->connection->lastInsertId('*PREFIX*share');
|
||||
}
|
||||
|
||||
private function getShareById($id) {
|
||||
$query = $this->connection->getQueryBuilder();
|
||||
$results = $query
|
||||
->select('*')
|
||||
->from('share')
|
||||
->where($query->expr()->eq('id', $query->expr()->literal($id)))
|
||||
->execute()
|
||||
->fetchAll();
|
||||
|
||||
if (!empty($results)) {
|
||||
return $results[0];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public function sharesDataProvider() {
|
||||
/**
|
||||
* For all these test cases we have the following situation:
|
||||
*
|
||||
* - "user1" is the share owner
|
||||
* - "user2" is the recipient, and member of "recipientgroup1" and "recipientgroup2"
|
||||
* - "user1" is member of "samegroup1", "samegroup2" for same group tests
|
||||
*/
|
||||
return [
|
||||
[
|
||||
// #0 legitimate share:
|
||||
// - outsider shares with group1, group2
|
||||
// - recipient renamed, resulting in subshares
|
||||
// - one subshare for each group share
|
||||
// - targets of subshare all match
|
||||
[
|
||||
[Constants::SHARE_TYPE_GROUP, 123, 'recipientgroup1', '/test', 31],
|
||||
[Constants::SHARE_TYPE_GROUP, 123, 'recipientgroup2', '/test', 31],
|
||||
// child of the previous ones
|
||||
[DefaultShareProvider::SHARE_TYPE_USERGROUP, 123, 'user2', '/test renamed', 31, 0],
|
||||
// child of the previous ones
|
||||
[DefaultShareProvider::SHARE_TYPE_USERGROUP, 123, 'user2', '/test renamed', 31, 1],
|
||||
// different unrelated share
|
||||
[Constants::SHARE_TYPE_GROUP, 456, 'recipientgroup1', '/test (4)', 31],
|
||||
],
|
||||
[
|
||||
['/test', 31],
|
||||
['/test', 31],
|
||||
// leave them alone
|
||||
['/test renamed', 31],
|
||||
['/test renamed', 31],
|
||||
// leave unrelated alone
|
||||
['/test (4)', 31],
|
||||
]
|
||||
],
|
||||
[
|
||||
// #1 broken share:
|
||||
// - outsider shares with group1, group2
|
||||
// - only one subshare for two group shares
|
||||
[
|
||||
[Constants::SHARE_TYPE_GROUP, 123, 'recipientgroup1', '/test', 31],
|
||||
[Constants::SHARE_TYPE_GROUP, 123, 'recipientgroup2', '/test', 31],
|
||||
// child of the previous one
|
||||
[DefaultShareProvider::SHARE_TYPE_USERGROUP, 123, 'user2', '/test (2)', 31, 1],
|
||||
// different unrelated share
|
||||
[Constants::SHARE_TYPE_GROUP, 456, 'recipientgroup1', '/test (4)', 31],
|
||||
],
|
||||
[
|
||||
['/test', 31],
|
||||
['/test', 31],
|
||||
['/test', 31],
|
||||
// leave unrelated alone
|
||||
['/test (4)', 31],
|
||||
]
|
||||
],
|
||||
[
|
||||
// #2 bogus share
|
||||
// - outsider shares with group1, group2
|
||||
// - one subshare for each group share
|
||||
// - but the targets do not match when grouped
|
||||
[
|
||||
[Constants::SHARE_TYPE_GROUP, 123, 'recipientgroup1', '/test', 31],
|
||||
[Constants::SHARE_TYPE_GROUP, 123, 'recipientgroup2', '/test', 31],
|
||||
// child of the previous ones
|
||||
[DefaultShareProvider::SHARE_TYPE_USERGROUP, 123, 'user2', '/test (2)', 31, 0],
|
||||
[DefaultShareProvider::SHARE_TYPE_USERGROUP, 123, 'user2', '/test (3)', 31, 1],
|
||||
// different unrelated share
|
||||
[Constants::SHARE_TYPE_GROUP, 456, 'recipientgroup1', '/test (4)', 31],
|
||||
],
|
||||
[
|
||||
['/test', 31],
|
||||
['/test', 31],
|
||||
// reset to original name
|
||||
['/test', 31],
|
||||
['/test', 31],
|
||||
// leave unrelated alone
|
||||
['/test (4)', 31],
|
||||
]
|
||||
],
|
||||
[
|
||||
// #3 bogus share
|
||||
// - outsider shares with group1, group2
|
||||
// - one subshare for each group share
|
||||
// - first subshare not renamed (as in real world scenario)
|
||||
// - but the targets do not match when grouped
|
||||
[
|
||||
[Constants::SHARE_TYPE_GROUP, 123, 'recipientgroup1', '/test', 31],
|
||||
[Constants::SHARE_TYPE_GROUP, 123, 'recipientgroup2', '/test', 31],
|
||||
// child of the previous ones
|
||||
[DefaultShareProvider::SHARE_TYPE_USERGROUP, 123, 'user2', '/test', 31, 0],
|
||||
[DefaultShareProvider::SHARE_TYPE_USERGROUP, 123, 'user2', '/test (2)', 31, 1],
|
||||
// different unrelated share
|
||||
[Constants::SHARE_TYPE_GROUP, 456, 'recipientgroup1', '/test (4)', 31],
|
||||
],
|
||||
[
|
||||
['/test', 31],
|
||||
['/test', 31],
|
||||
// reset to original name
|
||||
['/test', 31],
|
||||
['/test', 31],
|
||||
// leave unrelated alone
|
||||
['/test (4)', 31],
|
||||
]
|
||||
],
|
||||
[
|
||||
// #4 bogus share:
|
||||
// - outsider shares with group1, group2
|
||||
// - one subshare for each group share
|
||||
// - non-matching targets
|
||||
// - recipient deletes one duplicate (unshare from self, permissions 0)
|
||||
[
|
||||
[Constants::SHARE_TYPE_GROUP, 123, 'recipientgroup1', '/test', 31],
|
||||
[Constants::SHARE_TYPE_GROUP, 123, 'recipientgroup2', '/test', 15],
|
||||
// child of the previous ones
|
||||
[DefaultShareProvider::SHARE_TYPE_USERGROUP, 123, 'user2', '/test (2)', 0, 0],
|
||||
[DefaultShareProvider::SHARE_TYPE_USERGROUP, 123, 'user2', '/test (3)', 15, 1],
|
||||
// different unrelated share
|
||||
[Constants::SHARE_TYPE_GROUP, 456, 'recipientgroup1', '/test (4)', 31],
|
||||
],
|
||||
[
|
||||
['/test', 31],
|
||||
['/test', 15],
|
||||
// subshares repaired and permissions restored to the max allowed
|
||||
['/test', 31],
|
||||
['/test', 15],
|
||||
// leave unrelated alone
|
||||
['/test (4)', 31],
|
||||
]
|
||||
],
|
||||
[
|
||||
// #5 bogus share:
|
||||
// - outsider shares with group1, group2
|
||||
// - one subshare for each group share
|
||||
// - non-matching targets
|
||||
// - recipient deletes ALL duplicates (unshare from self, permissions 0)
|
||||
[
|
||||
[Constants::SHARE_TYPE_GROUP, 123, 'recipientgroup1', '/test', 31],
|
||||
[Constants::SHARE_TYPE_GROUP, 123, 'recipientgroup2', '/test', 15],
|
||||
// child of the previous ones
|
||||
[DefaultShareProvider::SHARE_TYPE_USERGROUP, 123, 'user2', '/test (2)', 0, 0],
|
||||
[DefaultShareProvider::SHARE_TYPE_USERGROUP, 123, 'user2', '/test (3)', 0, 1],
|
||||
// different unrelated share
|
||||
[Constants::SHARE_TYPE_GROUP, 456, 'recipientgroup1', '/test (4)', 31],
|
||||
],
|
||||
[
|
||||
['/test', 31],
|
||||
['/test', 15],
|
||||
// subshares target repaired but left "deleted" as it was the user's choice
|
||||
['/test', 0],
|
||||
['/test', 0],
|
||||
// leave unrelated alone
|
||||
['/test (4)', 31],
|
||||
]
|
||||
],
|
||||
[
|
||||
// #6 bogus share:
|
||||
// - outsider shares with group1, group2 and also user2
|
||||
// - one subshare for each group share
|
||||
// - one extra share entry for direct share to user2
|
||||
// - non-matching targets
|
||||
// - user share has more permissions
|
||||
[
|
||||
[Constants::SHARE_TYPE_GROUP, 123, 'recipientgroup1', '/test', 1],
|
||||
[Constants::SHARE_TYPE_GROUP, 123, 'recipientgroup2', '/test', 15],
|
||||
// child of the previous ones
|
||||
[DefaultShareProvider::SHARE_TYPE_USERGROUP, 123, 'user2', '/test (2)', 1, 0],
|
||||
[DefaultShareProvider::SHARE_TYPE_USERGROUP, 123, 'user2', '/test (3)', 15, 1],
|
||||
[Constants::SHARE_TYPE_USER, 123, 'user2', '/test (4)', 31],
|
||||
// different unrelated share
|
||||
[Constants::SHARE_TYPE_GROUP, 456, 'recipientgroup1', '/test (5)', 31],
|
||||
],
|
||||
[
|
||||
['/test', 1],
|
||||
['/test', 15],
|
||||
// subshares repaired
|
||||
['/test', 1],
|
||||
['/test', 15],
|
||||
['/test', 31],
|
||||
// leave unrelated alone
|
||||
['/test (5)', 31],
|
||||
]
|
||||
],
|
||||
[
|
||||
// #7 bogus share:
|
||||
// - outsider shares with group1 and also user2
|
||||
// - no subshare at all
|
||||
// - one extra share entry for direct share to user2
|
||||
// - non-matching targets
|
||||
// - user share has more permissions
|
||||
[
|
||||
[Constants::SHARE_TYPE_GROUP, 123, 'recipientgroup1', '/test', 1],
|
||||
[Constants::SHARE_TYPE_USER, 123, 'user2', '/test (2)', 31],
|
||||
// different unrelated share
|
||||
[Constants::SHARE_TYPE_GROUP, 456, 'recipientgroup1', '/test (5)', 31],
|
||||
],
|
||||
[
|
||||
['/test', 1],
|
||||
// user share repaired
|
||||
['/test', 31],
|
||||
// leave unrelated alone
|
||||
['/test (5)', 31],
|
||||
]
|
||||
],
|
||||
[
|
||||
// #8 legitimate share with own group:
|
||||
// - insider shares with both groups the user is already in
|
||||
// - no subshares in this case
|
||||
[
|
||||
[Constants::SHARE_TYPE_GROUP, 123, 'samegroup1', '/test', 31],
|
||||
[Constants::SHARE_TYPE_GROUP, 123, 'samegroup2', '/test', 31],
|
||||
// different unrelated share
|
||||
[Constants::SHARE_TYPE_GROUP, 456, 'recipientgroup1', '/test (4)', 31],
|
||||
],
|
||||
[
|
||||
// leave all alone
|
||||
['/test', 31],
|
||||
['/test', 31],
|
||||
// leave unrelated alone
|
||||
['/test (4)', 31],
|
||||
]
|
||||
],
|
||||
[
|
||||
// #9 legitimate shares:
|
||||
// - group share with same group
|
||||
// - group share with other group
|
||||
// - user share where recipient renamed
|
||||
// - user share where recipient did not rename
|
||||
[
|
||||
[Constants::SHARE_TYPE_GROUP, 123, 'samegroup1', '/test', 31],
|
||||
[Constants::SHARE_TYPE_GROUP, 123, 'recipientgroup1', '/test', 31],
|
||||
[Constants::SHARE_TYPE_USER, 123, 'user3', '/test legit rename', 31],
|
||||
[Constants::SHARE_TYPE_USER, 123, 'user4', '/test', 31],
|
||||
// different unrelated share
|
||||
[Constants::SHARE_TYPE_GROUP, 456, 'recipientgroup1', '/test (4)', 31],
|
||||
],
|
||||
[
|
||||
// leave all alone
|
||||
['/test', 31],
|
||||
['/test', 31],
|
||||
['/test legit rename', 31],
|
||||
['/test', 31],
|
||||
// leave unrelated alone
|
||||
['/test (4)', 31],
|
||||
]
|
||||
],
|
||||
[
|
||||
// #10 legitimate share:
|
||||
// - outsider shares with group and user directly with different permissions
|
||||
// - no subshares
|
||||
// - same targets
|
||||
[
|
||||
[Constants::SHARE_TYPE_GROUP, 123, 'samegroup1', '/test', 1],
|
||||
[Constants::SHARE_TYPE_USER, 123, 'user3', '/test', 31],
|
||||
// different unrelated share
|
||||
[Constants::SHARE_TYPE_GROUP, 456, 'recipientgroup1', '/test (4)', 31],
|
||||
],
|
||||
[
|
||||
// leave all alone
|
||||
['/test', 1],
|
||||
['/test', 31],
|
||||
// leave unrelated alone
|
||||
['/test (4)', 31],
|
||||
]
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Test merge shares from group shares
|
||||
*
|
||||
* @dataProvider sharesDataProvider
|
||||
*/
|
||||
public function testMergeGroupShares($shares, $expectedShares) {
|
||||
$shareIds = [];
|
||||
|
||||
foreach ($shares as $share) {
|
||||
// if parent
|
||||
if (isset($share[5])) {
|
||||
// adjust to real id
|
||||
$share[5] = $shareIds[$share[5]];
|
||||
} else {
|
||||
$share[5] = null;
|
||||
}
|
||||
$shareIds[] = $this->createShare($share[0], $share[1], $share[2], $share[3], $share[4], $share[5]);
|
||||
}
|
||||
|
||||
/** @var IOutput | \PHPUnit_Framework_MockObject_MockObject $outputMock */
|
||||
$outputMock = $this->getMockBuilder('\OCP\Migration\IOutput')
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
|
||||
$this->repair->run($outputMock);
|
||||
|
||||
foreach ($expectedShares as $index => $expectedShare) {
|
||||
$share = $this->getShareById($shareIds[$index]);
|
||||
$this->assertEquals($expectedShare[0], $share['file_target']);
|
||||
$this->assertEquals($expectedShare[1], $share['permissions']);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -25,7 +25,7 @@
|
|||
// We only can count up. The 4. digit is only for the internal patchlevel to trigger DB upgrades
|
||||
// between betas, final and RCs. This is _not_ the public version number. Reset minor/patchlevel
|
||||
// when updating major/minor version number.
|
||||
$OC_Version = array(9, 1, 0, 13);
|
||||
$OC_Version = array(9, 1, 0, 14);
|
||||
|
||||
// The human readable string
|
||||
$OC_VersionString = '10.0 beta';
|
||||
|
|
Loading…
Reference in New Issue