diff options
67 files changed, 1386 insertions, 419 deletions
diff --git a/3rdparty b/3rdparty -Subproject 70ed7fe3d530d74f0eb501ce6dd3dbe6fe8d81c +Subproject 4a43dcef48f684e9cd17f740b6f2c67bc9142a0 diff --git a/apps/files/ajax/list.php b/apps/files/ajax/list.php index 4aed79d70f7..f9facd0d7f7 100644 --- a/apps/files/ajax/list.php +++ b/apps/files/ajax/list.php @@ -22,10 +22,32 @@ try { $sortAttribute = isset($_GET['sort']) ? $_GET['sort'] : 'name'; $sortDirection = isset($_GET['sortdirection']) ? ($_GET['sortdirection'] === 'desc') : false; + $mimetypeFilters = isset($_GET['mimetypes']) ? json_decode($_GET['mimetypes']) : ''; - // make filelist + $files = []; + // Clean up duplicates from array + if (is_array($mimetypeFilters) && count($mimetypeFilters)) { + $mimetypeFilters = array_unique($mimetypeFilters); + + if (!in_array('httpd/unix-directory', $mimetypeFilters)) { + // append folder filter to be able to browse folders + $mimetypeFilters[] = 'httpd/unix-directory'; + } + + // create filelist with mimetype filter - as getFiles only supports on + // mimetype filter at once we will filter this folder for each + // mimetypeFilter + foreach ($mimetypeFilters as $mimetypeFilter) { + $files = array_merge($files, \OCA\Files\Helper::getFiles($dir, $sortAttribute, $sortDirection, $mimetypeFilter)); + } + + // sort the files accordingly + $files = \OCA\Files\Helper::sortFiles($files, $sortAttribute, $sortDirection); + } else { + // create file list without mimetype filter + $files = \OCA\Files\Helper::getFiles($dir, $sortAttribute, $sortDirection); + } - $files = \OCA\Files\Helper::getFiles($dir, $sortAttribute, $sortDirection); $files = \OCA\Files\Helper::populateTags($files); $data['directory'] = $dir; $data['files'] = \OCA\Files\Helper::formatFileInfos($files); diff --git a/apps/files/lib/helper.php b/apps/files/lib/helper.php index 84b1a0f1662..d92e69674f9 100644 --- a/apps/files/lib/helper.php +++ b/apps/files/lib/helper.php @@ -170,10 +170,11 @@ class Helper * @param string $dir path to the directory * @param string $sortAttribute attribute to sort on * @param bool $sortDescending true for descending sort, false otherwise + * @param string $mimetypeFilter limit returned content to this mimetype or mimepart * @return \OCP\Files\FileInfo[] files */ - public static function getFiles($dir, $sortAttribute = 'name', $sortDescending = false) { - $content = \OC\Files\Filesystem::getDirectoryContent($dir); + public static function getFiles($dir, $sortAttribute = 'name', $sortDescending = false, $mimetypeFilter = '') { + $content = \OC\Files\Filesystem::getDirectoryContent($dir, $mimetypeFilter); return self::sortFiles($content, $sortAttribute, $sortDescending); } diff --git a/apps/files/tests/controller/apicontrollertest.php b/apps/files/tests/controller/apicontrollertest.php index 35df1b6be28..87c57b6b7db 100644 --- a/apps/files/tests/controller/apicontrollertest.php +++ b/apps/files/tests/controller/apicontrollertest.php @@ -87,7 +87,7 @@ class ApiControllerTest extends TestCase { [ 'id' => null, 'parentId' => null, - 'date' => 'January 1, 1970 at 12:00:55 AM GMT+0', + 'date' => \OCP\Util::formatDate(55), 'mtime' => 55000, 'icon' => \OCA\Files\Helper::determineIcon($fileInfo), 'name' => 'root.txt', @@ -152,7 +152,7 @@ class ApiControllerTest extends TestCase { [ 'id' => null, 'parentId' => null, - 'date' => 'January 1, 1970 at 12:00:55 AM GMT+0', + 'date' => \OCP\Util::formatDate(55), 'mtime' => 55000, 'icon' => \OCA\Files\Helper::determineIcon($fileInfo1), 'name' => 'root.txt', @@ -171,7 +171,7 @@ class ApiControllerTest extends TestCase { [ 'id' => null, 'parentId' => null, - 'date' => 'January 1, 1970 at 12:16:39 AM GMT+0', + 'date' => \OCP\Util::formatDate(999), 'mtime' => 999000, 'icon' => \OCA\Files\Helper::determineIcon($fileInfo2), 'name' => 'root.txt', @@ -240,4 +240,4 @@ class ApiControllerTest extends TestCase { $expected = new DataResponse('My error message', Http::STATUS_NOT_FOUND); $this->assertEquals($expected, $this->apiController->updateFileTags('/path.txt', ['Tag1', 'Tag2'])); } -}
\ No newline at end of file +} diff --git a/apps/files/tests/service/tagservice.php b/apps/files/tests/service/tagservice.php index 158dd77e858..322a940ac74 100644 --- a/apps/files/tests/service/tagservice.php +++ b/apps/files/tests/service/tagservice.php @@ -65,7 +65,7 @@ class TagServiceTest extends \Test\TestCase { ->withAnyParameters() ->will($this->returnValue($user)); - $this->root = \OC::$server->getUserFolder(); + $this->root = \OC::$server->getUserFolder($this->user); $this->tagger = \OC::$server->getTagManager()->load('files'); $this->tagService = new TagService( diff --git a/apps/files_encryption/lib/util.php b/apps/files_encryption/lib/util.php index 14d0a0bc4b9..106ed3b2f08 100644 --- a/apps/files_encryption/lib/util.php +++ b/apps/files_encryption/lib/util.php @@ -24,6 +24,7 @@ */ namespace OCA\Files_Encryption; +use OC\User\NoUserException; /** * Class for utilities relating to encrypted file storage system @@ -945,8 +946,14 @@ class Util { ) { return true; } - $util = new Util($this->view, $user); - return $util->ready(); + try { + $util = new Util($this->view, $user); + return $util->ready(); + } catch (NoUserException $e) { + \OCP\Util::writeLog('Encryption library', + 'No User object for '.$user, \OCP\Util::DEBUG); + return false; + } } /** diff --git a/apps/files_encryption/tests/hooks.php b/apps/files_encryption/tests/hooks.php index 7c60024d637..755573e1dbc 100644 --- a/apps/files_encryption/tests/hooks.php +++ b/apps/files_encryption/tests/hooks.php @@ -30,6 +30,7 @@ class Hooks extends TestCase { const TEST_ENCRYPTION_HOOKS_USER1 = "test-encryption-hooks-user1.dot"; const TEST_ENCRYPTION_HOOKS_USER2 = "test-encryption-hooks-user2.dot"; + const TEST_ENCRYPTION_HOOKS_USER3 = "test-encryption-hooks-user3.dot"; /** @var \OC\Files\View */ public $user1View; // view on /data/user1/files @@ -91,6 +92,7 @@ class Hooks extends TestCase { // cleanup test user \OC_User::deleteUser(self::TEST_ENCRYPTION_HOOKS_USER1); \OC_User::deleteUser(self::TEST_ENCRYPTION_HOOKS_USER2); + \OC_User::deleteUser(self::TEST_ENCRYPTION_HOOKS_USER3); parent::tearDownAfterClass(); } @@ -407,31 +409,35 @@ class Hooks extends TestCase { $view = new \OC\Files\View(); // set user password for the first time - \OCA\Files_Encryption\Hooks::postCreateUser(array('uid' => 'newUser', 'password' => 'newUserPassword')); + \OC_User::createUser(self::TEST_ENCRYPTION_HOOKS_USER3, 'newUserPassword'); + \OCA\Files_Encryption\Hooks::postCreateUser(array( + 'uid' => self::TEST_ENCRYPTION_HOOKS_USER3, + 'password' => 'newUserPassword') + ); - $this->assertTrue($view->file_exists(\OCA\Files_Encryption\Keymanager::getPublicKeyPath() . '/newUser.publicKey')); - $this->assertTrue($view->file_exists('newUser/files_encryption/newUser.privateKey')); + $this->assertTrue($view->file_exists(\OCA\Files_Encryption\Keymanager::getPublicKeyPath() . '/'.self::TEST_ENCRYPTION_HOOKS_USER3.'.publicKey')); + $this->assertTrue($view->file_exists(self::TEST_ENCRYPTION_HOOKS_USER3.'/files_encryption/'.self::TEST_ENCRYPTION_HOOKS_USER3.'.privateKey')); // check if we are able to decrypt the private key - $encryptedKey = \OCA\Files_Encryption\Keymanager::getPrivateKey($view, 'newUser'); + $encryptedKey = \OCA\Files_Encryption\Keymanager::getPrivateKey($view, self::TEST_ENCRYPTION_HOOKS_USER3); $privateKey = \OCA\Files_Encryption\Crypt::decryptPrivateKey($encryptedKey, 'newUserPassword'); $this->assertTrue(is_string($privateKey)); // change the password before the user logged-in for the first time, // we can replace the encryption keys - \OCA\Files_Encryption\Hooks::setPassphrase(array('uid' => 'newUser', 'password' => 'passwordChanged')); + \OCA\Files_Encryption\Hooks::setPassphrase(array('uid' => self::TEST_ENCRYPTION_HOOKS_USER3, 'password' => 'passwordChanged')); - $encryptedKey = \OCA\Files_Encryption\Keymanager::getPrivateKey($view, 'newUser'); + $encryptedKey = \OCA\Files_Encryption\Keymanager::getPrivateKey($view, self::TEST_ENCRYPTION_HOOKS_USER3); $privateKey = \OCA\Files_Encryption\Crypt::decryptPrivateKey($encryptedKey, 'passwordChanged'); $this->assertTrue(is_string($privateKey)); // now create a files folder to simulate a already used account - $view->mkdir('/newUser/files'); + $view->mkdir('/'.self::TEST_ENCRYPTION_HOOKS_USER3.'/files'); // change the password after the user logged in, now the password should not change - \OCA\Files_Encryption\Hooks::setPassphrase(array('uid' => 'newUser', 'password' => 'passwordChanged2')); + \OCA\Files_Encryption\Hooks::setPassphrase(array('uid' => self::TEST_ENCRYPTION_HOOKS_USER3, 'password' => 'passwordChanged2')); - $encryptedKey = \OCA\Files_Encryption\Keymanager::getPrivateKey($view, 'newUser'); + $encryptedKey = \OCA\Files_Encryption\Keymanager::getPrivateKey($view, self::TEST_ENCRYPTION_HOOKS_USER3); $privateKey = \OCA\Files_Encryption\Crypt::decryptPrivateKey($encryptedKey, 'passwordChanged2'); $this->assertFalse($privateKey); diff --git a/apps/files_external/lib/owncloud.php b/apps/files_external/lib/owncloud.php index 04a1e959eb0..7d452e8ff4d 100644 --- a/apps/files_external/lib/owncloud.php +++ b/apps/files_external/lib/owncloud.php @@ -37,13 +37,13 @@ class OwnCloud extends \OC\Files\Storage\DAV{ $host = substr($host, 0, $hostSlashPos); } - if (substr($contextPath , 1) !== '/'){ + if (substr($contextPath, -1) !== '/'){ $contextPath .= '/'; } if (isset($params['root'])){ $root = $params['root']; - if (substr($root, 1) !== '/'){ + if (substr($root, 0, 1) !== '/'){ $root = '/' . $root; } } diff --git a/apps/files_external/lib/smb_oc.php b/apps/files_external/lib/smb_oc.php index a7c93d97fd1..5e9442746f8 100644 --- a/apps/files_external/lib/smb_oc.php +++ b/apps/files_external/lib/smb_oc.php @@ -18,13 +18,19 @@ class SMB_OC extends \OC\Files\Storage\SMB { * @throws \Exception */ public function __construct($params) { - if (isset($params['host']) && \OC::$server->getSession()->exists('smb-credentials')) { + if (isset($params['host'])) { $host=$params['host']; $this->username_as_share = ($params['username_as_share'] === 'true'); - $params_auth = json_decode(\OC::$server->getCrypto()->decrypt(\OC::$server->getSession()->get('smb-credentials')), true); - $user = \OC::$server->getSession()->get('loginname'); - $password = $params_auth['password']; + $user = 'foo'; + $password = 'bar'; + if (\OC::$server->getSession()->exists('smb-credentials')) { + $params_auth = json_decode(\OC::$server->getCrypto()->decrypt(\OC::$server->getSession()->get('smb-credentials')), true); + $user = \OC::$server->getSession()->get('loginname'); + $password = $params_auth['password']; + } else { + // assume we are testing from the admin section + } $root=isset($params['root'])?$params['root']:'/'; $share = ''; diff --git a/apps/files_external/tests/owncloudfunctions.php b/apps/files_external/tests/owncloudfunctions.php index 8232f30a5e2..ca9a8b231f1 100644 --- a/apps/files_external/tests/owncloudfunctions.php +++ b/apps/files_external/tests/owncloudfunctions.php @@ -68,6 +68,14 @@ class OwnCloudFunctions extends \Test\TestCase { ), 'http://testhost/testroot/remote.php/webdav/subdir/', ), + array( + array( + 'host' => 'http://testhost/testroot/', + 'root' => '/subdir', + 'secure' => false + ), + 'http://testhost/testroot/remote.php/webdav/subdir/', + ), ); } diff --git a/apps/files_sharing/ajax/external.php b/apps/files_sharing/ajax/external.php index 30c1f38801e..153285e11ff 100644 --- a/apps/files_sharing/ajax/external.php +++ b/apps/files_sharing/ajax/external.php @@ -38,8 +38,6 @@ $externalManager = new \OCA\Files_Sharing\External\Manager( \OC::$server->getUserSession()->getUser()->getUID() ); -$name = OCP\Files::buildNotExistingFileName('/', $name); - // check for ssl cert if (substr($remote, 0, 5) === 'https' and !OC_Util::getUrlContent($remote)) { \OCP\JSON::error(array('data' => array('message' => $l->t('Invalid or untrusted SSL certificate')))); diff --git a/apps/files_sharing/api/server2server.php b/apps/files_sharing/api/server2server.php index f2f7561598f..89a0262481c 100644 --- a/apps/files_sharing/api/server2server.php +++ b/apps/files_sharing/api/server2server.php @@ -64,8 +64,6 @@ class Server2Server { $shareWith ); - $name = \OCP\Files::buildNotExistingFileName('/', $name); - try { $externalManager->addShare($remote, $token, '', $name, $owner, false, $shareWith, $remoteId); diff --git a/apps/files_sharing/lib/controllers/sharecontroller.php b/apps/files_sharing/lib/controllers/sharecontroller.php index b224b7dc108..c89e005b95f 100644 --- a/apps/files_sharing/lib/controllers/sharecontroller.php +++ b/apps/files_sharing/lib/controllers/sharecontroller.php @@ -225,26 +225,48 @@ class ShareController extends Controller { } } + $files_list = null; + if (!is_null($files)) { // download selected files + $files_list = json_decode($files); + // in case we get only a single file + if ($files_list === null) { + $files_list = array($files); + } + } + $originalSharePath = self::getPath($token); + // Create the activities if (isset($originalSharePath) && Filesystem::isReadable($originalSharePath . $path)) { $originalSharePath = Filesystem::normalizePath($originalSharePath . $path); - $type = \OC\Files\Filesystem::is_dir($originalSharePath) ? 'folder' : 'file'; - $args = $type === 'folder' ? array('dir' => $originalSharePath) : array('dir' => dirname($originalSharePath), 'scrollto' => basename($originalSharePath)); - $linkToFile = \OCP\Util::linkToAbsolute('files', 'index.php', $args); - $subject = $type === 'folder' ? Activity::SUBJECT_PUBLIC_SHARED_FOLDER_DOWNLOADED : Activity::SUBJECT_PUBLIC_SHARED_FILE_DOWNLOADED; - $this->activityManager->publishActivity( - 'files_sharing', $subject, array($originalSharePath), '', array(), $originalSharePath, - $linkToFile, $linkItem['uid_owner'], Activity::TYPE_PUBLIC_LINKS, Activity::PRIORITY_MEDIUM); - } + $isDir = \OC\Files\Filesystem::is_dir($originalSharePath); + + $activities = []; + if (!$isDir) { + // Single file public share + $activities[$originalSharePath] = Activity::SUBJECT_PUBLIC_SHARED_FILE_DOWNLOADED; + } else if (!empty($files_list)) { + // Only some files are downloaded + foreach ($files_list as $file) { + $filePath = Filesystem::normalizePath($originalSharePath . '/' . $file); + $isDir = \OC\Files\Filesystem::is_dir($filePath); + $activities[$filePath] = ($isDir) ? Activity::SUBJECT_PUBLIC_SHARED_FOLDER_DOWNLOADED : Activity::SUBJECT_PUBLIC_SHARED_FILE_DOWNLOADED; + } + } else { + // The folder is downloaded + $activities[$originalSharePath] = Activity::SUBJECT_PUBLIC_SHARED_FOLDER_DOWNLOADED; + } - if (!is_null($files)) { // download selected files - $files_list = json_decode($files); - // in case we get only a single file - if ($files_list === NULL) { - $files_list = array($files); + foreach ($activities as $filePath => $subject) { + $this->activityManager->publishActivity( + 'files_sharing', $subject, array($filePath), '', array(), + $filePath, '', $linkItem['uid_owner'], Activity::TYPE_PUBLIC_LINKS, Activity::PRIORITY_MEDIUM + ); } + } + // download selected files + if (!is_null($files)) { // FIXME: The exit is required here because otherwise the AppFramework is trying to add headers as well // after dispatching the request which results in a "Cannot modify header information" notice. OC_Files::get($originalSharePath, $files_list, $_SERVER['REQUEST_METHOD'] == 'HEAD'); diff --git a/apps/files_sharing/lib/external/manager.php b/apps/files_sharing/lib/external/manager.php index 8985aeb3fce..aef7654d382 100644 --- a/apps/files_sharing/lib/external/manager.php +++ b/apps/files_sharing/lib/external/manager.php @@ -9,6 +9,7 @@ namespace OCA\Files_Sharing\External; use OC\Files\Filesystem; +use OCP\Files; class Manager { const STORAGE = '\OCA\Files_Sharing\External\Storage'; @@ -29,7 +30,7 @@ class Manager { private $mountManager; /** - * @var \OC\Files\Storage\StorageFactory + * @var \OCP\Files\Storage\IStorageFactory */ private $storageLoader; @@ -41,12 +42,12 @@ class Manager { /** * @param \OCP\IDBConnection $connection * @param \OC\Files\Mount\Manager $mountManager - * @param \OC\Files\Storage\StorageFactory $storageLoader + * @param \OCP\Files\Storage\IStorageFactory $storageLoader * @param \OC\HTTPHelper $httpHelper * @param string $uid */ public function __construct(\OCP\IDBConnection $connection, \OC\Files\Mount\Manager $mountManager, - \OC\Files\Storage\StorageFactory $storageLoader, \OC\HTTPHelper $httpHelper, $uid) { + \OCP\Files\Storage\IStorageFactory $storageLoader, \OC\HTTPHelper $httpHelper, $uid) { $this->connection = $connection; $this->mountManager = $mountManager; $this->storageLoader = $storageLoader; @@ -65,33 +66,64 @@ class Manager { * @param boolean $accepted * @param string $user * @param int $remoteId - * @return mixed + * @return Mount|null */ public function addShare($remote, $token, $password, $name, $owner, $accepted=false, $user = null, $remoteId = -1) { $user = $user ? $user : $this->uid; $accepted = $accepted ? 1 : 0; + $name = Filesystem::normalizePath('/' . $name); + + if (!$accepted) { + // To avoid conflicts with the mount point generation later, + // we only use a temporary mount point name here. The real + // mount point name will be generated when accepting the share, + // using the original share item name. + $tmpMountPointName = '{{TemporaryMountPointName#' . $name . '}}'; + $mountPoint = $tmpMountPointName; + $hash = md5($tmpMountPointName); + $data = [ + 'remote' => $remote, + 'share_token' => $token, + 'password' => $password, + 'name' => $name, + 'owner' => $owner, + 'user' => $user, + 'mountpoint' => $mountPoint, + 'mountpoint_hash' => $hash, + 'accepted' => $accepted, + 'remote_id' => $remoteId, + ]; + + $i = 1; + while (!$this->connection->insertIfNotExist('*PREFIX*share_external', $data, ['user', 'mountpoint_hash'])) { + // The external share already exists for the user + $data['mountpoint'] = $tmpMountPointName . '-' . $i; + $data['mountpoint_hash'] = md5($data['mountpoint']); + $i++; + } + return null; + } - $mountPoint = Filesystem::normalizePath('/' . $name); + $mountPoint = Files::buildNotExistingFileName('/', $name); + $mountPoint = Filesystem::normalizePath('/' . $mountPoint); + $hash = md5($mountPoint); $query = $this->connection->prepare(' INSERT INTO `*PREFIX*share_external` (`remote`, `share_token`, `password`, `name`, `owner`, `user`, `mountpoint`, `mountpoint_hash`, `accepted`, `remote_id`) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) '); - $hash = md5($mountPoint); $query->execute(array($remote, $token, $password, $name, $owner, $user, $mountPoint, $hash, $accepted, $remoteId)); - if ($accepted) { - $options = array( - 'remote' => $remote, - 'token' => $token, - 'password' => $password, - 'mountpoint' => $mountPoint, - 'owner' => $owner - ); - return $this->mountShare($options); - } + $options = array( + 'remote' => $remote, + 'token' => $token, + 'password' => $password, + 'mountpoint' => $mountPoint, + 'owner' => $owner + ); + return $this->mountShare($options); } private function setupMounts() { @@ -124,7 +156,7 @@ class Manager { */ private function getShare($id) { $getShare = $this->connection->prepare(' - SELECT `remote`, `share_token` + SELECT `remote`, `remote_id`, `share_token`, `name` FROM `*PREFIX*share_external` WHERE `id` = ? AND `user` = ?'); $result = $getShare->execute(array($id, $this->uid)); @@ -142,12 +174,18 @@ class Manager { $share = $this->getShare($id); if ($share) { + $mountPoint = Files::buildNotExistingFileName('/', $share['name']); + $mountPoint = Filesystem::normalizePath('/' . $mountPoint); + $hash = md5($mountPoint); + $acceptShare = $this->connection->prepare(' UPDATE `*PREFIX*share_external` - SET `accepted` = ? + SET `accepted` = ?, + `mountpoint` = ?, + `mountpoint_hash` = ? WHERE `id` = ? AND `user` = ?'); - $acceptShare->execute(array(1, $id, $this->uid)); - $this->sendFeedbackToRemote($share['remote'], $share['share_token'], $id, 'accept'); + $acceptShare->execute(array(1, $mountPoint, $hash, $id, $this->uid)); + $this->sendFeedbackToRemote($share['remote'], $share['share_token'], $share['remote_id'], 'accept'); } } @@ -164,7 +202,7 @@ class Manager { $removeShare = $this->connection->prepare(' DELETE FROM `*PREFIX*share_external` WHERE `id` = ? AND `user` = ?'); $removeShare->execute(array($id, $this->uid)); - $this->sendFeedbackToRemote($share['remote'], $share['share_token'], $id, 'decline'); + $this->sendFeedbackToRemote($share['remote'], $share['share_token'], $share['remote_id'], 'decline'); } } @@ -173,13 +211,13 @@ class Manager { * * @param string $remote * @param string $token - * @param int $id + * @param int $remoteId Share id on the remote host * @param string $feedback * @return boolean */ - private function sendFeedbackToRemote($remote, $token, $id, $feedback) { + private function sendFeedbackToRemote($remote, $token, $remoteId, $feedback) { - $url = $remote . \OCP\Share::BASE_PATH_TO_SHARE_API . '/' . $id . '/' . $feedback . '?format=' . \OCP\Share::RESPONSE_FORMAT; + $url = rtrim($remote, '/') . \OCP\Share::BASE_PATH_TO_SHARE_API . '/' . $remoteId . '/' . $feedback . '?format=' . \OCP\Share::RESPONSE_FORMAT; $fields = array('token' => $token); $result = $this->httpHelper->post($url, $fields); @@ -315,10 +353,29 @@ class Manager { * @return array list of open server-to-server shares */ public function getOpenShares() { - $openShares = $this->connection->prepare('SELECT * FROM `*PREFIX*share_external` WHERE `accepted` = ? AND `user` = ?'); - $result = $openShares->execute(array(0, $this->uid)); + return $this->getShares(false); + } + + /** + * return a list of shares for the user + * + * @param bool|null $accepted True for accepted only, + * false for not accepted, + * null for all shares of the user + * @return array list of open server-to-server shares + */ + private function getShares($accepted) { + $query = 'SELECT * FROM `*PREFIX*share_external` WHERE `user` = ?'; + $parameters = [$this->uid]; + if (!is_null($accepted)) { + $query .= ' AND `accepted` = ?'; + $parameters[] = (int) $accepted; + } + $query .= ' ORDER BY `id` ASC'; - return $result ? $openShares->fetchAll() : array(); + $shares = $this->connection->prepare($query); + $result = $shares->execute($parameters); + return $result ? $shares->fetchAll() : []; } -}
\ No newline at end of file +} diff --git a/apps/files_sharing/lib/external/storage.php b/apps/files_sharing/lib/external/storage.php index 648376e8cb5..bcd93f5de3f 100644 --- a/apps/files_sharing/lib/external/storage.php +++ b/apps/files_sharing/lib/external/storage.php @@ -70,7 +70,7 @@ class Storage extends DAV implements ISharedStorage { 'host' => $host, 'root' => $root, 'user' => $options['token'], - 'password' => $options['password'] + 'password' => (string)$options['password'] )); } @@ -237,7 +237,8 @@ class Storage extends DAV implements ISharedStorage { $errorMessage = curl_error($ch); curl_close($ch); if (!empty($errorMessage)) { - throw new \Exception($errorMessage); + \OCP\Util::writeLog('files_sharing', 'Error getting remote share info: ' . $errorMessage, \OCP\Util::ERROR); + throw new StorageNotAvailableException($errorMessage); } switch ($status) { diff --git a/apps/files_sharing/lib/readonlycache.php b/apps/files_sharing/lib/readonlycache.php deleted file mode 100644 index 6dd3b9cf61d..00000000000 --- a/apps/files_sharing/lib/readonlycache.php +++ /dev/null @@ -1,27 +0,0 @@ -<?php -/** - * Copyright (c) 2014 Robin Appelman <icewind@owncloud.com> - * This file is licensed under the Affero General Public License version 3 or - * later. - * See the COPYING-README file. - */ - -namespace OCA\Files_Sharing; - -use OC\Files\Cache\Cache; - -class ReadOnlyCache extends Cache { - public function get($path) { - $data = parent::get($path); - $data['permissions'] &= (\OCP\Constants::PERMISSION_READ | \OCP\Constants::PERMISSION_SHARE); - return $data; - } - - public function getFolderContents($path) { - $content = parent::getFolderContents($path); - foreach ($content as &$data) { - $data['permissions'] &= (\OCP\Constants::PERMISSION_READ | \OCP\Constants::PERMISSION_SHARE); - } - return $content; - } -} diff --git a/apps/files_sharing/lib/readonlywrapper.php b/apps/files_sharing/lib/readonlywrapper.php index 58a5695aff8..72377516fc0 100644 --- a/apps/files_sharing/lib/readonlywrapper.php +++ b/apps/files_sharing/lib/readonlywrapper.php @@ -8,7 +8,9 @@ namespace OCA\Files_Sharing; +use OC\Files\Cache\Wrapper\CachePermissionsMask; use OC\Files\Storage\Wrapper\Wrapper; +use OCP\Constants; class ReadOnlyWrapper extends Wrapper { public function isUpdatable($path) { @@ -51,6 +53,7 @@ class ReadOnlyWrapper extends Wrapper { if (!$storage) { $storage = $this; } - return new ReadOnlyCache($storage); + $sourceCache = $this->storage->getCache($path, $storage); + return new CachePermissionsMask($sourceCache, Constants::PERMISSION_READ | Constants::PERMISSION_SHARE); } } diff --git a/apps/files_sharing/publicwebdav.php b/apps/files_sharing/publicwebdav.php index 240891ffef6..f9b47bad565 100644 --- a/apps/files_sharing/publicwebdav.php +++ b/apps/files_sharing/publicwebdav.php @@ -40,7 +40,8 @@ $server->addPlugin(new OC_Connector_Sabre_ExceptionLoggerPlugin('webdav')); // wait with registering these until auth is handled and the filesystem is setup $server->subscribeEvent('beforeMethod', function () use ($server, $objectTree, $authBackend) { $share = $authBackend->getShare(); - $owner = $share['uid_owner']; + $rootShare = \OCP\Share::resolveReShare($share); + $owner = $rootShare['uid_owner']; $isWritable = $share['permissions'] & (\OCP\Constants::PERMISSION_UPDATE | \OCP\Constants::PERMISSION_CREATE); $fileId = $share['file_source']; diff --git a/apps/files_sharing/tests/external/managertest.php b/apps/files_sharing/tests/external/managertest.php new file mode 100644 index 00000000000..33a6465cf82 --- /dev/null +++ b/apps/files_sharing/tests/external/managertest.php @@ -0,0 +1,216 @@ +<?php +/** + * ownCloud + * + * @author Joas Schilling + * @copyright 2015 Joas Schilling <nickvergessen@owncloud.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + * License as published by the Free Software Foundation; either + * version 3 of the License, or any later version. + * + * This library 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 along with this library. If not, see <http://www.gnu.org/licenses/>. + * + */ + +namespace OCA\Files_Sharing\Tests\External; + +use OC\Files\Storage\StorageFactory; +use OCA\Files_Sharing\External\Manager; +use OCA\Files_Sharing\Tests\TestCase; + +class ManagerTest extends TestCase { + + /** @var Manager **/ + private $manager; + + /** @var \OC\Files\Mount\Manager */ + private $mountManager; + + /** @var \PHPUnit_Framework_MockObject_MockObject */ + private $httpHelper; + + private $uid; + + protected function setUp() { + parent::setUp(); + + $this->uid = $this->getUniqueID('user'); + $this->mountManager = new \OC\Files\Mount\Manager(); + $this->httpHelper = $httpHelper = $this->getMockBuilder('\OC\HTTPHelper')->disableOriginalConstructor()->getMock(); + /** @var \OC\HTTPHelper $httpHelper */ + $this->manager = new Manager( + \OC::$server->getDatabaseConnection(), + $this->mountManager, + new StorageFactory(), + $httpHelper, + $this->uid + ); + } + + public function testAddShare() { + + $shareData1 = [ + 'remote' => 'http://localhost', + 'token' => 'token1', + 'password' => '', + 'name' => '/SharedFolder', + 'owner' => 'foobar', + 'accepted' => false, + 'user' => $this->uid, + ]; + $shareData2 = $shareData1; + $shareData2['token'] = 'token2'; + $shareData3 = $shareData1; + $shareData3['token'] = 'token3'; + + // Add a share for "user" + $this->assertSame(null, call_user_func_array([$this->manager, 'addShare'], $shareData1)); + $openShares = $this->manager->getOpenShares(); + $this->assertCount(1, $openShares); + $this->assertExternalShareEntry($shareData1, $openShares[0], 1, '{{TemporaryMountPointName#' . $shareData1['name'] . '}}'); + + \Test_Helper::invokePrivate($this->manager, 'setupMounts'); + $this->assertNotMount('SharedFolder'); + $this->assertNotMount('{{TemporaryMountPointName#' . $shareData1['name'] . '}}'); + + // Add a second share for "user" with the same name + $this->assertSame(null, call_user_func_array([$this->manager, 'addShare'], $shareData2)); + $openShares = $this->manager->getOpenShares(); + $this->assertCount(2, $openShares); + $this->assertExternalShareEntry($shareData1, $openShares[0], 1, '{{TemporaryMountPointName#' . $shareData1['name'] . '}}'); + // New share falls back to "-1" appendix, because the name is already taken + $this->assertExternalShareEntry($shareData2, $openShares[1], 2, '{{TemporaryMountPointName#' . $shareData2['name'] . '}}-1'); + + \Test_Helper::invokePrivate($this->manager, 'setupMounts'); + $this->assertNotMount('SharedFolder'); + $this->assertNotMount('{{TemporaryMountPointName#' . $shareData1['name'] . '}}'); + $this->assertNotMount('{{TemporaryMountPointName#' . $shareData1['name'] . '}}-1'); + + $this->httpHelper->expects($this->at(0)) + ->method('post') + ->with($this->stringStartsWith('http://localhost/ocs/v1.php/cloud/shares/' . $openShares[0]['remote_id']), $this->anything()); + + // Accept the first share + $this->manager->acceptShare($openShares[0]['id']); + + // Check remaining shares - Accepted + $acceptedShares = \Test_Helper::invokePrivate($this->manager, 'getShares', [true]); + $this->assertCount(1, $acceptedShares); + $shareData1['accepted'] = true; + $this->assertExternalShareEntry($shareData1, $acceptedShares[0], 1, $shareData1['name']); + // Check remaining shares - Open + $openShares = $this->manager->getOpenShares(); + $this->assertCount(1, $openShares); + $this->assertExternalShareEntry($shareData2, $openShares[0], 2, '{{TemporaryMountPointName#' . $shareData2['name'] . '}}-1'); + + \Test_Helper::invokePrivate($this->manager, 'setupMounts'); + $this->assertMount($shareData1['name']); + $this->assertNotMount('{{TemporaryMountPointName#' . $shareData1['name'] . '}}'); + $this->assertNotMount('{{TemporaryMountPointName#' . $shareData1['name'] . '}}-1'); + + // Add another share for "user" with the same name + $this->assertSame(null, call_user_func_array([$this->manager, 'addShare'], $shareData3)); + $openShares = $this->manager->getOpenShares(); + $this->assertCount(2, $openShares); + $this->assertExternalShareEntry($shareData2, $openShares[0], 2, '{{TemporaryMountPointName#' . $shareData2['name'] . '}}-1'); + // New share falls back to the original name (no "-\d", because the name is not taken) + $this->assertExternalShareEntry($shareData3, $openShares[1], 3, '{{TemporaryMountPointName#' . $shareData3['name'] . '}}'); + + \Test_Helper::invokePrivate($this->manager, 'setupMounts'); + $this->assertMount($shareData1['name']); + $this->assertNotMount('{{TemporaryMountPointName#' . $shareData1['name'] . '}}'); + $this->assertNotMount('{{TemporaryMountPointName#' . $shareData1['name'] . '}}-1'); + + $this->httpHelper->expects($this->at(0)) + ->method('post') + ->with($this->stringStartsWith('http://localhost/ocs/v1.php/cloud/shares/' . $openShares[1]['remote_id'] . '/decline'), $this->anything()); + + // Decline the third share + $this->manager->declineShare($openShares[1]['id']); + + \Test_Helper::invokePrivate($this->manager, 'setupMounts'); + $this->assertMount($shareData1['name']); + $this->assertNotMount('{{TemporaryMountPointName#' . $shareData1['name'] . '}}'); + $this->assertNotMount('{{TemporaryMountPointName#' . $shareData1['name'] . '}}-1'); + + // Check remaining shares - Accepted + $acceptedShares = \Test_Helper::invokePrivate($this->manager, 'getShares', [true]); + $this->assertCount(1, $acceptedShares); + $shareData1['accepted'] = true; + $this->assertExternalShareEntry($shareData1, $acceptedShares[0], 1, $shareData1['name']); + // Check remaining shares - Open + $openShares = $this->manager->getOpenShares(); + $this->assertCount(1, $openShares); + $this->assertExternalShareEntry($shareData2, $openShares[0], 2, '{{TemporaryMountPointName#' . $shareData2['name'] . '}}-1'); + + \Test_Helper::invokePrivate($this->manager, 'setupMounts'); + $this->assertMount($shareData1['name']); + $this->assertNotMount('{{TemporaryMountPointName#' . $shareData1['name'] . '}}'); + $this->assertNotMount('{{TemporaryMountPointName#' . $shareData1['name'] . '}}-1'); + + $this->httpHelper->expects($this->at(0)) + ->method('post') + ->with($this->stringStartsWith('http://localhost/ocs/v1.php/cloud/shares/' . $openShares[0]['remote_id'] . '/decline'), $this->anything()); + $this->httpHelper->expects($this->at(1)) + ->method('post') + ->with($this->stringStartsWith('http://localhost/ocs/v1.php/cloud/shares/' . $acceptedShares[0]['remote_id'] . '/decline'), $this->anything()); + + $this->manager->removeUserShares($this->uid); + $this->assertEmpty(\Test_Helper::invokePrivate($this->manager, 'getShares', [null]), 'Asserting all shares for the user have been deleted'); + + $this->mountManager->clear(); + \Test_Helper::invokePrivate($this->manager, 'setupMounts'); + $this->assertNotMount($shareData1['name']); + $this->assertNotMount('{{TemporaryMountPointName#' . $shareData1['name'] . '}}'); + $this->assertNotMount('{{TemporaryMountPointName#' . $shareData1['name'] . '}}-1'); + } + + /** + * @param array $expected + * @param array $actual + * @param int $share + * @param string $mountPoint + */ + protected function assertExternalShareEntry($expected, $actual, $share, $mountPoint) { + $this->assertEquals($expected['remote'], $actual['remote'], 'Asserting remote of a share #' . $share); + $this->assertEquals($expected['token'], $actual['share_token'], 'Asserting token of a share #' . $share); + $this->assertEquals($expected['name'], $actual['name'], 'Asserting name of a share #' . $share); + $this->assertEquals($expected['owner'], $actual['owner'], 'Asserting owner of a share #' . $share); + $this->assertEquals($expected['accepted'], (int) $actual['accepted'], 'Asserting accept of a share #' . $share); + $this->assertEquals($expected['user'], $actual['user'], 'Asserting user of a share #' . $share); + $this->assertEquals($mountPoint, $actual['mountpoint'], 'Asserting mountpoint of a share #' . $share); + } + + private function assertMount($mountPoint) { + $mountPoint = rtrim($mountPoint, '/'); + $mount = $this->mountManager->find($this->getFullPath($mountPoint)); + $this->assertInstanceOf('\OCA\Files_Sharing\External\Mount', $mount); + $this->assertInstanceOf('\OCP\Files\Mount\IMountPoint', $mount); + $this->assertEquals($this->getFullPath($mountPoint), rtrim($mount->getMountPoint(), '/')); + $storage = $mount->getStorage(); + $this->assertInstanceOf('\OCA\Files_Sharing\External\Storage', $storage); + } + + private function assertNotMount($mountPoint) { + $mountPoint = rtrim($mountPoint, '/'); + $mount = $this->mountManager->find($this->getFullPath($mountPoint)); + if ($mount) { + $this->assertInstanceOf('\OCP\Files\Mount\IMountPoint', $mount); + $this->assertNotEquals($this->getFullPath($mountPoint), rtrim($mount->getMountPoint(), '/')); + } else { + $this->assertNull($mount); + } + } + + private function getFullPath($path) { + return '/' . $this->uid . '/files' . $path; + } +} diff --git a/apps/files_sharing/tests/sharedmount.php b/apps/files_sharing/tests/sharedmount.php index 715c22cf4ae..33fee2bd03e 100644 --- a/apps/files_sharing/tests/sharedmount.php +++ b/apps/files_sharing/tests/sharedmount.php @@ -141,14 +141,20 @@ class Test_Files_Sharing_Mount extends OCA\Files_sharing\Tests\TestCase { self::loginHelper(self::TEST_FILES_SHARING_API_USER2); - \OC\Files\Filesystem::rename($this->filename, "newFileName"); + \OC\Files\Filesystem::rename($this->filename, $this->filename . '_renamed'); - $this->assertTrue(\OC\Files\Filesystem::file_exists('newFileName')); + $this->assertTrue(\OC\Files\Filesystem::file_exists($this->filename . '_renamed')); $this->assertFalse(\OC\Files\Filesystem::file_exists($this->filename)); self::loginHelper(self::TEST_FILES_SHARING_API_USER1); $this->assertTrue(\OC\Files\Filesystem::file_exists($this->filename)); - $this->assertFalse(\OC\Files\Filesystem::file_exists("newFileName")); + $this->assertFalse(\OC\Files\Filesystem::file_exists($this->filename . '_renamed')); + + // rename back to original name + self::loginHelper(self::TEST_FILES_SHARING_API_USER2); + \OC\Files\Filesystem::rename($this->filename . '_renamed', $this->filename); + $this->assertFalse(\OC\Files\Filesystem::file_exists($this->filename . '_renamed')); + $this->assertTrue(\OC\Files\Filesystem::file_exists($this->filename)); //cleanup \OCP\Share::unshare('file', $fileinfo['fileid'], \OCP\Share::SHARE_TYPE_USER, self::TEST_FILES_SHARING_API_USER2); diff --git a/apps/files_trashbin/lib/storage.php b/apps/files_trashbin/lib/storage.php index d15b136924c..bb2d18d5318 100644 --- a/apps/files_trashbin/lib/storage.php +++ b/apps/files_trashbin/lib/storage.php @@ -80,11 +80,36 @@ class Storage extends Wrapper { /** * Deletes the given file by moving it into the trashbin. * - * @param string $path + * @param string $path path of file or folder to delete + * + * @return bool true if the operation succeeded, false otherwise */ public function unlink($path) { + return $this->doDelete($path, 'unlink'); + } + + /** + * Deletes the given folder by moving it into the trashbin. + * + * @param string $path path of folder to delete + * + * @return bool true if the operation succeeded, false otherwise + */ + public function rmdir($path) { + return $this->doDelete($path, 'rmdir'); + } + + /** + * Run the delete operation with the given method + * + * @param string $path path of file or folder to delete + * @param string $method either "unlink" or "rmdir" + * + * @return bool true if the operation succeeded, false otherwise + */ + private function doDelete($path, $method) { if (self::$disableTrash) { - return $this->storage->unlink($path); + return call_user_func_array([$this->storage, $method], [$path]); } $normalized = Filesystem::normalizePath($this->mountPoint . '/' . $path); $result = true; @@ -97,14 +122,14 @@ class Storage extends Wrapper { // in cross-storage cases the file will be copied // but not deleted, so we delete it here if ($result) { - $this->storage->unlink($path); + call_user_func_array([$this->storage, $method], [$path]); } } else { - $result = $this->storage->unlink($path); + $result = call_user_func_array([$this->storage, $method], [$path]); } unset($this->deletedFiles[$normalized]); } else if ($this->storage->file_exists($path)) { - $result = $this->storage->unlink($path); + $result = call_user_func_array([$this->storage, $method], [$path]); } return $result; diff --git a/apps/files_trashbin/tests/storage.php b/apps/files_trashbin/tests/storage.php index 24a04e68b2a..72103862ed3 100644 --- a/apps/files_trashbin/tests/storage.php +++ b/apps/files_trashbin/tests/storage.php @@ -54,6 +54,8 @@ class Storage extends \Test\TestCase { $this->userView = new \OC\Files\View('/' . $this->user . '/files/'); $this->userView->file_put_contents('test.txt', 'foo'); + $this->userView->mkdir('folder'); + $this->userView->file_put_contents('folder/inside.txt', 'bar'); } protected function tearDown() { @@ -68,7 +70,7 @@ class Storage extends \Test\TestCase { /** * Test that deleting a file puts it into the trashbin. */ - public function testSingleStorageDelete() { + public function testSingleStorageDeleteFile() { $this->assertTrue($this->userView->file_exists('test.txt')); $this->userView->unlink('test.txt'); list($storage,) = $this->userView->resolvePath('test.txt'); @@ -83,12 +85,34 @@ class Storage extends \Test\TestCase { } /** + * Test that deleting a folder puts it into the trashbin. + */ + public function testSingleStorageDeleteFolder() { + $this->assertTrue($this->userView->file_exists('folder/inside.txt')); + $this->userView->rmdir('folder'); + list($storage,) = $this->userView->resolvePath('folder/inside.txt'); + $storage->getScanner()->scan(''); // make sure we check the storage + $this->assertFalse($this->userView->getFileInfo('folder')); + + // check if folder is in trashbin + $results = $this->rootView->getDirectoryContent($this->user . '/files_trashbin/files/'); + $this->assertEquals(1, count($results)); + $name = $results[0]->getName(); + $this->assertEquals('folder', substr($name, 0, strrpos($name, '.'))); + + $results = $this->rootView->getDirectoryContent($this->user . '/files_trashbin/files/' . $name . '/'); + $this->assertEquals(1, count($results)); + $name = $results[0]->getName(); + $this->assertEquals('inside.txt', $name); + } + + /** * Test that deleting a file from another mounted storage properly * lands in the trashbin. This is a cross-storage situation because * the trashbin folder is in the root storage while the mounted one * isn't. */ - public function testCrossStorageDelete() { + public function testCrossStorageDeleteFile() { $storage2 = new Temporary(array()); \OC\Files\Filesystem::mount($storage2, array(), $this->user . '/files/substorage'); @@ -109,9 +133,41 @@ class Storage extends \Test\TestCase { } /** + * Test that deleting a folder from another mounted storage properly + * lands in the trashbin. This is a cross-storage situation because + * the trashbin folder is in the root storage while the mounted one + * isn't. + */ + public function testCrossStorageDeleteFolder() { + $storage2 = new Temporary(array()); + \OC\Files\Filesystem::mount($storage2, array(), $this->user . '/files/substorage'); + + $this->userView->mkdir('substorage/folder'); + $this->userView->file_put_contents('substorage/folder/subfile.txt', 'bar'); + $storage2->getScanner()->scan(''); + $this->assertTrue($storage2->file_exists('folder/subfile.txt')); + $this->userView->rmdir('substorage/folder'); + + $storage2->getScanner()->scan(''); + $this->assertFalse($this->userView->getFileInfo('substorage/folder')); + $this->assertFalse($storage2->file_exists('folder/subfile.txt')); + + // check if folder is in trashbin + $results = $this->rootView->getDirectoryContent($this->user . '/files_trashbin/files'); + $this->assertEquals(1, count($results)); + $name = $results[0]->getName(); + $this->assertEquals('folder', substr($name, 0, strrpos($name, '.'))); + + $results = $this->rootView->getDirectoryContent($this->user . '/files_trashbin/files/' . $name . '/'); + $this->assertEquals(1, count($results)); + $name = $results[0]->getName(); + $this->assertEquals('subfile.txt', $name); + } + + /** * Test that deleted versions properly land in the trashbin. */ - public function testDeleteVersions() { + public function testDeleteVersionsOfFile() { \OCA\Files_Versions\Hooks::connectHooks(); // trigger a version (multiple would not work because of the expire logic) @@ -130,7 +186,38 @@ class Storage extends \Test\TestCase { $results = $this->rootView->getDirectoryContent($this->user . '/files_trashbin/versions'); $this->assertEquals(1, count($results)); $name = $results[0]->getName(); - $this->assertEquals('test.txt', substr($name, 0, strlen('test.txt'))); + $this->assertEquals('test.txt.v', substr($name, 0, strlen('test.txt.v'))); + } + + /** + * Test that deleted versions properly land in the trashbin. + */ + public function testDeleteVersionsOfFolder() { + \OCA\Files_Versions\Hooks::connectHooks(); + + // trigger a version (multiple would not work because of the expire logic) + $this->userView->file_put_contents('folder/inside.txt', 'v1'); + + $results = $this->rootView->getDirectoryContent($this->user . '/files_versions/folder/'); + $this->assertEquals(1, count($results)); + + $this->userView->rmdir('folder'); + + // rescan trash storage + list($rootStorage,) = $this->rootView->resolvePath($this->user . '/files_trashbin'); + $rootStorage->getScanner()->scan(''); + + // check if versions are in trashbin + $results = $this->rootView->getDirectoryContent($this->user . '/files_trashbin/versions'); + $this->assertEquals(1, count($results)); + $name = $results[0]->getName(); + $this->assertEquals('folder.d', substr($name, 0, strlen('folder.d'))); + + // check if versions are in trashbin + $results = $this->rootView->getDirectoryContent($this->user . '/files_trashbin/versions/' . $name . '/'); + $this->assertEquals(1, count($results)); + $name = $results[0]->getName(); + $this->assertEquals('inside.txt.v', substr($name, 0, strlen('inside.txt.v'))); } /** @@ -138,7 +225,7 @@ class Storage extends \Test\TestCase { * storages. This is because rename() between storages would call * unlink() which should NOT trigger the version deletion logic. */ - public function testKeepFileAndVersionsWhenMovingBetweenStorages() { + public function testKeepFileAndVersionsWhenMovingFileBetweenStorages() { \OCA\Files_Versions\Hooks::connectHooks(); $storage2 = new Temporary(array()); @@ -155,7 +242,7 @@ class Storage extends \Test\TestCase { // move to another storage $this->userView->rename('test.txt', 'substorage/test.txt'); - $this->userView->file_exists('substorage/test.txt'); + $this->assertTrue($this->userView->file_exists('substorage/test.txt')); // rescan trash storage list($rootStorage,) = $this->rootView->resolvePath($this->user . '/files_trashbin'); @@ -175,9 +262,50 @@ class Storage extends \Test\TestCase { } /** + * Test that versions are not auto-trashed when moving a file between + * storages. This is because rename() between storages would call + * unlink() which should NOT trigger the version deletion logic. + */ + public function testKeepFileAndVersionsWhenMovingFolderBetweenStorages() { + \OCA\Files_Versions\Hooks::connectHooks(); + + $storage2 = new Temporary(array()); + \OC\Files\Filesystem::mount($storage2, array(), $this->user . '/files/substorage'); + + // trigger a version (multiple would not work because of the expire logic) + $this->userView->file_put_contents('folder/inside.txt', 'v1'); + + $results = $this->rootView->getDirectoryContent($this->user . '/files_trashbin/files'); + $this->assertEquals(0, count($results)); + + $results = $this->rootView->getDirectoryContent($this->user . '/files_versions/folder/'); + $this->assertEquals(1, count($results)); + + // move to another storage + $this->userView->rename('folder', 'substorage/folder'); + $this->assertTrue($this->userView->file_exists('substorage/folder/inside.txt')); + + // rescan trash storage + list($rootStorage,) = $this->rootView->resolvePath($this->user . '/files_trashbin'); + $rootStorage->getScanner()->scan(''); + + // versions were moved too + $results = $this->rootView->getDirectoryContent($this->user . '/files_versions/substorage/folder/'); + $this->assertEquals(1, count($results)); + + // check that nothing got trashed by the rename's unlink() call + $results = $this->rootView->getDirectoryContent($this->user . '/files_trashbin/files'); + $this->assertEquals(0, count($results)); + + // check that versions were moved and not trashed + $results = $this->rootView->getDirectoryContent($this->user . '/files_trashbin/versions/'); + $this->assertEquals(0, count($results)); + } + + /** * Delete should fail is the source file cant be deleted */ - public function testSingleStorageDeleteFail() { + public function testSingleStorageDeleteFileFail() { /** * @var \OC\Files\Storage\Temporary | \PHPUnit_Framework_MockObject_MockObject $storage */ @@ -187,9 +315,6 @@ class Storage extends \Test\TestCase { ->getMock(); $storage->expects($this->any()) - ->method('rename') - ->will($this->returnValue(false)); - $storage->expects($this->any()) ->method('unlink') ->will($this->returnValue(false)); @@ -206,4 +331,37 @@ class Storage extends \Test\TestCase { $results = $this->rootView->getDirectoryContent($this->user . '/files_trashbin/files/'); $this->assertEquals(0, count($results)); } + + /** + * Delete should fail is the source folder cant be deleted + */ + public function testSingleStorageDeleteFolderFail() { + /** + * @var \OC\Files\Storage\Temporary | \PHPUnit_Framework_MockObject_MockObject $storage + */ + $storage = $this->getMockBuilder('\OC\Files\Storage\Temporary') + ->setConstructorArgs([[]]) + ->setMethods(['rename', 'unlink', 'rmdir']) + ->getMock(); + + $storage->expects($this->any()) + ->method('rmdir') + ->will($this->returnValue(false)); + + $cache = $storage->getCache(); + + Filesystem::mount($storage, [], '/' . $this->user . '/files'); + $this->userView->mkdir('folder'); + $this->userView->file_put_contents('folder/test.txt', 'foo'); + $this->assertTrue($storage->file_exists('folder/test.txt')); + $this->assertFalse($this->userView->rmdir('folder')); + $this->assertTrue($storage->file_exists('folder')); + $this->assertTrue($storage->file_exists('folder/test.txt')); + $this->assertTrue($cache->inCache('folder')); + $this->assertTrue($cache->inCache('folder/test.txt')); + + // file should not be in the trashbin + $results = $this->rootView->getDirectoryContent($this->user . '/files_trashbin/files/'); + $this->assertEquals(0, count($results)); + } } diff --git a/apps/provisioning_api/lib/users.php b/apps/provisioning_api/lib/users.php index 4262dff7a6c..37bfd3b4eda 100644 --- a/apps/provisioning_api/lib/users.php +++ b/apps/provisioning_api/lib/users.php @@ -296,6 +296,10 @@ class Users { if(strtolower($group) == 'admin') { return new OC_OCS_Result(null, 103, 'Cannot create subadmins for admin group'); } + // We cannot be subadmin twice + if (OC_Subadmin::isSubAdminOfGroup($user, $group)) { + return new OC_OCS_Result(null, 100); + } // Go if(OC_Subadmin::createSubAdmin($user, $group)) { return new OC_OCS_Result(null, 100); diff --git a/apps/provisioning_api/tests/userstest.php b/apps/provisioning_api/tests/userstest.php index 917d06a8348..82ff26134dc 100644 --- a/apps/provisioning_api/tests/userstest.php +++ b/apps/provisioning_api/tests/userstest.php @@ -767,4 +767,29 @@ class UsersTest extends TestCase { $this->assertFalse($result->succeeded()); $this->assertEquals(101, $result->getStatusCode()); } + + public function testSubAdminOfGroupAlreadySubAdmin() { + $user1 = $this->generateUsers(); + $user2 = $this->generateUsers(); + \OC_User::setUserId($user1); + \OC_Group::addToGroup($user1, 'admin'); + $group1 = $this->getUniqueID(); + \OC_Group::createGroup($group1); + + //Make user2 subadmin of group1 + $_POST['groupid'] = $group1; + $result = \OCA\provisioning_api\Users::addSubAdmin([ + 'userid' => $user2, + ]); + $this->assertInstanceOf('OC_OCS_Result', $result); + $this->assertTrue($result->succeeded()); + + //Make user2 subadmin of group1 again + $_POST['groupid'] = $group1; + $result = \OCA\provisioning_api\Users::addSubAdmin([ + 'userid' => $user2, + ]); + $this->assertInstanceOf('OC_OCS_Result', $result); + $this->assertTrue($result->succeeded()); + } } diff --git a/apps/user_ldap/lib/connection.php b/apps/user_ldap/lib/connection.php index a9d21ffc8e7..d22adbd563c 100644 --- a/apps/user_ldap/lib/connection.php +++ b/apps/user_ldap/lib/connection.php @@ -23,8 +23,10 @@ namespace OCA\user_ldap\lib; -//magic properties (incomplete) +use OC\ServerNotAvailableException; + /** + * magic properties (incomplete) * responsible for LDAP connections in context with the provided configuration * * @property string ldapUserFilter @@ -46,7 +48,7 @@ class Connection extends LDAPUtility { //cache handler protected $cache; - //settings handler + /** @var Configuration settings handler **/ protected $configuration; protected $doNotValidate = false; @@ -159,7 +161,8 @@ class Connection extends LDAPUtility { $this->establishConnection(); } if(is_null($this->ldapConnectionRes)) { - \OCP\Util::writeLog('user_ldap', 'Connection could not be established', \OCP\Util::ERROR); + \OCP\Util::writeLog('user_ldap', 'No LDAP Connection to server ' . $this->configuration->ldapHost, \OCP\Util::ERROR); + throw new ServerNotAvailableException('Connection to LDAP server could not be established'); } return $this->ldapConnectionRes; } diff --git a/apps/user_ldap/lib/ldap.php b/apps/user_ldap/lib/ldap.php index 4dad34f5b1e..9d5cf4fee0a 100644 --- a/apps/user_ldap/lib/ldap.php +++ b/apps/user_ldap/lib/ldap.php @@ -23,6 +23,8 @@ namespace OCA\user_ldap\lib; +use OC\ServerNotAvailableException; + class LDAP implements ILDAPWrapper { protected $curFunc = ''; protected $curArgs = array(); @@ -280,6 +282,8 @@ class LDAP implements ILDAPWrapper { //for now } else if ($errorCode === 10) { //referrals, we switch them off, but then there is AD :) + } else if ($errorCode === -1) { + throw new ServerNotAvailableException('Lost connection to LDAP server.'); } else { \OCP\Util::writeLog('user_ldap', 'LDAP error '.$errorMsg.' (' . diff --git a/apps/user_ldap/lib/user/manager.php b/apps/user_ldap/lib/user/manager.php index ec50e031281..79b95818276 100644 --- a/apps/user_ldap/lib/user/manager.php +++ b/apps/user_ldap/lib/user/manager.php @@ -150,6 +150,11 @@ class Manager { $this->access->getUserMapper()); } + /** + * @brief returns a User object by it's ownCloud username + * @param string the DN or username of the user + * @return \OCA\user_ldap\lib\user\User|\OCA\user_ldap\lib\user\OfflineUser|null + */ protected function createInstancyByUserName($id) { //most likely a uid. Check whether it is a deleted user if($this->isDeletedUser($id)) { @@ -159,13 +164,14 @@ class Manager { if($dn !== false) { return $this->createAndCache($dn, $id); } - throw new \Exception('Could not create User instance'); + return null; } /** * @brief returns a User object by it's DN or ownCloud username * @param string the DN or username of the user * @return \OCA\user_ldap\lib\user\User|\OCA\user_ldap\lib\user\OfflineUser|null + * @throws \Exception when connection could not be established */ public function get($id) { $this->checkAccess(); @@ -182,12 +188,7 @@ class Manager { } } - try { - $user = $this->createInstancyByUserName($id); - return $user; - } catch (\Exception $e) { - return null; - } + return $this->createInstancyByUserName($id); } } diff --git a/apps/user_ldap/tests/user_ldap.php b/apps/user_ldap/tests/user_ldap.php index 3fa4f2bf0a1..119b0b186d4 100644 --- a/apps/user_ldap/tests/user_ldap.php +++ b/apps/user_ldap/tests/user_ldap.php @@ -411,21 +411,53 @@ class Test_User_Ldap_Direct extends \Test\TestCase { $this->prepareMockForUserExists($access); $access->expects($this->any()) - ->method('readAttribute') - ->will($this->returnCallback(function($dn) { - if($dn === 'dnOfRoland,dc=test') { - return array(); - } - return false; - })); + ->method('readAttribute') + ->will($this->returnCallback(function($dn) { + if($dn === 'dnOfRoland,dc=test') { + return array(); + } + return false; + })); //test for existing user $result = $backend->userExists('gunslinger'); $this->assertTrue($result); + } + + /** + * @expectedException \Exception + */ + public function testUserExistsForDeleted() { + $access = $this->getAccessMock(); + $backend = new UserLDAP($access, $this->getMock('\OCP\IConfig')); + $this->prepareMockForUserExists($access); + + $access->expects($this->any()) + ->method('readAttribute') + ->will($this->returnCallback(function($dn) { + if($dn === 'dnOfRoland,dc=test') { + return array(); + } + return false; + })); //test for deleted user $result = $backend->userExists('formerUser'); - $this->assertFalse($result); + } + + public function testUserExistsForNeverExisting() { + $access = $this->getAccessMock(); + $backend = new UserLDAP($access, $this->getMock('\OCP\IConfig')); + $this->prepareMockForUserExists($access); + + $access->expects($this->any()) + ->method('readAttribute') + ->will($this->returnCallback(function($dn) { + if($dn === 'dnOfRoland,dc=test') { + return array(); + } + return false; + })); //test for never-existing user $result = $backend->userExists('mallory'); @@ -439,21 +471,55 @@ class Test_User_Ldap_Direct extends \Test\TestCase { \OC_User::useBackend($backend); $access->expects($this->any()) - ->method('readAttribute') - ->will($this->returnCallback(function($dn) { - if($dn === 'dnOfRoland,dc=test') { - return array(); - } - return false; - })); + ->method('readAttribute') + ->will($this->returnCallback(function($dn) { + if($dn === 'dnOfRoland,dc=test') { + return array(); + } + return false; + })); //test for existing user $result = \OCP\User::userExists('gunslinger'); $this->assertTrue($result); + } + + /** + * @expectedException \Exception + */ + public function testUserExistsPublicAPIForDeleted() { + $access = $this->getAccessMock(); + $backend = new UserLDAP($access, $this->getMock('\OCP\IConfig')); + $this->prepareMockForUserExists($access); + \OC_User::useBackend($backend); + + $access->expects($this->any()) + ->method('readAttribute') + ->will($this->returnCallback(function($dn) { + if($dn === 'dnOfRoland,dc=test') { + return array(); + } + return false; + })); //test for deleted user $result = \OCP\User::userExists('formerUser'); - $this->assertFalse($result); + } + + public function testUserExistsPublicAPIForNeverExisting() { + $access = $this->getAccessMock(); + $backend = new UserLDAP($access, $this->getMock('\OCP\IConfig')); + $this->prepareMockForUserExists($access); + \OC_User::useBackend($backend); + + $access->expects($this->any()) + ->method('readAttribute') + ->will($this->returnCallback(function($dn) { + if($dn === 'dnOfRoland,dc=test') { + return array(); + } + return false; + })); //test for never-existing user $result = \OCP\User::userExists('mallory'); @@ -469,54 +535,105 @@ class Test_User_Ldap_Direct extends \Test\TestCase { $this->assertFalse($result); } - public function testGetHome() { + public function testGetHomeAbsolutePath() { $access = $this->getAccessMock(); $config = $this->getMock('\OCP\IConfig'); $backend = new UserLDAP($access, $config); $this->prepareMockForUserExists($access); $access->connection->expects($this->any()) - ->method('__get') - ->will($this->returnCallback(function($name) { - if($name === 'homeFolderNamingRule') { - return 'attr:testAttribute'; - } - return null; - })); + ->method('__get') + ->will($this->returnCallback(function($name) { + if($name === 'homeFolderNamingRule') { + return 'attr:testAttribute'; + } + return null; + })); $access->expects($this->any()) - ->method('readAttribute') - ->will($this->returnCallback(function($dn, $attr) { - switch ($dn) { - case 'dnOfRoland,dc=test': - if($attr === 'testAttribute') { - return array('/tmp/rolandshome/'); - } - return array(); - break; - case 'dnOfLadyOfShadows,dc=test': - if($attr === 'testAttribute') { - return array('susannah/'); - } - return array(); - break; - default: - return false; - } - })); - - $datadir = '/my/data/dir'; - $config->expects($this->once()) - ->method('getSystemValue') - ->will($this->returnValue($datadir)); + ->method('readAttribute') + ->will($this->returnCallback(function($dn, $attr) { + switch ($dn) { + case 'dnOfRoland,dc=test': + if($attr === 'testAttribute') { + return array('/tmp/rolandshome/'); + } + return array(); + break; + default: + return false; + } + })); //absolut path $result = $backend->getHome('gunslinger'); $this->assertEquals('/tmp/rolandshome/', $result); + } + + public function testGetHomeRelative() { + $access = $this->getAccessMock(); + $config = $this->getMock('\OCP\IConfig'); + $backend = new UserLDAP($access, $config); + $this->prepareMockForUserExists($access); + $access->connection->expects($this->any()) + ->method('__get') + ->will($this->returnCallback(function($name) { + if($name === 'homeFolderNamingRule') { + return 'attr:testAttribute'; + } + return null; + })); + + $access->expects($this->any()) + ->method('readAttribute') + ->will($this->returnCallback(function($dn, $attr) { + switch ($dn) { + case 'dnOfLadyOfShadows,dc=test': + if($attr === 'testAttribute') { + return array('susannah/'); + } + return array(); + break; + default: + return false; + } + })); //datadir-relativ path + $datadir = '/my/data/dir'; + $config->expects($this->once()) + ->method('getSystemValue') + ->will($this->returnValue($datadir)); + $result = $backend->getHome('ladyofshadows'); $this->assertEquals($datadir.'/susannah/', $result); + } + + /** + * @expectedException \Exception + */ + public function testGetHomeNoPath() { + $access = $this->getAccessMock(); + $backend = new UserLDAP($access, $this->getMock('\OCP\IConfig')); + $this->prepareMockForUserExists($access); + + $access->connection->expects($this->any()) + ->method('__get') + ->will($this->returnCallback(function($name) { + if($name === 'homeFolderNamingRule') { + return 'attr:testAttribute'; + } + return null; + })); + + $access->expects($this->any()) + ->method('readAttribute') + ->will($this->returnCallback(function($dn, $attr) { + switch ($dn) { + default: + return false; + } + })); //no path at all – triggers OC default behaviour $result = $backend->getHome('newyorker'); @@ -556,6 +673,12 @@ class Test_User_Ldap_Direct extends \Test\TestCase { $backend = new UserLDAP($access, $this->getMock('\OCP\IConfig')); $this->prepareMockForUserExists($access); + $access->connection->expects($this->any()) + ->method('getConnectionResource') + ->will($this->returnCallback(function() { + return true; + })); + //with displayName $result = $backend->getDisplayName('gunslinger'); $this->assertEquals('Roland Deschain', $result); @@ -567,9 +690,36 @@ class Test_User_Ldap_Direct extends \Test\TestCase { public function testGetDisplayNamePublicAPI() { $access = $this->getAccessMock(); + $access->expects($this->any()) + ->method('username2dn') + ->will($this->returnCallback(function($uid) { + switch ($uid) { + case 'gunslinger': + return 'dnOfRoland,dc=test'; + break; + case 'formerUser': + return 'dnOfFormerUser,dc=test'; + break; + case 'newyorker': + return 'dnOfNewYorker,dc=test'; + break; + case 'ladyofshadows': + return 'dnOfLadyOfShadows,dc=test'; + break; + default: + return false; + } + })); $this->prepareAccessForGetDisplayName($access); $backend = new UserLDAP($access, $this->getMock('\OCP\IConfig')); $this->prepareMockForUserExists($access); + + $access->connection->expects($this->any()) + ->method('getConnectionResource') + ->will($this->returnCallback(function() { + return true; + })); + \OC_User::useBackend($backend); //with displayName diff --git a/apps/user_ldap/user_ldap.php b/apps/user_ldap/user_ldap.php index 051e760105b..c65c68d609c 100644 --- a/apps/user_ldap/user_ldap.php +++ b/apps/user_ldap/user_ldap.php @@ -188,6 +188,7 @@ class USER_LDAP extends BackendUtility implements \OCP\IUserBackend, \OCP\UserIn * check if a user exists * @param string $uid the username * @return boolean + * @throws \Exception when connection could not be established */ public function userExists($uid) { if($this->access->connection->isCached('userExists'.$uid)) { @@ -206,17 +207,12 @@ class USER_LDAP extends BackendUtility implements \OCP\IUserBackend, \OCP\UserIn return true; } - try { - $result = $this->userExistsOnLDAP($user); - $this->access->connection->writeToCache('userExists'.$uid, $result); - if($result === true) { - $user->update(); - } - return $result; - } catch (\Exception $e) { - \OCP\Util::writeLog('user_ldap', $e->getMessage(), \OCP\Util::WARN); - return false; + $result = $this->userExistsOnLDAP($user); + $this->access->connection->writeToCache('userExists'.$uid, $result); + if($result === true) { + $user->update(); } + return $result; } /** diff --git a/core/ajax/share.php b/core/ajax/share.php index 6d0a6a4e3b9..6792753954b 100644 --- a/core/ajax/share.php +++ b/core/ajax/share.php @@ -105,7 +105,7 @@ if (isset($_POST['action']) && isset($_POST['itemType']) && isset($_POST['itemSo // don't send a mail to the user who shared the file $recipientList = array_diff($recipientList, array(\OCP\User::getUser())); - $mailNotification = new OC\Share\MailNotifications(); + $mailNotification = new OC\Share\MailNotifications(\OCP\User::getUser()); $result = $mailNotification->sendInternalShareMail($recipientList, $itemSource, $itemType); \OCP\Share::setSendMailStatus($itemType, $itemSource, $shareType, $recipient, true); @@ -137,7 +137,7 @@ if (isset($_POST['action']) && isset($_POST['itemType']) && isset($_POST['itemSo $file = $_POST['file']; $to_address = $_POST['toaddress']; - $mailNotification = new \OC\Share\MailNotifications(); + $mailNotification = new \OC\Share\MailNotifications(\OCP\User::getUser()); $expiration = null; if (isset($_POST['expiration']) && $_POST['expiration'] !== '') { diff --git a/core/js/oc-dialogs.js b/core/js/oc-dialogs.js index 0c046d8ef0e..f3bf4361d76 100644 --- a/core/js/oc-dialogs.js +++ b/core/js/oc-dialogs.js @@ -138,7 +138,7 @@ var OCdialogs = { * @param title dialog title * @param callback which will be triggered when user presses Choose * @param multiselect whether it should be possible to select multiple files - * @param mimetypeFilter mimetype to filter by + * @param mimetypeFilter mimetype to filter by - directories will always be included * @param modal make the dialog modal */ filepicker:function(title, callback, multiselect, mimetypeFilter, modal) { diff --git a/core/js/share.js b/core/js/share.js index b3533af4824..128ac729e5d 100644 --- a/core/js/share.js +++ b/core/js/share.js @@ -473,6 +473,10 @@ OC.Share={ } else { response(); } + }).fail(function(){ + $('#dropdown').find('.shareWithLoading').addClass('hidden'); + OC.Notification.show(t('core', 'An error occured. Please try again')); + window.setTimeout(OC.Notification.hide, 5000); }); }, focus: function(event, focused) { diff --git a/core/templates/login.php b/core/templates/login.php index f10a8102180..d139f32732c 100644 --- a/core/templates/login.php +++ b/core/templates/login.php @@ -24,6 +24,12 @@ script('core', [ <?php p($message); ?><br> </div> <?php endforeach; ?> + <?php if (isset($_['internalexception']) && ($_['internalexception'])): ?> + <div class="warning"> + <?php p($l->t('An internal error occured.')); ?><br> + <small><?php p($l->t('Please try again or contact your administrator.')); ?></small> + </div> + <?php endif; ?> <p id="message" class="hidden"> <img class="float-spinner" alt="" src="<?php p(\OCP\Util::imagePath('core', 'loading-dark.gif'));?>" /> @@ -57,6 +57,11 @@ try { exit; } + if (\OC::$server->getSystemConfig()->getValue('singleuser', false)) { + \OCP\Util::writeLog('cron', 'We are in admin only mode, skipping cron', \OCP\Util::DEBUG); + exit; + } + // load all apps to get all api routes properly setup OC_App::loadApps(); @@ -90,6 +95,22 @@ try { } if (OC::$CLI) { + // the cron job must be executed with the right user + if (!OC_Util::runningOnWindows()) { + if (!function_exists('posix_getuid')) { + echo "The posix extensions are required - see http://php.net/manual/en/book.posix.php" . PHP_EOL; + exit(0); + } + $user = posix_getpwuid(posix_getuid()); + $configUser = posix_getpwuid(fileowner(OC::$SERVERROOT . '/config/config.php')); + if ($user['name'] !== $configUser['name']) { + echo "Console has to be executed with the same user as the web server is operated" . PHP_EOL; + echo "Current user: " . $user['name'] . PHP_EOL; + echo "Web server user: " . $configUser['name'] . PHP_EOL; + exit(0); + } + } + // Create lock file first TemporaryCronClass::$lockfile = OC_Config::getValue("datadirectory", OC::$SERVERROOT . '/data') . '/cron.lock'; diff --git a/lib/base.php b/lib/base.php index 86097438d57..a5ee3612ab7 100644 --- a/lib/base.php +++ b/lib/base.php @@ -263,27 +263,37 @@ class OC { header('Retry-After: 120'); // render error page - $tmpl = new OC_Template('', 'update.user', 'guest'); + $template = new OC_Template('', 'update.user', 'guest'); OC_Util::addscript('maintenance-check'); - $tmpl->printPage(); + $template->printPage(); die(); } } - public static function checkSingleUserMode() { + public static function checkSingleUserMode($lockIfNoUserLoggedIn = false) { + if (!\OC::$server->getSystemConfig()->getValue('singleuser', false)) { + return; + } $user = OC_User::getUserSession()->getUser(); - $group = OC_Group::getManager()->get('admin'); - if ($user && \OC::$server->getSystemConfig()->getValue('singleuser', false) && !$group->inGroup($user)) { - // send http status 503 - header('HTTP/1.1 503 Service Temporarily Unavailable'); - header('Status: 503 Service Temporarily Unavailable'); - header('Retry-After: 120'); - - // render error page - $tmpl = new OC_Template('', 'singleuser.user', 'guest'); - $tmpl->printPage(); - die(); + if ($user) { + $group = \OC::$server->getGroupManager()->get('admin'); + if ($group->inGroup($user)) { + return; + } + } else { + if(!$lockIfNoUserLoggedIn) { + return; + } } + // send http status 503 + header('HTTP/1.1 503 Service Temporarily Unavailable'); + header('Status: 503 Service Temporarily Unavailable'); + header('Retry-After: 120'); + + // render error page + $template = new OC_Template('', 'singleuser.user', 'guest'); + $template->printPage(); + die(); } /** @@ -489,12 +499,11 @@ class OC { \OC::$server->getEventLogger()->log('autoloader', 'Autoloader', $loaderStart, $loaderEnd); \OC::$server->getEventLogger()->start('boot', 'Initialize'); - // set some stuff - //ob_start(); + // Don't display errors and log them error_reporting(E_ALL | E_STRICT); - if (defined('DEBUG') && DEBUG) { - ini_set('display_errors', 1); - } + @ini_set('display_errors', 0); + @ini_set('log_errors', 1); + self::$CLI = (php_sapi_name() == 'cli'); date_default_timezone_set('UTC'); @@ -880,6 +889,10 @@ class OC { } } catch (\OC\User\LoginException $e) { $messages[] = $e->getMessage(); + } catch (\Exception $ex) { + \OCP\Util::logException('handleLogin', $ex); + // do not disclose information. show generic error + $error[] = 'internalexception'; } OC_Util::displayLoginPage(array_unique($error), $messages); diff --git a/lib/private/connector/sabre/maintenanceplugin.php b/lib/private/connector/sabre/maintenanceplugin.php index 0208f3fb5a6..86fbd32702a 100644 --- a/lib/private/connector/sabre/maintenanceplugin.php +++ b/lib/private/connector/sabre/maintenanceplugin.php @@ -45,6 +45,9 @@ class OC_Connector_Sabre_MaintenancePlugin extends \Sabre\DAV\ServerPlugin * @return bool */ public function checkMaintenanceMode() { + if (\OC::$server->getSystemConfig()->getValue('singleuser', false)) { + throw new \Sabre\DAV\Exception\ServiceUnavailable(); + } if (OC_Config::getValue('maintenance', false)) { throw new \Sabre\DAV\Exception\ServiceUnavailable(); } diff --git a/lib/private/datetimezone.php b/lib/private/datetimezone.php index 727ce321dba..fcf1883cb4c 100644 --- a/lib/private/datetimezone.php +++ b/lib/private/datetimezone.php @@ -38,13 +38,14 @@ class DateTimeZone implements IDateTimeZone { /** * Get the timezone of the current user, based on his session information and config data * + * @param bool|int $timestamp * @return \DateTimeZone */ - public function getTimeZone() { + public function getTimeZone($timestamp = false) { $timeZone = $this->config->getUserValue($this->session->get('user_id'), 'core', 'timezone', null); if ($timeZone === null) { if ($this->session->exists('timezone')) { - return $this->guessTimeZoneFromOffset($this->session->get('timezone')); + return $this->guessTimeZoneFromOffset($this->session->get('timezone'), $timestamp); } $timeZone = $this->getDefaultTimeZone(); } @@ -64,9 +65,10 @@ class DateTimeZone implements IDateTimeZone { * we try to find it manually, before falling back to UTC. * * @param mixed $offset + * @param bool|int $timestamp * @return \DateTimeZone */ - protected function guessTimeZoneFromOffset($offset) { + protected function guessTimeZoneFromOffset($offset, $timestamp) { try { // Note: the timeZone name is the inverse to the offset, // so a positive offset means negative timeZone @@ -83,7 +85,13 @@ class DateTimeZone implements IDateTimeZone { // we try to guess one timezone that has the same offset foreach (\DateTimeZone::listIdentifiers() as $timeZone) { $dtz = new \DateTimeZone($timeZone); - $dtOffset = $dtz->getOffset(new \DateTime()); + $dateTime = new \DateTime(); + + if ($timestamp !== false) { + $dateTime->setTimestamp($timestamp); + } + + $dtOffset = $dtz->getOffset($dateTime); if ($dtOffset == 3600 * $offset) { return $dtz; } diff --git a/lib/private/db/connection.php b/lib/private/db/connection.php index 0da3c844f03..5f065027920 100644 --- a/lib/private/db/connection.php +++ b/lib/private/db/connection.php @@ -62,6 +62,8 @@ class Connection extends \Doctrine\DBAL\Connection implements IDBConnection { parent::__construct($params, $driver, $config, $eventManager); $this->adapter = new $params['adapter']($this); $this->tablePrefix = $params['tablePrefix']; + + parent::setTransactionIsolation(parent::TRANSACTION_READ_COMMITTED); } /** diff --git a/lib/private/db/migrator.php b/lib/private/db/migrator.php index fcf5aae0258..f55b5078c0e 100644 --- a/lib/private/db/migrator.php +++ b/lib/private/db/migrator.php @@ -100,7 +100,7 @@ class Migrator { * @return string */ protected function generateTemporaryTableName($name) { - return 'oc_' . $name . '_' . $this->random->generate(13, ISecureRandom::CHAR_LOWER . ISecureRandom::CHAR_DIGITS); + return $this->config->getSystemValue('dbtableprefix', 'oc_') . $name . '_' . $this->random->generate(13, ISecureRandom::CHAR_LOWER . ISecureRandom::CHAR_DIGITS); } /** @@ -151,7 +151,7 @@ class Migrator { $indexName = $index->getName(); } else { // avoid conflicts in index names - $indexName = 'oc_' . $this->random->generate(13, ISecureRandom::CHAR_LOWER . ISecureRandom::CHAR_DIGITS); + $indexName = $this->config->getSystemValue('dbtableprefix', 'oc_') . $this->random->generate(13, ISecureRandom::CHAR_LOWER . ISecureRandom::CHAR_DIGITS); } $newIndexes[] = new Index($indexName, $index->getColumns(), $index->isUnique(), $index->isPrimary()); } diff --git a/lib/private/files.php b/lib/private/files.php index 49917a5b8a0..be08fd37d60 100644 --- a/lib/private/files.php +++ b/lib/private/files.php @@ -161,11 +161,12 @@ class OC_Files { * @param false|string $filename */ private static function addSendfileHeader($filename) { - $filename = \OC\Files\Filesystem::getLocalFile($filename); if (isset($_SERVER['MOD_X_SENDFILE_ENABLED'])) { + $filename = \OC\Files\Filesystem::getLocalFile($filename); header("X-Sendfile: " . $filename); } if (isset($_SERVER['MOD_X_SENDFILE2_ENABLED'])) { + $filename = \OC\Files\Filesystem::getLocalFile($filename); if (isset($_SERVER['HTTP_RANGE']) && preg_match("/^bytes=([0-9]+)-([0-9]*)$/", $_SERVER['HTTP_RANGE'], $range)) { $filelength = filesize($filename); @@ -181,6 +182,11 @@ class OC_Files { } if (isset($_SERVER['MOD_X_ACCEL_REDIRECT_ENABLED'])) { + if (isset($_SERVER['MOD_X_ACCEL_REDIRECT_PREFIX'])) { + $filename = $_SERVER['MOD_X_ACCEL_REDIRECT_PREFIX'] . \OC\Files\Filesystem::getLocalFile($filename); + } else { + $filename = \OC::$WEBROOT . '/data' . \OC\Files\Filesystem::getRoot() . $filename; + } header("X-Accel-Redirect: " . $filename); } } diff --git a/lib/private/files/cache/cache.php b/lib/private/files/cache/cache.php index 1dfa9e1dd10..358e654aada 100644 --- a/lib/private/files/cache/cache.php +++ b/lib/private/files/cache/cache.php @@ -261,7 +261,7 @@ class Cache { $this->update($id, $data); return $id; } else { - throw new \RuntimeException('File entry exists when inserting and does not exist on select... go away'); + throw new \RuntimeException('File entry could not be inserted with insertIfNotExist() but could also not be selected with getId() in order to perform an update. Please try again.'); } } } @@ -285,10 +285,17 @@ class Cache { } list($queryParts, $params) = $this->buildParts($data); + // duplicate $params because we need the parts twice in the SQL statement + // once for the SET part, once in the WHERE clause + $params = array_merge($params, $params); $params[] = $id; - $sql = 'UPDATE `*PREFIX*filecache` SET ' . implode(' = ?, ', $queryParts) . '=? WHERE `fileid` = ?'; + // don't update if the data we try to set is the same as the one in the record + // some databases (Postgres) don't like superfluous updates + $sql = 'UPDATE `*PREFIX*filecache` SET ' . implode(' = ?, ', $queryParts) . '=? ' . + 'WHERE (' . implode(' <> ? OR ', $queryParts) . ' <> ? ) AND `fileid` = ? '; \OC_DB::executeAudited($sql, $params); + } /** diff --git a/lib/private/files/cache/storage.php b/lib/private/files/cache/storage.php index 9f2739bbedb..a2f1a9b6b0d 100644 --- a/lib/private/files/cache/storage.php +++ b/lib/private/files/cache/storage.php @@ -44,7 +44,7 @@ class Storage { if ($row = $result->fetchRow()) { $this->numericId = $row['numeric_id']; } else { - throw new \RuntimeException('Storage exists when inserting and does not exist on select... go away'); + throw new \RuntimeException('Storage could neither be inserted nor be selected from the database'); } } } diff --git a/lib/private/files/filesystem.php b/lib/private/files/filesystem.php index e933782ce2f..707440632f2 100644 --- a/lib/private/files/filesystem.php +++ b/lib/private/files/filesystem.php @@ -342,42 +342,42 @@ class Filesystem { $userObject = \OC_User::getManager()->get($user); - if (!is_null($userObject)) { - $homeStorage = \OC_Config::getValue( 'objectstore' ); - if (!empty($homeStorage)) { - // sanity checks - if (empty($homeStorage['class'])) { - \OCP\Util::writeLog('files', 'No class given for objectstore', \OCP\Util::ERROR); - } - if (!isset($homeStorage['arguments'])) { - $homeStorage['arguments'] = array(); - } - // instantiate object store implementation - $homeStorage['arguments']['objectstore'] = new $homeStorage['class']($homeStorage['arguments']); - // mount with home object store implementation - $homeStorage['class'] = '\OC\Files\ObjectStore\HomeObjectStoreStorage'; - } else { - $homeStorage = array( - //default home storage configuration: - 'class' => '\OC\Files\Storage\Home', - 'arguments' => array() - ); - } - $homeStorage['arguments']['user'] = $userObject; + if (is_null($userObject)) { + \OCP\Util::writeLog('files', ' Backends provided no user object for '.$user, \OCP\Util::ERROR); + throw new \OC\User\NoUserException(); + } - // check for legacy home id (<= 5.0.12) - if (\OC\Files\Cache\Storage::exists('local::' . $root . '/')) { - $homeStorage['arguments']['legacy'] = true; + $homeStorage = \OC_Config::getValue( 'objectstore' ); + if (!empty($homeStorage)) { + // sanity checks + if (empty($homeStorage['class'])) { + \OCP\Util::writeLog('files', 'No class given for objectstore', \OCP\Util::ERROR); } - - self::mount($homeStorage['class'], $homeStorage['arguments'], $user); - - $home = \OC\Files\Filesystem::getStorage($user); + if (!isset($homeStorage['arguments'])) { + $homeStorage['arguments'] = array(); + } + // instantiate object store implementation + $homeStorage['arguments']['objectstore'] = new $homeStorage['class']($homeStorage['arguments']); + // mount with home object store implementation + $homeStorage['class'] = '\OC\Files\ObjectStore\HomeObjectStoreStorage'; + } else { + $homeStorage = array( + //default home storage configuration: + 'class' => '\OC\Files\Storage\Home', + 'arguments' => array() + ); } - else { - self::mount('\OC\Files\Storage\Local', array('datadir' => $root), $user); + $homeStorage['arguments']['user'] = $userObject; + + // check for legacy home id (<= 5.0.12) + if (\OC\Files\Cache\Storage::exists('local::' . $root . '/')) { + $homeStorage['arguments']['legacy'] = true; } + self::mount($homeStorage['class'], $homeStorage['arguments'], $user); + + $home = \OC\Files\Filesystem::getStorage($user); + self::mountCacheDir($user); // Chance to mount for other storages diff --git a/lib/private/files/mount/mountpoint.php b/lib/private/files/mount/mountpoint.php index 85edb7cb570..b62dc478015 100644 --- a/lib/private/files/mount/mountpoint.php +++ b/lib/private/files/mount/mountpoint.php @@ -95,10 +95,12 @@ class MountPoint implements IMountPoint { } /** + * Sets the mount point path, relative to data/ + * * @param string $mountPoint new mount point */ public function setMountPoint($mountPoint) { - $this->mountPoint = $mountPoint; + $this->mountPoint = $this->formatPath($mountPoint); } /** diff --git a/lib/private/files/storage/dav.php b/lib/private/files/storage/dav.php index 4f7b3ff8940..9734d36f37d 100644 --- a/lib/private/files/storage/dav.php +++ b/lib/private/files/storage/dav.php @@ -126,14 +126,10 @@ class DAV extends \OC\Files\Storage\Common { return opendir('fakedir://' . $id); } catch (Exception\NotFound $e) { return false; - } catch (\Sabre\DAV\Exception $e) { - $this->convertSabreException($e); - return false; } catch (\Exception $e) { - // TODO: log for now, but in the future need to wrap/rethrow exception - \OCP\Util::writeLog('files_external', $e->getMessage(), \OCP\Util::ERROR); - return false; + $this->convertException($e); } + return false; } public function filetype($path) { @@ -148,14 +144,10 @@ class DAV extends \OC\Files\Storage\Common { return (count($responseType) > 0 and $responseType[0] == "{DAV:}collection") ? 'dir' : 'file'; } catch (Exception\NotFound $e) { return false; - } catch (\Sabre\DAV\Exception $e) { - $this->convertSabreException($e); - return false; } catch (\Exception $e) { - // TODO: log for now, but in the future need to wrap/rethrow exception - \OCP\Util::writeLog('files_external', $e->getMessage(), \OCP\Util::ERROR); - return false; + $this->convertException($e); } + return false; } public function file_exists($path) { @@ -166,14 +158,10 @@ class DAV extends \OC\Files\Storage\Common { return true; //no 404 exception } catch (Exception\NotFound $e) { return false; - } catch (\Sabre\DAV\Exception $e) { - $this->convertSabreException($e); - return false; } catch (\Exception $e) { - // TODO: log for now, but in the future need to wrap/rethrow exception - \OCP\Util::writeLog('files_external', $e->getMessage(), \OCP\Util::ERROR); - return false; + $this->convertException($e); } + return false; } public function unlink($path) { @@ -197,8 +185,12 @@ class DAV extends \OC\Files\Storage\Common { curl_setopt($curl, CURLOPT_URL, $this->createBaseUri() . $this->encodePath($path)); curl_setopt($curl, CURLOPT_FILE, $fp); curl_setopt($curl, CURLOPT_FOLLOWLOCATION, true); - curl_setopt($curl, CURLOPT_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS); - curl_setopt($curl, CURLOPT_REDIR_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS); + if(defined('CURLOPT_PROTOCOLS')) { + curl_setopt($curl, CURLOPT_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS); + } + if(defined('CURLOPT_REDIR_PROTOCOLS')) { + curl_setopt($curl, CURLOPT_REDIR_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS); + } if ($this->secure === true) { curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, true); curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, 2); @@ -285,12 +277,8 @@ class DAV extends \OC\Files\Storage\Common { $this->client->proppatch($this->encodePath($path), array('{DAV:}lastmodified' => $mtime)); } catch (Exception\NotImplemented $e) { return false; - } catch (\Sabre\DAV\Exception $e) { - $this->convertSabreException($e); - return false; } catch (\Exception $e) { - // TODO: log for now, but in the future need to wrap/rethrow exception - \OCP\Util::writeLog('files_external', $e->getMessage(), \OCP\Util::ERROR); + $this->convertException($e); return false; } } else { @@ -311,8 +299,12 @@ class DAV extends \OC\Files\Storage\Common { curl_setopt($curl, CURLOPT_INFILESIZE, filesize($path)); curl_setopt($curl, CURLOPT_PUT, true); curl_setopt($curl, CURLOPT_RETURNTRANSFER, true); - curl_setopt($curl, CURLOPT_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS); - curl_setopt($curl, CURLOPT_REDIR_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS); + if(defined('CURLOPT_PROTOCOLS')) { + curl_setopt($curl, CURLOPT_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS); + } + if(defined('CURLOPT_REDIR_PROTOCOLS')) { + curl_setopt($curl, CURLOPT_REDIR_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS); + } if ($this->secure === true) { curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, true); curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, 2); @@ -339,14 +331,10 @@ class DAV extends \OC\Files\Storage\Common { $this->removeCachedFile($path1); $this->removeCachedFile($path2); return true; - } catch (\Sabre\DAV\Exception $e) { - $this->convertSabreException($e); - return false; } catch (\Exception $e) { - // TODO: log for now, but in the future need to wrap/rethrow exception - \OCP\Util::writeLog('files_external', $e->getMessage(), \OCP\Util::ERROR); - return false; + $this->convertException($e); } + return false; } public function copy($path1, $path2) { @@ -357,14 +345,10 @@ class DAV extends \OC\Files\Storage\Common { $this->client->request('COPY', $path1, null, array('Destination' => $path2)); $this->removeCachedFile($path2); return true; - } catch (\Sabre\DAV\Exception $e) { - $this->convertSabreException($e); - return false; } catch (\Exception $e) { - // TODO: log for now, but in the future need to wrap/rethrow exception - \OCP\Util::writeLog('files_external', $e->getMessage(), \OCP\Util::ERROR); - return false; + $this->convertException($e); } + return false; } public function stat($path) { @@ -378,14 +362,10 @@ class DAV extends \OC\Files\Storage\Common { ); } catch (Exception\NotFound $e) { return array(); - } catch (\Sabre\DAV\Exception $e) { - $this->convertSabreException($e); - return false; } catch (\Exception $e) { - // TODO: log for now, but in the future need to wrap/rethrow exception - \OCP\Util::writeLog('files_external', $e->getMessage(), \OCP\Util::ERROR); - return array(); + $this->convertException($e); } + return array(); } public function getMimeType($path) { @@ -407,14 +387,10 @@ class DAV extends \OC\Files\Storage\Common { } } catch (Exception\NotFound $e) { return false; - } catch (\Sabre\DAV\Exception $e) { - $this->convertSabreException($e); - return false; } catch (\Exception $e) { - // TODO: log for now, but in the future need to wrap/rethrow exception - \OCP\Util::writeLog('files_external', $e->getMessage(), \OCP\Util::ERROR); - return false; + $this->convertException($e); } + return false; } /** @@ -455,16 +431,11 @@ class DAV extends \OC\Files\Storage\Common { return false; } - $this->convertSabreException($e); - return false; - } catch (\Sabre\DAV\Exception $e) { - $this->convertSabreException($e); - return false; + $this->convertException($e); } catch (\Exception $e) { - // TODO: log for now, but in the future need to wrap/rethrow exception - \OCP\Util::writeLog('files_external', $e->getMessage(), \OCP\Util::ERROR); - return false; + $this->convertException($e); } + return false; } /** @@ -565,23 +536,26 @@ class DAV extends \OC\Files\Storage\Common { } } catch (Exception\NotFound $e) { return false; - } catch (Exception $e) { - $this->convertSabreException($e); + } catch (\Exception $e) { + $this->convertException($e); return false; } } /** - * Convert sabre DAV exception to a storage exception, - * then throw it + * Interpret the given exception and decide whether it is due to an + * unavailable storage, invalid storage or other. + * This will either throw StorageInvalidException, StorageNotAvailableException + * or do nothing. * * @param \Sabre\Dav\Exception $e sabre exception + * * @throws StorageInvalidException if the storage is invalid, for example * when the authentication expired or is invalid * @throws StorageNotAvailableException if the storage is not available, * which might be temporary */ - private function convertSabreException(\Sabre\Dav\Exception $e) { + private function convertException(\Exception $e) { \OCP\Util::writeLog('files_external', $e->getMessage(), \OCP\Util::ERROR); if ($e instanceof \Sabre\DAV\Exception\NotAuthenticated) { // either password was changed or was invalid all along @@ -589,9 +563,18 @@ class DAV extends \OC\Files\Storage\Common { } else if ($e instanceof \Sabre\DAV\Exception\MethodNotAllowed) { // ignore exception, false will be returned return; + } else if ($e instanceof \Sabre\Dav\Exception) { + throw new StorageNotAvailableException(get_class($e).': '.$e->getMessage()); + } else if ($e instanceof \InvalidArgumentException) { + // parse error because the server returned HTML instead of XML, + // possibly temporarily down + throw new StorageNotAvailableException(get_class($e).': '.$e->getMessage()); + } else if (($e instanceof StorageNotAvailableException) || ($e instanceof StorageInvalidException)) { + // rethrow + throw $e; } - throw new StorageNotAvailableException(get_class($e).': '.$e->getMessage()); + // TODO: only log for now, but in the future need to wrap/rethrow exception } } diff --git a/lib/private/files/view.php b/lib/private/files/view.php index b5ad425a0fa..b58a8c2d639 100644 --- a/lib/private/files/view.php +++ b/lib/private/files/view.php @@ -639,7 +639,9 @@ class View { if (is_resource($dh)) { while (($file = readdir($dh)) !== false) { if (!Filesystem::isIgnoredDir($file)) { - $result = $this->copy($path1 . '/' . $file, $path2 . '/' . $file, $preserveMtime); + if (!$this->copy($path1 . '/' . $file, $path2 . '/' . $file, $preserveMtime)) { + $result = false; + } } } } diff --git a/lib/private/hook.php b/lib/private/hook.php index c9ca58f779e..d4e82057d96 100644 --- a/lib/private/hook.php +++ b/lib/private/hook.php @@ -80,6 +80,9 @@ class OC_Hook{ OC_Log::write('hook', 'error while running hook (' . $i["class"] . '::' . $i["name"] . '): '.$e->getMessage(), OC_Log::ERROR); + if($e instanceof \OC\ServerNotAvailableException) { + throw $e; + } } } diff --git a/lib/private/httphelper.php b/lib/private/httphelper.php index 08c35e4ae08..88e9ac76431 100644 --- a/lib/private/httphelper.php +++ b/lib/private/httphelper.php @@ -68,9 +68,12 @@ class HTTPHelper { curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); curl_setopt($curl, CURLOPT_CONNECTTIMEOUT, 10); curl_setopt($curl, CURLOPT_URL, $url); - curl_setopt($curl, CURLOPT_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS); - curl_setopt($curl, CURLOPT_REDIR_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS); - + if(defined('CURLOPT_PROTOCOLS')) { + curl_setopt($curl, CURLOPT_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS); + } + if(defined('CURLOPT_REDIR_PROTOCOLS')) { + curl_setopt($curl, CURLOPT_REDIR_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS); + } curl_setopt($curl, CURLOPT_USERAGENT, self::USER_AGENT); if ($proxy !== null) { curl_setopt($curl, CURLOPT_PROXY, $proxy); diff --git a/lib/private/mail.php b/lib/private/mail.php index 6b7eec6e080..1a2025de0b2 100644 --- a/lib/private/mail.php +++ b/lib/private/mail.php @@ -28,10 +28,11 @@ class OC_Mail { * @param string $ccaddress * @param string $ccname * @param string $bcc + * @param string $replyTo * @throws Exception */ public static function send($toaddress, $toname, $subject, $mailtext, $fromaddress, $fromname, - $html=0, $altbody='', $ccaddress='', $ccname='', $bcc='') { + $html=0, $altbody='', $ccaddress='', $ccname='', $bcc='', $replyTo='') { $SMTPMODE = OC_Config::getValue( 'mail_smtpmode', 'sendmail' ); $SMTPHOST = OC_Config::getValue( 'mail_smtphost', '127.0.0.1' ); @@ -79,7 +80,9 @@ class OC_Mail { if($ccaddress != '') $mailo->AddCC($ccaddress, $ccname); if($bcc != '') $mailo->AddBCC($bcc); - $mailo->AddReplyTo($fromaddress, $fromname); + if($replyTo !== '') { + $mailo->addReplyTo($replyTo); + } $mailo->WordWrap = 78; $mailo->IsHTML($html == 1); diff --git a/lib/private/servernotavailableexception.php b/lib/private/servernotavailableexception.php new file mode 100644 index 00000000000..5a57917d23a --- /dev/null +++ b/lib/private/servernotavailableexception.php @@ -0,0 +1,27 @@ +<?php +/** + * @author Morris Jobke <hey@morrisjobke.de> + * + * @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 OC; + + +class ServerNotAvailableException extends \Exception { + +} diff --git a/lib/private/share/mailnotifications.php b/lib/private/share/mailnotifications.php index 342d3d5057a..09d21968dfe 100644 --- a/lib/private/share/mailnotifications.php +++ b/lib/private/share/mailnotifications.php @@ -37,6 +37,9 @@ class MailNotifications { */ private $from; + /** @var string Mail address used for reply to */ + private $replyTo; + /** * @var string */ @@ -49,20 +52,16 @@ class MailNotifications { /** * - * @param string $sender user id (if nothing is set we use the currently logged-in user) + * @param string $sender user id */ - public function __construct($sender = null) { + public function __construct($sender) { $this->l = \OC::$server->getL10N('lib'); $this->senderId = $sender; $this->from = \OCP\Util::getDefaultEmailAddress('sharing-noreply'); - if ($this->senderId) { - $this->from = \OCP\Config::getUserValue($this->senderId, 'settings', 'email', $this->from); - $this->senderDisplayName = \OCP\User::getDisplayName($this->senderId); - } else { - $this->senderDisplayName = \OCP\User::getDisplayName(); - } + $this->replyTo = \OCP\Config::getUserValue($this->senderId, 'settings', 'email', $this->from); + $this->senderDisplayName = \OCP\User::getDisplayName($this->senderId); } /** @@ -105,6 +104,11 @@ class MailNotifications { $args = array( 'dir' => $filename, ); + } else if (strpos($filename, '/')) { + $args = array( + 'dir' => '/' . dirname($filename), + 'scrollto' => basename($filename), + ); } else { $args = array( 'dir' => '/', @@ -118,7 +122,7 @@ class MailNotifications { // send it out now try { - \OCP\Util::sendMail($to, $recipientDisplayName, $subject, $htmlMail, $this->from, $this->senderDisplayName, 1, $alttextMail); + \OC_MAIL::send($to, $recipientDisplayName, $subject, $htmlMail, $this->from, $this->senderDisplayName, 1, $alttextMail, '', '', '', $this->replyTo); } catch (\Exception $e) { \OCP\Util::writeLog('sharing', "Can't send mail to inform the user about an internal share: " . $e->getMessage() , \OCP\Util::ERROR); $noMail[] = $recipientDisplayName; @@ -145,7 +149,7 @@ class MailNotifications { $failed = array(); foreach ($rs as $r) { try { - \OCP\Util::sendMail($r, $r, $subject, $htmlMail, $this->from, $this->senderDisplayName, 1, $alttextMail); + \OC_MAIL::send($r, $r, $subject, $htmlMail, $this->from, $this->senderDisplayName, 1, $alttextMail, '', '', '', $this->replyTo); } catch (\Exception $e) { \OCP\Util::writeLog('sharing', "Can't send mail with public link to $r: " . $e->getMessage(), \OCP\Util::ERROR); $failed[] = $r; diff --git a/lib/private/share/share.php b/lib/private/share/share.php index 0069d70190a..e52704be3d8 100644 --- a/lib/private/share/share.php +++ b/lib/private/share/share.php @@ -312,18 +312,20 @@ class Share extends \OC\Share\Constants { */ public static function getItemSharedWithUser($itemType, $itemSource, $user, $owner = null, $shareType = null) { $shares = array(); - $fileDependend = false; + $fileDependent = false; if ($itemType === 'file' || $itemType === 'folder') { - $fileDependend = true; + $fileDependent = true; $column = 'file_source'; - $where = 'INNER JOIN `*PREFIX*filecache` ON `file_source` = `*PREFIX*filecache`.`fileid` WHERE'; + $where = 'INNER JOIN `*PREFIX*filecache` ON `file_source` = `*PREFIX*filecache`.`fileid` '; + $where .= 'INNER JOIN `*PREFIX*storages` ON `numeric_id` = `*PREFIX*filecache`.`storage` '; + $where .= ' WHERE'; } else { $column = 'item_source'; $where = 'WHERE'; } - $select = self::createSelectStatement(self::FORMAT_NONE, $fileDependend); + $select = self::createSelectStatement(self::FORMAT_NONE, $fileDependent); $where .= ' `' . $column . '` = ? AND `item_type` = ? '; $arguments = array($itemSource, $itemType); @@ -348,6 +350,9 @@ class Share extends \OC\Share\Constants { $result = \OC_DB::executeAudited($query, $arguments); while ($row = $result->fetchRow()) { + if ($fileDependent && !self::isFileReachable($row['path'], $row['storage_id'])) { + continue; + } $shares[] = $row; } @@ -1360,10 +1365,11 @@ class Share extends \OC\Share\Constants { } else { $root = ''; } - $where = 'INNER JOIN `*PREFIX*filecache` ON `file_source` = `*PREFIX*filecache`.`fileid`'; + $where = 'INNER JOIN `*PREFIX*filecache` ON `file_source` = `*PREFIX*filecache`.`fileid` '; if (!isset($item)) { - $where .= ' WHERE `file_target` IS NOT NULL'; + $where .= ' AND `file_target` IS NOT NULL '; } + $where .= 'INNER JOIN `*PREFIX*storages` ON `numeric_id` = `*PREFIX*filecache`.`storage` '; $fileDependent = true; $queryArgs = array(); } else { @@ -1504,6 +1510,9 @@ class Share extends \OC\Share\Constants { while ($row = $result->fetchRow()) { self::transformDBResults($row); // Filter out duplicate group shares for users with unique targets + if ($fileDependent && !self::isFileReachable($row['path'], $row['storage_id'])) { + continue; + } if ($row['share_type'] == self::$shareTypeGroupUserUnique && isset($items[$row['parent']])) { $row['share_type'] = self::SHARE_TYPE_GROUP; $row['unique_name'] = true; // remember that we use a unique name for this user @@ -2160,7 +2169,9 @@ class Share extends \OC\Share\Constants { $select = '*'; if ($format == self::FORMAT_STATUSES) { if ($fileDependent) { - $select = '`*PREFIX*share`.`id`, `*PREFIX*share`.`parent`, `share_type`, `path`, `storage`, `share_with`, `uid_owner` , `file_source`, `stime`, `*PREFIX*share`.`permissions`'; + $select = '`*PREFIX*share`.`id`, `*PREFIX*share`.`parent`, `share_type`, `path`, `storage`, ' + . '`share_with`, `uid_owner` , `file_source`, `stime`, `*PREFIX*share`.`permissions`, ' + . '`*PREFIX*storages`.`id` AS `storage_id`'; } else { $select = '`id`, `parent`, `share_type`, `share_with`, `uid_owner`, `item_source`, `stime`, `*PREFIX*share`.`permissions`'; } @@ -2169,7 +2180,8 @@ class Share extends \OC\Share\Constants { if ($fileDependent) { $select = '`*PREFIX*share`.`id`, `item_type`, `item_source`, `*PREFIX*share`.`parent`,' . ' `share_type`, `share_with`, `file_source`, `file_target`, `path`, `*PREFIX*share`.`permissions`, `stime`,' - . ' `expiration`, `token`, `storage`, `mail_send`, `uid_owner`'; + . ' `expiration`, `token`, `storage`, `mail_send`, `uid_owner`, ' + . '`*PREFIX*storages`.`id` AS `storage_id`'; } else { $select = '`id`, `item_type`, `item_source`, `parent`, `share_type`, `share_with`, `*PREFIX*share`.`permissions`,' . ' `stime`, `file_source`, `expiration`, `token`, `mail_send`, `uid_owner`'; @@ -2182,9 +2194,11 @@ class Share extends \OC\Share\Constants { . '`*PREFIX*share`.`permissions`, `expiration`, `storage`, `*PREFIX*filecache`.`parent` as `file_parent`, ' . '`name`, `mtime`, `mimetype`, `mimepart`, `size`, `unencrypted_size`, `encrypted`, `etag`, `mail_send`'; } else { - $select = '`*PREFIX*share`.`id`, `item_type`, `item_source`, `item_target`, - `*PREFIX*share`.`parent`, `share_type`, `share_with`, `uid_owner`, - `file_source`, `path`, `file_target`, `*PREFIX*share`.`permissions`, `stime`, `expiration`, `token`, `storage`, `mail_send`'; + $select = '`*PREFIX*share`.`id`, `item_type`, `item_source`, `item_target`,' + . '`*PREFIX*share`.`parent`, `share_type`, `share_with`, `uid_owner`,' + . '`file_source`, `path`, `file_target`, `*PREFIX*share`.`permissions`,' + . '`stime`, `expiration`, `token`, `storage`, `mail_send`,' + . '`*PREFIX*storages`.`id` AS `storage_id`'; } } } @@ -2275,15 +2289,17 @@ class Share extends \OC\Share\Constants { * * @param string $url * @param array $fields post parameters - * @return bool + * @return array */ private static function tryHttpPost($url, $fields) { $protocol = 'https://'; - $success = false; + $result = [ + 'success' => false, + 'result' => '', + ]; $try = 0; - while ($success === false && $try < 2) { + while ($result['success'] === false && $try < 2) { $result = \OC::$server->getHTTPHelper()->post($protocol . $url, $fields); - $success = $result['success']; $try++; $protocol = 'http://'; } @@ -2306,7 +2322,7 @@ class Share extends \OC\Share\Constants { list($user, $remote) = explode('@', $shareWith, 2); if ($user && $remote) { - $url = $remote . self::BASE_PATH_TO_SHARE_API . '?format=' . self::RESPONSE_FORMAT; + $url = rtrim($remote, '/') . self::BASE_PATH_TO_SHARE_API . '?format=' . self::RESPONSE_FORMAT; $local = \OC::$server->getURLGenerator()->getAbsoluteURL('/'); @@ -2339,8 +2355,9 @@ class Share extends \OC\Share\Constants { * @return bool */ private static function sendRemoteUnshare($remote, $id, $token) { - $url = $remote . self::BASE_PATH_TO_SHARE_API . '/' . $id . '/unshare?format=' . self::RESPONSE_FORMAT; + $url = rtrim($remote, '/') . self::BASE_PATH_TO_SHARE_API . '/' . $id . '/unshare?format=' . self::RESPONSE_FORMAT; $fields = array('token' => $token, 'format' => 'json'); + $url = self::removeProtocolFromUrl($url); $result = self::tryHttpPost($url, $fields); $status = json_decode($result['result'], true); @@ -2370,4 +2387,27 @@ class Share extends \OC\Share\Constants { return (int)\OCP\Config::getAppValue('core', 'shareapi_expire_after_n_days', '7'); } + /** + * Checks whether the given path is reachable for the given owner + * + * @param string $path path relative to files + * @param string $ownerStorageId storage id of the owner + * + * @return boolean true if file is reachable, false otherwise + */ + private static function isFileReachable($path, $ownerStorageId) { + // if outside the home storage, file is always considered reachable + if (!(substr($ownerStorageId, 0, 6) === 'home::')) { + return true; + } + + // if inside the home storage, the file has to be under "/files/" + $path = ltrim($path, '/'); + if (substr($path, 0, 6) === 'files/') { + return true; + } + + return false; + } + } diff --git a/lib/private/user/http.php b/lib/private/user/http.php index 8375c4e1e22..10c95ed43b9 100644 --- a/lib/private/user/http.php +++ b/lib/private/user/http.php @@ -72,8 +72,12 @@ class OC_User_HTTP extends OC_User_Backend implements \OCP\IUserBackend { curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_USERPWD, $user.':'.$password); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); - curl_setopt($ch, CURLOPT_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS); - curl_setopt($ch, CURLOPT_REDIR_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS); + if(defined('CURLOPT_PROTOCOLS')) { + curl_setopt($curl, CURLOPT_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS); + } + if(defined('CURLOPT_REDIR_PROTOCOLS')) { + curl_setopt($curl, CURLOPT_REDIR_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS); + } curl_exec($ch); diff --git a/lib/private/user/nouserexception.php b/lib/private/user/nouserexception.php new file mode 100644 index 00000000000..9452362b4e6 --- /dev/null +++ b/lib/private/user/nouserexception.php @@ -0,0 +1,14 @@ +<?php +/** + * ownCloud + * + * This file is licensed under the Affero General Public License version 3 or + * later. See the COPYING-AGPL file. + * + * @author Jörn Friedrich Dreyer <jfd@owncloud.com> + * @copyright Jörn Friedrich Dreyer 2015 + */ + +namespace OC\User; + +class NoUserException extends \Exception {}
\ No newline at end of file diff --git a/lib/private/util.php b/lib/private/util.php index 9454d9f9226..6b5905f7b58 100644 --- a/lib/private/util.php +++ b/lib/private/util.php @@ -628,25 +628,6 @@ class OC_Util { $webServerRestart = true; } - /** - * PHP 5.6 ships with a PHP setting which throws notices by default for a - * lot of endpoints. Thus we need to ensure that the value is set to -1 - * - * FIXME: Due to https://github.com/owncloud/core/pull/13593#issuecomment-71178078 - * this check is disabled for HHVM at the moment. This should get re-evaluated - * at a later point. - * - * @link https://github.com/owncloud/core/issues/13592 - */ - if(version_compare(phpversion(), '5.6.0', '>=') && - !self::runningOnHhvm() && - \OC::$server->getIniWrapper()->getNumeric('always_populate_raw_post_data') !== -1) { - $errors[] = array( - 'error' => $l->t('PHP is configured to populate raw post data. Since PHP 5.6 this will lead to PHP throwing notices for perfectly valid code.'), - 'hint' => $l->t('To fix this issue set <code>always_populate_raw_post_data</code> to <code>-1</code> in your php.ini') - ); - } - if (!self::isAnnotationsWorking()) { $errors[] = array( 'error' => $l->t('PHP is apparently setup to strip inline doc blocks. This will make several core apps inaccessible.'), diff --git a/lib/public/appframework/controller.php b/lib/public/appframework/controller.php index 00981df05ba..19b1294c0bb 100644 --- a/lib/public/appframework/controller.php +++ b/lib/public/appframework/controller.php @@ -28,6 +28,7 @@ namespace OCP\AppFramework; use OCP\AppFramework\Http\TemplateResponse; +use OCP\AppFramework\Http\Response; use OCP\AppFramework\Http\JSONResponse; use OCP\AppFramework\Http\DataResponse; use OCP\IRequest; diff --git a/lib/public/idatetimezone.php b/lib/public/idatetimezone.php index fb4c89538d5..24dc70a82fe 100644 --- a/lib/public/idatetimezone.php +++ b/lib/public/idatetimezone.php @@ -15,7 +15,8 @@ namespace OCP; interface IDateTimeZone { /** + * @param bool|int $timestamp * @return \DateTimeZone */ - public function getTimeZone(); + public function getTimeZone($timestamp = false); } diff --git a/ocs/v1.php b/ocs/v1.php index 0a86fb06411..5b73809e6f8 100644 --- a/ocs/v1.php +++ b/ocs/v1.php @@ -23,7 +23,9 @@ require_once '../lib/base.php'; -if (\OCP\Util::needUpgrade()) { +if (\OCP\Util::needUpgrade() + || \OC::$server->getSystemConfig()->getValue('maintenance', false) + || \OC::$server->getSystemConfig()->getValue('singleuser', false)) { // since the behavior of apps or remotes are unpredictable during // an upgrade, return a 503 directly OC_Response::setStatus(OC_Response::STATUS_SERVICE_UNAVAILABLE); diff --git a/public.php b/public.php index c5c227ef460..8a43b3e466a 100644 --- a/public.php +++ b/public.php @@ -12,7 +12,7 @@ try { } OC::checkMaintenanceMode(); - OC::checkSingleUserMode(); + OC::checkSingleUserMode(true); $pathInfo = OC_Request::getPathInfo(); if (!$pathInfo && !isset($_GET['service'])) { header('HTTP/1.0 404 Not Found'); diff --git a/tests/lib/db/migrator.php b/tests/lib/db/migrator.php index 54267740480..6bde68c2d20 100644 --- a/tests/lib/db/migrator.php +++ b/tests/lib/db/migrator.php @@ -26,11 +26,17 @@ class Migrator extends \Test\TestCase { */ private $manager; + /** + * @var IConfig + **/ + private $config; + private $tableName; protected function setUp() { parent::setUp(); + $this->config = \OC::$server->getConfig(); $this->connection = \OC_DB::getConnection(); if ($this->connection->getDatabasePlatform() instanceof OraclePlatform) { $this->markTestSkipped('DB migration tests are not supported on OCI'); @@ -39,7 +45,7 @@ class Migrator extends \Test\TestCase { $this->markTestSkipped('DB migration tests are not supported on MSSQL'); } $this->manager = new \OC\DB\MDB2SchemaManager($this->connection); - $this->tableName = strtolower($this->getUniqueID('oc_test_')); + $this->tableName = strtolower($this->getUniqueID($this->config->getSystemValue('dbtableprefix', 'oc_') . 'test_')); } protected function tearDown() { @@ -109,6 +115,27 @@ class Migrator extends \Test\TestCase { $this->assertTrue(true); } + public function testUpgradeDifferentPrefix() { + $oldTablePrefix = $this->config->getSystemValue('dbtableprefix', 'oc_'); + + $this->config->setSystemValue('dbtableprefix', 'ownc_'); + $this->tableName = strtolower($this->getUniqueID($this->config->getSystemValue('dbtableprefix') . 'test_')); + + list($startSchema, $endSchema) = $this->getDuplicateKeySchemas(); + $migrator = $this->manager->getMigrator(); + $migrator->migrate($startSchema); + + $this->connection->insert($this->tableName, array('id' => 1, 'name' => 'foo')); + $this->connection->insert($this->tableName, array('id' => 2, 'name' => 'bar')); + $this->connection->insert($this->tableName, array('id' => 3, 'name' => 'qwerty')); + + $migrator->checkMigrate($endSchema); + $migrator->migrate($endSchema); + $this->assertTrue(true); + + $this->config->setSystemValue('dbtableprefix', $oldTablePrefix); + } + public function testInsertAfterUpgrade() { list($startSchema, $endSchema) = $this->getDuplicateKeySchemas(); $migrator = $this->manager->getMigrator(); diff --git a/tests/lib/files/filesystem.php b/tests/lib/files/filesystem.php index 7bf59315d77..2da2937bb4f 100644 --- a/tests/lib/files/filesystem.php +++ b/tests/lib/files/filesystem.php @@ -22,7 +22,12 @@ namespace Test\Files; +use OC\User\NoUserException; + class Filesystem extends \Test\TestCase { + + const TEST_FILESYSTEM_USER1 = "test-filesystem-user1"; + /** * @var array tmpDirs */ @@ -244,8 +249,14 @@ class Filesystem extends \Test\TestCase { if (\OC\Files\Filesystem::getView()) { $user = \OC_User::getUser(); } else { - $user = $this->getUniqueID(); + $user = self::TEST_FILESYSTEM_USER1; + $backend = new \OC_User_Dummy(); + \OC_User::useBackend($backend); + $backend->createUser($user, $user); + $userObj = \OC::$server->getUserManager()->get($user); + \OC::$server->getUserSession()->setUser($userObj); \OC\Files\Filesystem::init($user, '/' . $user . '/files'); + } \OC_Hook::clear('OC_Filesystem'); \OC_Hook::connect('OC_Filesystem', 'post_write', $this, 'dummyHook'); @@ -267,19 +278,14 @@ class Filesystem extends \Test\TestCase { } /** - * Tests that a local storage mount is used when passed user - * does not exist. + * Tests that an exception is thrown when passed user does not exist. + * @expectedException \OC\User\NoUserException */ public function testLocalMountWhenUserDoesNotExist() { $datadir = \OC_Config::getValue("datadirectory", \OC::$SERVERROOT . "/data"); $userId = $this->getUniqueID('user_'); \OC\Files\Filesystem::initMountPoints($userId); - - $homeMount = \OC\Files\Filesystem::getStorage('/' . $userId . '/'); - - $this->assertTrue($homeMount->instanceOfStorage('\OC\Files\Storage\Local')); - $this->assertEquals('local::' . $datadir . '/' . $userId . '/', $homeMount->getId()); } /** diff --git a/tests/lib/files/mount/mountpoint.php b/tests/lib/files/mount/mountpoint.php index 5a9c6de3e0a..29610e6058d 100644 --- a/tests/lib/files/mount/mountpoint.php +++ b/tests/lib/files/mount/mountpoint.php @@ -31,6 +31,10 @@ class MountPoint extends \Test\TestCase { $this->assertEquals($storage, $mountPoint->getStorage()); $this->assertEquals(123, $mountPoint->getStorageId()); + $this->assertEquals('/mountpoint/', $mountPoint->getMountPoint()); + + $mountPoint->setMountPoint('another'); + $this->assertEquals('/another/', $mountPoint->getMountPoint()); } public function testInvalidStorage() { diff --git a/tests/lib/preview.php b/tests/lib/preview.php index 2a6761403f4..c7a58f84ab4 100644 --- a/tests/lib/preview.php +++ b/tests/lib/preview.php @@ -10,10 +10,7 @@ namespace Test; class Preview extends TestCase { - /** - * @var string - */ - private $user; + const TEST_PREVIEW_USER1 = "test-preview-user1"; /** * @var \OC\Files\View @@ -30,15 +27,18 @@ class Preview extends TestCase { // create a new user with his own filesystem view // this gets called by each test in this test class - $this->user = $this->getUniqueID(); - \OC_User::setUserId($this->user); - \OC\Files\Filesystem::init($this->user, '/' . $this->user . '/files'); + $backend = new \OC_User_Dummy(); + \OC_User::useBackend($backend); + $backend->createUser(self::TEST_PREVIEW_USER1, self::TEST_PREVIEW_USER1); + $user = \OC::$server->getUserManager()->get(self::TEST_PREVIEW_USER1); + \OC::$server->getUserSession()->setUser($user); + \OC\Files\Filesystem::init(self::TEST_PREVIEW_USER1, '/' . self::TEST_PREVIEW_USER1 . '/files'); \OC\Files\Filesystem::mount('OC\Files\Storage\Temporary', array(), '/'); $this->rootView = new \OC\Files\View(''); - $this->rootView->mkdir('/'.$this->user); - $this->rootView->mkdir('/'.$this->user.'/files'); + $this->rootView->mkdir('/'.self::TEST_PREVIEW_USER1); + $this->rootView->mkdir('/'.self::TEST_PREVIEW_USER1.'/files'); } protected function tearDown() { @@ -50,20 +50,20 @@ class Preview extends TestCase { public function testIsPreviewDeleted() { - $sampleFile = '/'.$this->user.'/files/test.txt'; + $sampleFile = '/'.self::TEST_PREVIEW_USER1.'/files/test.txt'; $this->rootView->file_put_contents($sampleFile, 'dummy file data'); $x = 50; $y = 50; - $preview = new \OC\Preview($this->user, 'files/', 'test.txt', $x, $y); + $preview = new \OC\Preview(self::TEST_PREVIEW_USER1, 'files/', 'test.txt', $x, $y); $preview->getPreview(); $fileInfo = $this->rootView->getFileInfo($sampleFile); $fileId = $fileInfo['fileid']; - $thumbCacheFile = '/' . $this->user . '/' . \OC\Preview::THUMBNAILS_FOLDER . '/' . $fileId . '/' . $x . '-' . $y . '.png'; + $thumbCacheFile = '/' . self::TEST_PREVIEW_USER1 . '/' . \OC\Preview::THUMBNAILS_FOLDER . '/' . $fileId . '/' . $x . '-' . $y . '.png'; $this->assertEquals($this->rootView->file_exists($thumbCacheFile), true); @@ -74,20 +74,20 @@ class Preview extends TestCase { public function testAreAllPreviewsDeleted() { - $sampleFile = '/'.$this->user.'/files/test.txt'; + $sampleFile = '/'.self::TEST_PREVIEW_USER1.'/files/test.txt'; $this->rootView->file_put_contents($sampleFile, 'dummy file data'); $x = 50; $y = 50; - $preview = new \OC\Preview($this->user, 'files/', 'test.txt', $x, $y); + $preview = new \OC\Preview(self::TEST_PREVIEW_USER1, 'files/', 'test.txt', $x, $y); $preview->getPreview(); $fileInfo = $this->rootView->getFileInfo($sampleFile); $fileId = $fileInfo['fileid']; - $thumbCacheFolder = '/' . $this->user . '/' . \OC\Preview::THUMBNAILS_FOLDER . '/' . $fileId . '/'; + $thumbCacheFolder = '/' . self::TEST_PREVIEW_USER1 . '/' . \OC\Preview::THUMBNAILS_FOLDER . '/' . $fileId . '/'; $this->assertEquals($this->rootView->is_dir($thumbCacheFolder), true); @@ -104,11 +104,11 @@ class Preview extends TestCase { \OC_Config::setValue('preview_max_x', $maxX); \OC_Config::setValue('preview_max_y', $maxY); - $sampleFile = '/'.$this->user.'/files/test.txt'; + $sampleFile = '/'.self::TEST_PREVIEW_USER1.'/files/test.txt'; $this->rootView->file_put_contents($sampleFile, 'dummy file data'); - $preview = new \OC\Preview($this->user, 'files/', 'test.txt', 1000, 1000); + $preview = new \OC\Preview(self::TEST_PREVIEW_USER1, 'files/', 'test.txt', 1000, 1000); $image = $preview->getPreview(); $this->assertEquals($image->width(), $maxX); @@ -131,9 +131,9 @@ class Preview extends TestCase { $x = 32; $y = 32; - $sample = '/'.$this->user.'/files/test.'.$extension; + $sample = '/'.self::TEST_PREVIEW_USER1.'/files/test.'.$extension; $this->rootView->file_put_contents($sample, $data); - $preview = new \OC\Preview($this->user, 'files/', 'test.'.$extension, $x, $y); + $preview = new \OC\Preview(self::TEST_PREVIEW_USER1, 'files/', 'test.'.$extension, $x, $y); $image = $preview->getPreview(); $resource = $image->resource(); @@ -149,7 +149,7 @@ class Preview extends TestCase { public function testCreationFromCached() { - $sampleFile = '/'.$this->user.'/files/test.txt'; + $sampleFile = '/'.self::TEST_PREVIEW_USER1.'/files/test.txt'; $this->rootView->file_put_contents($sampleFile, 'dummy file data'); @@ -157,22 +157,22 @@ class Preview extends TestCase { $x = 150; $y = 150; - $preview = new \OC\Preview($this->user, 'files/', 'test.txt', $x, $y); + $preview = new \OC\Preview(self::TEST_PREVIEW_USER1, 'files/', 'test.txt', $x, $y); $preview->getPreview(); $fileInfo = $this->rootView->getFileInfo($sampleFile); $fileId = $fileInfo['fileid']; - $thumbCacheFile = '/' . $this->user . '/' . \OC\Preview::THUMBNAILS_FOLDER . '/' . $fileId . '/' . $x . '-' . $y . '.png'; + $thumbCacheFile = '/' . self::TEST_PREVIEW_USER1 . '/' . \OC\Preview::THUMBNAILS_FOLDER . '/' . $fileId . '/' . $x . '-' . $y . '.png'; $this->assertEquals($this->rootView->file_exists($thumbCacheFile), true); // create smaller previews - $preview = new \OC\Preview($this->user, 'files/', 'test.txt', 50, 50); + $preview = new \OC\Preview(self::TEST_PREVIEW_USER1, 'files/', 'test.txt', 50, 50); $isCached = $preview->isCached($fileId); - $this->assertEquals($this->user . '/' . \OC\Preview::THUMBNAILS_FOLDER . '/' . $fileId . '/150-150.png', $isCached); + $this->assertEquals(self::TEST_PREVIEW_USER1 . '/' . \OC\Preview::THUMBNAILS_FOLDER . '/' . $fileId . '/150-150.png', $isCached); } /* diff --git a/tests/lib/share/share.php b/tests/lib/share/share.php index 42bb82968af..8f3d927be34 100644 --- a/tests/lib/share/share.php +++ b/tests/lib/share/share.php @@ -101,9 +101,16 @@ class Test_Share extends \Test\TestCase { OC_Group::deleteGroup($this->group2); OC_Group::deleteGroup($this->groupAndUser); + $this->logout(); parent::tearDown(); } + protected function setHttpHelper($httpHelper) { + \OC::$server->registerService('HTTPHelper', function () use ($httpHelper) { + return $httpHelper; + }); + } + public function testShareInvalidShareType() { $message = 'Share type foobar is not valid for test.txt'; try { @@ -388,6 +395,45 @@ class Test_Share extends \Test\TestCase { $this->assertSame(\OCP\Share::SHARE_TYPE_USER, $share['share_type']); } + public function testGetShareFromOutsideFilesFolder() { + OC_User::setUserId($this->user1); + $view = new \OC\Files\View('/' . $this->user1 . '/'); + $view->mkdir('files/test'); + $view->mkdir('files/test/sub'); + + $view->mkdir('files_trashbin'); + $view->mkdir('files_trashbin/files'); + + $fileInfo = $view->getFileInfo('files/test/sub'); + $fileId = $fileInfo->getId(); + + $this->assertTrue( + OCP\Share::shareItem('folder', $fileId, OCP\Share::SHARE_TYPE_USER, $this->user2, \OCP\Constants::PERMISSION_READ), + 'Failed asserting that user 1 successfully shared "test/sub" with user 2.' + ); + + $result = OCP\Share::getItemShared('folder', $fileId, Test_Share_Backend::FORMAT_SOURCE); + $this->assertNotEmpty($result); + + $result = OCP\Share::getItemSharedWithUser('folder', $fileId, $this->user2); + $this->assertNotEmpty($result); + + $result = OCP\Share::getItemsSharedWithUser('folder', $this->user2); + $this->assertNotEmpty($result); + + // move to trash (keeps file id) + $view->rename('files/test', 'files_trashbin/files/test'); + + $result = OCP\Share::getItemShared('folder', $fileId, Test_Share_Backend::FORMAT_SOURCE); + $this->assertEmpty($result, 'Share must not be returned for files outside of "files"'); + + $result = OCP\Share::getItemSharedWithUser('folder', $fileId, $this->user2); + $this->assertEmpty($result, 'Share must not be returned for files outside of "files"'); + + $result = OCP\Share::getItemsSharedWithUser('folder', $this->user2); + $this->assertEmpty($result, 'Share must not be returned for files outside of "files"'); + } + public function testSetExpireDateInPast() { OC_User::setUserId($this->user1); $this->shareUserOneTestFileWithUserTwo(); @@ -975,10 +1021,58 @@ class Test_Share extends \Test\TestCase { ); } + public function dataRemoteShareUrlCalls() { + return [ + ['admin@localhost', 'localhost'], + ['admin@https://localhost', 'localhost'], + ['admin@http://localhost', 'localhost'], + ['admin@localhost/subFolder', 'localhost/subFolder'], + ]; + } + + /** + * @dataProvider dataRemoteShareUrlCalls + * + * @param string $shareWith + * @param string $urlHost + */ + public function testRemoteShareUrlCalls($shareWith, $urlHost) { + $oldHttpHelper = \OC::$server->query('HTTPHelper'); + $httpHelperMock = $this->getMockBuilder('OC\HttpHelper') + ->disableOriginalConstructor() + ->getMock(); + $this->setHttpHelper($httpHelperMock); + + $httpHelperMock->expects($this->at(0)) + ->method('post') + ->with($this->stringStartsWith('https://' . $urlHost . '/ocs/v1.php/cloud/shares'), $this->anything()) + ->willReturn(['success' => false, 'result' => 'Exception']); + $httpHelperMock->expects($this->at(1)) + ->method('post') + ->with($this->stringStartsWith('http://' . $urlHost . '/ocs/v1.php/cloud/shares'), $this->anything()) + ->willReturn(['success' => true, 'result' => json_encode(['ocs' => ['meta' => ['statuscode' => 100]]])]); + + \OCP\Share::shareItem('test', 'test.txt', \OCP\Share::SHARE_TYPE_REMOTE, $shareWith, \OCP\Constants::PERMISSION_READ); + $shares = \OCP\Share::getItemShared('test', 'test.txt'); + $share = array_shift($shares); + + $httpHelperMock->expects($this->at(0)) + ->method('post') + ->with($this->stringStartsWith('https://' . $urlHost . '/ocs/v1.php/cloud/shares/' . $share['id'] . '/unshare'), $this->anything()) + ->willReturn(['success' => false, 'result' => 'Exception']); + $httpHelperMock->expects($this->at(1)) + ->method('post') + ->with($this->stringStartsWith('http://' . $urlHost . '/ocs/v1.php/cloud/shares/' . $share['id'] . '/unshare'), $this->anything()) + ->willReturn(['success' => true, 'result' => json_encode(['ocs' => ['meta' => ['statuscode' => 100]]])]); + + \OCP\Share::unshare('test', 'test.txt', \OCP\Share::SHARE_TYPE_REMOTE, $shareWith); + $this->setHttpHelper($oldHttpHelper); + } + /** * @dataProvider dataProviderTestGroupItems - * @param type $ungrouped - * @param type $grouped + * @param array $ungrouped + * @param array $grouped */ function testGroupItems($ungrouped, $grouped) { diff --git a/tests/lib/testcase.php b/tests/lib/testcase.php index 1ea3aa13547..704912b22a4 100644 --- a/tests/lib/testcase.php +++ b/tests/lib/testcase.php @@ -168,5 +168,7 @@ abstract class TestCase extends \PHPUnit_Framework_TestCase { static protected function logout() { \OC_Util::tearDownFS(); \OC_User::setUserId(''); + // needed for fully logout + \OC::$server->getUserSession()->setUser(null); } } diff --git a/tests/lib/util.php b/tests/lib/util.php index e77aa53b72d..5fc5e87807f 100644 --- a/tests/lib/util.php +++ b/tests/lib/util.php @@ -54,24 +54,27 @@ class Test_Util extends \Test\TestCase { public function formatDateWithTZFromSessionData() { return array( - array(3, 'October 13, 2012 at 2:53:25 PM GMT+3'), - array(15, 'October 13, 2012 at 11:53:25 AM GMT+0'), - array(-13, 'October 13, 2012 at 11:53:25 AM GMT+0'), - array(9.5, 'October 13, 2012 at 9:23:25 PM GMT+9:30'), - array(-4.5, 'October 13, 2012 at 7:23:25 AM GMT-4:30'), - array(15.5, 'October 13, 2012 at 11:53:25 AM GMT+0'), + array(3, 'October 13, 2012 at 2:53:25 PM GMT+3', 'Etc/GMT-3'), + array(15, 'October 13, 2012 at 11:53:25 AM GMT+0', 'UTC'), + array(-13, 'October 13, 2012 at 11:53:25 AM GMT+0', 'UTC'), + array(9.5, 'October 13, 2012 at 9:23:25 PM GMT+9:30', 'Australia/Darwin'), + array(-4.5, 'October 13, 2012 at 7:23:25 AM GMT-4:30', 'America/Caracas'), + array(15.5, 'October 13, 2012 at 11:53:25 AM GMT+0', 'UTC'), ); } /** * @dataProvider formatDateWithTZFromSessionData */ - function testFormatDateWithTZFromSession($offset, $expected) { + function testFormatDateWithTZFromSession($offset, $expected, $expectedTimeZone) { date_default_timezone_set("UTC"); $oldDateTimeFormatter = \OC::$server->query('DateTimeFormatter'); \OC::$server->getSession()->set('timezone', $offset); - $newDateTimeFormatter = new \OC\DateTimeFormatter(\OC::$server->getDateTimeZone()->getTimeZone(), new \OC_L10N('lib', 'en')); + + $selectedTimeZone = \OC::$server->getDateTimeZone()->getTimeZone(1350129205); + $this->assertEquals($expectedTimeZone, $selectedTimeZone->getName()); + $newDateTimeFormatter = new \OC\DateTimeFormatter($selectedTimeZone, new \OC_L10N('lib', 'en')); $this->setDateFormatter($newDateTimeFormatter); $result = OC_Util::formatDate(1350129205, false); diff --git a/version.php b/version.php index 8712f7a8593..98e64f14c55 100644 --- a/version.php +++ b/version.php @@ -3,10 +3,10 @@ // 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(8, 0, 3, 0); +$OC_Version=array(8, 0, 3, 4); // The human readable string -$OC_VersionString='8.0.3 RC1'; +$OC_VersionString='8.0.3'; // The ownCloud channel $OC_Channel='git'; |