diff options
25 files changed, 411 insertions, 141 deletions
diff --git a/.htaccess b/.htaccess index 4a4adce144c..725efa0971a 100644 --- a/.htaccess +++ b/.htaccess @@ -1,9 +1,12 @@ <IfModule mod_headers.c> - <IfModule mod_fcgid.c> - <IfModule mod_setenvif.c> + <IfModule mod_setenvif.c> + <IfModule mod_fcgid.c> SetEnvIfNoCase ^Authorization$ "(.+)" XAUTHORIZATION=$1 RequestHeader set XAuthorization %{XAUTHORIZATION}e env=XAUTHORIZATION </IfModule> + <IfModule mod_proxy_fcgi.c> + SetEnvIfNoCase Authorization "(.+)" HTTP_AUTHORIZATION=$1 + </IfModule> </IfModule> <IfModule mod_env.c> diff --git a/apps/files_external/controller/storagescontroller.php b/apps/files_external/controller/storagescontroller.php index db1cdeb23b9..65ceba21454 100644 --- a/apps/files_external/controller/storagescontroller.php +++ b/apps/files_external/controller/storagescontroller.php @@ -255,8 +255,9 @@ abstract class StoragesController extends Controller { ) ); } catch (InsufficientDataForMeaningfulAnswerException $e) { + $status = $e->getCode() ? $e->getCode() : StorageNotAvailableException::STATUS_INDETERMINATE; $storage->setStatus( - StorageNotAvailableException::STATUS_INDETERMINATE, + $status, $this->l10n->t('Insufficient data: %s', [$e->getMessage()]) ); } catch (StorageNotAvailableException $e) { diff --git a/apps/files_external/js/statusmanager.js b/apps/files_external/js/statusmanager.js index 27635d2f1df..33d2ea104be 100644 --- a/apps/files_external/js/statusmanager.js +++ b/apps/files_external/js/statusmanager.js @@ -22,15 +22,15 @@ if (!OCA.External.StatusManager) { OCA.External.StatusManager = { - mountStatus : null, - mountPointList : null, + mountStatus: null, + mountPointList: null, /** * Function * @param {callback} afterCallback */ - getMountStatus : function(afterCallback) { + getMountStatus: function (afterCallback) { var self = this; if (typeof afterCallback !== 'function' || self.isGetMountStatusRunning) { return; @@ -46,9 +46,9 @@ OCA.External.StatusManager = { * @param {string} mount_point */ - getMountPointListElement : function(mount_point) { + getMountPointListElement: function (mount_point) { var element; - $.each(this.mountPointList, function(key, value){ + $.each(this.mountPointList, function (key, value) { if (value.mount_point === mount_point) { element = value; return false; @@ -63,7 +63,7 @@ OCA.External.StatusManager = { * @param {string} mount_point */ - getMountStatusForMount : function(mountData, afterCallback) { + getMountStatusForMount: function (mountData, afterCallback) { var self = this; if (typeof afterCallback !== 'function' || self.isGetMountStatusRunning) { return $.Deferred().resolve(); @@ -72,41 +72,46 @@ OCA.External.StatusManager = { var defObj; if (self.mountStatus[mountData.mount_point]) { defObj = $.Deferred(); - afterCallback(mountData, self.mountStatus[mountData.mount_point]); + afterCallback(mountData, self.mountStatus[mountData.mount_point]); defObj.resolve(); // not really useful, but it'll keep the same behaviour } else { defObj = $.ajax({ - type : 'GET', + type: 'GET', url: OC.webroot + '/index.php/apps/files_external/' + ((mountData.type === 'personal') ? 'userstorages' : 'userglobalstorages') + '/' + mountData.id, - success : function(response) { + success: function (response) { if (response && response.status === 0) { self.mountStatus[mountData.mount_point] = response; } else { - if (response && response.statusMessage) { - // failure response with error message - self.mountStatus[mountData.mount_point] = { type: mountData.type, - status: 1, - error: response.statusMessage}; - } else { - self.mountStatus[mountData.mount_point] = { type: mountData.type, - status: 1, - error: t('files_external', 'Empty response from the server')}; - } + var statusCode = response.status ? response.status : 1; + var statusMessage = response.statusMessage ? response.statusMessage : t('files_external', 'Empty response from the server') + // failure response with error message + self.mountStatus[mountData.mount_point] = { + type: mountData.type, + status: statusCode, + id: mountData.id, + error: statusMessage, + userProvided: response.userProvided + }; } afterCallback(mountData, self.mountStatus[mountData.mount_point]); }, - error : function(jqxhr, state, error) { + error: function (jqxhr, state, error) { var message; - if(mountData.location === 3){ + if (mountData.location === 3) { // In this case the error is because mount point use Login credentials and don't exist in the session message = t('files_external', 'Couldn\'t access. Please logout and login to activate this mount point'); } else { - message = t('files_external', 'Couldn\'t get the information from the ownCloud server: {code} {type}', {code: jqxhr.status, type: error}); + message = t('files_external', 'Couldn\'t get the information from the ownCloud server: {code} {type}', { + code: jqxhr.status, + type: error + }); } - self.mountStatus[mountData.mount_point] = { type: mountData.type, - status: 1, - location: mountData.location, - error: message}; + self.mountStatus[mountData.mount_point] = { + type: mountData.type, + status: 1, + location: mountData.location, + error: message + }; afterCallback(mountData, self.mountStatus[mountData.mount_point]); } }); @@ -119,7 +124,7 @@ OCA.External.StatusManager = { * @param {function} afterCallback function to be executed */ - getMountPointList : function(afterCallback) { + getMountPointList: function (afterCallback) { var self = this; if (typeof afterCallback !== 'function' || self.isGetMountPointListRunning) { return; @@ -130,11 +135,11 @@ OCA.External.StatusManager = { } else { self.isGetMountPointListRunning = true; $.ajax({ - type : 'GET', - url : OC.linkToOCS('apps/files_external/api/v1') + 'mounts?format=json', - success : function(response) { + type: 'GET', + url: OC.linkToOCS('apps/files_external/api/v1') + 'mounts?format=json', + success: function (response) { self.mountPointList = []; - _.each(response.ocs.data, function(mount){ + _.each(response.ocs.data, function (mount) { var element = {}; element.mount_point = mount.name; element.type = mount.scope; @@ -147,11 +152,11 @@ OCA.External.StatusManager = { }); afterCallback(self.mountPointList); }, - error : function(jqxhr, state, error) { + error: function (jqxhr, state, error) { self.mountPointList = []; - OC.Notification.showTemporary(t('files_external', 'Couldn\'t get the list of external mount points: {type}', {type : error})); + OC.Notification.showTemporary(t('files_external', 'Couldn\'t get the list of external mount points: {type}', {type: error})); }, - complete : function() { + complete: function () { self.isGetMountPointListRunning = false; } }); @@ -163,21 +168,25 @@ OCA.External.StatusManager = { * @param {string} name MountPoint Name */ - manageMountPointError : function(name) { - var self = this; - this.getMountStatus($.proxy(function(allMountStatus) { - if (typeof allMountStatus[name] !== 'undefined' || allMountStatus[name].status === 1) { + manageMountPointError: function (name) { + this.getMountStatus($.proxy(function (allMountStatus) { + if (allMountStatus.hasOwnProperty(name) && allMountStatus[name].status > 0 && allMountStatus[name].status < 7) { var mountData = allMountStatus[name]; if (mountData.type === "system") { - OC.dialogs.confirm(t('files_external', 'There was an error with message: ') + mountData.error + '. Do you want to review mount point config in admin settings page?', t('files_external', 'External mount error'), function(e){ - if(e === true) { - window.location.href = OC.generateUrl('/settings/admin#files_external'); - } - }); + if (mountData.userProvided) { + // personal mount whit credentials problems + this.showCredentialsDialog(name, mountData); + } else { + OC.dialogs.confirm(t('files_external', 'There was an error with message: ') + mountData.error + '. Do you want to review mount point config in admin settings page?', t('files_external', 'External mount error'), function (e) { + if (e === true) { + OC.redirect(OC.generateUrl('/settings/admin#files_external')); + } + }); + } } else { - OC.dialogs.confirm(t('files_external', 'There was an error with message: ') + mountData.error + '. Do you want to review mount point config in personal settings page?', t('files_external', 'External mount error'), function(e){ - if(e === true) { - window.location.href = OC.generateUrl('/settings/personal#external-storage'); + OC.dialogs.confirm(t('files_external', 'There was an error with message: ') + mountData.error + '. Do you want to review mount point config in personal settings page?', t('files_external', 'External mount error'), function (e) { + if (e === true) { + OC.redirect(OC.generateUrl('/settings/personal#' + t('files_external', 'external-storage'))); } }); } @@ -191,13 +200,13 @@ OCA.External.StatusManager = { * @param {object} mountStatus */ - processMountStatusIndividual : function(mountData, mountStatus) { + processMountStatusIndividual: function (mountData, mountStatus) { var mountPoint = mountData.mount_point; - if (mountStatus.status === 1) { + if (mountStatus.status > 0) { var trElement = FileList.findFileEl(OCA.External.StatusManager.Utils.jqSelEscape(mountPoint)); - route = OCA.External.StatusManager.Utils.getIconRoute(trElement) + '-error'; + var route = OCA.External.StatusManager.Utils.getIconRoute(trElement) + '-error'; if (OCA.External.StatusManager.Utils.isCorrectViewAndRootFolder()) { OCA.External.StatusManager.Utils.showIconError(mountPoint, $.proxy(OCA.External.StatusManager.manageMountPointError, OCA.External.StatusManager), route); @@ -218,9 +227,9 @@ OCA.External.StatusManager = { * @param {object} mountStatus */ - processMountList : function(mountList) { + processMountList: function (mountList) { var elementList = null; - $.each(mountList, function(name, value){ + $.each(mountList, function (name, value) { var trElement = $('#fileList tr[data-file=\"' + OCA.External.StatusManager.Utils.jqSelEscape(value.mount_point) + '\"]'); //FileList.findFileEl(OCA.External.StatusManager.Utils.jqSelEscape(value.mount_point)); trElement.attr('data-external-backend', value.backend); if (elementList) { @@ -247,9 +256,9 @@ OCA.External.StatusManager = { * Function to process the whole mount point list in relation with their status (Async queue) */ - launchFullConnectivityCheckOneByOne : function() { + launchFullConnectivityCheckOneByOne: function () { var self = this; - this.getMountPointList(function(list){ + this.getMountPointList(function (list) { // check if we have a list first if (list === undefined && !self.emptyWarningShown) { self.emptyWarningShown = true; @@ -264,17 +273,19 @@ OCA.External.StatusManager = { } var ajaxQueue = []; - $.each(list, function(key, value){ - var queueElement = {funcName: $.proxy(self.getMountStatusForMount, self), - funcArgs: [value, - $.proxy(self.processMountStatusIndividual, self)]}; + $.each(list, function (key, value) { + var queueElement = { + funcName: $.proxy(self.getMountStatusForMount, self), + funcArgs: [value, + $.proxy(self.processMountStatusIndividual, self)] + }; ajaxQueue.push(queueElement); }); - var rolQueue = new OCA.External.StatusManager.RollingQueue(ajaxQueue, 4, function(){ + var rolQueue = new OCA.External.StatusManager.RollingQueue(ajaxQueue, 4, function () { if (!self.notificationHasShown) { var showNotification = false; - $.each(self.mountStatus, function(key, value){ + $.each(self.mountStatus, function (key, value) { if (value.status === 1) { self.notificationHasShown = true; showNotification = true; @@ -297,20 +308,22 @@ OCA.External.StatusManager = { * @param {boolean} recheck delete cached info and force api call to check mount point status */ - launchPartialConnectivityCheck : function(mountListData, recheck) { + launchPartialConnectivityCheck: function (mountListData, recheck) { if (mountListData.length === 0) { return; } var self = this; var ajaxQueue = []; - $.each(mountListData, function(key, value){ + $.each(mountListData, function (key, value) { if (recheck && value.mount_point in self.mountStatus) { delete self.mountStatus[value.mount_point]; } - var queueElement = {funcName: $.proxy(self.getMountStatusForMount, self), - funcArgs: [value, - $.proxy(self.processMountStatusIndividual, self)]}; + var queueElement = { + funcName: $.proxy(self.getMountStatusForMount, self), + funcArgs: [value, + $.proxy(self.processMountStatusIndividual, self)] + }; ajaxQueue.push(queueElement); }); new OCA.External.StatusManager.RollingQueue(ajaxQueue, 4).runQueue(); @@ -323,21 +336,19 @@ OCA.External.StatusManager = { * @param {boolean} recheck delete cached info and force api call to check mount point status */ - recheckConnectivityForMount : function(mountListNames, recheck) { + recheckConnectivityForMount: function (mountListNames, recheck) { if (mountListNames.length === 0) { return; } var self = this; var mountListData = []; - var recheckPersonalGlobal = false; - var recheckAdminGlobal = false; if (!self.mountStatus) { self.mountStatus = {}; } - $.each(mountListNames, function(key, value){ + $.each(mountListNames, function (key, value) { var mountData = self.getMountPointListElement(value); if (mountData) { mountListData.push(mountData); @@ -346,7 +357,7 @@ OCA.External.StatusManager = { // for all mounts in the list, delete the cached status values if (recheck) { - $.each(mountListData, function(key, value){ + $.each(mountListData, function (key, value) { if (value.mount_point in self.mountStatus) { delete self.mountStatus[value.mount_point]; } @@ -355,12 +366,96 @@ OCA.External.StatusManager = { self.processMountList(mountListData); self.launchPartialConnectivityCheck(mountListData, recheck); + }, + + credentialsDialogTemplate: + '<div id="files_external_div_form"><div>' + + '<div>{{credentials_text}}</div>' + + '<form>' + + '<input type="text" name="username" placeholder="{{placeholder_username}}"/>' + + '<input type="password" name="password" placeholder="{{placeholder_password}}"/>' + + '</form>' + + '</div></div>', + + /** + * Function to display custom dialog to enter credentials + * @param mountPoint + * @param mountData + */ + showCredentialsDialog: function (mountPoint, mountData) { + var template = Handlebars.compile(OCA.External.StatusManager.credentialsDialogTemplate); + var dialog = $(template({ + credentials_text: t('files_external', 'Please enter the credentials for the {mount} mount', { + 'mount': mountPoint + }), + placeholder_username: t('files_external', 'Username'), + placeholder_password: t('files_external', 'Password') + })); + + $('body').append(dialog); + + var apply = function () { + var username = dialog.find('[name=username]').val(); + var password = dialog.find('[name=password]').val(); + var endpoint = OC.generateUrl('apps/files_external/userglobalstorages/{id}', { + id: mountData.id + }); + $('.oc-dialog-close').hide(); + $.ajax({ + type: 'PUT', + url: endpoint, + data: { + backendOptions: { + user: username, + password: password + } + }, + success: function (data) { + OC.Notification.showTemporary(t('files_external', 'Credentials saved')); + dialog.ocdialog('close'); + /* Trigger status check again */ + OCA.External.StatusManager.recheckConnectivityForMount([OC.basename(data.mountPoint)], true); + }, + error: function () { + $('.oc-dialog-close').show(); + OC.Notification.showTemporary(t('files_external', 'Credentials saving failed')); + } + }); + return false; + }; + + var ocdialogParams = { + modal: true, + title: t('files_external', 'Credentials required'), + buttons: [{ + text: t('files_external', 'Save'), + click: apply, + closeOnEscape: true + }], + closeOnExcape: true + }; + + dialog.ocdialog(ocdialogParams) + .bind('ocdialogclose', function () { + dialog.ocdialog('destroy').remove(); + }); + + dialog.find('form').on('submit', apply); + dialog.find('form input:first').focus(); + dialog.find('form input').keyup(function (e) { + if ((e.which && e.which === 13) || (e.keyCode && e.keyCode === 13)) { + $(e.target).closest('form').submit(); + return false; + } else { + return true; + } + }); } }; OCA.External.StatusManager.Utils = { - showIconError: function(folder, clickAction, errorImageUrl) { + showIconError: function (folder, clickAction, errorImageUrl) { var imageUrl = "url(" + errorImageUrl + ")"; var trFolder = $('#fileList tr[data-file=\"' + OCA.External.StatusManager.Utils.jqSelEscape(folder) + '\"]'); //FileList.findFileEl(OCA.External.StatusManager.Utils.jqSelEscape(folder)); this.changeFolderIcon(folder, imageUrl); @@ -371,14 +466,14 @@ OCA.External.StatusManager.Utils = { /** * @param folder string with the folder or jQuery element pointing to the tr element */ - storeDefaultFolderIconAndBgcolor: function(folder) { + storeDefaultFolderIconAndBgcolor: function (folder) { var trFolder; if (folder instanceof $) { trFolder = folder; } else { trFolder = $('#fileList tr[data-file=\"' + OCA.External.StatusManager.Utils.jqSelEscape(folder) + '\"]'); //FileList.findFileEl(OCA.External.StatusManager.Utils.jqSelEscape(folder)); //$('#fileList tr[data-file=\"' + OCA.External.StatusManager.Utils.jqSelEscape(folder) + '\"]'); } - trFolder.each(function(){ + trFolder.each(function () { var thisElement = $(this); if (thisElement.data('oldbgcolor') === undefined) { thisElement.data('oldbgcolor', thisElement.css('background-color')); @@ -386,7 +481,7 @@ OCA.External.StatusManager.Utils = { }); var icon = trFolder.find('td:first-child div.thumbnail'); - icon.each(function(){ + icon.each(function () { var thisElement = $(this); if (thisElement.data('oldImage') === undefined) { thisElement.data('oldImage', thisElement.css('background-image')); @@ -397,7 +492,7 @@ OCA.External.StatusManager.Utils = { /** * @param folder string with the folder or jQuery element pointing to the tr element */ - restoreFolder: function(folder) { + restoreFolder: function (folder) { var trFolder; if (folder instanceof $) { trFolder = folder; @@ -407,7 +502,7 @@ OCA.External.StatusManager.Utils = { } trFolder.removeClass('externalErroredRow').removeClass('externalDisabledRow'); tdChilds = trFolder.find("td:first-child div.thumbnail"); - tdChilds.each(function(){ + tdChilds.each(function () { var thisElement = $(this); thisElement.css('background-image', thisElement.data('oldImage')); }); @@ -417,12 +512,12 @@ OCA.External.StatusManager.Utils = { * @param folder string with the folder or jQuery element pointing to the first td element * of the tr matching the folder name */ - changeFolderIcon: function(filename) { + changeFolderIcon: function (filename) { var file; var route; if (filename instanceof $) { //trElementList - $.each(filename, function(index){ + $.each(filename, function (index) { route = OCA.External.StatusManager.Utils.getIconRoute($(this)); $(this).attr("data-icon", route); $(this).find('td:first-child div.thumbnail').css('background-image', "url(" + route + ")").css('display', 'none').css('display', 'inline'); @@ -440,7 +535,7 @@ OCA.External.StatusManager.Utils = { * @param backend string with the name of the external storage backend * of the tr matching the folder name */ - getIconRoute: function(tr) { + getIconRoute: function (tr) { var icon = OC.imagePath('core', 'filetypes/folder-external'); var backend = null; @@ -460,7 +555,7 @@ OCA.External.StatusManager.Utils = { return icon; }, - toggleLink: function(filename, active, action) { + toggleLink: function (filename, active, action) { var link; if (filename instanceof $) { link = filename; @@ -473,7 +568,7 @@ OCA.External.StatusManager.Utils = { } else { link.find('.fileactions, .nametext .action').remove(); // from files/js/fileactions (display) link.off('click.connectivity'); - link.on('click.connectivity', function(e){ + link.on('click.connectivity', function (e) { if (action && $.isFunction(action)) { action(filename); } @@ -483,7 +578,7 @@ OCA.External.StatusManager.Utils = { } }, - isCorrectViewAndRootFolder: function() { + isCorrectViewAndRootFolder: function () { // correct views = files & extstoragemounts if (OCA.Files.App.getActiveView() === 'files' || OCA.Files.App.getActiveView() === 'extstoragemounts') { return OCA.Files.App.getCurrentAppContainer().find('#dir').val() === '/'; @@ -492,15 +587,15 @@ OCA.External.StatusManager.Utils = { }, /* escape a selector expression for jQuery */ - jqSelEscape: function(expression) { - if(expression){ + jqSelEscape: function (expression) { + if (expression) { return expression.replace(/[!"#$%&'()*+,.\/:;<=>?@\[\\\]^`{|}~]/g, '\\$&'); } return null; }, /* Copied from http://stackoverflow.com/questions/2631001/javascript-test-for-existence-of-nested-object-key */ - checkNested: function(cobj /*, level1, level2, ... levelN*/) { + checkNested: function (cobj /*, level1, level2, ... levelN*/) { var args = Array.prototype.slice.call(arguments), obj = args.shift(); diff --git a/apps/files_external/lib/auth/password/globalauth.php b/apps/files_external/lib/auth/password/globalauth.php index dcfea65b555..b1e52fb53ab 100644 --- a/apps/files_external/lib/auth/password/globalauth.php +++ b/apps/files_external/lib/auth/password/globalauth.php @@ -55,7 +55,10 @@ class GlobalAuth extends AuthMechanism { public function getAuth($uid) { $auth = $this->credentialsManager->retrieve($uid, self::CREDENTIALS_IDENTIFIER); if (!is_array($auth)) { - return []; + return [ + 'user' => '', + 'password' => '' + ]; } else { return $auth; } diff --git a/apps/files_external/lib/config/configadapter.php b/apps/files_external/lib/config/configadapter.php index 2bf39bcaa4f..51c2debd726 100644 --- a/apps/files_external/lib/config/configadapter.php +++ b/apps/files_external/lib/config/configadapter.php @@ -130,6 +130,16 @@ class ConfigAdapter implements IMountProvider { $impl = new FailedStorage(['exception' => $e]); } + try { + $availability = $impl->getAvailability(); + if (!$availability['available']) { + $impl = new FailedStorage(['exception' => null]); + } + } catch (\Exception $e) { + // propagate exception into filesystem + $impl = new FailedStorage(['exception' => $e]); + } + $mount = new MountPoint( $impl, '/' . $user->getUID() . '/files' . $storage->getMountPoint(), diff --git a/apps/files_external/lib/failedcache.php b/apps/files_external/lib/failedcache.php index f9866f43058..0f59495e595 100644 --- a/apps/files_external/lib/failedcache.php +++ b/apps/files_external/lib/failedcache.php @@ -22,6 +22,7 @@ namespace OCA\Files_External\Lib; use OC\Files\Cache\CacheEntry; +use OCP\Constants; use OCP\Files\Cache\ICache; /** @@ -40,7 +41,7 @@ class FailedCache implements ICache { 'size' => 0, 'mimetype' => 'httpd/unix-directory', 'mimepart' => 'httpd', - 'permissions' => 0, + 'permissions' => Constants::PERMISSION_READ, 'mtime' => time() ]); } else { diff --git a/apps/files_external/lib/insufficientdataformeaningfulanswerexception.php b/apps/files_external/lib/insufficientdataformeaningfulanswerexception.php index 871301b9b51..22d83ef56f4 100644 --- a/apps/files_external/lib/insufficientdataformeaningfulanswerexception.php +++ b/apps/files_external/lib/insufficientdataformeaningfulanswerexception.php @@ -27,4 +27,15 @@ use \OCP\Files\StorageNotAvailableException; * Authentication mechanism or backend has insufficient data */ class InsufficientDataForMeaningfulAnswerException extends StorageNotAvailableException { + /** + * StorageNotAvailableException constructor. + * + * @param string $message + * @param int $code + * @param \Exception $previous + * @since 6.0.0 + */ + public function __construct($message = '', $code = self::STATUS_INDETERMINATE, \Exception $previous = null) { + parent::__construct($message, $code, $previous); + } } diff --git a/apps/files_external/lib/smb.php b/apps/files_external/lib/smb.php index 9da21dc88e6..50bd56f28ad 100644 --- a/apps/files_external/lib/smb.php +++ b/apps/files_external/lib/smb.php @@ -314,4 +314,17 @@ class SMB extends Common { || Server::NativeAvailable() ) ? true : ['smbclient']; } + + /** + * Test a storage for availability + * + * @return bool + */ + public function test() { + try { + return parent::test(); + } catch (Exception $e) { + return false; + } + } } diff --git a/apps/files_external/lib/storageconfig.php b/apps/files_external/lib/storageconfig.php index 7f716893842..6f44b25a2e6 100644 --- a/apps/files_external/lib/storageconfig.php +++ b/apps/files_external/lib/storageconfig.php @@ -24,6 +24,7 @@ namespace OCA\Files_external\Lib; +use OCA\Files_External\Lib\Auth\IUserProvided; use \OCA\Files_External\Lib\Backend\Backend; use \OCA\Files_External\Lib\Auth\AuthMechanism; @@ -406,6 +407,7 @@ class StorageConfig implements \JsonSerializable { if (!is_null($this->statusMessage)) { $result['statusMessage'] = $this->statusMessage; } + $result['userProvided'] = $this->authMechanism instanceof IUserProvided; $result['type'] = ($this->getType() === self::MOUNT_TYPE_PERSONAl) ? 'personal': 'system'; return $result; } diff --git a/build/integration/features/sharing-v1.feature b/build/integration/features/sharing-v1.feature index bdc1a4224d8..1a1a5c1981a 100644 --- a/build/integration/features/sharing-v1.feature +++ b/build/integration/features/sharing-v1.feature @@ -480,13 +480,18 @@ Feature: sharing Then the OCS status code should be "100" And the HTTP status code should be "200" - - - - - - - - - - + Scenario: Keep usergroup shares (#22143) + Given As an "admin" + And user "user0" exists + And user "user1" exists + And user "user2" exists + And group "group" exists + And user "user1" belongs to group "group" + And user "user2" belongs to group "group" + And user "user0" created a folder "/TMP" + And file "TMP" of user "user0" is shared with group "group" + And user "user1" created a folder "/myFOLDER" + And User "user1" moves file "/TMP" to "/myFOLDER/myTMP" + And user "user2" does not exist + And user "user1" should see following elements + | /myFOLDER/myTMP/ | diff --git a/console.php b/console.php index 2073654fa8d..d08d400c051 100644 --- a/console.php +++ b/console.php @@ -80,7 +80,7 @@ try { echo "The process control (PCNTL) extensions are required in case you want to interrupt long running commands - see http://php.net/manual/en/book.pcntl.php" . PHP_EOL; } - $application = new Application(\OC::$server->getConfig()); + $application = new Application(\OC::$server->getConfig(), \OC::$server->getEventDispatcher(), \OC::$server->getRequest()); $application->loadCommands(new ConsoleOutput()); $application->run(); } catch (Exception $ex) { diff --git a/lib/private/console/application.php b/lib/private/console/application.php index c7d9c24d7cb..10ff69b1c80 100644 --- a/lib/private/console/application.php +++ b/lib/private/console/application.php @@ -25,25 +25,34 @@ namespace OC\Console; use OC_App; use OC_Defaults; +use OCP\Console\ConsoleEvent; use OCP\IConfig; +use OCP\IRequest; use Symfony\Component\Console\Application as SymfonyApplication; use Symfony\Component\Console\Input\ArgvInput; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; class Application { - /** - * @var IConfig - */ + /** @var IConfig */ private $config; + /** @var EventDispatcherInterface */ + private $dispatcher; + /** @var IRequest */ + private $request; /** * @param IConfig $config + * @param EventDispatcherInterface $dispatcher + * @param IRequest $request */ - public function __construct(IConfig $config) { + public function __construct(IConfig $config, EventDispatcherInterface $dispatcher, IRequest $request) { $defaults = new OC_Defaults; $this->config = $config; $this->application = new SymfonyApplication($defaults->getName(), \OC_Util::getVersionString()); + $this->dispatcher = $dispatcher; + $this->request = $request; } /** @@ -107,6 +116,10 @@ class Application { * @throws \Exception */ public function run(InputInterface $input = null, OutputInterface $output = null) { + $this->dispatcher->dispatch(ConsoleEvent::EVENT_RUN, new ConsoleEvent( + ConsoleEvent::EVENT_RUN, + $this->request->server['argv'] + )); return $this->application->run($input, $output); } } diff --git a/lib/private/db/mdb2schemamanager.php b/lib/private/db/mdb2schemamanager.php index 495ccb902d6..bcabb6fe57a 100644 --- a/lib/private/db/mdb2schemamanager.php +++ b/lib/private/db/mdb2schemamanager.php @@ -49,7 +49,6 @@ class MDB2SchemaManager { /** * saves database scheme to xml file * @param string $file name of file - * @param int|string $mode * @return bool * * TODO: write more documentation @@ -123,7 +122,7 @@ class MDB2SchemaManager { /** * update the database scheme * @param string $file file to read structure from - * @return string|boolean + * @return boolean */ public function simulateUpdateDbFromStructure($file) { $toSchema = $this->readSchemaFromFile($file); diff --git a/lib/private/diagnostics/querylogger.php b/lib/private/diagnostics/querylogger.php index 794e7a5e263..66a65b71d04 100644 --- a/lib/private/diagnostics/querylogger.php +++ b/lib/private/diagnostics/querylogger.php @@ -53,7 +53,7 @@ class QueryLogger implements IQueryLogger { } /** - * @return \OCP\Diagnostics\IQuery[] + * @return Query[] */ public function getQueries() { return $this->queries; diff --git a/lib/private/files.php b/lib/private/files.php index 7b451ac19be..a18bcc76519 100644 --- a/lib/private/files.php +++ b/lib/private/files.php @@ -160,6 +160,8 @@ class OC_Files { /** * @param View $view * @param string $name + * @param string $dir + * @param boolean $onlyHeader */ private static function getSingleFile($view, $dir, $name, $onlyHeader) { $filename = $dir . '/' . $name; @@ -185,7 +187,7 @@ class OC_Files { /** * @param View $view - * @param $dir + * @param string $dir * @param string[]|string $files */ public static function lockFiles($view, $dir, $files) { @@ -290,11 +292,11 @@ class OC_Files { } /** - * @param $dir + * @param string $dir * @param $files - * @param $getType + * @param integer $getType * @param View $view - * @param $filename + * @param string $filename */ private static function unlockAllTheFiles($dir, $files, $getType, $view, $filename) { if ($getType === self::FILE) { diff --git a/lib/private/files/node/root.php b/lib/private/files/node/root.php index 35163be0a0d..40ed531d5df 100644 --- a/lib/private/files/node/root.php +++ b/lib/private/files/node/root.php @@ -169,7 +169,7 @@ class Root extends Folder implements IRootFolder { * @param string $path * @throws \OCP\Files\NotFoundException * @throws \OCP\Files\NotPermittedException - * @return \OCP\Files\Node + * @return string */ public function get($path) { $path = $this->normalizePath($path); diff --git a/lib/private/files/storage/common.php b/lib/private/files/storage/common.php index 95bb3f74ba7..7d8d9ebd25e 100644 --- a/lib/private/files/storage/common.php +++ b/lib/private/files/storage/common.php @@ -396,7 +396,7 @@ abstract class Common implements Storage, ILockingStorage { * get the ETag for a file or folder * * @param string $path - * @return string|false + * @return string */ public function getETag($path) { return uniqid(); diff --git a/lib/private/files/stream/dir.php b/lib/private/files/stream/dir.php index fabadb0d596..7489ee683a2 100644 --- a/lib/private/files/stream/dir.php +++ b/lib/private/files/stream/dir.php @@ -58,6 +58,7 @@ class Dir { /** * @param string $path + * @param string[] $content */ public static function register($path, $content) { self::$dirs[$path] = $content; diff --git a/lib/private/files/type/detection.php b/lib/private/files/type/detection.php index 9cc2e97c3cc..f106a98064f 100644 --- a/lib/private/files/type/detection.php +++ b/lib/private/files/type/detection.php @@ -121,7 +121,7 @@ class Detection implements IMimeTypeDetector { } /** - * @return array + * @return string[] */ public function getAllAliases() { $this->loadAliases(); @@ -264,7 +264,7 @@ class Detection implements IMimeTypeDetector { /** * Get path to the icon of a file type - * @param string $mimeType the MIME type + * @param string $mimetype the MIME type * @return string the url */ public function mimeTypeIcon($mimetype) { diff --git a/lib/private/share/hooks.php b/lib/private/share/hooks.php index 1fa233916d1..c939164e39e 100644 --- a/lib/private/share/hooks.php +++ b/lib/private/share/hooks.php @@ -38,7 +38,7 @@ class Hooks extends \OC\Share\Constants { public static function post_deleteUser($arguments) { // Delete any items shared with the deleted user $query = \OC_DB::prepare('DELETE FROM `*PREFIX*share`' - .' WHERE `share_with` = ? AND `share_type` = ? OR `share_type` = ?'); + .' WHERE `share_with` = ? AND (`share_type` = ? OR `share_type` = ?)'); $query->execute(array($arguments['uid'], self::SHARE_TYPE_USER, self::$shareTypeGroupUserUnique)); // Delete any items the deleted user shared $query = \OC_DB::prepare('SELECT `id` FROM `*PREFIX*share` WHERE `uid_owner` = ?'); diff --git a/lib/private/share20/manager.php b/lib/private/share20/manager.php index 6ea638b84e6..d65fb927f9b 100644 --- a/lib/private/share20/manager.php +++ b/lib/private/share20/manager.php @@ -77,6 +77,7 @@ class Manager implements IManager { * @param IL10N $l * @param IProviderFactory $factory * @param IUserManager $userManager + * @param IRootFolder $rootFolder */ public function __construct( ILogger $logger, @@ -613,6 +614,19 @@ class Manager implements IManager { ]); } + if ($share->getPermissions() !== $originalShare->getPermissions()) { + $userFolder = $this->rootFolder->getUserFolder($share->getShareOwner()); + \OC_Hook::emit('OCP\Share', 'post_update_permissions', array( + 'itemType' => $share->getNode() instanceof \OCP\Files\File ? 'file' : 'folder', + 'itemSource' => $share->getNode()->getId(), + 'shareType' => $share->getShareType(), + 'shareWith' => $share->getSharedWith(), + 'uidOwner' => $share->getSharedBy(), + 'permissions' => $share->getPermissions(), + 'path' => $userFolder->getRelativePath($share->getNode()->getPath()), + )); + } + return $share; } diff --git a/lib/private/share20/share.php b/lib/private/share20/share.php index 448b05d20a3..cd30f24c42e 100644 --- a/lib/private/share20/share.php +++ b/lib/private/share20/share.php @@ -105,6 +105,8 @@ class Share implements \OCP\Share\IShare { * @inheritdoc */ public function setNode(Node $node) { + $this->fileId = null; + $this->nodeType = null; $this->node = $node; return $this; } diff --git a/lib/public/console/consoleevent.php b/lib/public/console/consoleevent.php new file mode 100644 index 00000000000..b3f1229f0e8 --- /dev/null +++ b/lib/public/console/consoleevent.php @@ -0,0 +1,69 @@ +<?php +/** + * @author Joas Schilling <nickvergessen@owncloud.com> + * + * @copyright Copyright (c) 2016, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see <http://www.gnu.org/licenses/> + * + */ + +namespace OCP\Console; + +use Symfony\Component\EventDispatcher\Event; + +/** + * Class ConsoleEvent + * + * @package OCP\Console + * @since 9.0.0 + */ +class ConsoleEvent extends Event { + + const EVENT_RUN = 'OC\Console\Application::run'; + + /** @var string */ + protected $event; + + /** @var string[] */ + protected $arguments; + + /** + * DispatcherEvent constructor. + * + * @param string $event + * @param string[] $arguments + * @since 9.0.0 + */ + public function __construct($event, array $arguments) { + $this->event = $event; + $this->arguments = $arguments; + } + + /** + * @return string + * @since 9.0.0 + */ + public function getEvent() { + return $this->event; + } + + /** + * @return string[] + * @since 9.0.0 + */ + public function getArguments() { + return $this->arguments; + } +} diff --git a/settings/js/users/users.js b/settings/js/users/users.js index 151ab6cdecc..306e3952e53 100644 --- a/settings/js/users/users.js +++ b/settings/js/users/users.js @@ -732,24 +732,20 @@ $(document).ready(function () { .focus() .keypress(function (event) { if (event.keyCode === 13) { - if ($(this).val().length > 0) { - $tr.data('mailAddress', $input.val()); - $input.blur(); - $.ajax({ - type: 'PUT', - url: OC.generateUrl('/settings/users/{id}/mailAddress', {id: uid}), - data: { - mailAddress: $(this).val() - } - }).fail(function (result) { - OC.Notification.show(result.responseJSON.data.message); - // reset the values - $tr.data('mailAddress', mailAddress); - $tr.children('.mailAddress').children('span').text(mailAddress); - }); - } else { - $input.blur(); - } + $tr.data('mailAddress', $input.val()); + $input.blur(); + $.ajax({ + type: 'PUT', + url: OC.generateUrl('/settings/users/{id}/mailAddress', {id: uid}), + data: { + mailAddress: $(this).val() + } + }).fail(function (result) { + OC.Notification.show(result.responseJSON.data.message); + // reset the values + $tr.data('mailAddress', mailAddress); + $tr.children('.mailAddress').children('span').text(mailAddress); + }); } }) .blur(function () { diff --git a/tests/lib/share20/managertest.php b/tests/lib/share20/managertest.php index 270f5da33cd..131bc7fbfd2 100644 --- a/tests/lib/share20/managertest.php +++ b/tests/lib/share20/managertest.php @@ -1870,19 +1870,24 @@ class ManagerTest extends \Test\TestCase { $originalShare = $this->manager->newShare(); $originalShare->setShareType(\OCP\Share::SHARE_TYPE_USER) - ->setSharedWith('origUser'); + ->setSharedWith('origUser') + ->setPermissions(1); + + $node = $this->getMock('\OCP\Files\File'); + $node->method('getId')->willReturn(100); + $node->method('getPath')->willReturn('/newUser/files/myPath'); $manager->expects($this->once())->method('canShare')->willReturn(true); $manager->expects($this->once())->method('getShareById')->with('foo:42')->willReturn($originalShare); - $node = $this->getMock('\OCP\Files\File'); - $share = $this->manager->newShare(); $share->setProviderId('foo') ->setId('42') ->setShareType(\OCP\Share::SHARE_TYPE_USER) ->setSharedWith('origUser') ->setShareOwner('newUser') + ->setSharedBy('sharer') + ->setPermissions(31) ->setNode($node); $this->defaultProvider->expects($this->once()) @@ -1894,6 +1899,20 @@ class ManagerTest extends \Test\TestCase { \OCP\Util::connectHook('OCP\Share', 'post_set_expiration_date', $hookListner, 'post'); $hookListner->expects($this->never())->method('post'); + $this->rootFolder->method('getUserFolder')->with('newUser')->will($this->returnSelf()); + $this->rootFolder->method('getRelativePath')->with('/newUser/files/myPath')->willReturn('/myPath'); + + $hookListner2 = $this->getMockBuilder('Dummy')->setMethods(['post'])->getMock(); + \OCP\Util::connectHook('OCP\Share', 'post_update_permissions', $hookListner2, 'post'); + $hookListner2->expects($this->once())->method('post')->with([ + 'itemType' => 'file', + 'itemSource' => 100, + 'shareType' => \OCP\Share::SHARE_TYPE_USER, + 'shareWith' => 'origUser', + 'uidOwner' => 'sharer', + 'permissions' => 31, + 'path' => '/myPath', + ]); $manager->updateShare($share); } @@ -1911,7 +1930,8 @@ class ManagerTest extends \Test\TestCase { $originalShare = $this->manager->newShare(); $originalShare->setShareType(\OCP\Share::SHARE_TYPE_GROUP) - ->setSharedWith('origUser'); + ->setSharedWith('origUser') + ->setPermissions(31); $manager->expects($this->once())->method('canShare')->willReturn(true); $manager->expects($this->once())->method('getShareById')->with('foo:42')->willReturn($originalShare); @@ -1924,7 +1944,8 @@ class ManagerTest extends \Test\TestCase { ->setShareType(\OCP\Share::SHARE_TYPE_GROUP) ->setSharedWith('origUser') ->setShareOwner('owner') - ->setNode($node); + ->setNode($node) + ->setPermissions(31); $this->defaultProvider->expects($this->once()) ->method('update') @@ -1935,6 +1956,9 @@ class ManagerTest extends \Test\TestCase { \OCP\Util::connectHook('OCP\Share', 'post_set_expiration_date', $hookListner, 'post'); $hookListner->expects($this->never())->method('post'); + $hookListner2 = $this->getMockBuilder('Dummy')->setMethods(['post'])->getMock(); + \OCP\Util::connectHook('OCP\Share', 'post_update_permissions', $hookListner2, 'post'); + $hookListner2->expects($this->never())->method('post'); $manager->updateShare($share); } @@ -1953,7 +1977,8 @@ class ManagerTest extends \Test\TestCase { ->getMock(); $originalShare = $this->manager->newShare(); - $originalShare->setShareType(\OCP\Share::SHARE_TYPE_LINK); + $originalShare->setShareType(\OCP\Share::SHARE_TYPE_LINK) + ->setPermissions(15); $tomorrow = new \DateTime(); $tomorrow->setTime(0,0,0); @@ -1970,7 +1995,8 @@ class ManagerTest extends \Test\TestCase { ->setShareOwner('owner') ->setPassword('password') ->setExpirationDate($tomorrow) - ->setNode($file); + ->setNode($file) + ->setPermissions(15); $manager->expects($this->once())->method('canShare')->willReturn(true); $manager->expects($this->once())->method('getShareById')->with('foo:42')->willReturn($originalShare); @@ -1990,6 +2016,10 @@ class ManagerTest extends \Test\TestCase { 'uidOwner' => 'owner', ]); + $hookListner2 = $this->getMockBuilder('Dummy')->setMethods(['post'])->getMock(); + \OCP\Util::connectHook('OCP\Share', 'post_update_permissions', $hookListner2, 'post'); + $hookListner2->expects($this->never())->method('post'); + $manager->updateShare($share); } |