diff options
78 files changed, 3616 insertions, 1433 deletions
diff --git a/apps/encryption/lib/migration.php b/apps/encryption/lib/migration.php index d22c571fd40..5396a7db627 100644 --- a/apps/encryption/lib/migration.php +++ b/apps/encryption/lib/migration.php @@ -72,7 +72,7 @@ class Migration { // only update during the first run if ($this->installedVersion !== '-1') { $query = $this->connection->getQueryBuilder(); - $query->update('*PREFIX*filecache') + $query->update('filecache') ->set('size', 'unencrypted_size') ->where($query->expr()->eq('encrypted', $query->createParameter('encrypted'))) ->setParameter('encrypted', 1); @@ -163,7 +163,7 @@ class Migration { $oldAppValues = $this->connection->getQueryBuilder(); $oldAppValues->select('*') - ->from('*PREFIX*appconfig') + ->from('appconfig') ->where($oldAppValues->expr()->eq('appid', $oldAppValues->createParameter('appid'))) ->setParameter('appid', 'files_encryption'); $appSettings = $oldAppValues->execute(); @@ -178,7 +178,7 @@ class Migration { $oldPreferences = $this->connection->getQueryBuilder(); $oldPreferences->select('*') - ->from('*PREFIX*preferences') + ->from('preferences') ->where($oldPreferences->expr()->eq('appid', $oldPreferences->createParameter('appid'))) ->setParameter('appid', 'files_encryption'); $preferenceSettings = $oldPreferences->execute(); diff --git a/apps/encryption/tests/lib/MigrationTest.php b/apps/encryption/tests/lib/MigrationTest.php index 5bc3b89b5b9..bb1f0a310a2 100644 --- a/apps/encryption/tests/lib/MigrationTest.php +++ b/apps/encryption/tests/lib/MigrationTest.php @@ -291,12 +291,12 @@ class MigrationTest extends \Test\TestCase { /** @var \OCP\IDBConnection $connection */ $connection = \OC::$server->getDatabaseConnection(); $query = $connection->getQueryBuilder(); - $query->delete('*PREFIX*appconfig') + $query->delete('appconfig') ->where($query->expr()->eq('appid', $query->createParameter('appid'))) ->setParameter('appid', 'encryption'); $query->execute(); $query = $connection->getQueryBuilder(); - $query->delete('*PREFIX*preferences') + $query->delete('preferences') ->where($query->expr()->eq('appid', $query->createParameter('appid'))) ->setParameter('appid', 'encryption'); $query->execute(); @@ -309,10 +309,10 @@ class MigrationTest extends \Test\TestCase { $this->invokePrivate($m, 'installedVersion', ['0.7']); $m->updateDB(); - $this->verifyDB('*PREFIX*appconfig', 'files_encryption', 0); - $this->verifyDB('*PREFIX*preferences', 'files_encryption', 0); - $this->verifyDB('*PREFIX*appconfig', 'encryption', 3); - $this->verifyDB('*PREFIX*preferences', 'encryption', 1); + $this->verifyDB('appconfig', 'files_encryption', 0); + $this->verifyDB('preferences', 'files_encryption', 0); + $this->verifyDB('appconfig', 'encryption', 3); + $this->verifyDB('preferences', 'encryption', 1); } @@ -329,17 +329,17 @@ class MigrationTest extends \Test\TestCase { $this->invokePrivate($m, 'installedVersion', ['0.7']); $m->updateDB(); - $this->verifyDB('*PREFIX*appconfig', 'files_encryption', 0); - $this->verifyDB('*PREFIX*preferences', 'files_encryption', 0); - $this->verifyDB('*PREFIX*appconfig', 'encryption', 3); - $this->verifyDB('*PREFIX*preferences', 'encryption', 1); + $this->verifyDB('appconfig', 'files_encryption', 0); + $this->verifyDB('preferences', 'files_encryption', 0); + $this->verifyDB('appconfig', 'encryption', 3); + $this->verifyDB('preferences', 'encryption', 1); // check if the existing values where overwritten correctly /** @var \OC\DB\Connection $connection */ $connection = \OC::$server->getDatabaseConnection(); $query = $connection->getQueryBuilder(); $query->select('configvalue') - ->from('*PREFIX*appconfig') + ->from('appconfig') ->where($query->expr()->andX( $query->expr()->eq('appid', $query->createParameter('appid')), $query->expr()->eq('configkey', $query->createParameter('configkey')) @@ -353,7 +353,7 @@ class MigrationTest extends \Test\TestCase { $query = $connection->getQueryBuilder(); $query->select('configvalue') - ->from('*PREFIX*preferences') + ->from('preferences') ->where($query->expr()->andX( $query->expr()->eq('appid', $query->createParameter('appid')), $query->expr()->eq('configkey', $query->createParameter('configkey')), @@ -399,7 +399,7 @@ class MigrationTest extends \Test\TestCase { $connection = \OC::$server->getDatabaseConnection(); $query = $connection->getQueryBuilder(); $query->select('*') - ->from('*PREFIX*filecache'); + ->from('filecache'); $result = $query->execute(); $entries = $result->fetchAll(); foreach($entries as $entry) { @@ -417,15 +417,15 @@ class MigrationTest extends \Test\TestCase { /** @var \OCP\IDBConnection $connection */ $connection = \OC::$server->getDatabaseConnection(); $query = $connection->getQueryBuilder(); - $query->delete('*PREFIX*filecache'); + $query->delete('filecache'); $query->execute(); $query = $connection->getQueryBuilder(); $result = $query->select('fileid') - ->from('*PREFIX*filecache') + ->from('filecache') ->setMaxResults(1)->execute()->fetchAll(); $this->assertEmpty($result); $query = $connection->getQueryBuilder(); - $query->insert('*PREFIX*filecache') + $query->insert('filecache') ->values( array( 'storage' => $query->createParameter('storage'), @@ -447,7 +447,7 @@ class MigrationTest extends \Test\TestCase { } $query = $connection->getQueryBuilder(); $result = $query->select('fileid') - ->from('*PREFIX*filecache') + ->from('filecache') ->execute()->fetchAll(); $this->assertSame(19, count($result)); } diff --git a/apps/files/appinfo/remote.php b/apps/files/appinfo/remote.php index fff3332ef49..36479ae13d0 100644 --- a/apps/files/appinfo/remote.php +++ b/apps/files/appinfo/remote.php @@ -33,51 +33,23 @@ set_time_limit(0); // Turn off output buffering to prevent memory problems \OC_Util::obEnd(); +$serverFactory = new \OC\Connector\Sabre\ServerFactory( + \OC::$server->getConfig(), + \OC::$server->getLogger(), + \OC::$server->getDatabaseConnection(), + \OC::$server->getUserSession(), + \OC::$server->getMountManager(), + \OC::$server->getTagManager() +); + // Backends $authBackend = new \OC\Connector\Sabre\Auth(); +$requestUri = \OC::$server->getRequest()->getRequestUri(); -// Fire up server -$objectTree = new \OC\Connector\Sabre\ObjectTree(); -$server = new \OC\Connector\Sabre\Server($objectTree); -// Set URL explicitly due to reverse-proxy situations -$server->httpRequest->setUrl(\OC::$server->getRequest()->getRequestUri()); -$server->setBaseUri($baseuri); - -// Load plugins -$defaults = new OC_Defaults(); -$server->addPlugin(new \OC\Connector\Sabre\BlockLegacyClientPlugin(\OC::$server->getConfig())); -$server->addPlugin(new \Sabre\DAV\Auth\Plugin($authBackend, $defaults->getName())); -// FIXME: The following line is a workaround for legacy components relying on being able to send a GET to / -$server->addPlugin(new \OC\Connector\Sabre\DummyGetResponsePlugin()); -$server->addPlugin(new \OC\Connector\Sabre\FilesPlugin($objectTree)); -$server->addPlugin(new \OC\Connector\Sabre\MaintenancePlugin(\OC::$server->getConfig())); -$server->addPlugin(new \OC\Connector\Sabre\ExceptionLoggerPlugin('webdav', \OC::$server->getLogger())); - -// wait with registering these until auth is handled and the filesystem is setup -$server->on('beforeMethod', function () use ($server, $objectTree) { - $view = \OC\Files\Filesystem::getView(); - $rootInfo = $view->getFileInfo(''); - - // Create ownCloud Dir - $mountManager = \OC\Files\Filesystem::getMountManager(); - $rootDir = new \OC\Connector\Sabre\Directory($view, $rootInfo); - $objectTree->init($rootDir, $view, $mountManager); - - $server->addPlugin(new \OC\Connector\Sabre\TagsPlugin($objectTree, \OC::$server->getTagManager())); - $server->addPlugin(new \OC\Connector\Sabre\QuotaPlugin($view)); - - // custom properties plugin must be the last one - $server->addPlugin( - new \Sabre\DAV\PropertyStorage\Plugin( - new \OC\Connector\Sabre\CustomPropertiesBackend( - $objectTree, - \OC::$server->getDatabaseConnection(), - \OC::$server->getUserSession()->getUser() - ) - ) - ); - $server->addPlugin(new \OC\Connector\Sabre\CopyEtagHeaderPlugin()); -}, 30); // priority 30: after auth (10) and acl(20), before lock(50) and handling the request +$server = $serverFactory->createServer($baseuri, $requestUri, $authBackend, function() { + // use the view for the logged in user + return \OC\Files\Filesystem::getView(); +}); // And off we go! $server->exec(); diff --git a/apps/files/css/files.css b/apps/files/css/files.css index 7e3318a962b..26ba86b28c8 100644 --- a/apps/files/css/files.css +++ b/apps/files/css/files.css @@ -249,8 +249,8 @@ table th.column-last, table td.column-last { box-sizing: border-box; position: relative; /* this can not be just width, both need to be set … table styling */ - min-width: 176px; - max-width: 176px; + min-width: 130px; + max-width: 130px; } /* Multiselect bar */ @@ -326,14 +326,7 @@ table td.filename .nametext, .uploadtext, .modified, .column-last>span:first-chi position: relative; overflow: hidden; text-overflow: ellipsis; - width: 90%; -} -/* ellipsize long modified dates to make room for showing delete button */ -#fileList tr:hover .modified, -#fileList tr:focus .modified, -#fileList tr:hover .column-last>span:first-child, -#fileList tr:focus .column-last>span:first-child { - width: 75%; + width: 110px; } /* TODO fix usability bug (accidental file/folder selection) */ @@ -372,45 +365,27 @@ table td.filename .nametext .innernametext { @media only screen and (min-width: 1366px) { table td.filename .nametext .innernametext { - max-width: 760px; - } - - table tr:hover td.filename .nametext .innernametext, - table tr:focus td.filename .nametext .innernametext { - max-width: 480px; + max-width: 660px; } } - @media only screen and (min-width: 1200px) and (max-width: 1366px) { table td.filename .nametext .innernametext { - max-width: 600px; - } - - table tr:hover td.filename .nametext .innernametext, - table tr:focus td.filename .nametext .innernametext { - max-width: 320px; + max-width: 500px; } } - -@media only screen and (min-width: 1000px) and (max-width: 1200px) { +@media only screen and (min-width: 1100px) and (max-width: 1200px) { table td.filename .nametext .innernametext { max-width: 400px; } - - table tr:hover td.filename .nametext .innernametext, - table tr:focus td.filename .nametext .innernametext { - max-width: 120px; +} +@media only screen and (min-width: 1000px) and (max-width: 1100px) { + table td.filename .nametext .innernametext { + max-width: 310px; } } - @media only screen and (min-width: 768px) and (max-width: 1000px) { table td.filename .nametext .innernametext { - max-width: 320px; - } - - table tr:hover td.filename .nametext .innernametext, - table tr:focus td.filename .nametext .innernametext { - max-width: 40px; + max-width: 240px; } } @@ -517,6 +492,23 @@ table td.filename .uploadtext { font-size: 11px; } +.busy .fileactions, .busy .action { + visibility: hidden; +} + +/* fix position of bubble pointer for Files app */ +.bubble, +#app-navigation .app-navigation-entry-menu { + border-top-right-radius: 3px; +} +.bubble:after, +#app-navigation .app-navigation-entry-menu:after { + right: 6px; +} +.bubble:before, +#app-navigation .app-navigation-entry-menu:before { + right: 6px; +} /* force show the loading icon, not only on hover */ #fileList .icon-loading-small { @@ -527,21 +519,15 @@ table td.filename .uploadtext { } #fileList img.move2trash { display:inline; margin:-8px 0; padding:16px 8px 16px 8px !important; float:right; } -#fileList a.action.delete { - position: absolute; - right: 15px; - padding: 17px 14px; -} #fileList .action.action-share-notification span, #fileList a.name { cursor: default !important; } -a.action>img { - max-height:16px; - max-width:16px; - vertical-align:text-bottom; - margin-bottom: -1px; +a.action > img { + max-height: 16px; + max-width: 16px; + vertical-align: text-bottom; } /* Actions for selected files */ @@ -578,10 +564,6 @@ a.action>img { display:none; } -#fileList a.action[data-action="Rename"] { - padding: 16px 14px 17px !important; -} - .ie8 #fileList a.action img, #fileList tr:hover a.action, #fileList a.action.permanent, @@ -693,3 +675,44 @@ table.dragshadow td.size { .mask.transparent{ opacity: 0; } + +.fileActionsMenu { + padding: 4px 12px; +} +.fileActionsMenu li { + padding: 5px 0; +} +#fileList .fileActionsMenu a.action img { + padding: initial; +} +#fileList .fileActionsMenu a.action { + padding: 10px; + margin: -10px; +} + +.fileActionsMenu.hidden { + display: none; +} + +#fileList .fileActionsMenu .action { + display: block; + line-height: 30px; + padding-left: 5px; + color: #000; + padding: 0; +} + +.fileActionsMenu .action img, +.fileActionsMenu .action .no-icon { + display: inline-block; + width: 16px; + margin-right: 5px; +} + +.fileActionsMenu .action { + opacity: 0.5; +} + +.fileActionsMenu li:hover .action { + opacity: 1; +} diff --git a/apps/files/css/mobile.css b/apps/files/css/mobile.css index 4881f7c70e4..dd8244a2913 100644 --- a/apps/files/css/mobile.css +++ b/apps/files/css/mobile.css @@ -5,11 +5,6 @@ min-width: initial !important; } -/* do not show Deleted Files on mobile, not optimized yet and button too long */ -#controls #trash { - display: none; -} - /* hide size and date columns */ table th#headerSize, table td.filesize, @@ -38,7 +33,8 @@ table td.filename .nametext { } /* always show actions on mobile, not only on hover */ -#fileList a.action { +#fileList a.action, +#fileList a.action.action-menu.permanent { -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=20)" !important; filter: alpha(opacity=20) !important; opacity: .2 !important; @@ -50,17 +46,19 @@ table td.filename .nametext { filter: alpha(opacity=70) !important; opacity: .7 !important; } -/* do not show Rename or Versions on mobile */ -#fileList .action.action-rename, -#fileList .action.action-versions { - display: none !important; +#fileList a.action.action-menu img { + padding-left: 2px; +} + +#fileList .fileActionsMenu { + margin-right: 5px; } /* some padding for better clickability */ #fileList a.action img { padding: 0 6px 0 12px; } -/* hide text of the actions on mobile */ -#fileList a.action span { +/* hide text of the share action on mobile */ +#fileList a.action-share span { display: none; } diff --git a/apps/files/index.php b/apps/files/index.php index dca3e5ae74d..a41ec059b55 100644 --- a/apps/files/index.php +++ b/apps/files/index.php @@ -138,6 +138,7 @@ foreach ($navItems as $item) { } OCP\Util::addscript('files', 'fileactions'); +OCP\Util::addscript('files', 'fileactionsmenu'); OCP\Util::addscript('files', 'files'); OCP\Util::addscript('files', 'navigation'); OCP\Util::addscript('files', 'keyboardshortcuts'); diff --git a/apps/files/js/fileactions.js b/apps/files/js/fileactions.js index 8dd26d71c3e..43f74c5816d 100644 --- a/apps/files/js/fileactions.js +++ b/apps/files/js/fileactions.js @@ -10,6 +10,12 @@ (function() { + var TEMPLATE_FILE_ACTION_TRIGGER = + '<a class="action action-{{nameLowerCase}}" href="#" data-action="{{name}}">' + + '{{#if icon}}<img class="svg" alt="{{altText}}" src="{{icon}}" />{{/if}}' + + '{{#if displayName}}<span> {{displayName}}</span>{{/if}}' + + '</a>'; + /** * Construct a new FileActions instance * @constructs FileActions @@ -18,6 +24,8 @@ var FileActions = function() { this.initialize(); }; + FileActions.TYPE_DROPDOWN = 0; + FileActions.TYPE_INLINE = 1; FileActions.prototype = { /** @lends FileActions.prototype */ actions: {}, @@ -38,6 +46,8 @@ */ _updateListeners: {}, + _fileActionTriggerTemplate: null, + /** * @private */ @@ -46,6 +56,8 @@ // abusing jquery for events until we get a real event lib this.$el = $('<div class="dummy-fileactions hidden"></div>'); $('body').append(this.$el); + + this._showMenuClosure = _.bind(this._showMenu, this); }, /** @@ -111,6 +123,7 @@ displayName: displayName || name }); }, + /** * Register action * @@ -125,15 +138,14 @@ displayName: action.displayName, mime: mime, icon: action.icon, - permissions: action.permissions + permissions: action.permissions, + type: action.type || FileActions.TYPE_DROPDOWN }; if (_.isUndefined(action.displayName)) { actionSpec.displayName = t('files', name); } if (_.isFunction(action.render)) { actionSpec.render = action.render; - } else { - actionSpec.render = _.bind(this._defaultRenderAction, this); } if (!this.actions[mime]) { this.actions[mime] = {}; @@ -162,6 +174,16 @@ this.defaults[mime] = name; this._notifyUpdateListeners('setDefault', {defaultAction: {mime: mime, name: name}}); }, + + /** + * Returns a map of file actions handlers matching the given conditions + * + * @param {string} mime mime type + * @param {string} type "dir" or "file" + * @param {int} permissions permissions + * + * @return {Object.<string,OCA.Files.FileActions~actionHandler>} map of action name to action spec + */ get: function (mime, type, permissions) { var actions = this.getActions(mime, type, permissions); var filteredActions = {}; @@ -170,6 +192,16 @@ }); return filteredActions; }, + + /** + * Returns an array of file actions matching the given conditions + * + * @param {string} mime mime type + * @param {string} type "dir" or "file" + * @param {int} permissions permissions + * + * @return {Array.<OCA.Files.FileAction>} array of action specs + */ getActions: function (mime, type, permissions) { var actions = {}; if (this.actions.all) { @@ -197,7 +229,37 @@ }); return filteredActions; }, + + /** + * Returns the default file action handler for the given conditions + * + * @param {string} mime mime type + * @param {string} type "dir" or "file" + * @param {int} permissions permissions + * + * @return {OCA.Files.FileActions~actionHandler} action handler + * + * @deprecated use getDefaultFileAction instead + */ getDefault: function (mime, type, permissions) { + var defaultActionSpec = this.getDefaultFileAction(mime, type, permissions); + if (defaultActionSpec) { + return defaultActionSpec.action; + } + return undefined; + }, + + /** + * Returns the default file action handler for the given conditions + * + * @param {string} mime mime type + * @param {string} type "dir" or "file" + * @param {int} permissions permissions + * + * @return {OCA.Files.FileActions~actionHandler} action handler + * @since 8.2 + */ + getDefaultFileAction: function(mime, type, permissions) { var mimePart; if (mime) { mimePart = mime.substr(0, mime.indexOf('/')); @@ -212,9 +274,10 @@ } else { name = this.defaults.all; } - var actions = this.get(mime, type, permissions); + var actions = this.getActions(mime, type, permissions); return actions[name]; }, + /** * Default function to render actions * @@ -224,87 +287,82 @@ * @param {OCA.Files.FileActionContext} context action context */ _defaultRenderAction: function(actionSpec, isDefault, context) { - var name = actionSpec.name; - if (name === 'Download' || !isDefault) { - var $actionLink = this._makeActionLink(actionSpec, context); + if (!isDefault) { + var params = { + name: actionSpec.name, + nameLowerCase: actionSpec.name.toLowerCase(), + displayName: actionSpec.displayName, + icon: actionSpec.icon, + altText: actionSpec.altText, + }; + if (_.isFunction(actionSpec.icon)) { + params.icon = actionSpec.icon(context.$file.attr('data-file')); + } + + var $actionLink = this._makeActionLink(params, context); context.$file.find('a.name>span.fileactions').append($actionLink); + $actionLink.addClass('permanent'); return $actionLink; } }, + /** * Renders the action link element * - * @param {OCA.Files.FileAction} actionSpec action object - * @param {OCA.Files.FileActionContext} context action context + * @param {Object} params action params */ - _makeActionLink: function(actionSpec, context) { - var img = actionSpec.icon; - if (img && img.call) { - img = img(context.$file.attr('data-file')); - } - var html = '<a href="#">'; - if (img) { - html += '<img class="svg" alt="" src="' + img + '" />'; - } - if (actionSpec.displayName) { - html += '<span> ' + actionSpec.displayName + '</span>'; + _makeActionLink: function(params) { + if (!this._fileActionTriggerTemplate) { + this._fileActionTriggerTemplate = Handlebars.compile(TEMPLATE_FILE_ACTION_TRIGGER); } - html += '</a>'; - return $(html); + return $(this._fileActionTriggerTemplate(params)); }, + /** - * Custom renderer for the "Rename" action. - * Displays the rename action as an icon behind the file name. + * Displays the file actions dropdown menu * - * @param {OCA.Files.FileAction} actionSpec file action to render - * @param {boolean} isDefault true if the action is a default action, - * false otherwise - * @param {OCAFiles.FileActionContext} context rendering context + * @param {string} fileName file name + * @param {OCA.Files.FileActionContext} context rendering context */ - _renderRenameAction: function(actionSpec, isDefault, context) { - var $actionEl = this._makeActionLink(actionSpec, context); - var $container = context.$file.find('a.name span.nametext'); - $actionEl.find('img').attr('alt', t('files', 'Rename')); - $container.find('.action-rename').remove(); - $container.append($actionEl); - return $actionEl; + _showMenu: function(fileName, context) { + var menu; + var $trigger = context.$file.closest('tr').find('.fileactions .action-menu'); + $trigger.addClass('open'); + + menu = new OCA.Files.FileActionsMenu(); + menu.$el.on('afterHide', function() { + context.$file.removeClass('mouseOver'); + $trigger.removeClass('open'); + menu.remove(); + }); + + context.$file.addClass('mouseOver'); + context.$file.find('td.filename').append(menu.$el); + menu.show(context); }, + /** - * Custom renderer for the "Delete" action. - * Displays the "Delete" action as a trash icon at the end of - * the table row. - * - * @param {OCA.Files.FileAction} actionSpec file action to render - * @param {boolean} isDefault true if the action is a default action, - * false otherwise - * @param {OCAFiles.FileActionContext} context rendering context + * Renders the menu trigger on the given file list row + * + * @param {Object} $tr file list row element + * @param {OCA.Files.FileActionContext} context rendering context */ - _renderDeleteAction: function(actionSpec, isDefault, context) { - var mountType = context.$file.attr('data-mounttype'); - var deleteTitle = t('files', 'Delete'); - if (mountType === 'external-root') { - deleteTitle = t('files', 'Disconnect storage'); - } else if (mountType === 'shared-root') { - deleteTitle = t('files', 'Unshare'); - } - var cssClasses = 'action delete icon-delete'; - if((context.$file.data('permissions') & OC.PERMISSION_DELETE) === 0) { - // add css class no-permission to delete icon - cssClasses += ' no-permission'; - deleteTitle = t('files', 'No permission to delete'); - } - var $actionLink = $('<a href="#" original-title="' + - escapeHTML(deleteTitle) + - '" class="' +cssClasses + '">' + - '<span class="hidden-visually">' + escapeHTML(deleteTitle) + '</span>' + - '</a>' - ); - var $container = context.$file.find('td:last'); - $container.find('.delete').remove(); - $container.append($actionLink); - return $actionLink; + _renderMenuTrigger: function($tr, context) { + // remove previous + $tr.find('.action-menu').remove(); + + var $el = this._renderInlineAction({ + name: 'menu', + displayName: '', + icon: OC.imagePath('core', 'actions/more'), + altText: t('files', 'Actions'), + action: this._showMenuClosure + }, false, context); + + $el.addClass('permanent'); }, + /** * Renders the action element by calling actionSpec.render() and * registers the click event to process the action. @@ -312,25 +370,32 @@ * @param {OCA.Files.FileAction} actionSpec file action to render * @param {boolean} isDefault true if the action is a default action, * false otherwise - * @param {OCAFiles.FileActionContext} context rendering context + * @param {OCA.Files.FileActionContext} context rendering context */ - _renderAction: function(actionSpec, isDefault, context) { - var $actionEl = actionSpec.render(actionSpec, isDefault, context); + _renderInlineAction: function(actionSpec, isDefault, context) { + var renderFunc = actionSpec.render || _.bind(this._defaultRenderAction, this); + var $actionEl = renderFunc(actionSpec, isDefault, context); if (!$actionEl || !$actionEl.length) { return; } - $actionEl.addClass('action action-' + actionSpec.name.toLowerCase()); - $actionEl.attr('data-action', actionSpec.name); $actionEl.on( 'click', { a: null }, function(event) { + event.stopPropagation(); + event.preventDefault(); + + if ($actionEl.hasClass('open')) { + return; + } + var $file = $(event.target).closest('tr'); + if ($file.hasClass('busy')) { + return; + } var currentFile = $file.find('td.filename'); var fileName = $file.attr('data-file'); - event.stopPropagation(); - event.preventDefault(); context.fileActions.currentFile = currentFile; // also set on global object for legacy apps @@ -346,6 +411,7 @@ ); return $actionEl; }, + /** * Display file actions for the given element * @param parent "td" element of the file for which to display actions @@ -376,36 +442,29 @@ nameLinks = parent.children('a.name'); nameLinks.find('.fileactions, .nametext .action').remove(); nameLinks.append('<span class="fileactions" />'); - var defaultAction = this.getDefault( + var defaultAction = this.getDefaultFileAction( this.getCurrentMimeType(), this.getCurrentType(), this.getCurrentPermissions() ); + var context = { + $file: $tr, + fileActions: this, + fileList: fileList + }; + $.each(actions, function (name, actionSpec) { - if (name !== 'Share') { - self._renderAction( + if (actionSpec.type === FileActions.TYPE_INLINE) { + self._renderInlineAction( actionSpec, - actionSpec.action === defaultAction, { - $file: $tr, - fileActions: this, - fileList : fileList - } + defaultAction && actionSpec.name === defaultAction.name, + context ); } }); - // added here to make sure it's always the last action - var shareActionSpec = actions.Share; - if (shareActionSpec){ - this._renderAction( - shareActionSpec, - shareActionSpec.action === defaultAction, { - $file: $tr, - fileActions: this, - fileList: fileList - } - ); - } + + this._renderMenuTrigger($tr, context); if (triggerEvent){ fileList.$fileList.trigger(jQuery.Event("fileActionsReady", {fileList: fileList, $files: $tr})); @@ -429,35 +488,42 @@ */ registerDefaultActions: function() { this.registerAction({ - name: 'Delete', - displayName: '', + name: 'Download', + displayName: t('files', 'Download'), mime: 'all', - // permission is READ because we show a hint instead if there is no permission permissions: OC.PERMISSION_READ, - icon: function() { - return OC.imagePath('core', 'actions/delete'); + icon: function () { + return OC.imagePath('core', 'actions/download'); }, - render: _.bind(this._renderDeleteAction, this), - actionHandler: function(fileName, context) { - // if there is no permission to delete do nothing - if((context.$file.data('permissions') & OC.PERMISSION_DELETE) === 0) { + actionHandler: function (filename, context) { + var dir = context.dir || context.fileList.getCurrentDirectory(); + var url = context.fileList.getDownloadUrl(filename, dir); + + var downloadFileaction = $(context.$file).find('.fileactions .action-download'); + + // don't allow a second click on the download action + if(downloadFileaction.hasClass('disabled')) { return; } - context.fileList.do_delete(fileName, context.dir); - $('.tipsy').remove(); + + if (url) { + var disableLoadingState = function() { + context.fileList.showFileBusyState(filename, false); + }; + + context.fileList.showFileBusyState(downloadFileaction, true); + OCA.Files.Files.handleDownload(url, disableLoadingState); + } } }); - // t('files', 'Rename') this.registerAction({ name: 'Rename', - displayName: '', mime: 'all', permissions: OC.PERMISSION_UPDATE, icon: function() { return OC.imagePath('core', 'actions/rename'); }, - render: _.bind(this._renderRenameAction, this), actionHandler: function (filename, context) { context.fileList.rename(filename); } @@ -471,30 +537,25 @@ context.fileList.changeDirectory(dir + filename); }); - this.setDefault('dir', 'Open'); - - this.register('all', 'Download', OC.PERMISSION_READ, function () { - return OC.imagePath('core', 'actions/download'); - }, function (filename, context) { - var dir = context.dir || context.fileList.getCurrentDirectory(); - var url = context.fileList.getDownloadUrl(filename, dir); - - var downloadFileaction = $(context.$file).find('.fileactions .action-download'); - - // don't allow a second click on the download action - if(downloadFileaction.hasClass('disabled')) { - return; + this.registerAction({ + name: 'Delete', + mime: 'all', + // permission is READ because we show a hint instead if there is no permission + permissions: OC.PERMISSION_READ, + icon: function() { + return OC.imagePath('core', 'actions/delete'); + }, + actionHandler: function(fileName, context) { + // if there is no permission to delete do nothing + if((context.$file.data('permissions') & OC.PERMISSION_DELETE) === 0) { + return; + } + context.fileList.do_delete(fileName, context.dir); + $('.tipsy').remove(); } + }); - if (url) { - var disableLoadingState = function(){ - OCA.Files.FileActions.updateFileActionSpinner(downloadFileaction, false); - }; - - OCA.Files.FileActions.updateFileActionSpinner(downloadFileaction, true); - OCA.Files.Files.handleDownload(url, disableLoadingState); - } - }, t('files', 'Download')); + this.setDefault('dir', 'Open'); } }; diff --git a/apps/files/js/fileactionsmenu.js b/apps/files/js/fileactionsmenu.js new file mode 100644 index 00000000000..623ebde5442 --- /dev/null +++ b/apps/files/js/fileactionsmenu.js @@ -0,0 +1,132 @@ +/* + * Copyright (c) 2014 + * + * This file is licensed under the Affero General Public License version 3 + * or later. + * + * See the COPYING-README file. + * + */ + +(function() { + + var TEMPLATE_MENU = + '<ul>' + + '{{#each items}}' + + '<li>' + + '<a href="#" class="action action-{{nameLowerCase}} permanent" data-action="{{name}}">{{#if icon}}<img src="{{icon}}"/>{{else}}<span class="no-icon"></span>{{/if}}<span>{{displayName}}</span></a>' + + '</li>' + + '{{/each}}' + + '</ul>'; + + /** + * Construct a new FileActionsMenu instance + * @constructs FileActionsMenu + * @memberof OCA.Files + */ + var FileActionsMenu = OC.Backbone.View.extend({ + tagName: 'div', + className: 'fileActionsMenu bubble hidden open menu', + + /** + * Current context + * + * @type OCA.Files.FileActionContext + */ + _context: null, + + events: { + 'click a.action': '_onClickAction' + }, + + template: function(data) { + if (!OCA.Files.FileActionsMenu._TEMPLATE) { + OCA.Files.FileActionsMenu._TEMPLATE = Handlebars.compile(TEMPLATE_MENU); + } + return OCA.Files.FileActionsMenu._TEMPLATE(data); + }, + + /** + * Event handler whenever an action has been clicked within the menu + * + * @param {Object} event event object + */ + _onClickAction: function(event) { + var $target = $(event.target); + if (!$target.is('a')) { + $target = $target.closest('a'); + } + var fileActions = this._context.fileActions; + var actionName = $target.attr('data-action'); + var actions = fileActions.getActions( + fileActions.getCurrentMimeType(), + fileActions.getCurrentType(), + fileActions.getCurrentPermissions() + ); + var actionSpec = actions[actionName]; + var fileName = this._context.$file.attr('data-file'); + + event.stopPropagation(); + event.preventDefault(); + + OC.hideMenus(); + + actionSpec.action( + fileName, + this._context + ); + }, + + /** + * Renders the menu with the currently set items + */ + render: function() { + var fileActions = this._context.fileActions; + var actions = fileActions.getActions( + fileActions.getCurrentMimeType(), + fileActions.getCurrentType(), + fileActions.getCurrentPermissions() + ); + + var defaultAction = fileActions.getDefaultFileAction( + fileActions.getCurrentMimeType(), + fileActions.getCurrentType(), + fileActions.getCurrentPermissions() + ); + + var items = _.filter(actions, function(actionSpec) { + return ( + actionSpec.type === OCA.Files.FileActions.TYPE_DROPDOWN && + (!defaultAction || actionSpec.name !== defaultAction.name) + ); + }); + items = _.map(items, function(item) { + item.nameLowerCase = item.name.toLowerCase(); + return item; + }); + + this.$el.html(this.template({ + items: items + })); + }, + + /** + * Displays the menu under the given element + * + * @param {OCA.Files.FileActionContext} context context + * @param {Object} $trigger trigger element + */ + show: function(context) { + this._context = context; + + this.render(); + this.$el.removeClass('hidden'); + + OC.showMenu(null, this.$el); + } + }); + + OCA.Files.FileActionsMenu = FileActionsMenu; + +})(); + diff --git a/apps/files/js/filelist.js b/apps/files/js/filelist.js index f5629ecd2c3..e297edcf11b 100644 --- a/apps/files/js/filelist.js +++ b/apps/files/js/filelist.js @@ -1444,9 +1444,7 @@ } _.each(fileNames, function(fileName) { var $tr = self.findFileEl(fileName); - var $thumbEl = $tr.find('.thumbnail'); - var oldBackgroundImage = $thumbEl.css('background-image'); - $thumbEl.css('background-image', 'url('+ OC.imagePath('core', 'loading.gif') + ')'); + self.showFileBusyState($tr, true); // TODO: improve performance by sending all file names in a single call $.post( OC.filePath('files', 'ajax', 'move.php'), @@ -1488,7 +1486,7 @@ } else { OC.dialogs.alert(t('files', 'Error moving file'), t('files', 'Error')); } - $thumbEl.css('background-image', oldBackgroundImage); + self.showFileBusyState($tr, false); } ); }); @@ -1549,14 +1547,13 @@ try { var newName = input.val(); - var $thumbEl = tr.find('.thumbnail'); input.tipsy('hide'); form.remove(); if (newName !== oldname) { checkInput(); // mark as loading (temp element) - $thumbEl.css('background-image', 'url('+ OC.imagePath('core', 'loading.gif') + ')'); + self.showFileBusyState(tr, true); tr.attr('data-file', newName); var basename = newName; if (newName.indexOf('.') > 0 && tr.data('type') !== 'dir') { @@ -1564,7 +1561,6 @@ } td.find('a.name span.nametext').text(basename); td.children('a.name').show(); - tr.find('.fileactions, .action').addClass('hidden'); $.ajax({ url: OC.filePath('files','ajax','rename.php'), @@ -1636,6 +1632,44 @@ inList:function(file) { return this.findFileEl(file).length; }, + + /** + * Shows busy state on a given file row or multiple + * + * @param {string|Array.<string>} files file name or array of file names + * @param {bool} [busy=true] busy state, true for busy, false to remove busy state + * + * @since 8.2 + */ + showFileBusyState: function(files, state) { + var self = this; + if (!_.isArray(files)) { + files = [files]; + } + + if (_.isUndefined(state)) { + state = true; + } + + _.each(files, function($tr) { + // jquery element already ? + if (!$tr.is) { + $tr = self.findFileEl($tr); + } + + var $thumbEl = $tr.find('.thumbnail'); + $tr.toggleClass('busy', state); + + if (state) { + $thumbEl.attr('data-oldimage', $thumbEl.css('background-image')); + $thumbEl.css('background-image', 'url('+ OC.imagePath('core', 'loading.gif') + ')'); + } else { + $thumbEl.css('background-image', $thumbEl.attr('data-oldimage')); + $thumbEl.removeAttr('data-oldimage'); + } + }); + }, + /** * Delete the given files from the given dir * @param files file names list (without path) @@ -1649,9 +1683,8 @@ files=[files]; } if (files) { + this.showFileBusyState(files, true); for (var i=0; i<files.length; i++) { - var deleteAction = this.findFileEl(files[i]).children("td.date").children(".action.delete"); - deleteAction.removeClass('icon-delete').addClass('icon-loading-small'); } } // Finish any existing actions @@ -1669,7 +1702,7 @@ // no files passed, delete all in current dir params.allfiles = true; // show spinner for all files - this.$fileList.find('tr>td.date .action.delete').removeClass('icon-delete').addClass('icon-loading-small'); + this.$fileList.find('tr').addClass('busy'); } $.post(OC.filePath('files', 'ajax', 'delete.php'), @@ -1712,8 +1745,7 @@ } else { $.each(files,function(index,file) { - var deleteAction = self.findFileEl(file).find('.action.delete'); - deleteAction.removeClass('icon-loading-small').addClass('icon-delete'); + self.$fileList.find('tr').removeClass('busy'); }); } } diff --git a/apps/files/js/tagsplugin.js b/apps/files/js/tagsplugin.js index 293e25176f3..ec69ce4b965 100644 --- a/apps/files/js/tagsplugin.js +++ b/apps/files/js/tagsplugin.js @@ -81,6 +81,7 @@ displayName: 'Favorite', mime: 'all', permissions: OC.PERMISSION_READ, + type: OCA.Files.FileActions.TYPE_INLINE, render: function(actionSpec, isDefault, context) { var $file = context.$file; var isFavorite = $file.data('favorite') === true; diff --git a/apps/files/lib/activity.php b/apps/files/lib/activity.php index fff49ea4ea5..bf80d0cfd7c 100644 --- a/apps/files/lib/activity.php +++ b/apps/files/lib/activity.php @@ -30,6 +30,7 @@ use OCP\IL10N; use OCP\IURLGenerator; class Activity implements IExtension { + const APP_FILES = 'files'; const FILTER_FILES = 'files'; const FILTER_FAVORITES = 'files_favorites'; @@ -78,7 +79,7 @@ class Activity implements IExtension { * @return IL10N */ protected function getL10N($languageCode = null) { - return $this->languageFactory->get('files', $languageCode); + return $this->languageFactory->get(self::APP_FILES, $languageCode); } /** @@ -86,14 +87,21 @@ class Activity implements IExtension { * If no additional types are to be added false is to be returned * * @param string $languageCode - * @return array|false + * @return array|false Array "stringID of the type" => "translated string description for the setting" + * or Array "stringID of the type" => [ + * 'desc' => "translated string description for the setting" + * 'methods' => [self::METHOD_*], + * ] */ public function getNotificationTypes($languageCode) { $l = $this->getL10N($languageCode); return [ self::TYPE_SHARE_CREATED => (string) $l->t('A new file or folder has been <strong>created</strong>'), self::TYPE_SHARE_CHANGED => (string) $l->t('A file or folder has been <strong>changed</strong>'), - self::TYPE_FAVORITES => (string) $l->t('Limit notifications about creation and changes to your <strong>favorite files</strong> <em>(Stream only)</em>'), + self::TYPE_FAVORITES => [ + 'desc' => (string) $l->t('Limit notifications about creation and changes to your <strong>favorite files</strong> <em>(Stream only)</em>'), + 'methods' => [self::METHOD_STREAM], + ], self::TYPE_SHARE_DELETED => (string) $l->t('A file or folder has been <strong>deleted</strong>'), self::TYPE_SHARE_RESTORED => (string) $l->t('A file or folder has been <strong>restored</strong>'), ]; @@ -107,7 +115,7 @@ class Activity implements IExtension { * @return array|false */ public function getDefaultTypes($method) { - if ($method === 'stream') { + if ($method === self::METHOD_STREAM) { $settings = array(); $settings[] = self::TYPE_SHARE_CREATED; $settings[] = self::TYPE_SHARE_CHANGED; @@ -132,29 +140,30 @@ class Activity implements IExtension { * @return string|false */ public function translate($app, $text, $params, $stripPath, $highlightParams, $languageCode) { - if ($app !== 'files') { + if ($app !== self::APP_FILES) { return false; } + $l = $this->getL10N($languageCode); switch ($text) { case 'created_self': - return (string) $this->l->t('You created %1$s', $params); + return (string) $l->t('You created %1$s', $params); case 'created_by': - return (string) $this->l->t('%2$s created %1$s', $params); + return (string) $l->t('%2$s created %1$s', $params); case 'created_public': - return (string) $this->l->t('%1$s was created in a public folder', $params); + return (string) $l->t('%1$s was created in a public folder', $params); case 'changed_self': - return (string) $this->l->t('You changed %1$s', $params); + return (string) $l->t('You changed %1$s', $params); case 'changed_by': - return (string) $this->l->t('%2$s changed %1$s', $params); + return (string) $l->t('%2$s changed %1$s', $params); case 'deleted_self': - return (string) $this->l->t('You deleted %1$s', $params); + return (string) $l->t('You deleted %1$s', $params); case 'deleted_by': - return (string) $this->l->t('%2$s deleted %1$s', $params); + return (string) $l->t('%2$s deleted %1$s', $params); case 'restored_self': - return (string) $this->l->t('You restored %1$s', $params); + return (string) $l->t('You restored %1$s', $params); case 'restored_by': - return (string) $this->l->t('%2$s restored %1$s', $params); + return (string) $l->t('%2$s restored %1$s', $params); default: return false; @@ -173,7 +182,7 @@ class Activity implements IExtension { * @return array|false */ function getSpecialParameterList($app, $text) { - if ($app === 'files') { + if ($app === self::APP_FILES) { switch ($text) { case 'created_self': case 'created_by': @@ -223,7 +232,7 @@ class Activity implements IExtension { * @return integer|false */ public function getGroupParameter($activity) { - if ($activity['app'] === 'files') { + if ($activity['app'] === self::APP_FILES) { switch ($activity['subject']) { case 'created_self': case 'created_by': @@ -309,7 +318,7 @@ class Activity implements IExtension { $user = $this->activityManager->getCurrentUserId(); // Display actions from all files if ($filter === self::FILTER_FILES) { - return ['`app` = ?', ['files']]; + return ['`app` = ?', [self::APP_FILES]]; } if (!$user) { @@ -323,7 +332,7 @@ class Activity implements IExtension { $favorites = $this->helper->getFavoriteFilePaths($user); } catch (\RuntimeException $e) { // Too many favorites, can not put them into one query anymore... - return ['`app` = ?', ['files']]; + return ['`app` = ?', [self::APP_FILES]]; } /* @@ -331,7 +340,7 @@ class Activity implements IExtension { * or `file` is a favorite or in a favorite folder */ $parameters = $fileQueryList = []; - $parameters[] = 'files'; + $parameters[] = self::APP_FILES; $fileQueryList[] = '(`type` <> ? AND `type` <> ?)'; $parameters[] = self::TYPE_SHARE_CREATED; @@ -346,7 +355,7 @@ class Activity implements IExtension { $parameters[] = $favorite . '/%'; } - $parameters[] = 'files'; + $parameters[] = self::APP_FILES; return [ ' CASE WHEN `app` = ? THEN (' . implode(' OR ', $fileQueryList) . ') ELSE `app` <> ? END ', @@ -363,6 +372,6 @@ class Activity implements IExtension { * @return bool */ protected function userSettingFavoritesOnly($user) { - return (bool) $this->config->getUserValue($user, 'activity', 'notify_stream_' . self::TYPE_FAVORITES, false); + return (bool) $this->config->getUserValue($user, 'activity', 'notify_' . self::METHOD_STREAM . '_' . self::TYPE_FAVORITES, false); } } diff --git a/apps/files/tests/activitytest.php b/apps/files/tests/activitytest.php index 4ab8ad11eae..cdb1d21bcd8 100644 --- a/apps/files/tests/activitytest.php +++ b/apps/files/tests/activitytest.php @@ -42,6 +42,9 @@ class ActivityTest extends TestCase { /** @var \PHPUnit_Framework_MockObject_MockObject */ protected $activityHelper; + /** @var \PHPUnit_Framework_MockObject_MockObject */ + protected $l10nFactory; + /** @var \OCA\Files\Activity */ protected $activityExtension; @@ -67,8 +70,28 @@ class ActivityTest extends TestCase { $this->config ); + $this->l10nFactory = $this->getMockBuilder('OC\L10N\Factory') + ->disableOriginalConstructor() + ->getMock(); + $deL10n = $this->getMockBuilder('OC_L10N') + ->disableOriginalConstructor() + ->getMock(); + $deL10n->expects($this->any()) + ->method('t') + ->willReturnCallback(function ($argument) { + return 'translate(' . $argument . ')'; + }); + + $this->l10nFactory->expects($this->any()) + ->method('get') + ->willReturnMap([ + ['files', null, new \OC_L10N('files', 'en')], + ['files', 'en', new \OC_L10N('files', 'en')], + ['files', 'de', $deL10n], + ]); + $this->activityExtension = $activityExtension = new Activity( - new \OC\L10N\Factory(), + $this->l10nFactory, $this->getMockBuilder('OCP\IURLGenerator')->disableOriginalConstructor()->getMock(), $this->activityManager, $this->activityHelper, @@ -111,6 +134,26 @@ class ActivityTest extends TestCase { $this->activityExtension->translate('files_sharing', '', [], false, false, 'en'), 'Asserting that no translations are set for files_sharing' ); + + // Test english + $this->assertNotFalse( + $this->activityExtension->translate('files', 'deleted_self', ['file'], false, false, 'en'), + 'Asserting that translations are set for files.deleted_self' + ); + $this->assertStringStartsWith( + 'You deleted ', + $this->activityExtension->translate('files', 'deleted_self', ['file'], false, false, 'en') + ); + + // Test translation + $this->assertNotFalse( + $this->activityExtension->translate('files', 'deleted_self', ['file'], false, false, 'de'), + 'Asserting that translations are set for files.deleted_self' + ); + $this->assertStringStartsWith( + 'translate(You deleted ', + $this->activityExtension->translate('files', 'deleted_self', ['file'], false, false, 'de') + ); } public function testGetSpecialParameterList() { diff --git a/apps/files/tests/js/fileactionsSpec.js b/apps/files/tests/js/fileactionsSpec.js index e420ab828af..236cff6cafd 100644 --- a/apps/files/tests/js/fileactionsSpec.js +++ b/apps/files/tests/js/fileactionsSpec.js @@ -20,8 +20,7 @@ */ describe('OCA.Files.FileActions tests', function() { - var $filesTable, fileList; - var FileActions; + var fileList, fileActions; beforeEach(function() { // init horrible parameters @@ -29,211 +28,191 @@ describe('OCA.Files.FileActions tests', function() { $body.append('<input type="hidden" id="dir" value="/subdir"></input>'); $body.append('<input type="hidden" id="permissions" value="31"></input>'); // dummy files table - $filesTable = $body.append('<table id="filestable"></table>'); - fileList = new OCA.Files.FileList($('#testArea')); - FileActions = new OCA.Files.FileActions(); - FileActions.registerDefaultActions(); + fileActions = new OCA.Files.FileActions(); + fileActions.registerAction({ + name: 'Testdropdown', + displayName: 'Testdropdowndisplay', + mime: 'all', + permissions: OC.PERMISSION_READ, + icon: function () { + return OC.imagePath('core', 'actions/download'); + } + }); + + fileActions.registerAction({ + name: 'Testinline', + displayName: 'Testinlinedisplay', + type: OCA.Files.FileActions.TYPE_INLINE, + mime: 'all', + permissions: OC.PERMISSION_READ + }); + + fileActions.registerAction({ + name: 'Testdefault', + displayName: 'Testdefaultdisplay', + mime: 'all', + permissions: OC.PERMISSION_READ + }); + fileActions.setDefault('all', 'Testdefault'); + fileList = new OCA.Files.FileList($body, { + fileActions: fileActions + }); }); afterEach(function() { - FileActions = null; + fileActions = null; fileList.destroy(); fileList = undefined; $('#dir, #permissions, #filestable').remove(); }); it('calling clear() clears file actions', function() { - FileActions.clear(); - expect(FileActions.actions).toEqual({}); - expect(FileActions.defaults).toEqual({}); - expect(FileActions.icons).toEqual({}); - expect(FileActions.currentFile).toBe(null); + fileActions.clear(); + expect(fileActions.actions).toEqual({}); + expect(fileActions.defaults).toEqual({}); + expect(fileActions.icons).toEqual({}); + expect(fileActions.currentFile).toBe(null); }); - it('calling display() sets file actions', function() { - var fileData = { - id: 18, - type: 'file', - name: 'testName.txt', - mimetype: 'text/plain', - size: '1234', - etag: 'a01234c', - mtime: '123456' - }; - - // note: FileActions.display() is called implicitly - var $tr = fileList.add(fileData); - - // actions defined after call - expect($tr.find('.action.action-download').length).toEqual(1); - expect($tr.find('.action.action-download').attr('data-action')).toEqual('Download'); - expect($tr.find('.nametext .action.action-rename').length).toEqual(1); - expect($tr.find('.nametext .action.action-rename').attr('data-action')).toEqual('Rename'); - expect($tr.find('.action.delete').length).toEqual(1); - }); - it('calling display() twice correctly replaces file actions', function() { - var fileData = { - id: 18, - type: 'file', - name: 'testName.txt', - mimetype: 'text/plain', - size: '1234', - etag: 'a01234c', - mtime: '123456' - }; - var $tr = fileList.add(fileData); - - FileActions.display($tr.find('td.filename'), true, fileList); - FileActions.display($tr.find('td.filename'), true, fileList); - - // actions defined after cal - expect($tr.find('.action.action-download').length).toEqual(1); - expect($tr.find('.nametext .action.action-rename').length).toEqual(1); - expect($tr.find('.action.delete').length).toEqual(1); - }); - it('redirects to download URL when clicking download', function() { - var redirectStub = sinon.stub(OC, 'redirect'); - var fileData = { - id: 18, - type: 'file', - name: 'testName.txt', - mimetype: 'text/plain', - size: '1234', - etag: 'a01234c', - mtime: '123456' - }; - var $tr = fileList.add(fileData); - FileActions.display($tr.find('td.filename'), true, fileList); - - $tr.find('.action-download').click(); - - expect(redirectStub.calledOnce).toEqual(true); - expect(redirectStub.getCall(0).args[0]).toContain( - OC.webroot + - '/index.php/apps/files/ajax/download.php' + - '?dir=%2Fsubdir&files=testName.txt'); - redirectStub.restore(); - }); - it('takes the file\'s path into account when clicking download', function() { - var redirectStub = sinon.stub(OC, 'redirect'); - var fileData = { - id: 18, - type: 'file', - name: 'testName.txt', - path: '/anotherpath/there', - mimetype: 'text/plain', - size: '1234', - etag: 'a01234c', - mtime: '123456' - }; - var $tr = fileList.add(fileData); - FileActions.display($tr.find('td.filename'), true, fileList); - - $tr.find('.action-download').click(); - - expect(redirectStub.calledOnce).toEqual(true); - expect(redirectStub.getCall(0).args[0]).toContain( - OC.webroot + '/index.php/apps/files/ajax/download.php' + - '?dir=%2Fanotherpath%2Fthere&files=testName.txt' - ); - redirectStub.restore(); - }); - it('deletes file when clicking delete', function() { - var deleteStub = sinon.stub(fileList, 'do_delete'); - var fileData = { - id: 18, - type: 'file', - name: 'testName.txt', - path: '/somepath/dir', - mimetype: 'text/plain', - size: '1234', - etag: 'a01234c', - mtime: '123456' - }; - var $tr = fileList.add(fileData); - FileActions.display($tr.find('td.filename'), true, fileList); - - $tr.find('.action.delete').click(); - - expect(deleteStub.calledOnce).toEqual(true); - expect(deleteStub.getCall(0).args[0]).toEqual('testName.txt'); - expect(deleteStub.getCall(0).args[1]).toEqual('/somepath/dir'); - deleteStub.restore(); - }); - it('shows delete hint when no permission to delete', function() { - var deleteStub = sinon.stub(fileList, 'do_delete'); - var fileData = { - id: 18, - type: 'file', - name: 'testName.txt', - path: '/somepath/dir', - mimetype: 'text/plain', - size: '1234', - etag: 'a01234c', - mtime: '123456', - permissions: OC.PERMISSION_READ - }; - var $tr = fileList.add(fileData); - FileActions.display($tr.find('td.filename'), true, fileList); + describe('displaying actions', function() { + var $tr; - var $action = $tr.find('.action.delete'); + beforeEach(function() { + var fileData = { + id: 18, + type: 'file', + name: 'testName.txt', + mimetype: 'text/plain', + size: '1234', + etag: 'a01234c', + mtime: '123456', + permissions: OC.PERMISSION_READ | OC.PERMISSION_UPDATE + }; - expect($action.hasClass('no-permission')).toEqual(true); - deleteStub.restore(); - }); - it('shows delete hint not when permission to delete', function() { - var deleteStub = sinon.stub(fileList, 'do_delete'); - var fileData = { - id: 18, - type: 'file', - name: 'testName.txt', - path: '/somepath/dir', - mimetype: 'text/plain', - size: '1234', - etag: 'a01234c', - mtime: '123456', - permissions: OC.PERMISSION_DELETE - }; - var $tr = fileList.add(fileData); - FileActions.display($tr.find('td.filename'), true, fileList); - - var $action = $tr.find('.action.delete'); - - expect($action.hasClass('no-permission')).toEqual(false); - deleteStub.restore(); + // note: FileActions.display() is called implicitly + $tr = fileList.add(fileData); + }); + it('renders inline file actions', function() { + // actions defined after call + expect($tr.find('.action.action-testinline').length).toEqual(1); + expect($tr.find('.action.action-testinline').attr('data-action')).toEqual('Testinline'); + }); + it('does not render dropdown actions', function() { + expect($tr.find('.action.action-testdropdown').length).toEqual(0); + }); + it('does not render default action', function() { + expect($tr.find('.action.action-testdefault').length).toEqual(0); + }); + it('replaces file actions when displayed twice', function() { + fileActions.display($tr.find('td.filename'), true, fileList); + fileActions.display($tr.find('td.filename'), true, fileList); + + expect($tr.find('.action.action-testinline').length).toEqual(1); + }); + it('renders actions menu trigger', function() { + expect($tr.find('.action.action-menu').length).toEqual(1); + expect($tr.find('.action.action-menu').attr('data-action')).toEqual('menu'); + }); + it('only renders actions relevant to the mime type', function() { + fileActions.registerAction({ + name: 'Match', + displayName: 'MatchDisplay', + type: OCA.Files.FileActions.TYPE_INLINE, + mime: 'text/plain', + permissions: OC.PERMISSION_READ + }); + fileActions.registerAction({ + name: 'Nomatch', + displayName: 'NoMatchDisplay', + type: OCA.Files.FileActions.TYPE_INLINE, + mime: 'application/octet-stream', + permissions: OC.PERMISSION_READ + }); + + fileActions.display($tr.find('td.filename'), true, fileList); + expect($tr.find('.action.action-match').length).toEqual(1); + expect($tr.find('.action.action-nomatch').length).toEqual(0); + }); + it('only renders actions relevant to the permissions', function() { + fileActions.registerAction({ + name: 'Match', + displayName: 'MatchDisplay', + type: OCA.Files.FileActions.TYPE_INLINE, + mime: 'text/plain', + permissions: OC.PERMISSION_UPDATE + }); + fileActions.registerAction({ + name: 'Nomatch', + displayName: 'NoMatchDisplay', + type: OCA.Files.FileActions.TYPE_INLINE, + mime: 'text/plain', + permissions: OC.PERMISSION_DELETE + }); + + fileActions.display($tr.find('td.filename'), true, fileList); + expect($tr.find('.action.action-match').length).toEqual(1); + expect($tr.find('.action.action-nomatch').length).toEqual(0); + }); }); - it('passes context to action handler', function() { - var actionStub = sinon.stub(); - var fileData = { - id: 18, - type: 'file', - name: 'testName.txt', - mimetype: 'text/plain', - size: '1234', - etag: 'a01234c', - mtime: '123456' - }; - var $tr = fileList.add(fileData); - FileActions.register( - 'all', - 'Test', - OC.PERMISSION_READ, - OC.imagePath('core', 'actions/test'), - actionStub - ); - FileActions.display($tr.find('td.filename'), true, fileList); - $tr.find('.action-test').click(); - expect(actionStub.calledOnce).toEqual(true); - expect(actionStub.getCall(0).args[0]).toEqual('testName.txt'); - var context = actionStub.getCall(0).args[1]; - expect(context.$file.is($tr)).toEqual(true); - expect(context.fileList).toBeDefined(); - expect(context.fileActions).toBeDefined(); - expect(context.dir).toEqual('/subdir'); - - // when data-path is defined - actionStub.reset(); - $tr.attr('data-path', '/somepath'); - $tr.find('.action-test').click(); - context = actionStub.getCall(0).args[1]; - expect(context.dir).toEqual('/somepath'); + describe('action handler', function() { + var actionStub, $tr; + + beforeEach(function() { + var fileData = { + id: 18, + type: 'file', + name: 'testName.txt', + mimetype: 'text/plain', + size: '1234', + etag: 'a01234c', + mtime: '123456' + }; + actionStub = sinon.stub(); + fileActions.registerAction({ + name: 'Test', + type: OCA.Files.FileActions.TYPE_INLINE, + mime: 'all', + icon: OC.imagePath('core', 'actions/test'), + permissions: OC.PERMISSION_READ, + actionHandler: actionStub + }); + $tr = fileList.add(fileData); + }); + it('passes context to action handler', function() { + $tr.find('.action-test').click(); + expect(actionStub.calledOnce).toEqual(true); + expect(actionStub.getCall(0).args[0]).toEqual('testName.txt'); + var context = actionStub.getCall(0).args[1]; + expect(context.$file.is($tr)).toEqual(true); + expect(context.fileList).toBeDefined(); + expect(context.fileActions).toBeDefined(); + expect(context.dir).toEqual('/subdir'); + + // when data-path is defined + actionStub.reset(); + $tr.attr('data-path', '/somepath'); + $tr.find('.action-test').click(); + context = actionStub.getCall(0).args[1]; + expect(context.dir).toEqual('/somepath'); + }); + describe('actions menu', function() { + it('shows actions menu inside row when clicking the menu trigger', function() { + expect($tr.find('td.filename .fileActionsMenu').length).toEqual(0); + $tr.find('.action-menu').click(); + expect($tr.find('td.filename .fileActionsMenu').length).toEqual(1); + }); + it('shows highlight on current row', function() { + $tr.find('.action-menu').click(); + expect($tr.hasClass('mouseOver')).toEqual(true); + }); + it('cleans up after hiding', function() { + var clock = sinon.useFakeTimers(); + $tr.find('.action-menu').click(); + expect($tr.find('.fileActionsMenu').length).toEqual(1); + OC.hideMenus(); + // sliding animation + clock.tick(500); + expect($tr.hasClass('mouseOver')).toEqual(false); + expect($tr.find('.fileActionsMenu').length).toEqual(0); + }); + }); }); describe('custom rendering', function() { var $tr; @@ -251,10 +230,11 @@ describe('OCA.Files.FileActions tests', function() { }); it('regular function', function() { var actionStub = sinon.stub(); - FileActions.registerAction({ + fileActions.registerAction({ name: 'Test', displayName: '', mime: 'all', + type: OCA.Files.FileActions.TYPE_INLINE, permissions: OC.PERMISSION_READ, render: function(actionSpec, isDefault, context) { expect(actionSpec.name).toEqual('Test'); @@ -266,13 +246,13 @@ describe('OCA.Files.FileActions tests', function() { expect(context.fileList).toEqual(fileList); expect(context.$file[0]).toEqual($tr[0]); - var $customEl = $('<a href="#"><span>blabli</span><span>blabla</span></a>'); + var $customEl = $('<a class="action action-test" href="#"><span>blabli</span><span>blabla</span></a>'); $tr.find('td:first').append($customEl); return $customEl; }, actionHandler: actionStub }); - FileActions.display($tr.find('td.filename'), true, fileList); + fileActions.display($tr.find('td.filename'), true, fileList); var $actionEl = $tr.find('td:first .action-test'); expect($actionEl.length).toEqual(1); @@ -306,20 +286,22 @@ describe('OCA.Files.FileActions tests', function() { var actions2 = new OCA.Files.FileActions(); var actionStub1 = sinon.stub(); var actionStub2 = sinon.stub(); - actions1.register( - 'all', - 'Test', - OC.PERMISSION_READ, - OC.imagePath('core', 'actions/test'), - actionStub1 - ); - actions2.register( - 'all', - 'Test2', - OC.PERMISSION_READ, - OC.imagePath('core', 'actions/test'), - actionStub2 - ); + actions1.registerAction({ + name: 'Test', + type: OCA.Files.FileActions.TYPE_INLINE, + mime: 'all', + permissions: OC.PERMISSION_READ, + icon: OC.imagePath('core', 'actions/test'), + actionHandler: actionStub1 + }); + actions2.registerAction({ + name: 'Test2', + type: OCA.Files.FileActions.TYPE_INLINE, + mime: 'all', + permissions: OC.PERMISSION_READ, + icon: OC.imagePath('core', 'actions/test'), + actionHandler: actionStub2 + }); actions2.merge(actions1); actions2.display($tr.find('td.filename'), true, fileList); @@ -342,20 +324,22 @@ describe('OCA.Files.FileActions tests', function() { var actions2 = new OCA.Files.FileActions(); var actionStub1 = sinon.stub(); var actionStub2 = sinon.stub(); - actions1.register( - 'all', - 'Test', - OC.PERMISSION_READ, - OC.imagePath('core', 'actions/test'), - actionStub1 - ); - actions2.register( - 'all', - 'Test', // override - OC.PERMISSION_READ, - OC.imagePath('core', 'actions/test'), - actionStub2 - ); + actions1.registerAction({ + name: 'Test', + type: OCA.Files.FileActions.TYPE_INLINE, + mime: 'all', + permissions: OC.PERMISSION_READ, + icon: OC.imagePath('core', 'actions/test'), + actionHandler: actionStub1 + }); + actions2.registerAction({ + name: 'Test', // override + mime: 'all', + type: OCA.Files.FileActions.TYPE_INLINE, + permissions: OC.PERMISSION_READ, + icon: OC.imagePath('core', 'actions/test'), + actionHandler: actionStub2 + }); actions1.merge(actions2); actions1.display($tr.find('td.filename'), true, fileList); @@ -371,24 +355,26 @@ describe('OCA.Files.FileActions tests', function() { var actions2 = new OCA.Files.FileActions(); var actionStub1 = sinon.stub(); var actionStub2 = sinon.stub(); - actions1.register( - 'all', - 'Test', - OC.PERMISSION_READ, - OC.imagePath('core', 'actions/test'), - actionStub1 - ); + actions1.registerAction({ + mime: 'all', + name: 'Test', + type: OCA.Files.FileActions.TYPE_INLINE, + permissions: OC.PERMISSION_READ, + icon: OC.imagePath('core', 'actions/test'), + actionHandler: actionStub1 + }); actions1.merge(actions2); // late override - actions1.register( - 'all', - 'Test', // override - OC.PERMISSION_READ, - OC.imagePath('core', 'actions/test'), - actionStub2 - ); + actions1.registerAction({ + mime: 'all', + name: 'Test', // override + type: OCA.Files.FileActions.TYPE_INLINE, + permissions: OC.PERMISSION_READ, + icon: OC.imagePath('core', 'actions/test'), + actionHandler: actionStub2 + }); actions1.display($tr.find('td.filename'), true, fileList); @@ -403,25 +389,27 @@ describe('OCA.Files.FileActions tests', function() { var actions2 = new OCA.Files.FileActions(); var actionStub1 = sinon.stub(); var actionStub2 = sinon.stub(); - actions1.register( - 'all', - 'Test', - OC.PERMISSION_READ, - OC.imagePath('core', 'actions/test'), - actionStub1 - ); + actions1.registerAction({ + mime: 'all', + name: 'Test', + type: OCA.Files.FileActions.TYPE_INLINE, + permissions: OC.PERMISSION_READ, + icon: OC.imagePath('core', 'actions/test'), + actionHandler: actionStub1 + }); // copy the Test action to actions2 actions2.merge(actions1); // late override - actions2.register( - 'all', - 'Test', // override - OC.PERMISSION_READ, - OC.imagePath('core', 'actions/test'), - actionStub2 - ); + actions2.registerAction({ + mime: 'all', + name: 'Test', // override + type: OCA.Files.FileActions.TYPE_INLINE, + permissions: OC.PERMISSION_READ, + icon: OC.imagePath('core', 'actions/test'), + actionHandler: actionStub2 + }); // check if original actions still call the correct handler actions1.display($tr.find('td.filename'), true, fileList); @@ -444,42 +432,45 @@ describe('OCA.Files.FileActions tests', function() { it('notifies update event handlers once after multiple changes', function() { var actionStub = sinon.stub(); var handler = sinon.stub(); - FileActions.on('registerAction', handler); - FileActions.register( - 'all', - 'Test', - OC.PERMISSION_READ, - OC.imagePath('core', 'actions/test'), - actionStub - ); - FileActions.register( - 'all', - 'Test2', - OC.PERMISSION_READ, - OC.imagePath('core', 'actions/test'), - actionStub - ); + fileActions.on('registerAction', handler); + fileActions.registerAction({ + mime: 'all', + name: 'Test', + type: OCA.Files.FileActions.TYPE_INLINE, + permissions: OC.PERMISSION_READ, + icon: OC.imagePath('core', 'actions/test'), + actionHandler: actionStub + }); + fileActions.registerAction({ + mime: 'all', + name: 'Test2', + permissions: OC.PERMISSION_READ, + icon: OC.imagePath('core', 'actions/test'), + actionHandler: actionStub + }); expect(handler.calledTwice).toEqual(true); }); it('does not notifies update event handlers after unregistering', function() { var actionStub = sinon.stub(); var handler = sinon.stub(); - FileActions.on('registerAction', handler); - FileActions.off('registerAction', handler); - FileActions.register( - 'all', - 'Test', - OC.PERMISSION_READ, - OC.imagePath('core', 'actions/test'), - actionStub - ); - FileActions.register( - 'all', - 'Test2', - OC.PERMISSION_READ, - OC.imagePath('core', 'actions/test'), - actionStub - ); + fileActions.on('registerAction', handler); + fileActions.off('registerAction', handler); + fileActions.registerAction({ + mime: 'all', + name: 'Test', + type: OCA.Files.FileActions.TYPE_INLINE, + permissions: OC.PERMISSION_READ, + icon: OC.imagePath('core', 'actions/test'), + actionHandler: actionStub + }); + fileActions.registerAction({ + mime: 'all', + name: 'Test2', + type: OCA.Files.FileActions.TYPE_INLINE, + permissions: OC.PERMISSION_READ, + icon: OC.imagePath('core', 'actions/test'), + actionHandler: actionStub + }); expect(handler.notCalled).toEqual(true); }); }); diff --git a/apps/files/tests/js/fileactionsmenuSpec.js b/apps/files/tests/js/fileactionsmenuSpec.js new file mode 100644 index 00000000000..0cfd12a2d04 --- /dev/null +++ b/apps/files/tests/js/fileactionsmenuSpec.js @@ -0,0 +1,273 @@ +/** +* ownCloud +* +* @author Vincent Petry +* @copyright 2015 Vincent Petry <pvince81@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/>. +* +*/ + +describe('OCA.Files.FileActionsMenu tests', function() { + var fileList, fileActions, menu, actionStub, $tr; + + beforeEach(function() { + // init horrible parameters + var $body = $('#testArea'); + $body.append('<input type="hidden" id="dir" value="/subdir"></input>'); + $body.append('<input type="hidden" id="permissions" value="31"></input>'); + // dummy files table + actionStub = sinon.stub(); + fileActions = new OCA.Files.FileActions(); + fileList = new OCA.Files.FileList($body, { + fileActions: fileActions + }); + + fileActions.registerAction({ + name: 'Testdropdown', + displayName: 'Testdropdowndisplay', + mime: 'all', + permissions: OC.PERMISSION_READ, + icon: function () { + return OC.imagePath('core', 'actions/download'); + }, + actionHandler: actionStub + }); + + fileActions.registerAction({ + name: 'Testdropdownnoicon', + displayName: 'Testdropdowndisplaynoicon', + mime: 'all', + permissions: OC.PERMISSION_READ, + actionHandler: actionStub + }); + + fileActions.registerAction({ + name: 'Testinline', + displayName: 'Testinlinedisplay', + type: OCA.Files.FileActions.TYPE_INLINE, + mime: 'all', + permissions: OC.PERMISSION_READ + }); + + fileActions.registerAction({ + name: 'Testdefault', + displayName: 'Testdefaultdisplay', + mime: 'all', + permissions: OC.PERMISSION_READ + }); + fileActions.setDefault('all', 'Testdefault'); + + var fileData = { + id: 18, + type: 'file', + name: 'testName.txt', + mimetype: 'text/plain', + size: '1234', + etag: 'a01234c', + mtime: '123456' + }; + $tr = fileList.add(fileData); + + var menuContext = { + $file: $tr, + fileList: fileList, + fileActions: fileActions, + dir: fileList.getCurrentDirectory() + }; + menu = new OCA.Files.FileActionsMenu(); + menu.show(menuContext); + }); + afterEach(function() { + fileActions = null; + fileList.destroy(); + fileList = undefined; + menu.remove(); + $('#dir, #permissions, #filestable').remove(); + }); + + describe('rendering', function() { + it('renders dropdown actions in menu', function() { + var $action = menu.$el.find('a[data-action=Testdropdown]'); + expect($action.length).toEqual(1); + expect($action.find('img').attr('src')) + .toEqual(OC.imagePath('core', 'actions/download')); + expect($action.find('.no-icon').length).toEqual(0); + + $action = menu.$el.find('a[data-action=Testdropdownnoicon]'); + expect($action.length).toEqual(1); + expect($action.find('img').length).toEqual(0); + expect($action.find('.no-icon').length).toEqual(1); + }); + it('does not render default actions', function() { + expect(menu.$el.find('a[data-action=Testdefault]').length).toEqual(0); + }); + it('does not render inline actions', function() { + expect(menu.$el.find('a[data-action=Testinline]').length).toEqual(0); + }); + it('only renders actions relevant to the mime type', function() { + fileActions.registerAction({ + name: 'Match', + displayName: 'MatchDisplay', + mime: 'text/plain', + permissions: OC.PERMISSION_READ + }); + fileActions.registerAction({ + name: 'Nomatch', + displayName: 'NoMatchDisplay', + mime: 'application/octet-stream', + permissions: OC.PERMISSION_READ + }); + + menu.render(); + expect(menu.$el.find('a[data-action=Match]').length).toEqual(1); + expect(menu.$el.find('a[data-action=NoMatch]').length).toEqual(0); + }); + it('only renders actions relevant to the permissions', function() { + fileActions.registerAction({ + name: 'Match', + displayName: 'MatchDisplay', + mime: 'text/plain', + permissions: OC.PERMISSION_UPDATE + }); + fileActions.registerAction({ + name: 'Nomatch', + displayName: 'NoMatchDisplay', + mime: 'text/plain', + permissions: OC.PERMISSION_DELETE + }); + + menu.render(); + expect(menu.$el.find('a[data-action=Match]').length).toEqual(1); + expect(menu.$el.find('a[data-action=NoMatch]').length).toEqual(0); + }); + }); + + describe('action handler', function() { + it('calls action handler when clicking menu item', function() { + var $action = menu.$el.find('a[data-action=Testdropdown]'); + $action.click(); + + expect(actionStub.calledOnce).toEqual(true); + expect(actionStub.getCall(0).args[0]).toEqual('testName.txt'); + expect(actionStub.getCall(0).args[1].$file[0]).toEqual($tr[0]); + expect(actionStub.getCall(0).args[1].fileList).toEqual(fileList); + expect(actionStub.getCall(0).args[1].fileActions).toEqual(fileActions); + expect(actionStub.getCall(0).args[1].dir).toEqual('/subdir'); + }); + }); + describe('default actions from registerDefaultActions', function() { + beforeEach(function() { + fileActions.clear(); + fileActions.registerDefaultActions(); + }); + it('redirects to download URL when clicking download', function() { + var redirectStub = sinon.stub(OC, 'redirect'); + var fileData = { + id: 18, + type: 'file', + name: 'testName.txt', + mimetype: 'text/plain', + size: '1234', + etag: 'a01234c', + mtime: '123456' + }; + var $tr = fileList.add(fileData); + fileActions.display($tr.find('td.filename'), true, fileList); + + var menuContext = { + $file: $tr, + fileList: fileList, + fileActions: fileActions, + dir: fileList.getCurrentDirectory() + }; + menu = new OCA.Files.FileActionsMenu(); + menu.show(menuContext); + + menu.$el.find('.action-download').click(); + + expect(redirectStub.calledOnce).toEqual(true); + expect(redirectStub.getCall(0).args[0]).toContain( + OC.webroot + + '/index.php/apps/files/ajax/download.php' + + '?dir=%2Fsubdir&files=testName.txt'); + redirectStub.restore(); + }); + it('takes the file\'s path into account when clicking download', function() { + var redirectStub = sinon.stub(OC, 'redirect'); + var fileData = { + id: 18, + type: 'file', + name: 'testName.txt', + path: '/anotherpath/there', + mimetype: 'text/plain', + size: '1234', + etag: 'a01234c', + mtime: '123456' + }; + var $tr = fileList.add(fileData); + fileActions.display($tr.find('td.filename'), true, fileList); + + var menuContext = { + $file: $tr, + fileList: fileList, + fileActions: fileActions, + dir: '/anotherpath/there' + }; + menu = new OCA.Files.FileActionsMenu(); + menu.show(menuContext); + + menu.$el.find('.action-download').click(); + + expect(redirectStub.calledOnce).toEqual(true); + expect(redirectStub.getCall(0).args[0]).toContain( + OC.webroot + '/index.php/apps/files/ajax/download.php' + + '?dir=%2Fanotherpath%2Fthere&files=testName.txt' + ); + redirectStub.restore(); + }); + it('deletes file when clicking delete', function() { + var deleteStub = sinon.stub(fileList, 'do_delete'); + var fileData = { + id: 18, + type: 'file', + name: 'testName.txt', + path: '/somepath/dir', + mimetype: 'text/plain', + size: '1234', + etag: 'a01234c', + mtime: '123456' + }; + var $tr = fileList.add(fileData); + fileActions.display($tr.find('td.filename'), true, fileList); + + var menuContext = { + $file: $tr, + fileList: fileList, + fileActions: fileActions, + dir: '/somepath/dir' + }; + menu = new OCA.Files.FileActionsMenu(); + menu.show(menuContext); + + menu.$el.find('.action-delete').click(); + + expect(deleteStub.calledOnce).toEqual(true); + expect(deleteStub.getCall(0).args[0]).toEqual('testName.txt'); + expect(deleteStub.getCall(0).args[1]).toEqual('/somepath/dir'); + deleteStub.restore(); + }); + }); +}); + diff --git a/apps/files/tests/js/filelistSpec.js b/apps/files/tests/js/filelistSpec.js index 5c0c8c96bc5..57e16626403 100644 --- a/apps/files/tests/js/filelistSpec.js +++ b/apps/files/tests/js/filelistSpec.js @@ -456,19 +456,19 @@ describe('OCA.Files.FileList tests', function() { expect(notificationStub.notCalled).toEqual(true); }); - it('shows spinner on files to be deleted', function() { + it('shows busy state on files to be deleted', function() { fileList.setFiles(testFiles); doDelete(); - expect(fileList.findFileEl('One.txt').find('.icon-loading-small:not(.icon-delete)').length).toEqual(1); - expect(fileList.findFileEl('Three.pdf').find('.icon-delete:not(.icon-loading-small)').length).toEqual(1); + expect(fileList.findFileEl('One.txt').hasClass('busy')).toEqual(true); + expect(fileList.findFileEl('Three.pdf').hasClass('busy')).toEqual(false); }); - it('shows spinner on all files when deleting all', function() { + it('shows busy state on all files when deleting all', function() { fileList.setFiles(testFiles); fileList.do_delete(); - expect(fileList.$fileList.find('tr .icon-loading-small:not(.icon-delete)').length).toEqual(4); + expect(fileList.$fileList.find('tr.busy').length).toEqual(4); }); it('updates summary when deleting last file', function() { var $summary; @@ -625,7 +625,7 @@ describe('OCA.Files.FileList tests', function() { doCancelRename(); expect($summary.find('.info').text()).toEqual('1 folder and 3 files'); }); - it('Hides actions while rename in progress', function() { + it('Shows busy state while rename in progress', function() { var $tr; doRename(); @@ -634,8 +634,7 @@ describe('OCA.Files.FileList tests', function() { expect($tr.length).toEqual(1); expect(fileList.findFileEl('One.txt').length).toEqual(0); // file actions are hidden - expect($tr.find('.action').hasClass('hidden')).toEqual(true); - expect($tr.find('.fileactions').hasClass('hidden')).toEqual(true); + expect($tr.hasClass('busy')).toEqual(true); // input and form are gone expect(fileList.$fileList.find('input.filename').length).toEqual(0); @@ -1918,16 +1917,17 @@ describe('OCA.Files.FileList tests', function() { it('Clicking on a file name will trigger default action', function() { var actionStub = sinon.stub(); fileList.setFiles(testFiles); - fileList.fileActions.register( - 'text/plain', - 'Test', - OC.PERMISSION_ALL, - function() { + fileList.fileActions.registerAction({ + mime: 'text/plain', + name: 'Test', + type: OCA.Files.FileActions.TYPE_INLINE, + permissions: OC.PERMISSION_ALL, + icon: function() { // Specify icon for hitory button return OC.imagePath('core','actions/history'); }, - actionStub - ); + actionHandler: actionStub + }); fileList.fileActions.setDefault('text/plain', 'Test'); var $tr = fileList.findFileEl('One.txt'); $tr.find('td.filename .nametext').click(); @@ -1958,16 +1958,17 @@ describe('OCA.Files.FileList tests', function() { fileList.$fileList.on('fileActionsReady', readyHandler); - fileList.fileActions.register( - 'text/plain', - 'Test', - OC.PERMISSION_ALL, - function() { + fileList.fileActions.registerAction({ + mime: 'text/plain', + name: 'Test', + type: OCA.Files.FileActions.TYPE_INLINE, + permissions: OC.PERMISSION_ALL, + icon: function() { // Specify icon for hitory button return OC.imagePath('core','actions/history'); }, - actionStub - ); + actionHandler: actionStub + }); var $tr = fileList.findFileEl('One.txt'); expect($tr.find('.action-test').length).toEqual(0); expect(readyHandler.notCalled).toEqual(true); @@ -2256,6 +2257,8 @@ describe('OCA.Files.FileList tests', function() { }); }); describe('Handeling errors', function () { + var redirectStub; + beforeEach(function () { redirectStub = sinon.stub(OC, 'redirect'); @@ -2281,4 +2284,36 @@ describe('OCA.Files.FileList tests', function() { expect(redirectStub.calledWith(OC.generateUrl('apps/files'))).toEqual(true); }); }); + describe('showFileBusyState', function() { + var $tr; + + beforeEach(function() { + fileList.setFiles(testFiles); + $tr = fileList.findFileEl('Two.jpg'); + }); + it('shows spinner on busy rows', function() { + fileList.showFileBusyState('Two.jpg', true); + expect($tr.hasClass('busy')).toEqual(true); + expect(OC.TestUtil.getImageUrl($tr.find('.thumbnail'))) + .toEqual(OC.imagePath('core', 'loading.gif')); + + fileList.showFileBusyState('Two.jpg', false); + expect($tr.hasClass('busy')).toEqual(false); + expect(OC.TestUtil.getImageUrl($tr.find('.thumbnail'))) + .toEqual(OC.imagePath('core', 'filetypes/image.svg')); + }); + it('accepts multiple input formats', function() { + _.each([ + 'Two.jpg', + ['Two.jpg'], + $tr, + [$tr] + ], function(testCase) { + fileList.showFileBusyState(testCase, true); + expect($tr.hasClass('busy')).toEqual(true); + fileList.showFileBusyState(testCase, false); + expect($tr.hasClass('busy')).toEqual(false); + }); + }); + }); }); diff --git a/apps/files_sharing/js/share.js b/apps/files_sharing/js/share.js index 12bec0e8c9a..04700b84011 100644 --- a/apps/files_sharing/js/share.js +++ b/apps/files_sharing/js/share.js @@ -89,57 +89,59 @@ } }); - fileActions.register( - 'all', - 'Share', - OC.PERMISSION_SHARE, - OC.imagePath('core', 'actions/share'), - function(filename, context) { - - var $tr = context.$file; - var itemType = 'file'; - if ($tr.data('type') === 'dir') { - itemType = 'folder'; - } - var possiblePermissions = $tr.data('share-permissions'); - if (_.isUndefined(possiblePermissions)) { - possiblePermissions = $tr.data('permissions'); - } + fileActions.registerAction({ + name: 'Share', + displayName: '', + mime: 'all', + permissions: OC.PERMISSION_SHARE, + icon: OC.imagePath('core', 'actions/share'), + type: OCA.Files.FileActions.TYPE_INLINE, + actionHandler: function(filename, context) { + var $tr = context.$file; + var itemType = 'file'; + if ($tr.data('type') === 'dir') { + itemType = 'folder'; + } + var possiblePermissions = $tr.data('share-permissions'); + if (_.isUndefined(possiblePermissions)) { + possiblePermissions = $tr.data('permissions'); + } - var appendTo = $tr.find('td.filename'); - // Check if drop down is already visible for a different file - if (OC.Share.droppedDown) { - if ($tr.attr('data-id') !== $('#dropdown').attr('data-item-source')) { - OC.Share.hideDropDown(function () { - $tr.addClass('mouseOver'); - OC.Share.showDropDown(itemType, $tr.data('id'), appendTo, true, possiblePermissions, filename); - }); + var appendTo = $tr.find('td.filename'); + // Check if drop down is already visible for a different file + if (OC.Share.droppedDown) { + if ($tr.attr('data-id') !== $('#dropdown').attr('data-item-source')) { + OC.Share.hideDropDown(function () { + $tr.addClass('mouseOver'); + OC.Share.showDropDown(itemType, $tr.data('id'), appendTo, true, possiblePermissions, filename); + }); + } else { + OC.Share.hideDropDown(); + } } else { - OC.Share.hideDropDown(); + $tr.addClass('mouseOver'); + OC.Share.showDropDown(itemType, $tr.data('id'), appendTo, true, possiblePermissions, filename); } - } else { - $tr.addClass('mouseOver'); - OC.Share.showDropDown(itemType, $tr.data('id'), appendTo, true, possiblePermissions, filename); + $('#dropdown').on('sharesChanged', function(ev) { + // files app current cannot show recipients on load, so we don't update the + // icon when changed for consistency + if (context.fileList.$el.closest('#app-content-files').length) { + return; + } + var recipients = _.pluck(ev.shares[OC.Share.SHARE_TYPE_USER], 'share_with_displayname'); + var groupRecipients = _.pluck(ev.shares[OC.Share.SHARE_TYPE_GROUP], 'share_with_displayname'); + recipients = recipients.concat(groupRecipients); + // note: we only update the data attribute because updateIcon() + // is called automatically after this event + if (recipients.length) { + $tr.attr('data-share-recipients', OCA.Sharing.Util.formatRecipients(recipients)); + } + else { + $tr.removeAttr('data-share-recipients'); + } + }); } - $('#dropdown').on('sharesChanged', function(ev) { - // files app current cannot show recipients on load, so we don't update the - // icon when changed for consistency - if (context.fileList.$el.closest('#app-content-files').length) { - return; - } - var recipients = _.pluck(ev.shares[OC.Share.SHARE_TYPE_USER], 'share_with_displayname'); - var groupRecipients = _.pluck(ev.shares[OC.Share.SHARE_TYPE_GROUP], 'share_with_displayname'); - recipients = recipients.concat(groupRecipients); - // note: we only update the data attribute because updateIcon() - // is called automatically after this event - if (recipients.length) { - $tr.attr('data-share-recipients', OCA.Sharing.Util.formatRecipients(recipients)); - } - else { - $tr.removeAttr('data-share-recipients'); - } - }); - }, t('files_sharing', 'Share')); + }); OC.addScript('files_sharing', 'sharetabview').done(function() { fileList.registerTabView(new OCA.Sharing.ShareTabView('shareTabView')); diff --git a/apps/files_sharing/lib/activity.php b/apps/files_sharing/lib/activity.php index e531674ddc2..204c0a037b9 100644 --- a/apps/files_sharing/lib/activity.php +++ b/apps/files_sharing/lib/activity.php @@ -106,7 +106,7 @@ class Activity implements IExtension { self::TYPE_REMOTE_SHARE, ]; - if ($method === 'stream') { + if ($method === self::METHOD_STREAM) { $defaultTypes[] = self::TYPE_PUBLIC_LINKS; } diff --git a/apps/files_sharing/publicwebdav.php b/apps/files_sharing/publicwebdav.php index 5bde908109d..eec158dd4b6 100644 --- a/apps/files_sharing/publicwebdav.php +++ b/apps/files_sharing/publicwebdav.php @@ -33,24 +33,18 @@ OC_Util::obEnd(); // Backends $authBackend = new OCA\Files_Sharing\Connector\PublicAuth(\OC::$server->getConfig()); -// Fire up server -$objectTree = new \OC\Connector\Sabre\ObjectTree(); -$server = new \OC\Connector\Sabre\Server($objectTree); -// Set URL explicitly due to reverse-proxy situations -$server->httpRequest->setUrl(\OC::$server->getRequest()->getRequestUri()); -$server->setBaseUri($baseuri); +$serverFactory = new \OC\Connector\Sabre\ServerFactory( + \OC::$server->getConfig(), + \OC::$server->getLogger(), + \OC::$server->getDatabaseConnection(), + \OC::$server->getUserSession(), + \OC::$server->getMountManager(), + \OC::$server->getTagManager() +); -// Load plugins -$defaults = new OC_Defaults(); -$server->addPlugin(new \Sabre\DAV\Auth\Plugin($authBackend, $defaults->getName())); -// FIXME: The following line is a workaround for legacy components relying on being able to send a GET to / -$server->addPlugin(new \OC\Connector\Sabre\DummyGetResponsePlugin()); -$server->addPlugin(new \OC\Connector\Sabre\FilesPlugin($objectTree, true)); -$server->addPlugin(new \OC\Connector\Sabre\MaintenancePlugin(\OC::$server->getConfig())); -$server->addPlugin(new \OC\Connector\Sabre\ExceptionLoggerPlugin('webdav', \OC::$server->getLogger())); +$requestUri = \OC::$server->getRequest()->getRequestUri(); -// wait with registering these until auth is handled and the filesystem is setup -$server->on('beforeMethod', function () use ($server, $objectTree, $authBackend) { +$server = $serverFactory->createServer($baseuri, $requestUri, $authBackend, function () use ($authBackend) { if (OCA\Files_Sharing\Helper::isOutgoingServer2serverShareEnabled() === false) { // this is what is thrown when trying to access a non-existing share throw new \Sabre\DAV\Exception\NotAuthenticated(); @@ -72,20 +66,8 @@ $server->on('beforeMethod', function () use ($server, $objectTree, $authBackend) $ownerView = \OC\Files\Filesystem::getView(); $path = $ownerView->getPath($fileId); - $view = new \OC\Files\View($ownerView->getAbsolutePath($path)); - $rootInfo = $view->getFileInfo(''); - - // Create ownCloud Dir - if ($rootInfo->getType() === 'dir') { - $root = new \OC\Connector\Sabre\Directory($view, $rootInfo); - } else { - $root = new \OC\Connector\Sabre\File($view, $rootInfo); - } - $mountManager = \OC\Files\Filesystem::getMountManager(); - $objectTree->init($root, $view, $mountManager); - - $server->addPlugin(new \OC\Connector\Sabre\QuotaPlugin($view)); -}, 30); // priority 30: after auth (10) and acl(20), before lock(50) and handling the request + return new \OC\Files\View($ownerView->getAbsolutePath($path)); +}); // And off we go! $server->exec(); diff --git a/apps/files_sharing/templates/public.php b/apps/files_sharing/templates/public.php index ffe0472b2b1..2962f62520d 100644 --- a/apps/files_sharing/templates/public.php +++ b/apps/files_sharing/templates/public.php @@ -7,6 +7,7 @@ OCP\Util::addStyle('files_sharing', 'public'); OCP\Util::addStyle('files_sharing', 'mobile'); OCP\Util::addScript('files_sharing', 'public'); OCP\Util::addScript('files', 'fileactions'); +OCP\Util::addScript('files', 'fileactionsmenu'); OCP\Util::addScript('files', 'jquery.iframe-transport'); OCP\Util::addScript('files', 'jquery.fileupload'); diff --git a/apps/files_sharing/tests/js/shareSpec.js b/apps/files_sharing/tests/js/shareSpec.js index aa409285ca4..581e15caf93 100644 --- a/apps/files_sharing/tests/js/shareSpec.js +++ b/apps/files_sharing/tests/js/shareSpec.js @@ -97,7 +97,7 @@ describe('OCA.Sharing.Util tests', function() { }]); $tr = fileList.$el.find('tbody tr:first'); $action = $tr.find('.action-share'); - expect($action.hasClass('permanent')).toEqual(false); + expect($action.hasClass('permanent')).toEqual(true); expect(OC.basename($action.find('img').attr('src'))).toEqual('share.svg'); expect(OC.basename(getImageUrl($tr.find('.filename .thumbnail')))).toEqual('folder.svg'); expect($action.find('img').length).toEqual(1); @@ -257,7 +257,7 @@ describe('OCA.Sharing.Util tests', function() { $action = fileList.$el.find('tbody tr:first .action-share'); $tr = fileList.$el.find('tr:first'); - expect($action.hasClass('permanent')).toEqual(false); + expect($action.hasClass('permanent')).toEqual(true); $tr.find('.action-share').click(); @@ -344,7 +344,7 @@ describe('OCA.Sharing.Util tests', function() { expect($tr.attr('data-share-recipients')).not.toBeDefined(); OC.Share.updateIcon('file', 1); - expect($action.hasClass('permanent')).toEqual(false); + expect($action.hasClass('permanent')).toEqual(true); }); it('keep share text after updating reshare', function() { var $action, $tr; diff --git a/apps/files_trashbin/command/cleanup.php b/apps/files_trashbin/command/cleanup.php index 0cc94912339..60717abac18 100644 --- a/apps/files_trashbin/command/cleanup.php +++ b/apps/files_trashbin/command/cleanup.php @@ -108,7 +108,7 @@ class CleanUp extends Command { if ($this->rootFolder->nodeExists('/' . $uid . '/files_trashbin')) { $this->rootFolder->get('/' . $uid . '/files_trashbin')->delete(); $query = $this->dbConnection->getQueryBuilder(); - $query->delete('*PREFIX*files_trash') + $query->delete('files_trash') ->where($query->expr()->eq('user', $query->createParameter('uid'))) ->setParameter('uid', $uid); $query->execute(); diff --git a/apps/files_trashbin/js/app.js b/apps/files_trashbin/js/app.js index 315349d293c..473cce88a71 100644 --- a/apps/files_trashbin/js/app.js +++ b/apps/files_trashbin/js/app.js @@ -59,7 +59,6 @@ OCA.Trashbin.App = { fileActions.registerAction({ name: 'Delete', - displayName: '', mime: 'all', permissions: OC.PERMISSION_READ, icon: function() { diff --git a/apps/files_trashbin/tests/command/cleanuptest.php b/apps/files_trashbin/tests/command/cleanuptest.php index a7400e901fa..d4cccee448e 100644 --- a/apps/files_trashbin/tests/command/cleanuptest.php +++ b/apps/files_trashbin/tests/command/cleanuptest.php @@ -43,7 +43,7 @@ class CleanUpTest extends TestCase { protected $dbConnection; /** @var string */ - protected $trashTable = '*PREFIX*files_trash'; + protected $trashTable = 'files_trash'; /** @var string */ protected $user0 = 'user0'; diff --git a/apps/provisioning_api/appinfo/routes.php b/apps/provisioning_api/appinfo/routes.php index 323c8d609c7..2ee3a185dae 100644 --- a/apps/provisioning_api/appinfo/routes.php +++ b/apps/provisioning_api/appinfo/routes.php @@ -21,30 +21,45 @@ * */ -// Users +namespace OCA\Provisioning_API\AppInfo; + use OCP\API; -API::register('get', '/cloud/users', array('OCA\Provisioning_API\Users', 'getUsers'), 'provisioning_api', API::ADMIN_AUTH); -API::register('post', '/cloud/users', array('OCA\Provisioning_API\Users', 'addUser'), 'provisioning_api', API::ADMIN_AUTH); -API::register('get', '/cloud/users/{userid}', array('OCA\Provisioning_API\Users', 'getUser'), 'provisioning_api', API::USER_AUTH); -API::register('put', '/cloud/users/{userid}', array('OCA\Provisioning_API\Users', 'editUser'), 'provisioning_api', API::USER_AUTH); -API::register('delete', '/cloud/users/{userid}', array('OCA\Provisioning_API\Users', 'deleteUser'), 'provisioning_api', API::SUBADMIN_AUTH); -API::register('get', '/cloud/users/{userid}/groups', array('OCA\Provisioning_API\Users', 'getUsersGroups'), 'provisioning_api', API::USER_AUTH); -API::register('post', '/cloud/users/{userid}/groups', array('OCA\Provisioning_API\Users', 'addToGroup'), 'provisioning_api', API::SUBADMIN_AUTH); -API::register('delete', '/cloud/users/{userid}/groups', array('OCA\Provisioning_API\Users', 'removeFromGroup'), 'provisioning_api', API::SUBADMIN_AUTH); -API::register('post', '/cloud/users/{userid}/subadmins', array('OCA\Provisioning_API\Users', 'addSubAdmin'), 'provisioning_api', API::ADMIN_AUTH); -API::register('delete', '/cloud/users/{userid}/subadmins', array('OCA\Provisioning_API\Users', 'removeSubAdmin'), 'provisioning_api', API::ADMIN_AUTH); -API::register('get', '/cloud/users/{userid}/subadmins', array('OCA\Provisioning_API\Users', 'getUserSubAdminGroups'), 'provisioning_api', API::ADMIN_AUTH); +// Users +$users = new \OCA\Provisioning_API\Users( + \OC::$server->getUserManager(), + \OC::$server->getConfig(), + \OC::$server->getGroupManager(), + \OC::$server->getUserSession() +); +API::register('get', '/cloud/users', [$users, 'getUsers'], 'provisioning_api', API::ADMIN_AUTH); +API::register('post', '/cloud/users', [$users, 'addUser'], 'provisioning_api', API::ADMIN_AUTH); +API::register('get', '/cloud/users/{userid}', [$users, 'getUser'], 'provisioning_api', API::USER_AUTH); +API::register('put', '/cloud/users/{userid}', [$users, 'editUser'], 'provisioning_api', API::USER_AUTH); +API::register('delete', '/cloud/users/{userid}', [$users, 'deleteUser'], 'provisioning_api', API::SUBADMIN_AUTH); +API::register('get', '/cloud/users/{userid}/groups', [$users, 'getUsersGroups'], 'provisioning_api', API::USER_AUTH); +API::register('post', '/cloud/users/{userid}/groups', [$users, 'addToGroup'], 'provisioning_api', API::SUBADMIN_AUTH); +API::register('delete', '/cloud/users/{userid}/groups', [$users, 'removeFromGroup'], 'provisioning_api', API::SUBADMIN_AUTH); +API::register('post', '/cloud/users/{userid}/subadmins', [$users, 'addSubAdmin'], 'provisioning_api', API::ADMIN_AUTH); +API::register('delete', '/cloud/users/{userid}/subadmins', [$users, 'removeSubAdmin'], 'provisioning_api', API::ADMIN_AUTH); +API::register('get', '/cloud/users/{userid}/subadmins', [$users, 'getUserSubAdminGroups'], 'provisioning_api', API::ADMIN_AUTH); // Groups -API::register('get', '/cloud/groups', array('OCA\Provisioning_API\Groups', 'getGroups'), 'provisioning_api', API::SUBADMIN_AUTH); -API::register('post', '/cloud/groups', array('OCA\Provisioning_API\Groups', 'addGroup'), 'provisioning_api', API::SUBADMIN_AUTH); -API::register('get', '/cloud/groups/{groupid}', array('OCA\Provisioning_API\Groups', 'getGroup'), 'provisioning_api', API::SUBADMIN_AUTH); -API::register('delete', '/cloud/groups/{groupid}', array('OCA\Provisioning_API\Groups', 'deleteGroup'), 'provisioning_api', API::ADMIN_AUTH); -API::register('get', '/cloud/groups/{groupid}/subadmins', array('OCA\Provisioning_API\Groups', 'getSubAdminsOfGroup'), 'provisioning_api', API::ADMIN_AUTH); +$groups = new \OCA\Provisioning_API\Groups( + \OC::$server->getGroupManager(), + \OC::$server->getUserSession() +); +API::register('get', '/cloud/groups', [$groups, 'getGroups'], 'provisioning_api', API::SUBADMIN_AUTH); +API::register('post', '/cloud/groups', [$groups, 'addGroup'], 'provisioning_api', API::SUBADMIN_AUTH); +API::register('get', '/cloud/groups/{groupid}', [$groups, 'getGroup'], 'provisioning_api', API::SUBADMIN_AUTH); +API::register('delete', '/cloud/groups/{groupid}', [$groups, 'deleteGroup'], 'provisioning_api', API::ADMIN_AUTH); +API::register('get', '/cloud/groups/{groupid}/subadmins', [$groups, 'getSubAdminsOfGroup'], 'provisioning_api', API::ADMIN_AUTH); // Apps -API::register('get', '/cloud/apps', array('OCA\Provisioning_API\Apps', 'getApps'), 'provisioning_api', API::ADMIN_AUTH); -API::register('get', '/cloud/apps/{appid}', array('OCA\Provisioning_API\Apps', 'getAppInfo'), 'provisioning_api', API::ADMIN_AUTH); -API::register('post', '/cloud/apps/{appid}', array('OCA\Provisioning_API\Apps', 'enable'), 'provisioning_api', API::ADMIN_AUTH); -API::register('delete', '/cloud/apps/{appid}', array('OCA\Provisioning_API\Apps', 'disable'), 'provisioning_api', API::ADMIN_AUTH); +$apps = new \OCA\Provisioning_API\Apps( + \OC::$server->getAppManager() +); +API::register('get', '/cloud/apps', [$apps, 'getApps'], 'provisioning_api', API::ADMIN_AUTH); +API::register('get', '/cloud/apps/{appid}', [$apps, 'getAppInfo'], 'provisioning_api', API::ADMIN_AUTH); +API::register('post', '/cloud/apps/{appid}', [$apps, 'enable'], 'provisioning_api', API::ADMIN_AUTH); +API::register('delete', '/cloud/apps/{appid}', [$apps, 'disable'], 'provisioning_api', API::ADMIN_AUTH); diff --git a/apps/provisioning_api/lib/apps.php b/apps/provisioning_api/lib/apps.php index 22713865c1e..168f6f3cad8 100644 --- a/apps/provisioning_api/lib/apps.php +++ b/apps/provisioning_api/lib/apps.php @@ -28,7 +28,14 @@ use \OC_App; class Apps { - public static function getApps($parameters){ + /** @var \OCP\App\IAppManager */ + private $appManager; + + public function __construct(\OCP\App\IAppManager $appManager) { + $this->appManager = $appManager; + } + + public function getApps($parameters){ $apps = OC_App::listAllApps(); $list = array(); foreach($apps as $app) { @@ -55,9 +62,9 @@ class Apps { } } - public static function getAppInfo($parameters){ + public function getAppInfo($parameters){ $app = $parameters['appid']; - $info = OC_App::getAppInfo($app); + $info = \OCP\App::getAppInfo($app); if(!is_null($info)) { return new OC_OCS_Result(OC_App::getAppInfo($app)); } else { @@ -65,15 +72,15 @@ class Apps { } } - public static function enable($parameters){ + public function enable($parameters){ $app = $parameters['appid']; - OC_App::enable($app); + $this->appManager->enableApp($app); return new OC_OCS_Result(null, 100); } - public static function disable($parameters){ + public function disable($parameters){ $app = $parameters['appid']; - OC_App::disable($app); + $this->appManager->disableApp($app); return new OC_OCS_Result(null, 100); } diff --git a/apps/provisioning_api/lib/groups.php b/apps/provisioning_api/lib/groups.php index 81a5a6e5c30..91d0a1c6342 100644 --- a/apps/provisioning_api/lib/groups.php +++ b/apps/provisioning_api/lib/groups.php @@ -24,33 +24,65 @@ namespace OCA\Provisioning_API; use \OC_OCS_Result; -use \OC_Group; use \OC_SubAdmin; class Groups{ + /** @var \OCP\IGroupManager */ + private $groupManager; + + /** @var \OCP\IUserSession */ + private $userSession; + + /** + * @param \OCP\IGroupManager $groupManager + * @param \OCP\IUserSession $userSession + */ + public function __construct(\OCP\IGroupManager $groupManager, + \OCP\IUserSession $userSession) { + $this->groupManager = $groupManager; + $this->userSession = $userSession; + } + /** * returns a list of groups */ - public static function getGroups($parameters){ + public function getGroups($parameters){ $search = !empty($_GET['search']) ? $_GET['search'] : ''; $limit = !empty($_GET['limit']) ? $_GET['limit'] : null; $offset = !empty($_GET['offset']) ? $_GET['offset'] : null; - return new OC_OCS_Result(array('groups' => OC_Group::getGroups($search, $limit, $offset))); + + $groups = $this->groupManager->search($search, $limit, $offset); + $groups = array_map(function($group) { + return $group->getGID(); + }, $groups); + + return new OC_OCS_Result(['groups' => $groups]); } /** * returns an array of users in the group specified */ - public static function getGroup($parameters){ + public function getGroup($parameters) { + // Check if user is logged in + $user = $this->userSession->getUser(); + if ($user === null) { + return new OC_OCS_Result(null, \OCP\API::RESPOND_UNAUTHORISED); + } + // Check the group exists - if(!OC_Group::groupExists($parameters['groupid'])){ + if(!$this->groupManager->groupExists($parameters['groupid'])){ return new OC_OCS_Result(null, \OCP\API::RESPOND_NOT_FOUND, 'The requested group could not be found'); } // Check subadmin has access to this group - if(\OC_User::isAdminUser(\OC_User::getUser()) - || in_array($parameters['groupid'], \OC_SubAdmin::getSubAdminsGroups(\OC_User::getUser()))){ - return new OC_OCS_Result(array('users' => OC_Group::usersInGroup($parameters['groupid']))); + if($this->groupManager->isAdmin($user->getUID()) + || in_array($parameters['groupid'], \OC_SubAdmin::getSubAdminsGroups($user->getUID()))){ + $users = $this->groupManager->get($parameters['groupid'])->getUsers(); + $users = array_map(function($user) { + return $user->getUID(); + }, $users); + $users = array_values($users); + return new OC_OCS_Result(['users' => $users]); } else { return new OC_OCS_Result(null, \OCP\API::RESPOND_UNAUTHORISED, 'User does not have access to specified group'); } @@ -59,7 +91,7 @@ class Groups{ /** * creates a new group */ - public static function addGroup($parameters){ + public function addGroup($parameters){ // Validate name $groupid = isset($_POST['groupid']) ? $_POST['groupid'] : ''; if( preg_match( '/[^a-zA-Z0-9 _\.@\-]/', $groupid ) || empty($groupid)){ @@ -67,21 +99,18 @@ class Groups{ return new OC_OCS_Result(null, 101, 'Invalid group name'); } // Check if it exists - if(OC_Group::groupExists($groupid)){ + if($this->groupManager->groupExists($groupid)){ return new OC_OCS_Result(null, 102); } - if(OC_Group::createGroup($groupid)){ - return new OC_OCS_Result(null, 100); - } else { - return new OC_OCS_Result(null, 103); - } + $this->groupManager->createGroup($groupid); + return new OC_OCS_Result(null, 100); } - public static function deleteGroup($parameters){ + public function deleteGroup($parameters){ // Check it exists - if(!OC_Group::groupExists($parameters['groupid'])){ + if(!$this->groupManager->groupExists($parameters['groupid'])){ return new OC_OCS_Result(null, 101); - } else if($parameters['groupid'] == 'admin' || !OC_Group::deleteGroup($parameters['groupid'])){ + } else if($parameters['groupid'] === 'admin' || !$this->groupManager->get($parameters['groupid'])->delete()){ // Cannot delete admin group return new OC_OCS_Result(null, 102); } else { @@ -89,10 +118,10 @@ class Groups{ } } - public static function getSubAdminsOfGroup($parameters) { + public function getSubAdminsOfGroup($parameters) { $group = $parameters['groupid']; // Check group exists - if(!OC_Group::groupExists($group)) { + if(!$this->groupManager->groupExists($group)) { return new OC_OCS_Result(null, 101, 'Group does not exist'); } // Go diff --git a/apps/provisioning_api/lib/users.php b/apps/provisioning_api/lib/users.php index fada85b293d..f5b201a55ea 100644 --- a/apps/provisioning_api/lib/users.php +++ b/apps/provisioning_api/lib/users.php @@ -27,32 +27,64 @@ namespace OCA\Provisioning_API; use \OC_OCS_Result; use \OC_SubAdmin; -use \OC_User; -use \OC_Group; use \OC_Helper; use OCP\Files\NotFoundException; class Users { + /** @var \OCP\IUserManager */ + private $userManager; + + /** @var \OCP\IConfig */ + private $config; + + /** @var \OCP\IGroupManager */ + private $groupManager; + + /** @var \OCP\IUserSession */ + private $userSession; + + /** + * @param \OCP\IUserManager $userManager + * @param \OCP\IConfig $config + * @param \OCP\IGroupManager $groupManager + * @param \OCP\IUserSession $user + */ + public function __construct(\OCP\IUserManager $userManager, + \OCP\IConfig $config, + \OCP\IGroupManager $groupManager, + \OCP\IUserSession $userSession) { + $this->userManager = $userManager; + $this->config = $config; + $this->groupManager = $groupManager; + $this->userSession = $userSession; + } + /** * returns a list of users */ - public static function getUsers(){ + public function getUsers(){ $search = !empty($_GET['search']) ? $_GET['search'] : ''; $limit = !empty($_GET['limit']) ? $_GET['limit'] : null; $offset = !empty($_GET['offset']) ? $_GET['offset'] : null; - return new OC_OCS_Result(array('users' => OC_User::getUsers($search, $limit, $offset))); + + $users = $this->userManager->search($search, $limit, $offset); + $users = array_keys($users); + + return new OC_OCS_Result([ + 'users' => $users + ]); } - public static function addUser(){ + public function addUser(){ $userId = isset($_POST['userid']) ? $_POST['userid'] : null; $password = isset($_POST['password']) ? $_POST['password'] : null; - if(OC_User::userExists($userId)) { + if($this->userManager->userExists($userId)) { \OCP\Util::writeLog('ocs_api', 'Failed addUser attempt: User already exists.', \OCP\Util::ERROR); return new OC_OCS_Result(null, 102, 'User already exists'); } else { try { - OC_User::createUser($userId, $password); + $this->userManager->createUser($userId, $password); \OCP\Util::writeLog('ocs_api', 'Successful addUser call with userid: '.$_POST['userid'], \OCP\Util::INFO); return new OC_OCS_Result(null, 100); } catch (\Exception $e) { @@ -65,25 +97,32 @@ class Users { /** * gets user info */ - public static function getUser($parameters){ + public function getUser($parameters){ $userId = $parameters['userid']; + + // Check if user is logged in + $user = $this->userSession->getUser(); + if ($user === null) { + return new OC_OCS_Result(null, \OCP\API::RESPOND_UNAUTHORISED); + } + // Admin? Or SubAdmin? - if(OC_User::isAdminUser(OC_User::getUser()) || OC_SubAdmin::isUserAccessible(OC_User::getUser(), $userId)) { + if($this->groupManager->isAdmin($user->getUID()) || OC_SubAdmin::isUserAccessible($user->getUID(), $userId)) { // Check they exist - if(!OC_User::userExists($userId)) { + if(!$this->userManager->userExists($userId)) { return new OC_OCS_Result(null, \OCP\API::RESPOND_NOT_FOUND, 'The requested user could not be found'); } // Show all - $return = array( + $return = [ 'email', 'enabled', - ); - if(OC_User::getUser() != $userId) { + ]; + if($user->getUID() !== $userId) { $return[] = 'quota'; } } else { // Check they are looking up themselves - if(OC_User::getUser() != $userId) { + if($user->getUID() !== $userId) { return new OC_OCS_Result(null, \OCP\API::RESPOND_UNAUTHORISED); } // Return some additional information compared to the core route @@ -93,14 +132,12 @@ class Users { ); } - $config = \OC::$server->getConfig(); - // Find the data $data = []; $data = self::fillStorageInfo($userId, $data); - $data['enabled'] = $config->getUserValue($userId, 'core', 'enabled', 'true'); - $data['email'] = $config->getUserValue($userId, 'settings', 'email'); - $data['displayname'] = OC_User::getDisplayName($parameters['userid']); + $data['enabled'] = $this->config->getUserValue($userId, 'core', 'enabled', 'true'); + $data['email'] = $this->config->getUserValue($userId, 'settings', 'email'); + $data['displayname'] = $this->userManager->get($parameters['userid'])->getDisplayName(); // Return the appropriate data $responseData = array(); @@ -114,21 +151,28 @@ class Users { /** * edit users */ - public static function editUser($parameters){ + public function editUser($parameters){ $userId = $parameters['userid']; - if($userId === OC_User::getUser()) { + + // Check if user is logged in + $user = $this->userSession->getUser(); + if ($user === null) { + return new OC_OCS_Result(null, \OCP\API::RESPOND_UNAUTHORISED); + } + + if($userId === $user->getUID()) { // Editing self (display, email) $permittedFields[] = 'display'; $permittedFields[] = 'email'; $permittedFields[] = 'password'; // If admin they can edit their own quota - if(OC_User::isAdminUser(OC_User::getUser())) { + if($this->groupManager->isAdmin($user->getUID())) { $permittedFields[] = 'quota'; } } else { // Check if admin / subadmin - if(OC_SubAdmin::isUserAccessible(OC_User::getUser(), $userId) - || OC_User::isAdminUser(OC_User::getUser())) { + if(OC_SubAdmin::isUserAccessible($user->getUID(), $userId) + || $this->groupManager->isAdmin($user->getUID())) { // They have permissions over the user $permittedFields[] = 'display'; $permittedFields[] = 'quota'; @@ -146,7 +190,7 @@ class Users { // Process the edit switch($parameters['_put']['key']){ case 'display': - OC_User::setDisplayName($userId, $parameters['_put']['value']); + $this->userManager->get($userId)->setDisplayName($parameters['_put']['value']); break; case 'quota': $quota = $parameters['_put']['value']; @@ -154,27 +198,27 @@ class Users { if (is_numeric($quota)) { $quota = floatval($quota); } else { - $quota = OC_Helper::computerFileSize($quota); + $quota = \OCP\Util::computerFileSize($quota); } if ($quota === false) { return new OC_OCS_Result(null, 103, "Invalid quota value {$parameters['_put']['value']}"); } - if($quota == 0) { + if($quota === 0) { $quota = 'default'; - }else if($quota == -1){ + }else if($quota === -1){ $quota = 'none'; } else { - $quota = OC_Helper::humanFileSize($quota); + $quota = \OCP\Util::humanFileSize($quota); } } - \OC::$server->getConfig()->setUserValue($userId, 'files', 'quota', $quota); + $this->config->setUserValue($userId, 'files', 'quota', $quota); break; case 'password': - OC_User::setPassword($userId, $parameters['_put']['value']); + $this->userManager->get($userId)->setPassword($parameters['_put']['value']); break; case 'email': if(filter_var($parameters['_put']['value'], FILTER_VALIDATE_EMAIL)) { - \OC::$server->getConfig()->setUserValue($userId, 'settings', 'email', $parameters['_put']['value']); + $this->config->setUserValue($userId, 'settings', 'email', $parameters['_put']['value']); } else { return new OC_OCS_Result(null, 102); } @@ -186,32 +230,53 @@ class Users { return new OC_OCS_Result(null, 100); } - public static function deleteUser($parameters){ - if(!OC_User::userExists($parameters['userid']) - || $parameters['userid'] === OC_User::getUser()) { + public function deleteUser($parameters){ + // Check if user is logged in + $user = $this->userSession->getUser(); + if ($user === null) { + return new OC_OCS_Result(null, \OCP\API::RESPOND_UNAUTHORISED); + } + + if(!$this->userManager->userExists($parameters['userid']) + || $parameters['userid'] === $user->getUID()) { return new OC_OCS_Result(null, 101); } // If not permitted - if(!OC_User::isAdminUser(OC_User::getUser()) && !OC_SubAdmin::isUserAccessible(OC_User::getUser(), $parameters['userid'])) { + if(!$this->groupManager->isAdmin($user->getUID()) && !OC_SubAdmin::isUserAccessible($user->getUID(), $parameters['userid'])) { return new OC_OCS_Result(null, 997); } // Go ahead with the delete - if(OC_User::deleteUser($parameters['userid'])) { + if($this->userManager->get($parameters['userid'])->delete()) { return new OC_OCS_Result(null, 100); } else { return new OC_OCS_Result(null, 101); } } - public static function getUsersGroups($parameters){ - if($parameters['userid'] === OC_User::getUser() || OC_User::isAdminUser(OC_User::getUser())) { + public function getUsersGroups($parameters) { + // Check if user is logged in + $user = $this->userSession->getUser(); + if ($user === null) { + return new OC_OCS_Result(null, \OCP\API::RESPOND_UNAUTHORISED); + } + + if($parameters['userid'] === $user->getUID() || $this->groupManager->isAdmin($user->getUID())) { // Self lookup or admin lookup - return new OC_OCS_Result(array('groups' => OC_Group::getUserGroups($parameters['userid']))); + return new OC_OCS_Result([ + 'groups' => $this->groupManager->getUserGroupIds( + $this->userManager->get($parameters['userid']) + ) + ]); } else { // Looking up someone else - if(OC_SubAdmin::isUserAccessible(OC_User::getUser(), $parameters['userid'])) { + if(OC_SubAdmin::isUserAccessible($user->getUID(), $parameters['userid'])) { // Return the group that the method caller is subadmin of for the user in question - $groups = array_intersect(OC_SubAdmin::getSubAdminsGroups(OC_User::getUser()), OC_Group::getUserGroups($parameters['userid'])); + $groups = array_intersect( + OC_SubAdmin::getSubAdminsGroups($user->getUID()), + $this->groupManager->getUserGroupIds( + $this->userManager->get($parameters['userid']) + ) + ); return new OC_OCS_Result(array('groups' => $groups)); } else { // Not permitted @@ -221,78 +286,96 @@ class Users { } - public static function addToGroup($parameters){ + public function addToGroup($parameters){ + // Check if user is logged in + $user = $this->userSession->getUser(); + if ($user === null) { + return new OC_OCS_Result(null, \OCP\API::RESPOND_UNAUTHORISED); + } + $group = !empty($_POST['groupid']) ? $_POST['groupid'] : null; if(is_null($group)){ return new OC_OCS_Result(null, 101); } // Check they're an admin - if(!OC_Group::inGroup(OC_User::getUser(), 'admin')){ + if(!$this->groupManager->isInGroup($user->getUID(), 'admin')){ // This user doesn't have rights to add a user to this group return new OC_OCS_Result(null, \OCP\API::RESPOND_UNAUTHORISED); } // Check if the group exists - if(!OC_Group::groupExists($group)){ + if(!$this->groupManager->groupExists($group)){ return new OC_OCS_Result(null, 102); } // Check if the user exists - if(!OC_User::userExists($parameters['userid'])){ + if(!$this->userManager->userExists($parameters['userid'])){ return new OC_OCS_Result(null, 103); } // Add user to group - return OC_Group::addToGroup($parameters['userid'], $group) ? new OC_OCS_Result(null, 100) : new OC_OCS_Result(null, 105); + $this->groupManager->get($group)->addUser( + $this->userManager->get($parameters['userid']) + ); + return new OC_OCS_Result(null, 100); } - public static function removeFromGroup($parameters){ + public function removeFromGroup($parameters) { + // Check if user is logged in + $user = $this->userSession->getUser(); + if ($user === null) { + return new OC_OCS_Result(null, \OCP\API::RESPOND_UNAUTHORISED); + } + $group = !empty($parameters['_delete']['groupid']) ? $parameters['_delete']['groupid'] : null; if(is_null($group)){ return new OC_OCS_Result(null, 101); } // If they're not an admin, check they are a subadmin of the group in question - if(!OC_Group::inGroup(OC_User::getUser(), 'admin') && !OC_SubAdmin::isSubAdminofGroup(OC_User::getUser(), $group)){ + if(!$this->groupManager->isInGroup($user->getUID(), 'admin') && !OC_SubAdmin::isSubAdminofGroup($user->getUID(), $group)){ return new OC_OCS_Result(null, 104); } // Check they aren't removing themselves from 'admin' or their 'subadmin; group - if($parameters['userid'] === OC_User::getUser()){ - if(OC_Group::inGroup(OC_User::getUser(), 'admin')){ + if($parameters['userid'] === $user->getUID()){ + if($this->groupManager->isInGroup($user->getUID(), 'admin')){ if($group === 'admin'){ return new OC_OCS_Result(null, 105, 'Cannot remove yourself from the admin group'); } } else { // Not an admin, check they are not removing themself from their subadmin group - if(in_array($group, OC_SubAdmin::getSubAdminsGroups(OC_User::getUser()))){ + if(in_array($group, OC_SubAdmin::getSubAdminsGroups($user->getUID()))){ return new OC_OCS_Result(null, 105, 'Cannot remove yourself from this group as you are a SubAdmin'); } } } // Check if the group exists - if(!OC_Group::groupExists($group)){ + if(!$this->groupManager->groupExists($group)){ return new OC_OCS_Result(null, 102); } // Check if the user exists - if(!OC_User::userExists($parameters['userid'])){ + if(!$this->userManager->userExists($parameters['userid'])){ return new OC_OCS_Result(null, 103); } // Remove user from group - return OC_Group::removeFromGroup($parameters['userid'], $group) ? new OC_OCS_Result(null, 100) : new OC_OCS_Result(null, 105); + $this->groupManager->get($group)->removeUser( + $this->userManager->get($parameters['userid']) + ); + return new OC_OCS_Result(null, 100); } /** * Creates a subadmin */ - public static function addSubAdmin($parameters) { + public function addSubAdmin($parameters) { $group = $_POST['groupid']; $user = $parameters['userid']; // Check if the user exists - if(!OC_User::userExists($user)) { + if(!$this->userManager->userExists($user)) { return new OC_OCS_Result(null, 101, 'User does not exist'); } // Check if group exists - if(!OC_Group::groupExists($group)) { + if(!$this->groupManager->groupExists($group)) { return new OC_OCS_Result(null, 102, 'Group:'.$group.' does not exist'); } // Check if trying to make subadmin of admin group - if(strtolower($group) == 'admin') { + if(strtolower($group) === 'admin') { return new OC_OCS_Result(null, 103, 'Cannot create subadmins for admin group'); } // We cannot be subadmin twice @@ -311,11 +394,11 @@ class Users { /** * Removes a subadmin from a group */ - public static function removeSubAdmin($parameters) { + public function removeSubAdmin($parameters) { $group = $parameters['_delete']['groupid']; $user = $parameters['userid']; // Check if the user exists - if(!OC_User::userExists($user)) { + if(!$this->userManager->userExists($user)) { return new OC_OCS_Result(null, 101, 'User does not exist'); } // Check if they are a subadmin of this said group @@ -333,10 +416,10 @@ class Users { /** * @Get the groups a user is a subadmin of */ - public static function getUserSubAdminGroups($parameters) { + public function getUserSubAdminGroups($parameters) { $user = $parameters['userid']; // Check if the user exists - if(!OC_User::userExists($user)) { + if(!$this->userManager->userExists($user)) { return new OC_OCS_Result(null, 101, 'User does not exist'); } // Get the subadmin groups diff --git a/apps/provisioning_api/tests/appstest.php b/apps/provisioning_api/tests/appstest.php index c4298f017fc..f2a3977eac4 100644 --- a/apps/provisioning_api/tests/appstest.php +++ b/apps/provisioning_api/tests/appstest.php @@ -25,8 +25,17 @@ namespace OCA\Provisioning_API\Tests; class AppsTest extends TestCase { + + public function setup() { + parent::setup(); + $this->appManager = \OC::$server->getAppManager(); + $this->groupManager = \OC::$server->getGroupManager(); + $this->userSession = \OC::$server->getUserSession(); + $this->api = new \OCA\Provisioning_API\Apps($this->appManager); + } + public function testGetAppInfo() { - $result = \OCA\provisioning_API\Apps::getAppInfo(array('appid' => 'provisioning_api')); + $result = $this->api->getAppInfo(['appid' => 'provisioning_api']); $this->assertInstanceOf('OC_OCS_Result', $result); $this->assertTrue($result->succeeded()); @@ -34,7 +43,7 @@ class AppsTest extends TestCase { public function testGetAppInfoOnBadAppID() { - $result = \OCA\provisioning_API\Apps::getAppInfo(array('appid' => 'not_provisioning_api')); + $result = $this->api->getAppInfo(['appid' => 'not_provisioning_api']); $this->assertInstanceOf('OC_OCS_Result', $result); $this->assertFalse($result->succeeded()); $this->assertEquals(\OCP\API::RESPOND_NOT_FOUND, $result->getStatusCode()); @@ -44,10 +53,10 @@ class AppsTest extends TestCase { public function testGetApps() { $user = $this->generateUsers(); - \OC_Group::addToGroup($user, 'admin'); - self::loginAsUser($user); + $this->groupManager->get('admin')->addUser($user); + $this->userSession->setUser($user); - $result = \OCA\provisioning_API\Apps::getApps(array()); + $result = $this->api->getApps([]); $this->assertTrue($result->succeeded()); $data = $result->getData(); @@ -58,7 +67,7 @@ class AppsTest extends TestCase { public function testGetAppsEnabled() { $_GET['filter'] = 'enabled'; - $result = \OCA\provisioning_API\Apps::getApps(array('filter' => 'enabled')); + $result = $this->api->getApps(['filter' => 'enabled']); $this->assertTrue($result->succeeded()); $data = $result->getData(); $this->assertEquals(count(\OC_App::getEnabledApps()), count($data['apps'])); @@ -68,7 +77,7 @@ class AppsTest extends TestCase { public function testGetAppsDisabled() { $_GET['filter'] = 'disabled'; - $result = \OCA\provisioning_API\Apps::getApps(array('filter' => 'disabled')); + $result = $this->api->getApps(['filter' => 'disabled']); $this->assertTrue($result->succeeded()); $data = $result->getData(); $apps = \OC_App::listAllApps(); @@ -78,6 +87,12 @@ class AppsTest extends TestCase { } $disabled = array_diff($list, \OC_App::getEnabledApps()); $this->assertEquals(count($disabled), count($data['apps'])); + } + public function testGetAppsInvalidFilter() { + $_GET['filter'] = 'foo'; + $result = $this->api->getApps([]); + $this->assertFalse($result->succeeded()); + $this->assertEquals(101, $result->getStatusCode()); } } diff --git a/apps/provisioning_api/tests/groupstest.php b/apps/provisioning_api/tests/groupstest.php index b8b02790698..73044e33120 100644 --- a/apps/provisioning_api/tests/groupstest.php +++ b/apps/provisioning_api/tests/groupstest.php @@ -24,18 +24,79 @@ namespace OCA\Provisioning_API\Tests; +use OCP\IUserManager; +use OCP\IGroupManager; +use OCP\IUserSession; + class GroupsTest extends TestCase { + + /** @var IUserManager */ + protected $userManager; + + /** @var IGroupManager */ + protected $groupManager; + + /** @var IUserSession */ + protected $userSession; + + protected function setup() { + parent::setup(); + + $this->userManager = \OC::$server->getUserManager(); + $this->groupManager = \OC::$server->getGroupManager(); + $this->userSession = \OC::$server->getUserSession(); + $this->api = new \OCA\Provisioning_API\Groups( + $this->groupManager, + $this->userSession + ); + } + + public function testGetGroups() { + $groups = []; + $id = $this->getUniqueID(); + + for ($i=0; $i < 10; $i++) { + $groups[] = $this->groupManager->createGroup($id . '_' . $i); + } + + $_GET = []; + $result = $this->api->getGroups([]); + $this->assertInstanceOf('OC_OCS_Result', $result); + $this->assertTrue($result->succeeded()); + $this->assertCount(count($this->groupManager->search('')), $result->getData()['groups']); + $this->assertContains('admin', $result->getData()['groups']); + foreach ($groups as $group) { + $this->assertContains($group->getGID(), $result->getData()['groups']); + } + + $_GET = [ + 'search' => $id, + 'limit' => 5, + 'offset' => 2 + ]; + $result = $this->api->getGroups([]); + $this->assertInstanceOf('OC_OCS_Result', $result); + $this->assertTrue($result->succeeded()); + $this->assertCount(5, $result->getData()['groups']); + foreach (array_splice($groups, 2, 5) as $group) { + $this->assertContains($group->getGID(), $result->getData()['groups']); + } + + foreach ($groups as $group) { + $group->delete(); + } + } + public function testGetGroupAsUser() { $users = $this->generateUsers(2); - self::loginAsUser($users[0]); + $this->userSession->setUser($users[0]); - $group = $this->getUniqueID(); - \OC_Group::createGroup($group); - \OC_Group::addToGroup($users[1], $group); + $group = $this->groupManager->createGroup($this->getUniqueID()); + $group->addUser($users[1]); - $result = \OCA\provisioning_api\Groups::getGroup(array( - 'groupid' => $group, + $result = $this->api->getGroup(array( + 'groupid' => $group->getGID(), )); $this->assertInstanceOf('OC_OCS_Result', $result); @@ -47,18 +108,17 @@ class GroupsTest extends TestCase { public function testGetGroupAsSubadmin() { $users = $this->generateUsers(2); - self::loginAsUser($users[0]); + $this->userSession->setUser($users[0]); - $group = $this->getUniqueID(); - \OC_Group::createGroup($group); - \OC_Group::addToGroup($users[0], $group); - \OC_Group::addToGroup($users[1], $group); + $group = $this->groupManager->createGroup($this->getUniqueID()); + $group->addUser($users[0]); + $group->addUser($users[1]); - \OC_SubAdmin::createSubAdmin($users[0], $group); + \OC_SubAdmin::createSubAdmin($users[0]->getUID(), $group->getGID()); - $result = \OCA\provisioning_api\Groups::getGroup(array( - 'groupid' => $group, - )); + $result = $this->api->getGroup([ + 'groupid' => $group->getGID(), + ]); $this->assertInstanceOf('OC_OCS_Result', $result); $this->assertTrue($result->succeeded()); @@ -67,6 +127,10 @@ class GroupsTest extends TestCase { $resultData = $result->getData(); $resultData = $resultData['users']; + $users = array_map(function($user) { + return $user->getUID(); + }, $users); + sort($users); sort($resultData); $this->assertEquals($users, $resultData); @@ -76,20 +140,18 @@ class GroupsTest extends TestCase { public function testGetGroupAsIrrelevantSubadmin() { $users = $this->generateUsers(2); - self::loginAsUser($users[0]); + $this->userSession->setUser($users[0]); - $group = $this->getUniqueID(); - \OC_Group::createGroup($group); - $group2 = $this->getUniqueID(); - \OC_Group::createGroup($group2); - \OC_Group::addToGroup($users[1], $group); - \OC_Group::addToGroup($users[0], $group2); + $group1 = $this->groupManager->createGroup($this->getUniqueID()); + $group2 = $this->groupManager->createGroup($this->getUniqueID()); + $group1->addUser($users[1]); + $group2->addUser($users[0]); - \OC_SubAdmin::createSubAdmin($users[0], $group2); + \OC_SubAdmin::createSubAdmin($users[0]->getUID(), $group2->getGID()); - $result = \OCA\provisioning_api\Groups::getGroup(array( - 'groupid' => $group, - )); + $result = $this->api->getGroup([ + 'groupid' => $group1->getGID(), + ]); $this->assertInstanceOf('OC_OCS_Result', $result); $this->assertFalse($result->succeeded()); @@ -100,49 +162,129 @@ class GroupsTest extends TestCase { public function testGetGroupAsAdmin() { $users = $this->generateUsers(2); - self::loginAsUser($users[0]); + $this->userSession->setUser($users[0]); - $group = $this->getUniqueID(); - \OC_Group::createGroup($group); + $group = $this->groupManager->createGroup($this->getUniqueID()); - \OC_Group::addToGroup($users[1], $group); - \OC_Group::addToGroup($users[0], 'admin'); + $group->addUser($users[1]); + $this->groupManager->get('admin')->addUser($users[0]); - $result = \OCA\provisioning_api\Groups::getGroup(array( - 'groupid' => $group, - )); + $result = $this->api->getGroup([ + 'groupid' => $group->getGID(), + ]); $this->assertInstanceOf('OC_OCS_Result', $result); $this->assertTrue($result->succeeded()); - $this->assertEquals(array('users' => array($users[1])), $result->getData()); + $this->assertEquals(['users' => [$users[1]->getUID()]], $result->getData()); + + } + + public function testGetGroupNonExisting() { + $result = $this->api->getGroup([ + 'groupid' => $this->getUniqueId() + ]); + $this->assertInstanceOf('OC_OCS_Result', $result); + $this->assertFalse($result->succeeded()); + $this->assertEquals(\OCP\API::RESPOND_NOT_FOUND, $result->getStatusCode()); + $this->assertEquals('The requested group could not be found', $result->getMeta()['message']); } public function testGetSubAdminsOfGroup() { $user1 = $this->generateUsers(); $user2 = $this->generateUsers(); - self::loginAsUser($user1); - \OC_Group::addToGroup($user1, 'admin'); - $group1 = $this->getUniqueID(); - \OC_Group::createGroup($group1); - \OC_SubAdmin::createSubAdmin($user2, $group1); - $result = \OCA\provisioning_api\Groups::getSubAdminsOfGroup(array( - 'groupid' => $group1, - )); + $this->userSession->setUser($user1); + $this->groupManager->get('admin')->addUser($user1); + $group1 = $this->groupManager->createGroup($this->getUniqueID()); + \OC_SubAdmin::createSubAdmin($user2->getUID(), $group1->getGID()); + $result = $this->api->getSubAdminsOfGroup([ + 'groupid' => $group1->getGID(), + ]); $this->assertInstanceOf('OC_OCS_Result', $result); $this->assertTrue($result->succeeded()); $data = $result->getData(); - $this->assertEquals($user2, reset($data)); - \OC_Group::deleteGroup($group1); + $this->assertEquals($user2->getUID(), reset($data)); + $group1->delete(); $user1 = $this->generateUsers(); - self::loginAsUser($user1); - \OC_Group::addToGroup($user1, 'admin'); - $result = \OCA\provisioning_api\Groups::getSubAdminsOfGroup(array( + $this->userSession->setUser($user1); + $this->groupManager->get('admin')->addUser($user1); + $result = $this->api->getSubAdminsOfGroup([ 'groupid' => $this->getUniqueID(), - )); + ]); $this->assertInstanceOf('OC_OCS_Result', $result); $this->assertFalse($result->succeeded()); $this->assertEquals(101, $result->getStatusCode()); } + + public function testAddGroupEmptyGroup() { + $_POST = []; + $result = $this->api->addGroup([]); + + $this->assertInstanceOf('OC_OCS_Result', $result); + $this->assertFalse($result->succeeded()); + $this->assertEquals(101, $result->getStatusCode()); + $this->assertEquals('Invalid group name', $result->getMeta()['message']); + } + + public function testAddGroupExistingGroup() { + $group = $this->groupManager->createGroup($this->getUniqueID()); + + $_POST = [ + 'groupid' => $group->getGID() + ]; + $result = $this->api->addGroup([]); + + $this->assertInstanceOf('OC_OCS_Result', $result); + $this->assertFalse($result->succeeded()); + $this->assertEquals(102, $result->getStatusCode()); + + $group->delete(); + } + + public function testAddGroup() { + $group = $this->getUniqueId(); + + $_POST = [ + 'groupid' => $group + ]; + + $result = $this->api->addGroup([]); + $this->assertInstanceOf('OC_OCS_Result', $result); + $this->assertTrue($result->succeeded()); + $this->assertTrue($this->groupManager->groupExists($group)); + + $this->groupManager->get($group)->delete(); + } + + public function testDeleteGroupNonExisting() { + $group = $this->getUniqueId(); + + $result = $this->api->deleteGroup([ + 'groupid' => $group + ]); + $this->assertInstanceOf('OC_OCS_Result', $result); + $this->assertFalse($result->succeeded()); + $this->assertEquals(101, $result->getStatusCode()); + } + + public function testDeleteAdminGroup() { + $result = $this->api->deleteGroup([ + 'groupid' => 'admin' + ]); + $this->assertInstanceOf('OC_OCS_Result', $result); + $this->assertFalse($result->succeeded()); + $this->assertEquals(102, $result->getStatusCode()); + } + + public function testDeleteGroup() { + $group = $this->groupManager->createGroup($this->getUniqueId()); + + $result = $this->api->deleteGroup([ + 'groupid' => $group->getGID() + ]); + $this->assertInstanceOf('OC_OCS_Result', $result); + $this->assertTrue($result->succeeded()); + $this->assertFalse($this->groupManager->groupExists($group->getGID())); + } } diff --git a/apps/provisioning_api/tests/testcase.php b/apps/provisioning_api/tests/testcase.php index 3d0468daa12..ee7eb2a5a9a 100644 --- a/apps/provisioning_api/tests/testcase.php +++ b/apps/provisioning_api/tests/testcase.php @@ -22,12 +22,24 @@ namespace OCA\Provisioning_API\Tests; +use OCP\IUserManager; +use OCP\IGroupManager; + abstract class TestCase extends \Test\TestCase { protected $users = array(); + /** @var IUserManager */ + protected $userManager; + + /** @var IGroupManager */ + protected $groupManager; + protected function setUp() { parent::setUp(); - \OC_Group::createGroup('admin'); + + $this->userManager = \OC::$server->getUserManager(); + $this->groupManager = \OC::$server->getGroupManager(); + $this->groupManager->createGroup('admin'); } /** @@ -38,8 +50,7 @@ abstract class TestCase extends \Test\TestCase { protected function generateUsers($num = 1) { $users = array(); for ($i = 0; $i < $num; $i++) { - $user = $this->getUniqueID(); - \OC_User::createUser($user, 'password'); + $user = $this->userManager->createUser($this->getUniqueID(), 'password'); $this->users[] = $user; $users[] = $user; } @@ -48,11 +59,10 @@ abstract class TestCase extends \Test\TestCase { protected function tearDown() { foreach($this->users as $user) { - \OC_User::deleteUser($user); + $user->delete(); } - \OC_Group::deleteGroup('admin'); - + $this->groupManager->get('admin')->delete(); parent::tearDown(); } } diff --git a/apps/provisioning_api/tests/userstest.php b/apps/provisioning_api/tests/userstest.php index f2862565039..350586f8335 100644 --- a/apps/provisioning_api/tests/userstest.php +++ b/apps/provisioning_api/tests/userstest.php @@ -26,34 +26,67 @@ namespace OCA\Provisioning_API\Tests; +use OCP\IUserManager; +use OCP\IConfig; +use OCP\IGroupManager; +use OCP\IUserSession; + class UsersTest extends TestCase { + + /** @var IUserManager */ + protected $userManager; + + /** @var IConfig */ + protected $config; + + /** @var IGroupManager */ + protected $groupManager; + + /** @var IUserSession */ + protected $userSession; + protected function resetParams() { $_GET = null; $_POST = null; } + protected function setup() { + parent::setup(); + + $this->userManager = \OC::$server->getUserManager(); + $this->config = \OC::$server->getConfig(); + $this->groupManager = \OC::$server->getGroupManager(); + $this->userSession = \OC::$server->getUserSession(); + $this->api = new \OCA\Provisioning_Api\Users( + $this->userManager, + $this->config, + $this->groupManager, + $this->userSession + ); + } + // Test getting the list of users public function testGetUsers() { - $result = \OCA\provisioning_API\Users::getUsers(array()); + $result = $this->api->getUsers(); $this->assertInstanceOf('OC_OCS_Result', $result); $this->assertTrue($result->succeeded()); $count = $result->getData(); $count = count($count['users']); - $this->assertEquals(count(\OC_User::getUsers()), $count); + $this->assertEquals(count($this->userManager->search('', null, null)), $count); $user = $this->generateUsers(); - $_GET['search'] = $user; - $result = \OCA\provisioning_API\Users::getUsers(array()); + $_GET['search'] = $user->getUID(); + $result = $this->api->getUsers(); $this->assertInstanceOf('OC_OCS_Result', $result); $this->assertTrue($result->succeeded()); $data = $result->getData(); - $this->assertEquals($user, reset($data['users'])); + $this->assertEquals($user->getUID(), reset($data['users'])); // Add several users $this->generateUsers(10); $this->resetParams(); $_GET['limit'] = 2; - $result = \OCA\provisioning_API\Users::getUsers(array()); + $result = $this->api->getUsers(); $this->assertInstanceOf('OC_OCS_Result', $result); $this->assertTrue($result->succeeded()); $count = $result->getData(); @@ -63,45 +96,93 @@ class UsersTest extends TestCase { $this->resetParams(); $_GET['limit'] = 1; $_GET['offset'] = 1; - $result = \OCA\provisioning_API\Users::getUsers(array()); + $result = $this->api->getUsers(array()); $this->assertInstanceOf('OC_OCS_Result', $result); $this->assertTrue($result->succeeded()); $data = $result->getData(); - $this->assertEquals(\OC_User::getUsers('', 1, 1), $data['users']); + $this->assertEquals(array_keys($this->userManager->search('', 1, 1)), $data['users']); } public function testAddUser() { $this->resetParams(); $_POST['userid'] = $this->getUniqueID(); $_POST['password'] = 'password'; - $result = \OCA\provisioning_API\Users::addUser(array()); + $result = $this->api->addUser(); $this->assertInstanceOf('OC_OCS_Result', $result); $this->assertTrue($result->succeeded()); - $this->assertTrue(\OC_User::userExists($_POST['userid'])); - $this->assertEquals($_POST['userid'], \OC_User::checkPassword($_POST['userid'], $_POST['password'])); - $this->users[] = $_POST['userid']; + $this->assertTrue($this->userManager->userExists($_POST['userid'])); + $this->assertEquals($_POST['userid'], $this->userManager->checkPassword($_POST['userid'], $_POST['password'])->getUID()); + $this->users[] = $this->userManager->get($_POST['userid']); + } + + public function testAddUserTwice() { + $this->resetParams(); + $_POST['userid'] = $this->getUniqueID(); + $_POST['password'] = 'password'; + $this->api->addUser(); + $result = $this->api->addUser(); + $this->assertInstanceOf('OC_OCS_Result', $result); + $this->assertFalse($result->succeeded()); + $this->assertEquals(102, $result->getStatusCode()); + $this->assertEquals('User already exists', $result->getMeta()['message']); + } + + public function testAddUserFails() { + $uid = $this->getUniqueID(); + + $userManager = $this->getMockBuilder('\OCP\IUserManager') + ->disableOriginalConstructor() + ->getMock(); + + $userManager->expects($this->once()) + ->method('userExists') + ->with($uid) + ->willReturn(false); + $userManager->expects($this->once()) + ->method('createUser') + ->with($uid, 'password') + ->will($this->throwException(new \Exception)); + + $api = new \OCA\Provisioning_Api\Users( + $userManager, + $this->config, + $this->groupManager, + $this->userSession + ); + + $this->resetParams(); + $_POST['userid'] = $uid; + $_POST['password'] = 'password'; + $result = $api->addUser(); + $this->assertInstanceOf('OC_OCS_Result', $result); + $this->assertFalse($result->succeeded()); + $this->assertEquals(101, $result->getStatusCode()); + $this->assertEquals('Bad request', $result->getMeta()['message']); } public function testGetUserOnSelf() { $user = $this->generateUsers(); - self::loginAsUser($user); - $params['userid'] = $user; - $result = \OCA\provisioning_API\Users::getUser($params); + $user->setDisplayName('foobar'); + $this->userSession->setUser($user); + $params['userid'] = $user->getUID(); + $result = $this->api->getUser($params); $this->assertInstanceOf('OC_OCS_Result', $result); $this->assertTrue($result->succeeded()); $data = $result->getData(); + + $this->assertEquals('foobar', $data['displayname']); } public function testGetUserOnNonExistingUser() { $user = $this->generateUsers(); - \OC_Group::addToGroup($user, 'admin'); - self::loginAsUser($user); + $this->groupManager->get('admin')->addUser($user); + $this->userSession->setUser($user); $params = array(); $params['userid'] = $this->getUniqueID(); - while(\OC_User::userExists($params['userid'])) { + while($this->userManager->userExists($params['userid'])) { $params['userid'] = $this->getUniqueID(); } - $result = \OCA\provisioning_API\Users::getUser($params); + $result = $this->api->getUser($params); $this->assertInstanceOf('OC_OCS_Result', $result); $this->assertFalse($result->succeeded()); $this->assertEquals(\OCP\API::RESPOND_NOT_FOUND, $result->getStatusCode()); @@ -111,33 +192,32 @@ class UsersTest extends TestCase { public function testGetUserOnOtherUser() { $users = $this->generateUsers(2); $params['userid'] = $users[0]; - self::loginAsUser($users[1]); - $result = \OCA\provisioning_API\Users::getUser($params); + $this->userSession->setUser($users[1]); + $result = $this->api->getUser($params); $this->assertInstanceOf('OC_OCS_Result', $result); $this->assertFalse($result->succeeded()); // Now as as admin $users = $this->generateUsers(2); - $params['userid'] = $users[0]; + $params['userid'] = $users[0]->getUID(); // login to generate home - self::loginAsUser($users[0]); - \OC_Group::addToGroup($users[1], 'admin'); - self::loginAsUser($users[1]); - $result = \OCA\provisioning_API\Users::getUser($params); + $this->userSession->setUser($users[0]); + $this->groupManager->get('admin')->addUser($users[1]); + $this->userSession->setUser($users[1]); + $result = $this->api->getUser($params); $this->assertInstanceOf('OC_OCS_Result', $result); $this->assertTrue($result->succeeded()); $data = $result->getData(); - $this->assertEquals(\OC::$server->getConfig()->getUserValue($users[0], 'core', 'enabled', 'true'), $data['enabled']); + $this->assertEquals(\OC::$server->getConfig()->getUserValue($users[0]->getUID(), 'core', 'enabled', 'true'), $data['enabled']); } public function testEditOwnDisplayName() { - // Test editing own name $user = $this->generateUsers(); - self::loginAsUser($user); - $result = \OCA\provisioning_API\Users::editUser( + $this->userSession->setUser($user); + $result = $this->api->editUser( array( - 'userid' => $user, + 'userid' => $user->getUID(), '_put' => array( 'key' => 'display', 'value' => 'newname', @@ -146,41 +226,39 @@ class UsersTest extends TestCase { ); $this->assertInstanceOf('OC_OCS_Result', $result); $this->assertTrue($result->succeeded()); - $this->assertEquals('newname', \OC_User::getDisplayName($user)); + $this->assertEquals('newname', $user->getDisplayName()); } public function testAdminEditDisplayNameOfUser() { - // Test admin editing users name $user = $this->generateUsers(); - \OC_Group::addToGroup($user, 'admin'); - self::loginAsUser($user); + $this->groupManager->get('admin')->addUser($user); + $this->userSession->setUser($user); $user2 = $this->generateUsers(); - $result = \OCA\provisioning_API\Users::editUser( - array( - 'userid' => $user2, - '_put' => array( + $result = $this->api->editUser( + [ + 'userid' => $user2->getUID(), + '_put' => [ 'key' => 'display', 'value' => 'newname', - ), - ) + ], + ] ); $this->assertInstanceOf('OC_OCS_Result', $result); $this->assertTrue($result->succeeded()); - $this->assertEquals('newname', \OC_User::getDisplayName($user2)); + $this->assertEquals('newname', $user2->getDisplayName()); } public function testUserEditOtherUserDisplayName() { - // Test editing other users name $user = $this->generateUsers(); - self::loginAsUser($user); + $this->userSession->setUser($user); $user2 = $this->generateUsers(); - $result = \OCA\provisioning_API\Users::editUser( + $result = $this->api->editUser( array( - 'userid' => $user2, + 'userid' => $user2->getUID(), '_put' => array( 'key' => 'display', 'value' => 'newname', @@ -199,11 +277,33 @@ class UsersTest extends TestCase { */ public function testEditOwnQuota($expected, $quota) { $user = $this->generateUsers(); - \OC_Group::addToGroup($user, 'admin'); - self::loginAsUser($user); - $result = \OCA\provisioning_API\Users::editUser( + $this->userSession->setUser($user); + $result = $this->api->editUser( + [ + 'userid' => $user->getUID(), + '_put' => [ + 'key' => 'quota', + 'value' => $quota, + ], + ] + ); + $this->assertInstanceOf('OC_OCS_Result', $result); + $this->assertFalse($result->succeeded()); + $this->assertEquals(997, $result->getStatusCode()); + } + + /** + * @dataProvider providesQuotas + * @param $expected + * @param $quota + */ + public function testEditOwnQuotaAsAdmin($expected, $quota) { + $user = $this->generateUsers(); + $this->groupManager->get('admin')->addUser($user); + $this->userSession->setUser($user); + $result = $this->api->editUser( [ - 'userid' => $user, + 'userid' => $user->getUID(), '_put' => [ 'key' => 'quota', 'value' => $quota, @@ -221,16 +321,18 @@ class UsersTest extends TestCase { [true, 'none'], [true, 'default'], [false, 'qwertzu'], + [true, 0], + [true, -1] ]; } public function testAdminEditOwnQuota() { $user = $this->generateUsers(); - \OC_Group::addToGroup($user, 'admin'); - self::loginAsUser($user); - $result = \OCA\provisioning_API\Users::editUser( + $this->groupManager->get('admin')->addUser($user); + $this->userSession->setUser($user); + $result = $this->api->editUser( array( - 'userid' => $user, + 'userid' => $user->getUID(), '_put' => array( 'key' => 'quota', 'value' => '20G', @@ -243,12 +345,12 @@ class UsersTest extends TestCase { public function testAdminEditOtherUserQuota() { $user = $this->generateUsers(); - \OC_Group::addToGroup($user, 'admin'); - self::loginAsUser($user); + $this->groupManager->get('admin')->addUser($user); + $this->userSession->setUser($user); $user2 = $this->generateUsers(); - $result = \OCA\provisioning_API\Users::editUser( + $result = $this->api->editUser( array( - 'userid' => $user2, + 'userid' => $user2->getUID(), '_put' => array( 'key' => 'quota', 'value' => '20G', @@ -261,11 +363,11 @@ class UsersTest extends TestCase { public function testUserEditOtherUserQuota() { $user = $this->generateUsers(); - self::loginAsUser($user); + $this->userSession->setUser($user); $user2 = $this->generateUsers(); - $result = \OCA\provisioning_API\Users::editUser( + $result = $this->api->editUser( array( - 'userid' => $user2, + 'userid' => $user2->getUID(), '_put' => array( 'key' => 'quota', 'value' => '20G', @@ -279,10 +381,10 @@ class UsersTest extends TestCase { public function testUserEditOwnEmail() { $user = $this->generateUsers(); $email = 'test@example.com'; - self::loginAsUser($user); - $result = \OCA\provisioning_API\Users::editUser( + $this->userSession->setUser($user); + $result = $this->api->editUser( array( - 'userid' => $user, + 'userid' => $user->getUID(), '_put' => array( 'key' => 'email', 'value' => $email, @@ -291,16 +393,32 @@ class UsersTest extends TestCase { ); $this->assertInstanceOf('OC_OCS_Result', $result); $this->assertTrue($result->succeeded()); - $this->assertEquals($email, \OC::$server->getConfig()->getUserValue($user, 'settings', 'email', null)); + $this->assertEquals($email, \OC::$server->getConfig()->getUserValue($user->getUID(), 'settings', 'email', null)); + } + + public function testUserEditOwnEmailInvalid() { + $user = $this->generateUsers(); + $email = 'test@example'; + $this->userSession->setUser($user); + $result = $this->api->editUser([ + 'userid' => $user->getUID(), + '_put' => [ + 'key' => 'email', + 'value' => $email, + ], + ]); + $this->assertInstanceOf('OC_OCS_Result', $result); + $this->assertFalse($result->succeeded()); + $this->assertEquals(102, $result->getStatusCode()); } public function testUserEditOtherUserEmailAsUser() { $users = $this->generateUsers(2); $email = 'test@example.com'; - self::loginAsUser($users[0]); - $result = \OCA\provisioning_API\Users::editUser( + $this->userSession->setUser($users[0]); + $result = $this->api->editUser( array( - 'userid' => $users[1], + 'userid' => $users[1]->getUID(), '_put' => array( 'key' => 'email', 'value' => $email, @@ -314,11 +432,11 @@ class UsersTest extends TestCase { public function testUserEditOtherUserEmailAsAdmin() { $users = $this->generateUsers(2); $email = 'test@example.com'; - self::loginAsUser($users[0]); - \OC_Group::addToGroup($users[0], 'admin'); - $result = \OCA\provisioning_API\Users::editUser( + $this->userSession->setUser($users[0]); + $this->groupManager->get('admin')->addUser($users[0]); + $result = $this->api->editUser( array( - 'userid' => $users[1], + 'userid' => $users[1]->getUID(), '_put' => array( 'key' => 'email', 'value' => $email, @@ -327,14 +445,60 @@ class UsersTest extends TestCase { ); $this->assertInstanceOf('OC_OCS_Result', $result); $this->assertTrue($result->succeeded()); - $this->assertEquals($email, \OC::$server->getConfig()->getUserValue($users[1], 'settings', 'email', null)); + $this->assertEquals($email, \OC::$server->getConfig()->getUserValue($users[1]->getUID(), 'settings', 'email', null)); + } + + public function testUserEditOwnPassword() { + $user = $this->generateUsers(); + $password = 'foo'; + $this->userSession->setUser($user); + $result = $this->api->editUser([ + 'userid' => $user->getUID(), + '_put' => [ + 'key' => 'password', + 'value' => $password, + ], + ]); + $this->assertInstanceOf('OC_OCS_Result', $result); + $this->assertTrue($result->succeeded()); + } + + public function testUserEditOtherUserPasswordAsUser() { + $users = $this->generateUsers(2); + $password = 'foo'; + $this->userSession->setUser($users[0]); + $result = $this->api->editUser([ + 'userid' => $users[1]->getUID(), + '_put' => [ + 'key' => 'password', + 'value' => $password, + ], + ]); + $this->assertInstanceOf('OC_OCS_Result', $result); + $this->assertFalse($result->succeeded()); + } + + public function testUserEditOtherUserPasswordAsAdmin() { + $users = $this->generateUsers(2); + $password = 'foo'; + $this->userSession->setUser($users[0]); + $this->groupManager->get('admin')->addUser($users[0]); + $result = $this->api->editUser([ + 'userid' => $users[1]->getUID(), + '_put' => [ + 'key' => 'password', + 'value' => $password, + ], + ]); + $this->assertInstanceOf('OC_OCS_Result', $result); + $this->assertTrue($result->succeeded()); } public function testDeleteSelf() { $user = $this->generateUsers(); - self::loginAsUser($user); - $result = \OCA\provisioning_API\Users::deleteUser(array( - 'userid' => $user, + $this->userSession->setUser($user); + $result = $this->api->deleteUser(array( + 'userid' => $user->getUID(), )); $this->assertInstanceOf('OC_OCS_Result', $result); $this->assertFalse($result->succeeded()); @@ -342,10 +506,10 @@ class UsersTest extends TestCase { public function testDeleteOtherAsUser() { $user = $this->generateUsers(); - self::loginAsUser($user); + $this->userSession->setUser($user); $user2 = $this->generateUsers(); - $result = \OCA\provisioning_API\Users::deleteUser(array( - 'userid' => $user2, + $result = $this->api->deleteUser(array( + 'userid' => $user2->getUID(), )); $this->assertInstanceOf('OC_OCS_Result', $result); $this->assertFalse($result->succeeded()); @@ -353,48 +517,45 @@ class UsersTest extends TestCase { public function testDeleteOtherAsSubAdmin() { $user = $this->generateUsers(); - self::loginAsUser($user); + $this->userSession->setUser($user); $user2 = $this->generateUsers(); - $group = $this->getUniqueID(); - \OC_Group::createGroup($group); - \OC_Group::addToGroup($user, $group); - \OC_Group::addToGroup($user2, $group); - \OC_SubAdmin::createSubAdmin($user, $group); - $result = \OCA\provisioning_API\Users::deleteUser(array( - 'userid' => $user2, + $group = $this->groupManager->createGroup($this->getUniqueID()); + $group->addUser($user); + $group->addUser($user2); + \OC_SubAdmin::createSubAdmin($user->getUID(), $group->getGID()); + $result = $this->api->deleteUser(array( + 'userid' => $user2->getUID(), )); $this->assertInstanceOf('OC_OCS_Result', $result); $this->assertTrue($result->succeeded()); - \OC_Group::deleteGroup($group); + $group->delete(); } public function testDeleteOtherAsIrelevantSubAdmin() { $user = $this->generateUsers(); - self::loginAsUser($user); + $this->userSession->setUser($user); $user2 = $this->generateUsers(); - $group = $this->getUniqueID(); - $group2 = $this->getUniqueID(); - \OC_Group::createGroup($group); - \OC_Group::createGroup($group2); - \OC_Group::addToGroup($user, $group); - \OC_Group::addToGroup($user2, $group2); - \OC_SubAdmin::createSubAdmin($user, $group); - $result = \OCA\provisioning_API\Users::deleteUser(array( - 'userid' => $user2, - )); + $group = $this->groupManager->createGroup($this->getUniqueID()); + $group2 = $this->groupManager->createGroup($this->getUniqueID()); + $group->addUser($user); + $group2->addUser($user2); + \OC_SubAdmin::createSubAdmin($user->getUID(), $group->getGID()); + $result = $this->api->deleteUser(array( + 'userid' => $user2->getUID(), + )); $this->assertInstanceOf('OC_OCS_Result', $result); $this->assertFalse($result->succeeded()); - \OC_Group::deleteGroup($group); - \OC_Group::deleteGroup($group2); + $group->delete(); + $group2->delete(); } public function testDeleteOtherAsAdmin() { $user = $this->generateUsers(); - \OC_Group::addToGroup($user, 'admin'); - self::loginAsUser($user); + $this->groupManager->get('admin')->addUser($user); + $this->userSession->setUser($user); $user2 = $this->generateUsers(); - $result = \OCA\provisioning_API\Users::deleteUser(array( - 'userid' => $user2, + $result = $this->api->deleteUser(array( + 'userid' => $user2->getUID(), )); $this->assertInstanceOf('OC_OCS_Result', $result); $this->assertTrue($result->succeeded()); @@ -402,288 +563,485 @@ class UsersTest extends TestCase { public function testDeleteSelfAsAdmin() { $user = $this->generateUsers(); - \OC_Group::addToGroup($user, 'admin'); - self::loginAsUser($user); - $result = \OCA\provisioning_API\Users::deleteUser(array( - 'userid' => $user, + $this->groupManager->get('admin')->addUser($user); + $this->userSession->setUser($user); + $result = $this->api->deleteUser(array( + 'userid' => $user->getUID(), )); $this->assertInstanceOf('OC_OCS_Result', $result); $this->assertFalse($result->succeeded()); } + public function testDeleteFails() { + $user = $this->getMockBuilder('\OCP\IUser') + ->disableOriginalConstructor() + ->getMock(); + $user->expects($this->once()) + ->method('delete') + ->willReturn(false); + + $user2 = $this->getMockBuilder('\OCP\IUser') + ->disableOriginalConstructor() + ->getMock(); + $user2->expects($this->any()) + ->method('getUID') + ->willReturn('user2'); + + $userManager = $this->getMockBuilder('\OCP\IUserManager') + ->disableOriginalConstructor() + ->getMock(); + $userManager->expects($this->once()) + ->method('userExists') + ->with('user') + ->willReturn(true); + $userManager->expects($this->once()) + ->method('get') + ->with('user') + ->willReturn($user); + + $userSession = $this->getMockBuilder('\OCP\IUserSession') + ->disableOriginalConstructor() + ->getMock(); + $userSession->expects($this->once()) + ->method('getUser') + ->willReturn($user2); + + $groupManager = $this->getMockBuilder('\OCP\IGroupManager') + ->disableOriginalConstructor() + ->getMock(); + $groupManager->expects($this->once()) + ->method('isAdmin') + ->with('user2') + ->willReturn(true); + + $api = new \OCA\Provisioning_Api\Users( + $userManager, + $this->config, + $groupManager, + $userSession + ); + + $result = $api->deleteUser([ + 'userid' => 'user', + ]); + $this->assertInstanceOf('OC_OCS_Result', $result); + $this->assertFalse($result->succeeded()); + $this->assertEquals(101, $result->getStatusCode()); + } + public function testGetUsersGroupsOnSelf() { $user = $this->generateUsers(); - self::loginAsUser($user); + $this->userSession->setUser($user); $group = $this->getUniqueID(); - \OC_Group::createGroup($group); - \OC_Group::addToGroup($user, $group); - $result = \OCA\provisioning_API\Users::getUsersGroups(array( - 'userid' => $user, + $group = $this->groupManager->createGroup($group); + $group->addUser($user); + $result = $this->api->getUsersGroups(array( + 'userid' => $user->getUID(), )); $this->assertInstanceOf('OC_OCS_Result', $result); $this->assertTrue($result->succeeded()); $data = $result->getData(); - $this->assertEquals($group, reset($data['groups'])); + $this->assertEquals($group->getGID(), reset($data['groups'])); $this->assertEquals(1, count($data['groups'])); - \OC_Group::deleteGroup($group); + $group->delete(); } public function testGetUsersGroupOnOther() { $user1 = $this->generateUsers(); $user2 = $this->generateUsers(); - self::loginAsUser($user1); + $this->userSession->setUser($user1); $group = $this->getUniqueID(); - \OC_Group::createGroup($group); - \OC_Group::addToGroup($user2, $group); - $result = \OCA\provisioning_API\Users::getUsersGroups(array( - 'userid' => $user2, + $group = $this->groupManager->createGroup($group); + $group->addUser($user2); + $result = $this->api->getUsersGroups(array( + 'userid' => $user2->getUID(), )); $this->assertInstanceOf('OC_OCS_Result', $result); $this->assertFalse($result->succeeded()); - \OC_Group::deleteGroup($group); + $group->delete(); } public function testGetUsersGroupOnOtherAsAdmin() { $user1 = $this->generateUsers(); - \OC_Group::addToGroup($user1, 'admin'); + $this->groupManager->get('admin')->addUser($user1); $user2 = $this->generateUsers(); - self::loginAsUser($user1); + $this->userSession->setUser($user1); $group = $this->getUniqueID(); - \OC_Group::createGroup($group); - \OC_Group::addToGroup($user2, $group); - $result = \OCA\provisioning_API\Users::getUsersGroups(array( - 'userid' => $user2, + $group = $this->groupManager->createGroup($group); + $group->addUser($user2); + $result = $this->api->getUsersGroups(array( + 'userid' => $user2->getUID(), )); $this->assertInstanceOf('OC_OCS_Result', $result); $this->assertTrue($result->succeeded()); $data = $result->getData(); - $this->assertEquals($group, reset($data['groups'])); + $this->assertEquals($group->getGID(), reset($data['groups'])); $this->assertEquals(1, count($data['groups'])); - \OC_Group::deleteGroup($group); + $group->delete(); } public function testGetUsersGroupsOnOtherAsSubAdmin() { $user1 = $this->generateUsers(); $user2 = $this->generateUsers(); - self::loginAsUser($user1); + $this->userSession->setUser($user1); $group1 = $this->getUniqueID(); $group2 = $this->getUniqueID(); - \OC_Group::createGroup($group1); - \OC_Group::createGroup($group2); - \OC_Group::addToGroup($user2, $group1); - \OC_Group::addToGroup($user2, $group2); - \OC_Group::addToGroup($user1, $group1); - \OC_SubAdmin::createSubAdmin($user1, $group1); - $result = \OCA\provisioning_API\Users::getUsersGroups(array( - 'userid' => $user2, + $group1 = $this->groupManager->createGroup($group1); + $group2 = $this->groupManager->createGroup($group2); + $group1->addUser($user2); + $group2->addUser($user2); + $group1->addUser($user1); + \OC_SubAdmin::createSubAdmin($user1->getUID(), $group1->getGID()); + $result = $this->api->getUsersGroups(array( + 'userid' => $user2->getUID(), )); $this->assertInstanceOf('OC_OCS_Result', $result); $this->assertTrue($result->succeeded()); $data = $result->getData(); - $this->assertEquals($group1, reset($data['groups'])); + $this->assertEquals($group1->getGID(), reset($data['groups'])); $this->assertEquals(1, count($data['groups'])); - \OC_Group::deleteGroup($group1); - \OC_Group::deleteGroup($group2); + $group1->delete(); + $group2->delete(); } public function testGetUsersGroupsOnOtherAsIrelevantSubAdmin() { $user1 = $this->generateUsers(); $user2 = $this->generateUsers(); - self::loginAsUser($user1); + $this->userSession->setUser($user1); $group1 = $this->getUniqueID(); $group2 = $this->getUniqueID(); - \OC_Group::createGroup($group1); - \OC_Group::createGroup($group2); - \OC_Group::addToGroup($user2, $group2); - \OC_Group::addToGroup($user1, $group1); - \OC_SubAdmin::createSubAdmin($user1, $group1); - $result = \OCA\provisioning_API\Users::getUsersGroups(array( - 'userid' => $user2, + $group1 = $this->groupManager->createGroup($group1); + $group2 = $this->groupManager->createGroup($group2); + $group2->addUser($user2); + $group1->addUser($user1); + \OC_SubAdmin::createSubAdmin($user1->getUID(), $group1->getGID()); + $result = $this->api->getUsersGroups(array( + 'userid' => $user2->getUID(), )); $this->assertInstanceOf('OC_OCS_Result', $result); $this->assertFalse($result->succeeded()); - \OC_Group::deleteGroup($group1); - \OC_Group::deleteGroup($group2); + $group1->delete(); + $group2->delete(); } public function testAddToGroup() { $user = $this->generateUsers(); $group = $this->getUniqueID(); - \OC_Group::createGroup($group); - self::loginAsUser($user); - $_POST['groupid'] = $group; - $result = \OCA\provisioning_API\Users::addToGroup(array( - 'userid' => $user, + $group = $this->groupManager->createGroup($group); + $this->userSession->setUser($user); + $_POST['groupid'] = $group->getGID(); + $result = $this->api->addToGroup(array( + 'userid' => $user->getUID(), )); $this->assertInstanceOf('OC_OCS_Result', $result); $this->assertFalse($result->succeeded()); - $this->assertFalse(\OC_Group::inGroup($user, $group)); - \OC_Group::deleteGroup($group); + $this->assertFalse($group->inGroup($user)); + $group->delete(); } public function testAddToGroupAsAdmin() { $user = $this->generateUsers(); - \OC_Group::addToGroup($user, 'admin'); + $this->groupManager->get('admin')->addUser($user); $group = $this->getUniqueID(); - \OC_Group::createGroup($group); + $group = $this->groupManager->createGroup($group); $user2 = $this->generateUsers(); - self::loginAsUser($user); - $_POST['groupid'] = $group; - $result = \OCA\provisioning_API\Users::addToGroup(array( - 'userid' => $user2, + $this->userSession->setUser($user); + $_POST['groupid'] = $group->getGID(); + $result = $this->api->addToGroup(array( + 'userid' => $user2->getUID(), )); $this->assertInstanceOf('OC_OCS_Result', $result); $this->assertTrue($result->succeeded()); - $this->assertTrue(\OC_Group::inGroup($user2, $group)); - \OC_Group::deleteGroup($group); + $this->assertTrue($group->inGroup($user2)); + $group->delete(); } public function testAddToGroupAsSubAdmin() { $user1 = $this->generateUsers(); $user2 = $this->generateUsers(); - self::loginAsUser($user1); + $this->userSession->setUser($user1); $group1 = $this->getUniqueID(); - \OC_Group::createGroup($group1); - \OC_SubAdmin::createSubAdmin($user1, $group1); - $_POST['groupid'] = $group1; - $result = \OCA\provisioning_API\Users::addToGroup(array( - 'userid' => $user2, + $group1 = $this->groupManager->createGroup($group1); + \OC_SubAdmin::createSubAdmin($user1->getUID(), $group1->getGID()); + $_POST['groupid'] = $group1->getGID(); + $result = $this->api->addToGroup(array( + 'userid' => $user2->getUID(), )); $this->assertInstanceOf('OC_OCS_Result', $result); $this->assertFalse($result->succeeded()); - $this->assertFalse(\OC_Group::inGroup($user2, $group1)); - \OC_Group::deleteGroup($group1); + $this->assertFalse($group1->inGroup($user2)); + $group1->delete(); } public function testAddToGroupAsIrelevantSubAdmin() { $user1 = $this->generateUsers(); $user2 = $this->generateUsers(); - self::loginAsUser($user1); + $this->userSession->setUser($user1); $group1 = $this->getUniqueID(); $group2 = $this->getUniqueID(); - \OC_Group::createGroup($group1); - \OC_Group::createGroup($group2); - \OC_SubAdmin::createSubAdmin($user1, $group1); - $_POST['groupid'] = $group2; - $result = \OCA\provisioning_API\Users::addToGroup(array( - 'userid' => $user2, + $group1 = $this->groupManager->createGroup($group1); + $group2 = $this->groupManager->createGroup($group2); + \OC_SubAdmin::createSubAdmin($user1->getUID(), $group1->getGID()); + $_POST['groupid'] = $group2->getGID(); + $result = $this->api->addToGroup(array( + 'userid' => $user2->getUID(), )); $this->assertInstanceOf('OC_OCS_Result', $result); $this->assertFalse($result->succeeded()); - $this->assertFalse(\OC_Group::inGroup($user2, $group2)); - \OC_Group::deleteGroup($group1); - \OC_Group::deleteGroup($group2); + $this->assertFalse($group2->inGroup($user2)); + $group1->delete(); + $group2->delete(); + } + + public function testAddToGroupNoGroupId() { + $_POST['groupid'] = ''; + $result = $this->api->addToGroup([ + 'userid' => $this->getUniqueID(), + ]); + $this->assertInstanceOf('OC_OCS_Result', $result); + $this->assertFalse($result->succeeded()); + $this->assertEquals(101, $result->getStatusCode()); + } + + public function testAddToNonExistingGroup() { + $user = $this->generateUsers(); + $this->groupManager->get('admin')->addUser($user); + $this->userSession->setUser($user); + + $group = $this->groupManager->createGroup($this->getUniqueID()); + $_POST['groupid'] = $group->getGID(); + $result = $this->api->addToGroup([ + 'userid' => $this->getUniqueID(), + ]); + $this->assertInstanceOf('OC_OCS_Result', $result); + $this->assertFalse($result->succeeded()); + $this->assertEquals(103, $result->getStatusCode()); + } + + public function testAddNonExistingUserToGroup() { + $user = $this->generateUsers(); + $this->groupManager->get('admin')->addUser($user); + $this->userSession->setUser($user); + + $_POST['groupid'] = $this->getUniqueID(); + $result = $this->api->addToGroup([ + 'userid' => $this->getUniqueID(), + ]); + $this->assertInstanceOf('OC_OCS_Result', $result); + $this->assertFalse($result->succeeded()); + $this->assertEquals(102, $result->getStatusCode()); } // test delete /cloud/users/{userid}/groups public function testRemoveFromGroupAsSelf() { $user1 = $this->generateUsers(); - self::loginAsUser($user1); + $this->userSession->setUser($user1); $group1 = $this->getUniqueID(); - \OC_Group::createGroup($group1); - \OC_Group::addToGroup($user1, $group1); - $result = \OCA\provisioning_api\Users::removeFromGroup(array( - 'userid' => $user1, + $group1 = $this->groupManager->createGroup($group1); + $group1->addUser($user1); + $result = $this->api->removeFromGroup(array( + 'userid' => $user1->getUID(), '_delete' => array( - 'groupid' => $group1, + 'groupid' => $group1->getGID(), ), )); $this->assertInstanceOf('OC_OCS_Result', $result); $this->assertFalse($result->succeeded()); - $this->assertTrue(\OC_Group::inGroup($user1, $group1)); - \OC_Group::deleteGroup($group1); + $this->assertTrue($group1->inGroup($user1)); + $group1->delete(); } public function testRemoveFromGroupAsAdmin() { $user1 = $this->generateUsers(); $user2 = $this->generateUsers(); - self::loginAsUser($user1); + $this->userSession->setUser($user1); $group1 = $this->getUniqueID(); - \OC_Group::createGroup($group1); - \OC_Group::addToGroup($user2, $group1); - \OC_Group::addToGroup($user1, 'admin'); - $result = \OCA\provisioning_api\Users::removeFromGroup(array( - 'userid' => $user2, + $group1 = $this->groupManager->createGroup($group1); + $group1->addUser($user2); + $this->groupManager->get('admin')->addUser($user1); + $result = $this->api->removeFromGroup(array( + 'userid' => $user2->getUID(), '_delete' => array( - 'groupid' => $group1, + 'groupid' => $group1->getGID(), ), )); $this->assertInstanceOf('OC_OCS_Result', $result); $this->assertTrue($result->succeeded()); - $this->assertFalse(\OC_Group::inGroup($user2, $group1)); - \OC_Group::deleteGroup($group1); + $this->assertFalse($group1->inGroup($user2)); + $group1->delete(); + } + + public function testRemoveSelfFromGroupAsAdmin() { + $user1 = $this->generateUsers(); + $this->userSession->setUser($user1); + $group1 = $this->groupManager->createGroup($this->getUniqueID()); + $group1->addUser($user1); + $this->groupManager->get('admin')->addUser($user1); + $result = $this->api->removeFromGroup([ + 'userid' => $user1->getUID(), + '_delete' => [ + 'groupid' => $group1->getGID(), + ], + ]); + $this->assertInstanceOf('OC_OCS_Result', $result); + $this->assertTrue($result->succeeded()); + $this->assertFalse($group1->inGroup($user1)); + $group1->delete(); } public function testRemoveFromGroupAsSubAdmin() { $user1 = $this->generateUsers(); - self::loginAsUser($user1); + $this->userSession->setUser($user1); $user2 = $this->generateUsers(); $group1 = $this->getUniqueID(); - \OC_Group::createGroup($group1); - \OC_Group::addToGroup($user1, $group1); - \OC_Group::addToGroup($user2, $group1); - \OC_SubAdmin::createSubAdmin($user1, $group1); - $result = \OCA\provisioning_api\Users::removeFromGroup(array( - 'userid' => $user2, + $group1 = $this->groupManager->createGroup($group1); + $group1->addUser($user1); + $group1->addUser($user2); + \OC_SubAdmin::createSubAdmin($user1->getUID(), $group1->getGID()); + $result = $this->api->removeFromGroup(array( + 'userid' => $user2->getUID(), '_delete' => array( - 'groupid' => $group1, + 'groupid' => $group1->getGID(), ), )); $this->assertInstanceOf('OC_OCS_Result', $result); $this->assertTrue($result->succeeded()); - $this->assertFalse(\OC_Group::inGroup($user2, $group1)); - \OC_Group::deleteGroup($group1); + $this->assertFalse($group1->inGroup($user2)); + $group1->delete(); } public function testRemoveFromGroupAsIrelevantSubAdmin() { $user1 = $this->generateUsers(); - self::loginAsUser($user1); + $this->userSession->setUser($user1); $user2 = $this->generateUsers(); $group1 = $this->getUniqueID(); $group2 = $this->getUniqueID(); - \OC_Group::createGroup($group1); - \OC_Group::createGroup($group2); - \OC_Group::addToGroup($user1, $group1); - \OC_Group::addToGroup($user2, $group2); - \OC_SubAdmin::createSubAdmin($user1, $group1); - $result = \OCA\provisioning_api\Users::removeFromGroup(array( - 'userid' => $user2, + $group1 = $this->groupManager->createGroup($group1); + $group2 = $this->groupManager->createGroup($group2); + $group1->addUser($user1); + $group2->addUser($user2); + \OC_SubAdmin::createSubAdmin($user1->getUID(), $group1->getGID()); + $result = $this->api->removeFromGroup(array( + 'userid' => $user2->getUID(), '_delete' => array( - 'groupid' => $group2, + 'groupid' => $group2->getGID(), ), )); $this->assertInstanceOf('OC_OCS_Result', $result); $this->assertFalse($result->succeeded()); - $this->assertTrue(\OC_Group::inGroup($user2, $group2)); - \OC_Group::deleteGroup($group1); - \OC_Group::deleteGroup($group2); + $this->assertTrue($group2->inGroup($user2)); + $group1->delete(); + $group2->delete(); + } + + public function testRemoveFromGroupNoGroupId() { + $result = $this->api->removeFromGroup([ + '_delete' => [ + 'groupid' => '' + ], + ]); + $this->assertInstanceOf('OC_OCS_Result', $result); + $this->assertFalse($result->succeeded()); + $this->assertEquals(101, $result->getStatusCode()); } + public function testRemoveSelfFromAdminAsAdmin() { + $user = $this->generateUsers(); + $this->userSession->setUser($user); + $this->groupManager->get('admin')->addUser($user); + + $result = $this->api->removeFromGroup([ + 'userid' => $user->getUID(), + '_delete' => [ + 'groupid' => 'admin' + ], + ]); + $this->assertInstanceOf('OC_OCS_Result', $result); + $this->assertFalse($result->succeeded()); + $this->assertEquals(105, $result->getStatusCode()); + $this->assertEquals('Cannot remove yourself from the admin group', $result->getMeta()['message']); + } + + public function testRemoveSelfFromSubAdminGroupAsSubAdmin() { + $user = $this->generateUsers(); + $this->userSession->setUser($user); + $group = $this->groupManager->createGroup($this->getUniqueID()); + \OC_SubAdmin::createSubAdmin($user->getUID(), $group->getGID()); + + $result = $this->api->removeFromGroup([ + 'userid' => $user->getUID(), + '_delete' => [ + 'groupid' => $group->getGID() + ], + ]); + $this->assertInstanceOf('OC_OCS_Result', $result); + $this->assertFalse($result->succeeded()); + $this->assertEquals(105, $result->getStatusCode()); + $this->assertEquals('Cannot remove yourself from this group as you are a SubAdmin', $result->getMeta()['message']); + $group->delete(); + } + + public function testRemoveFromNonExistingGroup() { + $user1 = $this->generateUsers(); + $this->userSession->setUser($user1); + $this->groupManager->get('admin')->addUser($user1); + + $user2 = $this->generateUsers(); + $result = $this->api->removeFromGroup([ + 'userid' => $user2->getUID(), + '_delete' => [ + 'groupid' => $this->getUniqueID() + ], + ]); + $this->assertInstanceOf('OC_OCS_Result', $result); + $this->assertFalse($result->succeeded()); + $this->assertEquals(102, $result->getStatusCode()); + } + + public function testRemoveFromNonGroupNonExistingUser() { + $user = $this->generateUsers(); + $this->userSession->setUser($user); + $this->groupManager->get('admin')->addUser($user); + + $group = $this->groupManager->createGroup($this->getUniqueID()); + + $result = $this->api->removeFromGroup([ + 'userid' => $this->getUniqueID(), + '_delete' => [ + 'groupid' => $group->getGID() + ], + ]); + $this->assertInstanceOf('OC_OCS_Result', $result); + $this->assertFalse($result->succeeded()); + $this->assertEquals(103, $result->getStatusCode()); + } + + public function testCreateSubAdmin() { $user1 = $this->generateUsers(); $user2 = $this->generateUsers(); - self::loginAsUser($user1); - \OC_Group::addToGroup($user1, 'admin'); + $this->userSession->setUser($user1); + $this->groupManager->get('admin')->addUser($user1); $group1 = $this->getUniqueID(); - \OC_Group::createGroup($group1); - $_POST['groupid'] = $group1; - $result = \OCA\provisioning_api\Users::addSubAdmin(array( - 'userid' => $user2, + $group1 = $this->groupManager->createGroup($group1); + $_POST['groupid'] = $group1->getGID(); + $result = $this->api->addSubAdmin(array( + 'userid' => $user2->getUID(), )); $this->assertInstanceOf('OC_OCS_Result', $result); $this->assertTrue($result->succeeded()); - $this->assertTrue(\OC_SubAdmin::isSubAdminofGroup($user2, $group1)); - \OC_Group::deleteGroup($group1); + $this->assertTrue(\OC_SubAdmin::isSubAdminofGroup($user2->getUID(), $group1->getGID())); + $group1->delete(); $this->resetParams(); $user1 = $this->generateUsers(); $user2 = $this->generateUsers(); - self::loginAsUser($user1); - \OC_Group::addToGroup($user1, 'admin'); + $this->userSession->setUser($user1); + $this->groupManager->get('admin')->addUser($user1); $_POST['groupid'] = 'admin'; - $result = \OCA\provisioning_api\Users::addSubAdmin(array( - 'userid' => $user2, + $result = $this->api->addSubAdmin(array( + 'userid' => $user2->getUID(), )); $this->assertInstanceOf('OC_OCS_Result', $result); $this->assertEquals(103, $result->getStatusCode()); @@ -692,46 +1050,58 @@ class UsersTest extends TestCase { $this->resetParams(); $user1 = $this->generateUsers(); - self::loginAsUser($user1); - \OC_Group::addToGroup($user1, 'admin'); + $this->userSession->setUser($user1); + $this->groupManager->get('admin')->addUser($user1); $group1 = $this->getUniqueID(); - \OC_Group::createGroup($group1); - $_POST['groupid'] = $group1; - $result = \OCA\provisioning_api\Users::addSubAdmin(array( + $group1 = $this->groupManager->createGroup($group1); + $_POST['groupid'] = $group1->getGID(); + $result = $this->api->addSubAdmin(array( 'userid' => $this->getUniqueID(), )); $this->assertInstanceOf('OC_OCS_Result', $result); $this->assertFalse($result->succeeded()); $this->assertEquals(101, $result->getStatusCode()); - \OC_Group::deleteGroup($group1); + $group1->delete(); + + $user1 = $this->generateUsers(); + $this->userSession->setUser($user1); + $group = $this->getUniqueID(); + $_POST['groupid'] = $group; + $result = $this->api->addSubAdmin([ + 'userid' => $user1->getUID() + ]); + $this->assertInstanceOf('OC_OCS_Result', $result); + $this->assertFalse($result->succeeded()); + $this->assertEquals(102, $result->getStatusCode()); + $this->assertEquals('Group:'.$group.' does not exist', $result->getMeta()['message']); } public function testRemoveSubAdmin() { $user1 = $this->generateUsers(); $user2 = $this->generateUsers(); - self::loginAsUser($user1); - \OC_Group::addToGroup($user1, 'admin'); + $this->userSession->setUser($user1); + $this->groupManager->get('admin')->addUser($user1); $group1 = $this->getUniqueID(); - \OC_Group::createGroup($group1); - \OC_SubAdmin::createSubAdmin($user2, $group1); - $result = \OCA\provisioning_api\Users::removeSubAdmin(array( - 'userid' => $user2, + $group1 = $this->groupManager->createGroup($group1); + \OC_SubAdmin::createSubAdmin($user2->getUID(), $group1->getGID()); + $result = $this->api->removeSubAdmin(array( + 'userid' => $user2->getUID(), '_delete' => array( - 'groupid' => $group1, + 'groupid' => $group1->getGID(), ), )); $this->assertInstanceOf('OC_OCS_Result', $result); $this->assertTrue($result->succeeded()); - $this->assertTrue(!\OC_SubAdmin::isSubAdminofGroup($user2, $group1)); - \OC_Group::deleteGroup($group1); + $this->assertTrue(!\OC_SubAdmin::isSubAdminofGroup($user2->getUID(), $group1->getGID())); + $group1->delete(); $user1 = $this->generateUsers(); - self::loginAsUser($user1); - \OC_Group::addToGroup($user1, 'admin'); - $result = \OCA\provisioning_api\Users::removeSubAdmin(array( + $this->userSession->setUser($user1); + $this->groupManager->get('admin')->addUser($user1); + $result = $this->api->removeSubAdmin(array( 'userid' => $this->getUniqueID(), '_delete' => array( - 'groupid' => $group1, + 'groupid' => $group1->getGID(), ), )); $this->assertInstanceOf('OC_OCS_Result', $result); @@ -742,45 +1112,44 @@ class UsersTest extends TestCase { $user1 = $this->generateUsers(); $user2 = $this->generateUsers(); - self::loginAsUser($user1); - \OC_Group::addToGroup($user1, 'admin'); + $this->userSession->setUser($user1); + $this->groupManager->get('admin')->addUser($user1); $group1 = $this->getUniqueID(); - \OC_Group::createGroup($group1); - $_POST['groupid'] = $group1; - $result = \OCA\provisioning_api\Users::removeSubAdmin(array( - 'userid' => $user2, + $group1 = $this->groupManager->createGroup($group1); + $_POST['groupid'] = $group1->getGID(); + $result = $this->api->removeSubAdmin(array( + 'userid' => $user2->getUID(), '_delete' => array( - 'groupid' => $group1, + 'groupid' => $group1->getGID(), ), )); $this->assertInstanceOf('OC_OCS_Result', $result); $this->assertFalse($result->succeeded()); $this->assertEquals(102, $result->getStatusCode()); - \OC_Group::deleteGroup($group1); + $group1->delete(); } public function testGetSubAdminGroups() { $user1 = $this->generateUsers(); $user2 = $this->generateUsers(); - self::loginAsUser($user1); - \OC_Group::addToGroup($user1, 'admin'); + $this->userSession->setUser($user1); + $this->groupManager->get('admin')->addUser($user1); $group1 = $this->getUniqueID(); - \OC_Group::createGroup($group1); - \OC_SubAdmin::createSubAdmin($user2, $group1); - $result = \OCA\provisioning_api\Users::getUserSubAdminGroups(array( - 'userid' => $user2, + $group1 = $this->groupManager->createGroup($group1); + \OC_SubAdmin::createSubAdmin($user2->getUID(), $group1->getGID()); + $result = $this->api->getUserSubAdminGroups(array( + 'userid' => $user2->getUID(), )); $this->assertInstanceOf('OC_OCS_Result', $result); $this->assertTrue($result->succeeded()); $data = $result->getData(); - $this->assertEquals($group1, reset($data)); - \OC_Group::deleteGroup($group1); + $this->assertEquals($group1->getGID(), reset($data)); + $group1->delete(); $user1 = $this->generateUsers(); - self::loginAsUser($user1); - \OC_Group::addToGroup($user1, 'admin'); - $group1 = $this->getUniqueID(); - $result = \OCA\provisioning_api\Users::getUserSubAdminGroups(array( + $this->userSession->setUser($user1); + $this->groupManager->get('admin')->addUser($user1); + $result = $this->api->getUserSubAdminGroups(array( 'userid' => $this->getUniqueID(), )); $this->assertInstanceOf('OC_OCS_Result', $result); @@ -791,25 +1160,25 @@ class UsersTest extends TestCase { public function testSubAdminOfGroupAlreadySubAdmin() { $user1 = $this->generateUsers(); $user2 = $this->generateUsers(); - self::loginAsUser($user1); - \OC_Group::addToGroup($user1, 'admin'); - $group1 = $this->getUniqueID(); - \OC_Group::createGroup($group1); + $this->userSession->setUser($user1); + $this->groupManager->get('admin')->addUser($user1); + $group1 = $this->groupManager->createGroup($this->getUniqueID()); //Make user2 subadmin of group1 - $_POST['groupid'] = $group1; - $result = \OCA\provisioning_api\Users::addSubAdmin([ - 'userid' => $user2, + $_POST['groupid'] = $group1->getGID(); + $result = $this->api->addSubAdmin([ + 'userid' => $user2->getUID(), ]); $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, + $_POST['groupid'] = $group1->getGID(); + $result = $this->api->addSubAdmin([ + 'userid' => $user2->getUID(), ]); $this->assertInstanceOf('OC_OCS_Result', $result); $this->assertTrue($result->succeeded()); + $group1->delete(); } } diff --git a/config/config.sample.php b/config/config.sample.php index 7becf3c3dc4..047e7ccdd12 100644 --- a/config/config.sample.php +++ b/config/config.sample.php @@ -1035,7 +1035,13 @@ $CONFIG = array( /** * Headers that should be trusted as client IP address in combination with - * `trusted_proxies` + * `trusted_proxies`. If the HTTP header looks like 'X-Forwarded-For', then use + * 'HTTP_X_FORWARDED_FOR' here. + * + * If set incorrectly, a client can spoof their IP address as visible to + * ownCloud, bypassing access controls and making logs useless! + * + * Defaults to 'HTTP_X_FORWARED_FOR' if unset */ 'forwarded_for_headers' => array('HTTP_X_FORWARDED', 'HTTP_FORWARDED_FOR'), diff --git a/core/css/apps.css b/core/css/apps.css index 5769120c5ed..300b186bba2 100644 --- a/core/css/apps.css +++ b/core/css/apps.css @@ -292,13 +292,13 @@ list-style-type: none; } +.bubble, #app-navigation .app-navigation-entry-menu { - display: none; position: absolute; background-color: #eee; color: #333; border-radius: 3px; - border-top-right-radius: 0px; + border-top-right-radius: 0; z-index: 110; margin: -5px 14px 5px 10px; right: 0; @@ -310,11 +310,17 @@ filter: drop-shadow(0 0 5px rgba(150, 150, 150, 0.75)); } +#app-navigation .app-navigation-entry-menu { + display: none; +} + #app-navigation .app-navigation-entry-menu.open { display: block; } /* miraculous border arrow stuff */ +.bubble:after, +.bubble:before, #app-navigation .app-navigation-entry-menu:after, #app-navigation .app-navigation-entry-menu:before { bottom: 100%; @@ -327,12 +333,15 @@ pointer-events: none; } +.bubble:after, #app-navigation .app-navigation-entry-menu:after { border-color: rgba(238, 238, 238, 0); border-bottom-color: #eee; border-width: 10px; margin-left: -10px; } + +.bubble:before, #app-navigation .app-navigation-entry-menu:before { border-color: rgba(187, 187, 187, 0); border-bottom-color: #bbb; diff --git a/core/js/core.json b/core/js/core.json index 1053debaa99..a67491c4a35 100644 --- a/core/js/core.json +++ b/core/js/core.json @@ -7,7 +7,8 @@ "moment/min/moment-with-locales.js", "handlebars/handlebars.js", "blueimp-md5/js/md5.js", - "bootstrap/js/tooltip.js" + "bootstrap/js/tooltip.js", + "backbone/backbone.js" ], "libraries": [ "jquery-showpassword.js", @@ -19,6 +20,7 @@ "jquery.ocdialog.js", "oc-dialogs.js", "js.js", + "oc-backbone.js", "l10n.js", "apps.js", "share.js", diff --git a/core/js/js.js b/core/js/js.js index 72d4edd28dd..89bb9a71430 100644 --- a/core/js/js.js +++ b/core/js/js.js @@ -571,21 +571,20 @@ var OC={ * @todo Write documentation */ registerMenu: function($toggle, $menuEl) { + var self = this; $menuEl.addClass('menu'); $toggle.on('click.menu', function(event) { // prevent the link event (append anchor to URL) event.preventDefault(); if ($menuEl.is(OC._currentMenu)) { - $menuEl.slideUp(OC.menuSpeed); - OC._currentMenu = null; - OC._currentMenuToggle = null; + self.hideMenus(); return; } // another menu was open? else if (OC._currentMenu) { // close it - OC._currentMenu.hide(); + self.hideMenus(); } $menuEl.slideToggle(OC.menuSpeed); OC._currentMenu = $menuEl; @@ -599,15 +598,56 @@ var OC={ unregisterMenu: function($toggle, $menuEl) { // close menu if opened if ($menuEl.is(OC._currentMenu)) { - $menuEl.slideUp(OC.menuSpeed); - OC._currentMenu = null; - OC._currentMenuToggle = null; + this.hideMenus(); } $toggle.off('click.menu').removeClass('menutoggle'); $menuEl.removeClass('menu'); }, /** + * Hides any open menus + * + * @param {Function} complete callback when the hiding animation is done + */ + hideMenus: function(complete) { + if (OC._currentMenu) { + var lastMenu = OC._currentMenu; + OC._currentMenu.trigger(new $.Event('beforeHide')); + OC._currentMenu.slideUp(OC.menuSpeed, function() { + lastMenu.trigger(new $.Event('afterHide')); + if (complete) { + complete.apply(this, arguments); + } + }); + } + OC._currentMenu = null; + OC._currentMenuToggle = null; + }, + + /** + * Shows a given element as menu + * + * @param {Object} [$toggle=null] menu toggle + * @param {Object} $menuEl menu element + * @param {Function} complete callback when the showing animation is done + */ + showMenu: function($toggle, $menuEl, complete) { + if ($menuEl.is(OC._currentMenu)) { + return; + } + this.hideMenus(); + OC._currentMenu = $menuEl; + OC._currentMenuToggle = $toggle; + $menuEl.trigger(new $.Event('beforeShow')); + $menuEl.show(); + $menuEl.trigger(new $.Event('afterShow')); + // no animation + if (_.isFunction()) { + complete(); + } + }, + + /** * Wrapper for matchMedia * * This is makes it possible for unit tests to @@ -1256,11 +1296,8 @@ function initCore() { // don't close when clicking on the menu directly or a menu toggle return false; } - if (OC._currentMenu) { - OC._currentMenu.slideUp(OC.menuSpeed); - } - OC._currentMenu = null; - OC._currentMenuToggle = null; + + OC.hideMenus(); }); diff --git a/core/js/setupchecks.js b/core/js/setupchecks.js index 35f24b188fa..fd192e6563b 100644 --- a/core/js/setupchecks.js +++ b/core/js/setupchecks.js @@ -72,6 +72,16 @@ if(data.isUsedTlsLibOutdated) { messages.push(data.isUsedTlsLibOutdated); } + if(data.phpSupported && data.phpSupported.eol) { + messages.push( + t('core', 'Your PHP version ({version}) is no longer <a href="{phpLink}">supported by PHP</a>. We encourage you to upgrade your PHP version to take advantage of performance and security updates provided by PHP.', {version: data.phpSupported.version, phpLink: 'https://secure.php.net/supported-versions.php'}) + ); + } + if(!data.forwardedForHeadersWorking) { + messages.push( + t('core', 'The reverse proxy headers configuration is incorrect, or you are accessing ownCloud from a trusted proxy. If you are not accessing ownCloud from a trusted proxy, this is a security issue and can allow an attacker to spoof their IP address as visible to ownCloud. Further information can be found in our <a href="{docLink}">documentation</a>.', {docLink: data.reverseProxyDocs}) + ); + } } else { messages.push(t('core', 'Error occurred while checking server setup')); } diff --git a/core/js/share.js b/core/js/share.js index 99fd08c6411..57dd0dd6553 100644 --- a/core/js/share.js +++ b/core/js/share.js @@ -266,7 +266,6 @@ OC.Share={ if (hasShares || owner) { recipients = $tr.attr('data-share-recipients'); - action.addClass('permanent'); message = t('core', 'Shared'); // even if reshared, only show "Shared by" if (owner) { @@ -281,8 +280,7 @@ OC.Share={ } } else { - action.removeClass('permanent'); - action.html(' <span>'+ escapeHTML(t('core', 'Share'))+'</span>').prepend(img); + action.html('<span></span>').prepend(img); } if (hasLink) { image = OC.imagePath('core', 'actions/public'); diff --git a/core/js/tests/specs/setupchecksSpec.js b/core/js/tests/specs/setupchecksSpec.js index ec8a732b4a1..d0efcf4b284 100644 --- a/core/js/tests/specs/setupchecksSpec.js +++ b/core/js/tests/specs/setupchecksSpec.js @@ -66,7 +66,12 @@ describe('OC.SetupChecks tests', function() { { 'Content-Type': 'application/json' }, - JSON.stringify({isUrandomAvailable: true, serverHasInternetConnection: false, memcacheDocs: 'https://doc.owncloud.org/server/go.php?to=admin-performance'}) + JSON.stringify({ + isUrandomAvailable: true, + serverHasInternetConnection: false, + memcacheDocs: 'https://doc.owncloud.org/server/go.php?to=admin-performance', + forwardedForHeadersWorking: true + }) ); async.done(function( data, s, x ){ @@ -83,7 +88,13 @@ describe('OC.SetupChecks tests', function() { { 'Content-Type': 'application/json' }, - JSON.stringify({isUrandomAvailable: true, serverHasInternetConnection: false, dataDirectoryProtected: false, memcacheDocs: 'https://doc.owncloud.org/server/go.php?to=admin-performance'}) + JSON.stringify({ + isUrandomAvailable: true, + serverHasInternetConnection: false, + dataDirectoryProtected: false, + memcacheDocs: 'https://doc.owncloud.org/server/go.php?to=admin-performance', + forwardedForHeadersWorking: true + }) ); async.done(function( data, s, x ){ @@ -100,7 +111,13 @@ describe('OC.SetupChecks tests', function() { { 'Content-Type': 'application/json', }, - JSON.stringify({isUrandomAvailable: true, serverHasInternetConnection: false, dataDirectoryProtected: false, isMemcacheConfigured: true}) + JSON.stringify({ + isUrandomAvailable: true, + serverHasInternetConnection: false, + dataDirectoryProtected: false, + isMemcacheConfigured: true, + forwardedForHeadersWorking: true + }) ); async.done(function( data, s, x ){ @@ -117,7 +134,14 @@ describe('OC.SetupChecks tests', function() { { 'Content-Type': 'application/json', }, - JSON.stringify({isUrandomAvailable: false, securityDocs: 'https://docs.owncloud.org/myDocs.html', serverHasInternetConnection: true, dataDirectoryProtected: true, isMemcacheConfigured: true}) + JSON.stringify({ + isUrandomAvailable: false, + securityDocs: 'https://docs.owncloud.org/myDocs.html', + serverHasInternetConnection: true, + dataDirectoryProtected: true, + isMemcacheConfigured: true, + forwardedForHeadersWorking: true + }) ); async.done(function( data, s, x ){ @@ -126,6 +150,30 @@ describe('OC.SetupChecks tests', function() { }); }); + it('should return an error if the forwarded for headers are not working', function(done) { + var async = OC.SetupChecks.checkSetup(); + + suite.server.requests[0].respond( + 200, + { + 'Content-Type': 'application/json', + }, + JSON.stringify({ + isUrandomAvailable: true, + serverHasInternetConnection: true, + dataDirectoryProtected: true, + isMemcacheConfigured: true, + forwardedForHeadersWorking: false, + reverseProxyDocs: 'https://docs.owncloud.org/foo/bar.html' + }) + ); + + async.done(function( data, s, x ){ + expect(data).toEqual(['The reverse proxy headers configuration is incorrect, or you are accessing ownCloud from a trusted proxy. If you are not accessing ownCloud from a trusted proxy, this is a security issue and can allow an attacker to spoof their IP address as visible to ownCloud. Further information can be found in our <a href="https://docs.owncloud.org/foo/bar.html">documentation</a>.']); + done(); + }); + }); + it('should return an error if the response has no statuscode 200', function(done) { var async = OC.SetupChecks.checkSetup(); @@ -142,6 +190,31 @@ describe('OC.SetupChecks tests', function() { done(); }); }); + + it('should return an error if the php version is no longer supported', function(done) { + var async = OC.SetupChecks.checkSetup(); + + suite.server.requests[0].respond( + 200, + { + 'Content-Type': 'application/json', + }, + JSON.stringify({ + isUrandomAvailable: true, + securityDocs: 'https://docs.owncloud.org/myDocs.html', + serverHasInternetConnection: true, + dataDirectoryProtected: true, + isMemcacheConfigured: true, + forwardedForHeadersWorking: true, + phpSupported: {eol: true, version: '5.4.0'} + }) + ); + + async.done(function( data, s, x ){ + expect(data).toEqual(['Your PHP version (5.4.0) is no longer <a href="https://secure.php.net/supported-versions.php">supported by PHP</a>. We encourage you to upgrade your PHP version to take advantage of performance and security updates provided by PHP.']); + done(); + }); + }); }); describe('checkGeneric', function() { diff --git a/lib/private/activitymanager.php b/lib/private/activitymanager.php index 7b1d5d29f2e..938335a87e1 100644 --- a/lib/private/activitymanager.php +++ b/lib/private/activitymanager.php @@ -56,14 +56,16 @@ class ActivityManager implements IManager { $this->config = $config; } - /** - * @var \Closure[] - */ + /** @var \Closure[] */ + private $consumersClosures = array(); + + /** @var IConsumer[] */ private $consumers = array(); - /** - * @var \Closure[] - */ + /** @var \Closure[] */ + private $extensionsClosures = array(); + + /** @var IExtension[] */ private $extensions = array(); /** @var array list of filters "name" => "is valid" */ @@ -80,6 +82,48 @@ class ActivityManager implements IManager { protected $specialParameters = array(); /** + * @return \OCP\Activity\IConsumer[] + */ + protected function getConsumers() { + if (!empty($this->consumers)) { + return $this->consumers; + } + + $this->consumers = []; + foreach($this->consumersClosures as $consumer) { + $c = $consumer(); + if ($c instanceof IConsumer) { + $this->consumers[] = $c; + } else { + throw new \InvalidArgumentException('The given consumer does not implement the \OCP\Activity\IConsumer interface'); + } + } + + return $this->consumers; + } + + /** + * @return \OCP\Activity\IExtension[] + */ + protected function getExtensions() { + if (!empty($this->extensions)) { + return $this->extensions; + } + + $this->extensions = []; + foreach($this->extensionsClosures as $extension) { + $e = $extension(); + if ($e instanceof IExtension) { + $this->extensions[] = $e; + } else { + throw new \InvalidArgumentException('The given extension does not implement the \OCP\Activity\IExtension interface'); + } + } + + return $this->extensions; + } + + /** * @param $app * @param $subject * @param $subjectParams @@ -93,10 +137,8 @@ class ActivityManager implements IManager { * @return mixed */ function publishActivity($app, $subject, $subjectParams, $message, $messageParams, $file, $link, $affectedUser, $type, $priority) { - foreach($this->consumers as $consumer) { - $c = $consumer(); - if ($c instanceof IConsumer) { - try { + foreach($this->getConsumers() as $c) { + try { $c->receive( $app, $subject, @@ -108,11 +150,9 @@ class ActivityManager implements IManager { $affectedUser, $type, $priority); - } catch (\Exception $ex) { - // TODO: log the exception - } + } catch (\Exception $ex) { + // TODO: log the exception } - } } @@ -125,7 +165,8 @@ class ActivityManager implements IManager { * @param \Closure $callable */ function registerConsumer(\Closure $callable) { - array_push($this->consumers, $callable); + array_push($this->consumersClosures, $callable); + $this->consumers = []; } /** @@ -138,7 +179,8 @@ class ActivityManager implements IManager { * @return void */ function registerExtension(\Closure $callable) { - array_push($this->extensions, $callable); + array_push($this->extensionsClosures, $callable); + $this->extensions = []; } /** @@ -149,13 +191,10 @@ class ActivityManager implements IManager { */ function getNotificationTypes($languageCode) { $notificationTypes = array(); - foreach($this->extensions as $extension) { - $c = $extension(); - if ($c instanceof IExtension) { - $result = $c->getNotificationTypes($languageCode); - if (is_array($result)) { - $notificationTypes = array_merge($notificationTypes, $result); - } + foreach ($this->getExtensions() as $c) { + $result = $c->getNotificationTypes($languageCode); + if (is_array($result)) { + $notificationTypes = array_merge($notificationTypes, $result); } } @@ -168,13 +207,10 @@ class ActivityManager implements IManager { */ function getDefaultTypes($method) { $defaultTypes = array(); - foreach($this->extensions as $extension) { - $c = $extension(); - if ($c instanceof IExtension) { - $types = $c->getDefaultTypes($method); - if (is_array($types)) { - $defaultTypes = array_merge($types, $defaultTypes); - } + foreach ($this->getExtensions() as $c) { + $types = $c->getDefaultTypes($method); + if (is_array($types)) { + $defaultTypes = array_merge($types, $defaultTypes); } } return $defaultTypes; @@ -189,14 +225,11 @@ class ActivityManager implements IManager { return $this->typeIcons[$type]; } - foreach($this->extensions as $extension) { - $c = $extension(); - if ($c instanceof IExtension) { - $icon = $c->getTypeIcon($type); - if (is_string($icon)) { - $this->typeIcons[$type] = $icon; - return $icon; - } + foreach ($this->getExtensions() as $c) { + $icon = $c->getTypeIcon($type); + if (is_string($icon)) { + $this->typeIcons[$type] = $icon; + return $icon; } } @@ -214,13 +247,10 @@ class ActivityManager implements IManager { * @return string|false */ function translate($app, $text, $params, $stripPath, $highlightParams, $languageCode) { - foreach($this->extensions as $extension) { - $c = $extension(); - if ($c instanceof IExtension) { - $translation = $c->translate($app, $text, $params, $stripPath, $highlightParams, $languageCode); - if (is_string($translation)) { - return $translation; - } + foreach ($this->getExtensions() as $c) { + $translation = $c->translate($app, $text, $params, $stripPath, $highlightParams, $languageCode); + if (is_string($translation)) { + return $translation; } } @@ -241,14 +271,11 @@ class ActivityManager implements IManager { $this->specialParameters[$app] = array(); } - foreach($this->extensions as $extension) { - $c = $extension(); - if ($c instanceof IExtension) { - $specialParameter = $c->getSpecialParameterList($app, $text); - if (is_array($specialParameter)) { - $this->specialParameters[$app][$text] = $specialParameter; - return $specialParameter; - } + foreach ($this->getExtensions() as $c) { + $specialParameter = $c->getSpecialParameterList($app, $text); + if (is_array($specialParameter)) { + $this->specialParameters[$app][$text] = $specialParameter; + return $specialParameter; } } @@ -261,13 +288,10 @@ class ActivityManager implements IManager { * @return integer|false */ function getGroupParameter($activity) { - foreach($this->extensions as $extension) { - $c = $extension(); - if ($c instanceof IExtension) { - $parameter = $c->getGroupParameter($activity); - if ($parameter !== false) { - return $parameter; - } + foreach ($this->getExtensions() as $c) { + $parameter = $c->getGroupParameter($activity); + if ($parameter !== false) { + return $parameter; } } @@ -282,14 +306,11 @@ class ActivityManager implements IManager { 'apps' => array(), 'top' => array(), ); - foreach($this->extensions as $extension) { - $c = $extension(); - if ($c instanceof IExtension) { - $additionalEntries = $c->getNavigation(); - if (is_array($additionalEntries)) { - $entries['apps'] = array_merge($entries['apps'], $additionalEntries['apps']); - $entries['top'] = array_merge($entries['top'], $additionalEntries['top']); - } + foreach ($this->getExtensions() as $c) { + $additionalEntries = $c->getNavigation(); + if (is_array($additionalEntries)) { + $entries['apps'] = array_merge($entries['apps'], $additionalEntries['apps']); + $entries['top'] = array_merge($entries['top'], $additionalEntries['top']); } } @@ -305,13 +326,10 @@ class ActivityManager implements IManager { return $this->validFilters[$filterValue]; } - foreach($this->extensions as $extension) { - $c = $extension(); - if ($c instanceof IExtension) { - if ($c->isFilterValid($filterValue) === true) { - $this->validFilters[$filterValue] = true; - return true; - } + foreach ($this->getExtensions() as $c) { + if ($c->isFilterValid($filterValue) === true) { + $this->validFilters[$filterValue] = true; + return true; } } @@ -329,13 +347,10 @@ class ActivityManager implements IManager { return $types; } - foreach($this->extensions as $extension) { - $c = $extension(); - if ($c instanceof IExtension) { - $result = $c->filterNotificationTypes($types, $filter); - if (is_array($result)) { - $types = $result; - } + foreach ($this->getExtensions() as $c) { + $result = $c->filterNotificationTypes($types, $filter); + if (is_array($result)) { + $types = $result; } } return $types; @@ -353,16 +368,13 @@ class ActivityManager implements IManager { $conditions = array(); $parameters = array(); - foreach($this->extensions as $extension) { - $c = $extension(); - if ($c instanceof IExtension) { - $result = $c->getQueryForFilter($filter); - if (is_array($result)) { - list($condition, $parameter) = $result; - if ($condition && is_array($parameter)) { - $conditions[] = $condition; - $parameters = array_merge($parameters, $parameter); - } + foreach ($this->getExtensions() as $c) { + $result = $c->getQueryForFilter($filter); + if (is_array($result)) { + list($condition, $parameter) = $result; + if ($condition && is_array($parameter)) { + $conditions[] = $condition; + $parameters = array_merge($parameters, $parameter); } } } diff --git a/lib/private/app.php b/lib/private/app.php index 74b21b2b107..6c6f79dfa9d 100644 --- a/lib/private/app.php +++ b/lib/private/app.php @@ -421,7 +421,6 @@ class OC_App { */ public static function getSettingsNavigation() { $l = \OC::$server->getL10N('lib'); - $defaults = new OC_Defaults(); $settings = array(); // by default, settings only contain the help menu @@ -432,7 +431,7 @@ class OC_App { array( "id" => "help", "order" => 1000, - "href" => $defaults->getKnowledgeBaseUrl(), + "href" => OC_Helper::linkToRoute("settings_help"), "name" => $l->t("Help"), "icon" => OC_Helper::imagePath("settings", "help.svg") ) diff --git a/lib/private/appframework/dependencyinjection/dicontainer.php b/lib/private/appframework/dependencyinjection/dicontainer.php index c66b792064d..544da74a010 100644 --- a/lib/private/appframework/dependencyinjection/dicontainer.php +++ b/lib/private/appframework/dependencyinjection/dicontainer.php @@ -57,6 +57,7 @@ class DIContainer extends SimpleContainer implements IAppContainer { * @param string $appName the name of the app */ public function __construct($appName, $urlParams = array()){ + parent::__construct(); $this['AppName'] = $appName; $this['urlParams'] = $urlParams; diff --git a/lib/private/appframework/http/request.php b/lib/private/appframework/http/request.php index 43f01dfde3f..aaad286e843 100644 --- a/lib/private/appframework/http/request.php +++ b/lib/private/appframework/http/request.php @@ -452,7 +452,10 @@ class Request implements \ArrayAccess, \Countable, IRequest { $trustedProxies = $this->config->getSystemValue('trusted_proxies', []); if(is_array($trustedProxies) && in_array($remoteAddress, $trustedProxies)) { - $forwardedForHeaders = $this->config->getSystemValue('forwarded_for_headers', []); + $forwardedForHeaders = $this->config->getSystemValue('forwarded_for_headers', [ + 'HTTP_X_FORWARDED_FOR' + // only have one default, so we cannot ship an insecure product out of the box + ]); foreach($forwardedForHeaders as $header) { if(isset($this->server[$header])) { diff --git a/lib/private/appframework/utility/simplecontainer.php b/lib/private/appframework/utility/simplecontainer.php index c1fc96d1975..83a08acde26 100644 --- a/lib/private/appframework/utility/simplecontainer.php +++ b/lib/private/appframework/utility/simplecontainer.php @@ -99,6 +99,7 @@ class SimpleContainer extends Container implements IContainer { * @throws QueryException if the query could not be resolved */ public function query($name) { + $name = $this->sanitizeName($name); if ($this->offsetExists($name)) { return $this->offsetGet($name); } else { @@ -128,6 +129,7 @@ class SimpleContainer extends Container implements IContainer { * @param bool $shared */ public function registerService($name, Closure $closure, $shared = true) { + $name = $this->sanitizeName($name); if (isset($this[$name])) { unset($this[$name]); } @@ -148,7 +150,15 @@ class SimpleContainer extends Container implements IContainer { public function registerAlias($alias, $target) { $this->registerService($alias, function (IContainer $container) use ($target) { return $container->query($target); - }); + }, false); + } + + /* + * @param string $name + * @return string + */ + protected function sanitizeName($name) { + return ltrim($name, '\\'); } } diff --git a/lib/private/connector/sabre/exceptionloggerplugin.php b/lib/private/connector/sabre/exceptionloggerplugin.php index 741ba4d3e05..53a1f738ea6 100644 --- a/lib/private/connector/sabre/exceptionloggerplugin.php +++ b/lib/private/connector/sabre/exceptionloggerplugin.php @@ -28,7 +28,7 @@ use Sabre\DAV\Exception; use Sabre\HTTP\Response; class ExceptionLoggerPlugin extends \Sabre\DAV\ServerPlugin { - private $nonFatalExceptions = array( + protected $nonFatalExceptions = array( 'Sabre\DAV\Exception\NotAuthenticated' => true, // the sync client uses this to find out whether files exist, // so it is not always an error, log it as debug diff --git a/lib/private/connector/sabre/file.php b/lib/private/connector/sabre/file.php index fa2f5ce18d7..b7d0c547f24 100644 --- a/lib/private/connector/sabre/file.php +++ b/lib/private/connector/sabre/file.php @@ -332,7 +332,7 @@ class File extends Node implements IFile { $info = \OC_FileChunking::decodeName($name); if (empty($info)) { - throw new NotImplemented(); + throw new NotImplemented('Invalid chunk name'); } $chunk_handler = new \OC_FileChunking($info); $bytesWritten = $chunk_handler->store($info['index'], $data); diff --git a/lib/private/connector/sabre/objecttree.php b/lib/private/connector/sabre/objecttree.php index 1e9b9ba59e2..18d3c1dcf23 100644 --- a/lib/private/connector/sabre/objecttree.php +++ b/lib/private/connector/sabre/objecttree.php @@ -41,7 +41,7 @@ class ObjectTree extends \Sabre\DAV\Tree { protected $fileView; /** - * @var \OC\Files\Mount\Manager + * @var \OCP\Files\Mount\IMountManager */ protected $mountManager; @@ -54,9 +54,9 @@ class ObjectTree extends \Sabre\DAV\Tree { /** * @param \Sabre\DAV\INode $rootNode * @param \OC\Files\View $view - * @param \OC\Files\Mount\Manager $mountManager + * @param \OCP\Files\Mount\IMountManager $mountManager */ - public function init(\Sabre\DAV\INode $rootNode, \OC\Files\View $view, \OC\Files\Mount\Manager $mountManager) { + public function init(\Sabre\DAV\INode $rootNode, \OC\Files\View $view, \OCP\Files\Mount\IMountManager $mountManager) { $this->rootNode = $rootNode; $this->fileView = $view; $this->mountManager = $mountManager; diff --git a/lib/private/connector/sabre/serverfactory.php b/lib/private/connector/sabre/serverfactory.php new file mode 100644 index 00000000000..525ff0104cd --- /dev/null +++ b/lib/private/connector/sabre/serverfactory.php @@ -0,0 +1,107 @@ +<?php +/** + * @author Robin Appelman <icewind@owncloud.com> + * + * @copyright Copyright (c) 2015, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see <http://www.gnu.org/licenses/> + * + */ + +namespace OC\Connector\Sabre; + +use OCP\Files\Mount\IMountManager; +use OCP\IConfig; +use OCP\IDBConnection; +use OCP\ILogger; +use OCP\ITagManager; +use OCP\IUserSession; +use Sabre\DAV\Auth\Backend\BackendInterface; + +class ServerFactory { + public function __construct( + IConfig $config, + ILogger $logger, + IDBConnection $databaseConnection, + IUserSession $userSession, + IMountManager $mountManager, + ITagManager $tagManager + ) { + $this->config = $config; + $this->logger = $logger; + $this->databaseConnection = $databaseConnection; + $this->userSession = $userSession; + $this->mountManager = $mountManager; + $this->tagManager = $tagManager; + } + + /** + * @param string $baseUri + * @param string $requestUri + * @param BackendInterface $authBackend + * @param callable $viewCallBack callback that should return the view for the dav endpoint + * @return Server + */ + public function createServer($baseUri, $requestUri, BackendInterface $authBackend, callable $viewCallBack) { + // Fire up server + $objectTree = new \OC\Connector\Sabre\ObjectTree(); + $server = new \OC\Connector\Sabre\Server($objectTree); + // Set URL explicitly due to reverse-proxy situations + $server->httpRequest->setUrl($requestUri); + $server->setBaseUri($baseUri); + + // Load plugins + $defaults = new \OC_Defaults(); + $server->addPlugin(new \OC\Connector\Sabre\BlockLegacyClientPlugin($this->config)); + $server->addPlugin(new \Sabre\DAV\Auth\Plugin($authBackend, $defaults->getName())); + // FIXME: The following line is a workaround for legacy components relying on being able to send a GET to / + $server->addPlugin(new \OC\Connector\Sabre\DummyGetResponsePlugin()); + $server->addPlugin(new \OC\Connector\Sabre\FilesPlugin($objectTree)); + $server->addPlugin(new \OC\Connector\Sabre\MaintenancePlugin($this->config)); + $server->addPlugin(new \OC\Connector\Sabre\ExceptionLoggerPlugin('webdav', $this->logger)); + + // wait with registering these until auth is handled and the filesystem is setup + $server->on('beforeMethod', function () use ($server, $objectTree, $viewCallBack) { + /** @var \OC\Files\View $view */ + $view = $viewCallBack(); + $rootInfo = $view->getFileInfo(''); + + // Create ownCloud Dir + if ($rootInfo->getType() === 'dir') { + $root = new \OC\Connector\Sabre\Directory($view, $rootInfo); + } else { + $root = new \OC\Connector\Sabre\File($view, $rootInfo); + } + $objectTree->init($root, $view, $this->mountManager); + + $server->addPlugin(new \OC\Connector\Sabre\QuotaPlugin($view)); + + if($this->userSession->isLoggedIn()) { + $server->addPlugin(new \OC\Connector\Sabre\TagsPlugin($objectTree, $this->tagManager)); + // custom properties plugin must be the last one + $server->addPlugin( + new \Sabre\DAV\PropertyStorage\Plugin( + new \OC\Connector\Sabre\CustomPropertiesBackend( + $objectTree, + $this->databaseConnection, + $this->userSession->getUser() + ) + ) + ); + } + $server->addPlugin(new \OC\Connector\Sabre\CopyEtagHeaderPlugin()); + }, 30); // priority 30: after auth (10) and acl(20), before lock(50) and handling the request + return $server; + } +} diff --git a/lib/private/db/querybuilder/querybuilder.php b/lib/private/db/querybuilder/querybuilder.php index 1a1408876f1..1d97faf77cc 100644 --- a/lib/private/db/querybuilder/querybuilder.php +++ b/lib/private/db/querybuilder/querybuilder.php @@ -37,6 +37,9 @@ class QueryBuilder implements IQueryBuilder { /** @var QuoteHelper */ private $helper; + /** @var bool */ + private $automaticTablePrefix = true; + /** * Initializes a new QueryBuilder. * @@ -49,6 +52,17 @@ class QueryBuilder implements IQueryBuilder { } /** + * Enable/disable automatic prefixing of table names with the oc_ prefix + * + * @param bool $enabled If set to true table names will be prefixed with the + * owncloud database prefix automatically. + * @since 8.2.0 + */ + public function automaticTablePrefix($enabled) { + $this->automaticTablePrefix = (bool) $enabled; + } + + /** * Gets an ExpressionBuilder used for object-oriented construction of query expressions. * This producer method is intended for convenient inline usage. Example: * @@ -329,7 +343,7 @@ class QueryBuilder implements IQueryBuilder { */ public function delete($delete = null, $alias = null) { $this->queryBuilder->delete( - $this->helper->quoteColumnName($delete), + $this->getTableName($delete), $alias ); @@ -354,7 +368,7 @@ class QueryBuilder implements IQueryBuilder { */ public function update($update = null, $alias = null) { $this->queryBuilder->update( - $this->helper->quoteColumnName($update), + $this->getTableName($update), $alias ); @@ -382,7 +396,7 @@ class QueryBuilder implements IQueryBuilder { */ public function insert($insert = null) { $this->queryBuilder->insert( - $this->helper->quoteColumnName($insert) + $this->getTableName($insert) ); return $this; @@ -405,7 +419,7 @@ class QueryBuilder implements IQueryBuilder { */ public function from($from, $alias = null) { $this->queryBuilder->from( - $this->helper->quoteColumnName($from), + $this->getTableName($from), $alias ); @@ -432,7 +446,7 @@ class QueryBuilder implements IQueryBuilder { public function join($fromAlias, $join, $alias, $condition = null) { $this->queryBuilder->join( $fromAlias, - $this->helper->quoteColumnName($join), + $this->getTableName($join), $alias, $condition ); @@ -460,7 +474,7 @@ class QueryBuilder implements IQueryBuilder { public function innerJoin($fromAlias, $join, $alias, $condition = null) { $this->queryBuilder->innerJoin( $fromAlias, - $this->helper->quoteColumnName($join), + $this->getTableName($join), $alias, $condition ); @@ -488,7 +502,7 @@ class QueryBuilder implements IQueryBuilder { public function leftJoin($fromAlias, $join, $alias, $condition = null) { $this->queryBuilder->leftJoin( $fromAlias, - $this->helper->quoteColumnName($join), + $this->getTableName($join), $alias, $condition ); @@ -516,7 +530,7 @@ class QueryBuilder implements IQueryBuilder { public function rightJoin($fromAlias, $join, $alias, $condition = null) { $this->queryBuilder->rightJoin( $fromAlias, - $this->helper->quoteColumnName($join), + $this->getTableName($join), $alias, $condition ); @@ -984,4 +998,16 @@ class QueryBuilder implements IQueryBuilder { public function createFunction($call) { return new QueryFunction($call); } + + /** + * @param string $table + * @return string + */ + private function getTableName($table) { + if ($this->automaticTablePrefix === false || strpos($table, '*PREFIX*') === 0) { + return $this->helper->quoteColumnName($table); + } + + return $this->helper->quoteColumnName('*PREFIX*' . $table); + } } diff --git a/lib/private/defaults.php b/lib/private/defaults.php index b86805357bd..16f45943f54 100644 --- a/lib/private/defaults.php +++ b/lib/private/defaults.php @@ -46,11 +46,9 @@ class OC_Defaults { private $defaultSlogan; private $defaultLogoClaim; private $defaultMailHeaderColor; - private $defaultKnowledgeBaseUrl; function __construct() { $this->l = \OC::$server->getL10N('lib'); - $urlGenerator = \OC::$server->getURLGenerator(); $version = OC_Util::getVersion(); $this->defaultEntity = 'ownCloud'; /* e.g. company name, used for footers and copyright notices */ @@ -66,7 +64,6 @@ class OC_Defaults { $this->defaultSlogan = $this->l->t('web services under your control'); $this->defaultLogoClaim = ''; $this->defaultMailHeaderColor = '#1d2d44'; /* header color of mail notifications */ - $this->defaultKnowledgeBaseUrl = $urlGenerator->linkToRoute('settings_help'); $themePath = OC::$SERVERROOT . '/themes/' . OC_Util::getTheme() . '/defaults.php'; if (file_exists($themePath)) { @@ -82,7 +79,6 @@ class OC_Defaults { /** * @param string $method - * @return bool */ private function themeExist($method) { if (isset($this->theme) && method_exists($this->theme, $method)) { @@ -284,19 +280,4 @@ class OC_Defaults { } } - /** - * get knowledge base URL, will be used for the "Help"-Link in the top - * right menu - * - * @return string - */ - public function getKnowledgeBaseUrl() { - if ($this->themeExist('getKnowledgeBaseUrl')) { - return $this->theme->getKnowledgeBaseUrl(); - } else { - return $this->defaultKnowledgeBaseUrl; - } - - } - } diff --git a/lib/private/files/view.php b/lib/private/files/view.php index dc6e8aaa719..9afa9d40b20 100644 --- a/lib/private/files/view.php +++ b/lib/private/files/view.php @@ -642,10 +642,10 @@ class View { } $run = true; - if ($this->shouldEmitHooks() && (Cache\Scanner::isPartialFile($path1) && !Cache\Scanner::isPartialFile($path2))) { + if ($this->shouldEmitHooks($path1) && (Cache\Scanner::isPartialFile($path1) && !Cache\Scanner::isPartialFile($path2))) { // if it was a rename from a part file to a regular file it was a write and not a rename operation $this->emit_file_hooks_pre($exists, $path2, $run); - } elseif ($this->shouldEmitHooks()) { + } elseif ($this->shouldEmitHooks($path1)) { \OC_Hook::emit( Filesystem::CLASSNAME, Filesystem::signal_rename, array( @@ -1087,6 +1087,11 @@ class View { return true; } $fullPath = $this->getAbsolutePath($path); + + if ($fullPath === $defaultRoot) { + return true; + } + return (strlen($fullPath) > strlen($defaultRoot)) && (substr($fullPath, 0, strlen($defaultRoot) + 1) === $defaultRoot . '/'); } diff --git a/lib/private/server.php b/lib/private/server.php index 9503cf16ff7..618431ff2d4 100644 --- a/lib/private/server.php +++ b/lib/private/server.php @@ -75,6 +75,7 @@ class Server extends SimpleContainer implements IServerContainer { * @param string $webRoot */ public function __construct($webRoot) { + parent::__construct(); $this->webRoot = $webRoot; $this->registerService('ContactsManager', function ($c) { diff --git a/lib/private/share/share.php b/lib/private/share/share.php index 40fcc59f219..9aea4677b5b 100644 --- a/lib/private/share/share.php +++ b/lib/private/share/share.php @@ -1218,7 +1218,7 @@ class Share extends Constants { $qb = $connection->getQueryBuilder(); $qb->select('uid_owner') - ->from('*PREFIX*share') + ->from('share') ->where($qb->expr()->eq('id', $qb->createParameter('shareId'))) ->setParameter(':shareId', $shareId); $result = $qb->execute(); @@ -1269,7 +1269,7 @@ class Share extends Constants { self::verifyPassword($password); $qb = $connection->getQueryBuilder(); - $qb->update('*PREFIX*share') + $qb->update('share') ->set('share_with', $qb->createParameter('pass')) ->where($qb->expr()->eq('id', $qb->createParameter('shareId'))) ->setParameter(':pass', is_null($password) ? null : \OC::$server->getHasher()->hash($password)) diff --git a/lib/public/activity/iextension.php b/lib/public/activity/iextension.php index 19d1d2e83a0..5d9fe3329ef 100644 --- a/lib/public/activity/iextension.php +++ b/lib/public/activity/iextension.php @@ -38,6 +38,8 @@ namespace OCP\Activity; * @since 8.0.0 */ interface IExtension { + const METHOD_STREAM = 'stream'; + const METHOD_MAIL = 'email'; const PRIORITY_VERYLOW = 10; const PRIORITY_LOW = 20; @@ -50,8 +52,13 @@ interface IExtension { * If no additional types are to be added false is to be returned * * @param string $languageCode - * @return array|false + * @return array|false Array "stringID of the type" => "translated string description for the setting" + * or Array "stringID of the type" => [ + * 'desc' => "translated string description for the setting" + * 'methods' => [self::METHOD_*], + * ] * @since 8.0.0 + * @changed 8.2.0 - Added support to allow limiting notifications to certain methods */ public function getNotificationTypes($languageCode); diff --git a/lib/public/activity/imanager.php b/lib/public/activity/imanager.php index cadb37da03b..0f5dccd8ba1 100644 --- a/lib/public/activity/imanager.php +++ b/lib/public/activity/imanager.php @@ -81,9 +81,15 @@ interface IManager { /** * Will return additional notification types as specified by other apps + * * @param string $languageCode - * @return array + * @return array Array "stringID of the type" => "translated string description for the setting" + * or Array "stringID of the type" => [ + * 'desc' => "translated string description for the setting" + * 'methods' => [\OCP\Activity\IExtension::METHOD_*], + * ] * @since 8.0.0 + * @changed 8.2.0 - Added support to allow limiting notifications to certain methods */ function getNotificationTypes($languageCode); diff --git a/lib/public/appframework/http/contentsecuritypolicy.php b/lib/public/appframework/http/contentsecuritypolicy.php index 9c7218dc8ba..ee36f7aac17 100644 --- a/lib/public/appframework/http/contentsecuritypolicy.php +++ b/lib/public/appframework/http/contentsecuritypolicy.php @@ -63,6 +63,7 @@ class ContentSecurityPolicy { /** @var array Domains from which images can get loaded */ private $allowedImageDomains = [ '\'self\'', + 'data:', ]; /** @var array Domains to which connections can be done */ private $allowedConnectDomains = [ diff --git a/lib/public/db/querybuilder/iquerybuilder.php b/lib/public/db/querybuilder/iquerybuilder.php index 09d5d199bef..3fc07af1a47 100644 --- a/lib/public/db/querybuilder/iquerybuilder.php +++ b/lib/public/db/querybuilder/iquerybuilder.php @@ -27,6 +27,15 @@ namespace OCP\DB\QueryBuilder; */ interface IQueryBuilder { /** + * Enable/disable automatic prefixing of table names with the oc_ prefix + * + * @param bool $enabled If set to true table names will be prefixed with the + * owncloud database prefix automatically. + * @since 8.2.0 + */ + public function automaticTablePrefix($enabled); + + /** * Gets an ExpressionBuilder used for object-oriented construction of query expressions. * This producer method is intended for convenient inline usage. Example: * diff --git a/lib/repair/cleantags.php b/lib/repair/cleantags.php index 2bda1047081..d16a49fbca7 100644 --- a/lib/repair/cleantags.php +++ b/lib/repair/cleantags.php @@ -65,8 +65,8 @@ class CleanTags extends BasicEmitter implements RepairStep { protected function deleteOrphanFileEntries() { $this->deleteOrphanEntries( '%d tags for delete files have been removed.', - '*PREFIX*vcategory_to_object', 'objid', - '*PREFIX*filecache', 'fileid', 'path_hash' + 'vcategory_to_object', 'objid', + 'filecache', 'fileid', 'path_hash' ); } @@ -76,8 +76,8 @@ class CleanTags extends BasicEmitter implements RepairStep { protected function deleteOrphanTagEntries() { $this->deleteOrphanEntries( '%d tag entries for deleted tags have been removed.', - '*PREFIX*vcategory_to_object', 'categoryid', - '*PREFIX*vcategory', 'id', 'uid' + 'vcategory_to_object', 'categoryid', + 'vcategory', 'id', 'uid' ); } @@ -87,8 +87,8 @@ class CleanTags extends BasicEmitter implements RepairStep { protected function deleteOrphanCategoryEntries() { $this->deleteOrphanEntries( '%d tags with no entries have been removed.', - '*PREFIX*vcategory', 'id', - '*PREFIX*vcategory_to_object', 'categoryid', 'type' + 'vcategory', 'id', + 'vcategory_to_object', 'categoryid', 'type' ); } diff --git a/lib/repair/filletags.php b/lib/repair/filletags.php index f1bb2c896c4..40072209982 100644 --- a/lib/repair/filletags.php +++ b/lib/repair/filletags.php @@ -42,7 +42,7 @@ class FillETags extends BasicEmitter implements \OC\RepairStep { public function run() { $qb = $this->connection->getQueryBuilder(); - $qb->update('*PREFIX*filecache') + $qb->update('filecache') ->set('etag', $qb->expr()->literal('xxx')) ->where($qb->expr()->eq('etag', $qb->expr()->literal(''))) ->orWhere($qb->expr()->isNull('etag')); diff --git a/settings/controller/checksetupcontroller.php b/settings/controller/checksetupcontroller.php index f849e3ed565..33f94fe4238 100644 --- a/settings/controller/checksetupcontroller.php +++ b/settings/controller/checksetupcontroller.php @@ -128,7 +128,7 @@ class CheckSetupController extends Controller { /** * Check if the used SSL lib is outdated. Older OpenSSL and NSS versions do * have multiple bugs which likely lead to problems in combination with - * functionalities required by ownCloud such as SNI. + * functionality required by ownCloud such as SNI. * * @link https://github.com/owncloud/core/issues/17446#issuecomment-122877546 * @link https://bugzilla.redhat.com/show_bug.cgi?id=1241172 @@ -175,6 +175,40 @@ class CheckSetupController extends Controller { return ''; } + + /* + * Whether the php version is still supported (at time of release) + * according to: https://secure.php.net/supported-versions.php + * + * @return array + */ + private function isPhpSupported() { + $eol = false; + + //PHP 5.4 is EOL on 14 Sep 2015 + if (version_compare(PHP_VERSION, '5.5.0') === -1) { + $eol = true; + } + + return ['eol' => $eol, 'version' => PHP_VERSION]; + } + + /* + * Check if the reverse proxy configuration is working as expected + * + * @return bool + */ + private function forwardedForHeadersWorking() { + $trustedProxies = $this->config->getSystemValue('trusted_proxies', []); + $remoteAddress = $this->request->getRemoteAddress(); + + if (is_array($trustedProxies) && in_array($remoteAddress, $trustedProxies)) { + return false; + } + + // either not enabled or working correctly + return true; + } /** * @return DataResponse @@ -189,6 +223,9 @@ class CheckSetupController extends Controller { 'isUrandomAvailable' => $this->isUrandomAvailable(), 'securityDocs' => $this->urlGenerator->linkToDocs('admin-security'), 'isUsedTlsLibOutdated' => $this->isUsedTlsLibOutdated(), + 'phpSupported' => $this->isPhpSupported(), + 'forwardedForHeadersWorking' => $this->forwardedForHeadersWorking(), + 'reverseProxyDocs' => $this->urlGenerator->linkToDocs('admin-reverse-proxy'), ] ); } diff --git a/tests/lib/activitymanager.php b/tests/lib/activitymanager.php index a35daeaf494..28caf575948 100644 --- a/tests/lib/activitymanager.php +++ b/tests/lib/activitymanager.php @@ -41,6 +41,9 @@ class Test_ActivityManager extends \Test\TestCase { $this->config ); + $this->activityManager->registerConsumer(function() { + return new NoOpConsumer(); + }); $this->activityManager->registerExtension(function() { return new NoOpExtension(); }); @@ -49,6 +52,40 @@ class Test_ActivityManager extends \Test\TestCase { }); } + public function testGetConsumers() { + $consumers = $this->invokePrivate($this->activityManager, 'getConsumers'); + + $this->assertNotEmpty($consumers); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testGetConsumersInvalidConsumer() { + $this->activityManager->registerConsumer(function() { + return new StdClass(); + }); + + $this->invokePrivate($this->activityManager, 'getConsumers'); + } + + public function testGetExtensions() { + $extensions = $this->invokePrivate($this->activityManager, 'getExtensions'); + + $this->assertNotEmpty($extensions); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testGetExtensionsInvalidExtension() { + $this->activityManager->registerExtension(function() { + return new StdClass(); + }); + + $this->invokePrivate($this->activityManager, 'getExtensions'); + } + public function testNotificationTypes() { $result = $this->activityManager->getNotificationTypes('en'); $this->assertTrue(is_array($result)); @@ -328,3 +365,9 @@ class NoOpExtension implements \OCP\Activity\IExtension { return false; } } + +class NoOpConsumer implements \OCP\Activity\IConsumer { + + public function receive($app, $subject, $subjectParams, $message, $messageParams, $file, $link, $affectedUser, $type, $priority) { + } +} diff --git a/tests/lib/appframework/controller/ControllerTest.php b/tests/lib/appframework/controller/ControllerTest.php index ccc373f4d59..0d7716da411 100644 --- a/tests/lib/appframework/controller/ControllerTest.php +++ b/tests/lib/appframework/controller/ControllerTest.php @@ -177,7 +177,7 @@ class ControllerTest extends \Test\TestCase { 'test' => 'something', 'Cache-Control' => 'no-cache, must-revalidate', 'Content-Type' => 'application/json; charset=utf-8', - 'Content-Security-Policy' => "default-src 'none';script-src 'self' 'unsafe-eval';style-src 'self' 'unsafe-inline';img-src 'self';font-src 'self';connect-src 'self';media-src 'self'", + 'Content-Security-Policy' => "default-src 'none';script-src 'self' 'unsafe-eval';style-src 'self' 'unsafe-inline';img-src 'self' data:;font-src 'self';connect-src 'self';media-src 'self'", ]; $response = $this->controller->customDataResponse(array('hi')); diff --git a/tests/lib/appframework/http/ContentSecurityPolicyTest.php b/tests/lib/appframework/http/ContentSecurityPolicyTest.php index 18d71df483f..082c032a420 100644 --- a/tests/lib/appframework/http/ContentSecurityPolicyTest.php +++ b/tests/lib/appframework/http/ContentSecurityPolicyTest.php @@ -28,19 +28,19 @@ class ContentSecurityPolicyTest extends \Test\TestCase { } public function testGetPolicyDefault() { - $defaultPolicy = "default-src 'none';script-src 'self' 'unsafe-eval';style-src 'self' 'unsafe-inline';img-src 'self';font-src 'self';connect-src 'self';media-src 'self'"; + $defaultPolicy = "default-src 'none';script-src 'self' 'unsafe-eval';style-src 'self' 'unsafe-inline';img-src 'self' data:;font-src 'self';connect-src 'self';media-src 'self'"; $this->assertSame($defaultPolicy, $this->contentSecurityPolicy->buildPolicy()); } public function testGetPolicyScriptDomainValid() { - $expectedPolicy = "default-src 'none';script-src 'self' www.owncloud.com 'unsafe-eval';style-src 'self' 'unsafe-inline';img-src 'self';font-src 'self';connect-src 'self';media-src 'self'"; + $expectedPolicy = "default-src 'none';script-src 'self' www.owncloud.com 'unsafe-eval';style-src 'self' 'unsafe-inline';img-src 'self' data:;font-src 'self';connect-src 'self';media-src 'self'"; $this->contentSecurityPolicy->addAllowedScriptDomain('www.owncloud.com'); $this->assertSame($expectedPolicy, $this->contentSecurityPolicy->buildPolicy()); } public function testGetPolicyScriptDomainValidMultiple() { - $expectedPolicy = "default-src 'none';script-src 'self' www.owncloud.com www.owncloud.org 'unsafe-eval';style-src 'self' 'unsafe-inline';img-src 'self';font-src 'self';connect-src 'self';media-src 'self'"; + $expectedPolicy = "default-src 'none';script-src 'self' www.owncloud.com www.owncloud.org 'unsafe-eval';style-src 'self' 'unsafe-inline';img-src 'self' data:;font-src 'self';connect-src 'self';media-src 'self'"; $this->contentSecurityPolicy->addAllowedScriptDomain('www.owncloud.com'); $this->contentSecurityPolicy->addAllowedScriptDomain('www.owncloud.org'); @@ -48,7 +48,7 @@ class ContentSecurityPolicyTest extends \Test\TestCase { } public function testGetPolicyDisallowScriptDomain() { - $expectedPolicy = "default-src 'none';script-src 'self' 'unsafe-eval';style-src 'self' 'unsafe-inline';img-src 'self';font-src 'self';connect-src 'self';media-src 'self'"; + $expectedPolicy = "default-src 'none';script-src 'self' 'unsafe-eval';style-src 'self' 'unsafe-inline';img-src 'self' data:;font-src 'self';connect-src 'self';media-src 'self'"; $this->contentSecurityPolicy->addAllowedScriptDomain('www.owncloud.com'); $this->contentSecurityPolicy->disallowScriptDomain('www.owncloud.com'); @@ -56,7 +56,7 @@ class ContentSecurityPolicyTest extends \Test\TestCase { } public function testGetPolicyDisallowScriptDomainMultiple() { - $expectedPolicy = "default-src 'none';script-src 'self' www.owncloud.com 'unsafe-eval';style-src 'self' 'unsafe-inline';img-src 'self';font-src 'self';connect-src 'self';media-src 'self'"; + $expectedPolicy = "default-src 'none';script-src 'self' www.owncloud.com 'unsafe-eval';style-src 'self' 'unsafe-inline';img-src 'self' data:;font-src 'self';connect-src 'self';media-src 'self'"; $this->contentSecurityPolicy->addAllowedScriptDomain('www.owncloud.com'); $this->contentSecurityPolicy->disallowScriptDomain('www.owncloud.org'); @@ -64,7 +64,7 @@ class ContentSecurityPolicyTest extends \Test\TestCase { } public function testGetPolicyDisallowScriptDomainMultipleStacked() { - $expectedPolicy = "default-src 'none';script-src 'self' 'unsafe-eval';style-src 'self' 'unsafe-inline';img-src 'self';font-src 'self';connect-src 'self';media-src 'self'"; + $expectedPolicy = "default-src 'none';script-src 'self' 'unsafe-eval';style-src 'self' 'unsafe-inline';img-src 'self' data:;font-src 'self';connect-src 'self';media-src 'self'"; $this->contentSecurityPolicy->addAllowedScriptDomain('www.owncloud.com'); $this->contentSecurityPolicy->disallowScriptDomain('www.owncloud.org')->disallowScriptDomain('www.owncloud.com'); @@ -72,14 +72,14 @@ class ContentSecurityPolicyTest extends \Test\TestCase { } public function testGetPolicyScriptAllowInline() { - $expectedPolicy = "default-src 'none';script-src 'self' 'unsafe-inline' 'unsafe-eval';style-src 'self' 'unsafe-inline';img-src 'self';font-src 'self';connect-src 'self';media-src 'self'"; + $expectedPolicy = "default-src 'none';script-src 'self' 'unsafe-inline' 'unsafe-eval';style-src 'self' 'unsafe-inline';img-src 'self' data:;font-src 'self';connect-src 'self';media-src 'self'"; $this->contentSecurityPolicy->allowInlineScript(true); $this->assertSame($expectedPolicy, $this->contentSecurityPolicy->buildPolicy()); } public function testGetPolicyScriptAllowInlineWithDomain() { - $expectedPolicy = "default-src 'none';script-src 'self' www.owncloud.com 'unsafe-inline' 'unsafe-eval';style-src 'self' 'unsafe-inline';img-src 'self';font-src 'self';connect-src 'self';media-src 'self'"; + $expectedPolicy = "default-src 'none';script-src 'self' www.owncloud.com 'unsafe-inline' 'unsafe-eval';style-src 'self' 'unsafe-inline';img-src 'self' data:;font-src 'self';connect-src 'self';media-src 'self'"; $this->contentSecurityPolicy->addAllowedScriptDomain('www.owncloud.com'); $this->contentSecurityPolicy->allowInlineScript(true); @@ -87,7 +87,7 @@ class ContentSecurityPolicyTest extends \Test\TestCase { } public function testGetPolicyScriptDisallowInlineAndEval() { - $expectedPolicy = "default-src 'none';script-src 'self';style-src 'self' 'unsafe-inline';img-src 'self';font-src 'self';connect-src 'self';media-src 'self'"; + $expectedPolicy = "default-src 'none';script-src 'self';style-src 'self' 'unsafe-inline';img-src 'self' data:;font-src 'self';connect-src 'self';media-src 'self'"; $this->contentSecurityPolicy->allowInlineScript(false); $this->contentSecurityPolicy->allowEvalScript(false); @@ -95,14 +95,14 @@ class ContentSecurityPolicyTest extends \Test\TestCase { } public function testGetPolicyStyleDomainValid() { - $expectedPolicy = "default-src 'none';script-src 'self' 'unsafe-eval';style-src 'self' www.owncloud.com 'unsafe-inline';img-src 'self';font-src 'self';connect-src 'self';media-src 'self'"; + $expectedPolicy = "default-src 'none';script-src 'self' 'unsafe-eval';style-src 'self' www.owncloud.com 'unsafe-inline';img-src 'self' data:;font-src 'self';connect-src 'self';media-src 'self'"; $this->contentSecurityPolicy->addAllowedStyleDomain('www.owncloud.com'); $this->assertSame($expectedPolicy, $this->contentSecurityPolicy->buildPolicy()); } public function testGetPolicyStyleDomainValidMultiple() { - $expectedPolicy = "default-src 'none';script-src 'self' 'unsafe-eval';style-src 'self' www.owncloud.com www.owncloud.org 'unsafe-inline';img-src 'self';font-src 'self';connect-src 'self';media-src 'self'"; + $expectedPolicy = "default-src 'none';script-src 'self' 'unsafe-eval';style-src 'self' www.owncloud.com www.owncloud.org 'unsafe-inline';img-src 'self' data:;font-src 'self';connect-src 'self';media-src 'self'"; $this->contentSecurityPolicy->addAllowedStyleDomain('www.owncloud.com'); $this->contentSecurityPolicy->addAllowedStyleDomain('www.owncloud.org'); @@ -110,7 +110,7 @@ class ContentSecurityPolicyTest extends \Test\TestCase { } public function testGetPolicyDisallowStyleDomain() { - $expectedPolicy = "default-src 'none';script-src 'self' 'unsafe-eval';style-src 'self' 'unsafe-inline';img-src 'self';font-src 'self';connect-src 'self';media-src 'self'"; + $expectedPolicy = "default-src 'none';script-src 'self' 'unsafe-eval';style-src 'self' 'unsafe-inline';img-src 'self' data:;font-src 'self';connect-src 'self';media-src 'self'"; $this->contentSecurityPolicy->addAllowedStyleDomain('www.owncloud.com'); $this->contentSecurityPolicy->disallowStyleDomain('www.owncloud.com'); @@ -118,7 +118,7 @@ class ContentSecurityPolicyTest extends \Test\TestCase { } public function testGetPolicyDisallowStyleDomainMultiple() { - $expectedPolicy = "default-src 'none';script-src 'self' 'unsafe-eval';style-src 'self' www.owncloud.com 'unsafe-inline';img-src 'self';font-src 'self';connect-src 'self';media-src 'self'"; + $expectedPolicy = "default-src 'none';script-src 'self' 'unsafe-eval';style-src 'self' www.owncloud.com 'unsafe-inline';img-src 'self' data:;font-src 'self';connect-src 'self';media-src 'self'"; $this->contentSecurityPolicy->addAllowedStyleDomain('www.owncloud.com'); $this->contentSecurityPolicy->disallowStyleDomain('www.owncloud.org'); @@ -126,7 +126,7 @@ class ContentSecurityPolicyTest extends \Test\TestCase { } public function testGetPolicyDisallowStyleDomainMultipleStacked() { - $expectedPolicy = "default-src 'none';script-src 'self' 'unsafe-eval';style-src 'self' 'unsafe-inline';img-src 'self';font-src 'self';connect-src 'self';media-src 'self'"; + $expectedPolicy = "default-src 'none';script-src 'self' 'unsafe-eval';style-src 'self' 'unsafe-inline';img-src 'self' data:;font-src 'self';connect-src 'self';media-src 'self'"; $this->contentSecurityPolicy->addAllowedStyleDomain('www.owncloud.com'); $this->contentSecurityPolicy->disallowStyleDomain('www.owncloud.org')->disallowStyleDomain('www.owncloud.com'); @@ -134,35 +134,35 @@ class ContentSecurityPolicyTest extends \Test\TestCase { } public function testGetPolicyStyleAllowInline() { - $expectedPolicy = "default-src 'none';script-src 'self' 'unsafe-eval';style-src 'self' 'unsafe-inline';img-src 'self';font-src 'self';connect-src 'self';media-src 'self'"; + $expectedPolicy = "default-src 'none';script-src 'self' 'unsafe-eval';style-src 'self' 'unsafe-inline';img-src 'self' data:;font-src 'self';connect-src 'self';media-src 'self'"; $this->contentSecurityPolicy->allowInlineStyle(true); $this->assertSame($expectedPolicy, $this->contentSecurityPolicy->buildPolicy()); } public function testGetPolicyStyleAllowInlineWithDomain() { - $expectedPolicy = "default-src 'none';script-src 'self' 'unsafe-eval';style-src 'self' www.owncloud.com 'unsafe-inline';img-src 'self';font-src 'self';connect-src 'self';media-src 'self'"; + $expectedPolicy = "default-src 'none';script-src 'self' 'unsafe-eval';style-src 'self' www.owncloud.com 'unsafe-inline';img-src 'self' data:;font-src 'self';connect-src 'self';media-src 'self'"; $this->contentSecurityPolicy->addAllowedStyleDomain('www.owncloud.com'); $this->assertSame($expectedPolicy, $this->contentSecurityPolicy->buildPolicy()); } public function testGetPolicyStyleDisallowInline() { - $expectedPolicy = "default-src 'none';script-src 'self' 'unsafe-eval';style-src 'self';img-src 'self';font-src 'self';connect-src 'self';media-src 'self'"; + $expectedPolicy = "default-src 'none';script-src 'self' 'unsafe-eval';style-src 'self';img-src 'self' data:;font-src 'self';connect-src 'self';media-src 'self'"; $this->contentSecurityPolicy->allowInlineStyle(false); $this->assertSame($expectedPolicy, $this->contentSecurityPolicy->buildPolicy()); } public function testGetPolicyImageDomainValid() { - $expectedPolicy = "default-src 'none';script-src 'self' 'unsafe-eval';style-src 'self' 'unsafe-inline';img-src 'self' www.owncloud.com;font-src 'self';connect-src 'self';media-src 'self'"; + $expectedPolicy = "default-src 'none';script-src 'self' 'unsafe-eval';style-src 'self' 'unsafe-inline';img-src 'self' data: www.owncloud.com;font-src 'self';connect-src 'self';media-src 'self'"; $this->contentSecurityPolicy->addAllowedImageDomain('www.owncloud.com'); $this->assertSame($expectedPolicy, $this->contentSecurityPolicy->buildPolicy()); } public function testGetPolicyImageDomainValidMultiple() { - $expectedPolicy = "default-src 'none';script-src 'self' 'unsafe-eval';style-src 'self' 'unsafe-inline';img-src 'self' www.owncloud.com www.owncloud.org;font-src 'self';connect-src 'self';media-src 'self'"; + $expectedPolicy = "default-src 'none';script-src 'self' 'unsafe-eval';style-src 'self' 'unsafe-inline';img-src 'self' data: www.owncloud.com www.owncloud.org;font-src 'self';connect-src 'self';media-src 'self'"; $this->contentSecurityPolicy->addAllowedImageDomain('www.owncloud.com'); $this->contentSecurityPolicy->addAllowedImageDomain('www.owncloud.org'); @@ -170,7 +170,7 @@ class ContentSecurityPolicyTest extends \Test\TestCase { } public function testGetPolicyDisallowImageDomain() { - $expectedPolicy = "default-src 'none';script-src 'self' 'unsafe-eval';style-src 'self' 'unsafe-inline';img-src 'self';font-src 'self';connect-src 'self';media-src 'self'"; + $expectedPolicy = "default-src 'none';script-src 'self' 'unsafe-eval';style-src 'self' 'unsafe-inline';img-src 'self' data:;font-src 'self';connect-src 'self';media-src 'self'"; $this->contentSecurityPolicy->addAllowedImageDomain('www.owncloud.com'); $this->contentSecurityPolicy->disallowImageDomain('www.owncloud.com'); @@ -178,7 +178,7 @@ class ContentSecurityPolicyTest extends \Test\TestCase { } public function testGetPolicyDisallowImageDomainMultiple() { - $expectedPolicy = "default-src 'none';script-src 'self' 'unsafe-eval';style-src 'self' 'unsafe-inline';img-src 'self' www.owncloud.com;font-src 'self';connect-src 'self';media-src 'self'"; + $expectedPolicy = "default-src 'none';script-src 'self' 'unsafe-eval';style-src 'self' 'unsafe-inline';img-src 'self' data: www.owncloud.com;font-src 'self';connect-src 'self';media-src 'self'"; $this->contentSecurityPolicy->addAllowedImageDomain('www.owncloud.com'); $this->contentSecurityPolicy->disallowImageDomain('www.owncloud.org'); @@ -186,7 +186,7 @@ class ContentSecurityPolicyTest extends \Test\TestCase { } public function testGetPolicyDisallowImageDomainMultipleStakes() { - $expectedPolicy = "default-src 'none';script-src 'self' 'unsafe-eval';style-src 'self' 'unsafe-inline';img-src 'self';font-src 'self';connect-src 'self';media-src 'self'"; + $expectedPolicy = "default-src 'none';script-src 'self' 'unsafe-eval';style-src 'self' 'unsafe-inline';img-src 'self' data:;font-src 'self';connect-src 'self';media-src 'self'"; $this->contentSecurityPolicy->addAllowedImageDomain('www.owncloud.com'); $this->contentSecurityPolicy->disallowImageDomain('www.owncloud.org')->disallowImageDomain('www.owncloud.com'); @@ -194,14 +194,14 @@ class ContentSecurityPolicyTest extends \Test\TestCase { } public function testGetPolicyFontDomainValid() { - $expectedPolicy = "default-src 'none';script-src 'self' 'unsafe-eval';style-src 'self' 'unsafe-inline';img-src 'self';font-src 'self' www.owncloud.com;connect-src 'self';media-src 'self'"; + $expectedPolicy = "default-src 'none';script-src 'self' 'unsafe-eval';style-src 'self' 'unsafe-inline';img-src 'self' data:;font-src 'self' www.owncloud.com;connect-src 'self';media-src 'self'"; $this->contentSecurityPolicy->addAllowedFontDomain('www.owncloud.com'); $this->assertSame($expectedPolicy, $this->contentSecurityPolicy->buildPolicy()); } public function testGetPolicyFontDomainValidMultiple() { - $expectedPolicy = "default-src 'none';script-src 'self' 'unsafe-eval';style-src 'self' 'unsafe-inline';img-src 'self';font-src 'self' www.owncloud.com www.owncloud.org;connect-src 'self';media-src 'self'"; + $expectedPolicy = "default-src 'none';script-src 'self' 'unsafe-eval';style-src 'self' 'unsafe-inline';img-src 'self' data:;font-src 'self' www.owncloud.com www.owncloud.org;connect-src 'self';media-src 'self'"; $this->contentSecurityPolicy->addAllowedFontDomain('www.owncloud.com'); $this->contentSecurityPolicy->addAllowedFontDomain('www.owncloud.org'); @@ -209,7 +209,7 @@ class ContentSecurityPolicyTest extends \Test\TestCase { } public function testGetPolicyDisallowFontDomain() { - $expectedPolicy = "default-src 'none';script-src 'self' 'unsafe-eval';style-src 'self' 'unsafe-inline';img-src 'self';font-src 'self';connect-src 'self';media-src 'self'"; + $expectedPolicy = "default-src 'none';script-src 'self' 'unsafe-eval';style-src 'self' 'unsafe-inline';img-src 'self' data:;font-src 'self';connect-src 'self';media-src 'self'"; $this->contentSecurityPolicy->addAllowedFontDomain('www.owncloud.com'); $this->contentSecurityPolicy->disallowFontDomain('www.owncloud.com'); @@ -217,7 +217,7 @@ class ContentSecurityPolicyTest extends \Test\TestCase { } public function testGetPolicyDisallowFontDomainMultiple() { - $expectedPolicy = "default-src 'none';script-src 'self' 'unsafe-eval';style-src 'self' 'unsafe-inline';img-src 'self';font-src 'self' www.owncloud.com;connect-src 'self';media-src 'self'"; + $expectedPolicy = "default-src 'none';script-src 'self' 'unsafe-eval';style-src 'self' 'unsafe-inline';img-src 'self' data:;font-src 'self' www.owncloud.com;connect-src 'self';media-src 'self'"; $this->contentSecurityPolicy->addAllowedFontDomain('www.owncloud.com'); $this->contentSecurityPolicy->disallowFontDomain('www.owncloud.org'); @@ -225,7 +225,7 @@ class ContentSecurityPolicyTest extends \Test\TestCase { } public function testGetPolicyDisallowFontDomainMultipleStakes() { - $expectedPolicy = "default-src 'none';script-src 'self' 'unsafe-eval';style-src 'self' 'unsafe-inline';img-src 'self';font-src 'self';connect-src 'self';media-src 'self'"; + $expectedPolicy = "default-src 'none';script-src 'self' 'unsafe-eval';style-src 'self' 'unsafe-inline';img-src 'self' data:;font-src 'self';connect-src 'self';media-src 'self'"; $this->contentSecurityPolicy->addAllowedFontDomain('www.owncloud.com'); $this->contentSecurityPolicy->disallowFontDomain('www.owncloud.org')->disallowFontDomain('www.owncloud.com'); @@ -233,14 +233,14 @@ class ContentSecurityPolicyTest extends \Test\TestCase { } public function testGetPolicyConnectDomainValid() { - $expectedPolicy = "default-src 'none';script-src 'self' 'unsafe-eval';style-src 'self' 'unsafe-inline';img-src 'self';font-src 'self';connect-src 'self' www.owncloud.com;media-src 'self'"; + $expectedPolicy = "default-src 'none';script-src 'self' 'unsafe-eval';style-src 'self' 'unsafe-inline';img-src 'self' data:;font-src 'self';connect-src 'self' www.owncloud.com;media-src 'self'"; $this->contentSecurityPolicy->addAllowedConnectDomain('www.owncloud.com'); $this->assertSame($expectedPolicy, $this->contentSecurityPolicy->buildPolicy()); } public function testGetPolicyConnectDomainValidMultiple() { - $expectedPolicy = "default-src 'none';script-src 'self' 'unsafe-eval';style-src 'self' 'unsafe-inline';img-src 'self';font-src 'self';connect-src 'self' www.owncloud.com www.owncloud.org;media-src 'self'"; + $expectedPolicy = "default-src 'none';script-src 'self' 'unsafe-eval';style-src 'self' 'unsafe-inline';img-src 'self' data:;font-src 'self';connect-src 'self' www.owncloud.com www.owncloud.org;media-src 'self'"; $this->contentSecurityPolicy->addAllowedConnectDomain('www.owncloud.com'); $this->contentSecurityPolicy->addAllowedConnectDomain('www.owncloud.org'); @@ -248,7 +248,7 @@ class ContentSecurityPolicyTest extends \Test\TestCase { } public function testGetPolicyDisallowConnectDomain() { - $expectedPolicy = "default-src 'none';script-src 'self' 'unsafe-eval';style-src 'self' 'unsafe-inline';img-src 'self';font-src 'self';connect-src 'self';media-src 'self'"; + $expectedPolicy = "default-src 'none';script-src 'self' 'unsafe-eval';style-src 'self' 'unsafe-inline';img-src 'self' data:;font-src 'self';connect-src 'self';media-src 'self'"; $this->contentSecurityPolicy->addAllowedConnectDomain('www.owncloud.com'); $this->contentSecurityPolicy->disallowConnectDomain('www.owncloud.com'); @@ -256,7 +256,7 @@ class ContentSecurityPolicyTest extends \Test\TestCase { } public function testGetPolicyDisallowConnectDomainMultiple() { - $expectedPolicy = "default-src 'none';script-src 'self' 'unsafe-eval';style-src 'self' 'unsafe-inline';img-src 'self';font-src 'self';connect-src 'self' www.owncloud.com;media-src 'self'"; + $expectedPolicy = "default-src 'none';script-src 'self' 'unsafe-eval';style-src 'self' 'unsafe-inline';img-src 'self' data:;font-src 'self';connect-src 'self' www.owncloud.com;media-src 'self'"; $this->contentSecurityPolicy->addAllowedConnectDomain('www.owncloud.com'); $this->contentSecurityPolicy->disallowConnectDomain('www.owncloud.org'); @@ -264,7 +264,7 @@ class ContentSecurityPolicyTest extends \Test\TestCase { } public function testGetPolicyDisallowConnectDomainMultipleStakes() { - $expectedPolicy = "default-src 'none';script-src 'self' 'unsafe-eval';style-src 'self' 'unsafe-inline';img-src 'self';font-src 'self';connect-src 'self';media-src 'self'"; + $expectedPolicy = "default-src 'none';script-src 'self' 'unsafe-eval';style-src 'self' 'unsafe-inline';img-src 'self' data:;font-src 'self';connect-src 'self';media-src 'self'"; $this->contentSecurityPolicy->addAllowedConnectDomain('www.owncloud.com'); $this->contentSecurityPolicy->disallowConnectDomain('www.owncloud.org')->disallowConnectDomain('www.owncloud.com'); @@ -272,14 +272,14 @@ class ContentSecurityPolicyTest extends \Test\TestCase { } public function testGetPolicyMediaDomainValid() { - $expectedPolicy = "default-src 'none';script-src 'self' 'unsafe-eval';style-src 'self' 'unsafe-inline';img-src 'self';font-src 'self';connect-src 'self';media-src 'self' www.owncloud.com"; + $expectedPolicy = "default-src 'none';script-src 'self' 'unsafe-eval';style-src 'self' 'unsafe-inline';img-src 'self' data:;font-src 'self';connect-src 'self';media-src 'self' www.owncloud.com"; $this->contentSecurityPolicy->addAllowedMediaDomain('www.owncloud.com'); $this->assertSame($expectedPolicy, $this->contentSecurityPolicy->buildPolicy()); } public function testGetPolicyMediaDomainValidMultiple() { - $expectedPolicy = "default-src 'none';script-src 'self' 'unsafe-eval';style-src 'self' 'unsafe-inline';img-src 'self';font-src 'self';connect-src 'self';media-src 'self' www.owncloud.com www.owncloud.org"; + $expectedPolicy = "default-src 'none';script-src 'self' 'unsafe-eval';style-src 'self' 'unsafe-inline';img-src 'self' data:;font-src 'self';connect-src 'self';media-src 'self' www.owncloud.com www.owncloud.org"; $this->contentSecurityPolicy->addAllowedMediaDomain('www.owncloud.com'); $this->contentSecurityPolicy->addAllowedMediaDomain('www.owncloud.org'); @@ -287,7 +287,7 @@ class ContentSecurityPolicyTest extends \Test\TestCase { } public function testGetPolicyDisallowMediaDomain() { - $expectedPolicy = "default-src 'none';script-src 'self' 'unsafe-eval';style-src 'self' 'unsafe-inline';img-src 'self';font-src 'self';connect-src 'self';media-src 'self'"; + $expectedPolicy = "default-src 'none';script-src 'self' 'unsafe-eval';style-src 'self' 'unsafe-inline';img-src 'self' data:;font-src 'self';connect-src 'self';media-src 'self'"; $this->contentSecurityPolicy->addAllowedMediaDomain('www.owncloud.com'); $this->contentSecurityPolicy->disallowMediaDomain('www.owncloud.com'); @@ -295,7 +295,7 @@ class ContentSecurityPolicyTest extends \Test\TestCase { } public function testGetPolicyDisallowMediaDomainMultiple() { - $expectedPolicy = "default-src 'none';script-src 'self' 'unsafe-eval';style-src 'self' 'unsafe-inline';img-src 'self';font-src 'self';connect-src 'self';media-src 'self' www.owncloud.com"; + $expectedPolicy = "default-src 'none';script-src 'self' 'unsafe-eval';style-src 'self' 'unsafe-inline';img-src 'self' data:;font-src 'self';connect-src 'self';media-src 'self' www.owncloud.com"; $this->contentSecurityPolicy->addAllowedMediaDomain('www.owncloud.com'); $this->contentSecurityPolicy->disallowMediaDomain('www.owncloud.org'); @@ -303,7 +303,7 @@ class ContentSecurityPolicyTest extends \Test\TestCase { } public function testGetPolicyDisallowMediaDomainMultipleStakes() { - $expectedPolicy = "default-src 'none';script-src 'self' 'unsafe-eval';style-src 'self' 'unsafe-inline';img-src 'self';font-src 'self';connect-src 'self';media-src 'self'"; + $expectedPolicy = "default-src 'none';script-src 'self' 'unsafe-eval';style-src 'self' 'unsafe-inline';img-src 'self' data:;font-src 'self';connect-src 'self';media-src 'self'"; $this->contentSecurityPolicy->addAllowedMediaDomain('www.owncloud.com'); $this->contentSecurityPolicy->disallowMediaDomain('www.owncloud.org')->disallowMediaDomain('www.owncloud.com'); @@ -311,14 +311,14 @@ class ContentSecurityPolicyTest extends \Test\TestCase { } public function testGetPolicyObjectDomainValid() { - $expectedPolicy = "default-src 'none';script-src 'self' 'unsafe-eval';style-src 'self' 'unsafe-inline';img-src 'self';font-src 'self';connect-src 'self';media-src 'self';object-src www.owncloud.com"; + $expectedPolicy = "default-src 'none';script-src 'self' 'unsafe-eval';style-src 'self' 'unsafe-inline';img-src 'self' data:;font-src 'self';connect-src 'self';media-src 'self';object-src www.owncloud.com"; $this->contentSecurityPolicy->addAllowedObjectDomain('www.owncloud.com'); $this->assertSame($expectedPolicy, $this->contentSecurityPolicy->buildPolicy()); } public function testGetPolicyObjectDomainValidMultiple() { - $expectedPolicy = "default-src 'none';script-src 'self' 'unsafe-eval';style-src 'self' 'unsafe-inline';img-src 'self';font-src 'self';connect-src 'self';media-src 'self';object-src www.owncloud.com www.owncloud.org"; + $expectedPolicy = "default-src 'none';script-src 'self' 'unsafe-eval';style-src 'self' 'unsafe-inline';img-src 'self' data:;font-src 'self';connect-src 'self';media-src 'self';object-src www.owncloud.com www.owncloud.org"; $this->contentSecurityPolicy->addAllowedObjectDomain('www.owncloud.com'); $this->contentSecurityPolicy->addAllowedObjectDomain('www.owncloud.org'); @@ -326,7 +326,7 @@ class ContentSecurityPolicyTest extends \Test\TestCase { } public function testGetPolicyDisallowObjectDomain() { - $expectedPolicy = "default-src 'none';script-src 'self' 'unsafe-eval';style-src 'self' 'unsafe-inline';img-src 'self';font-src 'self';connect-src 'self';media-src 'self'"; + $expectedPolicy = "default-src 'none';script-src 'self' 'unsafe-eval';style-src 'self' 'unsafe-inline';img-src 'self' data:;font-src 'self';connect-src 'self';media-src 'self'"; $this->contentSecurityPolicy->addAllowedObjectDomain('www.owncloud.com'); $this->contentSecurityPolicy->disallowObjectDomain('www.owncloud.com'); @@ -334,7 +334,7 @@ class ContentSecurityPolicyTest extends \Test\TestCase { } public function testGetPolicyDisallowObjectDomainMultiple() { - $expectedPolicy = "default-src 'none';script-src 'self' 'unsafe-eval';style-src 'self' 'unsafe-inline';img-src 'self';font-src 'self';connect-src 'self';media-src 'self';object-src www.owncloud.com"; + $expectedPolicy = "default-src 'none';script-src 'self' 'unsafe-eval';style-src 'self' 'unsafe-inline';img-src 'self' data:;font-src 'self';connect-src 'self';media-src 'self';object-src www.owncloud.com"; $this->contentSecurityPolicy->addAllowedObjectDomain('www.owncloud.com'); $this->contentSecurityPolicy->disallowObjectDomain('www.owncloud.org'); @@ -342,7 +342,7 @@ class ContentSecurityPolicyTest extends \Test\TestCase { } public function testGetPolicyDisallowObjectDomainMultipleStakes() { - $expectedPolicy = "default-src 'none';script-src 'self' 'unsafe-eval';style-src 'self' 'unsafe-inline';img-src 'self';font-src 'self';connect-src 'self';media-src 'self'"; + $expectedPolicy = "default-src 'none';script-src 'self' 'unsafe-eval';style-src 'self' 'unsafe-inline';img-src 'self' data:;font-src 'self';connect-src 'self';media-src 'self'"; $this->contentSecurityPolicy->addAllowedObjectDomain('www.owncloud.com'); $this->contentSecurityPolicy->disallowObjectDomain('www.owncloud.org')->disallowObjectDomain('www.owncloud.com'); @@ -350,14 +350,14 @@ class ContentSecurityPolicyTest extends \Test\TestCase { } public function testGetAllowedFrameDomain() { - $expectedPolicy = "default-src 'none';script-src 'self' 'unsafe-eval';style-src 'self' 'unsafe-inline';img-src 'self';font-src 'self';connect-src 'self';media-src 'self';frame-src www.owncloud.com"; + $expectedPolicy = "default-src 'none';script-src 'self' 'unsafe-eval';style-src 'self' 'unsafe-inline';img-src 'self' data:;font-src 'self';connect-src 'self';media-src 'self';frame-src www.owncloud.com"; $this->contentSecurityPolicy->addAllowedFrameDomain('www.owncloud.com'); $this->assertSame($expectedPolicy, $this->contentSecurityPolicy->buildPolicy()); } public function testGetPolicyFrameDomainValidMultiple() { - $expectedPolicy = "default-src 'none';script-src 'self' 'unsafe-eval';style-src 'self' 'unsafe-inline';img-src 'self';font-src 'self';connect-src 'self';media-src 'self';frame-src www.owncloud.com www.owncloud.org"; + $expectedPolicy = "default-src 'none';script-src 'self' 'unsafe-eval';style-src 'self' 'unsafe-inline';img-src 'self' data:;font-src 'self';connect-src 'self';media-src 'self';frame-src www.owncloud.com www.owncloud.org"; $this->contentSecurityPolicy->addAllowedFrameDomain('www.owncloud.com'); $this->contentSecurityPolicy->addAllowedFrameDomain('www.owncloud.org'); @@ -365,7 +365,7 @@ class ContentSecurityPolicyTest extends \Test\TestCase { } public function testGetPolicyDisallowFrameDomain() { - $expectedPolicy = "default-src 'none';script-src 'self' 'unsafe-eval';style-src 'self' 'unsafe-inline';img-src 'self';font-src 'self';connect-src 'self';media-src 'self'"; + $expectedPolicy = "default-src 'none';script-src 'self' 'unsafe-eval';style-src 'self' 'unsafe-inline';img-src 'self' data:;font-src 'self';connect-src 'self';media-src 'self'"; $this->contentSecurityPolicy->addAllowedFrameDomain('www.owncloud.com'); $this->contentSecurityPolicy->disallowFrameDomain('www.owncloud.com'); @@ -373,7 +373,7 @@ class ContentSecurityPolicyTest extends \Test\TestCase { } public function testGetPolicyDisallowFrameDomainMultiple() { - $expectedPolicy = "default-src 'none';script-src 'self' 'unsafe-eval';style-src 'self' 'unsafe-inline';img-src 'self';font-src 'self';connect-src 'self';media-src 'self';frame-src www.owncloud.com"; + $expectedPolicy = "default-src 'none';script-src 'self' 'unsafe-eval';style-src 'self' 'unsafe-inline';img-src 'self' data:;font-src 'self';connect-src 'self';media-src 'self';frame-src www.owncloud.com"; $this->contentSecurityPolicy->addAllowedFrameDomain('www.owncloud.com'); $this->contentSecurityPolicy->disallowFrameDomain('www.owncloud.org'); @@ -381,7 +381,7 @@ class ContentSecurityPolicyTest extends \Test\TestCase { } public function testGetPolicyDisallowFrameDomainMultipleStakes() { - $expectedPolicy = "default-src 'none';script-src 'self' 'unsafe-eval';style-src 'self' 'unsafe-inline';img-src 'self';font-src 'self';connect-src 'self';media-src 'self'"; + $expectedPolicy = "default-src 'none';script-src 'self' 'unsafe-eval';style-src 'self' 'unsafe-inline';img-src 'self' data:;font-src 'self';connect-src 'self';media-src 'self'"; $this->contentSecurityPolicy->addAllowedFrameDomain('www.owncloud.com'); $this->contentSecurityPolicy->disallowFrameDomain('www.owncloud.org')->disallowFrameDomain('www.owncloud.com'); @@ -389,14 +389,14 @@ class ContentSecurityPolicyTest extends \Test\TestCase { } public function testGetAllowedChildSrcDomain() { - $expectedPolicy = "default-src 'none';script-src 'self' 'unsafe-eval';style-src 'self' 'unsafe-inline';img-src 'self';font-src 'self';connect-src 'self';media-src 'self';child-src child.owncloud.com"; + $expectedPolicy = "default-src 'none';script-src 'self' 'unsafe-eval';style-src 'self' 'unsafe-inline';img-src 'self' data:;font-src 'self';connect-src 'self';media-src 'self';child-src child.owncloud.com"; $this->contentSecurityPolicy->addAllowedChildSrcDomain('child.owncloud.com'); $this->assertSame($expectedPolicy, $this->contentSecurityPolicy->buildPolicy()); } public function testGetPolicyChildSrcValidMultiple() { - $expectedPolicy = "default-src 'none';script-src 'self' 'unsafe-eval';style-src 'self' 'unsafe-inline';img-src 'self';font-src 'self';connect-src 'self';media-src 'self';child-src child.owncloud.com child.owncloud.org"; + $expectedPolicy = "default-src 'none';script-src 'self' 'unsafe-eval';style-src 'self' 'unsafe-inline';img-src 'self' data:;font-src 'self';connect-src 'self';media-src 'self';child-src child.owncloud.com child.owncloud.org"; $this->contentSecurityPolicy->addAllowedChildSrcDomain('child.owncloud.com'); $this->contentSecurityPolicy->addAllowedChildSrcDomain('child.owncloud.org'); @@ -404,7 +404,7 @@ class ContentSecurityPolicyTest extends \Test\TestCase { } public function testGetPolicyDisallowChildSrcDomain() { - $expectedPolicy = "default-src 'none';script-src 'self' 'unsafe-eval';style-src 'self' 'unsafe-inline';img-src 'self';font-src 'self';connect-src 'self';media-src 'self'"; + $expectedPolicy = "default-src 'none';script-src 'self' 'unsafe-eval';style-src 'self' 'unsafe-inline';img-src 'self' data:;font-src 'self';connect-src 'self';media-src 'self'"; $this->contentSecurityPolicy->addAllowedChildSrcDomain('www.owncloud.com'); $this->contentSecurityPolicy->disallowChildSrcDomain('www.owncloud.com'); @@ -412,7 +412,7 @@ class ContentSecurityPolicyTest extends \Test\TestCase { } public function testGetPolicyDisallowChildSrcDomainMultiple() { - $expectedPolicy = "default-src 'none';script-src 'self' 'unsafe-eval';style-src 'self' 'unsafe-inline';img-src 'self';font-src 'self';connect-src 'self';media-src 'self';child-src www.owncloud.com"; + $expectedPolicy = "default-src 'none';script-src 'self' 'unsafe-eval';style-src 'self' 'unsafe-inline';img-src 'self' data:;font-src 'self';connect-src 'self';media-src 'self';child-src www.owncloud.com"; $this->contentSecurityPolicy->addAllowedChildSrcDomain('www.owncloud.com'); $this->contentSecurityPolicy->disallowChildSrcDomain('www.owncloud.org'); @@ -420,7 +420,7 @@ class ContentSecurityPolicyTest extends \Test\TestCase { } public function testGetPolicyDisallowChildSrcDomainMultipleStakes() { - $expectedPolicy = "default-src 'none';script-src 'self' 'unsafe-eval';style-src 'self' 'unsafe-inline';img-src 'self';font-src 'self';connect-src 'self';media-src 'self'"; + $expectedPolicy = "default-src 'none';script-src 'self' 'unsafe-eval';style-src 'self' 'unsafe-inline';img-src 'self' data:;font-src 'self';connect-src 'self';media-src 'self'"; $this->contentSecurityPolicy->addAllowedChildSrcDomain('www.owncloud.com'); $this->contentSecurityPolicy->disallowChildSrcDomain('www.owncloud.org')->disallowChildSrcDomain('www.owncloud.com'); @@ -428,7 +428,7 @@ class ContentSecurityPolicyTest extends \Test\TestCase { } public function testConfigureStacked() { - $expectedPolicy = "default-src 'none';script-src 'self' script.owncloud.org;style-src 'self' style.owncloud.org;img-src 'self' img.owncloud.org;font-src 'self' font.owncloud.org;connect-src 'self' connect.owncloud.org;media-src 'self' media.owncloud.org;object-src objects.owncloud.org;frame-src frame.owncloud.org;child-src child.owncloud.org"; + $expectedPolicy = "default-src 'none';script-src 'self' script.owncloud.org;style-src 'self' style.owncloud.org;img-src 'self' data: img.owncloud.org;font-src 'self' font.owncloud.org;connect-src 'self' connect.owncloud.org;media-src 'self' media.owncloud.org;object-src objects.owncloud.org;frame-src frame.owncloud.org;child-src child.owncloud.org"; $this->contentSecurityPolicy->allowInlineStyle(false) ->allowEvalScript(false) diff --git a/tests/lib/appframework/http/DataResponseTest.php b/tests/lib/appframework/http/DataResponseTest.php index ca0582e10e5..2b7817c28e9 100644 --- a/tests/lib/appframework/http/DataResponseTest.php +++ b/tests/lib/appframework/http/DataResponseTest.php @@ -68,7 +68,7 @@ class DataResponseTest extends \Test\TestCase { $expectedHeaders = [ 'Cache-Control' => 'no-cache, must-revalidate', - 'Content-Security-Policy' => "default-src 'none';script-src 'self' 'unsafe-eval';style-src 'self' 'unsafe-inline';img-src 'self';font-src 'self';connect-src 'self';media-src 'self'", + 'Content-Security-Policy' => "default-src 'none';script-src 'self' 'unsafe-eval';style-src 'self' 'unsafe-inline';img-src 'self' data:;font-src 'self';connect-src 'self';media-src 'self'", ]; $expectedHeaders = array_merge($expectedHeaders, $headers); diff --git a/tests/lib/appframework/http/ResponseTest.php b/tests/lib/appframework/http/ResponseTest.php index c8b79fbd8b6..61dd95e5948 100644 --- a/tests/lib/appframework/http/ResponseTest.php +++ b/tests/lib/appframework/http/ResponseTest.php @@ -58,7 +58,7 @@ class ResponseTest extends \Test\TestCase { $this->childResponse->setHeaders($expected); $headers = $this->childResponse->getHeaders(); - $expected['Content-Security-Policy'] = "default-src 'none';script-src 'self' 'unsafe-eval';style-src 'self' 'unsafe-inline';img-src 'self';font-src 'self';connect-src 'self';media-src 'self'"; + $expected['Content-Security-Policy'] = "default-src 'none';script-src 'self' 'unsafe-eval';style-src 'self' 'unsafe-inline';img-src 'self' data:;font-src 'self';connect-src 'self';media-src 'self'"; $this->assertEquals($expected, $headers); } diff --git a/tests/lib/appframework/routing/RoutingTest.php b/tests/lib/appframework/routing/RoutingTest.php index 4ee3ed58807..51c191fdfb7 100644 --- a/tests/lib/appframework/routing/RoutingTest.php +++ b/tests/lib/appframework/routing/RoutingTest.php @@ -120,7 +120,8 @@ class RoutingTest extends \Test\TestCase } // route mocks - $route = $this->mockRoute($verb, $controllerName, $actionName, $requirements, $defaults); + $container = new DIContainer('app1'); + $route = $this->mockRoute($container, $verb, $controllerName, $actionName, $requirements, $defaults); // router mock $router = $this->getMock("\OC\Route\Router", array('create')); @@ -133,7 +134,6 @@ class RoutingTest extends \Test\TestCase ->will($this->returnValue($route)); // load route configuration - $container = new DIContainer('app1'); $config = new RouteConfig($container, $router, $routes); $config->register(); @@ -151,11 +151,12 @@ class RoutingTest extends \Test\TestCase $router = $this->getMock("\OC\Route\Router", array('create')); // route mocks - $indexRoute = $this->mockRoute('GET', $controllerName, 'index'); - $showRoute = $this->mockRoute('GET', $controllerName, 'show'); - $createRoute = $this->mockRoute('POST', $controllerName, 'create'); - $updateRoute = $this->mockRoute('PUT', $controllerName, 'update'); - $destroyRoute = $this->mockRoute('DELETE', $controllerName, 'destroy'); + $container = new DIContainer('app1'); + $indexRoute = $this->mockRoute($container, 'GET', $controllerName, 'index'); + $showRoute = $this->mockRoute($container, 'GET', $controllerName, 'show'); + $createRoute = $this->mockRoute($container, 'POST', $controllerName, 'create'); + $updateRoute = $this->mockRoute($container, 'PUT', $controllerName, 'update'); + $destroyRoute = $this->mockRoute($container, 'DELETE', $controllerName, 'destroy'); $urlWithParam = $url . '/{' . $paramName . '}'; @@ -191,21 +192,28 @@ class RoutingTest extends \Test\TestCase ->will($this->returnValue($destroyRoute)); // load route configuration - $container = new DIContainer('app1'); $config = new RouteConfig($container, $router, $yaml); $config->register(); } /** + * @param DIContainer $container * @param string $verb * @param string $controllerName * @param string $actionName + * @param array $requirements + * @param array $defaults * @return \PHPUnit_Framework_MockObject_MockObject */ - private function mockRoute($verb, $controllerName, $actionName, array $requirements=array(), array $defaults=array()) - { - $container = new DIContainer('app1'); + private function mockRoute( + DIContainer $container, + $verb, + $controllerName, + $actionName, + array $requirements=array(), + array $defaults=array() + ) { $route = $this->getMock("\OC\Route\Route", array('method', 'action', 'requirements', 'defaults'), array(), '', false); $route ->expects($this->exactly(1)) diff --git a/tests/lib/appframework/utility/SimpleContainerTest.php b/tests/lib/appframework/utility/SimpleContainerTest.php index 7ff579a85fc..e21e0ca13f2 100644 --- a/tests/lib/appframework/utility/SimpleContainerTest.php +++ b/tests/lib/appframework/utility/SimpleContainerTest.php @@ -159,10 +159,34 @@ class SimpleContainerTest extends \Test\TestCase { public function testRegisterAliasService() { $this->container->registerService('test', function() { + return new \StdClass; + }, true); + $this->container->registerAlias('test1', 'test'); + $this->assertSame( + $this->container->query('test'), $this->container->query('test')); + $this->assertSame( + $this->container->query('test1'), $this->container->query('test1')); + $this->assertSame( + $this->container->query('test'), $this->container->query('test1')); + } + + public function sanitizeNameProvider() { + return [ + ['ABC\\Foo', 'ABC\\Foo'], + ['\\ABC\\Foo', '\\ABC\\Foo'], + ['\\ABC\\Foo', 'ABC\\Foo'], + ['ABC\\Foo', '\\ABC\\Foo'], + ]; + } + + /** + * @dataProvider sanitizeNameProvider + */ + public function testSanitizeName($register, $query) { + $this->container->registerService($register, function() { return 'abc'; }); - $this->container->registerAlias('test1', 'test'); - $this->assertEquals('abc', $this->container->query('test1')); + $this->assertEquals('abc', $this->container->query($query)); } /** @@ -174,5 +198,25 @@ class SimpleContainerTest extends \Test\TestCase { ); } + public function testRegisterFactory() { + $this->container->registerService('test', function() { + return new \StdClass(); + }, false); + $this->assertNotSame( + $this->container->query('test'), $this->container->query('test')); + } + + public function testRegisterAliasFactory() { + $this->container->registerService('test', function() { + return new \StdClass(); + }, false); + $this->container->registerAlias('test1', 'test'); + $this->assertNotSame( + $this->container->query('test'), $this->container->query('test')); + $this->assertNotSame( + $this->container->query('test1'), $this->container->query('test1')); + $this->assertNotSame( + $this->container->query('test'), $this->container->query('test1')); + } } diff --git a/tests/lib/connector/sabre/requesttest/auth.php b/tests/lib/connector/sabre/requesttest/auth.php new file mode 100644 index 00000000000..7cab4da5264 --- /dev/null +++ b/tests/lib/connector/sabre/requesttest/auth.php @@ -0,0 +1,69 @@ +<?php +/** + * Copyright (c) 2015 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 Test\Connector\Sabre\RequestTest; + +use Sabre\DAV\Auth\Backend\BackendInterface; + +class Auth implements BackendInterface { + /** + * @var string + */ + private $user; + + /** + * @var string + */ + private $password; + + /** + * Auth constructor. + * + * @param string $user + * @param string $password + */ + public function __construct($user, $password) { + $this->user = $user; + $this->password = $password; + } + + + /** + * Authenticates the user based on the current request. + * + * If authentication is successful, true must be returned. + * If authentication fails, an exception must be thrown. + * + * @param \Sabre\DAV\Server $server + * @param string $realm + * @return bool + */ + function authenticate(\Sabre\DAV\Server $server, $realm) { + $userSession = \OC::$server->getUserSession(); + $result = $userSession->login($this->user, $this->password); + if ($result) { + //we need to pass the user name, which may differ from login name + $user = $userSession->getUser()->getUID(); + \OC_Util::setupFS($user); + //trigger creation of user home and /files folder + \OC::$server->getUserFolder($user); + } + return $result; + } + + /** + * Returns information about the currently logged in username. + * + * If nobody is currently logged in, this method should return null. + * + * @return string|null + */ + function getCurrentUser() { + return $this->user; + } +} diff --git a/tests/lib/connector/sabre/requesttest/exceptionplugin.php b/tests/lib/connector/sabre/requesttest/exceptionplugin.php new file mode 100644 index 00000000000..2b9e5d6d46d --- /dev/null +++ b/tests/lib/connector/sabre/requesttest/exceptionplugin.php @@ -0,0 +1,32 @@ +<?php +/** + * Copyright (c) 2015 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 Test\Connector\Sabre\RequestTest; + +use Sabre\DAV\Exception; + +class ExceptionPlugin extends \OC\Connector\Sabre\ExceptionLoggerPlugin { + /** + * @var \Exception[] + */ + protected $exceptions = []; + + public function logException(\Exception $ex) { + $exceptionClass = get_class($ex); + if (!isset($this->nonFatalExceptions[$exceptionClass])) { + $this->exceptions[] = $ex; + } + } + + /** + * @return \Exception[] + */ + public function getExceptions() { + return $this->exceptions; + } +} diff --git a/tests/lib/connector/sabre/requesttest/requesttest.php b/tests/lib/connector/sabre/requesttest/requesttest.php new file mode 100644 index 00000000000..c7739aefcd7 --- /dev/null +++ b/tests/lib/connector/sabre/requesttest/requesttest.php @@ -0,0 +1,163 @@ +<?php +/** + * Copyright (c) 2015 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 Test\Connector\Sabre\RequestTest; + +use OC\Connector\Sabre\Server; +use OC\Connector\Sabre\ServerFactory; +use OC\Files\Mount\MountPoint; +use OC\Files\Storage\Temporary; +use OC\Files\View; +use OCP\IUser; +use Sabre\HTTP\Request; +use Test\TestCase; + +abstract class RequestTest extends TestCase { + /** + * @var \OC_User_Dummy + */ + protected $userBackend; + + /** + * @var \OCP\Files\Config\IMountProvider[] + */ + protected $mountProviders; + + /** + * @var \OC\Connector\Sabre\ServerFactory + */ + protected $serverFactory; + + protected function getStream($string) { + $stream = fopen('php://temp', 'r+'); + fwrite($stream, $string); + fseek($stream, 0); + return $stream; + } + + /** + * @param $userId + * @param $storages + * @return \OCP\Files\Config\IMountProvider + */ + protected function getMountProvider($userId, $storages) { + $mounts = []; + foreach ($storages as $mountPoint => $storage) { + $mounts[] = new MountPoint($storage, $mountPoint); + } + $provider = $this->getMock('\OCP\Files\Config\IMountProvider'); + $provider->expects($this->any()) + ->method('getMountsForUser') + ->will($this->returnCallback(function (IUser $user) use ($userId, $mounts) { + if ($user->getUID() === $userId) { + return $mounts; + } else { + return []; + } + })); + return $provider; + } + + protected function setUp() { + parent::setUp(); + $this->userBackend = new \OC_User_Dummy(); + \OC::$server->getUserManager()->registerBackend($this->userBackend); + + $this->serverFactory = new ServerFactory( + \OC::$server->getConfig(), + \OC::$server->getLogger(), + \OC::$server->getDatabaseConnection(), + \OC::$server->getUserSession(), + \OC::$server->getMountManager(), + \OC::$server->getTagManager() + ); + } + + protected function tearDown() { + parent::tearDown(); + \OC::$server->getUserManager()->removeBackend($this->userBackend); + } + + protected function setupUser($name, $password) { + $this->userBackend->createUser($name, $password); + \OC::$server->getMountProviderCollection()->registerProvider($this->getMountProvider($name, [ + '/' . $name => new Temporary() + ])); + $this->loginAsUser($name); + return new View('/' . $name . '/files'); + } + + /** + * @param \OC\Files\View $view the view to run the webdav server against + * @param string $user + * @param string $password + * @param string $method + * @param string $url + * @param resource|string|null $body + * @param array|null $headers + * @return \Sabre\HTTP\Response + */ + protected function request($view, $user, $password, $method, $url, $body = null, $headers = null) { + if (is_string($body)) { + $body = $this->getStream($body); + } + $this->logout(); + $exceptionPlugin = new ExceptionPlugin('webdav', null); + $server = $this->getSabreServer($view, $user, $password, $exceptionPlugin); + $request = new Request($method, $url, $headers, $body); + + // since sabre catches all exceptions we need to save them and throw them from outside the sabre server + + $originalServer = $_SERVER; + + if (is_array($headers)) { + foreach ($headers as $header => $value) { + $_SERVER['HTTP_' . strtoupper(str_replace('-', '_', $header))] = $value; + } + } + + $result = $this->makeRequest($server, $request); + + foreach ($exceptionPlugin->getExceptions() as $exception) { + throw $exception; + } + $_SERVER = $originalServer; + return $result; + } + + /** + * @param Server $server + * @param Request $request + * @return \Sabre\HTTP\Response + */ + protected function makeRequest(Server $server, Request $request) { + $sapi = new Sapi($request); + $server->sapi = $sapi; + $server->httpRequest = $request; + $server->exec(); + return $sapi->getResponse(); + } + + /** + * @param View $view + * @param string $user + * @param string $password + * @param ExceptionPlugin $exceptionPlugin + * @return Server + */ + protected function getSabreServer(View $view, $user, $password, ExceptionPlugin $exceptionPlugin) { + $authBackend = new Auth($user, $password); + + $server = $this->serverFactory->createServer('/', 'dummy', $authBackend, function () use ($view) { + return $view; + }); + $server->addPlugin($exceptionPlugin); + + return $server; + } +} diff --git a/tests/lib/connector/sabre/requesttest/sapi.php b/tests/lib/connector/sabre/requesttest/sapi.php new file mode 100644 index 00000000000..7072b8bd286 --- /dev/null +++ b/tests/lib/connector/sabre/requesttest/sapi.php @@ -0,0 +1,53 @@ +<?php +/** + * Copyright (c) 2015 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 Test\Connector\Sabre\RequestTest; + +use Sabre\HTTP\Request; +use Sabre\HTTP\Response; + +class Sapi { + /** + * @var \Sabre\HTTP\Request + */ + private $request; + + /** + * @var \Sabre\HTTP\Response + */ + private $response; + + /** + * This static method will create a new Request object, based on the + * current PHP request. + * + * @return \Sabre\HTTP\Request + */ + public function getRequest() { + return $this->request; + } + + public function __construct(Request $request) { + $this->request = $request; + } + + /** + * @param \Sabre\HTTP\Response $response + * @return void + */ + public function sendResponse(Response $response) { + $this->response = $response; + } + + /** + * @return \Sabre\HTTP\Response + */ + public function getResponse() { + return $this->response; + } +} diff --git a/tests/lib/connector/sabre/requesttest/uploadtest.php b/tests/lib/connector/sabre/requesttest/uploadtest.php new file mode 100644 index 00000000000..2cc912d07f2 --- /dev/null +++ b/tests/lib/connector/sabre/requesttest/uploadtest.php @@ -0,0 +1,88 @@ +<?php +/** + * Copyright (c) 2015 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 Test\Connector\Sabre\RequestTest; + +class UploadTest extends RequestTest { + public function testBasicUpload() { + $user = $this->getUniqueID(); + $view = $this->setupUser($user, 'pass'); + + $this->assertFalse($view->file_exists('foo.txt')); + $response = $this->request($view, $user, 'pass', 'PUT', '/foo.txt', 'asd'); + + $this->assertEquals(201, $response->getStatus()); + $this->assertTrue($view->file_exists('foo.txt')); + $this->assertEquals('asd', $view->file_get_contents('foo.txt')); + } + + public function testUploadOverWrite() { + $user = $this->getUniqueID(); + $view = $this->setupUser($user, 'pass'); + + $view->file_put_contents('foo.txt', 'bar'); + + $response = $this->request($view, $user, 'pass', 'PUT', '/foo.txt', 'asd'); + + $this->assertEquals(204, $response->getStatus()); + $this->assertEquals('asd', $view->file_get_contents('foo.txt')); + } + + public function testChunkedUpload() { + $user = $this->getUniqueID(); + $view = $this->setupUser($user, 'pass'); + + $this->assertFalse($view->file_exists('foo.txt')); + $response = $this->request($view, $user, 'pass', 'PUT', '/foo.txt-chunking-123-2-0', 'asd', ['OC-Chunked' => '1']); + + $this->assertEquals(201, $response->getStatus()); + $this->assertFalse($view->file_exists('foo.txt')); + + $response = $this->request($view, $user, 'pass', 'PUT', '/foo.txt-chunking-123-2-1', 'bar', ['OC-Chunked' => '1']); + + $this->assertEquals(201, $response->getStatus()); + $this->assertTrue($view->file_exists('foo.txt')); + + $this->assertEquals('asdbar', $view->file_get_contents('foo.txt')); + } + + public function testChunkedUploadOverWrite() { + $user = $this->getUniqueID(); + $view = $this->setupUser($user, 'pass'); + + $view->file_put_contents('foo.txt', 'bar'); + $response = $this->request($view, $user, 'pass', 'PUT', '/foo.txt-chunking-123-2-0', 'asd', ['OC-Chunked' => '1']); + + $this->assertEquals(201, $response->getStatus()); + $this->assertEquals('bar', $view->file_get_contents('foo.txt')); + + $response = $this->request($view, $user, 'pass', 'PUT', '/foo.txt-chunking-123-2-1', 'bar', ['OC-Chunked' => '1']); + + $this->assertEquals(201, $response->getStatus()); + + $this->assertEquals('asdbar', $view->file_get_contents('foo.txt')); + } + + public function testChunkedUploadOutOfOrder() { + $user = $this->getUniqueID(); + $view = $this->setupUser($user, 'pass'); + + $this->assertFalse($view->file_exists('foo.txt')); + $response = $this->request($view, $user, 'pass', 'PUT', '/foo.txt-chunking-123-2-1', 'bar', ['OC-Chunked' => '1']); + + $this->assertEquals(201, $response->getStatus()); + $this->assertFalse($view->file_exists('foo.txt')); + + $response = $this->request($view, $user, 'pass', 'PUT', '/foo.txt-chunking-123-2-0', 'asd', ['OC-Chunked' => '1']); + + $this->assertEquals(201, $response->getStatus()); + $this->assertTrue($view->file_exists('foo.txt')); + + $this->assertEquals('asdbar', $view->file_get_contents('foo.txt')); + } +} diff --git a/tests/lib/db/querybuilder/querybuildertest.php b/tests/lib/db/querybuilder/querybuildertest.php index 02e516b7386..75e62ba944e 100644 --- a/tests/lib/db/querybuilder/querybuildertest.php +++ b/tests/lib/db/querybuilder/querybuildertest.php @@ -253,8 +253,8 @@ class QueryBuilderTest extends \Test\TestCase { public function dataDelete() { return [ - ['data', null, ['table' => '`data`', 'alias' => null], '`data`'], - ['data', 't', ['table' => '`data`', 'alias' => 't'], '`data` t'], + ['data', null, ['table' => '`*PREFIX*data`', 'alias' => null], '`*PREFIX*data`'], + ['data', 't', ['table' => '`*PREFIX*data`', 'alias' => 't'], '`*PREFIX*data` t'], ]; } @@ -282,8 +282,8 @@ class QueryBuilderTest extends \Test\TestCase { public function dataUpdate() { return [ - ['data', null, ['table' => '`data`', 'alias' => null], '`data`'], - ['data', 't', ['table' => '`data`', 'alias' => 't'], '`data` t'], + ['data', null, ['table' => '`*PREFIX*data`', 'alias' => null], '`*PREFIX*data`'], + ['data', 't', ['table' => '`*PREFIX*data`', 'alias' => 't'], '`*PREFIX*data` t'], ]; } @@ -311,7 +311,7 @@ class QueryBuilderTest extends \Test\TestCase { public function dataInsert() { return [ - ['data', ['table' => '`data`'], '`data`'], + ['data', ['table' => '`*PREFIX*data`'], '`*PREFIX*data`'], ]; } @@ -338,16 +338,16 @@ class QueryBuilderTest extends \Test\TestCase { public function dataFrom() { return [ - ['data', null, null, null, [['table' => '`data`', 'alias' => null]], '`data`'], - ['data', 't', null, null, [['table' => '`data`', 'alias' => 't']], '`data` t'], + ['data', null, null, null, [['table' => '`*PREFIX*data`', 'alias' => null]], '`*PREFIX*data`'], + ['data', 't', null, null, [['table' => '`*PREFIX*data`', 'alias' => 't']], '`*PREFIX*data` t'], ['data1', null, 'data2', null, [ - ['table' => '`data1`', 'alias' => null], - ['table' => '`data2`', 'alias' => null] - ], '`data1`, `data2`'], + ['table' => '`*PREFIX*data1`', 'alias' => null], + ['table' => '`*PREFIX*data2`', 'alias' => null] + ], '`*PREFIX*data1`, `*PREFIX*data2`'], ['data', 't1', 'data', 't2', [ - ['table' => '`data`', 'alias' => 't1'], - ['table' => '`data`', 'alias' => 't2'] - ], '`data` t1, `data` t2'], + ['table' => '`*PREFIX*data`', 'alias' => 't1'], + ['table' => '`*PREFIX*data`', 'alias' => 't2'] + ], '`*PREFIX*data` t1, `*PREFIX*data` t2'], ]; } @@ -382,18 +382,18 @@ class QueryBuilderTest extends \Test\TestCase { return [ [ 'd1', 'data2', null, null, - ['d1' => [['joinType' => 'inner', 'joinTable' => '`data2`', 'joinAlias' => null, 'joinCondition' => null]]], - '`data1` d1 INNER JOIN `data2` ON ' + ['d1' => [['joinType' => 'inner', 'joinTable' => '`*PREFIX*data2`', 'joinAlias' => null, 'joinCondition' => null]]], + '`*PREFIX*data1` d1 INNER JOIN `*PREFIX*data2` ON ' ], [ 'd1', 'data2', 'd2', null, - ['d1' => [['joinType' => 'inner', 'joinTable' => '`data2`', 'joinAlias' => 'd2', 'joinCondition' => null]]], - '`data1` d1 INNER JOIN `data2` d2 ON ' + ['d1' => [['joinType' => 'inner', 'joinTable' => '`*PREFIX*data2`', 'joinAlias' => 'd2', 'joinCondition' => null]]], + '`*PREFIX*data1` d1 INNER JOIN `*PREFIX*data2` d2 ON ' ], [ 'd1', 'data2', 'd2', 'd1.`field1` = d2.`field2`', - ['d1' => [['joinType' => 'inner', 'joinTable' => '`data2`', 'joinAlias' => 'd2', 'joinCondition' => 'd1.`field1` = d2.`field2`']]], - '`data1` d1 INNER JOIN `data2` d2 ON d1.`field1` = d2.`field2`' + ['d1' => [['joinType' => 'inner', 'joinTable' => '`*PREFIX*data2`', 'joinAlias' => 'd2', 'joinCondition' => 'd1.`field1` = d2.`field2`']]], + '`*PREFIX*data1` d1 INNER JOIN `*PREFIX*data2` d2 ON d1.`field1` = d2.`field2`' ], ]; @@ -463,18 +463,18 @@ class QueryBuilderTest extends \Test\TestCase { return [ [ 'd1', 'data2', null, null, - ['d1' => [['joinType' => 'left', 'joinTable' => '`data2`', 'joinAlias' => null, 'joinCondition' => null]]], - '`data1` d1 LEFT JOIN `data2` ON ' + ['d1' => [['joinType' => 'left', 'joinTable' => '`*PREFIX*data2`', 'joinAlias' => null, 'joinCondition' => null]]], + '`*PREFIX*data1` d1 LEFT JOIN `*PREFIX*data2` ON ' ], [ 'd1', 'data2', 'd2', null, - ['d1' => [['joinType' => 'left', 'joinTable' => '`data2`', 'joinAlias' => 'd2', 'joinCondition' => null]]], - '`data1` d1 LEFT JOIN `data2` d2 ON ' + ['d1' => [['joinType' => 'left', 'joinTable' => '`*PREFIX*data2`', 'joinAlias' => 'd2', 'joinCondition' => null]]], + '`*PREFIX*data1` d1 LEFT JOIN `*PREFIX*data2` d2 ON ' ], [ 'd1', 'data2', 'd2', 'd1.`field1` = d2.`field2`', - ['d1' => [['joinType' => 'left', 'joinTable' => '`data2`', 'joinAlias' => 'd2', 'joinCondition' => 'd1.`field1` = d2.`field2`']]], - '`data1` d1 LEFT JOIN `data2` d2 ON d1.`field1` = d2.`field2`' + ['d1' => [['joinType' => 'left', 'joinTable' => '`*PREFIX*data2`', 'joinAlias' => 'd2', 'joinCondition' => 'd1.`field1` = d2.`field2`']]], + '`*PREFIX*data1` d1 LEFT JOIN `*PREFIX*data2` d2 ON d1.`field1` = d2.`field2`' ], ]; } @@ -513,18 +513,18 @@ class QueryBuilderTest extends \Test\TestCase { return [ [ 'd1', 'data2', null, null, - ['d1' => [['joinType' => 'right', 'joinTable' => '`data2`', 'joinAlias' => null, 'joinCondition' => null]]], - '`data1` d1 RIGHT JOIN `data2` ON ' + ['d1' => [['joinType' => 'right', 'joinTable' => '`*PREFIX*data2`', 'joinAlias' => null, 'joinCondition' => null]]], + '`*PREFIX*data1` d1 RIGHT JOIN `*PREFIX*data2` ON ' ], [ 'd1', 'data2', 'd2', null, - ['d1' => [['joinType' => 'right', 'joinTable' => '`data2`', 'joinAlias' => 'd2', 'joinCondition' => null]]], - '`data1` d1 RIGHT JOIN `data2` d2 ON ' + ['d1' => [['joinType' => 'right', 'joinTable' => '`*PREFIX*data2`', 'joinAlias' => 'd2', 'joinCondition' => null]]], + '`*PREFIX*data1` d1 RIGHT JOIN `*PREFIX*data2` d2 ON ' ], [ 'd1', 'data2', 'd2', 'd1.`field1` = d2.`field2`', - ['d1' => [['joinType' => 'right', 'joinTable' => '`data2`', 'joinAlias' => 'd2', 'joinCondition' => 'd1.`field1` = d2.`field2`']]], - '`data1` d1 RIGHT JOIN `data2` d2 ON d1.`field1` = d2.`field2`' + ['d1' => [['joinType' => 'right', 'joinTable' => '`*PREFIX*data2`', 'joinAlias' => 'd2', 'joinCondition' => 'd1.`field1` = d2.`field2`']]], + '`*PREFIX*data1` d1 RIGHT JOIN `*PREFIX*data2` d2 ON d1.`field1` = d2.`field2`' ], ]; } @@ -591,7 +591,7 @@ class QueryBuilderTest extends \Test\TestCase { ); $this->assertSame( - 'UPDATE `data` SET ' . $expectedQuery, + 'UPDATE `*PREFIX*data` SET ' . $expectedQuery, $this->queryBuilder->getSQL() ); } @@ -774,7 +774,7 @@ class QueryBuilderTest extends \Test\TestCase { ); $this->assertSame( - 'INSERT INTO `data` ' . $expectedQuery, + 'INSERT INTO `*PREFIX*data` ' . $expectedQuery, $this->queryBuilder->getSQL() ); } @@ -799,7 +799,7 @@ class QueryBuilderTest extends \Test\TestCase { ); $this->assertSame( - 'INSERT INTO `data` ' . $expectedQuery, + 'INSERT INTO `*PREFIX*data` ' . $expectedQuery, $this->queryBuilder->getSQL() ); } @@ -996,4 +996,34 @@ class QueryBuilderTest extends \Test\TestCase { $this->queryBuilder->getSQL() ); } + + public function dataGetTableName() { + return [ + ['*PREFIX*table', null, '`*PREFIX*table`'], + ['*PREFIX*table', true, '`*PREFIX*table`'], + ['*PREFIX*table', false, '`*PREFIX*table`'], + + ['table', null, '`*PREFIX*table`'], + ['table', true, '`*PREFIX*table`'], + ['table', false, '`table`'], + ]; + } + + /** + * @dataProvider dataGetTableName + * + * @param string $tableName + * @param bool $automatic + * @param string $expected + */ + public function testGetTableName($tableName, $automatic, $expected) { + if ($automatic !== null) { + $this->queryBuilder->automaticTablePrefix($automatic); + } + + $this->assertSame( + $expected, + $this->invokePrivate($this->queryBuilder, 'getTableName', [$tableName]) + ); + } } diff --git a/tests/lib/files/view.php b/tests/lib/files/view.php index bf99a582117..bb42f385fc5 100644 --- a/tests/lib/files/view.php +++ b/tests/lib/files/view.php @@ -1386,7 +1386,9 @@ class View extends \Test\TestCase { ['/foo/files/bar', '/foo', true], ['/foo', '/foo', false], ['/foo', '/files/foo', true], - ['/foo', 'filesfoo', false] + ['/foo', 'filesfoo', false], + ['', '/foo/files', true], + ['', '/foo/files/bar.txt', true] ]; } diff --git a/tests/lib/repair/cleantags.php b/tests/lib/repair/cleantags.php index 2f6d0879642..a511daa03d6 100644 --- a/tests/lib/repair/cleantags.php +++ b/tests/lib/repair/cleantags.php @@ -40,13 +40,13 @@ class CleanTags extends \Test\TestCase { protected function cleanUpTables() { $qb = $this->connection->getQueryBuilder(); - $qb->delete('*PREFIX*vcategory') + $qb->delete('vcategory') ->execute(); - $qb->delete('*PREFIX*vcategory_to_object') + $qb->delete('vcategory_to_object') ->execute(); - $qb->delete('*PREFIX*filecache') + $qb->delete('filecache') ->execute(); } @@ -61,20 +61,20 @@ class CleanTags extends \Test\TestCase { $this->addTagEntry(9999999, $cat3, 'contacts'); // Retained $this->addTagEntry($this->getFileID(), $cat3 + 1, 'files'); // Deleted: Category is NULL - $this->assertEntryCount('*PREFIX*vcategory_to_object', 4, 'Assert tag entries count before repair step'); - $this->assertEntryCount('*PREFIX*vcategory', 4, 'Assert tag categories count before repair step'); + $this->assertEntryCount('vcategory_to_object', 4, 'Assert tag entries count before repair step'); + $this->assertEntryCount('vcategory', 4, 'Assert tag categories count before repair step'); self::invokePrivate($this->repair, 'deleteOrphanFileEntries'); - $this->assertEntryCount('*PREFIX*vcategory_to_object', 3, 'Assert tag entries count after cleaning file entries'); - $this->assertEntryCount('*PREFIX*vcategory', 4, 'Assert tag categories count after cleaning file entries'); + $this->assertEntryCount('vcategory_to_object', 3, 'Assert tag entries count after cleaning file entries'); + $this->assertEntryCount('vcategory', 4, 'Assert tag categories count after cleaning file entries'); self::invokePrivate($this->repair, 'deleteOrphanTagEntries'); - $this->assertEntryCount('*PREFIX*vcategory_to_object', 2, 'Assert tag entries count after cleaning tag entries'); - $this->assertEntryCount('*PREFIX*vcategory', 4, 'Assert tag categories count after cleaning tag entries'); + $this->assertEntryCount('vcategory_to_object', 2, 'Assert tag entries count after cleaning tag entries'); + $this->assertEntryCount('vcategory', 4, 'Assert tag categories count after cleaning tag entries'); self::invokePrivate($this->repair, 'deleteOrphanCategoryEntries'); - $this->assertEntryCount('*PREFIX*vcategory_to_object', 2, 'Assert tag entries count after cleaning category entries'); - $this->assertEntryCount('*PREFIX*vcategory', 2, 'Assert tag categories count after cleaning category entries'); + $this->assertEntryCount('vcategory_to_object', 2, 'Assert tag entries count after cleaning category entries'); + $this->assertEntryCount('vcategory', 2, 'Assert tag categories count after cleaning category entries'); } /** @@ -100,7 +100,7 @@ class CleanTags extends \Test\TestCase { */ protected function addTagCategory($category, $type) { $qb = $this->connection->getQueryBuilder(); - $qb->insert('*PREFIX*vcategory') + $qb->insert('vcategory') ->values([ 'uid' => $qb->createNamedParameter('TestRepairCleanTags'), 'category' => $qb->createNamedParameter($category), @@ -108,7 +108,7 @@ class CleanTags extends \Test\TestCase { ]) ->execute(); - return (int) $this->getLastInsertID('*PREFIX*vcategory', 'id'); + return (int) $this->getLastInsertID('vcategory', 'id'); } /** @@ -119,7 +119,7 @@ class CleanTags extends \Test\TestCase { */ protected function addTagEntry($objectId, $category, $type) { $qb = $this->connection->getQueryBuilder(); - $qb->insert('*PREFIX*vcategory_to_object') + $qb->insert('vcategory_to_object') ->values([ 'objid' => $qb->createNamedParameter($objectId, \PDO::PARAM_INT), 'categoryid' => $qb->createNamedParameter($category, \PDO::PARAM_INT), @@ -141,21 +141,21 @@ class CleanTags extends \Test\TestCase { // We create a new file entry and delete it after the test again $fileName = $this->getUniqueID('TestRepairCleanTags', 12); - $qb->insert('*PREFIX*filecache') + $qb->insert('filecache') ->values([ 'path' => $qb->createNamedParameter($fileName), 'path_hash' => $qb->createNamedParameter(md5($fileName)), ]) ->execute(); $fileName = $this->getUniqueID('TestRepairCleanTags', 12); - $qb->insert('*PREFIX*filecache') + $qb->insert('filecache') ->values([ 'path' => $qb->createNamedParameter($fileName), 'path_hash' => $qb->createNamedParameter(md5($fileName)), ]) ->execute(); - $this->createdFile = (int) $this->getLastInsertID('*PREFIX*filecache', 'fileid'); + $this->createdFile = (int) $this->getLastInsertID('filecache', 'fileid'); return $this->createdFile; } diff --git a/tests/lib/share/share.php b/tests/lib/share/share.php index b6d3e16826d..ef0d9822085 100644 --- a/tests/lib/share/share.php +++ b/tests/lib/share/share.php @@ -1288,7 +1288,7 @@ class Test_Share extends \Test\TestCase { // Find the share ID in the db $qb = $connection->getQueryBuilder(); $qb->select('id') - ->from('*PREFIX*share') + ->from('share') ->where($qb->expr()->eq('item_type', $qb->createParameter('type'))) ->andWhere($qb->expr()->eq('item_source', $qb->createParameter('source'))) ->andWhere($qb->expr()->eq('uid_owner', $qb->createParameter('owner'))) @@ -1309,7 +1309,7 @@ class Test_Share extends \Test\TestCase { // Fetch the hash from the database $qb = $connection->getQueryBuilder(); $qb->select('share_with') - ->from('*PREFIX*share') + ->from('share') ->where($qb->expr()->eq('id', $qb->createParameter('id'))) ->setParameter('id', $id); $hash = $qb->execute()->fetch()['share_with']; diff --git a/tests/settings/controller/CheckSetupControllerTest.php b/tests/settings/controller/CheckSetupControllerTest.php index 6096aae8652..414b1b91e17 100644 --- a/tests/settings/controller/CheckSetupControllerTest.php +++ b/tests/settings/controller/CheckSetupControllerTest.php @@ -31,11 +31,24 @@ use OC_Util; use Test\TestCase; /** + * Mock version_compare + * @param string $version1 + * @param string $version2 + * @return int + */ +function version_compare($version1, $version2) { + return CheckSetupControllerTest::$version_compare; +} + +/** * Class CheckSetupControllerTest * * @package OC\Settings\Controller */ class CheckSetupControllerTest extends TestCase { + /** @var int */ + public static $version_compare; + /** @var CheckSetupController */ private $checkSetupController; /** @var IRequest */ @@ -209,6 +222,66 @@ class CheckSetupControllerTest extends TestCase { ); } + public function testIsPhpSupportedFalse() { + self::$version_compare = -1; + + $this->assertEquals( + ['eol' => true, 'version' => PHP_VERSION], + self::invokePrivate($this->checkSetupController, 'isPhpSupported') + ); + } + + public function testIsPhpSupportedTrue() { + self::$version_compare = 0; + + $this->assertEquals( + ['eol' => false, 'version' => PHP_VERSION], + self::invokePrivate($this->checkSetupController, 'isPhpSupported') + ); + + + self::$version_compare = 1; + + $this->assertEquals( + ['eol' => false, 'version' => PHP_VERSION], + self::invokePrivate($this->checkSetupController, 'isPhpSupported') + ); + } + + public function testForwardedForHeadersWorkingFalse() { + $this->config->expects($this->once()) + ->method('getSystemValue') + ->with('trusted_proxies', []) + ->willReturn(['1.2.3.4']); + $this->request->expects($this->once()) + ->method('getRemoteAddress') + ->willReturn('1.2.3.4'); + + $this->assertFalse( + self::invokePrivate( + $this->checkSetupController, + 'forwardedForHeadersWorking' + ) + ); + } + + public function testForwardedForHeadersWorkingTrue() { + $this->config->expects($this->once()) + ->method('getSystemValue') + ->with('trusted_proxies', []) + ->willReturn(['1.2.3.4']); + $this->request->expects($this->once()) + ->method('getRemoteAddress') + ->willReturn('4.3.2.1'); + + $this->assertTrue( + self::invokePrivate( + $this->checkSetupController, + 'forwardedForHeadersWorking' + ) + ); + } + public function testCheck() { $this->config->expects($this->at(0)) ->method('getSystemValue') @@ -218,6 +291,14 @@ class CheckSetupControllerTest extends TestCase { ->method('getSystemValue') ->with('memcache.local', null) ->will($this->returnValue('SomeProvider')); + $this->config->expects($this->at(2)) + ->method('getSystemValue') + ->with('trusted_proxies', []) + ->willReturn(['1.2.3.4']); + + $this->request->expects($this->once()) + ->method('getRemoteAddress') + ->willReturn('4.3.2.1'); $client = $this->getMockBuilder('\OCP\Http\Client\IClient') ->disableOriginalConstructor()->getMock(); @@ -244,6 +325,11 @@ class CheckSetupControllerTest extends TestCase { ->method('linkToDocs') ->with('admin-security') ->willReturn('https://doc.owncloud.org/server/8.1/admin_manual/configuration_server/hardening.html'); + self::$version_compare = -1; + $this->urlGenerator->expects($this->at(2)) + ->method('linkToDocs') + ->with('admin-reverse-proxy') + ->willReturn('reverse-proxy-doc-link'); $expected = new DataResponse( [ @@ -254,6 +340,12 @@ class CheckSetupControllerTest extends TestCase { 'isUrandomAvailable' => self::invokePrivate($this->checkSetupController, 'isUrandomAvailable'), 'securityDocs' => 'https://doc.owncloud.org/server/8.1/admin_manual/configuration_server/hardening.html', 'isUsedTlsLibOutdated' => '', + 'phpSupported' => [ + 'eol' => true, + 'version' => PHP_VERSION + ], + 'forwardedForHeadersWorking' => true, + 'reverseProxyDocs' => 'reverse-proxy-doc-link', ] ); $this->assertEquals($expected, $this->checkSetupController->check()); diff --git a/themes/example/defaults.php b/themes/example/defaults.php index 21d80416e12..0dd0d46bd9c 100644 --- a/themes/example/defaults.php +++ b/themes/example/defaults.php @@ -28,7 +28,6 @@ class OC_Theme { private $themeSyncClientUrl; private $themeSlogan; private $themeMailHeaderColor; - private $themeKnowledgeBaseUrl; /* put your custom text in these variables */ function __construct() { @@ -40,7 +39,6 @@ class OC_Theme { $this->themeSyncClientUrl = 'https://owncloud.org/install'; $this->themeSlogan = 'Your custom cloud, personalized for you!'; $this->themeMailHeaderColor = '#745bca'; - $this->themeKnowledgeBaseUrl = 'https://doc.owncloud.org'; } /* nothing after this needs to be adjusted */ @@ -94,8 +92,4 @@ class OC_Theme { return $this->themeMailHeaderColor; } - public function getKnowledgeBaseUrl() { - return $this->themeKnowledgeBaseUrl; - } - } diff --git a/version.php b/version.php index 7ccd2e6b548..a115f4b26be 100644 --- a/version.php +++ b/version.php @@ -22,7 +22,7 @@ // We only can count up. The 4. digit is only for the internal patchlevel to trigger DB upgrades // between betas, final and RCs. This is _not_ the public version number. Reset minor/patchlevel // when updating major/minor version number. -$OC_Version=array(8, 2, 0, 3); +$OC_Version=array(8, 2, 0, 4); // The human readable string $OC_VersionString='8.2 pre alpha'; |