diff options
44 files changed, 3077 insertions, 150 deletions
diff --git a/.htaccess b/.htaccess index 740329e15a0..bb030c6acca 100644 --- a/.htaccess +++ b/.htaccess @@ -45,6 +45,7 @@ RewriteRule ^\.well-known/caldav /remote.php/dav/ [R=301,L] RewriteRule ^remote/(.*) remote.php [QSA,L] RewriteRule ^(build|tests|config|lib|3rdparty|templates)/.* - [R=404,L] + RewriteCond %{REQUEST_URI} !^/.well-known/acme-challenge/.* RewriteRule ^(\.|autotest|occ|issue|indie|db_|console).* - [R=404,L] # Rewrite rules for `front_controller_active` @@ -60,6 +61,7 @@ RewriteCond %{REQUEST_FILENAME} !/status.php RewriteCond %{REQUEST_FILENAME} !/ocs/v1.php RewriteCond %{REQUEST_FILENAME} !/ocs/v2.php + RewriteCond %{REQUEST_URI} !^/.well-known/acme-challenge/.* RewriteRule .* index.php [PT,E=PATH_INFO:$1] </IfModule> <IfModule mod_mime.c> diff --git a/apps/dav/appinfo/info.xml b/apps/dav/appinfo/info.xml index 5f681e784fc..d9eefaefcf1 100644 --- a/apps/dav/appinfo/info.xml +++ b/apps/dav/appinfo/info.xml @@ -6,8 +6,6 @@ <licence>AGPL</licence> <author>owncloud.org</author> <version>0.1.3</version> - <requiremin>9.0</requiremin> - <shipped>true</shipped> <standalone/> <default_enable/> <types> @@ -21,4 +19,7 @@ <public> <webdav>appinfo/v1/publicwebdav.php</webdav> </public> + <dependencies> + <owncloud min-version="9.0" max-version="9.0" /> + </dependencies> </info> diff --git a/apps/encryption/appinfo/info.xml b/apps/encryption/appinfo/info.xml index 2224f026e4d..1fb4e93c508 100644 --- a/apps/encryption/appinfo/info.xml +++ b/apps/encryption/appinfo/info.xml @@ -14,7 +14,6 @@ <name>Default encryption module</name> <license>AGPL</license> <author>Bjoern Schiessle, Clark Tomlinson</author> - <shipped>true</shipped> <documentation> <user>user-encryption</user> <admin>admin-encryption</admin> @@ -26,7 +25,7 @@ </types> <dependencies> <lib>openssl</lib> - <owncloud min-version="9.0" /> + <owncloud min-version="9.0" max-version="9.0" /> </dependencies> </info> diff --git a/apps/encryption/settings/settings-admin.php b/apps/encryption/settings/settings-admin.php index 8d55d587fed..32640ef0661 100644 --- a/apps/encryption/settings/settings-admin.php +++ b/apps/encryption/settings/settings-admin.php @@ -42,7 +42,7 @@ $util = new \OCA\Encryption\Util( $recoveryAdminEnabled = \OC::$server->getConfig()->getAppValue('encryption', 'recoveryAdminEnabled', '0'); $session = new \OCA\Encryption\Session(\OC::$server->getSession()); -$encryptHomeStorage = $util->shouldEncryptHomeStorage($user); +$encryptHomeStorage = $util->shouldEncryptHomeStorage(); $tmpl->assign('recoveryEnabled', $recoveryAdminEnabled); $tmpl->assign('initStatus', $session->getStatus()); diff --git a/apps/federation/appinfo/info.xml b/apps/federation/appinfo/info.xml index 53b2926ba53..92afc995366 100644 --- a/apps/federation/appinfo/info.xml +++ b/apps/federation/appinfo/info.xml @@ -8,7 +8,7 @@ <version>0.0.1</version> <namespace>Federation</namespace> <category>other</category> - <dependencies> - <owncloud min-version="9.0" /> - </dependencies> + <dependencies> + <owncloud min-version="9.0" max-version="9.0" /> + </dependencies> </info> diff --git a/apps/files/appinfo/info.xml b/apps/files/appinfo/info.xml index df12b87397d..37ee564057a 100644 --- a/apps/files/appinfo/info.xml +++ b/apps/files/appinfo/info.xml @@ -5,7 +5,6 @@ <description>File Management</description> <licence>AGPL</licence> <author>Robin Appelman, Vincent Petry</author> - <shipped>true</shipped> <standalone/> <default_enable/> <version>1.4.1</version> @@ -13,7 +12,7 @@ <filesystem/> </types> <dependencies> - <owncloud min-version="9.0" /> + <owncloud min-version="9.0" max-version="9.0" /> </dependencies> <documentation> <user>user-files</user> diff --git a/apps/files_external/appinfo/info.xml b/apps/files_external/appinfo/info.xml index 355d9feb4b9..1a9fa73de3f 100644 --- a/apps/files_external/appinfo/info.xml +++ b/apps/files_external/appinfo/info.xml @@ -9,7 +9,6 @@ </description> <licence>AGPL</licence> <author>Robin Appelman, Michael Gapczynski, Vincent Petry</author> - <shipped>true</shipped> <documentation> <admin>admin-external-storage</admin> </documentation> @@ -21,6 +20,6 @@ <ocsid>166048</ocsid> <dependencies> - <owncloud min-version="9.0" /> + <owncloud min-version="9.0" max-version="9.0" /> </dependencies> </info> diff --git a/apps/files_external/lib/smb.php b/apps/files_external/lib/smb.php index 80b44a4cbdf..125e0a6dd2c 100644 --- a/apps/files_external/lib/smb.php +++ b/apps/files_external/lib/smb.php @@ -302,7 +302,9 @@ class SMB extends Common { * check if smbclient is installed */ public static function checkDependencies() { - $smbClientExists = (bool)\OC_Helper::findBinaryPath('smbclient'); - return $smbClientExists ? true : array('smbclient'); + return ( + (bool)\OC_Helper::findBinaryPath('smbclient') + || Server::NativeAvailable() + ) ? true : ['smbclient']; } } diff --git a/apps/files_sharing/api/ocssharewrapper.php b/apps/files_sharing/api/ocssharewrapper.php index ca04c656c28..4640f4ea185 100644 --- a/apps/files_sharing/api/ocssharewrapper.php +++ b/apps/files_sharing/api/ocssharewrapper.php @@ -29,13 +29,18 @@ class OCSShareWrapper { return new Share20OCS( new \OC\Share20\Manager( \OC::$server->getLogger(), - \OC::$server->getAppConfig(), + \OC::$server->getConfig(), new \OC\Share20\DefaultShareProvider( \OC::$server->getDatabaseConnection(), \OC::$server->getUserManager(), \OC::$server->getGroupManager(), \OC::$server->getRootFolder() - ) + ), + \OC::$server->getSecureRandom(), + \OC::$server->getHasher(), + \OC::$server->getMountManager(), + \OC::$server->getGroupManager(), + \OC::$server->getL10N('core') ), \OC::$server->getGroupManager(), \OC::$server->getUserManager(), @@ -49,8 +54,8 @@ class OCSShareWrapper { return \OCA\Files_Sharing\API\Local::getAllShares($params); } - public function createShare($params) { - return \OCA\Files_Sharing\API\Local::createShare($params); + public function createShare() { + return $this->getShare20OCS()->createShare(); } public function getShare($params) { diff --git a/apps/files_sharing/api/share20ocs.php b/apps/files_sharing/api/share20ocs.php index 6c25b4a4426..003c028bf97 100644 --- a/apps/files_sharing/api/share20ocs.php +++ b/apps/files_sharing/api/share20ocs.php @@ -25,7 +25,6 @@ use OC\Share20\IShare; use OCP\IGroupManager; use OCP\IUserManager; use OCP\IRequest; -use OCP\Files\Folder; use OCP\IURLGenerator; use OCP\IUser; use OCP\Files\IRootFolder; @@ -98,7 +97,7 @@ class Share20OCS { $result['item_type'] = 'file'; } $result['storage_id'] = $path->getStorage()->getId(); - $result['storage'] = \OC\Files\Cache\Storage::getNumericStorageId($path->getStorage()->getId()); + $result['storage'] = $path->getStorage()->getCache()->getNumericStorageId(); $result['item_source'] = $path->getId(); $result['file_source'] = $path->getId(); $result['file_parent'] = $path->getParent()->getId(); @@ -192,6 +191,130 @@ class Share20OCS { } /** + * @return \OC_OCS_Result + */ + public function createShare() { + $share = $this->shareManager->newShare(); + + // Verify path + $path = $this->request->getParam('path', null); + if ($path === null) { + return new \OC_OCS_Result(null, 404, 'please specify a file or folder path'); + } + + $userFolder = $this->rootFolder->getUserFolder($this->currentUser->getUID()); + try { + $path = $userFolder->get($path); + } catch (\OCP\Files\NotFoundException $e) { + return new \OC_OCS_Result(null, 404, 'wrong path, file/folder doesn\'t exist'); + } + + $share->setPath($path); + + // Parse permissions (if available) + $permissions = $this->request->getParam('permissions', null); + if ($permissions === null) { + $permissions = \OCP\Constants::PERMISSION_ALL; + } else { + $permissions = (int)$permissions; + } + + if ($permissions < 0 || $permissions > \OCP\Constants::PERMISSION_ALL) { + return new \OC_OCS_Result(null, 404, 'invalid permissions'); + } + + // Shares always require read permissions + $permissions |= \OCP\Constants::PERMISSION_READ; + + if ($path instanceof \OCP\Files\File) { + // Single file shares should never have delete or create permissions + $permissions &= ~\OCP\Constants::PERMISSION_DELETE; + $permissions &= ~\OCP\Constants::PERMISSION_CREATE; + } + + $shareWith = $this->request->getParam('shareWith', null); + $shareType = (int)$this->request->getParam('shareType', '-1'); + + if ($shareType === \OCP\Share::SHARE_TYPE_USER) { + // Valid user is required to share + if ($shareWith === null || !$this->userManager->userExists($shareWith)) { + return new \OC_OCS_Result(null, 404, 'please specify a valid user'); + } + $share->setSharedWith($this->userManager->get($shareWith)); + $share->setPermissions($permissions); + } else if ($shareType === \OCP\Share::SHARE_TYPE_GROUP) { + // Valid group is required to share + if ($shareWith === null || !$this->groupManager->groupExists($shareWith)) { + return new \OC_OCS_Result(null, 404, 'please specify a valid group'); + } + $share->setSharedWith($this->groupManager->get($shareWith)); + $share->setPermissions($permissions); + } else if ($shareType === \OCP\Share::SHARE_TYPE_LINK) { + //Can we even share links? + if (!$this->shareManager->shareApiAllowLinks()) { + return new \OC_OCS_Result(null, 404, 'public link sharing is disabled by the administrator'); + } + + $publicUpload = $this->request->getParam('publicUpload', null); + if ($publicUpload === 'true') { + // Check if public upload is allowed + if (!$this->shareManager->shareApiLinkAllowPublicUpload()) { + return new \OC_OCS_Result(null, 403, '"public upload disabled by the administrator'); + } + + // Public upload can only be set for folders + if ($path instanceof \OCP\Files\File) { + return new \OC_OCS_Result(null, 404, '"public upload is only possible for public shared folders'); + } + + $share->setPermissions( + \OCP\Constants::PERMISSION_READ | + \OCP\Constants::PERMISSION_CREATE | + \OCP\Constants::PERMISSION_UPDATE + ); + } else { + $share->setPermissions(\OCP\Constants::PERMISSION_READ); + } + + // Set password + $share->setPassword($this->request->getParam('password', null)); + + //Expire date + $expireDate = $this->request->getParam('expireDate', null); + + if ($expireDate !== null) { + try { + $expireDate = $this->parseDate($expireDate); + $share->setExpirationDate($expireDate); + } catch (\Exception $e) { + return new \OC_OCS_Result(null, 404, 'Invalid Date. Format must be YYYY-MM-DD.'); + } + } + + } else if ($shareType === \OCP\Share::SHARE_TYPE_REMOTE) { + //fixme Remote shares are handled by old code path for now + return \OCA\Files_Sharing\API\Local::createShare([]); + } else { + return new \OC_OCS_Result(null, 400, "unknown share type"); + } + + $share->setShareType($shareType); + $share->setSharedBy($this->currentUser); + + try { + $share = $this->shareManager->createShare($share); + } catch (\OC\HintException $e) { + $code = $e->getCode() === 0 ? 403 : $e->getCode(); + return new \OC_OCS_Result(null, $code, $e->getHint()); + }catch (\Exception $e) { + return new \OC_OCS_Result(null, 403, $e->getMessage()); + } + + $share = $this->formatShare($share); + return new \OC_OCS_Result($share); + } + + /** * @param IShare $share * @return bool */ @@ -216,4 +339,30 @@ class Share20OCS { return false; } + + /** + * Make sure that the passed date is valid ISO 8601 + * So YYYY-MM-DD + * If not throw an exception + * + * @param string $expireDate + * + * @throws \Exception + * @return \DateTime + */ + private function parseDate($expireDate) { + try { + $date = new \DateTime($expireDate); + } catch (\Exception $e) { + throw new \Exception('Invalid date. Format must be YYYY-MM-DD'); + } + + if ($date === false) { + throw new \Exception('Invalid date. Format must be YYYY-MM-DD'); + } + + $date->setTime(0,0,0); + + return $date; + } } diff --git a/apps/files_sharing/appinfo/info.xml b/apps/files_sharing/appinfo/info.xml index 72e56456961..bb16345f101 100644 --- a/apps/files_sharing/appinfo/info.xml +++ b/apps/files_sharing/appinfo/info.xml @@ -9,14 +9,13 @@ Turning the feature off removes shared files and folders on the server for all s </description> <licence>AGPL</licence> <author>Michael Gapczynski, Bjoern Schiessle</author> - <shipped>true</shipped> <default_enable/> <version>0.8.1</version> <types> <filesystem/> </types> <dependencies> - <owncloud min-version="9.0" /> + <owncloud min-version="9.0" max-version="9.0" /> </dependencies> <public> <files>public.php</files> diff --git a/apps/files_sharing/tests/api/share20ocstest.php b/apps/files_sharing/tests/api/share20ocstest.php index b7c56fe17f6..74a5d0752a4 100644 --- a/apps/files_sharing/tests/api/share20ocstest.php +++ b/apps/files_sharing/tests/api/share20ocstest.php @@ -65,6 +65,7 @@ class Share20OCSTest extends \Test\TestCase { $this->rootFolder = $this->getMock('OCP\Files\IRootFolder'); $this->urlGenerator = $this->getMock('OCP\IURLGenerator'); $this->currentUser = $this->getMock('OCP\IUser'); + $this->currentUser->method('getUID')->willReturn('currentUser'); $this->ocs = new Share20OCS( $this->shareManager, @@ -171,8 +172,18 @@ class Share20OCSTest extends \Test\TestCase { $group = $this->getMock('OCP\IGroup'); $group->method('getGID')->willReturn('groupId'); - $storage = $this->getMock('OCP\Files\Storage'); + $cache = $this->getMockBuilder('OC\Files\Cache\Cache') + ->disableOriginalConstructor() + ->getMock(); + $cache->method('getNumericStorageId')->willReturn(101); + + $storage = $this->getMockBuilder('OC\Files\Storage\Storage') + ->disableOriginalConstructor() + ->getMock(); $storage->method('getId')->willReturn('STORAGE'); + $storage->method('getCache')->willReturn($cache); + + $parentFolder = $this->getMock('OCP\Files\Folder'); $parentFolder->method('getId')->willReturn(3); @@ -223,7 +234,7 @@ class Share20OCSTest extends \Test\TestCase { 'parent' => 6, 'storage_id' => 'STORAGE', 'path' => 'file', - 'storage' => null, // HACK around static function + 'storage' => 101, 'mail_send' => 0, ]; $data[] = [$share, $expected]; @@ -262,7 +273,7 @@ class Share20OCSTest extends \Test\TestCase { 'parent' => 6, 'storage_id' => 'STORAGE', 'path' => 'folder', - 'storage' => null, // HACK around static function + 'storage' => 101, 'mail_send' => 0, ]; $data[] = [$share, $expected]; @@ -304,7 +315,7 @@ class Share20OCSTest extends \Test\TestCase { 'parent' => 6, 'storage_id' => 'STORAGE', 'path' => 'folder', - 'storage' => null, // HACK around static function + 'storage' => 101, 'mail_send' => 0, 'url' => 'url', ]; @@ -391,4 +402,280 @@ class Share20OCSTest extends \Test\TestCase { $share->method('getShareType')->willReturn(\OCP\Share::SHARE_TYPE_LINK); $this->assertFalse($this->invokePrivate($this->ocs, 'canAccessShare', [$share])); } + + public function testCreateShareNoPath() { + $expected = new \OC_OCS_Result(null, 404, 'please specify a file or folder path'); + + $result = $this->ocs->createShare(); + + $this->assertEquals($expected->getMeta(), $result->getMeta()); + $this->assertEquals($expected->getData(), $result->getData()); + } + + public function testCreateShareInvalidPath() { + $this->request + ->method('getParam') + ->will($this->returnValueMap([ + ['path', null, 'invalid-path'], + ])); + + $userFolder = $this->getMock('\OCP\Files\Folder'); + $this->rootFolder->expects($this->once()) + ->method('getUserFolder') + ->with('currentUser') + ->willReturn($userFolder); + + $userFolder->expects($this->once()) + ->method('get') + ->with('invalid-path') + ->will($this->throwException(new \OCP\Files\NotFoundException())); + + $expected = new \OC_OCS_Result(null, 404, 'wrong path, file/folder doesn\'t exist'); + + $result = $this->ocs->createShare(); + + $this->assertEquals($expected->getMeta(), $result->getMeta()); + $this->assertEquals($expected->getData(), $result->getData()); + } + + public function testCreateShareInvalidPermissions() { + $share = $this->getMock('\OC\Share20\IShare'); + $this->shareManager->method('newShare')->willReturn($share); + + $this->request + ->method('getParam') + ->will($this->returnValueMap([ + ['path', null, 'valid-path'], + ['permissions', null, 32], + ])); + + $userFolder = $this->getMock('\OCP\Files\Folder'); + $this->rootFolder->expects($this->once()) + ->method('getUserFolder') + ->with('currentUser') + ->willReturn($userFolder); + + $path = $this->getMock('\OCP\Files\File'); + $userFolder->expects($this->once()) + ->method('get') + ->with('valid-path') + ->willReturn($path); + + $expected = new \OC_OCS_Result(null, 404, 'invalid permissions'); + + $result = $this->ocs->createShare(); + + $this->assertEquals($expected->getMeta(), $result->getMeta()); + $this->assertEquals($expected->getData(), $result->getData()); + } + + public function testCreateShareUserNoShareWith() { + $share = $this->getMock('\OC\Share20\IShare'); + $this->shareManager->method('newShare')->willReturn($share); + + $this->request + ->method('getParam') + ->will($this->returnValueMap([ + ['path', null, 'valid-path'], + ['permissions', null, \OCP\Constants::PERMISSION_ALL], + ['shareType', $this->any(), \OCP\Share::SHARE_TYPE_USER], + ])); + + $userFolder = $this->getMock('\OCP\Files\Folder'); + $this->rootFolder->expects($this->once()) + ->method('getUserFolder') + ->with('currentUser') + ->willReturn($userFolder); + + $path = $this->getMock('\OCP\Files\File'); + $userFolder->expects($this->once()) + ->method('get') + ->with('valid-path') + ->willReturn($path); + + $expected = new \OC_OCS_Result(null, 404, 'please specify a valid user'); + + $result = $this->ocs->createShare(); + + $this->assertEquals($expected->getMeta(), $result->getMeta()); + $this->assertEquals($expected->getData(), $result->getData()); + } + + public function testCreateShareUserNoValidShareWith() { + $share = $this->getMock('\OC\Share20\IShare'); + $this->shareManager->method('newShare')->willReturn($share); + + $this->request + ->method('getParam') + ->will($this->returnValueMap([ + ['path', null, 'valid-path'], + ['permissions', null, \OCP\Constants::PERMISSION_ALL], + ['shareType', $this->any(), \OCP\Share::SHARE_TYPE_USER], + ['shareWith', $this->any(), 'invalidUser'], + ])); + + $userFolder = $this->getMock('\OCP\Files\Folder'); + $this->rootFolder->expects($this->once()) + ->method('getUserFolder') + ->with('currentUser') + ->willReturn($userFolder); + + $path = $this->getMock('\OCP\Files\File'); + $userFolder->expects($this->once()) + ->method('get') + ->with('valid-path') + ->willReturn($path); + + $expected = new \OC_OCS_Result(null, 404, 'please specify a valid user'); + + $result = $this->ocs->createShare(); + + $this->assertEquals($expected->getMeta(), $result->getMeta()); + $this->assertEquals($expected->getData(), $result->getData()); + } + + public function testCreateShareUser() { + $share = $this->getMock('\OC\Share20\IShare'); + $this->shareManager->method('newShare')->willReturn($share); + + $ocs = $this->getMockBuilder('OCA\Files_Sharing\API\Share20OCS') + ->setConstructorArgs([ + $this->shareManager, + $this->groupManager, + $this->userManager, + $this->request, + $this->rootFolder, + $this->urlGenerator, + $this->currentUser + ])->setMethods(['formatShare']) + ->getMock(); + + $this->request + ->method('getParam') + ->will($this->returnValueMap([ + ['path', null, 'valid-path'], + ['permissions', null, \OCP\Constants::PERMISSION_ALL], + ['shareType', $this->any(), \OCP\Share::SHARE_TYPE_USER], + ['shareWith', null, 'validUser'], + ])); + + $userFolder = $this->getMock('\OCP\Files\Folder'); + $this->rootFolder->expects($this->once()) + ->method('getUserFolder') + ->with('currentUser') + ->willReturn($userFolder); + + $path = $this->getMock('\OCP\Files\File'); + $userFolder->expects($this->once()) + ->method('get') + ->with('valid-path') + ->willReturn($path); + + $user = $this->getMock('\OCP\IUser'); + $this->userManager->method('userExists')->with('validUser')->willReturn(true); + $this->userManager->method('get')->with('validUser')->willReturn($user); + + $share->method('setPath')->with($path); + $share->method('setPermissions') + ->with( + \OCP\Constants::PERMISSION_ALL & + ~\OCP\Constants::PERMISSION_DELETE & + ~\OCP\Constants::PERMISSION_CREATE); + $share->method('setShareType')->with(\OCP\Share::SHARE_TYPE_USER); + $share->method('setSharedWith')->with($user); + $share->method('setSharedBy')->with($this->currentUser); + + $expected = new \OC_OCS_Result(); + $result = $ocs->createShare(); + + $this->assertEquals($expected->getMeta(), $result->getMeta()); + $this->assertEquals($expected->getData(), $result->getData()); + } + + public function testCreateShareGroupNoValidShareWith() { + $share = $this->getMock('\OC\Share20\IShare'); + $this->shareManager->method('newShare')->willReturn($share); + + $this->request + ->method('getParam') + ->will($this->returnValueMap([ + ['path', null, 'valid-path'], + ['permissions', null, \OCP\Constants::PERMISSION_ALL], + ['shareType', $this->any(), \OCP\Share::SHARE_TYPE_GROUP], + ['shareWith', $this->any(), 'invalidGroup'], + ])); + + $userFolder = $this->getMock('\OCP\Files\Folder'); + $this->rootFolder->expects($this->once()) + ->method('getUserFolder') + ->with('currentUser') + ->willReturn($userFolder); + + $path = $this->getMock('\OCP\Files\File'); + $userFolder->expects($this->once()) + ->method('get') + ->with('valid-path') + ->willReturn($path); + + $expected = new \OC_OCS_Result(null, 404, 'please specify a valid user'); + + $result = $this->ocs->createShare(); + + $this->assertEquals($expected->getMeta(), $result->getMeta()); + $this->assertEquals($expected->getData(), $result->getData()); + } + + public function testCreateShareGroup() { + $share = $this->getMock('\OC\Share20\IShare'); + $this->shareManager->method('newShare')->willReturn($share); + + $ocs = $this->getMockBuilder('OCA\Files_Sharing\API\Share20OCS') + ->setConstructorArgs([ + $this->shareManager, + $this->groupManager, + $this->userManager, + $this->request, + $this->rootFolder, + $this->urlGenerator, + $this->currentUser + ])->setMethods(['formatShare']) + ->getMock(); + + $this->request + ->method('getParam') + ->will($this->returnValueMap([ + ['path', null, 'valid-path'], + ['permissions', null, \OCP\Constants::PERMISSION_ALL], + ['shareType', '-1', \OCP\Share::SHARE_TYPE_GROUP], + ['shareWith', null, 'validGroup'], + ])); + + $userFolder = $this->getMock('\OCP\Files\Folder'); + $this->rootFolder->expects($this->once()) + ->method('getUserFolder') + ->with('currentUser') + ->willReturn($userFolder); + + $path = $this->getMock('\OCP\Files\Folder'); + $userFolder->expects($this->once()) + ->method('get') + ->with('valid-path') + ->willReturn($path); + + $group = $this->getMock('\OCP\IGroup'); + $this->groupManager->method('groupExists')->with('validGroup')->willReturn(true); + $this->groupManager->method('get')->with('validGroup')->willReturn($group); + + $share->method('setPath')->with($path); + $share->method('setPermissions')->with(\OCP\Constants::PERMISSION_ALL); + $share->method('setShareType')->with(\OCP\Share::SHARE_TYPE_GROUP); + $share->method('setSharedWith')->with($group); + $share->method('setSharedBy')->with($this->currentUser); + + $expected = new \OC_OCS_Result(); + $result = $ocs->createShare(); + + $this->assertEquals($expected->getMeta(), $result->getMeta()); + $this->assertEquals($expected->getData(), $result->getData()); + } } diff --git a/apps/files_trashbin/appinfo/info.xml b/apps/files_trashbin/appinfo/info.xml index c4bade081b3..8fcb9956c7d 100644 --- a/apps/files_trashbin/appinfo/info.xml +++ b/apps/files_trashbin/appinfo/info.xml @@ -9,14 +9,13 @@ To prevent a user from running out of disk space, the ownCloud Deleted files app </description> <licence>AGPL</licence> <author>Bjoern Schiessle</author> - <shipped>true</shipped> <default_enable/> <version>0.8.0</version> <types> <filesystem/> </types> <dependencies> - <owncloud min-version="9.0" /> + <owncloud min-version="9.0" max-version="9.0" /> </dependencies> <documentation> <user>user-trashbin</user> diff --git a/apps/files_versions/appinfo/info.xml b/apps/files_versions/appinfo/info.xml index 8fe6372279a..0098ea9e20f 100644 --- a/apps/files_versions/appinfo/info.xml +++ b/apps/files_versions/appinfo/info.xml @@ -4,7 +4,6 @@ <name>Versions</name> <licence>AGPL</licence> <author>Frank Karlitschek, Bjoern Schiessle</author> - <shipped>true</shipped> <description> This application enables ownCloud to automatically maintain older versions of files that are changed. When enabled, a hidden versions folder is provisioned in every user’s directory and is used to store old file versions. A user can revert to an older version through the web interface at any time, with the replaced file becoming a version. ownCloud then automatically manages the versions folder to ensure the user doesn’t run out of Quota because of versions. In addition to the expiry of versions, ownCloud’s versions app makes certain never to use more than 50% of the user’s currently available free space. If stored versions exceed this limit, ownCloud will delete the oldest versions first until it meets this limit. More information is available in the Versions documentation. @@ -15,7 +14,7 @@ In addition to the expiry of versions, ownCloud’s versions app makes certain n <filesystem/> </types> <dependencies> - <owncloud min-version="9.0" /> + <owncloud min-version="9.0" max-version="9.0" /> </dependencies> <documentation> <user>user-versions</user> diff --git a/apps/provisioning_api/appinfo/info.xml b/apps/provisioning_api/appinfo/info.xml index a77b1f67e21..a8702aaf1ef 100644 --- a/apps/provisioning_api/appinfo/info.xml +++ b/apps/provisioning_api/appinfo/info.xml @@ -13,7 +13,6 @@ </description> <licence>AGPL</licence> <author>Tom Needham</author> - <shipped>true</shipped> <default_enable/> <documentation> <admin>admin-provisioning-api</admin> @@ -24,6 +23,6 @@ <filesystem/> </types> <dependencies> - <owncloud min-version="9.0" /> + <owncloud min-version="9.0" max-version="9.0" /> </dependencies> </info> diff --git a/apps/testing/appinfo/info.xml b/apps/testing/appinfo/info.xml new file mode 100644 index 00000000000..b11ec2f88e6 --- /dev/null +++ b/apps/testing/appinfo/info.xml @@ -0,0 +1,12 @@ +<?xml version="1.0"?> +<info> + <id>testing</id> + <name>QA Testing</name> + <description>This app is only for testing! It is dangerous to have it enabled in a live instance</description> + <licence>AGPL</licence> + <author>Joas Schilling</author> + <version>0.1.0</version> + <dependencies> + <owncloud min-version="9.0" /> + </dependencies> +</info> diff --git a/apps/testing/appinfo/routes.php b/apps/testing/appinfo/routes.php new file mode 100644 index 00000000000..b6f20d04ef2 --- /dev/null +++ b/apps/testing/appinfo/routes.php @@ -0,0 +1,46 @@ +<?php +/** + * @author Joas Schilling <nickvergessen@owncloud.com> + * + * @copyright Copyright (c) 2015, 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 OCA\Testing\AppInfo; + +use OCA\Testing\Config; +use OCP\API; + +$config = new Config( + \OC::$server->getConfig(), + \OC::$server->getRequest() +); + +API::register( + 'post', + '/apps/testing/api/v1/app/{appid}/{configkey}', + [$config, 'setAppValue'], + 'testing', + API::ADMIN_AUTH +); + +API::register( + 'delete', + '/apps/testing/api/v1/app/{appid}/{configkey}', + [$config, 'deleteAppValue'], + 'testing', + API::ADMIN_AUTH +); diff --git a/apps/testing/config.php b/apps/testing/config.php new file mode 100644 index 00000000000..068cb28e04b --- /dev/null +++ b/apps/testing/config.php @@ -0,0 +1,70 @@ +<?php +/** + * @author Joas Schilling <nickvergessen@owncloud.com> + * + * @copyright Copyright (c) 2015, 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 OCA\Testing; + +use OCP\IConfig; +use OCP\IRequest; + +class Config { + + /** @var IConfig */ + private $config; + + /** @var IRequest */ + private $request; + + /** + * @param IConfig $config + * @param IRequest $request + */ + public function __construct(IConfig $config, IRequest $request) { + $this->config = $config; + $this->request = $request; + } + + /** + * @param array $parameters + * @return \OC_OCS_Result + */ + public function setAppValue($parameters) { + $app = $parameters['appid']; + $configKey = $parameters['configkey']; + + $value = $this->request->getParam('value'); + $this->config->setAppValue($app, $configKey, $value); + + return new \OC_OCS_Result(); + } + + /** + * @param array $parameters + * @return \OC_OCS_Result + */ + public function deleteAppValue($parameters) { + $app = $parameters['appid']; + $configKey = $parameters['configkey']; + + $this->config->deleteAppValue($app, $configKey); + + return new \OC_OCS_Result(); + } +} diff --git a/apps/user_ldap/appinfo/info.xml b/apps/user_ldap/appinfo/info.xml index 7dbce1da5fa..864eaebe4d0 100644 --- a/apps/user_ldap/appinfo/info.xml +++ b/apps/user_ldap/appinfo/info.xml @@ -9,7 +9,6 @@ A user logs into ownCloud with their LDAP or AD credentials, and is granted acce </description> <licence>AGPL</licence> <author>Dominik Schmidt and Arthur Schiwon</author> - <shipped>true</shipped> <version>0.8.0</version> <types> <authentication/> @@ -19,6 +18,6 @@ A user logs into ownCloud with their LDAP or AD credentials, and is granted acce </documentation> <dependencies> <lib>ldap</lib> - <owncloud min-version="9.0" /> + <owncloud min-version="9.0" max-version="9.0" /> </dependencies> </info> diff --git a/build/integration/capabilities_features/capabilities.feature b/build/integration/capabilities_features/capabilities.feature index 6c1e727e7b3..3c1eb025ec7 100644 --- a/build/integration/capabilities_features/capabilities.feature +++ b/build/integration/capabilities_features/capabilities.feature @@ -7,25 +7,211 @@ Feature: capabilities When sending "GET" to "/cloud/capabilities" Then the HTTP status code should be "200" And fields of capabilities match with - | capability | feature | value_or_subfeature | value | - | core | pollinterval | 60 | | - | core | webdav-root | remote.php/webdav | | - | files_sharing | api_enabled | 1 | | - | files_sharing | public | enabled | 1 | - | files_sharing | public | upload | 1 | - | files_sharing | resharing | 1 | | - | files_sharing | federation | outgoing | 1 | - | files_sharing | federation | incoming | 1 | - | files | bigfilechunking | 1 | | - | files | undelete | 1 | | - | files | versioning | 1 | | + | capability | path_to_element | value | + | core | pollinterval | 60 | + | core | webdav-root | remote.php/webdav | + | files_sharing | api_enabled | 1 | + | files_sharing | public@@@enabled | 1 | + | files_sharing | public@@@upload | 1 | + | files_sharing | resharing | 1 | + | files_sharing | federation@@@outgoing | 1 | + | files_sharing | federation@@@incoming | 1 | + | files | bigfilechunking | 1 | + | files | undelete | 1 | + | files | versioning | 1 | + Scenario: Changing public upload + Given As an "admin" + And parameter "shareapi_allow_public_upload" of app "core" is set to "no" + When sending "GET" to "/cloud/capabilities" + Then the HTTP status code should be "200" + And fields of capabilities match with + | capability | path_to_element | value | + | core | pollinterval | 60 | + | core | webdav-root | remote.php/webdav | + | files_sharing | api_enabled | 1 | + | files_sharing | public@@@enabled | 1 | + | files_sharing | public@@@upload | EMPTY | + | files_sharing | resharing | 1 | + | files_sharing | federation@@@outgoing | 1 | + | files_sharing | federation@@@incoming | 1 | + | files | bigfilechunking | 1 | + | files | undelete | 1 | + | files | versioning | 1 | + Scenario: Disabling share api + Given As an "admin" + And parameter "shareapi_enabled" of app "core" is set to "no" + When sending "GET" to "/cloud/capabilities" + Then the HTTP status code should be "200" + And fields of capabilities match with + | capability | path_to_element | value | + | core | pollinterval | 60 | + | core | webdav-root | remote.php/webdav | + | files_sharing | api_enabled | EMPTY | + | files_sharing | public@@@enabled | EMPTY | + | files_sharing | public@@@upload | EMPTY | + | files_sharing | resharing | EMPTY | + | files_sharing | federation@@@outgoing | 1 | + | files_sharing | federation@@@incoming | 1 | + | files | bigfilechunking | 1 | + | files | undelete | 1 | + | files | versioning | 1 | + Scenario: Disabling public links + Given As an "admin" + And parameter "shareapi_allow_links" of app "core" is set to "no" + When sending "GET" to "/cloud/capabilities" + Then the HTTP status code should be "200" + And fields of capabilities match with + | capability | path_to_element | value | + | core | pollinterval | 60 | + | core | webdav-root | remote.php/webdav | + | files_sharing | api_enabled | 1 | + | files_sharing | public@@@enabled | EMPTY | + | files_sharing | public@@@upload | EMPTY | + | files_sharing | resharing | 1 | + | files_sharing | federation@@@outgoing | 1 | + | files_sharing | federation@@@incoming | 1 | + | files | bigfilechunking | 1 | + | files | undelete | 1 | + | files | versioning | 1 | + Scenario: Changing resharing + Given As an "admin" + And parameter "shareapi_allow_resharing" of app "core" is set to "no" + When sending "GET" to "/cloud/capabilities" + Then the HTTP status code should be "200" + And fields of capabilities match with + | capability | path_to_element | value | + | core | pollinterval | 60 | + | core | webdav-root | remote.php/webdav | + | files_sharing | api_enabled | 1 | + | files_sharing | public@@@enabled | 1 | + | files_sharing | public@@@upload | 1 | + | files_sharing | resharing | EMPTY | + | files_sharing | federation@@@outgoing | 1 | + | files_sharing | federation@@@incoming | 1 | + | files | bigfilechunking | 1 | + | files | undelete | 1 | + | files | versioning | 1 | + Scenario: Changing federation outgoing + Given As an "admin" + And parameter "outgoing_server2server_share_enabled" of app "files_sharing" is set to "no" + When sending "GET" to "/cloud/capabilities" + Then the HTTP status code should be "200" + And fields of capabilities match with + | capability | path_to_element | value | + | core | pollinterval | 60 | + | core | webdav-root | remote.php/webdav | + | files_sharing | api_enabled | 1 | + | files_sharing | public@@@enabled | 1 | + | files_sharing | public@@@upload | 1 | + | files_sharing | resharing | 1 | + | files_sharing | federation@@@outgoing | EMPTY | + | files_sharing | federation@@@incoming | 1 | + | files | bigfilechunking | 1 | + | files | undelete | 1 | + | files | versioning | 1 | + Scenario: Changing federation incoming + Given As an "admin" + And parameter "incoming_server2server_share_enabled" of app "files_sharing" is set to "no" + When sending "GET" to "/cloud/capabilities" + Then the HTTP status code should be "200" + And fields of capabilities match with + | capability | path_to_element | value | + | core | pollinterval | 60 | + | core | webdav-root | remote.php/webdav | + | files_sharing | api_enabled | 1 | + | files_sharing | public@@@enabled | 1 | + | files_sharing | public@@@upload | 1 | + | files_sharing | resharing | 1 | + | files_sharing | federation@@@outgoing | 1 | + | files_sharing | federation@@@incoming | EMPTY | + | files | bigfilechunking | 1 | + | files | undelete | 1 | + | files | versioning | 1 | + Scenario: Changing password enforce + Given As an "admin" + And parameter "shareapi_enforce_links_password" of app "core" is set to "yes" + When sending "GET" to "/cloud/capabilities" + Then the HTTP status code should be "200" + And fields of capabilities match with + | capability | path_to_element | value | + | core | pollinterval | 60 | + | core | webdav-root | remote.php/webdav | + | files_sharing | api_enabled | 1 | + | files_sharing | public@@@enabled | 1 | + | files_sharing | public@@@upload | 1 | + | files_sharing | public@@@password@@@enforced | 1 | + | files_sharing | resharing | 1 | + | files_sharing | federation@@@outgoing | 1 | + | files_sharing | federation@@@incoming | 1 | + | files | bigfilechunking | 1 | + | files | undelete | 1 | + | files | versioning | 1 | + Scenario: Changing public notifications + Given As an "admin" + And parameter "shareapi_allow_public_notification" of app "core" is set to "yes" + When sending "GET" to "/cloud/capabilities" + Then the HTTP status code should be "200" + And fields of capabilities match with + | capability | path_to_element | value | + | core | pollinterval | 60 | + | core | webdav-root | remote.php/webdav | + | files_sharing | api_enabled | 1 | + | files_sharing | public@@@enabled | 1 | + | files_sharing | public@@@upload | 1 | + | files_sharing | public@@@send_mail | 1 | + | files_sharing | resharing | 1 | + | files_sharing | federation@@@outgoing | 1 | + | files_sharing | federation@@@incoming | 1 | + | files | bigfilechunking | 1 | + | files | undelete | 1 | + | files | versioning | 1 | + Scenario: Changing expire date + Given As an "admin" + And parameter "shareapi_default_expire_date" of app "core" is set to "yes" + When sending "GET" to "/cloud/capabilities" + Then the HTTP status code should be "200" + And fields of capabilities match with + | capability | path_to_element | value | + | core | pollinterval | 60 | + | core | webdav-root | remote.php/webdav | + | files_sharing | api_enabled | 1 | + | files_sharing | public@@@enabled | 1 | + | files_sharing | public@@@upload | 1 | + | files_sharing | public@@@expire_date@@@enabled | 1 | + | files_sharing | resharing | 1 | + | files_sharing | federation@@@outgoing | 1 | + | files_sharing | federation@@@incoming | 1 | + | files | bigfilechunking | 1 | + | files | undelete | 1 | + | files | versioning | 1 | + Scenario: Changing expire date enforcing + Given As an "admin" + And parameter "shareapi_default_expire_date" of app "core" is set to "yes" + And parameter "shareapi_enforce_expire_date" of app "core" is set to "yes" + When sending "GET" to "/cloud/capabilities" + Then the HTTP status code should be "200" + And fields of capabilities match with + | capability | path_to_element | value | + | core | pollinterval | 60 | + | core | webdav-root | remote.php/webdav | + | files_sharing | api_enabled | 1 | + | files_sharing | public@@@enabled | 1 | + | files_sharing | public@@@upload | 1 | + | files_sharing | public@@@expire_date@@@enabled | 1 | + | files_sharing | public@@@expire_date@@@enforced | 1 | + | files_sharing | resharing | 1 | + | files_sharing | federation@@@outgoing | 1 | + | files_sharing | federation@@@incoming | 1 | + | files | bigfilechunking | 1 | + | files | undelete | 1 | + | files | versioning | 1 | diff --git a/build/integration/features/bootstrap/CapabilitiesContext.php b/build/integration/features/bootstrap/CapabilitiesContext.php index 1b0015dce73..d30984f0db7 100644 --- a/build/integration/features/bootstrap/CapabilitiesContext.php +++ b/build/integration/features/bootstrap/CapabilitiesContext.php @@ -2,6 +2,10 @@ use Behat\Behat\Context\Context; use Behat\Behat\Context\SnippetAcceptingContext; +use Behat\Behat\Hook\Scope\AfterScenarioScope; +use Behat\Behat\Hook\Scope\BeforeScenarioScope; +use GuzzleHttp\Client; +use GuzzleHttp\Message\ResponseInterface; require __DIR__ . '/../../vendor/autoload.php'; @@ -15,33 +19,116 @@ class CapabilitiesContext implements Context, SnippetAcceptingContext { use Sharing; /** + * @Given /^parameter "([^"]*)" of app "([^"]*)" is set to "([^"]*)"$/ + */ + public function serverParameterIsSetTo($parameter, $app, $value){ + $user = $this->currentUser; + $this->currentUser = 'admin'; + + $this->modifyServerConfig($app, $parameter, $value); + + $this->currentUser = $user; + } + + /** * @Then /^fields of capabilities match with$/ * @param \Behat\Gherkin\Node\TableNode|null $formData */ - public function checkCapabilitiesResponse($formData){ - if ($formData instanceof \Behat\Gherkin\Node\TableNode) { - $fd = $formData->getHash(); - } - + public function checkCapabilitiesResponse(\Behat\Gherkin\Node\TableNode $formData){ $capabilitiesXML = $this->response->xml()->data->capabilities; - - foreach ($fd as $row) { - if ($row['value'] === ''){ - $answeredValue = (string)$capabilitiesXML->$row['capability']->$row['feature']; - PHPUnit_Framework_Assert::assertEquals( - $answeredValue, - $row['value_or_subfeature'], - "Failed field " . $row['capability'] . " " . $row['feature'] - ); - } else{ - $answeredValue = (string)$capabilitiesXML->$row['capability']->$row['feature']->$row['value_or_subfeature']; - PHPUnit_Framework_Assert::assertEquals( - $answeredValue, - $row['value'], - "Failed field: " . $row['capability'] . " " . $row['feature'] . " " . $row['value_or_subfeature'] - ); + + foreach ($formData->getHash() as $row) { + $path_to_element = explode('@@@', $row['path_to_element']); + $answeredValue = $capabilitiesXML->$row['capability']; + for ($i = 0; $i < count($path_to_element); $i++){ + $answeredValue = $answeredValue->$path_to_element[$i]; } + $answeredValue = (string)$answeredValue; + PHPUnit_Framework_Assert::assertEquals( + $row['value']==="EMPTY" ? '' : $row['value'], + $answeredValue, + "Failed field " . $row['capability'] . " " . $row['path_to_element'] + ); + } } + protected function resetAppConfigs() { + $this->modifyServerConfig('core', 'shareapi_enabled', 'yes'); + $this->modifyServerConfig('core', 'shareapi_allow_links', 'yes'); + $this->modifyServerConfig('core', 'shareapi_allow_public_upload', 'yes'); + $this->modifyServerConfig('core', 'shareapi_allow_resharing', 'yes'); + $this->modifyServerConfig('files_sharing', 'outgoing_server2server_share_enabled', 'yes'); + $this->modifyServerConfig('files_sharing', 'incoming_server2server_share_enabled', 'yes'); + $this->modifyServerConfig('core', 'shareapi_enforce_links_password', 'no'); + $this->modifyServerConfig('core', 'shareapi_allow_public_notification', 'no'); + $this->modifyServerConfig('core', 'shareapi_default_expire_date', 'no'); + $this->modifyServerConfig('core', 'shareapi_enforce_expire_date', 'no'); + } + + /** + * @BeforeScenario + * + * Enable the testing app before the first scenario of the feature and + * reset the configs before each scenario + * @param BeforeScenarioScope $event + */ + public function prepareParameters(BeforeScenarioScope $event){ + $user = $this->currentUser; + $this->currentUser = 'admin'; + + $scenarios = $event->getFeature()->getScenarios(); + if ($event->getScenario() === reset($scenarios)) { + $this->setStatusTestingApp(true); + } + + $this->resetAppConfigs(); + + $this->currentUser = $user; + } + + /** + * @AfterScenario + * + * Reset the values after the last scenario of the feature and disable the testing app + * @param AfterScenarioScope $event + */ + public function undoChangingParameters(AfterScenarioScope $event) { + $scenarios = $event->getFeature()->getScenarios(); + if ($event->getScenario() === end($scenarios)) { + $user = $this->currentUser; + $this->currentUser = 'admin'; + + $this->resetAppConfigs(); + + $this->setStatusTestingApp(false); + $this->currentUser = $user; + } + } + + /** + * @param string $app + * @param string $parameter + * @param string $value + */ + protected function modifyServerConfig($app, $parameter, $value) { + $body = new \Behat\Gherkin\Node\TableNode([['value', $value]]); + $this->sendingToWith('post', "/apps/testing/api/v1/app/{$app}/{$parameter}", $body); + $this->theHTTPStatusCodeShouldBe('200'); + $this->theOCSStatusCodeShouldBe('100'); + } + + protected function setStatusTestingApp($enabled) { + $this->sendingTo(($enabled ? 'post' : 'delete'), '/cloud/apps/testing'); + $this->theHTTPStatusCodeShouldBe('200'); + $this->theOCSStatusCodeShouldBe('100'); + + $this->sendingTo('get', '/cloud/apps?filter=enabled'); + $this->theHTTPStatusCodeShouldBe('200'); + if ($enabled) { + PHPUnit_Framework_Assert::assertContains('testing', $this->response->getBody()->getContents()); + } else { + PHPUnit_Framework_Assert::assertNotContains('testing', $this->response->getBody()->getContents()); + } + } } diff --git a/build/integration/features/bootstrap/Provisioning.php b/build/integration/features/bootstrap/Provisioning.php index 9a21c0bb1d4..65a6611b06c 100644 --- a/build/integration/features/bootstrap/Provisioning.php +++ b/build/integration/features/bootstrap/Provisioning.php @@ -90,7 +90,13 @@ trait Provisioning { } elseif ($this->currentServer === 'REMOTE') { $this->createdRemoteUsers[$user] = $user; } - + + //Quick hack to login once with the current user + $options2 = [ + 'auth' => [$user, '123456'], + ]; + $url = $fullUrl.'/'.$user; + $client->send($client->createRequest('GET', $url, $options2)); } public function createUser($user) { diff --git a/build/integration/features/provisioning-v1.feature b/build/integration/features/provisioning-v1.feature index 2a3e8e07fc4..467ac448e94 100644 --- a/build/integration/features/provisioning-v1.feature +++ b/build/integration/features/provisioning-v1.feature @@ -291,8 +291,3 @@ Feature: provisioning Then the OCS status code should be "100" And the HTTP status code should be "200" And app "files_external" is disabled - - - - - diff --git a/build/integration/features/sharing-v1.feature b/build/integration/features/sharing-v1.feature index 31ba0d4ad7f..65a8459e9a1 100644 --- a/build/integration/features/sharing-v1.feature +++ b/build/integration/features/sharing-v1.feature @@ -231,20 +231,23 @@ Feature: sharing And User "user2" should be included in the response And User "user3" should not be included in the response - Scenario: getting all shares of a file with reshares - Given user "user0" exists - And user "user1" exists - And user "user2" exists - And user "user3" exists - And file "textfile0.txt" of user "user0" is shared with user "user1" - And file "textfile0.txt" of user "user1" is shared with user "user2" - And As an "user0" - When sending "GET" to "/apps/files_sharing/api/v1/shares?reshares=true&path=textfile0.txt" - Then the OCS status code should be "100" - And the HTTP status code should be "200" - And User "user1" should be included in the response - And User "user2" should be included in the response - And User "user3" should not be included in the response +# Skip this test for now. Since the new shares do not create reshares +# TODO enable when getshares is updated +# +# Scenario: getting all shares of a file with reshares +# Given user "user0" exists +# And user "user1" exists +# And user "user2" exists +# And user "user3" exists +# And file "textfile0.txt" of user "user0" is shared with user "user1" +# And file "textfile0.txt" of user "user1" is shared with user "user2" +# And As an "user0" +# When sending "GET" to "/apps/files_sharing/api/v1/shares?reshares=true&path=textfile0.txt" +# Then the OCS status code should be "100" +# And the HTTP status code should be "200" +# And User "user1" should be included in the response +# And User "user2" should be included in the response +# And User "user3" should not be included in the response Scenario: getting share info of a share Given user "user0" exists @@ -263,7 +266,7 @@ Feature: sharing | file_source | A_NUMBER | | file_target | /textfile0.txt | | path | /textfile0.txt | - | permissions | 23 | + | permissions | 19 | | stime | A_NUMBER | | storage | A_NUMBER | | mail_send | 0 | @@ -326,7 +329,7 @@ Feature: sharing | permissions | 8 | And As an "user1" When creating a share with - | path | /textfile0. (2).txt | + | path | /textfile0 (2).txt | | shareType | 0 | | shareWith | user2 | | permissions | 31 | @@ -346,7 +349,7 @@ Feature: sharing | permissions | 16 | And As an "user1" When creating a share with - | path | /textfile0. (2).txt | + | path | /textfile0 (2).txt | | shareType | 0 | | shareWith | user2 | | permissions | 31 | diff --git a/core/command/app/checkcode.php b/core/command/app/checkcode.php index e186d458c01..bc3ae07890c 100644 --- a/core/command/app/checkcode.php +++ b/core/command/app/checkcode.php @@ -131,6 +131,10 @@ class CheckCode extends Command { } }); + $infoChecker->listen('InfoChecker', 'missingRequirement', function($minMax) use ($output) { + $output->writeln("<comment>ownCloud $minMax version requirement missing (will be an error in ownCloud 11 and later)</comment>"); + }); + $infoChecker->listen('InfoChecker', 'duplicateRequirement', function($minMax) use ($output) { $output->writeln("<error>Duplicate $minMax ownCloud version requirement found</error>"); }); diff --git a/db_structure.xml b/db_structure.xml index be7208aa22e..e4bd8d998ee 100644 --- a/db_structure.xml +++ b/db_structure.xml @@ -684,7 +684,8 @@ </field> <!-- Foreign Key users::uid --> - <!-- This is the initiator of the share --> + <!-- This is the owner of the share + which does not have to be the initiator of the share --> <field> <name>uid_owner</name> <type>text</type> @@ -694,18 +695,17 @@ </field> <!-- Foreign Key users::uid --> - <!-- This is the owner of the file, this can be - different from the initiator of the share. - The naming is subobtimal but prevents huge - migration steps --> + <!-- This is the initiator of the share --> <field> - <name>uid_fileowner</name> + <name>uid_initiator</name> <type>text</type> <default></default> <notnull>false</notnull> <length>64</length> </field> + + <!-- Foreign Key share::id or NULL --> <field> <name>parent</name> diff --git a/lib/private/app/codechecker/infochecker.php b/lib/private/app/codechecker/infochecker.php index 24835d8148f..2589277118b 100644 --- a/lib/private/app/codechecker/infochecker.php +++ b/lib/private/app/codechecker/infochecker.php @@ -83,13 +83,18 @@ class InfoChecker extends BasicEmitter { 'type' => 'duplicateRequirement', 'field' => 'min', ]; + } else if (!isset($info['dependencies']['owncloud']['@attributes']['min-version'])) { + $this->emit('InfoChecker', 'missingRequirement', ['min']); } + if (isset($info['dependencies']['owncloud']['@attributes']['max-version']) && $info['requiremax']) { $this->emit('InfoChecker', 'duplicateRequirement', ['max']); $errors[] = [ 'type' => 'duplicateRequirement', 'field' => 'max', ]; + } else if (!isset($info['dependencies']['owncloud']['@attributes']['max-version'])) { + $this->emit('InfoChecker', 'missingRequirement', ['max']); } foreach ($info as $key => $value) { diff --git a/lib/private/appframework/dependencyinjection/dicontainer.php b/lib/private/appframework/dependencyinjection/dicontainer.php index 88ffc1c6f98..69476d84c9b 100644 --- a/lib/private/appframework/dependencyinjection/dicontainer.php +++ b/lib/private/appframework/dependencyinjection/dicontainer.php @@ -165,6 +165,10 @@ class DIContainer extends SimpleContainer implements IAppContainer { $this->registerAlias('OCP\\AppFramework\\Utility\\IControllerMethodReflector', 'OC\AppFramework\Utility\ControllerMethodReflector'); $this->registerAlias('ControllerMethodReflector', 'OCP\\AppFramework\\Utility\\IControllerMethodReflector'); + $this->registerService('OCP\\Files\\IMimeTypeDetector', function($c) { + return $this->getServer()->getMimeTypeDetector(); + }); + $this->registerService('OCP\\INavigationManager', function($c) { return $this->getServer()->getNavigationManager(); }); diff --git a/lib/private/share20/defaultshareprovider.php b/lib/private/share20/defaultshareprovider.php index bc3bc0ce9ed..a7155644920 100644 --- a/lib/private/share20/defaultshareprovider.php +++ b/lib/private/share20/defaultshareprovider.php @@ -64,11 +64,90 @@ class DefaultShareProvider implements IShareProvider { /** * Share a path - * + * * @param IShare $share * @return IShare The share object + * @throws ShareNotFound + * @throws \Exception */ public function create(IShare $share) { + $qb = $this->dbConn->getQueryBuilder(); + + $qb->insert('share'); + $qb->setValue('share_type', $qb->createNamedParameter($share->getShareType())); + + if ($share->getShareType() === \OCP\Share::SHARE_TYPE_USER) { + //Set the UID of the user we share with + $qb->setValue('share_with', $qb->createNamedParameter($share->getSharedWith()->getUID())); + } else if ($share->getShareType() === \OCP\Share::SHARE_TYPE_GROUP) { + //Set the GID of the group we share with + $qb->setValue('share_with', $qb->createNamedParameter($share->getSharedWith()->getGID())); + } else if ($share->getShareType() === \OCP\Share::SHARE_TYPE_LINK) { + //Set the token of the share + $qb->setValue('token', $qb->createNamedParameter($share->getToken())); + + //If a password is set store it + if ($share->getPassword() !== null) { + $qb->setValue('share_with', $qb->createNamedParameter($share->getPassword())); + } + + //If an expiration date is set store it + if ($share->getExpirationDate() !== null) { + $qb->setValue('expiration', $qb->createNamedParameter($share->getExpirationDate(), 'datetime')); + } + } else { + throw new \Exception('invalid share type!'); + } + + // Set what is shares + $qb->setValue('item_type', $qb->createParameter('itemType')); + if ($share->getPath() instanceof \OCP\Files\File) { + $qb->setParameter('itemType', 'file'); + } else { + $qb->setParameter('itemType', 'folder'); + } + + // Set the file id + $qb->setValue('item_source', $qb->createNamedParameter($share->getPath()->getId())); + $qb->setValue('file_source', $qb->createNamedParameter($share->getPath()->getId())); + + // set the permissions + $qb->setValue('permissions', $qb->createNamedParameter($share->getPermissions())); + + // Set who created this share + $qb->setValue('uid_initiator', $qb->createNamedParameter($share->getSharedBy()->getUID())); + + // Set who is the owner of this file/folder (and this the owner of the share) + $qb->setValue('uid_owner', $qb->createNamedParameter($share->getShareOwner()->getUID())); + + // Set the file target + $qb->setValue('file_target', $qb->createNamedParameter($share->getTarget())); + + // Set the time this share was created + $qb->setValue('stime', $qb->createNamedParameter(time())); + + // insert the data and fetch the id of the share + $this->dbConn->beginTransaction(); + $qb->execute(); + $id = $this->dbConn->lastInsertId('*PREFIX*share'); + $this->dbConn->commit(); + + // Now fetch the inserted share and create a complete share object + $qb = $this->dbConn->getQueryBuilder(); + $qb->select('*') + ->from('*PREFIX*share') + ->where($qb->expr()->eq('id', $qb->createNamedParameter($id))); + + $cursor = $qb->execute(); + $data = $cursor->fetch(); + $cursor->closeCursor(); + + if ($data === false) { + throw new ShareNotFound(); + } + + $share = $this->createShare($data); + return $share; } /** @@ -170,11 +249,29 @@ class DefaultShareProvider implements IShareProvider { /** * Get shares for a given path * - * @param \OCP\IUser $user * @param \OCP\Files\Node $path * @return IShare[] */ - public function getSharesByPath(IUser $user, Node $path) { + public function getSharesByPath(Node $path) { + $qb = $this->dbConn->getQueryBuilder(); + + $cursor = $qb->select('*') + ->from('share') + ->andWhere($qb->expr()->eq('file_source', $qb->createNamedParameter($path->getId()))) + ->andWhere( + $qb->expr()->orX( + $qb->expr()->eq('share_type', $qb->createNamedParameter(\OCP\Share::SHARE_TYPE_USER)), + $qb->expr()->eq('share_type', $qb->createNamedParameter(\OCP\Share::SHARE_TYPE_GROUP)) + ) + )->execute(); + + $shares = []; + while($data = $cursor->fetch()) { + $shares[] = $this->createShare($data); + } + $cursor->closeCursor(); + + return $shares; } /** @@ -223,16 +320,21 @@ class DefaultShareProvider implements IShareProvider { $share->setSharedWith($data['share_with']); } - $share->setSharedBy($this->userManager->get($data['uid_owner'])); - - // TODO: getById can return an array. How to handle this properly?? - $folder = $this->rootFolder->getUserFolder($share->getSharedBy()->getUID()); - $path = $folder->getById((int)$data['file_source'])[0]; + if ($data['uid_initiator'] === null) { + //OLD SHARE + $share->setSharedBy($this->userManager->get($data['uid_owner'])); + $folder = $this->rootFolder->getUserFolder($share->getSharedBy()->getUID()); + $path = $folder->getById((int)$data['file_source'])[0]; - $owner = $path->getOwner(); - $share->setShareOwner($owner); + $owner = $path->getOwner(); + $share->setShareOwner($owner); + } else { + //New share! + $share->setSharedBy($this->userManager->get($data['uid_initiator'])); + $share->setShareOwner($this->userManager->get($data['uid_owner'])); + } - $path = $this->rootFolder->getUserFolder($owner->getUID())->getById((int)$data['file_source'])[0]; + $path = $this->rootFolder->getUserFolder($share->getShareOwner()->getUID())->getById((int)$data['file_source'])[0]; $share->setPath($path); if ($data['expiration'] !== null) { diff --git a/lib/private/share20/ishare.php b/lib/private/share20/ishare.php index 2e54da7a029..a149c578fb2 100644 --- a/lib/private/share20/ishare.php +++ b/lib/private/share20/ishare.php @@ -101,7 +101,7 @@ interface IShare { * @param \DateTime $expireDate * @return Share The modified object */ - public function setExpirationDate(\DateTime $expireDate); + public function setExpirationDate($expireDate); /** * Get the share expiration date @@ -111,6 +111,14 @@ interface IShare { public function getExpirationDate(); /** + * Set the sharer of the path + * + * @param IUser|string $sharedBy + * @return Share The modified object + */ + public function setSharedBy($sharedBy); + + /** * Get share sharer * * @return IUser|string @@ -118,6 +126,15 @@ interface IShare { public function getSharedBy(); /** + * Set the original share owner (who owns the path) + * + * @param IUser|string + * + * @return Share The modified object + */ + public function setShareOwner($shareOwner); + + /** * Get the original share owner (who owns the path) * * @return IUser|string @@ -141,6 +158,14 @@ interface IShare { public function getPassword(); /** + * Set the token + * + * @param string $token + * @return Share The modified object + */ + public function setToken($token); + + /** * Get the token * * @return string @@ -155,6 +180,14 @@ interface IShare { public function getParent(); /** + * Set the target of this share + * + * @param string $target + * @return Share The modified object + */ + public function setTarget($target); + + /** * Get the target of this share * * @return string diff --git a/lib/private/share20/ishareprovider.php b/lib/private/share20/ishareprovider.php index 56a550acf71..97a2b728d5f 100644 --- a/lib/private/share20/ishareprovider.php +++ b/lib/private/share20/ishareprovider.php @@ -81,11 +81,10 @@ interface IShareProvider { /** * Get shares for a given path * - * @param \OCP\IUser $user * @param \OCP\Files\Node $path * @return IShare[] */ - public function getSharesByPath(\OCP\IUser $user, \OCP\Files\Node $path); + public function getSharesByPath(\OCP\Files\Node $path); /** * Get shared with the given user diff --git a/lib/private/share20/manager.php b/lib/private/share20/manager.php index 882b281c490..8d753061c0c 100644 --- a/lib/private/share20/manager.php +++ b/lib/private/share20/manager.php @@ -21,53 +21,458 @@ namespace OC\Share20; -use OCP\IAppConfig; +use OCP\IConfig; +use OCP\IL10N; use OCP\ILogger; +use OCP\Security\ISecureRandom; +use OCP\Security\IHasher; +use OCP\Files\Mount\IMountManager; +use OCP\IGroupManager; +use OCP\Files\File; +use OCP\Files\Folder; +use OCP\IUser; use OC\Share20\Exception\ShareNotFound; +use OC\HintException; /** * This class is the communication hub for all sharing related operations. */ class Manager { - /** - * @var IShareProvider[] - */ + /** @var IShareProvider[] */ private $defaultProvider; /** @var ILogger */ private $logger; - /** @var IAppConfig */ - private $appConfig; + /** @var IConfig */ + private $config; + + /** @var ISecureRandom */ + private $secureRandom; + + /** @var IHasher */ + private $hasher; + + /** @var IMountManager */ + private $mountManager; + + /** @var IGroupManager */ + private $groupManager; + + /** @var IL10N */ + private $l; /** * Manager constructor. * * @param ILogger $logger - * @param IAppConfig $appConfig + * @param IConfig $config * @param IShareProvider $defaultProvider + * @param ISecureRandom $secureRandom + * @param IHasher $hasher + * @param IMountManager $mountManager + * @param IGroupManager $groupManager + * @param IL10N $l */ public function __construct( ILogger $logger, - IAppConfig $appConfig, - IShareProvider $defaultProvider + IConfig $config, + IShareProvider $defaultProvider, + ISecureRandom $secureRandom, + IHasher $hasher, + IMountManager $mountManager, + IGroupManager $groupManager, + IL10N $l ) { $this->logger = $logger; - $this->appConfig = $appConfig; + $this->config = $config; + $this->secureRandom = $secureRandom; + $this->hasher = $hasher; + $this->mountManager = $mountManager; + $this->groupManager = $groupManager; + $this->l = $l; // TEMP SOLUTION JUST TO GET STARTED $this->defaultProvider = $defaultProvider; } /** + * Verify if a password meets all requirements + * + * @param string $password + * @throws \Exception + */ + protected function verifyPassword($password) { + if ($password === null) { + // No password is set, check if this is allowed. + if ($this->shareApiLinkEnforcePassword()) { + throw new \InvalidArgumentException('Passwords are enforced for link shares'); + } + + return; + } + + // Let others verify the password + $accepted = true; + $message = ''; + \OCP\Util::emitHook('\OC\Share', 'verifyPassword', [ + 'password' => $password, + 'accepted' => &$accepted, + 'message' => &$message + ]); + + if (!$accepted) { + throw new \Exception($message); + } + } + + /** + * Check for generic requirements before creating a share + * + * @param IShare $share + * @throws \Exception + */ + protected function generalCreateChecks(IShare $share) { + if ($share->getShareType() === \OCP\Share::SHARE_TYPE_USER) { + // We expect a valid user as sharedWith for user shares + if (!($share->getSharedWith() instanceof \OCP\IUser)) { + throw new \InvalidArgumentException('SharedWith should be an IUser'); + } + } else if ($share->getShareType() === \OCP\Share::SHARE_TYPE_GROUP) { + // We expect a valid group as sharedWith for group shares + if (!($share->getSharedWith() instanceof \OCP\IGroup)) { + throw new \InvalidArgumentException('SharedWith should be an IGroup'); + } + } else if ($share->getShareType() === \OCP\Share::SHARE_TYPE_LINK) { + if ($share->getSharedWith() !== null) { + throw new \InvalidArgumentException('SharedWith should be empty'); + } + } else { + // We can't handle other types yet + throw new \InvalidArgumentException('unkown share type'); + } + + // Verify the initiator of the share is et + if ($share->getSharedBy() === null) { + throw new \InvalidArgumentException('SharedBy should be set'); + } + + // Cannot share with yourself + if ($share->getSharedWith() === $share->getSharedBy()) { + throw new \InvalidArgumentException('Can\'t share with yourself'); + } + + // The path should be set + if ($share->getPath() === null) { + throw new \InvalidArgumentException('Path should be set'); + } + // And it should be a file or a folder + if (!($share->getPath() instanceof \OCP\Files\File) && + !($share->getPath() instanceof \OCP\Files\Folder)) { + throw new \InvalidArgumentException('Path should be either a file or a folder'); + } + + // Check if we actually have share permissions + if (!$share->getPath()->isShareable()) { + $message_t = $this->l->t('You are not allowed to share %s', [$share->getPath()->getPath()]); + throw new HintException($message_t, $message_t, 404); + } + + // Permissions should be set + if ($share->getPermissions() === null) { + throw new \InvalidArgumentException('A share requires permissions'); + } + + // Check that we do not share with more permissions than we have + if ($share->getPermissions() & ~$share->getPath()->getPermissions()) { + $message_t = $this->l->t('Cannot increase permissions of %s', [$share->getPath()->getPath()]); + throw new HintException($message_t, $message_t, 404); + } + + // Check that read permissions are always set + if (($share->getPermissions() & \OCP\Constants::PERMISSION_READ) === 0) { + throw new \InvalidArgumentException('Shares need at least read permissions'); + } + } + + /** + * Validate if the expiration date fits the system settings + * + * @param \DateTime $expireDate The current expiration date (can be null) + * @return \DateTime|null The expiration date or null if $expireDate was null and it is not required + * @throws \OC\HintException + */ + protected function validateExpiredate($expireDate) { + + if ($expireDate !== null) { + //Make sure the expiration date is a date + $expireDate->setTime(0, 0, 0); + + $date = new \DateTime(); + $date->setTime(0, 0, 0); + if ($date >= $expireDate) { + $message = $this->l->t('Expiration date is in the past'); + throw new \OC\HintException($message, $message, 404); + } + } + + // If we enforce the expiration date check that is does not exceed + if ($this->shareApiLinkDefaultExpireDateEnforced()) { + if ($expireDate === null) { + throw new \InvalidArgumentException('Expiration date is enforced'); + } + + $date = new \DateTime(); + $date->setTime(0, 0, 0); + $date->add(new \DateInterval('P' . $this->shareApiLinkDefaultExpireDays() . 'D')); + if ($date < $expireDate) { + $message = $this->l->t('Cannot set expiration date more than %s days in the future', [$this->shareApiLinkDefaultExpireDays()]); + throw new \OC\HintException($message, $message, 404); + } + + return $expireDate; + } + + // If expiredate is empty set a default one if there is a default + if ($expireDate === null && $this->shareApiLinkDefaultExpireDate()) { + $date = new \DateTime(); + $date->setTime(0,0,0); + $date->add(new \DateInterval('P'.$this->shareApiLinkDefaultExpireDays().'D')); + return $date; + } + + return $expireDate; + } + + + /** + * Check for pre share requirements for use shares + * + * @param IShare $share + * @throws \Exception + */ + protected function userCreateChecks(IShare $share) { + // Check if we can share with group members only + if ($this->shareWithGroupMembersOnly()) { + // Verify we can share with this user + $groups = array_intersect( + $this->groupManager->getUserGroupIds($share->getSharedBy()), + $this->groupManager->getUserGroupIds($share->getSharedWith()) + ); + if (empty($groups)) { + throw new \Exception('Only sharing with group members is allowed'); + } + } + + /* + * TODO: Could be costly, fix + * + * Also this is not what we want in the future.. then we want to squash identical shares. + */ + $existingShares = $this->defaultProvider->getSharesByPath($share->getPath()); + foreach($existingShares as $existingShare) { + // Identical share already existst + if ($existingShare->getSharedWith() === $share->getSharedWith()) { + throw new \Exception('Path already shared with this user'); + } + + // The share is already shared with this user via a group share + if ($existingShare->getShareType() === \OCP\Share::SHARE_TYPE_GROUP && + $existingShare->getSharedWith()->inGroup($share->getSharedWith()) && + $existingShare->getShareOwner() !== $share->getShareOwner()) { + throw new \Exception('Path already shared with this user'); + } + } + } + + /** + * Check for pre share requirements for group shares + * + * @param IShare $share + * @throws \Exception + */ + protected function groupCreateChecks(IShare $share) { + // Verify if the user can share with this group + if ($this->shareWithGroupMembersOnly()) { + if (!$share->getSharedWith()->inGroup($share->getSharedBy())) { + throw new \Exception('Only sharing within your own groups is allowed'); + } + } + + /* + * TODO: Could be costly, fix + * + * Also this is not what we want in the future.. then we want to squash identical shares. + */ + $existingShares = $this->defaultProvider->getSharesByPath($share->getPath()); + foreach($existingShares as $existingShare) { + if ($existingShare->getSharedWith() === $share->getSharedWith()) { + throw new \Exception('Path already shared with this group'); + } + } + } + + /** + * Check for pre share requirements for link shares + * + * @param IShare $share + * @throws \Exception + */ + protected function linkCreateChecks(IShare $share) { + // Are link shares allowed? + if (!$this->shareApiAllowLinks()) { + throw new \Exception('Link sharing not allowed'); + } + + // Link shares by definition can't have share permissions + if ($share->getPermissions() & \OCP\Constants::PERMISSION_SHARE) { + throw new \InvalidArgumentException('Link shares can\'t have reshare permissions'); + } + + // We don't allow deletion on link shares + if ($share->getPermissions() & \OCP\Constants::PERMISSION_DELETE) { + throw new \InvalidArgumentException('Link shares can\'t have delete permissions'); + } + + // Check if public upload is allowed + if (!$this->shareApiLinkAllowPublicUpload() && + ($share->getPermissions() & (\OCP\Constants::PERMISSION_CREATE | \OCP\Constants::PERMISSION_UPDATE))) { + throw new \InvalidArgumentException('Public upload not allowed'); + } + } + + /** + * @param File|Folder $path + */ + protected function pathCreateChecks($path) { + // Make sure that we do not share a path that contains a shared mountpoint + if ($path instanceof \OCP\Files\Folder) { + $mounts = $this->mountManager->findIn($path->getPath()); + foreach($mounts as $mount) { + if ($mount->getStorage()->instanceOfStorage('\OCA\Files_Sharing\ISharedStorage')) { + throw new \InvalidArgumentException('Path contains files shared with you'); + } + } + } + } + + /** + * Check if the user that is sharing can actually share + * + * @param IShare $share + * @return bool + */ + protected function canShare(IShare $share) { + if (!$this->shareApiEnabled()) { + return false; + } + + if ($this->isSharingDisabledForUser($share->getSharedBy())) { + return false; + } + + return true; + } + + /** * Share a path * - * @param Share $share + * @param IShare $share * @return Share The share object + * @throws \Exception + * + * TODO: handle link share permissions or check them */ - public function createShare(Share $share) { + public function createShare(IShare $share) { + if (!$this->canShare($share)) { + throw new \Exception('The Share API is disabled'); + } + + $this->generalCreateChecks($share); + + //Verify share type + if ($share->getShareType() === \OCP\Share::SHARE_TYPE_USER) { + $this->userCreateChecks($share); + } else if ($share->getShareType() === \OCP\Share::SHARE_TYPE_GROUP) { + $this->groupCreateChecks($share); + } else if ($share->getShareType() === \OCP\Share::SHARE_TYPE_LINK) { + $this->linkCreateChecks($share); + + /* + * For now ignore a set token. + */ + $share->setToken( + $this->secureRandom->generate( + \OC\Share\Constants::TOKEN_LENGTH, + \OCP\Security\ISecureRandom::CHAR_LOWER. + \OCP\Security\ISecureRandom::CHAR_UPPER. + \OCP\Security\ISecureRandom::CHAR_DIGITS + ) + ); + + //Verify the expiration date + $share->setExpirationDate($this->validateExpiredate($share->getExpirationDate())); + + //Verify the password + $this->verifyPassword($share->getPassword()); + + // If a password is set. Hash it! + if ($share->getPassword() !== null) { + $share->setPassword($this->hasher->hash($share->getPassword())); + } + } + + // Verify if there are any issues with the path + $this->pathCreateChecks($share->getPath()); + + // On creation of a share the owner is always the owner of the path + $share->setShareOwner($share->getPath()->getOwner()); + + // Generate the target + $target = $this->config->getSystemValue('share_folder', '/') .'/'. $share->getPath()->getName(); + $target = \OC\Files\Filesystem::normalizePath($target); + $share->setTarget($target); + + // Pre share hook + $run = true; + $error = ''; + $preHookData = [ + 'itemType' => $share->getPath() instanceof \OCP\Files\File ? 'file' : 'folder', + 'itemSource' => $share->getPath()->getId(), + 'shareType' => $share->getShareType(), + 'uidOwner' => $share->getSharedBy()->getUID(), + 'permissions' => $share->getPermissions(), + 'fileSource' => $share->getPath()->getId(), + 'expiration' => $share->getExpirationDate(), + 'token' => $share->getToken(), + 'run' => &$run, + 'error' => &$error + ]; + \OC_Hook::emit('OCP\Share', 'pre_shared', $preHookData); + + if ($run === false) { + throw new \Exception($error); + } + + $share = $this->defaultProvider->create($share); + + // Post share hook + $postHookData = [ + 'itemType' => $share->getPath() instanceof \OCP\Files\File ? 'file' : 'folder', + 'itemSource' => $share->getPath()->getId(), + 'shareType' => $share->getShareType(), + 'uidOwner' => $share->getSharedBy()->getUID(), + 'permissions' => $share->getPermissions(), + 'fileSource' => $share->getPath()->getId(), + 'expiration' => $share->getExpirationDate(), + 'token' => $share->getToken(), + 'id' => $share->getId(), + ]; + \OC_Hook::emit('OCP\Share', 'post_shared', $postHookData); + + return $share; } /** @@ -251,4 +656,115 @@ class Manager { */ public function getAccessList(\OCP\Files\Node $path) { } + + /** + * Create a new share + * @return IShare; + */ + public function newShare() { + return new \OC\Share20\Share(); + } + + /** + * Is the share API enabled + * + * @return bool + */ + public function shareApiEnabled() { + return $this->config->getAppValue('core', 'shareapi_enabled', 'yes') === 'yes'; + } + + /** + * Is public link sharing enabled + * + * @return bool + */ + public function shareApiAllowLinks() { + return $this->config->getAppValue('core', 'shareapi_allow_links', 'yes') === 'yes'; + } + + /** + * Is password on public link requires + * + * @return bool + */ + public function shareApiLinkEnforcePassword() { + return $this->config->getAppValue('core', 'shareapi_enforce_links_password', 'no') === 'yes'; + } + + /** + * Is default expire date enabled + * + * @return bool + */ + public function shareApiLinkDefaultExpireDate() { + return $this->config->getAppValue('core', 'shareapi_default_expire_date', 'no') === 'yes'; + } + + /** + * Is default expire date enforced + *` + * @return bool + */ + public function shareApiLinkDefaultExpireDateEnforced() { + return $this->config->getAppValue('core', 'shareapi_enforce_expire_date', 'no') === 'yes'; + } + + /** + * Number of default expire days + *shareApiLinkAllowPublicUpload + * @return int + */ + public function shareApiLinkDefaultExpireDays() { + return (int)$this->config->getAppValue('core', 'shareapi_expire_after_n_days', '7'); + } + + /** + * Allow public upload on link shares + * + * @return bool + */ + public function shareApiLinkAllowPublicUpload() { + return $this->config->getAppValue('core', 'shareapi_allow_public_upload', 'yes') === 'yes'; + } + + /** + * check if user can only share with group members + * @return bool + */ + public function shareWithGroupMembersOnly() { + return $this->config->getAppValue('core', 'shareapi_only_share_with_group_members', 'no') === 'yes'; + } + + + /** + * Copied from \OC_Util::isSharingDisabledForUser + * + * TODO: Deprecate fuction from OC_Util + * + * @param IUser $user + * @return bool + */ + public function isSharingDisabledForUser($user) { + if ($this->config->getAppValue('core', 'shareapi_exclude_groups', 'no') === 'yes') { + $groupsList = $this->config->getAppValue('core', 'shareapi_exclude_groups_list', ''); + $excludedGroups = json_decode($groupsList); + if (is_null($excludedGroups)) { + $excludedGroups = explode(',', $groupsList); + $newValue = json_encode($excludedGroups); + $this->config->setAppValue('core', 'shareapi_exclude_groups_list', $newValue); + } + $usersGroups = $this->groupManager->getUserGroupIds($user); + if (!empty($usersGroups)) { + $remainingGroups = array_diff($usersGroups, $excludedGroups); + // if the user is only in groups which are disabled for sharing then + // sharing is also disabled for the user + if (empty($remainingGroups)) { + return true; + } + } + } + return false; + } + } diff --git a/lib/private/share20/share.php b/lib/private/share20/share.php index b7ce38ac61d..4827000eefa 100644 --- a/lib/private/share20/share.php +++ b/lib/private/share20/share.php @@ -163,7 +163,7 @@ class Share implements IShare { * @param \DateTime $expireDate * @return Share The modified object */ - public function setExpirationDate(\DateTime $expireDate) { + public function setExpirationDate($expireDate) { //TODO checks $this->expireDate = $expireDate; diff --git a/lib/private/updater.php b/lib/private/updater.php index 04f8dcf7226..9ec72bab2f9 100644 --- a/lib/private/updater.php +++ b/lib/private/updater.php @@ -256,7 +256,7 @@ class Updater extends BasicEmitter { */ public function isUpgradePossible($oldVersion, $newVersion, $allowedPreviousVersion) { return (version_compare($allowedPreviousVersion, $oldVersion, '<=') - && version_compare($oldVersion, $newVersion, '<=')); + && (version_compare($oldVersion, $newVersion, '<=') || $this->config->getSystemValue('debug', false))); } /** diff --git a/lib/private/util.php b/lib/private/util.php index c9738b29ca1..6a9980fc129 100644 --- a/lib/private/util.php +++ b/lib/private/util.php @@ -1483,6 +1483,7 @@ class OC_Util { * * @param \OCP\IConfig $config * @return bool whether the core or any app needs an upgrade + * @throws \OC\HintException When the upgrade from the given version is not allowed */ public static function needUpgrade(\OCP\IConfig $config) { if ($config->getSystemValue('installed', false)) { @@ -1491,6 +1492,19 @@ class OC_Util { $versionDiff = version_compare($currentVersion, $installedVersion); if ($versionDiff > 0) { return true; + } else if ($config->getSystemValue('debug', false) && $versionDiff < 0) { + // downgrade with debug + $installedMajor = explode('.', $installedVersion); + $installedMajor = $installedMajor[0] . '.' . $installedMajor[1]; + $currentMajor = explode('.', $currentVersion); + $currentMajor = $currentMajor[0] . '.' . $currentMajor[1]; + if ($installedMajor === $currentMajor) { + // Same major, allow downgrade for developers + return true; + } else { + // downgrade attempt, throw exception + throw new \OC\HintException('Downgrading is not supported and is likely to cause unpredictable issues (from ' . $installedVersion . ' to ' . $currentVersion . ')'); + } } else if ($versionDiff < 0) { // downgrade attempt, throw exception throw new \OC\HintException('Downgrading is not supported and is likely to cause unpredictable issues (from ' . $installedVersion . ' to ' . $currentVersion . ')'); diff --git a/settings/controller/appsettingscontroller.php b/settings/controller/appsettingscontroller.php index 765fe2d2850..79b7589027b 100644 --- a/settings/controller/appsettingscontroller.php +++ b/settings/controller/appsettingscontroller.php @@ -296,6 +296,9 @@ class AppSettingsController extends Controller { $app['canInstall'] = empty($missing); $app['missingDependencies'] = $missing; + $app['missingMinOwnCloudVersion'] = !isset($app['dependencies']['owncloud']['@attributes']['min-version']); + $app['missingMaxOwnCloudVersion'] = !isset($app['dependencies']['owncloud']['@attributes']['max-version']); + return $app; }, $apps); diff --git a/settings/css/settings.css b/settings/css/settings.css index effabd928f9..8805919c96a 100644 --- a/settings/css/settings.css +++ b/settings/css/settings.css @@ -171,6 +171,11 @@ td.password>img,td.displayName>img, td.remove>a, td.quota>img { visibility:hidde td.password, td.quota, td.displayName { width:12em; cursor:pointer; } td.password>span, td.quota>span, rd.displayName>span { margin-right: 1.2em; color: #C7C7C7; } span.usersLastLoginTooltip { white-space: nowrap; } + +/* dropdowns will be relative to this element */ +#userlist { + position: relative; +} #userlist .mailAddress, #userlist .storageLocation, #userlist .userBackend, diff --git a/settings/js/apps.js b/settings/js/apps.js index 9782fafd2bd..85627e613c6 100644 --- a/settings/js/apps.js +++ b/settings/js/apps.js @@ -422,6 +422,11 @@ OC.Settings.Apps = OC.Settings.Apps || { return app.name.toLowerCase().indexOf(query) !== -1; }); + // App ID + apps = apps.concat(_.filter(OC.Settings.Apps.State.apps, function (app) { + return app.id.toLowerCase().indexOf(query) !== -1; + })); + // App Description apps = apps.concat(_.filter(OC.Settings.Apps.State.apps, function (app) { return app.description.toLowerCase().indexOf(query) !== -1; diff --git a/settings/templates/apps.php b/settings/templates/apps.php index 7213d552c08..94efd76c7c2 100644 --- a/settings/templates/apps.php +++ b/settings/templates/apps.php @@ -97,6 +97,18 @@ script( <div class="app-description-toggle-show"><?php p($l->t("Show description …"));?></div> <div class="app-description-toggle-hide hidden"><?php p($l->t("Hide description …"));?></div> + {{#if missingMinOwnCloudVersion}} + <div class="app-dependencies"> + <p><?php p($l->t('This app has no minimum ownCloud version assigned. This will be an error in ownCloud 11 and later.')); ?></p> + </div> + {{else}} + {{#if missingMaxOwnCloudVersion}} + <div class="app-dependencies"> + <p><?php p($l->t('This app has no maximum ownCloud version assigned. This will be an error in ownCloud 11 and later.')); ?></p> + </div> + {{/if}} + {{/if}} + {{#unless canInstall}} <div class="app-dependencies"> <p><?php p($l->t('This app cannot be installed because the following dependencies are not fulfilled:')); ?></p> diff --git a/tests/lib/server.php b/tests/lib/server.php index e2670061e8d..44e1aac5cce 100644 --- a/tests/lib/server.php +++ b/tests/lib/server.php @@ -94,6 +94,8 @@ class Server extends \Test\TestCase { ['HttpClientService', '\OCP\Http\Client\IClientService'], ['IniWrapper', '\bantu\IniGetWrapper\IniGetWrapper'], + ['MimeTypeDetector', '\OCP\Files\IMimeTypeDetector'], + ['MimeTypeDetector', '\OC\Files\Type\Detection'], ['JobList', '\OC\BackgroundJob\JobList'], ['JobList', '\OCP\BackgroundJob\IJobList'], diff --git a/tests/lib/share20/defaultshareprovidertest.php b/tests/lib/share20/defaultshareprovidertest.php index dc45bc7c085..beef4c9ef53 100644 --- a/tests/lib/share20/defaultshareprovidertest.php +++ b/tests/lib/share20/defaultshareprovidertest.php @@ -26,6 +26,12 @@ use OCP\IGroupManager; use OCP\Files\IRootFolder; use OC\Share20\DefaultShareProvider; +/** + * Class DefaultShareProviderTest + * + * @package Test\Share20 + * @group DB + */ class DefaultShareProviderTest extends \Test\TestCase { /** @var IDBConnection */ @@ -533,4 +539,186 @@ class DefaultShareProviderTest extends \Test\TestCase { $this->assertEquals(null, $children[1]->getExpirationDate()); $this->assertEquals('myTarget2', $children[1]->getTarget()); } + + public function testCreateUserShare() { + $share = new \OC\Share20\Share(); + + $sharedWith = $this->getMock('OCP\IUser'); + $sharedWith->method('getUID')->willReturn('sharedWith'); + $sharedBy = $this->getMock('OCP\IUser'); + $sharedBy->method('getUID')->willReturn('sharedBy'); + $shareOwner = $this->getMock('OCP\IUser'); + $shareOwner->method('getUID')->WillReturn('shareOwner'); + + $this->userManager + ->method('get') + ->will($this->returnValueMap([ + ['sharedWith', $sharedWith], + ['sharedBy', $sharedBy], + ['shareOwner', $shareOwner], + ])); + + $path = $this->getMock('\OCP\Files\File'); + $path->method('getId')->willReturn(100); + $path->method('getOwner')->willReturn($shareOwner); + + $ownerFolder = $this->getMock('OCP\Files\Folder'); + $userFolder = $this->getMock('OCP\Files\Folder'); + $this->rootFolder + ->method('getUserFolder') + ->will($this->returnValueMap([ + ['sharedBy', $userFolder], + ['shareOwner', $ownerFolder], + ])); + + $userFolder->method('getById') + ->with(100) + ->willReturn([$path]); + $ownerFolder->method('getById') + ->with(100) + ->willReturn([$path]); + + $share->setShareType(\OCP\Share::SHARE_TYPE_USER); + $share->setSharedWith($sharedWith); + $share->setSharedBy($sharedBy); + $share->setShareOwner($shareOwner); + $share->setPath($path); + $share->setPermissions(1); + $share->setTarget('/target'); + + $share2 = $this->provider->create($share); + + $this->assertNotNull($share2->getId()); + $this->assertSame(\OCP\Share::SHARE_TYPE_USER, $share2->getShareType()); + $this->assertSame($sharedWith, $share2->getSharedWith()); + $this->assertSame($sharedBy, $share2->getSharedBy()); + $this->assertSame($shareOwner, $share2->getShareOwner()); + $this->assertSame(1, $share2->getPermissions()); + $this->assertSame('/target', $share2->getTarget()); + $this->assertLessThanOrEqual(time(), $share2->getSharetime()); + $this->assertSame($path, $share2->getPath()); + } + + public function testCreateGroupShare() { + $share = new \OC\Share20\Share(); + + $sharedWith = $this->getMock('OCP\IGroup'); + $sharedWith->method('getGID')->willReturn('sharedWith'); + $sharedBy = $this->getMock('OCP\IUser'); + $sharedBy->method('getUID')->willReturn('sharedBy'); + $shareOwner = $this->getMock('OCP\IUser'); + $shareOwner->method('getUID')->WillReturn('shareOwner'); + + $this->userManager + ->method('get') + ->will($this->returnValueMap([ + ['sharedBy', $sharedBy], + ['shareOwner', $shareOwner], + ])); + $this->groupManager + ->method('get') + ->with('sharedWith') + ->willReturn($sharedWith); + + $path = $this->getMock('\OCP\Files\Folder'); + $path->method('getId')->willReturn(100); + $path->method('getOwner')->willReturn($shareOwner); + + $ownerFolder = $this->getMock('OCP\Files\Folder'); + $userFolder = $this->getMock('OCP\Files\Folder'); + $this->rootFolder + ->method('getUserFolder') + ->will($this->returnValueMap([ + ['sharedBy', $userFolder], + ['shareOwner', $ownerFolder], + ])); + + $userFolder->method('getById') + ->with(100) + ->willReturn([$path]); + $ownerFolder->method('getById') + ->with(100) + ->willReturn([$path]); + + $share->setShareType(\OCP\Share::SHARE_TYPE_GROUP); + $share->setSharedWith($sharedWith); + $share->setSharedBy($sharedBy); + $share->setShareOwner($shareOwner); + $share->setPath($path); + $share->setPermissions(1); + $share->setTarget('/target'); + + $share2 = $this->provider->create($share); + + $this->assertNotNull($share2->getId()); + $this->assertSame(\OCP\Share::SHARE_TYPE_GROUP, $share2->getShareType()); + $this->assertSame($sharedWith, $share2->getSharedWith()); + $this->assertSame($sharedBy, $share2->getSharedBy()); + $this->assertSame($shareOwner, $share2->getShareOwner()); + $this->assertSame(1, $share2->getPermissions()); + $this->assertSame('/target', $share2->getTarget()); + $this->assertLessThanOrEqual(time(), $share2->getSharetime()); + $this->assertSame($path, $share2->getPath()); + } + + public function testCreateLinkShare() { + $share = new \OC\Share20\Share(); + + $sharedBy = $this->getMock('OCP\IUser'); + $sharedBy->method('getUID')->willReturn('sharedBy'); + $shareOwner = $this->getMock('OCP\IUser'); + $shareOwner->method('getUID')->WillReturn('shareOwner'); + + $this->userManager + ->method('get') + ->will($this->returnValueMap([ + ['sharedBy', $sharedBy], + ['shareOwner', $shareOwner], + ])); + + $path = $this->getMock('\OCP\Files\Folder'); + $path->method('getId')->willReturn(100); + $path->method('getOwner')->willReturn($shareOwner); + + $ownerFolder = $this->getMock('OCP\Files\Folder'); + $userFolder = $this->getMock('OCP\Files\Folder'); + $this->rootFolder + ->method('getUserFolder') + ->will($this->returnValueMap([ + ['sharedBy', $userFolder], + ['shareOwner', $ownerFolder], + ])); + + $userFolder->method('getById') + ->with(100) + ->willReturn([$path]); + $ownerFolder->method('getById') + ->with(100) + ->willReturn([$path]); + + $share->setShareType(\OCP\Share::SHARE_TYPE_LINK); + $share->setSharedBy($sharedBy); + $share->setShareOwner($shareOwner); + $share->setPath($path); + $share->setPermissions(1); + $share->setPassword('password'); + $share->setToken('token'); + $expireDate = new \DateTime(); + $share->setExpirationDate($expireDate); + $share->setTarget('/target'); + + $share2 = $this->provider->create($share); + + $this->assertNotNull($share2->getId()); + $this->assertSame(\OCP\Share::SHARE_TYPE_LINK, $share2->getShareType()); + $this->assertSame($sharedBy, $share2->getSharedBy()); + $this->assertSame($shareOwner, $share2->getShareOwner()); + $this->assertSame(1, $share2->getPermissions()); + $this->assertSame('/target', $share2->getTarget()); + $this->assertLessThanOrEqual(time(), $share2->getSharetime()); + $this->assertSame($path, $share2->getPath()); + $this->assertSame('password', $share2->getPassword()); + $this->assertSame('token', $share2->getToken()); + $this->assertEquals($expireDate, $share2->getExpirationDate()); + } } diff --git a/tests/lib/share20/managertest.php b/tests/lib/share20/managertest.php index e4d0bfad584..57e7e110712 100644 --- a/tests/lib/share20/managertest.php +++ b/tests/lib/share20/managertest.php @@ -23,10 +23,21 @@ namespace Test\Share20; use OC\Share20\Manager; use OC\Share20\Exception; +use OCP\IL10N; use OCP\ILogger; -use OCP\IAppConfig; +use OCP\IConfig; use OC\Share20\IShareProvider; +use OCP\Security\ISecureRandom; +use OCP\Security\IHasher; +use OCP\Files\Mount\IMountManager; +use OCP\IGroupManager; +/** + * Class ManagerTest + * + * @package Test\Share20 + * @group DB + */ class ManagerTest extends \Test\TestCase { /** @var Manager */ @@ -35,22 +46,52 @@ class ManagerTest extends \Test\TestCase { /** @var ILogger */ protected $logger; - /** @var IAppConfig */ - protected $appConfig; + /** @var IConfig */ + protected $config; + + /** @var ISecureRandom */ + protected $secureRandom; + + /** @var IHasher */ + protected $hasher; /** @var IShareProvider */ protected $defaultProvider; + /** @var IMountManager */ + protected $mountManager; + + /** @var IGroupManager */ + protected $groupManager; + + /** @var IL10N */ + protected $l; + public function setUp() { $this->logger = $this->getMock('\OCP\ILogger'); - $this->appConfig = $this->getMock('\OCP\IAppConfig'); + $this->config = $this->getMock('\OCP\IConfig'); $this->defaultProvider = $this->getMock('\OC\Share20\IShareProvider'); + $this->secureRandom = $this->getMock('\OCP\Security\ISecureRandom'); + $this->hasher = $this->getMock('\OCP\Security\IHasher'); + $this->mountManager = $this->getMock('\OCP\Files\Mount\IMountManager'); + $this->groupManager = $this->getMock('\OCP\IGroupManager'); + + $this->l = $this->getMock('\OCP\IL10N'); + $this->l->method('t') + ->will($this->returnCallback(function($text, $parameters = []) { + return vsprintf($text, $parameters); + })); $this->manager = new Manager( $this->logger, - $this->appConfig, - $this->defaultProvider + $this->config, + $this->defaultProvider, + $this->secureRandom, + $this->hasher, + $this->mountManager, + $this->groupManager, + $this->l ); } @@ -91,8 +132,13 @@ class ManagerTest extends \Test\TestCase { $manager = $this->getMockBuilder('\OC\Share20\Manager') ->setConstructorArgs([ $this->logger, - $this->appConfig, - $this->defaultProvider + $this->config, + $this->defaultProvider, + $this->secureRandom, + $this->hasher, + $this->mountManager, + $this->groupManager, + $this->l ]) ->setMethods(['getShareById', 'deleteChildren']) ->getMock(); @@ -177,8 +223,13 @@ class ManagerTest extends \Test\TestCase { $manager = $this->getMockBuilder('\OC\Share20\Manager') ->setConstructorArgs([ $this->logger, - $this->appConfig, - $this->defaultProvider + $this->config, + $this->defaultProvider, + $this->secureRandom, + $this->hasher, + $this->mountManager, + $this->groupManager, + $this->l, ]) ->setMethods(['getShareById']) ->getMock(); @@ -317,8 +368,13 @@ class ManagerTest extends \Test\TestCase { $manager = $this->getMockBuilder('\OC\Share20\Manager') ->setConstructorArgs([ $this->logger, - $this->appConfig, - $this->defaultProvider + $this->config, + $this->defaultProvider, + $this->secureRandom, + $this->hasher, + $this->mountManager, + $this->groupManager, + $this->l, ]) ->setMethods(['deleteShare']) ->getMock(); @@ -365,4 +421,1128 @@ class ManagerTest extends \Test\TestCase { $this->assertEquals($share, $this->manager->getShareById(42)); } + + /** + * @expectedException InvalidArgumentException + * @expectedExceptionMessage Passwords are enforced for link shares + */ + public function testVerifyPasswordNullButEnforced() { + $this->config->method('getAppValue')->will($this->returnValueMap([ + ['core', 'shareapi_enforce_links_password', 'no', 'yes'], + ])); + + $this->invokePrivate($this->manager, 'verifyPassword', [null]); + } + + public function testVerifyPasswordNull() { + $this->config->method('getAppValue')->will($this->returnValueMap([ + ['core', 'shareapi_enforce_links_password', 'no', 'no'], + ])); + + $result = $this->invokePrivate($this->manager, 'verifyPassword', [null]); + $this->assertNull($result); + } + + public function testVerifyPasswordHook() { + $this->config->method('getAppValue')->will($this->returnValueMap([ + ['core', 'shareapi_enforce_links_password', 'no', 'no'], + ])); + + $hookListner = $this->getMockBuilder('Dummy')->setMethods(['listner'])->getMock(); + \OCP\Util::connectHook('\OC\Share', 'verifyPassword', $hookListner, 'listner'); + + $hookListner->expects($this->once()) + ->method('listner') + ->with([ + 'password' => 'password', + 'accepted' => true, + 'message' => '' + ]); + + $result = $this->invokePrivate($this->manager, 'verifyPassword', ['password']); + $this->assertNull($result); + } + + /** + * @expectedException Exception + * @expectedExceptionMessage password not accepted + */ + public function testVerifyPasswordHookFails() { + $this->config->method('getAppValue')->will($this->returnValueMap([ + ['core', 'shareapi_enforce_links_password', 'no', 'no'], + ])); + + $dummy = new DummyPassword(); + \OCP\Util::connectHook('\OC\Share', 'verifyPassword', $dummy, 'listner'); + $this->invokePrivate($this->manager, 'verifyPassword', ['password']); + } + + public function createShare($id, $type, $path, $sharedWith, $sharedBy, $shareOwner, + $permissions, $expireDate = null, $password = null) { + $share = $this->getMock('\OC\Share20\IShare'); + + $share->method('getShareType')->willReturn($type); + $share->method('getSharedWith')->willReturn($sharedWith); + $share->method('getSharedBy')->willReturn($sharedBy); + $share->method('getSharedOwner')->willReturn($shareOwner); + $share->method('getPath')->willReturn($path); + $share->method('getPermissions')->willReturn($permissions); + $share->method('getExpirationDate')->willReturn($expireDate); + $share->method('getPassword')->willReturn($password); + + return $share; + } + + public function dataGeneralChecks() { + $user = $this->getMock('\OCP\IUser'); + $user2 = $this->getMock('\OCP\IUser'); + $group = $this->getMock('\OCP\IGroup'); + + $file = $this->getMock('\OCP\Files\File'); + $node = $this->getMock('\OCP\Files\Node'); + + $data = [ + [$this->createShare(null, \OCP\Share::SHARE_TYPE_USER, $file, null, $user, $user, 31, null, null), 'SharedWith should be an IUser', true], + [$this->createShare(null, \OCP\Share::SHARE_TYPE_USER, $file, $group, $user, $user, 31, null, null), 'SharedWith should be an IUser', true], + [$this->createShare(null, \OCP\Share::SHARE_TYPE_USER, $file, 'foo@bar.com', $user, $user, 31, null, null), 'SharedWith should be an IUser', true], + [$this->createShare(null, \OCP\Share::SHARE_TYPE_GROUP, $file, null, $user, $user, 31, null, null), 'SharedWith should be an IGroup', true], + [$this->createShare(null, \OCP\Share::SHARE_TYPE_GROUP, $file, $user2, $user, $user, 31, null, null), 'SharedWith should be an IGroup', true], + [$this->createShare(null, \OCP\Share::SHARE_TYPE_GROUP, $file, 'foo@bar.com', $user, $user, 31, null, null), 'SharedWith should be an IGroup', true], + [$this->createShare(null, \OCP\Share::SHARE_TYPE_LINK, $file, $user2, $user, $user, 31, null, null), 'SharedWith should be empty', true], + [$this->createShare(null, \OCP\Share::SHARE_TYPE_LINK, $file, $group, $user, $user, 31, null, null), 'SharedWith should be empty', true], + [$this->createShare(null, \OCP\Share::SHARE_TYPE_LINK, $file, 'foo@bar.com', $user, $user, 31, null, null), 'SharedWith should be empty', true], + [$this->createShare(null, -1, $file, null, $user, $user, 31, null, null), 'unkown share type', true], + + [$this->createShare(null, \OCP\Share::SHARE_TYPE_USER, $file, $user2, null, $user, 31, null, null), 'SharedBy should be set', true], + [$this->createShare(null, \OCP\Share::SHARE_TYPE_GROUP, $file, $group, null, $user, 31, null, null), 'SharedBy should be set', true], + [$this->createShare(null, \OCP\Share::SHARE_TYPE_LINK, $file, null, null, $user, 31, null, null), 'SharedBy should be set', true], + + [$this->createShare(null, \OCP\Share::SHARE_TYPE_USER, $file, $user, $user, $user, 31, null, null), 'Can\'t share with yourself', true], + + [$this->createShare(null, \OCP\Share::SHARE_TYPE_USER, null, $user2, $user, $user, 31, null, null), 'Path should be set', true], + [$this->createShare(null, \OCP\Share::SHARE_TYPE_GROUP, null, $group, $user, $user, 31, null, null), 'Path should be set', true], + [$this->createShare(null, \OCP\Share::SHARE_TYPE_LINK, null, null, $user, $user, 31, null, null), 'Path should be set', true], + + [$this->createShare(null, \OCP\Share::SHARE_TYPE_USER, $node, $user2, $user, $user, 31, null, null), 'Path should be either a file or a folder', true], + [$this->createShare(null, \OCP\Share::SHARE_TYPE_GROUP, $node, $group, $user, $user, 31, null, null), 'Path should be either a file or a folder', true], + [$this->createShare(null, \OCP\Share::SHARE_TYPE_LINK, $node, null, $user, $user, 31, null, null), 'Path should be either a file or a folder', true], + ]; + + $nonShareAble = $this->getMock('\OCP\Files\Folder'); + $nonShareAble->method('isShareable')->willReturn(false); + $nonShareAble->method('getPath')->willReturn('path'); + + $data[] = [$this->createShare(null, \OCP\Share::SHARE_TYPE_USER, $nonShareAble, $user2, $user, $user, 31, null, null), 'You are not allowed to share path', true]; + $data[] = [$this->createShare(null, \OCP\Share::SHARE_TYPE_GROUP, $nonShareAble, $group, $user, $user, 31, null, null), 'You are not allowed to share path', true]; + $data[] = [$this->createShare(null, \OCP\Share::SHARE_TYPE_LINK, $nonShareAble, null, $user, $user, 31, null, null), 'You are not allowed to share path', true]; + + $limitedPermssions = $this->getMock('\OCP\Files\File'); + $limitedPermssions->method('isShareable')->willReturn(true); + $limitedPermssions->method('getPermissions')->willReturn(\OCP\Constants::PERMISSION_READ); + $limitedPermssions->method('getPath')->willReturn('path'); + + $data[] = [$this->createShare(null, \OCP\Share::SHARE_TYPE_USER, $limitedPermssions, $user2, $user, $user, null, null, null), 'A share requires permissions', true]; + $data[] = [$this->createShare(null, \OCP\Share::SHARE_TYPE_GROUP, $limitedPermssions, $group, $user, $user, null, null, null), 'A share requires permissions', true]; + $data[] = [$this->createShare(null, \OCP\Share::SHARE_TYPE_LINK, $limitedPermssions, null, $user, $user, null, null, null), 'A share requires permissions', true]; + + $data[] = [$this->createShare(null, \OCP\Share::SHARE_TYPE_USER, $limitedPermssions, $user2, $user, $user, 31, null, null), 'Cannot increase permissions of path', true]; + $data[] = [$this->createShare(null, \OCP\Share::SHARE_TYPE_GROUP, $limitedPermssions, $group, $user, $user, 17, null, null), 'Cannot increase permissions of path', true]; + $data[] = [$this->createShare(null, \OCP\Share::SHARE_TYPE_LINK, $limitedPermssions, null, $user, $user, 3, null, null), 'Cannot increase permissions of path', true]; + + $allPermssions = $this->getMock('\OCP\Files\Folder'); + $allPermssions->method('isShareable')->willReturn(true); + $allPermssions->method('getPermissions')->willReturn(\OCP\Constants::PERMISSION_ALL); + + $data[] = [$this->createShare(null, \OCP\Share::SHARE_TYPE_USER, $allPermssions, $user2, $user, $user, 30, null, null), 'Shares need at least read permissions', true]; + $data[] = [$this->createShare(null, \OCP\Share::SHARE_TYPE_GROUP, $allPermssions, $group, $user, $user, 2, null, null), 'Shares need at least read permissions', true]; + $data[] = [$this->createShare(null, \OCP\Share::SHARE_TYPE_LINK, $allPermssions, null, $user, $user, 16, null, null), 'Shares need at least read permissions', true]; + + $data[] = [$this->createShare(null, \OCP\Share::SHARE_TYPE_USER, $allPermssions, $user2, $user, $user, 31, null, null), null, false]; + $data[] = [$this->createShare(null, \OCP\Share::SHARE_TYPE_GROUP, $allPermssions, $group, $user, $user, 3, null, null), null, false]; + $data[] = [$this->createShare(null, \OCP\Share::SHARE_TYPE_LINK, $allPermssions, null, $user, $user, 17, null, null), null, false]; + + return $data; + } + + /** + * @dataProvider dataGeneralChecks + * + * @param $share + * @param $exceptionMessage + */ + public function testGeneralChecks($share, $exceptionMessage, $exception) { + $thrown = null; + + try { + $this->invokePrivate($this->manager, 'generalCreateChecks', [$share]); + $thrown = false; + } catch (\OC\HintException $e) { + $this->assertEquals($exceptionMessage, $e->getHint()); + $thrown = true; + } catch(\InvalidArgumentException $e) { + $this->assertEquals($exceptionMessage, $e->getMessage()); + $thrown = true; + } + + $this->assertSame($exception, $thrown); + } + + /** + * @expectedException \OC\HintException + * @expectedExceptionMessage Expiration date is in the past + */ + public function testValidateExpiredateInPast() { + + // Expire date in the past + $past = new \DateTime(); + $past->sub(new \DateInterval('P1D')); + + $this->invokePrivate($this->manager, 'validateExpiredate', [$past]); + } + + /** + * @expectedException InvalidArgumentException + * @expectedExceptionMessage Expiration date is enforced + */ + public function testValidateExpiredateEnforceButNotSet() { + $this->config->method('getAppValue') + ->will($this->returnValueMap([ + ['core', 'shareapi_enforce_expire_date', 'no', 'yes'], + ])); + + $this->invokePrivate($this->manager, 'validateExpiredate', [null]); + } + + public function testValidateExpiredateEnforceToFarIntoFuture() { + // Expire date in the past + $future = new \DateTime(); + $future->add(new \DateInterval('P7D')); + + $this->config->method('getAppValue') + ->will($this->returnValueMap([ + ['core', 'shareapi_enforce_expire_date', 'no', 'yes'], + ['core', 'shareapi_expire_after_n_days', '7', '3'], + ])); + + try { + $this->invokePrivate($this->manager, 'validateExpiredate', [$future]); + } catch (\OC\HintException $e) { + $this->assertEquals('Cannot set expiration date more than 3 days in the future', $e->getMessage()); + $this->assertEquals('Cannot set expiration date more than 3 days in the future', $e->getHint()); + $this->assertEquals(404, $e->getCode()); + } + } + + public function testValidateExpiredateEnforceValid() { + // Expire date in the past + $future = new \DateTime(); + $future->add(new \DateInterval('P2D')); + $future->setTime(0,0,0); + $expected = $future->format(\DateTime::ISO8601); + $future->setTime(1,2,3); + + $this->config->method('getAppValue') + ->will($this->returnValueMap([ + ['core', 'shareapi_enforce_expire_date', 'no', 'yes'], + ['core', 'shareapi_expire_after_n_days', '7', '3'], + ])); + + $future = $this->invokePrivate($this->manager, 'validateExpiredate', [$future]); + + $this->assertEquals($expected, $future->format(\DateTime::ISO8601)); + } + + public function testValidateExpiredateNoDateNoDefaultNull() { + $date = new \DateTime(); + $date->add(new \DateInterval('P5D')); + + $res = $this->invokePrivate($this->manager, 'validateExpiredate', [$date]); + + $this->assertEquals($date, $res); + } + + public function testValidateExpiredateNoDateNoDefault() { + $date = $this->invokePrivate($this->manager, 'validateExpiredate', [null]); + + $this->assertNull($date); + } + + public function testValidateExpiredateNoDateDefault() { + $future = new \DateTime(); + $future->add(new \DateInterval('P3D')); + $future->setTime(0,0,0); + + $this->config->method('getAppValue') + ->will($this->returnValueMap([ + ['core', 'shareapi_default_expire_date', 'no', 'yes'], + ['core', 'shareapi_expire_after_n_days', '7', '3'], + ])); + + $date = $this->invokePrivate($this->manager, 'validateExpiredate', [null]); + + $this->assertEquals($future->format(\DateTime::ISO8601), $date->format(\DateTime::ISO8601)); + } + + /** + * @expectedException Exception + * @expectedExceptionMessage Only sharing with group members is allowed + */ + public function testUserCreateChecksShareWithGroupMembersOnlyDifferentGroups() { + $share = new \OC\Share20\Share(); + + $sharedBy = $this->getMock('\OCP\IUser'); + $sharedWith = $this->getMock('\OCP\IUser'); + $share->setSharedBy($sharedBy)->setSharedWith($sharedWith); + + $this->groupManager + ->method('getUserGroupIds') + ->will( + $this->returnValueMap([ + [$sharedBy, ['group1']], + [$sharedWith, ['group2']], + ]) + ); + + $this->config + ->method('getAppValue') + ->will($this->returnValueMap([ + ['core', 'shareapi_only_share_with_group_members', 'no', 'yes'], + ])); + + $this->invokePrivate($this->manager, 'userCreateChecks', [$share]); + } + + public function testUserCreateChecksShareWithGroupMembersOnlySharedGroup() { + $share = new \OC\Share20\Share(); + + $sharedBy = $this->getMock('\OCP\IUser'); + $sharedWith = $this->getMock('\OCP\IUser'); + $share->setSharedBy($sharedBy)->setSharedWith($sharedWith); + + $path = $this->getMock('\OCP\Files\Node'); + $share->setPath($path); + + $this->groupManager + ->method('getUserGroupIds') + ->will( + $this->returnValueMap([ + [$sharedBy, ['group1', 'group3']], + [$sharedWith, ['group2', 'group3']], + ]) + ); + + $this->config + ->method('getAppValue') + ->will($this->returnValueMap([ + ['core', 'shareapi_only_share_with_group_members', 'no', 'yes'], + ])); + + $this->defaultProvider + ->method('getSharesByPath') + ->with($path) + ->willReturn([]); + + $this->invokePrivate($this->manager, 'userCreateChecks', [$share]); + } + + /** + * @expectedException Exception + * @expectedExceptionMessage Path already shared with this user + */ + public function testUserCreateChecksIdenticalShareExists() { + $share = new \OC\Share20\Share(); + + $sharedWith = $this->getMock('\OCP\IUser'); + $share->setSharedWith($sharedWith); + + $path = $this->getMock('\OCP\Files\Node'); + $share->setPath($path); + + $this->defaultProvider + ->method('getSharesByPath') + ->with($path) + ->willReturn([$share]); + + $this->invokePrivate($this->manager, 'userCreateChecks', [$share]); + } + + /** + * @expectedException Exception + * @expectedExceptionMessage Path already shared with this user + */ + public function testUserCreateChecksIdenticalPathSharedViaGroup() { + $share = new \OC\Share20\Share(); + $sharedWith = $this->getMock('\OCP\IUser'); + $owner = $this->getMock('\OCP\IUser'); + $path = $this->getMock('\OCP\Files\Node'); + $share->setSharedWith($sharedWith) + ->setPath($path) + ->setShareOwner($owner); + + $share2 = new \OC\Share20\Share(); + $owner2 = $this->getMock('\OCP\IUser'); + $share2->setShareType(\OCP\Share::SHARE_TYPE_GROUP) + ->setShareOwner($owner2); + + $group = $this->getMock('\OCP\IGroup'); + $group->method('inGroup') + ->with($sharedWith) + ->willReturn(true); + + $share2->setSharedWith($group); + + $this->defaultProvider + ->method('getSharesByPath') + ->with($path) + ->willReturn([$share2]); + + $this->invokePrivate($this->manager, 'userCreateChecks', [$share]); + } + + public function testUserCreateChecksIdenticalPathNotSharedWithUser() { + $share = new \OC\Share20\Share(); + $sharedWith = $this->getMock('\OCP\IUser'); + $owner = $this->getMock('\OCP\IUser'); + $path = $this->getMock('\OCP\Files\Node'); + $share->setSharedWith($sharedWith) + ->setPath($path) + ->setShareOwner($owner); + + $share2 = new \OC\Share20\Share(); + $owner2 = $this->getMock('\OCP\IUser'); + $share2->setShareType(\OCP\Share::SHARE_TYPE_GROUP) + ->setShareOwner($owner2); + + $group = $this->getMock('\OCP\IGroup'); + $group->method('inGroup') + ->with($sharedWith) + ->willReturn(false); + + $share2->setSharedWith($group); + + $this->defaultProvider + ->method('getSharesByPath') + ->with($path) + ->willReturn([$share2]); + + $this->invokePrivate($this->manager, 'userCreateChecks', [$share]); + } + + + /** + * @expectedException Exception + * @expectedExceptionMessage Only sharing within your own groups is allowed + */ + public function testGroupCreateChecksShareWithGroupMembersOnlyNotInGroup() { + $share = new \OC\Share20\Share(); + + $sharedBy = $this->getMock('\OCP\IUser'); + $sharedWith = $this->getMock('\OCP\IGroup'); + $share->setSharedBy($sharedBy)->setSharedWith($sharedWith); + + $sharedWith->method('inGroup')->with($sharedBy)->willReturn(false); + + $this->config + ->method('getAppValue') + ->will($this->returnValueMap([ + ['core', 'shareapi_only_share_with_group_members', 'no', 'yes'], + ])); + + $this->invokePrivate($this->manager, 'groupCreateChecks', [$share]); + } + + public function testGroupCreateChecksShareWithGroupMembersOnlyInGroup() { + $share = new \OC\Share20\Share(); + + $sharedBy = $this->getMock('\OCP\IUser'); + $sharedWith = $this->getMock('\OCP\IGroup'); + $share->setSharedBy($sharedBy)->setSharedWith($sharedWith); + + $sharedWith->method('inGroup')->with($sharedBy)->willReturn(true); + + $path = $this->getMock('\OCP\Files\Node'); + $share->setPath($path); + + $this->defaultProvider->method('getSharesByPath') + ->with($path) + ->willReturn([]); + + $this->config + ->method('getAppValue') + ->will($this->returnValueMap([ + ['core', 'shareapi_only_share_with_group_members', 'no', 'yes'], + ])); + + $this->invokePrivate($this->manager, 'groupCreateChecks', [$share]); + } + + /** + * @expectedException Exception + * @expectedExceptionMessage Path already shared with this group + */ + public function testGroupCreateChecksPathAlreadySharedWithSameGroup() { + $share = new \OC\Share20\Share(); + + $sharedWith = $this->getMock('\OCP\IGroup'); + $share->setSharedWith($sharedWith); + + $path = $this->getMock('\OCP\Files\Node'); + $share->setPath($path); + + $share2 = new \OC\Share20\Share(); + $share2->setSharedWith($sharedWith); + + $this->defaultProvider->method('getSharesByPath') + ->with($path) + ->willReturn([$share2]); + + $this->invokePrivate($this->manager, 'groupCreateChecks', [$share]); + } + + public function testGroupCreateChecksPathAlreadySharedWithDifferentGroup() { + $share = new \OC\Share20\Share(); + + $sharedWith = $this->getMock('\OCP\IGroup'); + $share->setSharedWith($sharedWith); + + $path = $this->getMock('\OCP\Files\Node'); + $share->setPath($path); + + $share2 = new \OC\Share20\Share(); + $sharedWith2 = $this->getMock('\OCP\IGroup'); + $share2->setSharedWith($sharedWith2); + + $this->defaultProvider->method('getSharesByPath') + ->with($path) + ->willReturn([$share2]); + + $this->invokePrivate($this->manager, 'groupCreateChecks', [$share]); + } + + /** + * @expectedException Exception + * @expectedExceptionMessage Link sharing not allowed + */ + public function testLinkCreateChecksNoLinkSharesAllowed() { + $share = new \OC\Share20\Share(); + + $this->config + ->method('getAppValue') + ->will($this->returnValueMap([ + ['core', 'shareapi_allow_links', 'yes', 'no'], + ])); + + $this->invokePrivate($this->manager, 'linkCreateChecks', [$share]); + } + + /** + * @expectedException Exception + * @expectedExceptionMessage Link shares can't have reshare permissions + */ + public function testLinkCreateChecksSharePermissions() { + $share = new \OC\Share20\Share(); + + $share->setPermissions(\OCP\Constants::PERMISSION_SHARE); + + $this->config + ->method('getAppValue') + ->will($this->returnValueMap([ + ['core', 'shareapi_allow_links', 'yes', 'yes'], + ])); + + $this->invokePrivate($this->manager, 'linkCreateChecks', [$share]); + } + + /** + * @expectedException Exception + * @expectedExceptionMessage Link shares can't have delete permissions + */ + public function testLinkCreateChecksDeletePermissions() { + $share = new \OC\Share20\Share(); + + $share->setPermissions(\OCP\Constants::PERMISSION_DELETE); + + $this->config + ->method('getAppValue') + ->will($this->returnValueMap([ + ['core', 'shareapi_allow_links', 'yes', 'yes'], + ])); + + $this->invokePrivate($this->manager, 'linkCreateChecks', [$share]); + } + + /** + * @expectedException Exception + * @expectedExceptionMessage Public upload not allowed + */ + public function testLinkCreateChecksNoPublicUpload() { + $share = new \OC\Share20\Share(); + + $share->setPermissions(\OCP\Constants::PERMISSION_CREATE | \OCP\Constants::PERMISSION_UPDATE); + + $this->config + ->method('getAppValue') + ->will($this->returnValueMap([ + ['core', 'shareapi_allow_links', 'yes', 'yes'], + ['core', 'shareapi_allow_public_upload', 'yes', 'no'] + ])); + + $this->invokePrivate($this->manager, 'linkCreateChecks', [$share]); + } + + + public function testLinkCreateChecksPublicUpload() { + $share = new \OC\Share20\Share(); + + $share->setPermissions(\OCP\Constants::PERMISSION_CREATE | \OCP\Constants::PERMISSION_UPDATE); + + $this->config + ->method('getAppValue') + ->will($this->returnValueMap([ + ['core', 'shareapi_allow_links', 'yes', 'yes'], + ['core', 'shareapi_allow_public_upload', 'yes', 'yes'] + ])); + + $this->invokePrivate($this->manager, 'linkCreateChecks', [$share]); + } + + public function testLinkCreateChecksReadOnly() { + $share = new \OC\Share20\Share(); + + $share->setPermissions(\OCP\Constants::PERMISSION_READ); + + $this->config + ->method('getAppValue') + ->will($this->returnValueMap([ + ['core', 'shareapi_allow_links', 'yes', 'yes'], + ['core', 'shareapi_allow_public_upload', 'yes', 'no'] + ])); + + $this->invokePrivate($this->manager, 'linkCreateChecks', [$share]); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage Path contains files shared with you + */ + public function testPathCreateChecksContainsSharedMount() { + $path = $this->getMock('\OCP\Files\Folder'); + $path->method('getPath')->willReturn('path'); + + $mount = $this->getMock('\OCP\Files\Mount\IMountPoint'); + $storage = $this->getMock('\OCP\Files\Storage'); + $mount->method('getStorage')->willReturn($storage); + $storage->method('instanceOfStorage')->with('\OCA\Files_Sharing\ISharedStorage')->willReturn(true); + + $this->mountManager->method('findIn')->with('path')->willReturn([$mount]); + + $this->invokePrivate($this->manager, 'pathCreateChecks', [$path]); + } + + public function testPathCreateChecksContainsNoSharedMount() { + $path = $this->getMock('\OCP\Files\Folder'); + $path->method('getPath')->willReturn('path'); + + $mount = $this->getMock('\OCP\Files\Mount\IMountPoint'); + $storage = $this->getMock('\OCP\Files\Storage'); + $mount->method('getStorage')->willReturn($storage); + $storage->method('instanceOfStorage')->with('\OCA\Files_Sharing\ISharedStorage')->willReturn(false); + + $this->mountManager->method('findIn')->with('path')->willReturn([$mount]); + + $this->invokePrivate($this->manager, 'pathCreateChecks', [$path]); + } + + public function testPathCreateChecksContainsNoFolder() { + $path = $this->getMock('\OCP\Files\File'); + + $this->invokePrivate($this->manager, 'pathCreateChecks', [$path]); + } + + public function dataIsSharingDisabledForUser() { + $data = []; + + // No exclude groups + $data[] = ['no', null, null, null, false]; + + // empty exclude list, user no groups + $data[] = ['yes', '', json_encode(['']), [], false]; + + // empty exclude list, user groups + $data[] = ['yes', '', json_encode(['']), ['group1', 'group2'], false]; + + // Convert old list to json + $data[] = ['yes', 'group1,group2', json_encode(['group1', 'group2']), [], false]; + + // Old list partly groups in common + $data[] = ['yes', 'group1,group2', json_encode(['group1', 'group2']), ['group1', 'group3'], false]; + + // Old list only groups in common + $data[] = ['yes', 'group1,group2', json_encode(['group1', 'group2']), ['group1'], true]; + + // New list partly in common + $data[] = ['yes', json_encode(['group1', 'group2']), null, ['group1', 'group3'], false]; + + // New list only groups in common + $data[] = ['yes', json_encode(['group1', 'group2']), null, ['group2'], true]; + + return $data; + } + + /** + * @dataProvider dataIsSharingDisabledForUser + * + * @param string $excludeGroups + * @param string $groupList + * @param string $setList + * @param string[] $groupIds + * @param bool $expected + */ + public function testIsSharingDisabledForUser($excludeGroups, $groupList, $setList, $groupIds, $expected) { + $user = $this->getMock('\OCP\IUser'); + + $this->config->method('getAppValue') + ->will($this->returnValueMap([ + ['core', 'shareapi_exclude_groups', 'no', $excludeGroups], + ['core', 'shareapi_exclude_groups_list', '', $groupList], + ])); + + if ($setList !== null) { + $this->config->expects($this->once()) + ->method('setAppValue') + ->with('core', 'shareapi_exclude_groups_list', $setList); + } else { + $this->config->expects($this->never()) + ->method('setAppValue'); + } + + $this->groupManager->method('getUserGroupIds') + ->with($user) + ->willReturn($groupIds); + + $res = $this->manager->isSharingDisabledForUser($user); + $this->assertEquals($expected, $res); + } + + public function dataCanShare() { + $data = []; + + /* + * [expected, sharing enabled, disabled for user] + */ + + $data[] = [false, 'no', false]; + $data[] = [false, 'no', true]; + $data[] = [true, 'yes', false]; + $data[] = [false, 'yes', true]; + + return $data; + } + + /** + * @dataProvider dataCanShare + * + * @param bool $expected + * @param string $sharingEnabled + * @param bool $disabledForUser + */ + public function testCanShare($expected, $sharingEnabled, $disabledForUser) { + $this->config->method('getAppValue') + ->will($this->returnValueMap([ + ['core', 'shareapi_enabled', 'yes', $sharingEnabled], + ])); + + $manager = $this->getMockBuilder('\OC\Share20\Manager') + ->setConstructorArgs([ + $this->logger, + $this->config, + $this->defaultProvider, + $this->secureRandom, + $this->hasher, + $this->mountManager, + $this->groupManager, + $this->l, + ]) + ->setMethods(['isSharingDisabledForUser']) + ->getMock(); + + $manager->method('isSharingDisabledForUser')->willReturn($disabledForUser); + + $user = $this->getMock('\OCP\IUser'); + $share = new \OC\Share20\Share(); + $share->setSharedBy($user); + + $res = $this->invokePrivate($manager, 'canShare', [$share]); + $this->assertEquals($expected, $res); + } + + /** + * @expectedException Exception + * @expectedExceptionMessage The Share API is disabled + */ + public function testCreateShareCantShare() { + $manager = $this->getMockBuilder('\OC\Share20\Manager') + ->setConstructorArgs([ + $this->logger, + $this->config, + $this->defaultProvider, + $this->secureRandom, + $this->hasher, + $this->mountManager, + $this->groupManager, + $this->l, + ]) + ->setMethods(['canShare']) + ->getMock(); + + $manager->expects($this->once())->method('canShare')->willReturn(false); + $share = new \OC\Share20\Share(); + $manager->createShare($share); + } + + public function testCreateShareUser() { + $manager = $this->getMockBuilder('\OC\Share20\Manager') + ->setConstructorArgs([ + $this->logger, + $this->config, + $this->defaultProvider, + $this->secureRandom, + $this->hasher, + $this->mountManager, + $this->groupManager, + $this->l, + ]) + ->setMethods(['canShare', 'generalCreateChecks', 'userCreateChecks', 'pathCreateChecks']) + ->getMock(); + + $sharedWith = $this->getMock('\OCP\IUser'); + $sharedBy = $this->getMock('\OCP\IUser'); + $shareOwner = $this->getMock('\OCP\IUser'); + + $path = $this->getMock('\OCP\Files\File'); + $path->method('getOwner')->willReturn($shareOwner); + $path->method('getName')->willReturn('target'); + + $share = $this->createShare( + null, + \OCP\Share::SHARE_TYPE_USER, + $path, + $sharedWith, + $sharedBy, + null, + \OCP\Constants::PERMISSION_ALL); + + $manager->expects($this->once()) + ->method('canShare') + ->with($share) + ->willReturn(true); + $manager->expects($this->once()) + ->method('generalCreateChecks') + ->with($share);; + $manager->expects($this->once()) + ->method('userCreateChecks') + ->with($share);; + $manager->expects($this->once()) + ->method('pathCreateChecks') + ->with($path); + + $this->defaultProvider + ->expects($this->once()) + ->method('create') + ->with($share) + ->will($this->returnArgument(0)); + + $share->expects($this->once()) + ->method('setShareOwner') + ->with($shareOwner); + $share->expects($this->once()) + ->method('setTarget') + ->with('/target'); + + $manager->createShare($share); + } + + public function testCreateShareGroup() { + $manager = $this->getMockBuilder('\OC\Share20\Manager') + ->setConstructorArgs([ + $this->logger, + $this->config, + $this->defaultProvider, + $this->secureRandom, + $this->hasher, + $this->mountManager, + $this->groupManager, + $this->l, + ]) + ->setMethods(['canShare', 'generalCreateChecks', 'groupCreateChecks', 'pathCreateChecks']) + ->getMock(); + + $sharedWith = $this->getMock('\OCP\IGroup'); + $sharedBy = $this->getMock('\OCP\IUser'); + $shareOwner = $this->getMock('\OCP\IUser'); + + $path = $this->getMock('\OCP\Files\File'); + $path->method('getOwner')->willReturn($shareOwner); + $path->method('getName')->willReturn('target'); + + $share = $this->createShare( + null, + \OCP\Share::SHARE_TYPE_GROUP, + $path, + $sharedWith, + $sharedBy, + null, + \OCP\Constants::PERMISSION_ALL); + + $manager->expects($this->once()) + ->method('canShare') + ->with($share) + ->willReturn(true); + $manager->expects($this->once()) + ->method('generalCreateChecks') + ->with($share);; + $manager->expects($this->once()) + ->method('groupCreateChecks') + ->with($share);; + $manager->expects($this->once()) + ->method('pathCreateChecks') + ->with($path); + + $this->defaultProvider + ->expects($this->once()) + ->method('create') + ->with($share) + ->will($this->returnArgument(0)); + + $share->expects($this->once()) + ->method('setShareOwner') + ->with($shareOwner); + $share->expects($this->once()) + ->method('setTarget') + ->with('/target'); + + $manager->createShare($share); + } + + public function testCreateShareLink() { + $manager = $this->getMockBuilder('\OC\Share20\Manager') + ->setConstructorArgs([ + $this->logger, + $this->config, + $this->defaultProvider, + $this->secureRandom, + $this->hasher, + $this->mountManager, + $this->groupManager, + $this->l, + ]) + ->setMethods([ + 'canShare', + 'generalCreateChecks', + 'linkCreateChecks', + 'pathCreateChecks', + 'validateExpiredate', + 'verifyPassword', + ]) + ->getMock(); + + $sharedBy = $this->getMock('\OCP\IUser'); + $sharedBy->method('getUID')->willReturn('sharedBy'); + $shareOwner = $this->getMock('\OCP\IUser'); + + $path = $this->getMock('\OCP\Files\File'); + $path->method('getOwner')->willReturn($shareOwner); + $path->method('getName')->willReturn('target'); + $path->method('getId')->willReturn(1); + + $date = new \DateTime(); + + $share = $this->createShare( + null, + \OCP\Share::SHARE_TYPE_LINK, + $path, + null, + $sharedBy, + null, + \OCP\Constants::PERMISSION_ALL, + $date, + 'password'); + + $manager->expects($this->once()) + ->method('canShare') + ->with($share) + ->willReturn(true); + $manager->expects($this->once()) + ->method('generalCreateChecks') + ->with($share);; + $manager->expects($this->once()) + ->method('linkCreateChecks') + ->with($share);; + $manager->expects($this->once()) + ->method('pathCreateChecks') + ->with($path); + $manager->expects($this->once()) + ->method('validateExpiredate') + ->with($date) + ->will($this->returnArgument(0)); + $manager->expects($this->once()) + ->method('verifyPassword') + ->with('password'); + + $this->hasher->expects($this->once()) + ->method('hash') + ->with('password') + ->willReturn('hashed'); + + $this->secureRandom->method('getMediumStrengthGenerator') + ->will($this->returnSelf()); + $this->secureRandom->method('generate') + ->willReturn('token'); + + $this->defaultProvider + ->expects($this->once()) + ->method('create') + ->with($share) + ->will($this->returnArgument(0)); + + $share->expects($this->once()) + ->method('setShareOwner') + ->with($shareOwner); + $share->expects($this->once()) + ->method('setTarget') + ->with('/target'); + $share->expects($this->once()) + ->method('setExpirationDate') + ->with($date); + $share->expects($this->once()) + ->method('setPassword') + ->with('hashed'); + $share->method('getToken') + ->willReturn('token'); + + $hookListner = $this->getMockBuilder('Dummy')->setMethods(['pre', 'post'])->getMock(); + \OCP\Util::connectHook('OCP\Share', 'pre_shared', $hookListner, 'pre'); + \OCP\Util::connectHook('OCP\Share', 'post_shared', $hookListner, 'post'); + + $hookListnerExpectsPre = [ + 'itemType' => 'file', + 'itemSource' => 1, + 'shareType' => \OCP\Share::SHARE_TYPE_LINK, + 'uidOwner' => 'sharedBy', + 'permissions' => 31, + 'fileSource' => 1, + 'expiration' => $date, + 'token' => 'token', + 'run' => true, + 'error' => '', + ]; + + $hookListnerExpectsPost = [ + 'itemType' => 'file', + 'itemSource' => 1, + 'shareType' => \OCP\Share::SHARE_TYPE_LINK, + 'uidOwner' => 'sharedBy', + 'permissions' => 31, + 'fileSource' => 1, + 'expiration' => $date, + 'token' => 'token', + 'id' => 42, + ]; + + $share->method('getId')->willReturn(42); + + $hookListner->expects($this->once()) + ->method('pre') + ->with($this->equalTo($hookListnerExpectsPre)); + $hookListner->expects($this->once()) + ->method('post') + ->with($this->equalTo($hookListnerExpectsPost)); + + $manager->createShare($share); + } + + /** + * @expectedException Exception + * @expectedExceptionMessage I won't let you share + */ + public function testCreateShareHookError() { + $manager = $this->getMockBuilder('\OC\Share20\Manager') + ->setConstructorArgs([ + $this->logger, + $this->config, + $this->defaultProvider, + $this->secureRandom, + $this->hasher, + $this->mountManager, + $this->groupManager, + $this->l, + ]) + ->setMethods([ + 'canShare', + 'generalCreateChecks', + 'userCreateChecks', + 'pathCreateChecks', + ]) + ->getMock(); + + $sharedWith = $this->getMock('\OCP\IUser'); + $sharedBy = $this->getMock('\OCP\IUser'); + $shareOwner = $this->getMock('\OCP\IUser'); + + $path = $this->getMock('\OCP\Files\File'); + $path->method('getOwner')->willReturn($shareOwner); + $path->method('getName')->willReturn('target'); + + $date = new \DateTime(); + + $share = $this->createShare( + null, + \OCP\Share::SHARE_TYPE_USER, + $path, + $sharedWith, + $sharedBy, + null, + \OCP\Constants::PERMISSION_ALL); + + $manager->expects($this->once()) + ->method('canShare') + ->with($share) + ->willReturn(true); + $manager->expects($this->once()) + ->method('generalCreateChecks') + ->with($share);; + $manager->expects($this->once()) + ->method('userCreateChecks') + ->with($share);; + $manager->expects($this->once()) + ->method('pathCreateChecks') + ->with($path); + + $share->expects($this->once()) + ->method('setShareOwner') + ->with($shareOwner); + $share->expects($this->once()) + ->method('setTarget') + ->with('/target'); + + $dummy = new DummyCreate(); + \OCP\Util::connectHook('OCP\Share', 'pre_shared', $dummy, 'listner'); + + $manager->createShare($share); + } } + +class DummyPassword { + public function listner($array) { + $array['accepted'] = false; + $array['message'] = 'password not accepted'; + } +} + +class DummyCreate { + public function listner($array) { + $array['run'] = false; + $array['error'] = 'I won\'t let you share!'; + } +} + diff --git a/tests/lib/updater.php b/tests/lib/updater.php index 313ffb65738..8ee77b9f81e 100644 --- a/tests/lib/updater.php +++ b/tests/lib/updater.php @@ -27,7 +27,7 @@ use OCP\ILogger; use OC\IntegrityCheck\Checker; class UpdaterTest extends \Test\TestCase { - /** @var IConfig */ + /** @var IConfig|\PHPUnit_Framework_MockObject_MockObject */ private $config; /** @var HTTPHelper */ private $httpHelper; @@ -136,9 +136,33 @@ class UpdaterTest extends \Test\TestCase { ['9.0.0.0', '8.0.0.0', '7.0', false], ['9.1.0.0', '8.0.0.0', '7.0', false], ['8.2.0.0', '8.1.0.0', '8.0', false], + + // With debug enabled + ['8.0.0.0', '8.2.0.0', '8.1', false, true], + ['8.1.0.0', '8.2.0.0', '8.1', true, true], + ['8.2.0.1', '8.2.0.1', '8.1', true, true], + ['8.3.0.0', '8.2.0.0', '8.1', true, true], ]; } + /** + * @dataProvider versionCompatibilityTestData + * + * @param string $oldVersion + * @param string $newVersion + * @param string $allowedVersion + * @param bool $result + * @param bool $debug + */ + public function testIsUpgradePossible($oldVersion, $newVersion, $allowedVersion, $result, $debug = false) { + $this->config->expects($this->any()) + ->method('getSystemValue') + ->with('debug', false) + ->willReturn($debug); + + $this->assertSame($result, $this->updater->isUpgradePossible($oldVersion, $newVersion, $allowedVersion)); + } + public function testSetSimulateStepEnabled() { $this->updater->setSimulateStepEnabled(true); $this->assertSame(true, $this->invokePrivate($this->updater, 'simulateStepEnabled')); @@ -160,18 +184,6 @@ class UpdaterTest extends \Test\TestCase { $this->assertSame(false, $this->invokePrivate($this->updater, 'skip3rdPartyAppsDisable')); } - /** - * @dataProvider versionCompatibilityTestData - * - * @param string $oldVersion - * @param string $newVersion - * @param string $allowedVersion - * @param bool $result - */ - public function testIsUpgradePossible($oldVersion, $newVersion, $allowedVersion, $result) { - $this->assertSame($result, $this->updater->isUpgradePossible($oldVersion, $newVersion, $allowedVersion)); - } - public function testCheckInCache() { $expectedResult = [ 'version' => '8.0.4.2', diff --git a/version.php b/version.php index ca90fdb3167..b8b1ad2b5fa 100644 --- a/version.php +++ b/version.php @@ -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, 0, 0, 5); +$OC_Version = array(9, 0, 0, 6); // The human readable string $OC_VersionString = '9.0 pre alpha'; |