diff options
author | Thomas Müller <thomas.mueller@tmit.eu> | 2014-02-28 14:54:10 +0100 |
---|---|---|
committer | Thomas Müller <thomas.mueller@tmit.eu> | 2014-02-28 14:54:10 +0100 |
commit | 15d1df055b093ecce0c5ae52561dd73584145c7c (patch) | |
tree | 4f2b10bacfa5371e3f2a53122a3165bf94114d01 /apps | |
parent | 65843e245996c9ecfd167be2b520bb917b32aa7e (diff) | |
parent | dd32091016481b0b6845e03ea87ce419b3cda19e (diff) | |
download | nextcloud-server-15d1df055b093ecce0c5ae52561dd73584145c7c.tar.gz nextcloud-server-15d1df055b093ecce0c5ae52561dd73584145c7c.zip |
Merge branch 'master' into display-share-owner-master
Conflicts:
apps/files_sharing/lib/cache.php
Diffstat (limited to 'apps')
32 files changed, 831 insertions, 150 deletions
diff --git a/apps/files/ajax/rawlist.php b/apps/files/ajax/rawlist.php index 40da32b223a..89c21a172fc 100644 --- a/apps/files/ajax/rawlist.php +++ b/apps/files/ajax/rawlist.php @@ -1,12 +1,12 @@ <?php // only need filesystem apps -$RUNTIME_APPTYPES=array('filesystem'); +$RUNTIME_APPTYPES = array('filesystem'); OCP\JSON::checkLoggedIn(); // Load the files -$dir = isset( $_GET['dir'] ) ? $_GET['dir'] : ''; +$dir = isset($_GET['dir']) ? $_GET['dir'] : ''; $mimetypes = isset($_GET['mimetypes']) ? json_decode($_GET['mimetypes'], true) : ''; // Clean up duplicates from array and deal with non-array requests @@ -18,43 +18,40 @@ if (is_array($mimetypes)) { // make filelist $files = array(); +/** + * @var \OCP\Files\FileInfo[] $files + */ // If a type other than directory is requested first load them. -if($mimetypes && !in_array('httpd/unix-directory', $mimetypes)) { - foreach( \OC\Files\Filesystem::getDirectoryContent( $dir, 'httpd/unix-directory' ) as $file ) { - $file['directory'] = $dir; - $file['isPreviewAvailable'] = \OC::$server->getPreviewManager()->isMimeSupported($file['mimetype']); - $file["date"] = OCP\Util::formatDate($file["mtime"]); - $file['mimetype_icon'] = \OCA\Files\Helper::determineIcon($file); - $files[] = $file; - } +if ($mimetypes && !in_array('httpd/unix-directory', $mimetypes)) { + $files = array_merge($files, \OC\Files\Filesystem::getDirectoryContent($dir, 'httpd/unix-directory')); } if (is_array($mimetypes) && count($mimetypes)) { foreach ($mimetypes as $mimetype) { - foreach( \OC\Files\Filesystem::getDirectoryContent( $dir, $mimetype ) as $file ) { - $file['directory'] = $dir; - $file['isPreviewAvailable'] = \OC::$server->getPreviewManager()->isMimeSupported($file['mimetype']); - $file["date"] = OCP\Util::formatDate($file["mtime"]); - $file['mimetype_icon'] = \OCA\Files\Helper::determineIcon($file); - $files[] = $file; - } + $files = array_merge($files, \OC\Files\Filesystem::getDirectoryContent($dir, $mimetype)); } } else { - foreach( \OC\Files\Filesystem::getDirectoryContent( $dir ) as $file ) { - $file['directory'] = $dir; - $file['isPreviewAvailable'] = \OC::$server->getPreviewManager()->isMimeSupported($file['mimetype']); - $file["date"] = OCP\Util::formatDate($file["mtime"]); - $file['mimetype_icon'] = \OCA\Files\Helper::determineIcon($file); - $files[] = $file; - } + $files = array_merge($files, \OC\Files\Filesystem::getDirectoryContent($dir)); +} + +$result = array(); +foreach ($files as $file) { + $fileData = array(); + $fileData['directory'] = $dir; + $fileData['name'] = $file->getName(); + $fileData['type'] = $file->getType(); + $fileData['path'] = $file['path']; + $fileData['id'] = $file->getId(); + $fileData['size'] = $file->getSize(); + $fileData['mtime'] = $file->getMtime(); + $fileData['mimetype'] = $file->getMimetype(); + $fileData['isPreviewAvailable'] = \OC::$server->getPreviewManager()->isMimeSupported($file->getMimetype()); + $fileData["date"] = OCP\Util::formatDate($file->getMtime()); + $fileData['mimetype_icon'] = \OCA\Files\Helper::determineIcon($file); + $result[] = $fileData; } // Sort by name -usort($files, function ($a, $b) { - if ($a['name'] === $b['name']) { - return 0; - } - return ($a['name'] < $b['name']) ? -1 : 1; -}); +usort($result, array('\OCA\Files\Helper', 'fileCmp')); -OC_JSON::success(array('data' => $files)); +OC_JSON::success(array('data' => $result)); diff --git a/apps/files/css/files.css b/apps/files/css/files.css index 3ad167054c2..af863aca33e 100644 --- a/apps/files/css/files.css +++ b/apps/files/css/files.css @@ -20,7 +20,7 @@ padding: 10px; font-weight: normal; } -#new>a { +#new > a { padding: 14px 10px; position: relative; top: 7px; @@ -30,7 +30,7 @@ border-bottom-right-radius: 0; border-bottom: none; } -#new>ul { +#new > ul { display: none; position: fixed; min-width: 112px; @@ -39,16 +39,26 @@ padding-bottom: 0; margin-top: 14px; margin-left: -1px; - text-align:left; + text-align: left; background: #f8f8f8; border: 1px solid #ddd; border-radius: 5px; border-top-left-radius: 0; - box-shadow:0 2px 7px rgba(170,170,170,.4); + box-shadow: 0 2px 7px rgba(170,170,170,.4); +} +#new > ul > li { + height: 36px; + margin: 5px; + padding-left: 42px; + padding-bottom: 2px; + background-position: initial; + cursor: pointer; +} +#new > ul > li > p { + cursor: pointer; + padding-top: 7px; + padding-bottom: 7px; } -#new>ul>li { height:36px; margin:5px; padding-left:48px; padding-bottom:2px; - background-repeat:no-repeat; cursor:pointer; } -#new>ul>li>p { cursor:pointer; padding-top: 7px; padding-bottom: 7px;} #new .error, #fileList .error { color: #e9322d; diff --git a/apps/files/js/admin.js b/apps/files/js/admin.js index f735079fcbe..842b73c0cae 100644 --- a/apps/files/js/admin.js +++ b/apps/files/js/admin.js @@ -8,19 +8,22 @@ * */ -function switchPublicFolder() -{ +function switchPublicFolder() { var publicEnable = $('#publicEnable').is(':checked'); - var sharingaimGroup = $('input:radio[name=sharingaim]'); //find all radiobuttons of that group + // find all radiobuttons of that group + var sharingaimGroup = $('input:radio[name=sharingaim]'); $.each(sharingaimGroup, function(index, sharingaimItem) { - sharingaimItem.disabled = !publicEnable; //set all buttons to the correct state + // set all buttons to the correct state + sharingaimItem.disabled = !publicEnable; }); } -$(document).ready(function(){ - switchPublicFolder(); // Execute the function after loading DOM tree - $('#publicEnable').click(function(){ - switchPublicFolder(); // To get rid of onClick() +$(document).ready(function() { + // Execute the function after loading DOM tree + switchPublicFolder(); + $('#publicEnable').click(function() { + // To get rid of onClick() + switchPublicFolder(); }); $('#allowZipDownload').bind('change', function() { diff --git a/apps/files/js/filelist.js b/apps/files/js/filelist.js index d6cffde05de..550c10dba3e 100644 --- a/apps/files/js/filelist.js +++ b/apps/files/js/filelist.js @@ -11,6 +11,7 @@ /* global OC, t, n, FileList, FileActions, Files */ /* global procesSelection, dragOptions, SVGSupport, replaceSVG */ window.FileList={ + appName: t('files', 'Files'), useUndo:true, postProcessList: function() { $('#fileList tr').each(function() { @@ -19,6 +20,21 @@ window.FileList={ }); }, /** + * Sets a new page title + */ + setPageTitle: function(title){ + if (title) { + title += ' - '; + } else { + title = ''; + } + title += FileList.appName; + // Sets the page title with the " - ownCloud" suffix as in templates + window.document.title = title + ' - ' + oc_defaults.title; + + return true; + }, + /** * Returns the tr element for a given file name */ findFileEl: function(fileName){ @@ -129,7 +145,7 @@ window.FileList={ if (loading) { imgurl = OC.imagePath('core', 'loading.gif'); } else { - imgurl = OC.imagePath('core', 'filetypes/file.png'); + imgurl = OC.imagePath('core', 'filetypes/file'); } var tr = this.createRow( 'file', @@ -157,7 +173,7 @@ window.FileList={ var tr = this.createRow( 'dir', name, - OC.imagePath('core', 'filetypes/folder.png'), + OC.imagePath('core', 'filetypes/folder'), OC.linkTo('files', 'index.php')+"?dir="+ encodeURIComponent($('#dir').val()+'/'+name).replace(/%2F/g, '/'), size, lastModified, @@ -204,7 +220,16 @@ window.FileList={ return OC.linkTo('files', 'index.php')+"?dir="+ encodeURIComponent(dir).replace(/%2F/g, '/'); }, setCurrentDir: function(targetDir, changeUrl) { - var url; + var url, + baseDir = OC.basename(targetDir); + + if (baseDir !== '') { + FileList.setPageTitle(baseDir); + } + else { + FileList.setPageTitle(); + } + $('#dir').val(targetDir); if (changeUrl !== false) { if (window.history.pushState && changeUrl !== false) { @@ -847,7 +872,8 @@ window.FileList={ }; $(document).ready(function() { - var isPublic = !!$('#isPublic').val(); + var baseDir, + isPublic = !!$('#isPublic').val(); // handle upload events var file_upload_start = $('#file_upload_start'); @@ -943,7 +969,7 @@ $(document).ready(function() { uploadtext.attr('currentUploads', currentUploads); var translatedText = n('files', 'Uploading %n file', 'Uploading %n files', currentUploads); if (currentUploads === 0) { - var img = OC.imagePath('core', 'filetypes/folder.png'); + var img = OC.imagePath('core', 'filetypes/folder'); data.context.find('td.filename').attr('style','background-image:url('+img+')'); uploadtext.text(translatedText); uploadtext.hide(); @@ -1003,7 +1029,7 @@ $(document).ready(function() { if (data.errorThrown === 'abort') { //cleanup uploading to a dir var uploadtext = $('tr .uploadtext'); - var img = OC.imagePath('core', 'filetypes/folder.png'); + var img = OC.imagePath('core', 'filetypes/folder'); uploadtext.parents('td.filename').attr('style','background-image:url('+img+')'); uploadtext.fadeOut(); uploadtext.attr('currentUploads', 0); @@ -1016,7 +1042,7 @@ $(document).ready(function() { if (data.errorThrown === 'abort') { //cleanup uploading to a dir var uploadtext = $('tr .uploadtext'); - var img = OC.imagePath('core', 'filetypes/folder.png'); + var img = OC.imagePath('core', 'filetypes/folder'); uploadtext.parents('td.filename').attr('style','background-image:url('+img+')'); uploadtext.fadeOut(); uploadtext.attr('currentUploads', 0); @@ -1132,5 +1158,7 @@ $(document).ready(function() { } } + FileList.setCurrentDir(parseCurrentDirFromUrl(), false); + FileList.createFileSummary(); }); diff --git a/apps/files/js/files.js b/apps/files/js/files.js index fbac601f67a..f4546120702 100644 --- a/apps/files/js/files.js +++ b/apps/files/js/files.js @@ -734,6 +734,9 @@ Files.getMimeIcon = function(mime, ready) { ready(Files.getMimeIcon.cache[mime]); } else { $.get( OC.filePath('files','ajax','mimeicon.php'), {mime: mime}, function(path) { + if(SVGSupport()){ + path = path.substr(0, path.length-4) + '.svg'; + } Files.getMimeIcon.cache[mime]=path; ready(Files.getMimeIcon.cache[mime]); }); @@ -783,13 +786,16 @@ Files.lazyLoadPreview = function(path, mime, ready, width, height, etag) { } previewURL = previewURL.replace('(', '%28'); previewURL = previewURL.replace(')', '%29'); + previewURL += '&forceIcon=0'; // preload image to prevent delay // this will make the browser cache the image var img = new Image(); img.onload = function(){ - //set preview thumbnail URL - ready(previewURL); + // if loading the preview image failed (no preview for the mimetype) then img.width will < 5 + if (img.width > 5) { + ready(previewURL); + } } img.src = previewURL; }); diff --git a/apps/files/lib/helper.php b/apps/files/lib/helper.php index ac8a2ad3200..b9e41a352bc 100644 --- a/apps/files/lib/helper.php +++ b/apps/files/lib/helper.php @@ -22,6 +22,7 @@ class Helper public static function determineIcon($file) { if($file['type'] === 'dir') { $dir = $file['directory']; + $icon = \OC_Helper::mimetypeIcon('dir'); $absPath = \OC\Files\Filesystem::getView()->getAbsolutePath($dir.'/'.$file['name']); $mount = \OC\Files\Filesystem::getMountManager()->find($absPath); if (!is_null($mount)) { @@ -29,21 +30,22 @@ class Helper if (!is_null($sid)) { $sid = explode(':', $sid); if ($sid[0] === 'shared') { - return \OC_Helper::mimetypeIcon('dir-shared'); + $icon = \OC_Helper::mimetypeIcon('dir-shared'); } if ($sid[0] !== 'local' and $sid[0] !== 'home') { - return \OC_Helper::mimetypeIcon('dir-external'); + $icon = \OC_Helper::mimetypeIcon('dir-external'); } } } - return \OC_Helper::mimetypeIcon('dir'); + }else{ + if($file['isPreviewAvailable']) { + $pathForPreview = $file['directory'] . '/' . $file['name']; + return \OC_Helper::previewIcon($pathForPreview) . '&c=' . $file['etag']; + } + $icon = \OC_Helper::mimetypeIcon($file['mimetype']); } - if($file['isPreviewAvailable']) { - $pathForPreview = $file['directory'] . '/' . $file['name']; - return \OC_Helper::previewIcon($pathForPreview) . '&c=' . $file['etag']; - } - return \OC_Helper::mimetypeIcon($file['mimetype']); + return substr($icon, 0, -3) . 'svg'; } /** diff --git a/apps/files/templates/index.php b/apps/files/templates/index.php index 939043b2c9f..ed15e46a5ac 100644 --- a/apps/files/templates/index.php +++ b/apps/files/templates/index.php @@ -5,12 +5,17 @@ <div id="new" class="button"> <a><?php p($l->t('New'));?></a> <ul> - <li style="background-image:url('<?php p(OCP\mimetype_icon('text/plain')) ?>')" - data-type='file' data-newname='<?php p($l->t('New text file')) ?>.txt'><p><?php p($l->t('Text file'));?></p></li> - <li style="background-image:url('<?php p(OCP\mimetype_icon('dir')) ?>')" - data-type='folder' data-newname='<?php p($l->t('New folder')) ?>'><p><?php p($l->t('Folder'));?></p></li> - <li style="background-image:url('<?php p(OCP\image_path('core', 'places/link.svg')) ?>')" - data-type='web'><p><?php p($l->t('From link'));?></p></li> + <li class="icon icon-filetype-text" + data-type="file" data-newname="<?php p($l->t('New text file')) ?>.txt"> + <p><?php p($l->t('Text file'));?></p> + </li> + <li class="icon icon-filetype-folder" + data-type="folder" data-newname="<?php p($l->t('New folder')) ?>"> + <p><?php p($l->t('Folder'));?></p> + </li> + <li class="icon icon-link" data-type="web"> + <p><?php p($l->t('From link'));?></p> + </li> </ul> </div> <?php endif;?> diff --git a/apps/files/tests/ajax_rename.php b/apps/files/tests/ajax_rename.php index a1a5c8983ba..e53c0fb3dd1 100644 --- a/apps/files/tests/ajax_rename.php +++ b/apps/files/tests/ajax_rename.php @@ -110,7 +110,9 @@ class Test_OC_Files_App_Rename extends \PHPUnit_Framework_TestCase { $this->assertEquals('/test', $result['data']['directory']); $this->assertEquals(18, $result['data']['size']); $this->assertEquals('httpd/unix-directory', $result['data']['mime']); - $this->assertEquals(\OC_Helper::mimetypeIcon('dir'), $result['data']['icon']); + $icon = \OC_Helper::mimetypeIcon('dir'); + $icon = substr($icon, 0, -3) . 'svg'; + $this->assertEquals($icon, $result['data']['icon']); $this->assertFalse($result['data']['isPreviewAvailable']); } @@ -165,7 +167,9 @@ class Test_OC_Files_App_Rename extends \PHPUnit_Framework_TestCase { $this->assertEquals(18, $result['data']['size']); $this->assertEquals('httpd/unix-directory', $result['data']['mime']); $this->assertEquals('abcdef', $result['data']['etag']); - $this->assertEquals(\OC_Helper::mimetypeIcon('dir'), $result['data']['icon']); + $icon = \OC_Helper::mimetypeIcon('dir'); + $icon = substr($icon, 0, -3) . 'svg'; + $this->assertEquals($icon, $result['data']['icon']); $this->assertFalse($result['data']['isPreviewAvailable']); } diff --git a/apps/files_encryption/hooks/hooks.php b/apps/files_encryption/hooks/hooks.php index 3af43f10264..0b6c5adf3fb 100644 --- a/apps/files_encryption/hooks/hooks.php +++ b/apps/files_encryption/hooks/hooks.php @@ -501,11 +501,20 @@ class Hooks { * @param array $params with the old path and the new path
*/
public static function preRename($params) {
- $util = new Util(new \OC_FilesystemView('/'), \OCP\User::getUser());
+ $user = \OCP\User::getUser();
+ $view = new \OC_FilesystemView('/');
+ $util = new Util($view, $user);
list($ownerOld, $pathOld) = $util->getUidAndFilename($params['oldpath']);
- self::$renamedFiles[$params['oldpath']] = array(
- 'uid' => $ownerOld,
- 'path' => $pathOld);
+
+ // we only need to rename the keys if the rename happens on the same mountpoint
+ // otherwise we perform a stream copy, so we get a new set of keys
+ $mp1 = $view->getMountPoint('/' . $user . '/files/' . $params['oldpath']);
+ $mp2 = $view->getMountPoint('/' . $user . '/files/' . $params['newpath']);
+ if ($mp1 === $mp2) {
+ self::$renamedFiles[$params['oldpath']] = array(
+ 'uid' => $ownerOld,
+ 'path' => $pathOld);
+ }
}
/**
diff --git a/apps/files_encryption/js/settings-admin.js b/apps/files_encryption/js/settings-admin.js index c2140a6f1eb..785d02002fa 100644 --- a/apps/files_encryption/js/settings-admin.js +++ b/apps/files_encryption/js/settings-admin.js @@ -7,28 +7,6 @@ * See the COPYING-README file. */ -OC.msg={ - startSaving:function(selector){ - $(selector) - .html( t('settings', 'Saving...') ) - .removeClass('success') - .removeClass('error') - .stop(true, true) - .show(); - }, - finishedSaving:function(selector, data){ - if( data.status === "success" ){ - $(selector).html( data.data.message ) - .addClass('success') - .stop(true, true) - .delay(3000) - .fadeOut(900); - }else{ - $(selector).html( data.data.message ).addClass('error'); - } - } -}; - $(document).ready(function(){ // Trigger ajax on recoveryAdmin status change var enabledStatus = $('#adminEnableRecovery').val(); diff --git a/apps/files_encryption/lib/proxy.php b/apps/files_encryption/lib/proxy.php index 9d456f6c517..a2d42c22c13 100644 --- a/apps/files_encryption/lib/proxy.php +++ b/apps/files_encryption/lib/proxy.php @@ -38,6 +38,7 @@ class Proxy extends \OC_FileProxy { private static $blackList = null; //mimetypes blacklisted from encryption private static $unencryptedSizes = array(); // remember unencrypted size + private static $fopenMode = array(); // remember the fopen mode /** * Check if a file requires encryption @@ -146,7 +147,7 @@ class Proxy extends \OC_FileProxy { if ( isset(self::$unencryptedSizes[$normalizedPath]) ) { $view = new \OC_FilesystemView('/'); $view->putFileInfo($normalizedPath, - array('encrypted' => true, 'encrypted_size' => self::$unencryptedSizes[$normalizedPath])); + array('encrypted' => true, 'unencrypted_size' => self::$unencryptedSizes[$normalizedPath])); unset(self::$unencryptedSizes[$normalizedPath]); } @@ -214,6 +215,16 @@ class Proxy extends \OC_FileProxy { } /** + * @brief remember initial fopen mode because sometimes it gets changed during the request + * @param string $path path + * @param string $mode type of access + */ + public function preFopen($path, $mode) { + self::$fopenMode[$path] = $mode; + } + + + /** * @param $path * @param $result * @return resource @@ -240,7 +251,15 @@ class Proxy extends \OC_FileProxy { $proxyStatus = \OC_FileProxy::$enabled; \OC_FileProxy::$enabled = false; - $meta = stream_get_meta_data($result); + // if we remember the mode from the pre proxy we re-use it + // oterwise we fall back to stream_get_meta_data() + if (isset(self::$fopenMode[$path])) { + $mode = self::$fopenMode[$path]; + unset(self::$fopenMode[$path]); + } else { + $meta = stream_get_meta_data($result); + $mode = $meta['mode']; + } $view = new \OC_FilesystemView(''); @@ -258,14 +277,15 @@ class Proxy extends \OC_FileProxy { // Open the file using the crypto stream wrapper // protocol and let it do the decryption work instead - $result = fopen('crypt://' . $path, $meta['mode']); + $result = fopen('crypt://' . $path, $mode); } elseif ( - self::shouldEncrypt($path) - and $meta['mode'] !== 'r' - and $meta['mode'] !== 'rb' + self::shouldEncrypt($path) + and $mode !== 'r' + and $mode !== 'rb' + ) { - $result = fopen('crypt://' . $path, $meta['mode']); + $result = fopen('crypt://' . $path, $mode); } // Re-enable the proxy diff --git a/apps/files_encryption/lib/session.php b/apps/files_encryption/lib/session.php index aa58e33e9d2..3daaa06425f 100644 --- a/apps/files_encryption/lib/session.php +++ b/apps/files_encryption/lib/session.php @@ -134,6 +134,14 @@ class Session { } + /** + * @brief remove encryption keys and init status from session + */ + public function closeSession() { + \OC::$session->remove('encryptionInitialized'); + \OC::$session->remove('privateKey'); + } + /** * @brief Gets status if we already tried to initialize the encryption app diff --git a/apps/files_encryption/lib/stream.php b/apps/files_encryption/lib/stream.php index 88eacc6f136..58ac03373a7 100644 --- a/apps/files_encryption/lib/stream.php +++ b/apps/files_encryption/lib/stream.php @@ -167,6 +167,9 @@ class Stream { } else { $this->meta = stream_get_meta_data($this->handle); + // sometimes fopen changes the mode, e.g. for a url "r" convert to "r+" + // but we need to remember the original access type + $this->meta['mode'] = $mode; } diff --git a/apps/files_encryption/lib/util.php b/apps/files_encryption/lib/util.php index ec06bd52f5e..6bf69cd8ee1 100644 --- a/apps/files_encryption/lib/util.php +++ b/apps/files_encryption/lib/util.php @@ -1772,4 +1772,12 @@ class Util { return $session; } + /* + * @brief remove encryption related keys from the session + */ + public function closeEncryptionSession() { + $session = new \OCA\Encryption\Session($this->view); + $session->closeSession(); + } + } diff --git a/apps/files_encryption/tests/hooks.php b/apps/files_encryption/tests/hooks.php index 7d926caea1b..d0e4b5f732e 100644 --- a/apps/files_encryption/tests/hooks.php +++ b/apps/files_encryption/tests/hooks.php @@ -47,6 +47,7 @@ class Test_Encryption_Hooks extends \PHPUnit_Framework_TestCase { public $rootView; // view on /data/user public $data; public $filename; + public $folder; public static function setUpBeforeClass() { // reset backend @@ -89,6 +90,7 @@ class Test_Encryption_Hooks extends \PHPUnit_Framework_TestCase { // init short data $this->data = 'hats'; $this->filename = 'enc_hooks_tests-' . uniqid() . '.txt'; + $this->folder = 'enc_hooks_tests_folder-' . uniqid(); } @@ -268,4 +270,57 @@ class Test_Encryption_Hooks extends \PHPUnit_Framework_TestCase { } } + /** + * @brief test rename operation + */ + function testRenameHook() { + + // save file with content + $cryptedFile = file_put_contents('crypt:///' . self::TEST_ENCRYPTION_HOOKS_USER1 . '/files/' . $this->filename, $this->data); + + // test that data was successfully written + $this->assertTrue(is_int($cryptedFile)); + + // check if keys exists + $this->assertTrue($this->rootView->file_exists( + '/' . self::TEST_ENCRYPTION_HOOKS_USER1 . '/files_encryption/share-keys/' + . $this->filename . '.' . self::TEST_ENCRYPTION_HOOKS_USER1 . '.shareKey')); + + $this->assertTrue($this->rootView->file_exists( + '/' . self::TEST_ENCRYPTION_HOOKS_USER1 . '/files_encryption/keyfiles/' + . $this->filename . '.key')); + + // make subfolder + $this->rootView->mkdir('/' . self::TEST_ENCRYPTION_HOOKS_USER1 . '/files/' . $this->folder); + + $this->assertTrue($this->rootView->is_dir('/' . self::TEST_ENCRYPTION_HOOKS_USER1 . '/files/' . $this->folder)); + + // move the file out of the shared folder + $root = $this->rootView->getRoot(); + $this->rootView->chroot('/' . self::TEST_ENCRYPTION_HOOKS_USER1 . '/files/'); + $this->rootView->rename($this->filename, '/' . $this->folder . '/' . $this->filename); + $this->rootView->chroot($root); + + $this->assertFalse($this->rootView->file_exists('/' . self::TEST_ENCRYPTION_HOOKS_USER1 . '/files/' . $this->filename)); + $this->assertTrue($this->rootView->file_exists('/' . self::TEST_ENCRYPTION_HOOKS_USER1 . '/files/' . $this->folder . '/' . $this->filename)); + + // keys should be renamed too + $this->assertFalse($this->rootView->file_exists( + '/' . self::TEST_ENCRYPTION_HOOKS_USER1 . '/files_encryption/share-keys/' + . $this->filename . '.' . self::TEST_ENCRYPTION_HOOKS_USER1 . '.shareKey')); + $this->assertFalse($this->rootView->file_exists( + '/' . self::TEST_ENCRYPTION_HOOKS_USER1 . '/files_encryption/keyfiles/' + . $this->filename . '.key')); + + $this->assertTrue($this->rootView->file_exists( + '/' . self::TEST_ENCRYPTION_HOOKS_USER1 . '/files_encryption/share-keys/' . $this->folder . '/' + . $this->filename . '.' . self::TEST_ENCRYPTION_HOOKS_USER1 . '.shareKey')); + $this->assertTrue($this->rootView->file_exists( + '/' . self::TEST_ENCRYPTION_HOOKS_USER1 . '/files_encryption/keyfiles/' . $this->folder . '/' + . $this->filename . '.key')); + + // cleanup + $this->rootView->unlink('/' . self::TEST_ENCRYPTION_HOOKS_USER1 . '/files/' . $this->folder); + } + } diff --git a/apps/files_encryption/tests/share.php b/apps/files_encryption/tests/share.php index 46a21dd55cd..be56968ac09 100755 --- a/apps/files_encryption/tests/share.php +++ b/apps/files_encryption/tests/share.php @@ -127,6 +127,7 @@ class Test_Encryption_Share extends \PHPUnit_Framework_TestCase { \OC_User::deleteUser(\Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER4); } + /** * @medium * @param bool $withTeardown @@ -498,6 +499,7 @@ class Test_Encryption_Share extends \PHPUnit_Framework_TestCase { } } + function testPublicShareFile() { // login as admin \Test_Encryption_Util::loginHelper(\Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER1); @@ -864,6 +866,13 @@ class Test_Encryption_Share extends \PHPUnit_Framework_TestCase { \OCA\Encryption\Helper::adminDisableRecovery('test123'); $this->assertEquals(0, \OC::$server->getAppConfig()->getValue('files_encryption', 'recoveryAdminEnabled')); + + //clean up, reset passwords + \OC_User::setPassword(\Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER2, \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER2, 'test123'); + $params = array('uid' => \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER2, + 'password' => \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER2, + 'recoveryPassword' => 'test123'); + \OCA\Encryption\Hooks::setPassphrase($params); } /** @@ -947,4 +956,65 @@ class Test_Encryption_Share extends \PHPUnit_Framework_TestCase { $this->view->chroot('/'); } + + /** + * @brief test moving a shared file out of the Shared folder + */ + function testRename() { + + // login as admin + \Test_Encryption_Util::loginHelper(\Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER1); + + // save file with content + $cryptedFile = file_put_contents('crypt:///' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER1 . '/files/' . $this->filename, $this->dataShort); + + // test that data was successfully written + $this->assertTrue(is_int($cryptedFile)); + + // get the file info from previous created file + $fileInfo = $this->view->getFileInfo( + '/' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER1 . '/files/' . $this->filename); + + // check if we have a valid file info + $this->assertTrue($fileInfo instanceof \OC\Files\FileInfo); + + // share the file + \OCP\Share::shareItem('file', $fileInfo['fileid'], \OCP\Share::SHARE_TYPE_USER, \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER2, OCP\PERMISSION_ALL); + + // check if share key for user2exists + $this->assertTrue($this->view->file_exists( + '/' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER1 . '/files_encryption/share-keys/' + . $this->filename . '.' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER2 . '.shareKey')); + + + // login as user2 + \Test_Encryption_Util::loginHelper(\Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER2); + + $this->assertTrue($this->view->file_exists('/' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER2 . '/files/Shared/' . $this->filename)); + + // get file contents + $retrievedCryptedFile = $this->view->file_get_contents( + '/' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER2 . '/files/Shared/' . $this->filename); + + // check if data is the same as we previously written + $this->assertEquals($this->dataShort, $retrievedCryptedFile); + + // move the file out of the shared folder + $this->view->rename('/' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER2 . '/files/Shared/' . $this->filename, + '/' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER2 . '/files/' . $this->filename); + + // check if we can read the moved file + $retrievedRenamedFile = $this->view->file_get_contents( + '/' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER2 . '/files/' . $this->filename); + + // check if data is the same as we previously written + $this->assertEquals($this->dataShort, $retrievedRenamedFile); + + // the owners file should be deleted + $this->assertFalse($this->view->file_exists('/' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER1 . '/files/' . $this->filename)); + + // cleanup + $this->view->unlink('/' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER2 . '/files/' . $this->filename); + } + } diff --git a/apps/files_encryption/tests/util.php b/apps/files_encryption/tests/util.php index f70e30c4d73..203ba55dbfd 100755 --- a/apps/files_encryption/tests/util.php +++ b/apps/files_encryption/tests/util.php @@ -344,7 +344,7 @@ class Test_Encryption_Util extends \PHPUnit_Framework_TestCase { // check if mtime and etags unchanged $this->assertEquals($fileInfoEncrypted['mtime'], $fileInfoUnencrypted['mtime']); - $this->assertEquals($fileInfoEncrypted['etag'], $fileInfoUnencrypted['etag']); + $this->assertSame($fileInfoEncrypted['etag'], $fileInfoUnencrypted['etag']); $this->view->unlink($this->userId . '/files/' . $filename); } @@ -373,7 +373,7 @@ class Test_Encryption_Util extends \PHPUnit_Framework_TestCase { // check if mtime and etags unchanged $this->assertEquals($fileInfoEncrypted['mtime'], $fileInfoUnencrypted['mtime']); - $this->assertEquals($fileInfoEncrypted['etag'], $fileInfoUnencrypted['etag']); + $this->assertSame($fileInfoEncrypted['etag'], $fileInfoUnencrypted['etag']); // file should no longer be encrypted $this->assertEquals(0, $fileInfoUnencrypted['encrypted']); diff --git a/apps/files_external/lib/config.php b/apps/files_external/lib/config.php index b2109e5eacd..43275d36c06 100755 --- a/apps/files_external/lib/config.php +++ b/apps/files_external/lib/config.php @@ -277,15 +277,21 @@ class OC_Mount_Config { $mountType, $applicable, $isPersonal = false) { + $backends = self::getBackends(); $mountPoint = OC\Files\Filesystem::normalizePath($mountPoint); if ($mountPoint === '' || $mountPoint === '/' || $mountPoint == '/Shared') { // can't mount at root or "Shared" folder return false; } + + if (!isset($backends[$class])) { + // invalid backend + return false; + } if ($isPersonal) { // Verify that the mount point applies for the current user // Prevent non-admin users from mounting local storage - if ($applicable != OCP\User::getUser() || $class == '\OC\Files\Storage\Local') { + if ($applicable !== OCP\User::getUser() || strtolower($class) === '\oc\files\storage\local') { return false; } $mountPoint = '/'.$applicable.'/files/'.ltrim($mountPoint, '/'); @@ -353,7 +359,8 @@ class OC_Mount_Config { $jsonFile = OC_User::getHome(OCP\User::getUser()).'/mount.json'; } else { $phpFile = OC::$SERVERROOT.'/config/mount.php'; - $jsonFile = \OC_Config::getValue("mount_file", \OC::$SERVERROOT . "/data/mount.json"); + $datadir = \OC_Config::getValue('datadirectory', \OC::$SERVERROOT . '/data/'); + $jsonFile = \OC_Config::getValue('mount_file', $datadir . '/mount.json'); } if (is_file($jsonFile)) { $mountPoints = json_decode(file_get_contents($jsonFile), true); @@ -379,7 +386,8 @@ class OC_Mount_Config { if ($isPersonal) { $file = OC_User::getHome(OCP\User::getUser()).'/mount.json'; } else { - $file = \OC_Config::getValue("mount_file", \OC::$SERVERROOT . "/data/mount.json"); + $datadir = \OC_Config::getValue('datadirectory', \OC::$SERVERROOT . '/data/'); + $file = \OC_Config::getValue('mount_file', $datadir . '/mount.json'); } $content = json_encode($data); @file_put_contents($file, $content); diff --git a/apps/files_external/tests/mountconfig.php b/apps/files_external/tests/mountconfig.php index 941aec680bb..24ebcf51346 100644 --- a/apps/files_external/tests/mountconfig.php +++ b/apps/files_external/tests/mountconfig.php @@ -48,4 +48,29 @@ class Test_Mount_Config extends \PHPUnit_Framework_TestCase { $this->assertEquals(false, OC_Mount_Config::addMountPoint('/Shared', $storageClass, array(), $mountType, $applicable, $isPersonal)); } + + public function testAddMountPointSingleUser() { + \OC_User::setUserId('test'); + $mountType = 'user'; + $applicable = 'test'; + $isPersonal = true; + // local + $this->assertEquals(false, OC_Mount_Config::addMountPoint('/ext', '\OC\Files\storage\local', array(), $mountType, $applicable, $isPersonal)); + // non-local + // FIXME: can't test this yet as the class (write operation) is not mockable + // $this->assertEquals(true, OC_Mount_Config::addMountPoint('/ext', '\OC\Files\Storage\SFTP', array(), $mountType, $applicable, $isPersonal)); + + } + + public function testAddMountPointUnexistClass() { + \OC_User::setUserId('test'); + $storageClass = 'Unexist_Storage'; + $mountType = 'user'; + $applicable = 'test'; + $isPersonal = true; + // local + // non-local + $this->assertEquals(false, OC_Mount_Config::addMountPoint('/ext', $storageClass, array(), $mountType, $applicable, $isPersonal)); + + } } diff --git a/apps/files_sharing/lib/api.php b/apps/files_sharing/lib/api.php index 19a2d22b068..0ba58aa896a 100644 --- a/apps/files_sharing/lib/api.php +++ b/apps/files_sharing/lib/api.php @@ -172,12 +172,12 @@ class Api { // workaround because folders are named 'dir' in this context $itemType = $file['type'] === 'file' ? 'file' : 'folder'; $share = \OCP\Share::getItemShared($itemType, $file['fileid']); - $receivedFrom = \OCP\Share::getItemSharedWithBySource($itemType, $file['fileid']); - if ($receivedFrom) { - $share['received_from'] = $receivedFrom['uid_owner']; - $share['received_from_displayname'] = \OCP\User::getDisplayName($receivedFrom['uid_owner']); - } - if ($share) { + if($share) { + $receivedFrom = \OCP\Share::getItemSharedWithBySource($itemType, $file['fileid']); + if ($receivedFrom) { + $share['received_from'] = $receivedFrom['uid_owner']; + $share['received_from_displayname'] = \OCP\User::getDisplayName($receivedFrom['uid_owner']); + } $result = array_merge($result, $share); } } diff --git a/apps/files_sharing/lib/cache.php b/apps/files_sharing/lib/cache.php index 27602f69abf..10f2182655f 100644 --- a/apps/files_sharing/lib/cache.php +++ b/apps/files_sharing/lib/cache.php @@ -131,20 +131,16 @@ class Shared_Cache extends Cache { foreach ($files as &$file) { $file['mimetype'] = $this->getMimetype($file['mimetype']); $file['mimepart'] = $this->getMimetype($file['mimepart']); + $file['usersPath'] = 'files/Shared/' . ltrim($file['path'], '/'); } return $files; } else { - if ($cache = $this->getSourceCache($folder)) { + $cache = $this->getSourceCache($folder); + if ($cache) { $parent = $this->storage->getFile($folder); $sourceFolderContent = $cache->getFolderContents($this->files[$folder]); foreach ($sourceFolderContent as $key => $c) { - $ownerPathParts = explode('/', \OC_Filesystem::normalizePath($c['path'])); - $userPathParts = explode('/', \OC_Filesystem::normalizePath($folder)); - $usersPath = 'files/Shared/'.$userPathParts[1]; - foreach (array_slice($ownerPathParts, 3) as $part) { - $usersPath .= '/'.$part; - } - $sourceFolderContent[$key]['usersPath'] = $usersPath; + $sourceFolderContent[$key]['usersPath'] = 'files/Shared/' . $folder . '/' . $c['name']; $sourceFolderContent[$key]['uid_owner'] = $parent['uid_owner']; $sourceFolderContent[$key]['displayname_owner'] = $parent['uid_owner']; } diff --git a/apps/files_sharing/public.php b/apps/files_sharing/public.php index e7a5f5024b8..fe61dd4d5a0 100644 --- a/apps/files_sharing/public.php +++ b/apps/files_sharing/public.php @@ -32,7 +32,8 @@ function determineIcon($file, $sharingRoot, $sharingToken) { if($file['isPreviewAvailable']) { return OCP\publicPreview_icon($relativePath, $sharingToken) . '&c=' . $file['etag']; } - return OCP\mimetype_icon($file['mimetype']); + $icon = OCP\mimetype_icon($file['mimetype']); + return substr($icon, 0, -3) . 'svg'; } if (isset($_GET['t'])) { diff --git a/apps/files_sharing/templates/authenticate.php b/apps/files_sharing/templates/authenticate.php index 928be93fc96..19b1fb27630 100644 --- a/apps/files_sharing/templates/authenticate.php +++ b/apps/files_sharing/templates/authenticate.php @@ -8,7 +8,10 @@ <?php endif; ?> <p class="infield"> <label for="password" class="infield"><?php p($l->t('Password')); ?></label> - <input type="password" name="password" id="password" placeholder="" value="" autofocus /> + <input type="password" name="password" id="password" + placeholder="" value="" + autocomplete="off" autocapitalize="off" autocorrect="off" + autofocus /> <input type="submit" value="" class="svg icon icon-confirm" /> </p> </fieldset> diff --git a/apps/files_sharing/tests/api.php b/apps/files_sharing/tests/api.php index 1278e0c4d1f..0d3d9b98b2e 100644 --- a/apps/files_sharing/tests/api.php +++ b/apps/files_sharing/tests/api.php @@ -33,13 +33,16 @@ class Test_Files_Sharing_Api extends Test_Files_Sharing_Base { parent::setUp(); $this->folder = '/folder_share_api_test'; + $this->subfolder = '/subfolder_share_api_test'; $this->filename = 'share-api-test.txt'; // save file with content $this->view->file_put_contents($this->filename, $this->data); $this->view->mkdir($this->folder); + $this->view->mkdir($this->folder . '/' . $this->subfolder); $this->view->file_put_contents($this->folder.'/'.$this->filename, $this->data); + $this->view->file_put_contents($this->folder.'/' . $this->subfolder . '/' .$this->filename, $this->data); } function tearDown() { @@ -287,6 +290,71 @@ class Test_Files_Sharing_Api extends Test_Files_Sharing_Base { } /** + * @brief share a folder, than reshare a file within the shared folder and check if we construct the correct path + * @medium + */ + function testGetShareFromFolderReshares() { + + self::loginHelper(self::TEST_FILES_SHARING_API_USER1); + + $fileInfo1 = $this->view->getFileInfo($this->folder); + $fileInfo2 = $this->view->getFileInfo($this->folder.'/'.$this->filename); + $fileInfo3 = $this->view->getFileInfo($this->folder.'/' . $this->subfolder . '/' .$this->filename); + + // share root folder to user2 + $result = \OCP\Share::shareItem('folder', $fileInfo1['fileid'], \OCP\Share::SHARE_TYPE_USER, + \Test_Files_Sharing_Api::TEST_FILES_SHARING_API_USER2, 31); + + // share was successful? + $this->assertTrue($result); + + // login as user2 + self::loginHelper(self::TEST_FILES_SHARING_API_USER2); + + // share file in root folder + $result = \OCP\Share::shareItem('file', $fileInfo2['fileid'], \OCP\Share::SHARE_TYPE_LINK, null, 1); + // share was successful? + $this->assertTrue(is_string($result)); + + // share file in subfolder + $result = \OCP\Share::shareItem('file', $fileInfo3['fileid'], \OCP\Share::SHARE_TYPE_LINK, null, 1); + // share was successful? + $this->assertTrue(is_string($result)); + + $testValues=array( + array('query' => 'Shared/' . $this->folder, + 'expectedResult' => '/Shared' . $this->folder . '/' . $this->filename), + array('query' => 'Shared/' . $this->folder . $this->subfolder, + 'expectedResult' => '/Shared' . $this->folder . $this->subfolder . '/' . $this->filename), + ); + foreach ($testValues as $value) { + + $_GET['path'] = $value['query']; + $_GET['subfiles'] = 'true'; + + $result = Share\Api::getAllShares(array()); + + $this->assertTrue($result->succeeded()); + + // test should return one share within $this->folder + $data = $result->getData(); + + $this->assertEquals($value['expectedResult'], $data[0]['path']); + } + + // cleanup + + \OCP\Share::unshare('file', $fileInfo2['fileid'], \OCP\Share::SHARE_TYPE_LINK, null); + \OCP\Share::unshare('file', $fileInfo3['fileid'], \OCP\Share::SHARE_TYPE_LINK, null); + + self::loginHelper(self::TEST_FILES_SHARING_API_USER1); + + \OCP\Share::unshare('folder', $fileInfo1['fileid'], \OCP\Share::SHARE_TYPE_USER, + \Test_Files_Sharing_Api::TEST_FILES_SHARING_API_USER2); + + } + + /** * @medium */ function testGetShareFromUnknownId() { diff --git a/apps/files_sharing/tests/base.php b/apps/files_sharing/tests/base.php index 3e283271f5d..d44972d01f1 100644 --- a/apps/files_sharing/tests/base.php +++ b/apps/files_sharing/tests/base.php @@ -43,6 +43,7 @@ abstract class Test_Files_Sharing_Base extends \PHPUnit_Framework_TestCase { */ public $view; public $folder; + public $subfolder; public static function setUpBeforeClass() { // reset backend diff --git a/apps/files_sharing/tests/cache.php b/apps/files_sharing/tests/cache.php index 56a51c83f6b..a75e1860527 100644 --- a/apps/files_sharing/tests/cache.php +++ b/apps/files_sharing/tests/cache.php @@ -2,8 +2,9 @@ /** * ownCloud * - * @author Vincent Petry + * @author Vincent Petry, Bjoern Schiessle * @copyright 2014 Vincent Petry <pvince81@owncloud.com> + * 2014 Bjoern Schiessle <schiessle@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 @@ -23,13 +24,19 @@ require_once __DIR__ . '/base.php'; class Test_Files_Sharing_Cache extends Test_Files_Sharing_Base { + /** + * @var OC_FilesystemView + */ + public $user2View; + function setUp() { parent::setUp(); self::loginHelper(self::TEST_FILES_SHARING_API_USER1); + $this->user2View = new \OC\Files\View('/'. self::TEST_FILES_SHARING_API_USER2 . '/files'); + // prepare user1's dir structure - $textData = "dummy file data\n"; $this->view->mkdir('container'); $this->view->mkdir('container/shareddir'); $this->view->mkdir('container/shareddir/subdir'); @@ -115,20 +122,128 @@ class Test_Files_Sharing_Cache extends Test_Files_Sharing_Base { $this->verifyFiles($check, $results); } + function testGetFolderContentsInRoot() { + $results = $this->user2View->getDirectoryContent('/Shared/'); + + $this->verifyFiles( + array( + array( + 'name' => 'shareddir', + 'path' => '/shareddir', + 'mimetype' => 'httpd/unix-directory', + 'usersPath' => 'files/Shared/shareddir' + ), + array( + 'name' => 'shared single file.txt', + 'path' => '/shared single file.txt', + 'mimetype' => 'text/plain', + 'usersPath' => 'files/Shared/shared single file.txt' + ), + ), + $results + ); + } + + function testGetFolderContentsInSubdir() { + $results = $this->user2View->getDirectoryContent('/Shared/shareddir'); + + $this->verifyFiles( + array( + array( + 'name' => 'bar.txt', + 'path' => 'files/container/shareddir/bar.txt', + 'mimetype' => 'text/plain', + 'usersPath' => 'files/Shared/shareddir/bar.txt' + ), + array( + 'name' => 'emptydir', + 'path' => 'files/container/shareddir/emptydir', + 'mimetype' => 'httpd/unix-directory', + 'usersPath' => 'files/Shared/shareddir/emptydir' + ), + array( + 'name' => 'subdir', + 'path' => 'files/container/shareddir/subdir', + 'mimetype' => 'httpd/unix-directory', + 'usersPath' => 'files/Shared/shareddir/subdir' + ), + ), + $results + ); + } + + function testGetFolderContentsWhenSubSubdirShared() { + self::loginHelper(self::TEST_FILES_SHARING_API_USER1); + + $fileinfo = $this->view->getFileInfo('container/shareddir/subdir'); + \OCP\Share::shareItem('folder', $fileinfo['fileid'], \OCP\Share::SHARE_TYPE_USER, + self::TEST_FILES_SHARING_API_USER3, 31); + + self::loginHelper(self::TEST_FILES_SHARING_API_USER3); + + $thirdView = new \OC\Files\View('/' . self::TEST_FILES_SHARING_API_USER3 . '/files'); + $results = $thirdView->getDirectoryContent('/Shared/subdir'); + + $this->verifyFiles( + array( + array( + 'name' => 'another too.txt', + 'path' => 'files/container/shareddir/subdir/another too.txt', + 'mimetype' => 'text/plain', + 'usersPath' => 'files/Shared/subdir/another too.txt' + ), + array( + 'name' => 'another.txt', + 'path' => 'files/container/shareddir/subdir/another.txt', + 'mimetype' => 'text/plain', + 'usersPath' => 'files/Shared/subdir/another.txt' + ), + array( + 'name' => 'not a text file.xml', + 'path' => 'files/container/shareddir/subdir/not a text file.xml', + 'mimetype' => 'application/xml', + 'usersPath' => 'files/Shared/subdir/not a text file.xml' + ), + ), + $results + ); + + self::loginHelper(self::TEST_FILES_SHARING_API_USER1); + + \OCP\Share::unshare('folder', $fileinfo['fileid'], \OCP\Share::SHARE_TYPE_USER, + self::TEST_FILES_SHARING_API_USER3); + } + /** - * Checks that all provided attributes exist in the files list, - * only the values provided in $examples will be used to check against - * the file list. The files order also needs to be the same. + * Check if 'results' contains the expected 'examples' only. * * @param array $examples array of example files - * @param array $files array of files + * @param array $results array of files */ - private function verifyFiles($examples, $files) { - $this->assertEquals(count($examples), count($files)); - foreach ($files as $i => $file) { - foreach ($examples[$i] as $key => $value) { - $this->assertEquals($value, $file[$key]); + private function verifyFiles($examples, $results) { + $this->assertEquals(count($examples), count($results)); + + foreach ($examples as $example) { + foreach ($results as $key => $result) { + if ($result['name'] === $example['name']) { + $this->verifyKeys($example, $result); + unset($results[$key]); + break; + } } } + $this->assertTrue(empty($results)); } + + /** + * @brief verify if each value from the result matches the expected result + * @param array $example array with the expected results + * @param array $result array with the results + */ + private function verifyKeys($example, $result) { + foreach ($example as $key => $value) { + $this->assertEquals($value, $result[$key]); + } + } + } diff --git a/apps/files_trashbin/js/filelist.js b/apps/files_trashbin/js/filelist.js index f42abb6d029..a88459b0a9a 100644 --- a/apps/files_trashbin/js/filelist.js +++ b/apps/files_trashbin/js/filelist.js @@ -1,3 +1,4 @@ +/* globals OC, FileList, t */ // override reload with own ajax call FileList.reload = function(){ FileList.showMask(); @@ -17,7 +18,36 @@ FileList.reload = function(){ FileList.reloadCallback(result); } }); -} +}; + +FileList.appName = t('files_trashbin', 'Deleted files'); + +FileList._deletedRegExp = new RegExp(/^(.+)\.d[0-9]+$/); + +/** + * Convert a file name in the format filename.d12345 to the real file name. + * This will use basename. + * The name will not be changed if it has no ".d12345" suffix. + * @param name file name + * @return converted file name + */ +FileList.getDeletedFileName = function(name) { + name = OC.basename(name); + var match = FileList._deletedRegExp.exec(name); + if (match && match.length > 1) { + name = match[1]; + } + return name; +}; +var oldSetCurrentDir = FileList.setCurrentDir; +FileList.setCurrentDir = function(targetDir) { + oldSetCurrentDir.apply(this, arguments); + + var baseDir = OC.basename(targetDir); + if (baseDir !== '') { + FileList.setPageTitle(FileList.getDeletedFileName(baseDir)); + } +}; FileList.linkTo = function(dir){ return OC.linkTo('files_trashbin', 'index.php')+"?dir="+ encodeURIComponent(dir).replace(/%2F/g, '/'); diff --git a/apps/user_ldap/group_ldap.php b/apps/user_ldap/group_ldap.php index 32e2cec5960..cef9ca3c4cf 100644 --- a/apps/user_ldap/group_ldap.php +++ b/apps/user_ldap/group_ldap.php @@ -61,8 +61,7 @@ class GROUP_LDAP extends BackendUtility implements \OCP\GroupInterface { return false; } //usually, LDAP attributes are said to be case insensitive. But there are exceptions of course. - $members = $this->access->readAttribute($dn_group, - $this->access->connection->ldapGroupMemberAssocAttr); + $members = array_keys($this->_groupMembers($dn_group)); if(!$members) { $this->access->connection->writeToCache('inGroup'.$uid.':'.$gid, false); return false; @@ -89,6 +88,39 @@ class GROUP_LDAP extends BackendUtility implements \OCP\GroupInterface { return $isInGroup; } + private function _groupMembers($dnGroup, &$seen = null) { + if ($seen === null) { + $seen = array(); + } + $allMembers = array(); + if (array_key_exists($dnGroup, $seen)) { + // avoid loops + return array(); + } + // used extensively in cron job, caching makes sense for nested groups + $cacheKey = '_groupMembers'.$dnGroup; + if($this->access->connection->isCached($cacheKey)) { + return $this->access->connection->getFromCache($cacheKey); + } + $seen[$dnGroup] = 1; + $members = $this->access->readAttribute($dnGroup, $this->access->connection->ldapGroupMemberAssocAttr, + $this->access->connection->ldapGroupFilter); + if (is_array($members)) { + foreach ($members as $memberDN) { + $allMembers[$memberDN] = 1; + $nestedGroups = $this->access->connection->ldapNestedGroups; + if (!empty($nestedGroups)) { + $subMembers = $this->_groupMembers($memberDN, $seen); + if ($subMembers) { + $allMembers = array_merge($allMembers, $subMembers); + } + } + } + } + $this->access->connection->writeToCache($cacheKey, $allMembers); + return $allMembers; + } + /** * @brief Get all groups a user belongs to * @param $uid Name of the user @@ -124,18 +156,45 @@ class GROUP_LDAP extends BackendUtility implements \OCP\GroupInterface { $uid = $userDN; } - $filter = $this->access->combineFilterWithAnd(array( - $this->access->connection->ldapGroupFilter, - $this->access->connection->ldapGroupMemberAssocAttr.'='.$uid - )); - $groups = $this->access->fetchListOfGroups($filter, - array($this->access->connection->ldapGroupDisplayName, 'dn')); + $groups = array_values($this->getGroupsByMember($uid)); $groups = array_unique($this->access->ownCloudGroupNames($groups), SORT_LOCALE_STRING); $this->access->connection->writeToCache($cacheKey, $groups); return $groups; } + private function getGroupsByMember($dn, &$seen = null) { + if ($seen === null) { + $seen = array(); + } + $allGroups = array(); + if (array_key_exists($dn, $seen)) { + // avoid loops + return array(); + } + $seen[$dn] = true; + $filter = $this->access->combineFilterWithAnd(array( + $this->access->connection->ldapGroupFilter, + $this->access->connection->ldapGroupMemberAssocAttr.'='.$dn + )); + $groups = $this->access->fetchListOfGroups($filter, + array($this->access->connection->ldapGroupDisplayName, 'dn')); + if (is_array($groups)) { + foreach ($groups as $groupobj) { + $groupDN = $groupobj['dn']; + $allGroups[$groupDN] = $groupobj; + $nestedGroups = $this->access->connection->ldapNestedGroups; + if (!empty($nestedGroups)) { + $supergroups = $this->getGroupsByMember($groupDN, $seen); + if (is_array($supergroups) && (count($supergroups)>0)) { + $allGroups = array_merge($allGroups, $supergroups); + } + } + } + } + return $allGroups; + } + /** * @brief get a list of all users in a group * @returns array with user ids @@ -172,8 +231,7 @@ class GROUP_LDAP extends BackendUtility implements \OCP\GroupInterface { return array(); } - $members = $this->access->readAttribute($groupDN, - $this->access->connection->ldapGroupMemberAssocAttr); + $members = array_keys($this->_groupMembers($groupDN)); if(!$members) { //in case users could not be retrieved, return empty resultset $this->access->connection->writeToCache($cachekey, array()); diff --git a/apps/user_ldap/lib/configuration.php b/apps/user_ldap/lib/configuration.php index 954d0501fad..612a623e910 100644 --- a/apps/user_ldap/lib/configuration.php +++ b/apps/user_ldap/lib/configuration.php @@ -76,6 +76,7 @@ class Configuration { 'ldapExpertUUIDUserAttr' => null, 'ldapExpertUUIDGroupAttr' => null, 'lastJpegPhotoLookup' => null, + 'ldapNestedGroups' => false, ); /** @@ -342,6 +343,7 @@ class Configuration { 'ldap_expert_uuid_group_attr' => '', 'has_memberof_filter_support' => 0, 'last_jpegPhoto_lookup' => 0, + 'ldap_nested_groups' => 0, ); } @@ -393,6 +395,7 @@ class Configuration { 'ldap_expert_uuid_group_attr' => 'ldapExpertUUIDGroupAttr', 'has_memberof_filter_support' => 'hasMemberOfFilterSupport', 'last_jpegPhoto_lookup' => 'lastJpegPhotoLookup', + 'ldap_nested_groups' => 'ldapNestedGroups', ); return $array; } diff --git a/apps/user_ldap/templates/settings.php b/apps/user_ldap/templates/settings.php index 3ccc7a860f5..79c4ae224c3 100644 --- a/apps/user_ldap/templates/settings.php +++ b/apps/user_ldap/templates/settings.php @@ -36,6 +36,7 @@ <p><label for="ldap_base_groups"><?php p($l->t('Base Group Tree'));?></label><textarea id="ldap_base_groups" name="ldap_base_groups" placeholder="<?php p($l->t('One Group Base DN per line'));?>" data-default="<?php p($_['ldap_base_groups_default']); ?>" title="<?php p($l->t('Base Group Tree'));?>"></textarea></p> <p><label for="ldap_attributes_for_group_search"><?php p($l->t('Group Search Attributes'));?></label><textarea id="ldap_attributes_for_group_search" name="ldap_attributes_for_group_search" placeholder="<?php p($l->t('Optional; one attribute per line'));?>" data-default="<?php p($_['ldap_attributes_for_group_search_default']); ?>" title="<?php p($l->t('Group Search Attributes'));?>"></textarea></p> <p><label for="ldap_group_member_assoc_attribute"><?php p($l->t('Group-Member association'));?></label><select id="ldap_group_member_assoc_attribute" name="ldap_group_member_assoc_attribute" data-default="<?php p($_['ldap_group_member_assoc_attribute_default']); ?>" ><option value="uniqueMember"<?php if (isset($_['ldap_group_member_assoc_attribute']) && ($_['ldap_group_member_assoc_attribute'] === 'uniqueMember')) p(' selected'); ?>>uniqueMember</option><option value="memberUid"<?php if (isset($_['ldap_group_member_assoc_attribute']) && ($_['ldap_group_member_assoc_attribute'] === 'memberUid')) p(' selected'); ?>>memberUid</option><option value="member"<?php if (isset($_['ldap_group_member_assoc_attribute']) && ($_['ldap_group_member_assoc_attribute'] === 'member')) p(' selected'); ?>>member (AD)</option></select></p> + <p><label for="ldap_nested_groups"><?php p($l->t('Nested Groups'));?></label><input type="checkbox" id="ldap_nested_groups" name="ldap_nested_groups" value="1" data-default="<?php p($_['ldap_nested_groups_default']); ?>" title="<?php p($l->t('When switched on, groups that contain groups are supported. (Only works if the group member attribute contains DNs.)'));?>" /></p> </div> <h3><?php p($l->t('Special Attributes'));?></h3> <div> diff --git a/apps/user_ldap/tests/access.php b/apps/user_ldap/tests/access.php new file mode 100644 index 00000000000..9beb2b97336 --- /dev/null +++ b/apps/user_ldap/tests/access.php @@ -0,0 +1,71 @@ +<?php +/** +* ownCloud +* +* @author Arthur Schiwon +* @copyright 2013 Arthur Schiwon blizzz@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\user_ldap\tests; + +use \OCA\user_ldap\lib\Access; +use \OCA\user_ldap\lib\Connection; +use \OCA\user_ldap\lib\ILDAPWrapper; + +class Test_Access extends \PHPUnit_Framework_TestCase { + private function getConnecterAndLdapMock() { + static $conMethods; + static $accMethods; + + if(is_null($conMethods) || is_null($accMethods)) { + $conMethods = get_class_methods('\OCA\user_ldap\lib\Connection'); + $accMethods = get_class_methods('\OCA\user_ldap\lib\Access'); + } + $lw = $this->getMock('\OCA\user_ldap\lib\ILDAPWrapper'); + $connector = $this->getMock('\OCA\user_ldap\lib\Connection', + $conMethods, + array($lw, null, null)); + + return array($lw, $connector); + } + + public function testEscapeFilterPartValidChars() { + list($lw, $con) = $this->getConnecterAndLdapMock(); + $access = new Access($con, $lw); + + $input = 'okay'; + $this->assertTrue($input === $access->escapeFilterPart($input)); + } + + public function testEscapeFilterPartEscapeWildcard() { + list($lw, $con) = $this->getConnecterAndLdapMock(); + $access = new Access($con, $lw); + + $input = '*'; + $expected = '\\\\*'; + $this->assertTrue($expected === $access->escapeFilterPart($input)); + } + + public function testEscapeFilterPartEscapeWildcard2() { + list($lw, $con) = $this->getConnecterAndLdapMock(); + $access = new Access($con, $lw); + + $input = 'foo*bar'; + $expected = 'foo\\\\*bar'; + $this->assertTrue($expected === $access->escapeFilterPart($input)); + } +}
\ No newline at end of file diff --git a/apps/user_ldap/tests/user_ldap.php b/apps/user_ldap/tests/user_ldap.php index 9193a005ae5..8c8d85b3c33 100644 --- a/apps/user_ldap/tests/user_ldap.php +++ b/apps/user_ldap/tests/user_ldap.php @@ -83,6 +83,12 @@ class Test_User_Ldap_Direct extends \PHPUnit_Framework_TestCase { * @return void */ private function prepareAccessForCheckPassword(&$access) { + $access->expects($this->once()) + ->method('escapeFilterPart') + ->will($this->returnCallback(function($uid) { + return $uid; + })); + $access->connection->expects($this->any()) ->method('__get') ->will($this->returnCallback(function($name) { @@ -116,17 +122,34 @@ class Test_User_Ldap_Direct extends \PHPUnit_Framework_TestCase { })); } - public function testCheckPassword() { + public function testCheckPasswordUidReturn() { $access = $this->getAccessMock(); + $this->prepareAccessForCheckPassword($access); $backend = new UserLDAP($access); \OC_User::useBackend($backend); $result = $backend->checkPassword('roland', 'dt19'); $this->assertEquals('gunslinger', $result); + } + + public function testCheckPasswordWrongPassword() { + $access = $this->getAccessMock(); + + $this->prepareAccessForCheckPassword($access); + $backend = new UserLDAP($access); + \OC_User::useBackend($backend); $result = $backend->checkPassword('roland', 'wrong'); $this->assertFalse($result); + } + + public function testCheckPasswordWrongUser() { + $access = $this->getAccessMock(); + + $this->prepareAccessForCheckPassword($access); + $backend = new UserLDAP($access); + \OC_User::useBackend($backend); $result = $backend->checkPassword('mallory', 'evil'); $this->assertFalse($result); @@ -140,9 +163,23 @@ class Test_User_Ldap_Direct extends \PHPUnit_Framework_TestCase { $result = \OCP\User::checkPassword('roland', 'dt19'); $this->assertEquals('gunslinger', $result); + } + + public function testCheckPasswordPublicAPIWrongPassword() { + $access = $this->getAccessMock(); + $this->prepareAccessForCheckPassword($access); + $backend = new UserLDAP($access); + \OC_User::useBackend($backend); $result = \OCP\User::checkPassword('roland', 'wrong'); $this->assertFalse($result); + } + + public function testCheckPasswordPublicAPIWrongUser() { + $access = $this->getAccessMock(); + $this->prepareAccessForCheckPassword($access); + $backend = new UserLDAP($access); + \OC_User::useBackend($backend); $result = \OCP\User::checkPassword('mallory', 'evil'); $this->assertFalse($result); @@ -154,6 +191,12 @@ class Test_User_Ldap_Direct extends \PHPUnit_Framework_TestCase { * @return void */ private function prepareAccessForGetUsers(&$access) { + $access->expects($this->once()) + ->method('escapeFilterPart') + ->will($this->returnCallback(function($search) { + return $search; + })); + $access->expects($this->any()) ->method('getFilterPartForUserSearch') ->will($this->returnCallback(function($search) { @@ -191,28 +234,52 @@ class Test_User_Ldap_Direct extends \PHPUnit_Framework_TestCase { ->will($this->returnArgument(0)); } - public function testGetUsers() { + public function testGetUsersNoParam() { $access = $this->getAccessMock(); $this->prepareAccessForGetUsers($access); $backend = new UserLDAP($access); $result = $backend->getUsers(); $this->assertEquals(3, count($result)); + } + + public function testGetUsersLimitOffset() { + $access = $this->getAccessMock(); + $this->prepareAccessForGetUsers($access); + $backend = new UserLDAP($access); $result = $backend->getUsers('', 1, 2); $this->assertEquals(1, count($result)); + } + + public function testGetUsersLimitOffset2() { + $access = $this->getAccessMock(); + $this->prepareAccessForGetUsers($access); + $backend = new UserLDAP($access); $result = $backend->getUsers('', 2, 1); $this->assertEquals(2, count($result)); + } + + public function testGetUsersSearchWithResult() { + $access = $this->getAccessMock(); + $this->prepareAccessForGetUsers($access); + $backend = new UserLDAP($access); $result = $backend->getUsers('yo'); $this->assertEquals(2, count($result)); + } + + public function testGetUsersSearchEmptyResult() { + $access = $this->getAccessMock(); + $this->prepareAccessForGetUsers($access); + $backend = new UserLDAP($access); $result = $backend->getUsers('nix'); $this->assertEquals(0, count($result)); } - public function testGetUsersViaAPI() { + public function testGetUsersViaAPINoParam() { $access = $this->getAccessMock(); $this->prepareAccessForGetUsers($access); $backend = new UserLDAP($access); @@ -220,15 +287,43 @@ class Test_User_Ldap_Direct extends \PHPUnit_Framework_TestCase { $result = \OCP\User::getUsers(); $this->assertEquals(3, count($result)); + } + + public function testGetUsersViaAPILimitOffset() { + $access = $this->getAccessMock(); + $this->prepareAccessForGetUsers($access); + $backend = new UserLDAP($access); + \OC_User::useBackend($backend); $result = \OCP\User::getUsers('', 1, 2); $this->assertEquals(1, count($result)); + } + + public function testGetUsersViaAPILimitOffset2() { + $access = $this->getAccessMock(); + $this->prepareAccessForGetUsers($access); + $backend = new UserLDAP($access); + \OC_User::useBackend($backend); $result = \OCP\User::getUsers('', 2, 1); $this->assertEquals(2, count($result)); + } + + public function testGetUsersViaAPISearchWithResult() { + $access = $this->getAccessMock(); + $this->prepareAccessForGetUsers($access); + $backend = new UserLDAP($access); + \OC_User::useBackend($backend); $result = \OCP\User::getUsers('yo'); $this->assertEquals(2, count($result)); + } + + public function testGetUsersViaAPISearchEmptyResult() { + $access = $this->getAccessMock(); + $this->prepareAccessForGetUsers($access); + $backend = new UserLDAP($access); + \OC_User::useBackend($backend); $result = \OCP\User::getUsers('nix'); $this->assertEquals(0, count($result)); |