From e5d872685930e4eecef037468ee14b1132487ca5 Mon Sep 17 00:00:00 2001 From: Hendrik Leppelsack Date: Thu, 16 Jun 2016 08:28:43 +0200 Subject: [PATCH 01/14] remove ie8+9 support --- core/css/apps.css | 8 --- core/css/fixes.css | 123 -------------------------------- core/css/multiselect.css | 7 -- core/css/styles.css | 10 +-- core/js/js.js | 30 ++------ core/templates/layout.base.php | 4 +- core/templates/layout.guest.php | 4 +- core/templates/layout.user.php | 4 +- settings/css/settings.css | 20 ------ settings/templates/personal.php | 4 +- 10 files changed, 11 insertions(+), 203 deletions(-) diff --git a/core/css/apps.css b/core/css/apps.css index e8b33ecba6..3ffa7d8709 100644 --- a/core/css/apps.css +++ b/core/css/apps.css @@ -162,7 +162,6 @@ text-overflow: ellipsis; overflow: hidden; display: inline-block; - width: 201px; /* fallback for IE8 */ width: calc(100% - 49px); line-height: 44px; float: left; @@ -309,12 +308,6 @@ .edge #app-navigation .app-navigation-entry-menu:after { border: 1px solid #eee; } -.ie8 .bubble { - margin-top: 18px; -} -.ie8 .bubble:after { - display: none; -} /* miraculous border arrow stuff */ .bubble:after, #app-navigation .app-navigation-entry-menu:after { @@ -392,7 +385,6 @@ #app-navigation .app-navigation-entry-edit input { border-bottom-right-radius: 0; border-top-right-radius: 0; - width: 204px; /* fallback for IE8 */ width: calc(100% - 36px); padding: 5px; margin-right: 0; diff --git a/core/css/fixes.css b/core/css/fixes.css index 71cb09cae7..7b4540395a 100644 --- a/core/css/fixes.css +++ b/core/css/fixes.css @@ -10,131 +10,8 @@ select { height: 32px; } -/* reset typeface for IE8 because OpenSans renders too small */ -.ie8 body { - font-family: Frutiger, Calibri, 'Myriad Pro', Myriad, Arial, sans-serif; -} - -.lte8 .icon-delete { background-image: url('../img/actions/delete.png'); } -.lte8 .icon-delete:hover, .icon-delete:focus { - background-image: url('../img/actions/delete-hover.png'); -} - -.ie8 .icon-checkmark { - background-image: url('../img/actions/checkmark.png'); -} - -.ie8 .icon-close { - background-image: url('../img/actions/close.png'); -} - -.lte9 .icon-triangle-e { - background-image: url('../img/actions/triangle-e.png'); -} -.lte9 .icon-triangle-n { - background-image: url('../img/actions/triangle-n.png'); -} -.lte9 .icon-triangle-s { - background-image: url('../img/actions/triangle-s.png'); -} -.lte9 .icon-settings, -.lte9 .settings-button { - background-image: url('../img/actions/settings.png'); -} - -.lte9 input[type="submit"], .lte9 input[type="button"], -.lte9 button, .lte9 .button, -.lte9 #quota, .lte9 select, .lte9 .pager li a { - background-color: #f1f1f1; -} - -/* IE8 needs PNG image for header logo */ -.ie8 #header .logo { - background-image: url(../img/logo-icon-175px.png); -} - -/* IE8 needs background to be set to same color to make transparency look good. */ -.lte9 #body-login form input[type="text"] { - border: 1px solid lightgrey; /* use border to add 1px line between input fields */ - background-color: white; /* don't change background on hover */ -} -.lte9 #body-login form input[type="password"] { - /* leave out top border for 1px line between input fields*/ - border-left: 1px solid lightgrey; - border-right: 1px solid lightgrey; - border-bottom: 1px solid lightgrey; - background-color: white; /* don't change background on hover */ -} -.ie8 #body-login input[type="submit"] { - padding: 10px 5px; - margin-top: 3px; -} -/* for whatever unexplained reason */ -.ie8 #password { - width: 271px !important; - min-width: auto !important; -} - -/* disable opacity of info text on gradient - since we cannot set a good backround color to use the filter&background hack as with the input labels */ -.lte9 #body-login p.info { - filter: initial; -} - /* deactivate show password toggle for IE. Does not work for 8 and 9+ have their own implementation. */ .ie #show, .ie #show+label { display: none; visibility: hidden; } - -/* fix installation screen rendering issue for IE8+9 */ -.lte9 #body-login { - min-height: 100%; - height: auto !important; -} - -/* oc-dialog only uses box shadow which is not supported by ie8 */ -.ie8 .oc-dialog { - border: 1px solid #888888; -} - -/* IE8 doesn't support transparent background - let's emulate black with an opacity of .3 on a dark blue background*/ -.ie8 fieldset .warning, .ie8 #body-login .error { - background-color: #1B314D; -} - -/* IE8 isn't able to display transparent background. So it is specified using a gradient */ -.ie8 #nojavascript { - filter: progid:DXImageTransform.Microsoft.gradient(GradientType=0,startColorstr='#4c320000', endColorstr='#4c320000'); -} - -/* IE8 doesn't have rounded corners, so the strengthify bar should be wider */ -.lte8 #body-login .strengthify-wrapper { - width: 271px; - left: 6px; -} - -/* fix background of navigation popup in IE8 */ -.ie8 #navigation, -.ie8 #expanddiv { - background-color: #111; -} - -/* needed else IE8 will randomly hide the borders... */ -.ie8 table th#headerDate, table td.date, -.ie8 table th.column-last, table td.column-last { - position: static; -} - -.ie8 #controls { - background-color: white; -} - -.ie8 #content-wrapper { - overflow-y: auto; -} - -.ie8 #app-navigation .app-navigation-entry-edit input { - line-height: 38px; -} - diff --git a/core/css/multiselect.css b/core/css/multiselect.css index a4b0331915..ef56044fd0 100644 --- a/core/css/multiselect.css +++ b/core/css/multiselect.css @@ -111,10 +111,3 @@ ul.multiselectoptions > li.creator > input { padding: 5px; margin: -5px; } - -.ie8 div.multiselect span:first-child { - display:block; - position:relative; - width: 90%; - margin-right:-1px; -} diff --git a/core/css/styles.css b/core/css/styles.css index 32d4deb79e..e339c888ec 100644 --- a/core/css/styles.css +++ b/core/css/styles.css @@ -187,7 +187,6 @@ a.two-factor-cancel { .emptycontent { color: #888; text-align: center; - margin-top: 100px; /* ie8 */ margin-top: 30vh; width: 100%; } @@ -416,14 +415,7 @@ label.infield { -ms-user-select: none; user-select: none; } -html.ie8 #body-login form input[type="checkbox"]+label { - margin-left: -28px; - margin-top: -3px; - vertical-align: auto; -} -html.ie8 #body-login form input[type="checkbox"] { - margin-top: 5px; -} + #body-login form .errors { background:#fed7d7; border:1px solid #f00; list-style-indent:inside; margin:0 0 2em; padding:1em; } #body-login .success { background:#d7fed7; border:1px solid #0f0; width: 35%; margin: 30px auto; padding:1em; text-align: center;} diff --git a/core/js/js.js b/core/js/js.js index 1c49d38f95..7f98668dcb 100644 --- a/core/js/js.js +++ b/core/js/js.js @@ -788,7 +788,6 @@ var OC={ $(document).trigger(new $.Event('ajaxError'), xhr); }; - // FIXME: also needs an IE8 way if (xhr.addEventListener) { xhr.addEventListener('load', loadCallback); xhr.addEventListener('error', errorCallback); @@ -1857,30 +1856,10 @@ OC.Util = { * This scales the image to the element's actual size, the URL is * taken from the "background-image" CSS attribute. * + * @deprecated IE8 isn't supported since 9.0 * @param {Object} $el image element */ - scaleFixForIE8: function($el) { - if (!this.isIE8()) { - return; - } - var self = this; - $($el).each(function() { - var url = $(this).css('background-image'); - var r = url.match(/url\(['"]?([^'")]*)['"]?\)/); - if (!r) { - return; - } - url = r[1]; - url = self.replaceSVGIcon(url); - // TODO: escape - url = url.replace(/'/g, '%27'); - $(this).css({ - 'filter': 'progid:DXImageTransform.Microsoft.AlphaImageLoader(src=\'' + url + '\', sizingMethod=\'scale\')', - 'background-image': '' - }); - }); - return $el; - }, + scaleFixForIE8: function($el) {}, /** * Returns whether this is IE @@ -1894,10 +1873,11 @@ OC.Util = { /** * Returns whether this is IE8 * - * @return {bool} true if this is IE8, false otherwise + * @deprecated IE8 isn't supported since 9.0 + * @return {bool} false (IE8 isn't supported anymore) */ isIE8: function() { - return $('html').hasClass('ie8'); + return false; }, /** diff --git a/core/templates/layout.base.php b/core/templates/layout.base.php index 29c2ca6696..7301ae690c 100644 --- a/core/templates/layout.base.php +++ b/core/templates/layout.base.php @@ -1,7 +1,5 @@ - - - + diff --git a/core/templates/layout.guest.php b/core/templates/layout.guest.php index 3f9c47f9aa..985e95294a 100644 --- a/core/templates/layout.guest.php +++ b/core/templates/layout.guest.php @@ -1,7 +1,5 @@ <!DOCTYPE html> -<!--[if lte IE 8]><html class="ng-csp ie ie8 lte9 lte8" data-placeholder-focus="false" lang="<?php p($_['language']); ?>" ><![endif]--> -<!--[if IE 9]><html class="ng-csp ie ie9 lte9" data-placeholder-focus="false" lang="<?php p($_['language']); ?>" ><![endif]--> -<!--[if (gt IE 9)|!(IE)]><!--><html class="ng-csp" data-placeholder-focus="false" lang="<?php p($_['language']); ?>" ><!--<![endif]--> +<html class="ng-csp" data-placeholder-focus="false" lang="<?php p($_['language']); ?>" > <head data-requesttoken="<?php p($_['requesttoken']); ?>"> <meta charset="utf-8"> <title> diff --git a/core/templates/layout.user.php b/core/templates/layout.user.php index 601af6077f..b19095dee8 100644 --- a/core/templates/layout.user.php +++ b/core/templates/layout.user.php @@ -1,7 +1,5 @@ <!DOCTYPE html> -<!--[if lte IE 8]><html class="ng-csp ie ie8 lte9 lte8" data-placeholder-focus="false" lang="<?php p($_['language']); ?>" ><![endif]--> -<!--[if IE 9]><html class="ng-csp ie ie9 lte9" data-placeholder-focus="false" lang="<?php p($_['language']); ?>" ><![endif]--> -<!--[if (gt IE 9)|!(IE)]><!--><html class="ng-csp" data-placeholder-focus="false" lang="<?php p($_['language']); ?>" ><!--<![endif]--> +<html class="ng-csp" data-placeholder-focus="false" lang="<?php p($_['language']); ?>" > <head data-user="<?php p($_['user_uid']); ?>" data-user-displayname="<?php p($_['user_displayname']); ?>" data-requesttoken="<?php p($_['requesttoken']); ?>"> <meta charset="utf-8"> <title> diff --git a/settings/css/settings.css b/settings/css/settings.css index e4ddec9152..3bb88b4628 100644 --- a/settings/css/settings.css +++ b/settings/css/settings.css @@ -161,10 +161,6 @@ table.nostyle td { padding: 0.2em 0; } width: 32px; } -.ie8 #newgroup-form .icon-add { - height: 30px; -} - .isgroup .groupname { width: 85%; display: block; @@ -281,10 +277,6 @@ input.userFilter {width: 200px;} width: 32px; } - -.ie8 table.hascontrols{border-collapse:collapse;width: 100%;} -.ie8 table.hascontrols tbody tr{border-collapse:collapse;border: 1px solid #ddd !important;} - /* used to highlight a user row in red */ #userlist tr.row-warning { background-color: #FDD; @@ -540,18 +532,6 @@ span.indeterminate { margin-top: -7px; } -.ie8 .strengthify-wrapper { - left: 389px; -} - -.onlyInIE8 { - display: none; -} - -.ie8 .onlyInIE8 { - display: inline; -} - /* OPERA hack for strengthify*/ doesnotexist:-o-prefocus, .strengthify-wrapper { left: 185px; diff --git a/settings/templates/personal.php b/settings/templates/personal.php index b9b8429d94..e93155f0f3 100644 --- a/settings/templates/personal.php +++ b/settings/templates/personal.php @@ -121,11 +121,11 @@ if($_['passwordChangeSupported']) { <div class="hidden icon-checkmark" id="password-changed"></div> <div class="hidden" id="password-error"><?php p($l->t('Unable to change your password'));?></div> <br> - <label for="pass1" class="onlyInIE8"><?php echo $l->t('Current password');?>: </label> + <label for="pass1" class="hidden-visually"><?php echo $l->t('Current password');?>: </label> <input type="password" id="pass1" name="oldpassword" placeholder="<?php echo $l->t('Current password');?>" autocomplete="off" autocapitalize="off" autocorrect="off" /> - <label for="pass2" class="onlyInIE8"><?php echo $l->t('New password');?>: </label> + <label for="pass2" class="hidden-visually"><?php echo $l->t('New password');?>: </label> <input type="password" id="pass2" name="personal-password" placeholder="<?php echo $l->t('New password');?>" data-typetoggle="#personal-show" From 040c5411386d05f64c6dc2a6e3d52ba5c3af9f96 Mon Sep 17 00:00:00 2001 From: Robin Appelman <icewind@owncloud.com> Date: Thu, 23 Jun 2016 14:19:55 +0200 Subject: [PATCH 02/14] Remove a fed share from the local table before trying to notify the remote server --- apps/federatedfilesharing/lib/FederatedShareProvider.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/federatedfilesharing/lib/FederatedShareProvider.php b/apps/federatedfilesharing/lib/FederatedShareProvider.php index 4892908c32..0173725676 100644 --- a/apps/federatedfilesharing/lib/FederatedShareProvider.php +++ b/apps/federatedfilesharing/lib/FederatedShareProvider.php @@ -473,6 +473,8 @@ class FederatedShareProvider implements IShareProvider { $isOwner = false; + $this->removeShareFromTable($share); + // if the local user is the owner we can send the unShare request directly... if ($this->userManager->userExists($share->getShareOwner())) { $this->notifications->sendRemoteUnShare($remote, $share->getId(), $share->getToken()); @@ -494,8 +496,6 @@ class FederatedShareProvider implements IShareProvider { } $this->notifications->sendRevokeShare($remote, $remoteId, $share->getToken()); } - - $this->removeShareFromTable($share); } /** From da4ba82b834f3485e82113b7cb6d1c5bd68ec315 Mon Sep 17 00:00:00 2001 From: Vincent Petry <pvince81@owncloud.com> Date: Thu, 23 Jun 2016 18:26:01 +0200 Subject: [PATCH 03/14] Use OC.Backbone instead of Backbone directly in authtoken JS code Fixes asset pipeline issue with the auth token in personal page --- settings/js/authtoken.js | 6 +++--- settings/js/authtoken_collection.js | 6 +++--- settings/js/authtoken_view.js | 10 +++++----- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/settings/js/authtoken.js b/settings/js/authtoken.js index 215192d716..1d958a4d67 100644 --- a/settings/js/authtoken.js +++ b/settings/js/authtoken.js @@ -20,14 +20,14 @@ * */ -(function(OC, Backbone) { +(function(OC) { 'use strict'; OC.Settings = OC.Settings || {}; - var AuthToken = Backbone.Model.extend({ + var AuthToken = OC.Backbone.Model.extend({ }); OC.Settings.AuthToken = AuthToken; -})(OC, Backbone); +})(OC); diff --git a/settings/js/authtoken_collection.js b/settings/js/authtoken_collection.js index a78e053995..ab7f7d5804 100644 --- a/settings/js/authtoken_collection.js +++ b/settings/js/authtoken_collection.js @@ -20,12 +20,12 @@ * */ -(function(OC, Backbone) { +(function(OC) { 'use strict'; OC.Settings = OC.Settings || {}; - var AuthTokenCollection = Backbone.Collection.extend({ + var AuthTokenCollection = OC.Backbone.Collection.extend({ model: OC.Settings.AuthToken, @@ -49,4 +49,4 @@ OC.Settings.AuthTokenCollection = AuthTokenCollection; -})(OC, Backbone); +})(OC); diff --git a/settings/js/authtoken_view.js b/settings/js/authtoken_view.js index da5861689a..bfafee8243 100644 --- a/settings/js/authtoken_view.js +++ b/settings/js/authtoken_view.js @@ -1,4 +1,4 @@ -/* global Backbone, Handlebars, moment */ +/* global Handlebars, moment */ /** * @author Christoph Wurst <christoph@owncloud.com> @@ -20,7 +20,7 @@ * */ -(function(OC, _, Backbone, $, Handlebars, moment) { +(function(OC, _, $, Handlebars, moment) { 'use strict'; OC.Settings = OC.Settings || {}; @@ -32,7 +32,7 @@ + '<td><a class="icon-delete has-tooltip" title="' + t('core', 'Disconnect') + '"></a></td>' + '<tr>'; - var SubView = Backbone.View.extend({ + var SubView = OC.Backbone.View.extend({ collection: null, /** @@ -94,7 +94,7 @@ } }); - var AuthTokenView = Backbone.View.extend({ + var AuthTokenView = OC.Backbone.View.extend({ collection: null, _views: [], @@ -237,4 +237,4 @@ OC.Settings.AuthTokenView = AuthTokenView; -})(OC, _, Backbone, $, Handlebars, moment); +})(OC, _, $, Handlebars, moment); From 9524115c2c33b5785255f27b09b3ca1899f798eb Mon Sep 17 00:00:00 2001 From: Vincent Petry <pvince81@owncloud.com> Date: Thu, 23 Jun 2016 18:30:45 +0200 Subject: [PATCH 04/14] Remove tooltip when disconnecting token --- settings/js/authtoken_view.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/settings/js/authtoken_view.js b/settings/js/authtoken_view.js index da5861689a..61ca2f9474 100644 --- a/settings/js/authtoken_view.js +++ b/settings/js/authtoken_view.js @@ -220,6 +220,8 @@ var destroyingToken = token.destroy(); + $row.find('.icon-delete').tooltip('hide'); + var _this = this; $.when(destroyingToken).fail(function() { OC.Notification.showTemporary(t('core', 'Error while deleting the token')); From 955635c7aaaf932c698069a08ff8f218f0ea990c Mon Sep 17 00:00:00 2001 From: Vincent Petry <pvince81@owncloud.com> Date: Thu, 23 Jun 2016 15:43:21 +0200 Subject: [PATCH 05/14] Add explicit delete permission to link shares Link shares always allowed deletion, however internally the permissions were stored as 7 which lacked delete permissions. This created an inconsistency in the Webdav permissions. This fix makes sure we include delete permissions in the share permissions, which now become 15. In case a client is still passing 7 for legacy reasons, it gets converted automatically to 15. --- apps/files_sharing/lib/API/Share20OCS.php | 21 ++++- .../tests/API/Share20OCSTest.php | 77 ++++++++++++++++--- apps/files_sharing/tests/ApiTest.php | 16 +++- build/integration/features/sharing-v1.feature | 6 +- core/js/sharedialoglinkshareview.js | 2 +- lib/private/Share20/Manager.php | 7 +- tests/lib/Share20/ManagerTest.php | 18 ----- 7 files changed, 103 insertions(+), 44 deletions(-) diff --git a/apps/files_sharing/lib/API/Share20OCS.php b/apps/files_sharing/lib/API/Share20OCS.php index 53b27aae0b..436b8d15ac 100644 --- a/apps/files_sharing/lib/API/Share20OCS.php +++ b/apps/files_sharing/lib/API/Share20OCS.php @@ -354,7 +354,8 @@ class Share20OCS { $share->setPermissions( \OCP\Constants::PERMISSION_READ | \OCP\Constants::PERMISSION_CREATE | - \OCP\Constants::PERMISSION_UPDATE + \OCP\Constants::PERMISSION_UPDATE | + \OCP\Constants::PERMISSION_DELETE ); } else { $share->setPermissions(\OCP\Constants::PERMISSION_READ); @@ -591,7 +592,7 @@ class Share20OCS { $newPermissions = null; if ($publicUpload === 'true') { - $newPermissions = \OCP\Constants::PERMISSION_READ | \OCP\Constants::PERMISSION_CREATE | \OCP\Constants::PERMISSION_UPDATE; + $newPermissions = \OCP\Constants::PERMISSION_READ | \OCP\Constants::PERMISSION_CREATE | \OCP\Constants::PERMISSION_UPDATE | \OCP\Constants::PERMISSION_DELETE; } else if ($publicUpload === 'false') { $newPermissions = \OCP\Constants::PERMISSION_READ; } @@ -602,12 +603,21 @@ class Share20OCS { if ($newPermissions !== null && $newPermissions !== \OCP\Constants::PERMISSION_READ && - $newPermissions !== (\OCP\Constants::PERMISSION_READ | \OCP\Constants::PERMISSION_CREATE | \OCP\Constants::PERMISSION_UPDATE)) { + // legacy + $newPermissions !== (\OCP\Constants::PERMISSION_READ | \OCP\Constants::PERMISSION_CREATE | \OCP\Constants::PERMISSION_UPDATE) && + // correct + $newPermissions !== (\OCP\Constants::PERMISSION_READ | \OCP\Constants::PERMISSION_CREATE | \OCP\Constants::PERMISSION_UPDATE | \OCP\Constants::PERMISSION_DELETE) + ) { $share->getNode()->unlock(ILockingProvider::LOCK_SHARED); return new \OC_OCS_Result(null, 400, $this->l->t('Can\'t change permissions for public share links')); } - if ($newPermissions === (\OCP\Constants::PERMISSION_READ | \OCP\Constants::PERMISSION_CREATE | \OCP\Constants::PERMISSION_UPDATE)) { + if ( + // legacy + $newPermissions === (\OCP\Constants::PERMISSION_READ | \OCP\Constants::PERMISSION_CREATE | \OCP\Constants::PERMISSION_UPDATE) || + // correct + $newPermissions === (\OCP\Constants::PERMISSION_READ | \OCP\Constants::PERMISSION_CREATE | \OCP\Constants::PERMISSION_UPDATE | \OCP\Constants::PERMISSION_DELETE) + ) { if (!$this->shareManager->shareApiLinkAllowPublicUpload()) { $share->getNode()->unlock(ILockingProvider::LOCK_SHARED); return new \OC_OCS_Result(null, 403, $this->l->t('Public upload disabled by the administrator')); @@ -617,6 +627,9 @@ class Share20OCS { $share->getNode()->unlock(ILockingProvider::LOCK_SHARED); return new \OC_OCS_Result(null, 400, $this->l->t('Public upload is only possible for publicly shared folders')); } + + // normalize to correct public upload permissions + $newPermissions = \OCP\Constants::PERMISSION_READ | \OCP\Constants::PERMISSION_CREATE | \OCP\Constants::PERMISSION_UPDATE | \OCP\Constants::PERMISSION_DELETE; } if ($newPermissions !== null) { diff --git a/apps/files_sharing/tests/API/Share20OCSTest.php b/apps/files_sharing/tests/API/Share20OCSTest.php index b760a0f47a..6435c992f2 100644 --- a/apps/files_sharing/tests/API/Share20OCSTest.php +++ b/apps/files_sharing/tests/API/Share20OCSTest.php @@ -1035,7 +1035,7 @@ class Share20OCSTest extends \Test\TestCase { $this->callback(function (\OCP\Share\IShare $share) use ($path) { return $share->getNode() === $path && $share->getShareType() === \OCP\Share::SHARE_TYPE_LINK && - $share->getPermissions() === \OCP\Constants::PERMISSION_READ | \OCP\Constants::PERMISSION_CREATE | \OCP\Constants::PERMISSION_DELETE && + $share->getPermissions() === (\OCP\Constants::PERMISSION_READ | \OCP\Constants::PERMISSION_CREATE | \OCP\Constants::PERMISSION_UPDATE | \OCP\Constants::PERMISSION_DELETE) && $share->getSharedBy() === 'currentUser' && $share->getPassword() === null && $share->getExpirationDate() === null; @@ -1366,7 +1366,7 @@ class Share20OCSTest extends \Test\TestCase { $date = new \DateTime('2000-01-01'); $date->setTime(0,0,0); - return $share->getPermissions() === \OCP\Constants::PERMISSION_READ | \OCP\Constants::PERMISSION_CREATE && \OCP\Constants::PERMISSION_DELETE && + return $share->getPermissions() === (\OCP\Constants::PERMISSION_READ | \OCP\Constants::PERMISSION_CREATE | \OCP\Constants::PERMISSION_UPDATE | \OCP\Constants::PERMISSION_DELETE) && $share->getPassword() === 'password' && $share->getExpirationDate() == $date; }) @@ -1379,6 +1379,44 @@ class Share20OCSTest extends \Test\TestCase { $this->assertEquals($expected->getData(), $result->getData()); } + /** + * @dataProvider publicUploadParamsProvider + */ + public function testUpdateLinkShareEnablePublicUpload($params) { + $ocs = $this->mockFormatShare(); + + $folder = $this->getMock('\OCP\Files\Folder'); + + $share = \OC::$server->getShareManager()->newShare(); + $share->setPermissions(\OCP\Constants::PERMISSION_ALL) + ->setSharedBy($this->currentUser->getUID()) + ->setShareType(\OCP\Share::SHARE_TYPE_LINK) + ->setPassword('password') + ->setNode($folder); + + $this->request + ->method('getParam') + ->will($this->returnValueMap($params)); + + $this->shareManager->method('getShareById')->with('ocinternal:42')->willReturn($share); + $this->shareManager->method('shareApiLinkAllowPublicUpload')->willReturn(true); + $this->shareManager->method('getSharedWith')->willReturn([]); + + $this->shareManager->expects($this->once())->method('updateShare')->with( + $this->callback(function (\OCP\Share\IShare $share) { + return $share->getPermissions() === (\OCP\Constants::PERMISSION_READ | \OCP\Constants::PERMISSION_CREATE | \OCP\Constants::PERMISSION_UPDATE | \OCP\Constants::PERMISSION_DELETE) && + $share->getPassword() === 'password' && + $share->getExpirationDate() === null; + }) + )->will($this->returnArgument(0)); + + $expected = new \OC_OCS_Result(null); + $result = $ocs->updateShare(42); + + $this->assertEquals($expected->getMeta(), $result->getMeta()); + $this->assertEquals($expected->getData(), $result->getData()); + } + public function testUpdateLinkShareInvalidDate() { $ocs = $this->mockFormatShare(); @@ -1408,7 +1446,30 @@ class Share20OCSTest extends \Test\TestCase { $this->assertEquals($expected->getData(), $result->getData()); } - public function testUpdateLinkSharePublicUploadNotAllowed() { + public function publicUploadParamsProvider() { + return [ + [[ + ['publicUpload', null, 'true'], + ['expireDate', '', null], + ['password', '', 'password'], + ]], [[ + // legacy had no delete + ['permissions', null, \OCP\Constants::PERMISSION_READ | \OCP\Constants::PERMISSION_CREATE | \OCP\Constants::PERMISSION_UPDATE], + ['expireDate', '', null], + ['password', '', 'password'], + ]], [[ + // correct + ['permissions', null, \OCP\Constants::PERMISSION_READ | \OCP\Constants::PERMISSION_CREATE | \OCP\Constants::PERMISSION_UPDATE | \OCP\Constants::PERMISSION_DELETE], + ['expireDate', '', null], + ['password', '', 'password'], + ]], + ]; + } + + /** + * @dataProvider publicUploadParamsProvider + */ + public function testUpdateLinkSharePublicUploadNotAllowed($params) { $ocs = $this->mockFormatShare(); $folder = $this->getMock('\OCP\Files\Folder'); @@ -1421,11 +1482,7 @@ class Share20OCSTest extends \Test\TestCase { $this->request ->method('getParam') - ->will($this->returnValueMap([ - ['publicUpload', null, 'true'], - ['expireDate', '', null], - ['password', '', 'password'], - ])); + ->will($this->returnValueMap($params)); $this->shareManager->method('getShareById')->with('ocinternal:42')->willReturn($share); $this->shareManager->method('shareApiLinkAllowPublicUpload')->willReturn(false); @@ -1585,7 +1642,7 @@ class Share20OCSTest extends \Test\TestCase { $this->shareManager->expects($this->once())->method('updateShare')->with( $this->callback(function (\OCP\Share\IShare $share) use ($date) { - return $share->getPermissions() === \OCP\Constants::PERMISSION_READ | \OCP\Constants::PERMISSION_CREATE | \OCP\Constants::PERMISSION_DELETE && + return $share->getPermissions() === (\OCP\Constants::PERMISSION_READ | \OCP\Constants::PERMISSION_CREATE | \OCP\Constants::PERMISSION_UPDATE | \OCP\Constants::PERMISSION_DELETE) && $share->getPassword() === 'password' && $share->getExpirationDate() === $date; }) @@ -1625,7 +1682,7 @@ class Share20OCSTest extends \Test\TestCase { $this->shareManager->expects($this->once())->method('updateShare')->with( $this->callback(function (\OCP\Share\IShare $share) use ($date) { - return $share->getPermissions() === \OCP\Constants::PERMISSION_READ | \OCP\Constants::PERMISSION_CREATE | \OCP\Constants::PERMISSION_DELETE && + return $share->getPermissions() === (\OCP\Constants::PERMISSION_READ | \OCP\Constants::PERMISSION_CREATE | \OCP\Constants::PERMISSION_UPDATE | \OCP\Constants::PERMISSION_DELETE) && $share->getPassword() === 'password' && $share->getExpirationDate() === $date; }) diff --git a/apps/files_sharing/tests/ApiTest.php b/apps/files_sharing/tests/ApiTest.php index 058b0c4758..40c9085353 100644 --- a/apps/files_sharing/tests/ApiTest.php +++ b/apps/files_sharing/tests/ApiTest.php @@ -257,7 +257,13 @@ class ApiTest extends TestCase { $this->assertTrue($result->succeeded()); $data = $result->getData(); - $this->assertEquals(7, $data['permissions']); + $this->assertEquals( + \OCP\Constants::PERMISSION_READ | + \OCP\Constants::PERMISSION_CREATE | + \OCP\Constants::PERMISSION_UPDATE | + \OCP\Constants::PERMISSION_DELETE, + $data['permissions'] + ); $this->assertEmpty($data['expiration']); $this->assertTrue(is_string($data['token'])); @@ -1081,7 +1087,13 @@ class ApiTest extends TestCase { $this->assertTrue($result->succeeded()); $share1 = $this->shareManager->getShareById($share1->getFullId()); - $this->assertEquals(7, $share1->getPermissions()); + $this->assertEquals( + \OCP\Constants::PERMISSION_READ | + \OCP\Constants::PERMISSION_CREATE | + \OCP\Constants::PERMISSION_UPDATE | + \OCP\Constants::PERMISSION_DELETE, + $share1->getPermissions() + ); // cleanup $this->shareManager->deleteShare($share1); diff --git a/build/integration/features/sharing-v1.feature b/build/integration/features/sharing-v1.feature index 79dc1326f3..3878e741f6 100644 --- a/build/integration/features/sharing-v1.feature +++ b/build/integration/features/sharing-v1.feature @@ -61,7 +61,7 @@ Feature: sharing And the HTTP status code should be "200" And Share fields of last share match with | id | A_NUMBER | - | permissions | 7 | + | permissions | 15 | | expiration | +3 days | | url | AN_URL | | token | A_TOKEN | @@ -159,7 +159,7 @@ Feature: sharing | share_type | 3 | | file_source | A_NUMBER | | file_target | /FOLDER | - | permissions | 7 | + | permissions | 15 | | stime | A_NUMBER | | token | A_TOKEN | | storage | A_NUMBER | @@ -189,7 +189,7 @@ Feature: sharing | share_type | 3 | | file_source | A_NUMBER | | file_target | /FOLDER | - | permissions | 7 | + | permissions | 15 | | stime | A_NUMBER | | token | A_TOKEN | | storage | A_NUMBER | diff --git a/core/js/sharedialoglinkshareview.js b/core/js/sharedialoglinkshareview.js index 817c3408e7..59f7ffcae0 100644 --- a/core/js/sharedialoglinkshareview.js +++ b/core/js/sharedialoglinkshareview.js @@ -202,7 +202,7 @@ var permissions = OC.PERMISSION_READ; if($checkbox.is(':checked')) { - permissions = OC.PERMISSION_UPDATE | OC.PERMISSION_CREATE | OC.PERMISSION_READ; + permissions = OC.PERMISSION_UPDATE | OC.PERMISSION_CREATE | OC.PERMISSION_READ | OC.PERMISSION_DELETE; } this.model.saveLinkShare({ diff --git a/lib/private/Share20/Manager.php b/lib/private/Share20/Manager.php index e2730f4d5f..2c08e616f8 100644 --- a/lib/private/Share20/Manager.php +++ b/lib/private/Share20/Manager.php @@ -446,14 +446,9 @@ class Manager implements IManager { throw new \InvalidArgumentException('Link shares can\'t have reshare permissions'); } - // We don't allow deletion on link shares - if ($share->getPermissions() & \OCP\Constants::PERMISSION_DELETE) { - throw new \InvalidArgumentException('Link shares can\'t have delete permissions'); - } - // Check if public upload is allowed if (!$this->shareApiLinkAllowPublicUpload() && - ($share->getPermissions() & (\OCP\Constants::PERMISSION_CREATE | \OCP\Constants::PERMISSION_UPDATE))) { + ($share->getPermissions() & (\OCP\Constants::PERMISSION_CREATE | \OCP\Constants::PERMISSION_UPDATE | \OCP\Constants::PERMISSION_DELETE))) { throw new \InvalidArgumentException('Public upload not allowed'); } } diff --git a/tests/lib/Share20/ManagerTest.php b/tests/lib/Share20/ManagerTest.php index a50ea6c892..d2539f8577 100644 --- a/tests/lib/Share20/ManagerTest.php +++ b/tests/lib/Share20/ManagerTest.php @@ -1318,24 +1318,6 @@ class ManagerTest extends \Test\TestCase { $this->invokePrivate($this->manager, 'linkCreateChecks', [$share]); } - /** - * @expectedException Exception - * @expectedExceptionMessage Link shares can't have delete permissions - */ - public function testLinkCreateChecksDeletePermissions() { - $share = $this->manager->newShare(); - - $share->setPermissions(\OCP\Constants::PERMISSION_DELETE); - - $this->config - ->method('getAppValue') - ->will($this->returnValueMap([ - ['core', 'shareapi_allow_links', 'yes', 'yes'], - ])); - - $this->invokePrivate($this->manager, 'linkCreateChecks', [$share]); - } - /** * @expectedException Exception * @expectedExceptionMessage Public upload not allowed From 0ad065cb8d7bbc3665140f0b264a8ac74167cfa8 Mon Sep 17 00:00:00 2001 From: Vincent Petry <pvince81@owncloud.com> Date: Thu, 23 Jun 2016 16:37:03 +0200 Subject: [PATCH 06/14] Repair step to adjust link share delete permissions --- lib/private/Repair/RepairInvalidShares.php | 23 ++++++ tests/lib/Repair/RepairInvalidSharesTest.php | 87 ++++++++++++++++++++ version.php | 2 +- 3 files changed, 111 insertions(+), 1 deletion(-) diff --git a/lib/private/Repair/RepairInvalidShares.php b/lib/private/Repair/RepairInvalidShares.php index 30f67a1f39..728632486d 100644 --- a/lib/private/Repair/RepairInvalidShares.php +++ b/lib/private/Repair/RepairInvalidShares.php @@ -71,6 +71,25 @@ class RepairInvalidShares implements IRepairStep { } } + /** + * In the past link shares with public upload enabled were missing the delete permission. + */ + private function addShareLinkDeletePermission(IOutput $out) { + $oldPerms = \OCP\Constants::PERMISSION_READ | \OCP\Constants::PERMISSION_CREATE | \OCP\Constants::PERMISSION_UPDATE; + $newPerms = \OCP\Constants::PERMISSION_READ | \OCP\Constants::PERMISSION_CREATE | \OCP\Constants::PERMISSION_UPDATE | \OCP\Constants::PERMISSION_DELETE; + $builder = $this->connection->getQueryBuilder(); + $builder + ->update('share') + ->set('permissions', $builder->expr()->literal($newPerms)) + ->where($builder->expr()->eq('share_type', $builder->expr()->literal(\OC\Share\Constants::SHARE_TYPE_LINK))) + ->andWhere($builder->expr()->eq('permissions', $builder->expr()->literal($oldPerms))); + + $updatedEntries = $builder->execute(); + if ($updatedEntries > 0) { + $out->info('Fixed link share permissions for ' . $updatedEntries . ' shares'); + } + } + /** * Remove shares where the parent share does not exist anymore */ @@ -113,6 +132,10 @@ class RepairInvalidShares implements IRepairStep { // this situation was only possible before 8.2 $this->removeExpirationDateFromNonLinkShares($out); } + if (version_compare($ocVersionFromBeforeUpdate, '9.1.0.9', '<')) { + // this situation was only possible before 9.1 + $this->addShareLinkDeletePermission($out); + } $this->removeSharesNonExistingParent($out); } diff --git a/tests/lib/Repair/RepairInvalidSharesTest.php b/tests/lib/Repair/RepairInvalidSharesTest.php index a1e871bcc8..1ac42e53bf 100644 --- a/tests/lib/Repair/RepairInvalidSharesTest.php +++ b/tests/lib/Repair/RepairInvalidSharesTest.php @@ -123,6 +123,93 @@ class RepairInvalidSharesTest extends TestCase { $this->assertNotNull($linkShare['expiration'], 'valid link share expiration date still there'); } + /** + * Test remove expiration date for non-link shares + */ + public function testAddShareLinkDeletePermission() { + $oldPerms = \OCP\Constants::PERMISSION_READ | \OCP\Constants::PERMISSION_CREATE | \OCP\Constants::PERMISSION_UPDATE; + $newPerms = \OCP\Constants::PERMISSION_READ | \OCP\Constants::PERMISSION_CREATE | \OCP\Constants::PERMISSION_UPDATE | \OCP\Constants::PERMISSION_DELETE; + + // share with old permissions + $qb = $this->connection->getQueryBuilder(); + $qb->insert('share') + ->values([ + 'share_type' => $qb->expr()->literal(Constants::SHARE_TYPE_LINK), + 'uid_owner' => $qb->expr()->literal('user1'), + 'item_type' => $qb->expr()->literal('folder'), + 'item_source' => $qb->expr()->literal(123), + 'item_target' => $qb->expr()->literal('/123'), + 'file_source' => $qb->expr()->literal(123), + 'file_target' => $qb->expr()->literal('/test'), + 'permissions' => $qb->expr()->literal($oldPerms), + 'stime' => $qb->expr()->literal(time()), + ]) + ->execute(); + + $bogusShareId = $this->getLastShareId(); + + // share with read-only permissions + $qb = $this->connection->getQueryBuilder(); + $qb->insert('share') + ->values([ + 'share_type' => $qb->expr()->literal(Constants::SHARE_TYPE_LINK), + 'uid_owner' => $qb->expr()->literal('user1'), + 'item_type' => $qb->expr()->literal('folder'), + 'item_source' => $qb->expr()->literal(123), + 'item_target' => $qb->expr()->literal('/123'), + 'file_source' => $qb->expr()->literal(123), + 'file_target' => $qb->expr()->literal('/test'), + 'permissions' => $qb->expr()->literal(\OCP\Constants::PERMISSION_READ), + 'stime' => $qb->expr()->literal(time()), + ]) + ->execute(); + + $keepThisShareId = $this->getLastShareId(); + + // user share to keep + $qb = $this->connection->getQueryBuilder(); + $qb->insert('share') + ->values([ + 'share_type' => $qb->expr()->literal(Constants::SHARE_TYPE_USER), + 'share_with' => $qb->expr()->literal('recipientuser1'), + 'uid_owner' => $qb->expr()->literal('user1'), + 'item_type' => $qb->expr()->literal('folder'), + 'item_source' => $qb->expr()->literal(123), + 'item_target' => $qb->expr()->literal('/123'), + 'file_source' => $qb->expr()->literal(123), + 'file_target' => $qb->expr()->literal('/test'), + 'permissions' => $qb->expr()->literal(3), + 'stime' => $qb->expr()->literal(time()), + ]) + ->execute(); + + $keepThisShareId2 = $this->getLastShareId(); + + /** @var IOutput | \PHPUnit_Framework_MockObject_MockObject $outputMock */ + $outputMock = $this->getMockBuilder('\OCP\Migration\IOutput') + ->disableOriginalConstructor() + ->getMock(); + + $this->repair->run($outputMock); + + $results = $this->connection->getQueryBuilder() + ->select('*') + ->from('share') + ->orderBy('permissions', 'ASC') + ->execute() + ->fetchAll(); + + $this->assertCount(3, $results); + + $untouchedShare = $results[0]; + $untouchedShare2 = $results[1]; + $updatedShare = $results[2]; + $this->assertEquals($keepThisShareId, $untouchedShare['id'], 'sanity check'); + $this->assertEquals($keepThisShareId2, $untouchedShare2['id'], 'sanity check'); + $this->assertEquals($bogusShareId, $updatedShare['id'], 'sanity check'); + $this->assertEquals($newPerms, $updatedShare['permissions'], 'delete permission was added'); + } + /** * Test remove shares where the parent share does not exist anymore */ diff --git a/version.php b/version.php index 3015d976e5..b439ffbbda 100644 --- a/version.php +++ b/version.php @@ -25,7 +25,7 @@ // We only can count up. The 4. digit is only for the internal patchlevel to trigger DB upgrades // between betas, final and RCs. This is _not_ the public version number. Reset minor/patchlevel // when updating major/minor version number. -$OC_Version = array(9, 1, 0, 9); +$OC_Version = array(9, 1, 0, 10); // The human readable string $OC_VersionString = '9.1.0 beta 2'; From e677ad56fd58e56151ec5420b72dc5fd797e2eb8 Mon Sep 17 00:00:00 2001 From: Vincent Petry <pvince81@owncloud.com> Date: Fri, 24 Jun 2016 10:24:41 +0200 Subject: [PATCH 07/14] Make code integrity check work when OC is not installed yet --- lib/private/IntegrityCheck/Checker.php | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/lib/private/IntegrityCheck/Checker.php b/lib/private/IntegrityCheck/Checker.php index ab68f75220..57127f280c 100644 --- a/lib/private/IntegrityCheck/Checker.php +++ b/lib/private/IntegrityCheck/Checker.php @@ -108,7 +108,11 @@ class Checker { * applicable for very specific scenarios and we should not advertise it * too prominent. So please do not add it to config.sample.php. */ - $isIntegrityCheckDisabled = $this->config->getSystemValue('integrity.check.disabled', false); + if ($this->config !== null) { + $isIntegrityCheckDisabled = $this->config->getSystemValue('integrity.check.disabled', false); + } else { + $isIntegrityCheckDisabled = false; + } if($isIntegrityCheckDisabled === true) { return false; } @@ -401,7 +405,10 @@ class Checker { return json_decode($cachedResults, true); } - return json_decode($this->config->getAppValue('core', self::CACHE_KEY, '{}'), true); + if ($this->config !== null) { + return json_decode($this->config->getAppValue('core', self::CACHE_KEY, '{}'), true); + } + return []; } /** @@ -416,7 +423,9 @@ class Checker { if(!empty($result)) { $resultArray[$scope] = $result; } - $this->config->setAppValue('core', self::CACHE_KEY, json_encode($resultArray)); + if ($this->config !== null) { + $this->config->setAppValue('core', self::CACHE_KEY, json_encode($resultArray)); + } $this->cache->set(self::CACHE_KEY, json_encode($resultArray)); } From b4cf29775849236be338fc7c8e2ad0d7f361ade7 Mon Sep 17 00:00:00 2001 From: Vincent Petry <pvince81@owncloud.com> Date: Thu, 23 Jun 2016 11:27:02 +0200 Subject: [PATCH 08/14] Prerender file list pages to include search results When filtering the file list, if a result is on an unrendered page, make sure to call _nextPage() to prerender the pages in order to display all matching results. --- apps/files/js/filelist.js | 22 ++++++++++++++++++---- apps/files/tests/js/filelistSpec.js | 11 +++++++++++ 2 files changed, 29 insertions(+), 4 deletions(-) diff --git a/apps/files/js/filelist.js b/apps/files/js/filelist.js index f249f2d35c..18f17a7207 100644 --- a/apps/files/js/filelist.js +++ b/apps/files/js/filelist.js @@ -2351,22 +2351,36 @@ * @param filter */ setFilter:function(filter) { + var total = 0; this._filter = filter; this.fileSummary.setFilter(filter, this.files); + total = this.fileSummary.getTotal(); if (!this.$el.find('.mask').exists()) { this.hideIrrelevantUIWhenNoFilesMatch(); } var that = this; + var visibleCount = 0; filter = filter.toLowerCase(); - this.$fileList.find('tr').each(function(i,e) { - var $e = $(e); + + function filterRows(tr) { + var $e = $(tr); if ($e.data('file').toString().toLowerCase().indexOf(filter) === -1) { $e.addClass('hidden'); } else { + visibleCount++; $e.removeClass('hidden'); } - }); - that.$container.trigger('scroll'); + } + + var $trs = this.$fileList.find('tr'); + do { + _.each($trs, filterRows); + if (visibleCount < total) { + $trs = this._nextPage(false); + } + } while (visibleCount < total); + + this.$container.trigger('scroll'); }, hideIrrelevantUIWhenNoFilesMatch:function() { if (this._filter && this.fileSummary.summary.totalDirs + this.fileSummary.summary.totalFiles === 0) { diff --git a/apps/files/tests/js/filelistSpec.js b/apps/files/tests/js/filelistSpec.js index 7e6408128b..a74e1c7328 100644 --- a/apps/files/tests/js/filelistSpec.js +++ b/apps/files/tests/js/filelistSpec.js @@ -989,6 +989,17 @@ describe('OCA.Files.FileList tests', function() { expect($summary.find('.info').text()).toEqual("1 folder and 3 files"); expect($nofilterresults.hasClass('hidden')).toEqual(true); }); + it('filters the list of non-rendered rows using filter()', function() { + var $summary = $('#filestable .summary'); + var $nofilterresults = fileList.$el.find(".nofilterresults"); + fileList.setFiles(generateFiles(0, 64)); + + fileList.setFilter('63'); + expect($('#fileList tr:not(.hidden)').length).toEqual(1); + expect($summary.hasClass('hidden')).toEqual(false); + expect($summary.find('.info').text()).toEqual("0 folders and 1 file matches '63'"); + expect($nofilterresults.hasClass('hidden')).toEqual(true); + }); it('hides the emptyfiles notice when using filter()', function() { expect(fileList.files.length).toEqual(0); expect(fileList.files).toEqual([]); From 89198e62e84d734d1ce6a4d0628bfc13b8b081bf Mon Sep 17 00:00:00 2001 From: Christoph Wurst <christoph@owncloud.com> Date: Fri, 24 Jun 2016 13:57:09 +0200 Subject: [PATCH 09/14] check login name when authenticating with client token --- lib/private/User/Session.php | 13 +++++++++++-- tests/lib/User/SessionTest.php | 30 ++++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+), 2 deletions(-) diff --git a/lib/private/User/Session.php b/lib/private/User/Session.php index 2b65f31af2..6219a89e5b 100644 --- a/lib/private/User/Session.php +++ b/lib/private/User/Session.php @@ -280,7 +280,7 @@ class Session implements IUserSession, Emitter { */ public function login($uid, $password) { $this->session->regenerateId(); - if ($this->validateToken($password)) { + if ($this->validateToken($password, $uid)) { // When logging in with token, the password must be decrypted first before passing to login hook try { $token = $this->tokenProvider->getToken($password); @@ -584,15 +584,24 @@ class Session implements IUserSession, Emitter { * Invalidates the token if checks fail * * @param string $token + * @param string $user login name * @return boolean */ - private function validateToken($token) { + private function validateToken($token, $user = null) { try { $dbToken = $this->tokenProvider->getToken($token); } catch (InvalidTokenException $ex) { return false; } + // Check if login names match + if (!is_null($user) && $dbToken->getLoginName() !== $user) { + // TODO: this makes it imposssible to use different login names on browser and client + // e.g. login by e-mail 'user@example.com' on browser for generating the token will not + // allow to use the client token with the login name 'user'. + return false; + } + if (!$this->checkTokenCredentials($dbToken, $token)) { return false; } diff --git a/tests/lib/User/SessionTest.php b/tests/lib/User/SessionTest.php index eef4c7ff5e..447c6142f3 100644 --- a/tests/lib/User/SessionTest.php +++ b/tests/lib/User/SessionTest.php @@ -314,6 +314,36 @@ class SessionTest extends \Test\TestCase { $userSession->login('foo', 'bar'); } + /** + * When using a device token, the loginname must match the one that was used + * when generating the token on the browser. + */ + public function testLoginWithDifferentTokenLoginName() { + $session = $this->getMock('\OC\Session\Memory', array(), array('')); + $manager = $this->getMock('\OC\User\Manager'); + $backend = $this->getMock('\Test\Util\User\Dummy'); + $userSession = new \OC\User\Session($manager, $session, $this->timeFactory, $this->tokenProvider, $this->config); + $username = 'user123'; + $token = new \OC\Authentication\Token\DefaultToken(); + $token->setLoginName($username); + + $session->expects($this->never()) + ->method('set'); + $session->expects($this->once()) + ->method('regenerateId'); + $this->tokenProvider->expects($this->once()) + ->method('getToken') + ->with('bar') + ->will($this->returnValue($token)); + + $manager->expects($this->once()) + ->method('checkPassword') + ->with('foo', 'bar') + ->will($this->returnValue(false)); + + $userSession->login('foo', 'bar'); + } + /** * @expectedException \OC\Authentication\Exceptions\PasswordLoginForbiddenException */ From 26aacb4e62cbd8f83f59ede459463a13a16eb319 Mon Sep 17 00:00:00 2001 From: Christoph Wurst <christoph@owncloud.com> Date: Fri, 24 Jun 2016 15:31:48 +0200 Subject: [PATCH 10/14] show which login name to use for the new app password --- settings/Controller/AuthSettingsController.php | 1 + settings/js/authtoken_view.js | 6 ++++++ settings/templates/personal.php | 1 + 3 files changed, 8 insertions(+) diff --git a/settings/Controller/AuthSettingsController.php b/settings/Controller/AuthSettingsController.php index fba663b034..db2db6e5bf 100644 --- a/settings/Controller/AuthSettingsController.php +++ b/settings/Controller/AuthSettingsController.php @@ -118,6 +118,7 @@ class AuthSettingsController extends Controller { return [ 'token' => $token, + 'loginName' => $loginName, 'deviceToken' => $deviceToken ]; } diff --git a/settings/js/authtoken_view.js b/settings/js/authtoken_view.js index da5861689a..cf6b4f7c8d 100644 --- a/settings/js/authtoken_view.js +++ b/settings/js/authtoken_view.js @@ -107,6 +107,8 @@ _result: undefined, + _newAppLoginName: undefined, + _newAppPassword: undefined, _hideAppPasswordBtn: undefined, @@ -136,6 +138,7 @@ this._addAppPasswordBtn.click(_.bind(this._addAppPassword, this)); this._result = $('#app-password-result'); + this._newAppLoginName = $('#new-app-login-name'); this._newAppPassword = $('#new-app-password'); this._newAppPassword.on('focus', _.bind(this._onNewTokenFocus, this)); this._hideAppPasswordBtn = $('#app-password-hide'); @@ -181,6 +184,9 @@ $.when(creatingToken).done(function(resp) { _this.collection.add(resp.deviceToken); _this.render(); + _this._newAppLoginName.text(t('core', 'You may now configure your client with username "{loginName}" and the following password:', { + loginName: resp.loginName + })); _this._newAppPassword.val(resp.token); _this._toggleFormResult(false); _this._newAppPassword.select(); diff --git a/settings/templates/personal.php b/settings/templates/personal.php index b9b8429d94..19a9968cec 100644 --- a/settings/templates/personal.php +++ b/settings/templates/personal.php @@ -203,6 +203,7 @@ if($_['passwordChangeSupported']) { <button id="add-app-password" class="button"><?php p($l->t('Create new app password')); ?></button> </div> <div id="app-password-result" class="hidden"> + <span id="new-app-login-name"></span> <input id="new-app-password" type="text" readonly="readonly"/> <button id="app-password-hide" class="button"><?php p($l->t('Done')); ?></button> </div> From f062b62f0396b50436070426ad9d10b853543c3f Mon Sep 17 00:00:00 2001 From: Vincent Petry <pvince81@owncloud.com> Date: Fri, 24 Jun 2016 16:46:25 +0200 Subject: [PATCH 11/14] Keep encryption enabled if decrypting for single user When decrypting all files of a single user, the admin usually does not intend encryption to be suddenly disabled for everyone. This fix reenables encryption after decrypting for a single user. Decrypting for all users will still disable encryption globally. --- core/Command/Encryption/DecryptAll.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/core/Command/Encryption/DecryptAll.php b/core/Command/Encryption/DecryptAll.php index 83c6c1dc16..cda7853b8b 100644 --- a/core/Command/Encryption/DecryptAll.php +++ b/core/Command/Encryption/DecryptAll.php @@ -150,6 +150,9 @@ class DecryptAll extends Command { $output->writeln(' aborted.'); $output->writeln('Server side encryption remains enabled'); $this->config->setAppValue('core', 'encryption_enabled', 'yes'); + } else if ($uid !== '') { + $output->writeln('Server side encryption remains enabled'); + $this->config->setAppValue('core', 'encryption_enabled', 'yes'); } $this->resetSingleUserAndTrashbin(); } else { From 1454b4515c8bdcb05c400c605cf6d84c38476efc Mon Sep 17 00:00:00 2001 From: Christoph Wurst <christoph@owncloud.com> Date: Fri, 24 Jun 2016 17:00:59 +0200 Subject: [PATCH 12/14] fix layout --- settings/css/settings.css | 8 ++++++++ settings/js/authtoken_view.js | 9 ++++++--- settings/templates/personal.php | 13 ++++++++++--- 3 files changed, 24 insertions(+), 6 deletions(-) diff --git a/settings/css/settings.css b/settings/css/settings.css index e4ddec9152..914c0d7515 100644 --- a/settings/css/settings.css +++ b/settings/css/settings.css @@ -131,11 +131,19 @@ table.nostyle td { padding: 0.2em 0; } opacity: 0.6; } +#new-app-login-name, #new-app-password { width: 186px; font-family: monospace; background-color: lightyellow; } +.app-password-row { + display: table-row; +} +.app-password-label { + display: table-cell; + padding-right: 1em; +} /* USERS */ #newgroup-init a span { margin-left: 20px; } diff --git a/settings/js/authtoken_view.js b/settings/js/authtoken_view.js index cf6b4f7c8d..db6dac85ce 100644 --- a/settings/js/authtoken_view.js +++ b/settings/js/authtoken_view.js @@ -139,6 +139,7 @@ this._result = $('#app-password-result'); this._newAppLoginName = $('#new-app-login-name'); + this._newAppLoginName.on('focus', _.bind(this._onNewTokenLoginNameFocus, this)); this._newAppPassword = $('#new-app-password'); this._newAppPassword.on('focus', _.bind(this._onNewTokenFocus, this)); this._hideAppPasswordBtn = $('#app-password-hide'); @@ -184,9 +185,7 @@ $.when(creatingToken).done(function(resp) { _this.collection.add(resp.deviceToken); _this.render(); - _this._newAppLoginName.text(t('core', 'You may now configure your client with username "{loginName}" and the following password:', { - loginName: resp.loginName - })); + _this._newAppLoginName.val(resp.loginName); _this._newAppPassword.val(resp.token); _this._toggleFormResult(false); _this._newAppPassword.select(); @@ -200,6 +199,10 @@ }); }, + _onNewTokenLoginNameFocus: function() { + this._newAppLoginName.select(); + }, + _onNewTokenFocus: function() { this._newAppPassword.select(); }, diff --git a/settings/templates/personal.php b/settings/templates/personal.php index 19a9968cec..50e7ef79f8 100644 --- a/settings/templates/personal.php +++ b/settings/templates/personal.php @@ -203,9 +203,16 @@ if($_['passwordChangeSupported']) { <button id="add-app-password" class="button"><?php p($l->t('Create new app password')); ?></button> </div> <div id="app-password-result" class="hidden"> - <span id="new-app-login-name"></span> - <input id="new-app-password" type="text" readonly="readonly"/> - <button id="app-password-hide" class="button"><?php p($l->t('Done')); ?></button> + <span><?php p($l->t('Use the credentials below to configure your app or device.')); ?></span> + <div class="app-password-row"> + <span class="app-password-label"><?php p($l->t('Username')); ?></span> + <input id="new-app-login-name" type="text" readonly="readonly"/> + </div> + <div class="app-password-row"> + <span class="app-password-label"><?php p($l->t('Password')); ?></span> + <input id="new-app-password" type="text" readonly="readonly"/> + <button id="app-password-hide" class="button"><?php p($l->t('Done')); ?></button> + </div> </div> </div> From ee90bef50a179e542b15ea5c7b1cf3d808cd3ad4 Mon Sep 17 00:00:00 2001 From: Jenkins for ownCloud <owncloud-bot@tmit.eu> Date: Mon, 27 Jun 2016 01:55:57 -0400 Subject: [PATCH 13/14] [tx-robot] updated from transifex --- apps/comments/l10n/fi_FI.js | 3 +++ apps/comments/l10n/fi_FI.json | 3 +++ apps/comments/l10n/pl.js | 3 +++ apps/comments/l10n/pl.json | 3 +++ apps/systemtags/l10n/pl.js | 1 + apps/systemtags/l10n/pl.json | 1 + core/l10n/pl.js | 7 +++++++ core/l10n/pl.json | 7 +++++++ settings/l10n/pl.js | 2 ++ settings/l10n/pl.json | 2 ++ 10 files changed, 32 insertions(+) diff --git a/apps/comments/l10n/fi_FI.js b/apps/comments/l10n/fi_FI.js index 7b4de94676..b541b9987f 100644 --- a/apps/comments/l10n/fi_FI.js +++ b/apps/comments/l10n/fi_FI.js @@ -12,6 +12,9 @@ OC.L10N.register( "More comments..." : "Lisää kommentteja...", "Save" : "Tallenna", "Allowed characters {count} of {max}" : "Sallittujen merkkien määrä {count}/{max}", + "Error occurred while retrieving comment with id {id}" : "Virhe noutaessa kommenttia tunnisteella {id}", + "Error occurred while updating comment with id {id}" : "Virhe päivittäessä kommenttia tunnisteella {id}", + "Error occurred while posting comment" : "Virhe kommenttia lähettäessä", "{count} unread comments" : "{count} lukematonta kommenttia", "Comment" : "Kommentti", "<strong>Comments</strong> for files <em>(always listed in stream)</em>" : "<strong>Kommentit</strong> tiedostoille <em>(aina listattu luettelossa)</em>", diff --git a/apps/comments/l10n/fi_FI.json b/apps/comments/l10n/fi_FI.json index 5e9bcc7440..b5be601d90 100644 --- a/apps/comments/l10n/fi_FI.json +++ b/apps/comments/l10n/fi_FI.json @@ -10,6 +10,9 @@ "More comments..." : "Lisää kommentteja...", "Save" : "Tallenna", "Allowed characters {count} of {max}" : "Sallittujen merkkien määrä {count}/{max}", + "Error occurred while retrieving comment with id {id}" : "Virhe noutaessa kommenttia tunnisteella {id}", + "Error occurred while updating comment with id {id}" : "Virhe päivittäessä kommenttia tunnisteella {id}", + "Error occurred while posting comment" : "Virhe kommenttia lähettäessä", "{count} unread comments" : "{count} lukematonta kommenttia", "Comment" : "Kommentti", "<strong>Comments</strong> for files <em>(always listed in stream)</em>" : "<strong>Kommentit</strong> tiedostoille <em>(aina listattu luettelossa)</em>", diff --git a/apps/comments/l10n/pl.js b/apps/comments/l10n/pl.js index d4a492e1da..b8df4dea02 100644 --- a/apps/comments/l10n/pl.js +++ b/apps/comments/l10n/pl.js @@ -12,6 +12,9 @@ OC.L10N.register( "More comments..." : "Więcej komentarzy...", "Save" : "Zapisz", "Allowed characters {count} of {max}" : "Dozwolone znaki {count} z {max}", + "Error occurred while retrieving comment with id {id}" : "W trakcie otrzymywania komentarza o identyfikatorze {id} wystąpił błąd.", + "Error occurred while updating comment with id {id}" : "W trakcie aktualizacji komentarza o identyfikatorze {id} wystąpił błąd.", + "Error occurred while posting comment" : "Podczas wysyłania komentarza wystąpił błąd", "{count} unread comments" : "{count} nieprzeczytanych komentarzy", "Comment" : "Komentarz", "<strong>Comments</strong> for files <em>(always listed in stream)</em>" : "<strong>Komentarze</strong> dla plików <em>(zawsze wypisane w strumieniu)</em>", diff --git a/apps/comments/l10n/pl.json b/apps/comments/l10n/pl.json index 78e9f0ff21..47e4b101f9 100644 --- a/apps/comments/l10n/pl.json +++ b/apps/comments/l10n/pl.json @@ -10,6 +10,9 @@ "More comments..." : "Więcej komentarzy...", "Save" : "Zapisz", "Allowed characters {count} of {max}" : "Dozwolone znaki {count} z {max}", + "Error occurred while retrieving comment with id {id}" : "W trakcie otrzymywania komentarza o identyfikatorze {id} wystąpił błąd.", + "Error occurred while updating comment with id {id}" : "W trakcie aktualizacji komentarza o identyfikatorze {id} wystąpił błąd.", + "Error occurred while posting comment" : "Podczas wysyłania komentarza wystąpił błąd", "{count} unread comments" : "{count} nieprzeczytanych komentarzy", "Comment" : "Komentarz", "<strong>Comments</strong> for files <em>(always listed in stream)</em>" : "<strong>Komentarze</strong> dla plików <em>(zawsze wypisane w strumieniu)</em>", diff --git a/apps/systemtags/l10n/pl.js b/apps/systemtags/l10n/pl.js index f5e4775d54..fca05665ec 100644 --- a/apps/systemtags/l10n/pl.js +++ b/apps/systemtags/l10n/pl.js @@ -14,6 +14,7 @@ OC.L10N.register( "%1$s updated system tag %3$s to %2$s" : "%1$s zaktualizowany system etykiet%3$s do %2$s", "%1$s assigned system tag %3$s to %2$s" : "%1$s przypisywalny system etykiet%3$s do %2$s", "%1$s unassigned system tag %3$s from %2$s" : "%1$s nieprzypisany system etykiet %3$s z %2$s", + "%s (restricted)" : "%s (ograniczone)", "%s (invisible)" : "%s (niewidoczny)", "No files in here" : "Brak plików", "No entries found in this folder" : "Brak wpisów w tym folderze", diff --git a/apps/systemtags/l10n/pl.json b/apps/systemtags/l10n/pl.json index 6cb103ed4a..97ae0230be 100644 --- a/apps/systemtags/l10n/pl.json +++ b/apps/systemtags/l10n/pl.json @@ -12,6 +12,7 @@ "%1$s updated system tag %3$s to %2$s" : "%1$s zaktualizowany system etykiet%3$s do %2$s", "%1$s assigned system tag %3$s to %2$s" : "%1$s przypisywalny system etykiet%3$s do %2$s", "%1$s unassigned system tag %3$s from %2$s" : "%1$s nieprzypisany system etykiet %3$s z %2$s", + "%s (restricted)" : "%s (ograniczone)", "%s (invisible)" : "%s (niewidoczny)", "No files in here" : "Brak plików", "No entries found in this folder" : "Brak wpisów w tym folderze", diff --git a/core/l10n/pl.js b/core/l10n/pl.js index 3b284163b4..da251abd07 100644 --- a/core/l10n/pl.js +++ b/core/l10n/pl.js @@ -166,9 +166,13 @@ OC.L10N.register( "delete" : "usuń", "access control" : "kontrola dostępu", "Could not unshare" : "Nie udało się usunąć udostępnienia", + "Share details could not be loaded for this item." : "Szczegóły udziału nie mogły zostać wczytane dla tego obiektu.", "No users or groups found for {search}" : "Nie znaleziono użytkowników lub grup dla {search}", "No users found for {search}" : "Nie znaleziono użytkowników dla {search}", "An error occurred. Please try again" : "Wystąpił błąd. Proszę spróbować ponownie.", + "{sharee} (group)" : "{sharee} (grupa)", + "{sharee} (at {server})" : "{sharee} (na {server})", + "{sharee} (remote)" : "{sharee} (zdalny)", "Share" : "Udostępnij", "Share with people on other ownClouds using the syntax username@example.com/owncloud" : "Współdziel z użytkownikami innych chmur ownCloud używając wzorca uzytkownik@example.com/owncloud", "Share with users…" : "Współdziel z użytkownikami...", @@ -181,6 +185,7 @@ OC.L10N.register( "Non-existing tag #{tag}" : "Znacznik #{tag} nie istnieje", "restricted" : "ograniczone", "invisible" : "niewidoczny", + "({scope})" : "({scope})", "Delete" : "Usuń", "Rename" : "Zmień nazwę", "The object type is not specified." : "Nie określono typu obiektu.", @@ -237,6 +242,7 @@ OC.L10N.register( "Data folder" : "Katalog danych", "Configure the database" : "Skonfiguruj bazę danych", "Only %s is available." : "Dostępne jest wyłącznie %s.", + "Install and activate additional PHP modules to choose other database types." : "Zainstaluj lub aktywuj dodatkowe moduły PHP, aby uzyskać możliwość wyboru innych typów baz danych.", "For more details check out the documentation." : "Aby uzyskać więcej informacji zapoznaj się z dokumentacją.", "Database user" : "Użytkownik bazy danych", "Database password" : "Hasło do bazy danych", @@ -289,6 +295,7 @@ OC.L10N.register( "To avoid timeouts with larger installations, you can instead run the following command from your installation directory:" : "Aby uniknąć timeout-ów przy większych instalacjach, możesz zamiast tego uruchomić następującą komendę w katalogu Twojej instalacji:", "Detailed logs" : "Szczegółowe logi", "Update needed" : "Wymagana aktualizacja", + "Please use the command line updater because you have a big instance." : "Ze względu na rozmiar Twojej instalacji użyj programu do aktualizacji z linii poleceń.", "For help, see the <a target=\"_blank\" rel=\"noreferrer\" href=\"%s\">documentation</a>." : "Aby uzyskać pomoc, zajrzyj do <a target=\"_blank\" rel=\"noreferrer\" href=\"%s\">dokumentacji</a>.", "This page will refresh itself when the %s instance is available again." : "Strona odświeży się gdy instancja %s będzie ponownie dostępna." }, diff --git a/core/l10n/pl.json b/core/l10n/pl.json index 6833611713..9f06fcf2d6 100644 --- a/core/l10n/pl.json +++ b/core/l10n/pl.json @@ -164,9 +164,13 @@ "delete" : "usuń", "access control" : "kontrola dostępu", "Could not unshare" : "Nie udało się usunąć udostępnienia", + "Share details could not be loaded for this item." : "Szczegóły udziału nie mogły zostać wczytane dla tego obiektu.", "No users or groups found for {search}" : "Nie znaleziono użytkowników lub grup dla {search}", "No users found for {search}" : "Nie znaleziono użytkowników dla {search}", "An error occurred. Please try again" : "Wystąpił błąd. Proszę spróbować ponownie.", + "{sharee} (group)" : "{sharee} (grupa)", + "{sharee} (at {server})" : "{sharee} (na {server})", + "{sharee} (remote)" : "{sharee} (zdalny)", "Share" : "Udostępnij", "Share with people on other ownClouds using the syntax username@example.com/owncloud" : "Współdziel z użytkownikami innych chmur ownCloud używając wzorca uzytkownik@example.com/owncloud", "Share with users…" : "Współdziel z użytkownikami...", @@ -179,6 +183,7 @@ "Non-existing tag #{tag}" : "Znacznik #{tag} nie istnieje", "restricted" : "ograniczone", "invisible" : "niewidoczny", + "({scope})" : "({scope})", "Delete" : "Usuń", "Rename" : "Zmień nazwę", "The object type is not specified." : "Nie określono typu obiektu.", @@ -235,6 +240,7 @@ "Data folder" : "Katalog danych", "Configure the database" : "Skonfiguruj bazę danych", "Only %s is available." : "Dostępne jest wyłącznie %s.", + "Install and activate additional PHP modules to choose other database types." : "Zainstaluj lub aktywuj dodatkowe moduły PHP, aby uzyskać możliwość wyboru innych typów baz danych.", "For more details check out the documentation." : "Aby uzyskać więcej informacji zapoznaj się z dokumentacją.", "Database user" : "Użytkownik bazy danych", "Database password" : "Hasło do bazy danych", @@ -287,6 +293,7 @@ "To avoid timeouts with larger installations, you can instead run the following command from your installation directory:" : "Aby uniknąć timeout-ów przy większych instalacjach, możesz zamiast tego uruchomić następującą komendę w katalogu Twojej instalacji:", "Detailed logs" : "Szczegółowe logi", "Update needed" : "Wymagana aktualizacja", + "Please use the command line updater because you have a big instance." : "Ze względu na rozmiar Twojej instalacji użyj programu do aktualizacji z linii poleceń.", "For help, see the <a target=\"_blank\" rel=\"noreferrer\" href=\"%s\">documentation</a>." : "Aby uzyskać pomoc, zajrzyj do <a target=\"_blank\" rel=\"noreferrer\" href=\"%s\">dokumentacji</a>.", "This page will refresh itself when the %s instance is available again." : "Strona odświeży się gdy instancja %s będzie ponownie dostępna." },"pluralForm" :"nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);" diff --git a/settings/l10n/pl.js b/settings/l10n/pl.js index 0d68b38493..903b374e0a 100644 --- a/settings/l10n/pl.js +++ b/settings/l10n/pl.js @@ -28,6 +28,7 @@ OC.L10N.register( "Email saved" : "E-mail zapisany", "Your full name has been changed." : "Twoja pełna nazwa została zmieniona.", "Unable to change full name" : "Nie można zmienić pełnej nazwy", + "Redis" : "Redis", "Security & setup warnings" : "Ostrzeżenia bezpieczeństwa i konfiguracji", "Sharing" : "Udostępnianie", "Server-side encryption" : "Szyfrowanie po stronie serwera", @@ -48,6 +49,7 @@ OC.L10N.register( "Migration in progress. Please wait until the migration is finished" : "Trwa migracja. Proszę poczekać, aż migracja dobiegnie końca.", "Migration started …" : "Migracja rozpoczęta...", "Sending..." : "Wysyłam...", + "Experimental" : "Eksperymentalny", "All" : "Wszystkie", "No apps found for your version" : "Nie znaleziono aplikacji dla twojej wersji", "Update to %s" : "Uaktualnij do %s", diff --git a/settings/l10n/pl.json b/settings/l10n/pl.json index bdae17d4ee..f1c1994dd1 100644 --- a/settings/l10n/pl.json +++ b/settings/l10n/pl.json @@ -26,6 +26,7 @@ "Email saved" : "E-mail zapisany", "Your full name has been changed." : "Twoja pełna nazwa została zmieniona.", "Unable to change full name" : "Nie można zmienić pełnej nazwy", + "Redis" : "Redis", "Security & setup warnings" : "Ostrzeżenia bezpieczeństwa i konfiguracji", "Sharing" : "Udostępnianie", "Server-side encryption" : "Szyfrowanie po stronie serwera", @@ -46,6 +47,7 @@ "Migration in progress. Please wait until the migration is finished" : "Trwa migracja. Proszę poczekać, aż migracja dobiegnie końca.", "Migration started …" : "Migracja rozpoczęta...", "Sending..." : "Wysyłam...", + "Experimental" : "Eksperymentalny", "All" : "Wszystkie", "No apps found for your version" : "Nie znaleziono aplikacji dla twojej wersji", "Update to %s" : "Uaktualnij do %s", From 0d3de20b021e3323f1873accacb7c4533f34e213 Mon Sep 17 00:00:00 2001 From: Vincent Petry <pvince81@owncloud.com> Date: Mon, 27 Jun 2016 10:50:10 +0200 Subject: [PATCH 14/14] Quickfix: do not lazy load auth mechanisms for ext storages Some auth mechanisms like SessionCredentials need to register hooks early, so they cannot be lazy loaded. --- apps/files_external/lib/AppInfo/Application.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/apps/files_external/lib/AppInfo/Application.php b/apps/files_external/lib/AppInfo/Application.php index f78411038f..b2eaf225e9 100644 --- a/apps/files_external/lib/AppInfo/Application.php +++ b/apps/files_external/lib/AppInfo/Application.php @@ -52,6 +52,10 @@ class Application extends App implements IBackendProvider, IAuthMechanismProvide $backendService->registerBackendProvider($this); $backendService->registerAuthMechanismProvider($this); + // force-load auth mechanisms since some will register hooks + // TODO: obsolete these and use the TokenProvider to get the user's password from the session + $this->getAuthMechanisms(); + // app developers: do NOT depend on this! it will disappear with oC 9.0! \OC::$server->getEventDispatcher()->dispatch( 'OCA\\Files_External::loadAdditionalBackends'