From c42d977185648fcc34c8e0135973ebc1c4776512 Mon Sep 17 00:00:00 2001 From: Christoph Wurst Date: Wed, 20 Apr 2016 12:19:39 +0200 Subject: [PATCH 01/53] Add more personal information fields to the settings page for enhanced federated sharing fix layout Add generic way of handling input change events Signed-off-by: Roeland Jago Douma --- settings/css/settings.css | 46 ++++++++-- settings/js/federationscopemenu.js | 114 +++++++++++++++++++++++++ settings/js/federationsettingsview.js | 116 ++++++++++++++++++++++++++ settings/js/personal.js | 23 +++-- settings/personal.php | 4 +- settings/templates/personal.php | 111 +++++++++++++++++------- 6 files changed, 371 insertions(+), 43 deletions(-) create mode 100644 settings/js/federationscopemenu.js create mode 100644 settings/js/federationsettingsview.js diff --git a/settings/css/settings.css b/settings/css/settings.css index 9008ba5a985ba..fe3518ae6a300 100644 --- a/settings/css/settings.css +++ b/settings/css/settings.css @@ -8,8 +8,6 @@ input#openid, input#webdav { width:20em; } /* PERSONAL */ #avatar { - display: inline-block; - float: left; width: 160px; padding-right: 0; } @@ -63,6 +61,46 @@ input#openid, input#webdav { width:20em; } float: right; } +#personal-settings-avatar-container { + float: left; +} +#personal-settings-container { + position: relative; + float: left; + min-width: 280px; + width: calc(100% - 200px); +} +#personal-settings-container:after { + clear: both; +} +#personal-settings-container > div { + float: left; + height: 100px; + min-width: 300px; +} +#personal-settings-container > div h2 span[class^="icon-"] { + display: inline-block; + margin-left: 5px; + background-size: 110%; + opacity: 0.3; + cursor: pointer; +} +.personal-settings-setting-box input[type="text"], +.personal-settings-setting-box input[type="email"], +.personal-settings-setting-box input[type="tel"] { + width: 17em; +} +.federationScopeMenu { + top: 66px; +} +.federationScopeMenu.bubble::after { + left: 45px; +} +.federationScopeMenu.popovermenu { + font-weight: 100; + font-size: medium; +} + #displaynameform, #lostpassword, #groups, @@ -104,10 +142,6 @@ input#openid, input#webdav { width:20em; } input#identity { width: 20em; } -#displayName, -#email { - width: 17em; -} #showWizard { display: inline-block; diff --git a/settings/js/federationscopemenu.js b/settings/js/federationscopemenu.js new file mode 100644 index 0000000000000..6eb9afb54e05c --- /dev/null +++ b/settings/js/federationscopemenu.js @@ -0,0 +1,114 @@ +/* + * Copyright (c) 2016 + * + * This file is licensed under the Affero General Public License version 3 + * or later. + * + * See the COPYING-README file. + * + */ + +/* global OC, Handlebars */ + +(function() { + + var TEMPLATE_MENU = + ''; + + /** + * Construct a new FederationScopeMenu instance + * @constructs FederationScopeMenu + * @memberof OC.Settings + */ + var FederationScopeMenu = OC.Backbone.View.extend({ + tagName: 'div', + className: 'federationScopeMenu popovermenu bubble hidden open menu', + _scopes: [ + { + name: 'private', + displayName: t('core', 'Private'), + icon: OC.imagePath('core', 'actions/password') + }, + { + name: 'contacts', + displayName: t('core', 'Contacts'), + icon: OC.imagePath('core', 'places/contacts-dark') + }, + { + name: 'public', + displayName: t('core', 'Public'), + icon: OC.imagePath('core', 'places/link') + } + ], + + /** + * Current context + * + * @type OCA.Files.FileActionContext + */ + _context: null, + + events: { + 'click a.action': '_onClickAction' + }, + + template: Handlebars.compile(TEMPLATE_MENU), + + /** + * Event handler whenever an action has been clicked within the menu + * + * @param {Object} event event object + */ + _onClickAction: function(event) { + var $target = $(event.currentTarget); + if (!$target.hasClass('menuitem')) { + $target = $target.closest('.menuitem'); + } + + this.trigger('select:scope', $target.data('action')); + + OC.hideMenus(); + }, + + /** + * Renders the menu with the currently set items + */ + render: function() { + this.$el.html(this.template({ + items: this._scopes + })); + }, + + /** + * Displays the menu + */ + show: function(context) { + this._context = context; + + this.render(); + this.$el.removeClass('hidden'); + + OC.showMenu(null, this.$el); + } + }); + + OC.Settings = OC.Settings || {}; + OC.Settings.FederationScopeMenu = FederationScopeMenu; + +})(); + diff --git a/settings/js/federationsettingsview.js b/settings/js/federationsettingsview.js new file mode 100644 index 0000000000000..cf5158fa78577 --- /dev/null +++ b/settings/js/federationsettingsview.js @@ -0,0 +1,116 @@ +/* global OC */ + +/** + * Copyright (c) 2016, Christoph Wurst + * + * This file is licensed under the Affero General Public License version 3 or later. + * See the COPYING-README file. + */ + +(function() { + 'use strict'; + + var FederationSettingsView = OC.Backbone.View.extend({ + _inputFields: undefined, + + /** @var Backbone.Model */ + _config: undefined, + + initialize: function(options) { + options = options || {}; + + if (options.config) { + this._config = options.config; + } else { + this._config = new OC.Backbone.Model() + } + + this._inputFields = [ + 'displayname', + 'phone', + 'email', + 'website', + 'address' + ]; + + this._registerEvents(); + }, + + render: function() { + var self = this; + _.each(this._inputFields, function(field) { + var $heading = self.$('#' + field + 'form > h2'); + var $icon = self.$('#' + field + 'form > h2 > span'); + var scopeMenu = new OC.Settings.FederationScopeMenu(); + + self.listenTo(scopeMenu, 'select:scope', function(scope) { + self._onScopeChanged(field, scope); + }); + $heading.append(scopeMenu.$el); + $icon.on('click', _.bind(scopeMenu.show, scopeMenu)); + + // Fix absolute position according to the heading text length + // TODO: fix position without magic numbers + var pos = ($heading.width() - $heading.find('label').width()) - 68; + scopeMenu.$el.css('right', pos); + }); + }, + + _registerEvents: function() { + var self = this; + _.each(this._inputFields, function(field) { + self.$('#' + field).keyUpDelayedOrEnter(_.bind(self._onInputChanged, self)); + }); + }, + + _onInputChanged: function(e) { + OC.msg.startSaving('#personal-settings-container .msg'); + var $target = $(e.target); + var value = $target.val(); + var field = $target.attr('id'); + console.log(field + ' changed to ' + value); + this._config.set(field, value); + console.log(this._config.toJSON()); + // TODO: this._config.save(); + // TODO: OC.msg.finishedSaving('#personal-settings-container .msg', result); + // TODO: call _updateDisplayName after successful update + }, + + _updateDisplayName: function(displayName) { + // update displayName on the top right expand button + $('#expandDisplayName').text($('#displayName').val()); + // update avatar if avatar is available + if(!$('#removeavatar').hasClass('hidden')) { + updateAvatar(); + } + }, + + _onScopeChanged: function(field, scope) { + // TODO: save changes to the server + console.log(field + ' changed to ' + scope); + + this._setFieldScopeIcon(field, scope); + }, + + _setFieldScopeIcon: function(field, scope) { + var $icon = this.$('#' + field + 'form > h2 > span'); + $icon.removeClass('icon-password'); + $icon.removeClass('icon-contacts-dark'); + $icon.removeClass('icon-link'); + switch (scope) { + case 'private': + $icon.addClass('icon-password'); + break; + case 'contacts': + $icon.addClass('icon-contacts-dark'); + break; + case 'public': + $icon.addClass('icon-link'); + break; + } + } + }); + + OC.Settings = OC.Settings || {}; + OC.Settings.FederationSettingsView = FederationSettingsView; +})(); \ No newline at end of file diff --git a/settings/js/personal.js b/settings/js/personal.js index c2cb437bd13a8..7a8d43d147564 100644 --- a/settings/js/personal.js +++ b/settings/js/personal.js @@ -1,10 +1,15 @@ +/* global OC */ + /** * Copyright (c) 2011, Robin Appelman * 2013, Morris Jobke + * 2016, Christoph Wurst * This file is licensed under the Affero General Public License version 3 or later. * See the COPYING-README file. */ +OC.Settings = OC.Settings || {}; + /** * The callback will be fired as soon as enter is pressed by the * user or 1 second after the last data entry @@ -21,21 +26,21 @@ jQuery.fn.keyUpDelayedOrEnter = function (callback, allowEmptyValue) { return; } if (allowEmptyValue || that.val() !== '') { - cb(); + cb(event); } }, 1000)); this.keypress(function (event) { if (event.keyCode === 13 && (allowEmptyValue || that.val() !== '')) { event.preventDefault(); - cb(); + cb(event); } }); - this.bind('paste', null, function (e) { - if(!e.keyCode){ + this.bind('paste', null, function (event) { + if(!event.keyCode){ if (allowEmptyValue || that.val() !== '') { - cb(); + cb(event); } } }); @@ -265,8 +270,10 @@ $(document).ready(function () { } }); - $('#displayName').keyUpDelayedOrEnter(changeDisplayName); - $('#email').keyUpDelayedOrEnter(changeEmailAddress, true); + var federationSettingsView = new OC.Settings.FederationSettingsView({ + el: '#personal-settings-container' + }); + federationSettingsView.render(); $("#languageinput").change(function () { // Serialize the data @@ -452,3 +459,5 @@ OC.Encryption.msg = { } } }; + +OC.Settings.updateAvatar = updateAvatar; diff --git a/settings/personal.php b/settings/personal.php index 01c358de3aefa..1780dddb5563c 100644 --- a/settings/personal.php +++ b/settings/personal.php @@ -47,7 +47,9 @@ OC_Util::addScript('settings', 'authtoken'); OC_Util::addScript('settings', 'authtoken_collection'); OC_Util::addScript('settings', 'authtoken_view'); -OC_Util::addScript( 'settings', 'personal' ); +OC_Util::addScript('settings', 'federationsettingsview'); +OC_Util::addScript('settings', 'federationscopemenu'); +OC_Util::addScript('settings', 'personal'); OC_Util::addScript('settings', 'certificates'); OC_Util::addStyle( 'settings', 'settings' ); \OC_Util::addVendorScript('strengthify/jquery.strengthify'); diff --git a/settings/templates/personal.php b/settings/templates/personal.php index 8f34d7b87b861..4fb9cefdeb0e5 100644 --- a/settings/templates/personal.php +++ b/settings/templates/personal.php @@ -33,44 +33,94 @@ -
-

t('Profile picture')); ?>

-
-
- - - -
- - -

t('png or jpg, max. 20 MB')); ?>

- - t('Picture provided by original account')); ?> - -
+
+ +

t('Profile picture')); ?>

+
+
+ + + +
+ + +

t('png or jpg, max. 20 MB')); ?>

+ + t('Picture provided by original account')); ?> + +
- -
-

- -

- - - -
+
+
+
+

+ + +

+ +
+
+
+
+

+ + +

+ +
+
+
+
+

+ + +

+ +
+ t('For password recovery and notifications')); ?> +
+
+
+
+

+ + +

+ +
+
+
+
+

+ + +

+ +
+
+ +
@@ -99,10 +149,13 @@ class="password-confirm-required" +======= +>>>>>>> Add more personal information fields to the settings page for enhanced federated sharing

t('Email'); ?>

t('No email address set')); }?>
+ From 20739c93a680d7085d0e71c0e4f9c0bb24018fb9 Mon Sep 17 00:00:00 2001 From: Christoph Wurst Date: Wed, 20 Apr 2016 17:03:50 +0200 Subject: [PATCH 02/53] Persist settings on the server Persist personal settings federated sharing scopes Show new settings fields in read-only mode too Insert values on page load Return updated values; show inline success feedback Signed-off-by: Roeland Jago Douma --- settings/Controller/UsersController.php | 52 ++++++++++++++++++++-- settings/css/settings.css | 11 ++++- settings/js/federationsettingsview.js | 58 ++++++++++++++++++++----- settings/js/usersettings.js | 47 ++++++++++++++++++++ settings/personal.php | 13 ++++++ settings/routes.php | 3 +- settings/templates/personal.php | 34 +++++++++++++-- 7 files changed, 197 insertions(+), 21 deletions(-) create mode 100644 settings/js/usersettings.js diff --git a/settings/Controller/UsersController.php b/settings/Controller/UsersController.php index 89831a66abad0..f06eabf6f9678 100644 --- a/settings/Controller/UsersController.php +++ b/settings/Controller/UsersController.php @@ -493,17 +493,62 @@ public function destroy($id) { } /** - * Set the mail address of a user + * @todo add method description * * @NoAdminRequired * @NoSubadminRequired * @PasswordConfirmationRequired * + * @param string $userId + * @param string $displayname + * @param string $displaynameScope + * @param string $phone + * @param string $phoneScope + * @param string $email + * @param string $emailScope + * @param string $website + * @param string $websiteScope + * @param string $address + * @param string $addressScope + * @return DataResponse + */ + public function saveUserSettings($userId, + $displayname, $displaynameScope, + $phone, $phoneScope, + $email, $emailScope, + $website, $websiteScope, + $address, $addressScope) { + // TODO: implement + return new DataResponse( + array( + 'status' => 'success', + 'data' => array( + 'userId' => $userId, + 'displayname' => $displayname, + 'displaynameScope' => 'public', // force value for test purposes + 'email' => $email, + 'emailScope' => $emailScope, + 'website' => $website, + 'websiteScope' => $websiteScope, + 'address' => $address, + 'addressScope' => $addressScope, + 'message' => (string)$this->l10n->t('Settings saved') + ) + ), + Http::STATUS_OK + ); + } + + /** + * Set the mail address of a user + * + * @todo Merge into saveUserSettings + * * @param string $id * @param string $mailAddress * @return DataResponse */ - public function setMailAddress($id, $mailAddress) { + private function setMailAddress($id, $mailAddress) { $userId = $this->userSession->getUser()->getUID(); $user = $this->userManager->get($id); @@ -619,12 +664,13 @@ public function stats() { * @NoAdminRequired * @NoSubadminRequired * @PasswordConfirmationRequired + * @todo merge into saveUserSettings * * @param string $username * @param string $displayName * @return DataResponse */ - public function setDisplayName($username, $displayName) { + private function setDisplayName($username, $displayName) { $currentUser = $this->userSession->getUser(); if ($username === null) { diff --git a/settings/css/settings.css b/settings/css/settings.css index fe3518ae6a300..4428f6130d6d2 100644 --- a/settings/css/settings.css +++ b/settings/css/settings.css @@ -78,6 +78,10 @@ input#openid, input#webdav { width:20em; } height: 100px; min-width: 300px; } +#personal-settings-container.no-edit > div { + height: 20px; + min-width: 200px; +} #personal-settings-container > div h2 span[class^="icon-"] { display: inline-block; margin-left: 5px; @@ -90,6 +94,12 @@ input#openid, input#webdav { width:20em; } .personal-settings-setting-box input[type="tel"] { width: 17em; } +#personal-settings-container > div > form span[class^="icon-checkmark"] { + position: absolute; + left: 239px; + top: 73px; + pointer-events: none; +} .federationScopeMenu { top: 66px; } @@ -101,7 +111,6 @@ input#openid, input#webdav { width:20em; } font-size: medium; } -#displaynameform, #lostpassword, #groups, #passwordform { diff --git a/settings/js/federationsettingsview.js b/settings/js/federationsettingsview.js index cf5158fa78577..6a10d9f7f7e3a 100644 --- a/settings/js/federationsettingsview.js +++ b/settings/js/federationsettingsview.js @@ -1,4 +1,4 @@ -/* global OC */ +/* global OC, result */ /** * Copyright (c) 2016, Christoph Wurst @@ -22,7 +22,7 @@ if (options.config) { this._config = options.config; } else { - this._config = new OC.Backbone.Model() + this._config = new OC.Settings.UserSettings(); } this._inputFields = [ @@ -33,6 +33,21 @@ 'address' ]; + var self = this; + _.each(this._inputFields, function(field) { + // Initialize config model + self._config.set(field, $('#' + field).val()); + self._config.set(field + 'Scope', $('#' + field + 'scope').val()); + + // Set inputs whenever model values change + self.listenTo(self._config, 'change:' + field, function () { + self.$('#' + field).val(self._config.get(field)); + }); + self.listenTo(self._config, 'change:' + field + 'Scope', function () { + self._onScopeChanged(field, self._config.get(field + 'Scope')); + }); + }); + this._registerEvents(); }, @@ -53,6 +68,8 @@ // TODO: fix position without magic numbers var pos = ($heading.width() - $heading.find('label').width()) - 68; scopeMenu.$el.css('right', pos); + + self._onScopeChanged(field, self._config.get(field + 'Scope')); }); }, @@ -64,21 +81,30 @@ }, _onInputChanged: function(e) { - OC.msg.startSaving('#personal-settings-container .msg'); + var self = this; + var $target = $(e.target); var value = $target.val(); var field = $target.attr('id'); - console.log(field + ' changed to ' + value); this._config.set(field, value); - console.log(this._config.toJSON()); - // TODO: this._config.save(); - // TODO: OC.msg.finishedSaving('#personal-settings-container .msg', result); - // TODO: call _updateDisplayName after successful update + var savingData = this._config.save({ + error: function(jqXHR) { + OC.msg.finishedSaving('#personal-settings-container .msg', jqXHR); + } + }); + + $.when(savingData).done(function() { + //OC.msg.finishedSaving('#personal-settings-container .msg', result) + self._showInputChangeSuccess(field); + if (field === 'displayname') { + self._updateDisplayName(value); + } + }); }, _updateDisplayName: function(displayName) { // update displayName on the top right expand button - $('#expandDisplayName').text($('#displayName').val()); + $('#expandDisplayName').text(displayName); // update avatar if avatar is available if(!$('#removeavatar').hasClass('hidden')) { updateAvatar(); @@ -86,12 +112,20 @@ }, _onScopeChanged: function(field, scope) { - // TODO: save changes to the server - console.log(field + ' changed to ' + scope); - + this._config.set(field + 'Scope', scope); + // TODO: user loading/success feedback + this._config.save(); this._setFieldScopeIcon(field, scope); }, + _showInputChangeSuccess: function(field) { + var $icon = this.$('#' + field + 'form > span'); + $icon.fadeIn(200); + setTimeout(function() { + $icon.fadeOut(300); + }, 2000); + }, + _setFieldScopeIcon: function(field, scope) { var $icon = this.$('#' + field + 'form > h2 > span'); $icon.removeClass('icon-password'); diff --git a/settings/js/usersettings.js b/settings/js/usersettings.js new file mode 100644 index 0000000000000..d8d089f83deb4 --- /dev/null +++ b/settings/js/usersettings.js @@ -0,0 +1,47 @@ +/* global OC */ + +/** + * Copyright (c) 2016, Christoph Wurst + * + * This file is licensed under the Affero General Public License version 3 or later. + * See the COPYING-README file. + */ + +(function() { + 'use strict'; + + /** + * Model for storing and saving user settings + * + * @class UserSettings + */ + var UserSettings = OC.Backbone.Model.extend({ + url: OC.generateUrl('/settings/users/{id}/settings', {id: OC.currentUser}), + parse: function(data) { + if (_.isUndefined(data)) { + return null; + } + if (_.isUndefined(data.data)) { + return null; + } + data = data.data; + + var ignored = [ + 'userId', + 'message' + ]; + + _.each(ignored, function(ign) { + if (!_.isUndefined(data[ign])) { + delete data[ign]; + } + }); + + return data; + } + }); + + OC.Settings = OC.Settings || {}; + + OC.Settings.UserSettings = UserSettings; +})(); \ No newline at end of file diff --git a/settings/personal.php b/settings/personal.php index 1780dddb5563c..a18a88a9b6382 100644 --- a/settings/personal.php +++ b/settings/personal.php @@ -47,6 +47,7 @@ OC_Util::addScript('settings', 'authtoken'); OC_Util::addScript('settings', 'authtoken_collection'); OC_Util::addScript('settings', 'authtoken_view'); +OC_Util::addScript('settings', 'usersettings'); OC_Util::addScript('settings', 'federationsettingsview'); OC_Util::addScript('settings', 'federationscopemenu'); OC_Util::addScript('settings', 'personal'); @@ -164,6 +165,18 @@ $tmpl->assign('passwordChangeSupported', OC_User::canUserChangePassword(OC_User::getUser())); $tmpl->assign('displayNameChangeSupported', OC_User::canUserChangeDisplayName(OC_User::getUser())); $tmpl->assign('displayName', OC_User::getDisplayName()); +// TODO: insert real data +$tmpl->assign('phone', '+43 660 56565 5446'); +$tmpl->assign('website', 'owncloud.org'); +$tmpl->assign('address', 'Stuttgart'); + +$tmpl->assign('avatarScope', 'contacts'); +$tmpl->assign('displayNameScope', 'public'); +$tmpl->assign('phoneScope', 'contacts'); +$tmpl->assign('emailScope', 'contacts'); +$tmpl->assign('websiteScope', 'public'); +$tmpl->assign('addressScope', 'private'); +// END TODO $tmpl->assign('enableAvatars', $config->getSystemValue('enable_avatars', true) === true); $tmpl->assign('avatarChangeSupported', OC_User::canUserChangeAvatar(OC_User::getUser())); $tmpl->assign('certs', $certificateManager->listCertificates()); diff --git a/settings/routes.php b/settings/routes.php index 58a576063128d..ac4ade4f14ab4 100644 --- a/settings/routes.php +++ b/settings/routes.php @@ -50,8 +50,7 @@ ['name' => 'AppSettings#viewApps', 'url' => '/settings/apps', 'verb' => 'GET'], ['name' => 'AppSettings#listApps', 'url' => '/settings/apps/list', 'verb' => 'GET'], ['name' => 'SecuritySettings#trustedDomains', 'url' => '/settings/admin/security/trustedDomains', 'verb' => 'POST'], - ['name' => 'Users#setDisplayName', 'url' => '/settings/users/{username}/displayName', 'verb' => 'POST'], - ['name' => 'Users#setMailAddress', 'url' => '/settings/users/{id}/mailAddress', 'verb' => 'PUT'], + ['name' => 'Users#saveUserSettings', 'url' => '/settings/users/{username}/settings', 'verb' => 'POST'], ['name' => 'Users#stats', 'url' => '/settings/users/stats', 'verb' => 'GET'], ['name' => 'LogSettings#setLogLevel', 'url' => '/settings/admin/log/level', 'verb' => 'POST'], ['name' => 'LogSettings#getEntries', 'url' => '/settings/admin/log/entries', 'verb' => 'GET'], diff --git a/settings/templates/personal.php b/settings/templates/personal.php index 4fb9cefdeb0e5..3f59954b6dd29 100644 --- a/settings/templates/personal.php +++ b/settings/templates/personal.php @@ -71,6 +71,8 @@ +
@@ -82,6 +84,8 @@ +
@@ -93,8 +97,10 @@ +
t('For password recovery and notifications')); ?> +
@@ -106,6 +112,8 @@ +
@@ -117,6 +125,8 @@ +
@@ -149,13 +159,31 @@ class="password-confirm-required" -======= ->>>>>>> Add more personal information fields to the settings page for enhanced federated sharing

t('Email'); ?>

t('No email address set')); }?> +
+
+

t('Full name'));?>

+ t('No display name set')); } ?> +
+
+

t('Email')); ?>

+ t('No email address set')); }?> +
+
+

t('Phone')); ?>

+ t('No phone number set')); }?> +
+
+

t('Website')); ?>

+ t('No website set')); }?> +
+
+

t('Address')); ?>

+ t('No address set')); }?> +
- From 78f6e29954ed3c0e868d4eccdc95e4b6e8febdea Mon Sep 17 00:00:00 2001 From: Christoph Wurst Date: Thu, 21 Apr 2016 10:33:03 +0200 Subject: [PATCH 03/53] Add federation scope to the user avatar Signed-off-by: Roeland Jago Douma --- settings/css/settings.css | 11 +++++----- settings/js/federationsettingsview.js | 29 ++++++++++++++++++--------- settings/js/personal.js | 6 +++--- settings/templates/personal.php | 10 +++++++-- 4 files changed, 37 insertions(+), 19 deletions(-) diff --git a/settings/css/settings.css b/settings/css/settings.css index 4428f6130d6d2..3183355ac901d 100644 --- a/settings/css/settings.css +++ b/settings/css/settings.css @@ -7,14 +7,14 @@ input#openid, input#webdav { width:20em; } /* PERSONAL */ -#avatar { +#avatarform { width: 160px; padding-right: 0; } -#avatar .avatardiv { +#avatarform .avatardiv { margin-bottom: 10px; } -#avatar .warning { +#avatarform .warning { width: 100%; } #uploadavatarbutton, @@ -26,7 +26,7 @@ input#openid, input#webdav { width:20em; } .jcrop-holder { z-index: 500; } -#avatar #cropper { +#avatarform #cropper { float: left; z-index: 500; /* float cropper above settings page to prevent unexpected flowing from dynamically sized element */ @@ -82,6 +82,7 @@ input#openid, input#webdav { width:20em; } height: 20px; min-width: 200px; } +#avatarform > h2 span, #personal-settings-container > div h2 span[class^="icon-"] { display: inline-block; margin-left: 5px; @@ -120,7 +121,7 @@ input#openid, input#webdav { width:20em; } padding-right: 0; min-width: 60%; } -#avatar, +#avatarform, #passwordform { margin-bottom: 0; padding-bottom: 0; diff --git a/settings/js/federationsettingsview.js b/settings/js/federationsettingsview.js index 6a10d9f7f7e3a..997f4fef3f1e0 100644 --- a/settings/js/federationsettingsview.js +++ b/settings/js/federationsettingsview.js @@ -30,19 +30,26 @@ 'phone', 'email', 'website', - 'address' + 'address', + 'avatar' ]; var self = this; _.each(this._inputFields, function(field) { + var scopeOnly = field === 'avatar'; + // Initialize config model - self._config.set(field, $('#' + field).val()); + if (!scopeOnly) { + self._config.set(field, $('#' + field).val()); + } self._config.set(field + 'Scope', $('#' + field + 'scope').val()); // Set inputs whenever model values change - self.listenTo(self._config, 'change:' + field, function () { - self.$('#' + field).val(self._config.get(field)); - }); + if (!scopeOnly) { + self.listenTo(self._config, 'change:' + field, function () { + self.$('#' + field).val(self._config.get(field)); + }); + } self.listenTo(self._config, 'change:' + field + 'Scope', function () { self._onScopeChanged(field, self._config.get(field + 'Scope')); }); @@ -54,8 +61,8 @@ render: function() { var self = this; _.each(this._inputFields, function(field) { - var $heading = self.$('#' + field + 'form > h2'); - var $icon = self.$('#' + field + 'form > h2 > span'); + var $heading = self.$('#' + field + 'form h2'); + var $icon = self.$('#' + field + 'form h2 > span'); var scopeMenu = new OC.Settings.FederationScopeMenu(); self.listenTo(scopeMenu, 'select:scope', function(scope) { @@ -65,8 +72,9 @@ $icon.on('click', _.bind(scopeMenu.show, scopeMenu)); // Fix absolute position according to the heading text length - // TODO: fix position without magic numbers - var pos = ($heading.width() - $heading.find('label').width()) - 68; + // TODO: find alternative to those magic number + var diff = field === 'avatar' ? 104 : 68; + var pos = ($heading.width() - $heading.find('label').width()) - diff; scopeMenu.$el.css('right', pos); self._onScopeChanged(field, self._config.get(field + 'Scope')); @@ -76,6 +84,9 @@ _registerEvents: function() { var self = this; _.each(this._inputFields, function(field) { + if (field === 'avatar') { + return; + } self.$('#' + field).keyUpDelayedOrEnter(_.bind(self._onInputChanged, self)); }); }, diff --git a/settings/js/personal.js b/settings/js/personal.js index 7a8d43d147564..9045851ba0c12 100644 --- a/settings/js/personal.js +++ b/settings/js/personal.js @@ -192,7 +192,7 @@ function avatarResponseHandler (data) { if (typeof data === 'string') { data = JSON.parse(data); } - var $warning = $('#avatar .warning'); + var $warning = $('#avatarform .warning'); $warning.hide(); if (data.status === "success") { updateAvatar(); @@ -271,7 +271,7 @@ $(document).ready(function () { }); var federationSettingsView = new OC.Settings.FederationSettingsView({ - el: '#personal-settings-container' + el: '#personal-settings' }); federationSettingsView.render(); @@ -412,7 +412,7 @@ $(document).ready(function () { // Load the big avatar if (oc_config.enable_avatars) { - $('#avatar .avatardiv').avatar(OC.currentUser, 145); + $('#avatarform .avatardiv').avatar(OC.currentUser, 145); } // Show token views diff --git a/settings/templates/personal.php b/settings/templates/personal.php index 3f59954b6dd29..d1eebe1aacd12 100644 --- a/settings/templates/personal.php +++ b/settings/templates/personal.php @@ -32,10 +32,14 @@
+
-
-

t('Profile picture')); ?>

+ +

+ + +

@@ -54,6 +58,7 @@
t('Cancel')); ?>
t('Choose as profile picture')); ?>
+
@@ -187,6 +192,7 @@ class="password-confirm-required" +

t('Groups')); ?>

From d1233b47b0d96df364a1b5c043cabedc74d5eb01 Mon Sep 17 00:00:00 2001 From: Christoph Wurst Date: Thu, 21 Apr 2016 11:19:10 +0200 Subject: [PATCH 04/53] use PUT to update user settings Signed-off-by: Roeland Jago Douma --- settings/Controller/UsersController.php | 4 +++- settings/js/federationsettingsview.js | 5 +++-- settings/js/usersettings.js | 3 +++ settings/routes.php | 2 +- 4 files changed, 10 insertions(+), 4 deletions(-) diff --git a/settings/Controller/UsersController.php b/settings/Controller/UsersController.php index f06eabf6f9678..7529cedd196e3 100644 --- a/settings/Controller/UsersController.php +++ b/settings/Controller/UsersController.php @@ -500,6 +500,7 @@ public function destroy($id) { * @PasswordConfirmationRequired * * @param string $userId + * @param string $avatarScope * @param string $displayname * @param string $displaynameScope * @param string $phone @@ -512,7 +513,7 @@ public function destroy($id) { * @param string $addressScope * @return DataResponse */ - public function saveUserSettings($userId, + public function saveUserSettings($userId, $avatarScope, $displayname, $displaynameScope, $phone, $phoneScope, $email, $emailScope, @@ -524,6 +525,7 @@ public function saveUserSettings($userId, 'status' => 'success', 'data' => array( 'userId' => $userId, + 'avatarScope' => $avatarScope, 'displayname' => $displayname, 'displaynameScope' => 'public', // force value for test purposes 'email' => $email, diff --git a/settings/js/federationsettingsview.js b/settings/js/federationsettingsview.js index 997f4fef3f1e0..9921ff6469168 100644 --- a/settings/js/federationsettingsview.js +++ b/settings/js/federationsettingsview.js @@ -51,7 +51,7 @@ }); } self.listenTo(self._config, 'change:' + field + 'Scope', function () { - self._onScopeChanged(field, self._config.get(field + 'Scope')); + self._setFieldScopeIcon(field, self._config.get(field + 'Scope')); }); }); @@ -77,7 +77,8 @@ var pos = ($heading.width() - $heading.find('label').width()) - diff; scopeMenu.$el.css('right', pos); - self._onScopeChanged(field, self._config.get(field + 'Scope')); + // Restore initial state + self._setFieldScopeIcon(field, self._config.get(field + 'Scope')); }); }, diff --git a/settings/js/usersettings.js b/settings/js/usersettings.js index d8d089f83deb4..fcfe556b1d9a6 100644 --- a/settings/js/usersettings.js +++ b/settings/js/usersettings.js @@ -17,6 +17,9 @@ */ var UserSettings = OC.Backbone.Model.extend({ url: OC.generateUrl('/settings/users/{id}/settings', {id: OC.currentUser}), + isNew: function() { + return false; // Force PUT on .save() + }, parse: function(data) { if (_.isUndefined(data)) { return null; diff --git a/settings/routes.php b/settings/routes.php index ac4ade4f14ab4..d4286cd56ff52 100644 --- a/settings/routes.php +++ b/settings/routes.php @@ -50,7 +50,7 @@ ['name' => 'AppSettings#viewApps', 'url' => '/settings/apps', 'verb' => 'GET'], ['name' => 'AppSettings#listApps', 'url' => '/settings/apps/list', 'verb' => 'GET'], ['name' => 'SecuritySettings#trustedDomains', 'url' => '/settings/admin/security/trustedDomains', 'verb' => 'POST'], - ['name' => 'Users#saveUserSettings', 'url' => '/settings/users/{username}/settings', 'verb' => 'POST'], + ['name' => 'Users#saveUserSettings', 'url' => '/settings/users/{username}/settings', 'verb' => 'PUT'], ['name' => 'Users#stats', 'url' => '/settings/users/stats', 'verb' => 'GET'], ['name' => 'LogSettings#setLogLevel', 'url' => '/settings/admin/log/level', 'verb' => 'POST'], ['name' => 'LogSettings#getEntries', 'url' => '/settings/admin/log/entries', 'verb' => 'GET'], From 40b99734d3413ecee7c7ae1d71868c801b7c4188 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Schie=C3=9Fle?= Date: Mon, 25 Apr 2016 14:56:11 +0200 Subject: [PATCH 05/53] introduce accounts table and keep it up-to-date with the data added to the personal settings Signed-off-by: Roeland Jago Douma --- db_structure.xml | 32 ++++ lib/private/Accounts/AccountManager.php | 116 ++++++++++++++ settings/Application.php | 135 +++++++++++++++- settings/Controller/UsersController.php | 26 +++- tests/lib/accounts/AccountsManagerTest.php | 173 +++++++++++++++++++++ 5 files changed, 479 insertions(+), 3 deletions(-) create mode 100644 lib/private/Accounts/AccountManager.php create mode 100644 tests/lib/accounts/AccountsManagerTest.php diff --git a/db_structure.xml b/db_structure.xml index c7e1e072a8eff..545628a9233a7 100644 --- a/db_structure.xml +++ b/db_structure.xml @@ -2113,4 +2113,36 @@ + + + *dbprefix*accounts + + + + uid + text + + true + 64 + + + data + clob + + true + + + + uid_index + true + true + + uid + ascending + + + + +
+ diff --git a/lib/private/Accounts/AccountManager.php b/lib/private/Accounts/AccountManager.php new file mode 100644 index 0000000000000..3decac0153aac --- /dev/null +++ b/lib/private/Accounts/AccountManager.php @@ -0,0 +1,116 @@ + + * + * @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 + * + */ + + +namespace OC\Accounts; + + +use OCP\IDBConnection; + +/** + * Class AccountManager + * + * Manage system accounts table + * + * @group DB + * @package OC\Accounts + */ +class AccountManager { + + /** @var IDBConnection database connection */ + private $connection; + + /** @var string table name */ + private $table = 'accounts'; + + /** + * AccountManager constructor. + * + * @param IDBConnection $connection + */ + public function __construct(IDBConnection $connection) { + $this->connection = $connection; + } + + public function updateUser($uid, $data) { + $userData = $this->getUser($uid); + if (empty($userData)) { + $this->insertNewUser($uid, $data); + } else { + $this->updateExistingUser($uid, $data); + } + } + + /** + * get stored data from a given user + * + * @param $uid + * @return array + */ + public function getUser($uid) { + $query = $this->connection->getQueryBuilder(); + $query->select('data')->from($this->table) + ->where($query->expr()->eq('uid', $query->createParameter('uid'))) + ->setParameter('uid', $uid); + $query->execute(); + $result = $query->execute()->fetchAll(); + + if (empty($result)) { + return []; + } + + return json_decode($result[0]['data'], true); + } + + /** + * add new user to accounts table + * + * @param string $uid + * @param array $data + */ + protected function insertNewUser($uid, $data) { + $jsonEncodedData = json_encode($data); + $query = $this->connection->getQueryBuilder(); + $query->insert($this->table) + ->values( + [ + 'uid' => $query->createNamedParameter($uid), + 'data' => $query->createNamedParameter($jsonEncodedData), + ] + ) + ->execute(); + } + + /** + * update existing user in accounts table + * + * @param string $uid + * @param array $data + */ + protected function updateExistingUser($uid, $data) { + $jsonEncodedData = json_encode($data); + $query = $this->connection->getQueryBuilder(); + $query->update($this->table) + ->set('data', $query->createNamedParameter($jsonEncodedData)) + ->where($query->expr()->eq('uid', $query->createNamedParameter($uid))) + ->execute(); + } +} diff --git a/settings/Application.php b/settings/Application.php index d907cd666fb76..d0eab02cdd3b9 100644 --- a/settings/Application.php +++ b/settings/Application.php @@ -35,8 +35,21 @@ use OC\AppFramework\Utility\TimeFactory; use OC\Authentication\Token\IProvider; use OC\Server; +use OC\Files\View; +use OC\Server; +use OC\Settings\Controller\AppSettingsController; +use OC\Settings\Controller\AuthSettingsController; +use OC\Settings\Controller\CertificateController; +use OC\Settings\Controller\CheckSetupController; +use OC\Settings\Controller\EncryptionController; +use OC\Settings\Controller\GroupsController; +use OC\Settings\Controller\LogSettingsController; +use OC\Settings\Controller\MailSettingsController; +use OC\Settings\Controller\SecuritySettingsController; +use OC\Settings\Controller\UsersController; use OC\Settings\Middleware\SubadminMiddleware; use OCP\AppFramework\App; +use OCP\AppFramework\IAppContainer; use OCP\IContainer; use OCP\Settings\IManager; use OCP\Util; @@ -57,7 +70,127 @@ public function __construct(array $urlParams=[]){ // Register Middleware $container->registerAlias('SubadminMiddleware', SubadminMiddleware::class); - $container->registerMiddleWare('SubadminMiddleware'); + /** + * Controllers + */ + $container->registerService('MailSettingsController', function(IContainer $c) { + return new MailSettingsController( + $c->query('AppName'), + $c->query('Request'), + $c->query('L10N'), + $c->query('Config'), + $c->query('UserSession'), + $c->query('Defaults'), + $c->query('Mailer'), + $c->query('DefaultMailAddress') + ); + }); + $container->registerService('EncryptionController', function(IContainer $c) { + return new EncryptionController( + $c->query('AppName'), + $c->query('Request'), + $c->query('L10N'), + $c->query('Config'), + $c->query('DatabaseConnection'), + $c->query('UserManager'), + new View(), + $c->query('Logger') + ); + }); + $container->registerService('AppSettingsController', function(IContainer $c) { + return new AppSettingsController( + $c->query('AppName'), + $c->query('Request'), + $c->query('L10N'), + $c->query('Config'), + $c->query('ICacheFactory'), + $c->query('INavigationManager'), + $c->query('IAppManager'), + $c->query('OcsClient') + ); + }); + $container->registerService('AuthSettingsController', function(IContainer $c) { + return new AuthSettingsController( + $c->query('AppName'), + $c->query('Request'), + $c->query('ServerContainer')->query('OC\Authentication\Token\IProvider'), + $c->query('UserManager'), + $c->query('ServerContainer')->getSession(), + $c->query('ServerContainer')->getSecureRandom(), + $c->query('UserId') + ); + }); + $container->registerService('SecuritySettingsController', function(IContainer $c) { + return new SecuritySettingsController( + $c->query('AppName'), + $c->query('Request'), + $c->query('Config') + ); + }); + $container->registerService('AccountManager', function(IAppContainer $c) { + return new AccountManager($c->getServer()->getDatabaseConnection()); + }); + $container->registerService('CertificateController', function(IContainer $c) { + return new CertificateController( + $c->query('AppName'), + $c->query('Request'), + $c->query('CertificateManager'), + $c->query('SystemCertificateManager'), + $c->query('L10N'), + $c->query('IAppManager') + ); + }); + $container->registerService('GroupsController', function(IContainer $c) { + return new GroupsController( + $c->query('AppName'), + $c->query('Request'), + $c->query('GroupManager'), + $c->query('UserSession'), + $c->query('IsAdmin'), + $c->query('L10N') + ); + }); + $container->registerService('UsersController', function(IContainer $c) { + return new UsersController( + $c->query('AppName'), + $c->query('Request'), + $c->query('UserManager'), + $c->query('GroupManager'), + $c->query('UserSession'), + $c->query('Config'), + $c->query('IsAdmin'), + $c->query('L10N'), + $c->query('Logger'), + $c->query('Defaults'), + $c->query('Mailer'), + $c->query('DefaultMailAddress'), + $c->query('URLGenerator'), + $c->query('OCP\\App\\IAppManager'), + $c->query('OCP\\IAvatarManager'), + $c->query('AccountManager') + ); + }); + $container->registerService('LogSettingsController', function(IContainer $c) { + return new LogSettingsController( + $c->query('AppName'), + $c->query('Request'), + $c->query('Config'), + $c->query('L10N') + ); + }); + $container->registerService('CheckSetupController', function(IContainer $c) { + return new CheckSetupController( + $c->query('AppName'), + $c->query('Request'), + $c->query('Config'), + $c->query('ClientService'), + $c->query('URLGenerator'), + $c->query('Util'), + $c->query('L10N'), + $c->query('Checker') + ); + }); + /** * Core class wrappers diff --git a/settings/Controller/UsersController.php b/settings/Controller/UsersController.php index 7529cedd196e3..353442343307c 100644 --- a/settings/Controller/UsersController.php +++ b/settings/Controller/UsersController.php @@ -30,6 +30,7 @@ namespace OC\Settings\Controller; +use OC\Accounts\AccountManager; use OC\AppFramework\Http; use OC\User\User; use OCP\App\IAppManager; @@ -80,6 +81,8 @@ class UsersController extends Controller { private $isRestoreEnabled = false; /** @var IAvatarManager */ private $avatarManager; + /** @var AccountManager */ + private $accountManager; /** * @param string $appName @@ -97,6 +100,7 @@ class UsersController extends Controller { * @param IURLGenerator $urlGenerator * @param IAppManager $appManager * @param IAvatarManager $avatarManager + * @param AccountManager $accountManager */ public function __construct($appName, IRequest $request, @@ -112,7 +116,9 @@ public function __construct($appName, $fromMailAddress, IURLGenerator $urlGenerator, IAppManager $appManager, - IAvatarManager $avatarManager) { + IAvatarManager $avatarManager, + AccountManager $accountManager +) { parent::__construct($appName, $request); $this->userManager = $userManager; $this->groupManager = $groupManager; @@ -126,6 +132,7 @@ public function __construct($appName, $this->fromMailAddress = $fromMailAddress; $this->urlGenerator = $urlGenerator; $this->avatarManager = $avatarManager; + $this->accountManager = $accountManager; // check for encryption state - TODO see formatUserForIndex $this->isEncryptionAppEnabled = $appManager->isEnabledForUser('encryption'); @@ -519,7 +526,22 @@ public function saveUserSettings($userId, $avatarScope, $email, $emailScope, $website, $websiteScope, $address, $addressScope) { - // TODO: implement + + if($userId === null) { + $userId = $this->userSession->getUser()->getUID(); + } + + $data = [ + 'avatar' => ['scope' => $avatarScope], + 'displayName' => ['value' => $displayname, 'scope' => $displaynameScope], + 'email' => ['value' => $email, 'scope' => $emailScope], + 'website' => ['value' => $website, 'scope' => $websiteScope], + 'address' => ['value' => $address, 'scope' => $addressScope], + 'phone' => ['value' => $phone, 'scope' => $phoneScope] + ]; + + $this->accountManager->updateUser($userId, $data); + return new DataResponse( array( 'status' => 'success', diff --git a/tests/lib/accounts/AccountsManagerTest.php b/tests/lib/accounts/AccountsManagerTest.php new file mode 100644 index 0000000000000..1e9464bceb749 --- /dev/null +++ b/tests/lib/accounts/AccountsManagerTest.php @@ -0,0 +1,173 @@ + + * + * @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 + * + */ + + +namespace Test\Accounts; + + +use OC\Accounts\AccountManager; +use OC\Mail\Mailer; +use Test\TestCase; + +/** + * Class AccountsManagerTest + * + * @group DB + * @package Test\Accounts + */ +class AccountsManagerTest extends TestCase { + + /** @var \OCP\IDBConnection */ + private $connection; + + /** @var string accounts table name */ + private $table = 'accounts'; + + public function setUp() { + parent::setUp(); + $this->connection = \OC::$server->getDatabaseConnection(); + } + + public function tearDown() { + parent::tearDown(); + $query = $this->connection->getQueryBuilder(); + $query->delete($this->table)->execute(); + } + + /** + * get a instance of the accountManager + * + * @param array $mockedMethods list of methods which should be mocked + * @return \PHPUnit_Framework_MockObject_MockObject | AccountManager + */ + public function getInstance($mockedMethods = null) { + return $this->getMockBuilder('OC\Accounts\AccountManager') + ->setConstructorArgs([$this->connection]) + ->setMethods($mockedMethods) + ->getMock(); + + } + + /** + * @dataProvider dataTrueFalse + * + * @param bool $userAlreadyExists + */ + public function testUpdateUser($userAlreadyExists) { + $accountManager = $this->getInstance(['getUser', 'insertNewUser', 'updateExistingUser']); + + $accountManager->expects($this->once())->method('getUser')->willReturn($userAlreadyExists); + + if ($userAlreadyExists) { + $accountManager->expects($this->once())->method('updateExistingUser') + ->with('uid', 'data'); + $accountManager->expects($this->never())->method('insertNewUser'); + } else { + $accountManager->expects($this->once())->method('insertNewUser') + ->with('uid', 'data'); + $accountManager->expects($this->never())->method('updateExistingUser'); + } + + $accountManager->updateUser('uid', 'data'); + } + + public function dataTrueFalse() { + return [ + [true], + [false] + ]; + } + + + /** + * @dataProvider dataTestGetUser + * + * @param string $setUser + * @param array $setData + * @param string $askUser + * @param array $expectedData + */ + public function testGetUser($setUser, $setData, $askUser, $expectedData) { + $accountManager = $this->getInstance(); + $this->addDummyValuesToTable($setUser, $setData); + $this->assertEquals($expectedData, + $accountManager->getUser($askUser) + ); + } + + public function dataTestGetUser() { + return [ + ['user1', ['key' => 'value'], 'user1', ['key' => 'value']], + ['user1', ['key' => 'value'], 'user2', []], + ]; + } + + public function testUpdateExistingUser() { + $user = 'user1'; + $oldData = ['key' => 'value']; + $newData = ['newKey' => 'newValue']; + + $accountManager = $this->getInstance(); + $this->addDummyValuesToTable($user, $oldData); + $this->invokePrivate($accountManager, 'updateExistingUser', [$user, $newData]); + $newDataFromTable = $this->getDataFromTable($user); + $this->assertEquals($newData, $newDataFromTable); + } + + public function testInsertNewUser() { + $user = 'user1'; + $data = ['key' => 'value']; + + $accountManager = $this->getInstance(); + $this->assertNull($this->getDataFromTable($user)); + $this->invokePrivate($accountManager, 'insertNewUser', [$user, $data]); + + $dataFromDb = $this->getDataFromTable($user); + $this->assertEquals($data, $dataFromDb); + } + + private function addDummyValuesToTable($uid, $data) { + + $query = $this->connection->getQueryBuilder(); + $query->insert($this->table) + ->values( + [ + 'uid' => $query->createNamedParameter($uid), + 'data' => $query->createNamedParameter(json_encode($data)), + ] + ) + ->execute(); + } + + private function getDataFromTable($uid) { + $query = $this->connection->getQueryBuilder(); + $query->select('data')->from($this->table) + ->where($query->expr()->eq('uid', $query->createParameter('uid'))) + ->setParameter('uid', $uid); + $query->execute(); + $result = $query->execute()->fetchAll(); + + if (!empty($result)) { + return json_decode($result[0]['data'], true); + } + } + +} From 445a254c1e84444134b90b4c874cbf32119e2df1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Schie=C3=9Fle?= Date: Mon, 25 Apr 2016 16:07:00 +0200 Subject: [PATCH 06/53] display stored user data Signed-off-by: Roeland Jago Douma --- settings/personal.php | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/settings/personal.php b/settings/personal.php index a18a88a9b6382..87e1af36c940d 100644 --- a/settings/personal.php +++ b/settings/personal.php @@ -40,6 +40,7 @@ $defaults = \OC::$server->getThemingDefaults(); $certificateManager = \OC::$server->getCertificateManager(); +$accountManager = new \OC\Accounts\AccountManager(\OC::$server->getDatabaseConnection()); $config = \OC::$server->getConfig(); $urlGenerator = \OC::$server->getURLGenerator(); @@ -155,6 +156,8 @@ } else { $totalSpace = OC_Helper::humanFileSize($storageInfo['total']); } +$userData = $accountManager->getUser($user->getUID()); + $tmpl->assign('total_space', $totalSpace); $tmpl->assign('usage_relative', $storageInfo['relative']); $tmpl->assign('clients', $clients); @@ -165,18 +168,18 @@ $tmpl->assign('passwordChangeSupported', OC_User::canUserChangePassword(OC_User::getUser())); $tmpl->assign('displayNameChangeSupported', OC_User::canUserChangeDisplayName(OC_User::getUser())); $tmpl->assign('displayName', OC_User::getDisplayName()); -// TODO: insert real data -$tmpl->assign('phone', '+43 660 56565 5446'); -$tmpl->assign('website', 'owncloud.org'); -$tmpl->assign('address', 'Stuttgart'); - -$tmpl->assign('avatarScope', 'contacts'); -$tmpl->assign('displayNameScope', 'public'); -$tmpl->assign('phoneScope', 'contacts'); -$tmpl->assign('emailScope', 'contacts'); -$tmpl->assign('websiteScope', 'public'); -$tmpl->assign('addressScope', 'private'); -// END TODO + +$tmpl->assign('phone', isset($userData['phone']['value']) ? $userData['phone']['value'] : null); +$tmpl->assign('website', isset($userData['website']['value']) ? $userData['website']['value'] : null); +$tmpl->assign('address', isset($userData['address']['value']) ? $userData['address']['value'] : null); + +$tmpl->assign('avatarScope', isset($userData['avatar']['scope']) ? $userData['avatar']['scope'] : 'contacts'); +$tmpl->assign('displayNameScope', isset($userData['displayName']['scope']) ? $userData['displayName']['scope'] : 'contacts'); +$tmpl->assign('phoneScope', isset($userData['phone']['scope']) ? $userData['phone']['scope'] : 'private'); +$tmpl->assign('emailScope', isset($userData['email']['scope']) ? $userData['email']['scope'] : 'private'); +$tmpl->assign('websiteScope', isset($userData['website']['scope']) ? $userData['website']['scope'] : 'private'); +$tmpl->assign('addressScope', isset($userData['address']['scope']) ? $userData['address']['scope'] : 'private'); + $tmpl->assign('enableAvatars', $config->getSystemValue('enable_avatars', true) === true); $tmpl->assign('avatarChangeSupported', OC_User::canUserChangeAvatar(OC_User::getUser())); $tmpl->assign('certs', $certificateManager->listCertificates()); From 1700e3dcccc4edbb48f583b055ba3fed539a8b42 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Schie=C3=9Fle?= Date: Mon, 25 Apr 2016 16:30:06 +0200 Subject: [PATCH 07/53] allow multiple values for phone, website, address and email Signed-off-by: Roeland Jago Douma --- settings/Controller/UsersController.php | 8 ++++---- settings/personal.php | 14 +++++++------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/settings/Controller/UsersController.php b/settings/Controller/UsersController.php index 353442343307c..6791475ce5277 100644 --- a/settings/Controller/UsersController.php +++ b/settings/Controller/UsersController.php @@ -534,10 +534,10 @@ public function saveUserSettings($userId, $avatarScope, $data = [ 'avatar' => ['scope' => $avatarScope], 'displayName' => ['value' => $displayname, 'scope' => $displaynameScope], - 'email' => ['value' => $email, 'scope' => $emailScope], - 'website' => ['value' => $website, 'scope' => $websiteScope], - 'address' => ['value' => $address, 'scope' => $addressScope], - 'phone' => ['value' => $phone, 'scope' => $phoneScope] + 'email' => [['value' => $email, 'scope' => $emailScope]], + 'website' => [['value' => $website, 'scope' => $websiteScope]], + 'address' => [['value' => $address, 'scope' => $addressScope]], + 'phone' => [['value' => $phone, 'scope' => $phoneScope]] ]; $this->accountManager->updateUser($userId, $data); diff --git a/settings/personal.php b/settings/personal.php index 87e1af36c940d..9475b674b7503 100644 --- a/settings/personal.php +++ b/settings/personal.php @@ -169,16 +169,16 @@ $tmpl->assign('displayNameChangeSupported', OC_User::canUserChangeDisplayName(OC_User::getUser())); $tmpl->assign('displayName', OC_User::getDisplayName()); -$tmpl->assign('phone', isset($userData['phone']['value']) ? $userData['phone']['value'] : null); -$tmpl->assign('website', isset($userData['website']['value']) ? $userData['website']['value'] : null); -$tmpl->assign('address', isset($userData['address']['value']) ? $userData['address']['value'] : null); +$tmpl->assign('phone', isset($userData['phone'][0]['value']) ? $userData['phone'][0]['value'] : null); +$tmpl->assign('website', isset($userData['website'][0]['value']) ? $userData['website'][0]['value'] : null); +$tmpl->assign('address', isset($userData['address'][0]['value']) ? $userData['address'][0]['value'] : null); $tmpl->assign('avatarScope', isset($userData['avatar']['scope']) ? $userData['avatar']['scope'] : 'contacts'); $tmpl->assign('displayNameScope', isset($userData['displayName']['scope']) ? $userData['displayName']['scope'] : 'contacts'); -$tmpl->assign('phoneScope', isset($userData['phone']['scope']) ? $userData['phone']['scope'] : 'private'); -$tmpl->assign('emailScope', isset($userData['email']['scope']) ? $userData['email']['scope'] : 'private'); -$tmpl->assign('websiteScope', isset($userData['website']['scope']) ? $userData['website']['scope'] : 'private'); -$tmpl->assign('addressScope', isset($userData['address']['scope']) ? $userData['address']['scope'] : 'private'); +$tmpl->assign('phoneScope', isset($userData['phone'][0]['scope']) ? $userData['phone'][0]['scope'] : 'private'); +$tmpl->assign('emailScope', isset($userData['email'][0]['scope']) ? $userData['email'][0]['scope'] : 'private'); +$tmpl->assign('websiteScope', isset($userData['website'][0]['scope']) ? $userData['website'][0]['scope'] : 'private'); +$tmpl->assign('addressScope', isset($userData['address'][0]['scope']) ? $userData['address'][0]['scope'] : 'private'); $tmpl->assign('enableAvatars', $config->getSystemValue('enable_avatars', true) === true); $tmpl->assign('avatarChangeSupported', OC_User::canUserChangeAvatar(OC_User::getUser())); From de1f3f05fdb47b4cde9b8cde468d54cdc856ef08 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Schie=C3=9Fle?= Date: Tue, 26 Apr 2016 16:19:10 +0200 Subject: [PATCH 08/53] allow to change display names in the user settings again keep display name and email address in sync with the accounts table Signed-off-by: Roeland Jago Douma --- settings/Controller/UsersController.php | 193 ++++++++---------- settings/routes.php | 3 +- .../Controller/UsersControllerTest.php | 1 + 3 files changed, 87 insertions(+), 110 deletions(-) diff --git a/settings/Controller/UsersController.php b/settings/Controller/UsersController.php index 6791475ce5277..6bbf878652078 100644 --- a/settings/Controller/UsersController.php +++ b/settings/Controller/UsersController.php @@ -32,6 +32,7 @@ use OC\Accounts\AccountManager; use OC\AppFramework\Http; +use OC\ForbiddenException; use OC\User\User; use OCP\App\IAppManager; use OCP\AppFramework\Controller; @@ -48,6 +49,7 @@ use OCP\IUserSession; use OCP\Mail\IMailer; use OCP\IAvatarManager; +use Punic\Exception; /** * @package OC\Settings\Controller @@ -61,7 +63,7 @@ class UsersController extends Controller { private $isAdmin; /** @var IUserManager */ private $userManager; - /** @var IGroupManager */ + /** @var \OC\Group\Manager */ private $groupManager; /** @var IConfig */ private $config; @@ -506,7 +508,6 @@ public function destroy($id) { * @NoSubadminRequired * @PasswordConfirmationRequired * - * @param string $userId * @param string $avatarScope * @param string $displayname * @param string $displaynameScope @@ -520,17 +521,29 @@ public function destroy($id) { * @param string $addressScope * @return DataResponse */ - public function saveUserSettings($userId, $avatarScope, - $displayname, $displaynameScope, - $phone, $phoneScope, - $email, $emailScope, - $website, $websiteScope, - $address, $addressScope) { - - if($userId === null) { - $userId = $this->userSession->getUser()->getUID(); + public function setUserSettings($avatarScope, + $displayname, $displaynameScope, + $phone, $phoneScope, + $email, $emailScope, + $website, $websiteScope, + $address, $addressScope + ) { + + + if(!$this->mailer->validateMailAddress($email)) { + return new DataResponse( + array( + 'status' => 'error', + 'data' => array( + 'message' => (string)$this->l10n->t('Invalid mail address') + ) + ), + Http::STATUS_UNPROCESSABLE_ENTITY + ); } + $userId = $this->userSession->getUser()->getUID(); + $data = [ 'avatar' => ['scope' => $avatarScope], 'displayName' => ['value' => $displayname, 'scope' => $displaynameScope], @@ -542,106 +555,66 @@ public function saveUserSettings($userId, $avatarScope, $this->accountManager->updateUser($userId, $data); - return new DataResponse( - array( - 'status' => 'success', - 'data' => array( - 'userId' => $userId, - 'avatarScope' => $avatarScope, - 'displayname' => $displayname, - 'displaynameScope' => 'public', // force value for test purposes - 'email' => $email, - 'emailScope' => $emailScope, - 'website' => $website, - 'websiteScope' => $websiteScope, - 'address' => $address, - 'addressScope' => $addressScope, - 'message' => (string)$this->l10n->t('Settings saved') - ) - ), - Http::STATUS_OK - ); - } - - /** - * Set the mail address of a user - * - * @todo Merge into saveUserSettings - * - * @param string $id - * @param string $mailAddress - * @return DataResponse - */ - private function setMailAddress($id, $mailAddress) { - $userId = $this->userSession->getUser()->getUID(); - $user = $this->userManager->get($id); - - if($userId !== $id - && !$this->isAdmin - && !$this->groupManager->getSubAdmin()->isUserAccessible($this->userSession->getUser(), $user)) { + try { + $this->saveUserSettings($userId, $data); return new DataResponse( array( - 'status' => 'error', + 'status' => 'success', 'data' => array( - 'message' => (string)$this->l10n->t('Forbidden') + 'userId' => $userId, + 'avatarScope' => $avatarScope, + 'displayname' => $displayname, + 'displaynameScope' => $displaynameScope, + 'email' => $email, + 'emailScope' => $emailScope, + 'website' => $website, + 'websiteScope' => $websiteScope, + 'address' => $address, + 'addressScope' => $addressScope, + 'message' => (string)$this->l10n->t('Settings saved') ) ), - Http::STATUS_FORBIDDEN + Http::STATUS_OK ); + } catch (ForbiddenException $e) { + return new DataResponse([ + 'status' => 'error', + 'data' => [ + 'message' => $e->getMessage() + ], + ]); } - if($mailAddress !== '' && !$this->mailer->validateMailAddress($mailAddress)) { - return new DataResponse( - array( - 'status' => 'error', - 'data' => array( - 'message' => (string)$this->l10n->t('Invalid mail address') - ) - ), - Http::STATUS_UNPROCESSABLE_ENTITY - ); - } + } - if(!$user){ - return new DataResponse( - array( - 'status' => 'error', - 'data' => array( - 'message' => (string)$this->l10n->t('Invalid user') - ) - ), - Http::STATUS_UNPROCESSABLE_ENTITY - ); - } - // this is the only permission a backend provides and is also used - // for the permission of setting a email address - if(!$user->canChangeDisplayName()){ - return new DataResponse( - array( - 'status' => 'error', - 'data' => array( - 'message' => (string)$this->l10n->t('Unable to change mail address') - ) - ), - Http::STATUS_FORBIDDEN - ); + /** + * update account manager with new user data + * + * @param string $userId + * @param array $data + * @throws ForbiddenException + */ + private function saveUserSettings($userId, $data) { + $user = $this->userManager->get($userId); + + // keep the user back-end up-to-date with the latest display name and email + // address + if (isset($data['displayName']['value']) && $user->getDisplayName() !== $data['displayName']['value']) { + $result = $user->setDisplayName($data['displayName']['value']); + if ($result === false) { + throw new ForbiddenException($this->l10n->t('Unable to change full name')); + } } - // delete user value if email address is empty - $user->setEMailAddress($mailAddress); + if (isset($data['email'][0]['value']) && $user->getEMailAddress() !== $data['email'][0]['value']) { + $result = $user->setEMailAddress($data['email'][0]['value']); + if ($result === false) { + throw new ForbiddenException($this->l10n->t('Unable to change mail address')); + } + } - return new DataResponse( - array( - 'status' => 'success', - 'data' => array( - 'username' => $id, - 'mailAddress' => $mailAddress, - 'message' => (string)$this->l10n->t('Email saved') - ) - ), - Http::STATUS_OK - ); + $this->accountManager->updateUser($userId, $data); } /** @@ -694,13 +667,8 @@ public function stats() { * @param string $displayName * @return DataResponse */ - private function setDisplayName($username, $displayName) { + public function setDisplayName($username, $displayName) { $currentUser = $this->userSession->getUser(); - - if ($username === null) { - $username = $currentUser->getUID(); - } - $user = $this->userManager->get($username); if ($user === null || @@ -708,8 +676,10 @@ private function setDisplayName($username, $displayName) { ( !$this->groupManager->isAdmin($currentUser->getUID()) && !$this->groupManager->getSubAdmin()->isUserAccessible($currentUser, $user) && - $currentUser !== $user) - ) { + $currentUser->getUID() !== $username + + ) + ) { return new DataResponse([ 'status' => 'error', 'data' => [ @@ -718,7 +688,12 @@ private function setDisplayName($username, $displayName) { ]); } - if ($user->setDisplayName($displayName)) { + $userData = $this->accountManager->getUser($user->getUID()); + $userData['displayName']['value'] = $displayName; + + + try { + $this->saveUserSettings($username, $userData); return new DataResponse([ 'status' => 'success', 'data' => [ @@ -727,11 +702,11 @@ private function setDisplayName($username, $displayName) { 'displayName' => $displayName, ], ]); - } else { + } catch (ForbiddenException $e) { return new DataResponse([ 'status' => 'error', 'data' => [ - 'message' => $this->l10n->t('Unable to change full name'), + 'message' => $e->getMessage(), 'displayName' => $user->getDisplayName(), ], ]); diff --git a/settings/routes.php b/settings/routes.php index d4286cd56ff52..62cfc398fdc99 100644 --- a/settings/routes.php +++ b/settings/routes.php @@ -50,7 +50,8 @@ ['name' => 'AppSettings#viewApps', 'url' => '/settings/apps', 'verb' => 'GET'], ['name' => 'AppSettings#listApps', 'url' => '/settings/apps/list', 'verb' => 'GET'], ['name' => 'SecuritySettings#trustedDomains', 'url' => '/settings/admin/security/trustedDomains', 'verb' => 'POST'], - ['name' => 'Users#saveUserSettings', 'url' => '/settings/users/{username}/settings', 'verb' => 'PUT'], + ['name' => 'Users#setDisplayName', 'url' => '/settings/users/{username}/displayName', 'verb' => 'POST'], + ['name' => 'Users#setUserSettings', 'url' => '/settings/users/{username}/settings', 'verb' => 'PUT'], ['name' => 'Users#stats', 'url' => '/settings/users/stats', 'verb' => 'GET'], ['name' => 'LogSettings#setLogLevel', 'url' => '/settings/admin/log/level', 'verb' => 'POST'], ['name' => 'LogSettings#getEntries', 'url' => '/settings/admin/log/entries', 'verb' => 'GET'], diff --git a/tests/Settings/Controller/UsersControllerTest.php b/tests/Settings/Controller/UsersControllerTest.php index 03c3a2e2ab464..e2b13b24b8cad 100644 --- a/tests/Settings/Controller/UsersControllerTest.php +++ b/tests/Settings/Controller/UsersControllerTest.php @@ -2040,6 +2040,7 @@ public function testSetDisplayNameFails() { ->expects($this->once()) ->method('getUser') ->willReturn($user); + $this->userManager ->expects($this->once()) ->method('get') From 51b5f2754d493c926f7474d3f991e39d0c42f68c Mon Sep 17 00:00:00 2001 From: Christoph Wurst Date: Mon, 31 Oct 2016 10:59:24 +0100 Subject: [PATCH 09/53] increase version to trigger db migration Signed-off-by: Christoph Wurst --- version.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/version.php b/version.php index d556386a848a0..0eb9f3de72a04 100644 --- a/version.php +++ b/version.php @@ -25,7 +25,8 @@ // 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(11, 0, 0, 1); + +$OC_Version = array(11, 0, 0, 2); // The human readable string $OC_VersionString = '11.0 alpha'; From 9c7533ff6b5d8c38f5b1aeb9bc91504a0934df56 Mon Sep 17 00:00:00 2001 From: Christoph Wurst Date: Mon, 31 Oct 2016 11:53:37 +0100 Subject: [PATCH 10/53] fix profile picture fed sharing scope menu Signed-off-by: Christoph Wurst --- settings/css/settings.css | 8 ++++++-- settings/js/federationsettingsview.js | 12 ++++++------ settings/templates/personal.php | 2 +- 3 files changed, 13 insertions(+), 9 deletions(-) diff --git a/settings/css/settings.css b/settings/css/settings.css index 3183355ac901d..887d2beb7b84d 100644 --- a/settings/css/settings.css +++ b/settings/css/settings.css @@ -83,7 +83,11 @@ input#openid, input#webdav { width:20em; } min-width: 200px; } #avatarform > h2 span, -#personal-settings-container > div h2 span[class^="icon-"] { +#personal-settings-container > div h2 { + position: relative; +} +#personal-settings-container > div h2 span[class^="icon-"], +#personal-settings-avatar-container h2 span[class^="icon-"] { display: inline-block; margin-left: 5px; background-size: 110%; @@ -102,7 +106,7 @@ input#openid, input#webdav { width:20em; } pointer-events: none; } .federationScopeMenu { - top: 66px; + top: 44px; } .federationScopeMenu.bubble::after { left: 45px; diff --git a/settings/js/federationsettingsview.js b/settings/js/federationsettingsview.js index 9921ff6469168..73b9f85755899 100644 --- a/settings/js/federationsettingsview.js +++ b/settings/js/federationsettingsview.js @@ -1,4 +1,4 @@ -/* global OC, result */ +/* global OC, result, _ */ /** * Copyright (c) 2016, Christoph Wurst @@ -7,7 +7,7 @@ * See the COPYING-README file. */ -(function() { +(function(_, $, OC) { 'use strict'; var FederationSettingsView = OC.Backbone.View.extend({ @@ -46,11 +46,11 @@ // Set inputs whenever model values change if (!scopeOnly) { - self.listenTo(self._config, 'change:' + field, function () { + self.listenTo(self._config, 'change:' + field, function() { self.$('#' + field).val(self._config.get(field)); }); } - self.listenTo(self._config, 'change:' + field + 'Scope', function () { + self.listenTo(self._config, 'change:' + field + 'Scope', function() { self._setFieldScopeIcon(field, self._config.get(field + 'Scope')); }); }); @@ -118,7 +118,7 @@ // update displayName on the top right expand button $('#expandDisplayName').text(displayName); // update avatar if avatar is available - if(!$('#removeavatar').hasClass('hidden')) { + if (!$('#removeavatar').hasClass('hidden')) { updateAvatar(); } }, @@ -159,4 +159,4 @@ OC.Settings = OC.Settings || {}; OC.Settings.FederationSettingsView = FederationSettingsView; -})(); \ No newline at end of file +})(_, $, OC); \ No newline at end of file diff --git a/settings/templates/personal.php b/settings/templates/personal.php index d1eebe1aacd12..2db0cfa488290 100644 --- a/settings/templates/personal.php +++ b/settings/templates/personal.php @@ -38,7 +38,7 @@

- +

From 77c034fc6966209278b0e696827006edafd4d64c Mon Sep 17 00:00:00 2001 From: Christoph Wurst Date: Mon, 7 Nov 2016 09:30:37 +0100 Subject: [PATCH 11/53] remove double import --- settings/Application.php | 1 - 1 file changed, 1 deletion(-) diff --git a/settings/Application.php b/settings/Application.php index d0eab02cdd3b9..2f9b786d4388b 100644 --- a/settings/Application.php +++ b/settings/Application.php @@ -34,7 +34,6 @@ use OC\App\AppStore\Fetcher\CategoryFetcher; use OC\AppFramework\Utility\TimeFactory; use OC\Authentication\Token\IProvider; -use OC\Server; use OC\Files\View; use OC\Server; use OC\Settings\Controller\AppSettingsController; From 91774208b162ac83ffee604efe4de168ea7cf457 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?John=20Molakvo=C3=A6=20=28skjnldsv=29?= Date: Mon, 7 Nov 2016 15:54:10 +0100 Subject: [PATCH 12/53] Fix popover layout MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: John Molakvoæ (skjnldsv) --- core/css/apps.css | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/core/css/apps.css b/core/css/apps.css index 852879aee89f6..87ec86d701d67 100644 --- a/core/css/apps.css +++ b/core/css/apps.css @@ -676,11 +676,12 @@ em { } .popovermenu .menuitem { - display: block; + display: flex; line-height: 30px; padding-left: 5px; color: #000; padding: 0; + align-items: center; } .popovermenu .menuitem .icon, From b75ef0f8b3dea718b52b0dedcac16b504374b7cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?John=20Molakvo=C3=A6=20=28skjnldsv=29?= Date: Mon, 7 Nov 2016 17:53:20 +0100 Subject: [PATCH 13/53] Fix popover layout fix for files and global MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: John Molakvoæ (skjnldsv) --- apps/files/css/files.css | 20 ++------------------ core/css/apps.css | 18 ++++-------------- settings/css/settings.css | 3 ++- 3 files changed, 8 insertions(+), 33 deletions(-) diff --git a/apps/files/css/files.css b/apps/files/css/files.css index 9b844919c4e8d..c0460ff6058b6 100644 --- a/apps/files/css/files.css +++ b/apps/files/css/files.css @@ -550,7 +550,7 @@ html.ie8 #fileList tr.selected td.filename>.selectCheckBox { } .bubble:after, #app-navigation .app-navigation-entry-menu:after { - right: 6px; + right: 12px; } .bubble:before, #app-navigation .app-navigation-entry-menu:before { @@ -625,13 +625,6 @@ html.ie8 .column-mtime .selectedActions { padding-right: 14px; } -#fileList .popovermenu { - margin-right: 6px; -} -.ie8 #fileList .popovermenu { - margin-top: -10px; -} - .ie8 #fileList a.action img, #fileList tr:hover a.action, #fileList a.action.permanent, @@ -649,7 +642,6 @@ html.ie8 .column-mtime .selectedActions { -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=30)"; filter: alpha(opacity=30); opacity: .3; - display:inline; } .ie8 #fileList a.action:hover img, #fileList tr a.action.disabled.action-download, @@ -673,6 +665,7 @@ html.ie8 .column-mtime .selectedActions { -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=70)" !important; filter: alpha(opacity=70) !important; opacity: .7 !important; + display:inline; } /* always show actions on mobile, not only on hover */ #fileList a.action.action-menu.permanent { @@ -775,11 +768,6 @@ table.dragshadow td.size { padding: initial; } -#fileList .popovermenu a.action { - padding: 10px; - margin: -10px; -} - html.ie8 #controls .button.new { padding-right: 0; } @@ -826,11 +814,7 @@ html.ie8 #controls .button.new { } #fileList .popovermenu .action { - display: block; - line-height: 30px; - padding-left: 5px; color: #000; - padding: 0; } #filestable .filename .action .icon, diff --git a/core/css/apps.css b/core/css/apps.css index 87ec86d701d67..ee640d5464188 100644 --- a/core/css/apps.css +++ b/core/css/apps.css @@ -289,7 +289,7 @@ border-radius: 3px; border-top-right-radius: 0; z-index: 110; - margin: -5px 14px 5px 10px; + margin-top: -5px; right: 0; -webkit-filter: drop-shadow(0 0 5px rgba(150, 150, 150, 0.75)); -moz-filter: drop-shadow(0 0 5px rgba(150, 150, 150, 0.75)); @@ -652,14 +652,6 @@ em { opacity: 1; } -.popovermenu { - padding: 4px 12px; -} - -.popovermenu li { - padding: 5px 0; -} - .popovermenu .menuitem img { padding: initial; } @@ -667,8 +659,8 @@ em { .popovermenu a.menuitem, .popovermenu label.menuitem, .popovermenu .menuitem { - padding: 10px; - margin: -10px; + padding: 10px !important; + width: auto; } .popovermenu.hidden { @@ -676,11 +668,9 @@ em { } .popovermenu .menuitem { - display: flex; + display: flex !important; line-height: 30px; - padding-left: 5px; color: #000; - padding: 0; align-items: center; } diff --git a/settings/css/settings.css b/settings/css/settings.css index 887d2beb7b84d..835a0a6b64f7d 100644 --- a/settings/css/settings.css +++ b/settings/css/settings.css @@ -109,7 +109,8 @@ input#openid, input#webdav { width:20em; } top: 44px; } .federationScopeMenu.bubble::after { - left: 45px; + right: 50%; + margin-right: -14px; } .federationScopeMenu.popovermenu { font-weight: 100; From 987995ac920a2d1e1251df80a482e3aef97c06f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?John=20Molakvo=C3=A6=20=28skjnldsv=29?= Date: Mon, 7 Nov 2016 18:17:27 +0100 Subject: [PATCH 14/53] Fix popup arrow positioning MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: John Molakvoæ (skjnldsv) --- settings/css/settings.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/settings/css/settings.css b/settings/css/settings.css index 835a0a6b64f7d..055c11b635c0a 100644 --- a/settings/css/settings.css +++ b/settings/css/settings.css @@ -110,7 +110,7 @@ input#openid, input#webdav { width:20em; } } .federationScopeMenu.bubble::after { right: 50%; - margin-right: -14px; + transform: translate(50%, 0); } .federationScopeMenu.popovermenu { font-weight: 100; From 8f33d9d1c07c164b18be9d36e9e9f79b5bdfa8be Mon Sep 17 00:00:00 2001 From: Bjoern Schiessle Date: Fri, 11 Nov 2016 14:36:17 +0100 Subject: [PATCH 15/53] update system address book if the user change the personal settings Signed-off-by: Bjoern Schiessle --- apps/dav/lib/AppInfo/Application.php | 10 ++ apps/dav/lib/CardDAV/Converter.php | 144 +++++++++--------------- apps/dav/lib/CardDAV/SyncService.php | 27 ++++- apps/dav/lib/HookManager.php | 9 +- lib/private/Accounts/AccountManager.php | 112 +++++++++++++++--- lib/private/Updater.php | 2 +- settings/Controller/UsersController.php | 39 ++++--- settings/js/federationsettingsview.js | 3 +- settings/personal.php | 35 +++--- settings/templates/personal.php | 24 +++- 10 files changed, 261 insertions(+), 144 deletions(-) diff --git a/apps/dav/lib/AppInfo/Application.php b/apps/dav/lib/AppInfo/Application.php index 844e0780ffb92..80d2946ab07c6 100644 --- a/apps/dav/lib/AppInfo/Application.php +++ b/apps/dav/lib/AppInfo/Application.php @@ -101,6 +101,12 @@ public function registerHooks() { } }); + $dispatcher->addListener('OC\AccountManager::userUpdated', function(GenericEvent $event) { + $user = $event->getSubject(); + $syncService = $this->getContainer()->query(SyncService::class); + $syncService->updateUser($user); + }); + $dispatcher->addListener('\OCA\DAV\CalDAV\CalDavBackend::createCalendar', function(GenericEvent $event) { /** @var Backend $backend */ $backend = $this->getContainer()->query(Backend::class); @@ -136,6 +142,10 @@ public function registerHooks() { ); }); + $dispatcher->addListener('OC\AccountManager::userUpdated', function(GenericEvent $event) { + error_log("hello"); + }); + $listener = function(GenericEvent $event, $eventName) { /** @var Backend $backend */ $backend = $this->getContainer()->query(Backend::class); diff --git a/apps/dav/lib/CardDAV/Converter.php b/apps/dav/lib/CardDAV/Converter.php index d1fb754017e17..065c5494ec38f 100644 --- a/apps/dav/lib/CardDAV/Converter.php +++ b/apps/dav/lib/CardDAV/Converter.php @@ -22,6 +22,7 @@ namespace OCA\DAV\CardDAV; +use OC\Accounts\AccountManager; use OCP\IImage; use OCP\IUser; use Sabre\VObject\Component\VCard; @@ -29,109 +30,76 @@ class Converter { + /** @var AccountManager */ + private $accountManager; + /** - * @param IUser $user - * @return VCard + * Converter constructor. + * + * @param AccountManager $accountManager */ - public function createCardFromUser(IUser $user) { - - $uid = $user->getUID(); - $displayName = $user->getDisplayName(); - $displayName = empty($displayName ) ? $uid : $displayName; - $emailAddress = $user->getEMailAddress(); - $cloudId = $user->getCloudId(); - $image = $this->getAvatarImage($user); - - $vCard = new VCard(); - $vCard->VERSION = '3.0'; - $vCard->UID = $uid; - if (!empty($displayName)) { - $vCard->FN = $displayName; - $vCard->N = $this->splitFullName($displayName); - } - if (!empty($emailAddress)) { - $vCard->add(new Text($vCard, 'EMAIL', $emailAddress, ['TYPE' => 'OTHER'])); - } - if (!empty($cloudId)) { - $vCard->CLOUD = $cloudId; - } - if ($image) { - $vCard->add('PHOTO', $image->data(), ['ENCODING' => 'b', 'TYPE' => $image->mimeType()]); - } - $vCard->validate(); - - return $vCard; + public function __construct(AccountManager $accountManager) { + $this->accountManager = $accountManager; } /** - * @param VCard $vCard * @param IUser $user - * @return bool + * @return VCard|null */ - public function updateCard(VCard $vCard, IUser $user) { + public function createCardFromUser(IUser $user) { + + $userData = $this->accountManager->getUser($user); + $uid = $user->getUID(); - $displayName = $user->getDisplayName(); - $displayName = empty($displayName ) ? $uid : $displayName; - $emailAddress = $user->getEMailAddress(); $cloudId = $user->getCloudId(); $image = $this->getAvatarImage($user); - $updated = false; - if($this->propertyNeedsUpdate($vCard, 'FN', $displayName)) { - $vCard->FN = new Text($vCard, 'FN', $displayName); - unset($vCard->N); - $vCard->add(new Text($vCard, 'N', $this->splitFullName($displayName))); - $updated = true; - } - if($this->propertyNeedsUpdate($vCard, 'EMAIL', $emailAddress)) { - $vCard->EMAIL = new Text($vCard, 'EMAIL', $emailAddress); - $updated = true; - } - if($this->propertyNeedsUpdate($vCard, 'CLOUD', $cloudId)) { - $vCard->CLOUD = new Text($vCard, 'CLOUD', $cloudId); - $updated = true; - } - - if($this->propertyNeedsUpdate($vCard, 'PHOTO', $image)) { - unset($vCard->PHOTO); - $vCard->add('PHOTO', $image->data(), ['ENCODING' => 'b', 'TYPE' => $image->mimeType()]); - $updated = true; - } - - if (empty($emailAddress) && !is_null($vCard->EMAIL)) { - unset($vCard->EMAIL); - $updated = true; - } - if (empty($cloudId) && !is_null($vCard->CLOUD)) { - unset($vCard->CLOUD); - $updated = true; - } - if (empty($image) && !is_null($vCard->PHOTO)) { - unset($vCard->PHOTO); - $updated = true; + $vCard = new VCard(); + $vCard->add(new Text($vCard, 'UID', $uid)); + + $publish = false; + + foreach ($userData as $property => $value) { + if ($value['scope'] === AccountManager::VISIBILITY_CONTACTS_ONLY || + $value['scope'] === AccountManager::VISIBILITY_PUBLIC + ) { + $publish = true; + switch ($property) { + case AccountManager::PROPERTY_DISPLAYNAME: + $vCard->add(new Text($vCard, 'FN', $value['value'])); + $vCard->add(new Text($vCard, 'N', $this->splitFullName($value['value']))); + break; + case AccountManager::PROPERTY_AVATAR: + if ($image !== null) { + $vCard->add('PHOTO', $image->data(), ['ENCODING' => 'b', 'TYPE' => $image->mimeType()]); + } + break; + case AccountManager::PROPERTY_EMAIL: + $vCard->add(new Text($vCard, 'EMAIL', $value['value'], ['TYPE' => 'OTHER'])); + break; + case AccountManager::PROPERTY_WEBSITE: + $vCard->add(new Text($vCard, 'URL', $value['value'])); + break; + case AccountManager::PROPERTY_PHONE: + $vCard->add(new Text($vCard, 'TEL', $value['value'], ['TYPE' => 'OTHER'])); + break; + case AccountManager::PROPERTY_ADDRESS: + $vCard->add(new Text($vCard, 'ADR', $value['value'], ['TYPE' => 'OTHER'])); + break; + case AccountManager::PROPERTY_TWITTER: + $vCard->add(new Text($vCard, 'X-SOCIALPROFILE', $value['value'], ['TYPE' => 'TWITTER'])); + break; + } + } } - return $updated; - } - - /** - * @param VCard $vCard - * @param string $name - * @param string|IImage $newValue - * @return bool - */ - private function propertyNeedsUpdate(VCard $vCard, $name, $newValue) { - if (is_null($newValue)) { - return false; + if ($publish && !empty($cloudId)) { + $vCard->add(new Text($vCard, 'CLOUD', $cloudId)); + $vCard->validate(); + return $vCard; } - $value = $vCard->__get($name); - if (!is_null($value)) { - $value = $value->getValue(); - $newValue = $newValue instanceof IImage ? $newValue->data() : $newValue; - return $value !== $newValue; - } - return true; + return null; } /** diff --git a/apps/dav/lib/CardDAV/SyncService.php b/apps/dav/lib/CardDAV/SyncService.php index 1ad37da6bfe4b..1293d8ae8a005 100644 --- a/apps/dav/lib/CardDAV/SyncService.php +++ b/apps/dav/lib/CardDAV/SyncService.php @@ -24,6 +24,7 @@ namespace OCA\DAV\CardDAV; +use OC\Accounts\AccountManager; use OCP\AppFramework\Http; use OCP\ILogger; use OCP\IUser; @@ -48,10 +49,22 @@ class SyncService { /** @var array */ private $localSystemAddressBook; - public function __construct(CardDavBackend $backend, IUserManager $userManager, ILogger $logger) { + /** @var AccountManager */ + private $accountManager; + + /** + * SyncService constructor. + * + * @param CardDavBackend $backend + * @param IUserManager $userManager + * @param ILogger $logger + * @param AccountManager $accountManager + */ + public function __construct(CardDavBackend $backend, IUserManager $userManager, ILogger $logger, AccountManager $accountManager) { $this->backend = $backend; $this->userManager = $userManager; $this->logger = $logger; + $this->accountManager = $accountManager; } /** @@ -215,7 +228,7 @@ private function parseMultiStatus($body) { public function updateUser($user) { $systemAddressBook = $this->getLocalSystemAddressBook(); $addressBookId = $systemAddressBook['id']; - $converter = new Converter(); + $converter = new Converter($this->accountManager); $name = $user->getBackendClassName(); $userId = $user->getUID(); @@ -223,10 +236,14 @@ public function updateUser($user) { $card = $this->backend->getCard($addressBookId, $cardId); if ($card === false) { $vCard = $converter->createCardFromUser($user); - $this->backend->createCard($addressBookId, $cardId, $vCard->serialize()); + if ($vCard !== null) { + $this->backend->createCard($addressBookId, $cardId, $vCard->serialize()); + } } else { - $vCard = Reader::read($card['carddata']); - if ($converter->updateCard($vCard, $user)) { + $vCard = $converter->createCardFromUser($user); + if (is_null($vCard)) { + $this->backend->deleteCard($addressBookId, $cardId); + } else { $this->backend->updateCard($addressBookId, $cardId, $vCard->serialize()); } } diff --git a/apps/dav/lib/HookManager.php b/apps/dav/lib/HookManager.php index 247d4b291af15..26f3895a45986 100644 --- a/apps/dav/lib/HookManager.php +++ b/apps/dav/lib/HookManager.php @@ -27,6 +27,8 @@ use OCP\IUser; use OCP\IUserManager; use OCP\Util; +use Symfony\Component\EventDispatcher\EventDispatcher; +use Symfony\Component\EventDispatcher\GenericEvent; class HookManager { @@ -51,14 +53,19 @@ class HookManager { /** @var array */ private $addressBooksToDelete; + /** @var EventDispatcher */ + private $eventDispatcher; + public function __construct(IUserManager $userManager, SyncService $syncService, CalDavBackend $calDav, - CardDavBackend $cardDav) { + CardDavBackend $cardDav, + EventDispatcher $eventDispatcher) { $this->userManager = $userManager; $this->syncService = $syncService; $this->calDav = $calDav; $this->cardDav = $cardDav; + $this->eventDispatcher = $eventDispatcher; } public function setup() { diff --git a/lib/private/Accounts/AccountManager.php b/lib/private/Accounts/AccountManager.php index 3decac0153aac..9485f8bc19a79 100644 --- a/lib/private/Accounts/AccountManager.php +++ b/lib/private/Accounts/AccountManager.php @@ -1,8 +1,9 @@ + * @author Björn Schießle * * @copyright Copyright (c) 2016, ownCloud, Inc. + * @copyright Copyright (c) 2016, Björn Schießle * @license AGPL-3.0 * * This code is free software: you can redistribute it and/or modify @@ -24,6 +25,9 @@ use OCP\IDBConnection; +use OCP\IUser; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; +use Symfony\Component\EventDispatcher\GenericEvent; /** * Class AccountManager @@ -35,37 +39,69 @@ */ class AccountManager { + /** nobody can see my account details */ + const VISIBILITY_PRIVATE = 'private'; + /** only contacts, especially trusted servers can see my contact details */ + const VISIBILITY_CONTACTS_ONLY = 'contacts'; + /** every body ca see my contact detail, will be published to the lookup server */ + const VISIBILITY_PUBLIC = 'public'; + + const PROPERTY_AVATAR = 'avatar'; + const PROPERTY_DISPLAYNAME = 'displayname'; + const PROPERTY_PHONE = 'phone'; + const PROPERTY_EMAIL = 'email'; + const PROPERTY_WEBSITE = 'website'; + const PROPERTY_ADDRESS = 'address'; + const PROPERTY_TWITTER = 'twitter'; + /** @var IDBConnection database connection */ private $connection; /** @var string table name */ private $table = 'accounts'; + /** @var EventDispatcherInterface */ + private $eventDispatcher; + /** * AccountManager constructor. * * @param IDBConnection $connection + * @param EventDispatcherInterface $eventDispatcher */ - public function __construct(IDBConnection $connection) { + public function __construct(IDBConnection $connection, EventDispatcherInterface $eventDispatcher) { $this->connection = $connection; + $this->eventDispatcher = $eventDispatcher; } - public function updateUser($uid, $data) { - $userData = $this->getUser($uid); + /** + * update user record + * + * @param IUser $user + * @param $data + */ + public function updateUser(IUser $user, $data) { + $userData = $this->getUser($user); if (empty($userData)) { - $this->insertNewUser($uid, $data); + $this->insertNewUser($user, $data); } else { - $this->updateExistingUser($uid, $data); + $this->updateExistingUser($user, $data); } + + $this->eventDispatcher->dispatch( + 'OC\AccountManager::userUpdated', + new GenericEvent($user) + ); } /** * get stored data from a given user * - * @param $uid + * @param IUser $user * @return array */ - public function getUser($uid) { + public function getUser(IUser $user) { + $uid = $user->getUID(); $query = $this->connection->getQueryBuilder(); $query->select('data')->from($this->table) ->where($query->expr()->eq('uid', $query->createParameter('uid'))) @@ -74,7 +110,9 @@ public function getUser($uid) { $result = $query->execute()->fetchAll(); if (empty($result)) { - return []; + $userData = $this->buildDefaultUserRecord($user); + $this->insertNewUser($user, $userData); + return $userData; } return json_decode($result[0]['data'], true); @@ -83,10 +121,11 @@ public function getUser($uid) { /** * add new user to accounts table * - * @param string $uid + * @param IUser $user * @param array $data */ - protected function insertNewUser($uid, $data) { + protected function insertNewUser(IUser $user, $data) { + $uid = $user->getUID(); $jsonEncodedData = json_encode($data); $query = $this->connection->getQueryBuilder(); $query->insert($this->table) @@ -102,10 +141,11 @@ protected function insertNewUser($uid, $data) { /** * update existing user in accounts table * - * @param string $uid + * @param IUser $user * @param array $data */ - protected function updateExistingUser($uid, $data) { + protected function updateExistingUser(IUser $user, $data) { + $uid = $user->getUID(); $jsonEncodedData = json_encode($data); $query = $this->connection->getQueryBuilder(); $query->update($this->table) @@ -113,4 +153,50 @@ protected function updateExistingUser($uid, $data) { ->where($query->expr()->eq('uid', $query->createNamedParameter($uid))) ->execute(); } + + /** + * build default user record in case not data set exists yet + * + * @param IUser $user + * @return array + */ + protected function buildDefaultUserRecord(IUser $user) { + return [ + self::PROPERTY_DISPLAYNAME => + [ + 'value' => $user->getDisplayName(), + 'scope' => self::VISIBILITY_CONTACTS_ONLY, + ], + self::PROPERTY_ADDRESS => + [ + 'value' => '', + 'scope' => self::VISIBILITY_PRIVATE, + ], + self::PROPERTY_WEBSITE => + [ + 'value' => '', + 'scope' => self::VISIBILITY_PRIVATE, + ], + self::PROPERTY_EMAIL => + [ + 'value' => $user->getEMailAddress(), + 'scope' => self::VISIBILITY_CONTACTS_ONLY, + ], + self::PROPERTY_AVATAR => + [ + 'scope' => self::VISIBILITY_CONTACTS_ONLY + ], + self::PROPERTY_PHONE => + [ + 'value' => '', + 'scope' => self::VISIBILITY_PRIVATE, + ], + self::PROPERTY_TWITTER => + [ + 'value' => '', + 'scope' => self::VISIBILITY_PRIVATE, + ], + ]; + } + } diff --git a/lib/private/Updater.php b/lib/private/Updater.php index 2fed67988b889..c3d8ef9fea372 100644 --- a/lib/private/Updater.php +++ b/lib/private/Updater.php @@ -53,7 +53,7 @@ class Updater extends BasicEmitter { /** @var ILogger $log */ private $log; - + /** @var IConfig */ private $config; diff --git a/settings/Controller/UsersController.php b/settings/Controller/UsersController.php index 6bbf878652078..08d97a0f581e3 100644 --- a/settings/Controller/UsersController.php +++ b/settings/Controller/UsersController.php @@ -519,6 +519,8 @@ public function destroy($id) { * @param string $websiteScope * @param string $address * @param string $addressScope + * @param string $twitter + * @param string $twitterScope * @return DataResponse */ public function setUserSettings($avatarScope, @@ -526,7 +528,8 @@ public function setUserSettings($avatarScope, $phone, $phoneScope, $email, $emailScope, $website, $websiteScope, - $address, $addressScope + $address, $addressScope, + $twitter, $twitterScope ) { @@ -542,26 +545,27 @@ public function setUserSettings($avatarScope, ); } - $userId = $this->userSession->getUser()->getUID(); + $user = $this->userSession->getUser(); $data = [ - 'avatar' => ['scope' => $avatarScope], - 'displayName' => ['value' => $displayname, 'scope' => $displaynameScope], - 'email' => [['value' => $email, 'scope' => $emailScope]], - 'website' => [['value' => $website, 'scope' => $websiteScope]], - 'address' => [['value' => $address, 'scope' => $addressScope]], - 'phone' => [['value' => $phone, 'scope' => $phoneScope]] + AccountManager::PROPERTY_AVATAR => ['scope' => $avatarScope], + AccountManager::PROPERTY_DISPLAYNAME => ['value' => $displayname, 'scope' => $displaynameScope], + AccountManager::PROPERTY_EMAIL=> ['value' => $email, 'scope' => $emailScope], + AccountManager::PROPERTY_WEBSITE => ['value' => $website, 'scope' => $websiteScope], + AccountManager::PROPERTY_ADDRESS => ['value' => $address, 'scope' => $addressScope], + AccountManager::PROPERTY_PHONE => ['value' => $phone, 'scope' => $phoneScope], + AccountManager::PROPERTY_TWITTER => ['value' => $twitter, 'scope' => $twitterScope] ]; - $this->accountManager->updateUser($userId, $data); + $this->accountManager->updateUser($user, $data); try { - $this->saveUserSettings($userId, $data); + $this->saveUserSettings($user, $data); return new DataResponse( array( 'status' => 'success', 'data' => array( - 'userId' => $userId, + 'userId' => $user->getUID(), 'avatarScope' => $avatarScope, 'displayname' => $displayname, 'displaynameScope' => $displaynameScope, @@ -591,12 +595,11 @@ public function setUserSettings($avatarScope, /** * update account manager with new user data * - * @param string $userId + * @param IUser $user * @param array $data * @throws ForbiddenException */ - private function saveUserSettings($userId, $data) { - $user = $this->userManager->get($userId); + private function saveUserSettings(IUser $user, $data) { // keep the user back-end up-to-date with the latest display name and email // address @@ -614,7 +617,7 @@ private function saveUserSettings($userId, $data) { } } - $this->accountManager->updateUser($userId, $data); + $this->accountManager->updateUser($user, $data); } /** @@ -688,12 +691,12 @@ public function setDisplayName($username, $displayName) { ]); } - $userData = $this->accountManager->getUser($user->getUID()); - $userData['displayName']['value'] = $displayName; + $userData = $this->accountManager->getUser($user); + $userData[AccountManager::PROPERTY_DISPLAYNAME]['value'] = $displayName; try { - $this->saveUserSettings($username, $userData); + $this->saveUserSettings($user, $userData); return new DataResponse([ 'status' => 'success', 'data' => [ diff --git a/settings/js/federationsettingsview.js b/settings/js/federationsettingsview.js index 73b9f85755899..6d9c407b2daf5 100644 --- a/settings/js/federationsettingsview.js +++ b/settings/js/federationsettingsview.js @@ -30,6 +30,7 @@ 'phone', 'email', 'website', + 'twitter', 'address', 'avatar' ]; @@ -159,4 +160,4 @@ OC.Settings = OC.Settings || {}; OC.Settings.FederationSettingsView = FederationSettingsView; -})(_, $, OC); \ No newline at end of file +})(_, $, OC); diff --git a/settings/personal.php b/settings/personal.php index 9475b674b7503..24955c94071d8 100644 --- a/settings/personal.php +++ b/settings/personal.php @@ -40,7 +40,7 @@ $defaults = \OC::$server->getThemingDefaults(); $certificateManager = \OC::$server->getCertificateManager(); -$accountManager = new \OC\Accounts\AccountManager(\OC::$server->getDatabaseConnection()); +$accountManager = new \OC\Accounts\AccountManager(\OC::$server->getDatabaseConnection(), \OC::$server->getEventDispatcher()); $config = \OC::$server->getConfig(); $urlGenerator = \OC::$server->getURLGenerator(); @@ -70,7 +70,6 @@ $storageInfo=OC_Helper::getStorageInfo('/'); $user = OC::$server->getUserManager()->get(OC_User::getUser()); -$email = $user->getEMailAddress(); $userLang=$config->getUserValue( OC_User::getUser(), 'core', 'lang', \OC::$server->getL10NFactory()->findLanguage() ); $languageCodes = \OC::$server->getL10NFactory()->findAvailableLanguages(); @@ -156,29 +155,33 @@ } else { $totalSpace = OC_Helper::humanFileSize($storageInfo['total']); } -$userData = $accountManager->getUser($user->getUID()); + +$uid = $user->getUID(); +$userData = $accountManager->getUser($user); $tmpl->assign('total_space', $totalSpace); $tmpl->assign('usage_relative', $storageInfo['relative']); $tmpl->assign('clients', $clients); -$tmpl->assign('email', $email); +$tmpl->assign('email', $userData[\OC\Accounts\AccountManager::PROPERTY_EMAIL]['value']); $tmpl->assign('languages', $languages); $tmpl->assign('commonlanguages', $commonLanguages); $tmpl->assign('activelanguage', $userLang); $tmpl->assign('passwordChangeSupported', OC_User::canUserChangePassword(OC_User::getUser())); $tmpl->assign('displayNameChangeSupported', OC_User::canUserChangeDisplayName(OC_User::getUser())); -$tmpl->assign('displayName', OC_User::getDisplayName()); - -$tmpl->assign('phone', isset($userData['phone'][0]['value']) ? $userData['phone'][0]['value'] : null); -$tmpl->assign('website', isset($userData['website'][0]['value']) ? $userData['website'][0]['value'] : null); -$tmpl->assign('address', isset($userData['address'][0]['value']) ? $userData['address'][0]['value'] : null); - -$tmpl->assign('avatarScope', isset($userData['avatar']['scope']) ? $userData['avatar']['scope'] : 'contacts'); -$tmpl->assign('displayNameScope', isset($userData['displayName']['scope']) ? $userData['displayName']['scope'] : 'contacts'); -$tmpl->assign('phoneScope', isset($userData['phone'][0]['scope']) ? $userData['phone'][0]['scope'] : 'private'); -$tmpl->assign('emailScope', isset($userData['email'][0]['scope']) ? $userData['email'][0]['scope'] : 'private'); -$tmpl->assign('websiteScope', isset($userData['website'][0]['scope']) ? $userData['website'][0]['scope'] : 'private'); -$tmpl->assign('addressScope', isset($userData['address'][0]['scope']) ? $userData['address'][0]['scope'] : 'private'); +$tmpl->assign('displayName', $userData[\OC\Accounts\AccountManager::PROPERTY_DISPLAYNAME]['value']); + +$tmpl->assign('phone', $userData[\OC\Accounts\AccountManager::PROPERTY_PHONE]['value']); +$tmpl->assign('website', $userData[\OC\Accounts\AccountManager::PROPERTY_WEBSITE]['value']); +$tmpl->assign('twitter', $userData[\OC\Accounts\AccountManager::PROPERTY_TWITTER]['value']); +$tmpl->assign('address', $userData[\OC\Accounts\AccountManager::PROPERTY_ADDRESS]['value']); + +$tmpl->assign('avatarScope', $userData[\OC\Accounts\AccountManager::PROPERTY_AVATAR]['scope']); +$tmpl->assign('displayNameScope', $userData[\OC\Accounts\AccountManager::PROPERTY_DISPLAYNAME]['scope']); +$tmpl->assign('phoneScope', $userData[\OC\Accounts\AccountManager::PROPERTY_PHONE]['scope']); +$tmpl->assign('emailScope', $userData[\OC\Accounts\AccountManager::PROPERTY_EMAIL]['scope']); +$tmpl->assign('websiteScope', $userData[\OC\Accounts\AccountManager::PROPERTY_WEBSITE]['scope']); +$tmpl->assign('twitterScope', $userData[\OC\Accounts\AccountManager::PROPERTY_TWITTER]['scope']); +$tmpl->assign('addressScope', $userData[\OC\Accounts\AccountManager::PROPERTY_ADDRESS]['scope']); $tmpl->assign('enableAvatars', $config->getSystemValue('enable_avatars', true) === true); $tmpl->assign('avatarChangeSupported', OC_User::canUserChangeAvatar(OC_User::getUser())); diff --git a/settings/templates/personal.php b/settings/templates/personal.php index 2db0cfa488290..01f114d9e956b 100644 --- a/settings/templates/personal.php +++ b/settings/templates/personal.php @@ -83,11 +83,12 @@

- +

+ +
+
+

+ + +

+ +
+ +

@@ -128,6 +145,7 @@

+
+

t('Twitter')); ?>

+ t('No twitter handle set')); }?> +

t('Address')); ?>

t('No address set')); }?> From f7a4db5040be1682efb5ffb48273a62fc599e1b7 Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Tue, 15 Nov 2016 18:10:28 +0100 Subject: [PATCH 16/53] fix avatar cropper --- settings/css/settings.css | 10 +++++----- settings/templates/personal.php | 6 ++++-- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/settings/css/settings.css b/settings/css/settings.css index 055c11b635c0a..f73386067a26f 100644 --- a/settings/css/settings.css +++ b/settings/css/settings.css @@ -26,7 +26,7 @@ input#openid, input#webdav { width:20em; } .jcrop-holder { z-index: 500; } -#avatarform #cropper { +#cropper { float: left; z-index: 500; /* float cropper above settings page to prevent unexpected flowing from dynamically sized element */ @@ -38,7 +38,7 @@ input#openid, input#webdav { width:20em; } width: 100%; height: calc(100% - 45px); } -#avatar #cropper .inner-container { +#cropper .inner-container { z-index: 2001; /* above the top bar if needed */ position: absolute; top: 50%; @@ -51,13 +51,13 @@ input#openid, input#webdav { width:20em; } padding: 15px; } -#avatar #cropper .inner-container .jcrop-holder { +#cropper .inner-container .jcrop-holder { box-shadow: 0 0 7px #888; } -#avatar #cropper .inner-container .button { +#cropper .inner-container .button { margin-top: 15px; } -#avatar #cropper .inner-container .primary { +#cropper .inner-container .primary { float: right; } diff --git a/settings/templates/personal.php b/settings/templates/personal.php index 01f114d9e956b..cd3093a75edbb 100644 --- a/settings/templates/personal.php +++ b/settings/templates/personal.php @@ -55,8 +55,10 @@
From 3f8bfbdb110a7010038f995d31d6d5132a543112 Mon Sep 17 00:00:00 2001 From: Bjoern Schiessle Date: Wed, 16 Nov 2016 12:34:32 +0100 Subject: [PATCH 17/53] change order of email and phone number Signed-off-by: Bjoern Schiessle --- settings/templates/personal.php | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/settings/templates/personal.php b/settings/templates/personal.php index cd3093a75edbb..16cb5a0c76184 100644 --- a/settings/templates/personal.php +++ b/settings/templates/personal.php @@ -83,32 +83,32 @@
-
+

- +

- + + +
+ t('For password recovery and notifications')); ?>
-
+

- +

- - -
- t('For password recovery and notifications')); ?>
From f489fd99c352be3dfa65c1955573e44c13732e30 Mon Sep 17 00:00:00 2001 From: Bjoern Schiessle Date: Wed, 16 Nov 2016 12:35:07 +0100 Subject: [PATCH 18/53] change scope to 'local' for display name and avatar Signed-off-by: Bjoern Schiessle --- settings/js/federationscopemenu.js | 45 ++++++++++++++++----------- settings/js/federationsettingsview.js | 2 +- 2 files changed, 28 insertions(+), 19 deletions(-) diff --git a/settings/js/federationscopemenu.js b/settings/js/federationscopemenu.js index 6eb9afb54e05c..45937911f099c 100644 --- a/settings/js/federationscopemenu.js +++ b/settings/js/federationscopemenu.js @@ -16,7 +16,7 @@ '
+
+
+

+ + +

+ +

@@ -124,7 +138,6 @@

-

@@ -139,21 +152,6 @@

- -
-
-

- - -

- -
t('Phone')); ?> t('No phone number set')); }?> +
+

t('Address')); ?>

+ t('No address set')); }?> +

t('Website')); ?>

t('No website set')); }?> @@ -208,10 +210,7 @@ class="password-confirm-required"

t('Twitter')); ?>

t('No twitter handle set')); }?>
-
-

t('Address')); ?>

- t('No address set')); }?> -
+ Date: Thu, 17 Nov 2016 12:14:13 +0100 Subject: [PATCH 26/53] push public user data to the lookup server Signed-off-by: Bjoern Schiessle --- .gitignore | 1 + apps/lookup_server_connector/appinfo/app.php | 28 ++++++ apps/lookup_server_connector/appinfo/info.xml | 18 ++++ .../lib/BackgroundJobs/RetryJob.php | 33 +++++++ .../lib/UpdateLookupServer.php | 93 +++++++++++++++++++ core/shipped.json | 2 + tests/lib/App/ManagerTest.php | 4 + 7 files changed, 179 insertions(+) create mode 100644 apps/lookup_server_connector/appinfo/app.php create mode 100644 apps/lookup_server_connector/appinfo/info.xml create mode 100644 apps/lookup_server_connector/lib/BackgroundJobs/RetryJob.php create mode 100644 apps/lookup_server_connector/lib/UpdateLookupServer.php diff --git a/.gitignore b/.gitignore index 964701eea63ae..fa49588fad4b2 100644 --- a/.gitignore +++ b/.gitignore @@ -22,6 +22,7 @@ !/apps/files_sharing !/apps/files_trashbin !/apps/files_versions +!/apps/lookup_server_connector !/apps/user_ldap !/apps/provisioning_api !/apps/systemtags diff --git a/apps/lookup_server_connector/appinfo/app.php b/apps/lookup_server_connector/appinfo/app.php new file mode 100644 index 0000000000000..e74f101853fc2 --- /dev/null +++ b/apps/lookup_server_connector/appinfo/app.php @@ -0,0 +1,28 @@ + + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * 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 + * along with this program. If not, see . + * + */ + +$dispatcher = \OC::$server->getEventDispatcher(); + +$dispatcher->addListener('OC\AccountManager::userUpdated', function(GenericEvent $event) { + $user = $event->getSubject(); + $updateLookupServer = new \OCA\LookupServerConnector\UpdateLookupServer(); + $updateLookupServer->userUpdated($user); +}); diff --git a/apps/lookup_server_connector/appinfo/info.xml b/apps/lookup_server_connector/appinfo/info.xml new file mode 100644 index 0000000000000..88898c0b71b21 --- /dev/null +++ b/apps/lookup_server_connector/appinfo/info.xml @@ -0,0 +1,18 @@ + + + lookup_server_connector + Lookup Server Connector + Sync public user information with the lookup server + AGPL + Bjoern Schiessle + LookupServerConnector + 1.0.0 + other + + + + + + + + diff --git a/apps/lookup_server_connector/lib/BackgroundJobs/RetryJob.php b/apps/lookup_server_connector/lib/BackgroundJobs/RetryJob.php new file mode 100644 index 0000000000000..5c4f1e7325973 --- /dev/null +++ b/apps/lookup_server_connector/lib/BackgroundJobs/RetryJob.php @@ -0,0 +1,33 @@ + + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * 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 + * along with this program. If not, see . + * + */ + + +namespace OCA\LookupServerConnector\BackgroundJobs; + + +use OC\BackgroundJob\Job; + +class RetryJob extends Job { + + protected function run($argument) { + // TODO: Implement run() method. + } +} diff --git a/apps/lookup_server_connector/lib/UpdateLookupServer.php b/apps/lookup_server_connector/lib/UpdateLookupServer.php new file mode 100644 index 0000000000000..2ccdf5b5a8c27 --- /dev/null +++ b/apps/lookup_server_connector/lib/UpdateLookupServer.php @@ -0,0 +1,93 @@ + + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * 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 + * along with this program. If not, see . + * + */ + + +namespace OCA\LookupServerConnector; + + +use OC\Accounts\AccountManager; +use OCP\IConfig; +use OCP\IUser; + +/** + * Class UpdateLookupServer + * + * @package OCA\LookupServerConnector + */ +class UpdateLookupServer { + + /** @var AccountManager */ + private $accountManager; + + /** @var IConfig */ + private $config; + + /** + * UpdateLookupServer constructor. + * + * @param AccountManager $accountManager + * @param IConfig $config + */ + public function __construct(AccountManager $accountManager, IConfig $config) { + $this->accountManager; + $this->config = $config; + } + + + public function userUpdated(IUser $user) { + $userData = $this->accountManager->getUser($user); + $authKey = $this->config->getUserValue($user->getUID(), 'lookup_server_connector', 'authKey'); + + $publicData = []; + + foreach ($userData as $data) { + if ($data['scope'] === AccountManager::VISIBILITY_PUBLIC) { + $publicData[] = $data; + } + } + + if (empty($publicData)) { + $this->removeFromLookupServer($user); + } else { + $this->sendToLookupServer($publicData, $authKey); + } + } + + /** + * remove user from lookup server + * + * @param IUser $user + */ + protected function removeFromLookupServer(IUser $user) { + $this->config->deleteUserValue($user->getUID(), 'lookup_server_connector', 'authKey'); + } + + /** + * send public user data to the lookup server + * + * @param array $publicData + * @param string $authKey + */ + protected function sendToLookupServer($publicData, $authKey) { + // If we don't update a existing entry, the server will return a authKey and we + // will add it to the database + } +} diff --git a/core/shipped.json b/core/shipped.json index f831d17f36a5c..7fb87b7f17db7 100644 --- a/core/shipped.json +++ b/core/shipped.json @@ -22,6 +22,7 @@ "firstrunwizard", "gallery", "logreader", + "lookup_server_connector", "notifications", "password_policy", "provisioning_api", @@ -42,6 +43,7 @@ "files", "dav", "federatedfilesharing", + "lookup_server_connector", "provisioning_api", "twofactor_backupcodes", "workflowengine" diff --git a/tests/lib/App/ManagerTest.php b/tests/lib/App/ManagerTest.php index 3dbcb8a560988..e38f72b3d92fe 100644 --- a/tests/lib/App/ManagerTest.php +++ b/tests/lib/App/ManagerTest.php @@ -320,6 +320,7 @@ public function testGetInstalledApps() { 'dav', 'federatedfilesharing', 'files', + 'lookup_server_connector', 'provisioning_api', 'test1', 'test3', @@ -344,6 +345,7 @@ public function testGetAppsForUser() { 'dav', 'federatedfilesharing', 'files', + 'lookup_server_connector', 'provisioning_api', 'test1', 'test3', @@ -364,6 +366,7 @@ public function testGetAppsNeedingUpgrade() { 'files' => ['id' => 'files'], 'federatedfilesharing' => ['id' => 'federatedfilesharing'], 'provisioning_api' => ['id' => 'provisioning_api'], + 'lookup_server_connector' => ['id' => 'lookup_server_connector'], 'test1' => ['id' => 'test1', 'version' => '1.0.1', 'requiremax' => '9.0.0'], 'test2' => ['id' => 'test2', 'version' => '1.0.0', 'requiremin' => '8.2.0'], 'test3' => ['id' => 'test3', 'version' => '1.2.4', 'requiremin' => '9.0.0'], @@ -408,6 +411,7 @@ public function testGetIncompatibleApps() { 'files' => ['id' => 'files'], 'federatedfilesharing' => ['id' => 'federatedfilesharing'], 'provisioning_api' => ['id' => 'provisioning_api'], + 'lookup_server_connector' => ['id' => 'lookup_server_connector'], 'test1' => ['id' => 'test1', 'version' => '1.0.1', 'requiremax' => '8.0.0'], 'test2' => ['id' => 'test2', 'version' => '1.0.0', 'requiremin' => '8.2.0'], 'test3' => ['id' => 'test3', 'version' => '1.2.4', 'requiremin' => '9.0.0'], From 069023416c646ab8340a8521c4db5b453e57481d Mon Sep 17 00:00:00 2001 From: Bjoern Schiessle Date: Thu, 17 Nov 2016 17:00:25 +0100 Subject: [PATCH 27/53] lookup server connector Signed-off-by: Bjoern Schiessle --- apps/lookup_server_connector/appinfo/app.php | 9 +- .../lib/UpdateLookupServer.php | 85 ++++++++++++++++--- 2 files changed, 82 insertions(+), 12 deletions(-) diff --git a/apps/lookup_server_connector/appinfo/app.php b/apps/lookup_server_connector/appinfo/app.php index e74f101853fc2..dc076b78828b9 100644 --- a/apps/lookup_server_connector/appinfo/app.php +++ b/apps/lookup_server_connector/appinfo/app.php @@ -21,8 +21,13 @@ $dispatcher = \OC::$server->getEventDispatcher(); -$dispatcher->addListener('OC\AccountManager::userUpdated', function(GenericEvent $event) { +$dispatcher->addListener('OC\AccountManager::userUpdated', function(\Symfony\Component\EventDispatcher\GenericEvent $event) { $user = $event->getSubject(); - $updateLookupServer = new \OCA\LookupServerConnector\UpdateLookupServer(); + $updateLookupServer = new \OCA\LookupServerConnector\UpdateLookupServer( + new \OC\Accounts\AccountManager(\OC::$server->getDatabaseConnection(), \OC::$server->getEventDispatcher()), + \OC::$server->getConfig(), + \OC::$server->getSecureRandom(), + \OC::$server->getHTTPClientService() + ); $updateLookupServer->userUpdated($user); }); diff --git a/apps/lookup_server_connector/lib/UpdateLookupServer.php b/apps/lookup_server_connector/lib/UpdateLookupServer.php index 2ccdf5b5a8c27..b6d8b9782a42e 100644 --- a/apps/lookup_server_connector/lib/UpdateLookupServer.php +++ b/apps/lookup_server_connector/lib/UpdateLookupServer.php @@ -24,8 +24,10 @@ use OC\Accounts\AccountManager; +use OCP\Http\Client\IClientService; use OCP\IConfig; use OCP\IUser; +use OCP\Security\ISecureRandom; /** * Class UpdateLookupServer @@ -40,15 +42,31 @@ class UpdateLookupServer { /** @var IConfig */ private $config; + /** @var ISecureRandom */ + private $secureRandom; + + /** @var IClientService */ + private $clientService; + + /** @var string URL point to lookup server */ + private $lookupServer = 'http://192.168.56.102'; + /** * UpdateLookupServer constructor. * * @param AccountManager $accountManager * @param IConfig $config + * @param ISecureRandom $secureRandom + * @param IClientService $clientService */ - public function __construct(AccountManager $accountManager, IConfig $config) { - $this->accountManager; + public function __construct(AccountManager $accountManager, + IConfig $config, + ISecureRandom $secureRandom, + IClientService $clientService) { + $this->accountManager = $accountManager; $this->config = $config; + $this->secureRandom = $secureRandom; + $this->clientService = $clientService; } @@ -58,16 +76,16 @@ public function userUpdated(IUser $user) { $publicData = []; - foreach ($userData as $data) { + foreach ($userData as $key => $data) { if ($data['scope'] === AccountManager::VISIBILITY_PUBLIC) { - $publicData[] = $data; + $publicData[$key] = $data; } } - if (empty($publicData)) { - $this->removeFromLookupServer($user); + if (empty($publicData) && !empty($authKey)) { + $this->removeFromLookupServer($user, $authKey); } else { - $this->sendToLookupServer($publicData, $authKey); + $this->sendToLookupServer($user, $publicData, $authKey); } } @@ -83,11 +101,58 @@ protected function removeFromLookupServer(IUser $user) { /** * send public user data to the lookup server * + * @param IUser $user * @param array $publicData * @param string $authKey */ - protected function sendToLookupServer($publicData, $authKey) { - // If we don't update a existing entry, the server will return a authKey and we - // will add it to the database + protected function sendToLookupServer(IUser $user, $publicData, $authKey) { + if (empty($authKey)) { + $authKey = $this->secureRandom->generate(16); + $this->sendNewRecord($user, $publicData, $authKey); + $this->config->setUserValue($user->getUID(), 'lookup_server_connector', 'authKey', $authKey); + } else { + $this->updateExistingRecord($user, $publicData, $authKey); + } + } + + protected function sendNewRecord(IUser $user, $publicData, $authKey) { + $httpClient = $this->clientService->newClient(); + $response = $httpClient->post($this->lookupServer, + [ + 'body' => [ + 'key' => $authKey, + 'federationid' => $publicData[$user->getCloudId()], + 'name' => isset($publicData[AccountManager::PROPERTY_DISPLAYNAME]) ? $publicData[AccountManager::PROPERTY_DISPLAYNAME]['value'] : '', + 'email' => isset($publicData[AccountManager::PROPERTY_EMAIL]) ? $publicData[AccountManager::PROPERTY_EMAIL]['value'] : '', + 'address' => isset($publicData[AccountManager::PROPERTY_ADDRESS]) ? $publicData[AccountManager::PROPERTY_ADDRESS]['value'] : '', + 'website' => isset($publicData[AccountManager::PROPERTY_WEBSITE]) ? $publicData[AccountManager::PROPERTY_WEBSITE]['value'] : '', + 'twitter' => isset($publicData[AccountManager::PROPERTY_TWITTER]) ? $publicData[AccountManager::PROPERTY_TWITTER]['value'] : '', + 'phone' => isset($publicData[AccountManager::PROPERTY_PHONE]) ? $publicData[AccountManager::PROPERTY_PHONE]['value'] : '', + ], + 'timeout' => 3, + 'connect_timeout' => 3, + ] + ); + } + + protected function updateExistingRecord(IUser $user, $publicData, $authKey) { + $httpClient = $this->clientService->newClient(); + $httpClient->put($this->lookupServer, + [ + 'body' => [ + 'key' => $authKey, + 'federationid' => $publicData[$user->getCloudId()], + 'name' => isset($publicData[AccountManager::PROPERTY_DISPLAYNAME]) ? $publicData[AccountManager::PROPERTY_DISPLAYNAME]['value'] : '', + 'email' => isset($publicData[AccountManager::PROPERTY_EMAIL]) ? $publicData[AccountManager::PROPERTY_EMAIL]['value'] : '', + 'address' => isset($publicData[AccountManager::PROPERTY_ADDRESS]) ? $publicData[AccountManager::PROPERTY_ADDRESS]['value'] : '', + 'website' => isset($publicData[AccountManager::PROPERTY_WEBSITE]) ? $publicData[AccountManager::PROPERTY_WEBSITE]['value'] : '', + 'twitter' => isset($publicData[AccountManager::PROPERTY_TWITTER]) ? $publicData[AccountManager::PROPERTY_TWITTER]['value'] : '', + 'phone' => isset($publicData[AccountManager::PROPERTY_PHONE]) ? $publicData[AccountManager::PROPERTY_PHONE]['value'] : '', + ], + 'timeout' => 3, + 'connect_timeout' => 3, + ] + ); + } } From 056ce5125e2d4913946a688fed35d9b8849c2e2c Mon Sep 17 00:00:00 2001 From: Roeland Jago Douma Date: Thu, 17 Nov 2016 17:32:14 +0100 Subject: [PATCH 28/53] Fix password display issue Signed-off-by: Roeland Jago Douma --- settings/css/settings.css | 2 ++ 1 file changed, 2 insertions(+) diff --git a/settings/css/settings.css b/settings/css/settings.css index fcff32bc1e366..5ed97a61a2580 100644 --- a/settings/css/settings.css +++ b/settings/css/settings.css @@ -135,6 +135,8 @@ input#openid, input#webdav { width:20em; } #groups { overflow-wrap: break-word; max-width: 75%; + display: block;; + clear: both; } .clientsbox img { From 53c8391e9691ac9eb92adf5b80436f5065944a1f Mon Sep 17 00:00:00 2001 From: Lukas Reschke Date: Thu, 17 Nov 2016 17:35:43 +0100 Subject: [PATCH 29/53] Add private Signed-off-by: Lukas Reschke --- lib/private/Security/IdentityProof/Key.php | 46 ++++++++++ .../Security/IdentityProof/Manager.php | 90 +++++++++++++++++++ 2 files changed, 136 insertions(+) create mode 100644 lib/private/Security/IdentityProof/Key.php create mode 100644 lib/private/Security/IdentityProof/Manager.php diff --git a/lib/private/Security/IdentityProof/Key.php b/lib/private/Security/IdentityProof/Key.php new file mode 100644 index 0000000000000..9739a9571bb1f --- /dev/null +++ b/lib/private/Security/IdentityProof/Key.php @@ -0,0 +1,46 @@ + + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * 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 + * along with this program. If not, see . + * + */ + +namespace OC\Security\IdentityProof; + +class Key { + /** @var string */ + private $publicKey; + /** @var string */ + private $privateKey; + + /** + * @param string $publicKey + * @param string $privateKey + */ + public function __construct($publicKey, $privateKey) { + $this->publicKey = $publicKey; + $this->privateKey = $privateKey; + } + + public function getPrivate() { + return $this->privateKey; + } + + public function getPublic() { + return $this->publicKey; + } +} diff --git a/lib/private/Security/IdentityProof/Manager.php b/lib/private/Security/IdentityProof/Manager.php new file mode 100644 index 0000000000000..b3dba5f278f0b --- /dev/null +++ b/lib/private/Security/IdentityProof/Manager.php @@ -0,0 +1,90 @@ + + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * 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 + * along with this program. If not, see . + * + */ + +namespace OC\Security\IdentityProof; + +use OCP\Files\IAppData; +use OCP\Files\SimpleFS\ISimpleFolder; +use OCP\IUser; +use OCP\Security\ICrypto; + +class Manager { + /** @var ISimpleFolder */ + private $folder; + /** @var ICrypto */ + private $crypto; + + /** + * @param IAppData $appData + * @param ICrypto $crypto + */ + public function __construct(IAppData $appData, + ICrypto $crypto) { + $this->folder = $appData->getFolder('identityproof'); + $this->crypto = $crypto; + } + + /** + * Generate a key for $user + * Note: If a key already exists it will be overwritten + * + * @param IUser $user + * @return Key + */ + public function generateKey(IUser $user) { + $config = [ + 'digest_alg' => 'sha512', + 'private_key_bits' => 2048, + ]; + + // Generate new key + $res = openssl_pkey_new($config); + openssl_pkey_export($res, $privateKey); + + // Extract the public key from $res to $pubKey + $publicKey = openssl_pkey_get_details($res); + $publicKey = $publicKey['key']; + + // Write the private and public key to the disk + $this->folder->newFile($user->getUID() . '.private') + ->putContent($this->crypto->encrypt($privateKey)); + $this->folder->newFile($user->getUID() . '.public') + ->putContent($publicKey); + + return new Key($publicKey, $privateKey); + } + + /** + * Get public and private key for $user + * + * @param IUser $user + * @return Key + */ + public function getKey(IUser $user) { + try { + $privateKey = $this->crypto->decrypt($this->folder->getFile($user->getUID() . '.private')->getContent()); + $publicKey = $this->folder->getFile($user->getUID() . '.public')->getContent(); + return new Key($publicKey, $privateKey); + } catch (\Exception $e) { + return $this->generateKey($user); + } + } +} From 6f4cb12be2d026c45237359e2b0eea2266b86bdf Mon Sep 17 00:00:00 2001 From: Lukas Reschke Date: Thu, 17 Nov 2016 17:35:14 +0100 Subject: [PATCH 30/53] Add identity proof Signed-off-by: Lukas Reschke --- core/Application.php | 11 ++++++- core/Controller/OCSController.php | 32 ++++++++++++++++--- core/routes.php | 1 + .../Security/IdentityProof/Manager.php | 15 ++++----- 4 files changed, 45 insertions(+), 14 deletions(-) diff --git a/core/Application.php b/core/Application.php index f68f7929e6a71..545b5fe420b6d 100644 --- a/core/Application.php +++ b/core/Application.php @@ -30,7 +30,10 @@ namespace OC\Core; +use OC\AppFramework\Utility\SimpleContainer; +use OC\Security\IdentityProof\Manager; use OCP\AppFramework\App; +use OCP\Files\IAppData; use OCP\Util; /** @@ -45,8 +48,14 @@ public function __construct() { $container = $this->getContainer(); - $container->registerService('defaultMailAddress', function() { + $container->registerService('defaultMailAddress', function () { return Util::getDefaultEmailAddress('lostpassword-noreply'); }); + $container->registerService(Manager::class, function () { + return new Manager( + \OC::$server->getAppDataDir('identityproof'), + \OC::$server->getCrypto() + ); + }); } } diff --git a/core/Controller/OCSController.php b/core/Controller/OCSController.php index 27ab9deb08a10..b1c4f377a1433 100644 --- a/core/Controller/OCSController.php +++ b/core/Controller/OCSController.php @@ -23,6 +23,7 @@ use OC\CapabilitiesManager; use OC\Security\Bruteforce\Throttler; +use OC\Security\IdentityProof\Manager; use OCP\AppFramework\Http\DataResponse; use OCP\IRequest; use OCP\IUserManager; @@ -32,13 +33,12 @@ class OCSController extends \OCP\AppFramework\OCSController { /** @var CapabilitiesManager */ private $capabilitiesManager; - /** @var IUserSession */ private $userSession; - /** @var IUserManager */ private $userManager; - + /** @var Manager */ + private $keyManager; /** @var Throttler */ private $throttler; @@ -51,19 +51,21 @@ class OCSController extends \OCP\AppFramework\OCSController { * @param IUserSession $userSession * @param IUserManager $userManager * @param Throttler $throttler + * @param Manager $keyManager */ public function __construct($appName, IRequest $request, CapabilitiesManager $capabilitiesManager, IUserSession $userSession, IUserManager $userManager, - Throttler $throttler) { + Throttler $throttler, + Manager $keyManager) { parent::__construct($appName, $request); - $this->capabilitiesManager = $capabilitiesManager; $this->userSession = $userSession; $this->userManager = $userManager; $this->throttler = $throttler; + $this->keyManager = $keyManager; } /** @@ -139,4 +141,24 @@ public function personCheck($login = '', $password = '') { } return new DataResponse(null, 101); } + + /** + * @PublicPage + * + * @param string $cloudId + * @return DataResponse + */ + public function getIdentityProof($cloudId) { + $userObject = $this->userManager->get($cloudId); + + if($cloudId !== null) { + $key = $this->keyManager->getKey($userObject); + $data = [ + 'public' => $key->getPublic(), + ]; + return new DataResponse($data); + } + + return new DataResponse(101); + } } diff --git a/core/routes.php b/core/routes.php index e5636ff6c007d..2b8080a3b7bee 100644 --- a/core/routes.php +++ b/core/routes.php @@ -61,6 +61,7 @@ ['root' => '/cloud', 'name' => 'OCS#getCurrentUser', 'url' => '/user', 'verb' => 'GET'], ['root' => '', 'name' => 'OCS#getConfig', 'url' => '/config', 'verb' => 'GET'], ['root' => '/person', 'name' => 'OCS#personCheck', 'url' => '/check', 'verb' => 'POST'], + ['root' => '/identityproof', 'name' => 'OCS#getIdentityProof', 'url' => '/key/{cloudId}', 'verb' => 'GET'], ], ]); diff --git a/lib/private/Security/IdentityProof/Manager.php b/lib/private/Security/IdentityProof/Manager.php index b3dba5f278f0b..223af05410b9c 100644 --- a/lib/private/Security/IdentityProof/Manager.php +++ b/lib/private/Security/IdentityProof/Manager.php @@ -22,13 +22,12 @@ namespace OC\Security\IdentityProof; use OCP\Files\IAppData; -use OCP\Files\SimpleFS\ISimpleFolder; use OCP\IUser; use OCP\Security\ICrypto; class Manager { - /** @var ISimpleFolder */ - private $folder; + /** @var IAppData */ + private $appData; /** @var ICrypto */ private $crypto; @@ -38,7 +37,7 @@ class Manager { */ public function __construct(IAppData $appData, ICrypto $crypto) { - $this->folder = $appData->getFolder('identityproof'); + $this->appData = $appData; $this->crypto = $crypto; } @@ -64,9 +63,9 @@ public function generateKey(IUser $user) { $publicKey = $publicKey['key']; // Write the private and public key to the disk - $this->folder->newFile($user->getUID() . '.private') + $this->appData->getFolder($user->getUID())->newFile('private') ->putContent($this->crypto->encrypt($privateKey)); - $this->folder->newFile($user->getUID() . '.public') + $this->appData->getFolder($user->getUID())->newFile('public') ->putContent($publicKey); return new Key($publicKey, $privateKey); @@ -80,8 +79,8 @@ public function generateKey(IUser $user) { */ public function getKey(IUser $user) { try { - $privateKey = $this->crypto->decrypt($this->folder->getFile($user->getUID() . '.private')->getContent()); - $publicKey = $this->folder->getFile($user->getUID() . '.public')->getContent(); + $privateKey = $this->crypto->decrypt($this->appData->getFolder($user->getUID())->getFile('private')->getContent()); + $publicKey = $this->appData->getFolder($user->getUID())->getFile('public')->getContent(); return new Key($publicKey, $privateKey); } catch (\Exception $e) { return $this->generateKey($user); From a32d6e481faacee43f09fa02945a4ba04ffdae50 Mon Sep 17 00:00:00 2001 From: Bjoern Schiessle Date: Thu, 17 Nov 2016 18:51:59 +0100 Subject: [PATCH 31/53] fix unit tests Signed-off-by: Bjoern Schiessle --- settings/Controller/UsersController.php | 5 +- .../Controller/UsersControllerTest.php | 81 ++----------------- 2 files changed, 10 insertions(+), 76 deletions(-) diff --git a/settings/Controller/UsersController.php b/settings/Controller/UsersController.php index 08d97a0f581e3..94f97a356130f 100644 --- a/settings/Controller/UsersController.php +++ b/settings/Controller/UsersController.php @@ -603,8 +603,9 @@ private function saveUserSettings(IUser $user, $data) { // keep the user back-end up-to-date with the latest display name and email // address - if (isset($data['displayName']['value']) && $user->getDisplayName() !== $data['displayName']['value']) { - $result = $user->setDisplayName($data['displayName']['value']); + $oldDisplayName = $user->getDisplayName(); + if (isset($data[AccountManager::PROPERTY_DISPLAYNAME]['value']) && $oldDisplayName !== $data[AccountManager::PROPERTY_DISPLAYNAME]['value']) { + $result = $user->setDisplayName($data[AccountManager::PROPERTY_DISPLAYNAME]['value']); if ($result === false) { throw new ForbiddenException($this->l10n->t('Unable to change full name')); } diff --git a/tests/Settings/Controller/UsersControllerTest.php b/tests/Settings/Controller/UsersControllerTest.php index e2b13b24b8cad..ec92479b40e38 100644 --- a/tests/Settings/Controller/UsersControllerTest.php +++ b/tests/Settings/Controller/UsersControllerTest.php @@ -10,6 +10,7 @@ namespace Tests\Settings\Controller; +use OC\Accounts\AccountManager; use OC\Group\Manager; use OC\Settings\Controller\UsersController; use OCP\App\IAppManager; @@ -57,6 +58,8 @@ class UsersControllerTest extends \Test\TestCase { private $avatarManager; /** @var IL10N|\PHPUnit_Framework_MockObject_MockObject */ private $l; + /** @var AccountManager | \PHPUnit_Framework_MockObject_MockObject */ + private $accountManager; protected function setUp() { parent::setUp(); @@ -71,6 +74,7 @@ protected function setUp() { $this->urlGenerator = $this->createMock(IURLGenerator::class); $this->appManager = $this->createMock(IAppManager::class); $this->avatarManager = $this->createMock(IAvatarManager::class); + $this->accountManager = $this->createMock(AccountManager::class); $this->l = $this->createMock(IL10N::class); $this->l->method('t') ->will($this->returnCallback(function ($text, $parameters = []) { @@ -117,7 +121,8 @@ protected function getController($isAdmin) { 'no-reply@owncloud.com', $this->urlGenerator, $this->appManager, - $this->avatarManager + $this->avatarManager, + $this->accountManager ); } @@ -1760,74 +1765,6 @@ public function testNoAvatar() { $this->assertEquals($expectedResult, $result); } - /** - * @return array - */ - public function setEmailAddressData() { - return [ - /* mailAddress, isValid, expectsUpdate, canChangeDisplayName, responseCode */ - [ '', true, true, true, Http::STATUS_OK ], - [ 'foo@local', true, true, true, Http::STATUS_OK], - [ 'foo@bar@local', false, false, true, Http::STATUS_UNPROCESSABLE_ENTITY], - [ 'foo@local', true, false, false, Http::STATUS_FORBIDDEN], - ]; - } - - /** - * @dataProvider setEmailAddressData - * - * @param string $mailAddress - * @param bool $isValid - * @param bool $expectsUpdate - * @param bool $expectsDelete - */ - public function testSetEmailAddress($mailAddress, $isValid, $expectsUpdate, $canChangeDisplayName, $responseCode) { - $controller = $this->getController(true); - - $user = $this->getMockBuilder('\OC\User\User') - ->disableOriginalConstructor()->getMock(); - $user - ->expects($this->any()) - ->method('getUID') - ->will($this->returnValue('foo')); - $user - ->expects($this->any()) - ->method('canChangeDisplayName') - ->will($this->returnValue($canChangeDisplayName)); - $user - ->expects($expectsUpdate ? $this->once() : $this->never()) - ->method('setEMailAddress') - ->with( - $this->equalTo($mailAddress) - ); - - $this->userSession - ->expects($this->atLeastOnce()) - ->method('getUser') - ->will($this->returnValue($user)); - $this->mailer - ->expects($this->any()) - ->method('validateMailAddress') - ->with($mailAddress) - ->willReturn($isValid); - - if ($isValid) { - $user->expects($this->atLeastOnce()) - ->method('canChangeDisplayName') - ->willReturn(true); - - $this->userManager - ->expects($this->atLeastOnce()) - ->method('get') - ->with('foo') - ->will($this->returnValue($user)); - } - - $response = $controller->setMailAddress($user->getUID(), $mailAddress); - - $this->assertSame($responseCode, $response->getStatus()); - } - public function testStatsAdmin() { $controller = $this->getController(true); @@ -1976,6 +1913,7 @@ public function testSetDisplayName($currentUser, $editUser, $isAdmin, $isSubAdmi ->method('get') ->with($editUser->getUID()) ->willReturn($editUser); + $this->accountManager->expects($this->any())->method('getUser')->willReturn([]); $subadmin = $this->getMockBuilder('\OC\SubAdmin') ->disableOriginalConstructor() @@ -1994,10 +1932,6 @@ public function testSetDisplayName($currentUser, $editUser, $isAdmin, $isSubAdmi ->willReturn($isAdmin); if ($valid === true) { - $editUser->expects($this->once()) - ->method('setDisplayName') - ->with('newDisplayName') - ->willReturn(true); $expectedResponse = new DataResponse( [ 'status' => 'success', @@ -2009,7 +1943,6 @@ public function testSetDisplayName($currentUser, $editUser, $isAdmin, $isSubAdmi ] ); } else { - $editUser->expects($this->never())->method('setDisplayName'); $expectedResponse = new DataResponse( [ 'status' => 'error', From fb91bf6a5b55fa39414add74f86f3f5af21b6552 Mon Sep 17 00:00:00 2001 From: Lukas Reschke Date: Fri, 18 Nov 2016 10:10:05 +0100 Subject: [PATCH 32/53] Add a signer class for signing Signed-off-by: Lukas Reschke --- apps/lookup_server_connector/appinfo/app.php | 14 +- .../lib/UpdateLookupServer.php | 97 ++++------ core/Controller/OCSController.php | 4 +- .../Security/IdentityProof/Manager.php | 37 +++- lib/private/Security/IdentityProof/Signer.php | 120 +++++++++++++ settings/Controller/UsersController.php | 18 +- tests/lib/AppTest.php | 1 + .../Security/IdentityProof/ManagerTest.php | 166 ++++++++++++++++++ 8 files changed, 378 insertions(+), 79 deletions(-) create mode 100644 lib/private/Security/IdentityProof/Signer.php create mode 100644 tests/lib/Security/IdentityProof/ManagerTest.php diff --git a/apps/lookup_server_connector/appinfo/app.php b/apps/lookup_server_connector/appinfo/app.php index dc076b78828b9..d757284f7d540 100644 --- a/apps/lookup_server_connector/appinfo/app.php +++ b/apps/lookup_server_connector/appinfo/app.php @@ -23,11 +23,23 @@ $dispatcher->addListener('OC\AccountManager::userUpdated', function(\Symfony\Component\EventDispatcher\GenericEvent $event) { $user = $event->getSubject(); + + $keyManager = new \OC\Security\IdentityProof\Manager( + \OC::$server->getAppDataDir('identityproof'), + \OC::$server->getCrypto() + ); $updateLookupServer = new \OCA\LookupServerConnector\UpdateLookupServer( new \OC\Accounts\AccountManager(\OC::$server->getDatabaseConnection(), \OC::$server->getEventDispatcher()), \OC::$server->getConfig(), \OC::$server->getSecureRandom(), - \OC::$server->getHTTPClientService() + \OC::$server->getHTTPClientService(), + $keyManager, + new \OC\Security\IdentityProof\Signer( + $keyManager, + new \OC\AppFramework\Utility\TimeFactory(), + \OC::$server->getURLGenerator(), + \OC::$server->getUserManager() + ) ); $updateLookupServer->userUpdated($user); }); diff --git a/apps/lookup_server_connector/lib/UpdateLookupServer.php b/apps/lookup_server_connector/lib/UpdateLookupServer.php index b6d8b9782a42e..fc20ddcd143e9 100644 --- a/apps/lookup_server_connector/lib/UpdateLookupServer.php +++ b/apps/lookup_server_connector/lib/UpdateLookupServer.php @@ -1,6 +1,7 @@ + * @copyright Copyright (c) 2016 Lukas Reschke * * @license GNU AGPL version 3 or any later version * @@ -19,11 +20,11 @@ * */ - namespace OCA\LookupServerConnector; - use OC\Accounts\AccountManager; +use OC\Security\IdentityProof\Manager; +use OC\Security\IdentityProof\Signer; use OCP\Http\Client\IClientService; use OCP\IConfig; use OCP\IUser; @@ -35,45 +36,48 @@ * @package OCA\LookupServerConnector */ class UpdateLookupServer { - - /** @var AccountManager */ + /** @var AccountManager */ private $accountManager; - /** @var IConfig */ private $config; - /** @var ISecureRandom */ private $secureRandom; - /** @var IClientService */ private $clientService; - + /** @var Manager */ + private $keyManager; + /** @var Signer */ + private $signer; /** @var string URL point to lookup server */ - private $lookupServer = 'http://192.168.56.102'; + private $lookupServer = 'http://192.168.176.105/lookup-server/server/'; /** - * UpdateLookupServer constructor. - * * @param AccountManager $accountManager * @param IConfig $config * @param ISecureRandom $secureRandom * @param IClientService $clientService + * @param Manager $manager + * @param Signer $signer */ public function __construct(AccountManager $accountManager, IConfig $config, ISecureRandom $secureRandom, - IClientService $clientService) { + IClientService $clientService, + Manager $manager, + Signer $signer) { $this->accountManager = $accountManager; $this->config = $config; $this->secureRandom = $secureRandom; $this->clientService = $clientService; + $this->keyManager = $manager; + $this->signer = $signer; } - + /** + * @param IUser $user + */ public function userUpdated(IUser $user) { $userData = $this->accountManager->getUser($user); - $authKey = $this->config->getUserValue($user->getUID(), 'lookup_server_connector', 'authKey'); - $publicData = []; foreach ($userData as $key => $data) { @@ -83,13 +87,15 @@ public function userUpdated(IUser $user) { } if (empty($publicData) && !empty($authKey)) { - $this->removeFromLookupServer($user, $authKey); + $this->removeFromLookupServer($user); } else { - $this->sendToLookupServer($user, $publicData, $authKey); + $this->sendToLookupServer($user, $publicData); } } /** + * TODO: FIXME. Implement removal from lookup server. + * * remove user from lookup server * * @param IUser $user @@ -103,56 +109,25 @@ protected function removeFromLookupServer(IUser $user) { * * @param IUser $user * @param array $publicData - * @param string $authKey */ - protected function sendToLookupServer(IUser $user, $publicData, $authKey) { - if (empty($authKey)) { - $authKey = $this->secureRandom->generate(16); - $this->sendNewRecord($user, $publicData, $authKey); - $this->config->setUserValue($user->getUID(), 'lookup_server_connector', 'authKey', $authKey); - } else { - $this->updateExistingRecord($user, $publicData, $authKey); - } - } - - protected function sendNewRecord(IUser $user, $publicData, $authKey) { + protected function sendToLookupServer(IUser $user, array $publicData) { + $dataArray = [ + 'federationId' => $user->getCloudId(), + 'name' => isset($publicData[AccountManager::PROPERTY_DISPLAYNAME]) ? $publicData[AccountManager::PROPERTY_DISPLAYNAME]['value'] : '', + 'email' => isset($publicData[AccountManager::PROPERTY_EMAIL]) ? $publicData[AccountManager::PROPERTY_EMAIL]['value'] : '', + 'address' => isset($publicData[AccountManager::PROPERTY_ADDRESS]) ? $publicData[AccountManager::PROPERTY_ADDRESS]['value'] : '', + 'website' => isset($publicData[AccountManager::PROPERTY_WEBSITE]) ? $publicData[AccountManager::PROPERTY_WEBSITE]['value'] : '', + 'twitter' => isset($publicData[AccountManager::PROPERTY_TWITTER]) ? $publicData[AccountManager::PROPERTY_TWITTER]['value'] : '', + 'phone' => isset($publicData[AccountManager::PROPERTY_PHONE]) ? $publicData[AccountManager::PROPERTY_PHONE]['value'] : '', + ]; + $dataArray = $this->signer->sign('lookupserver', $dataArray, $user); $httpClient = $this->clientService->newClient(); - $response = $httpClient->post($this->lookupServer, + $httpClient->post($this->lookupServer, [ - 'body' => [ - 'key' => $authKey, - 'federationid' => $publicData[$user->getCloudId()], - 'name' => isset($publicData[AccountManager::PROPERTY_DISPLAYNAME]) ? $publicData[AccountManager::PROPERTY_DISPLAYNAME]['value'] : '', - 'email' => isset($publicData[AccountManager::PROPERTY_EMAIL]) ? $publicData[AccountManager::PROPERTY_EMAIL]['value'] : '', - 'address' => isset($publicData[AccountManager::PROPERTY_ADDRESS]) ? $publicData[AccountManager::PROPERTY_ADDRESS]['value'] : '', - 'website' => isset($publicData[AccountManager::PROPERTY_WEBSITE]) ? $publicData[AccountManager::PROPERTY_WEBSITE]['value'] : '', - 'twitter' => isset($publicData[AccountManager::PROPERTY_TWITTER]) ? $publicData[AccountManager::PROPERTY_TWITTER]['value'] : '', - 'phone' => isset($publicData[AccountManager::PROPERTY_PHONE]) ? $publicData[AccountManager::PROPERTY_PHONE]['value'] : '', - ], + 'body' => $dataArray, 'timeout' => 3, 'connect_timeout' => 3, ] ); } - - protected function updateExistingRecord(IUser $user, $publicData, $authKey) { - $httpClient = $this->clientService->newClient(); - $httpClient->put($this->lookupServer, - [ - 'body' => [ - 'key' => $authKey, - 'federationid' => $publicData[$user->getCloudId()], - 'name' => isset($publicData[AccountManager::PROPERTY_DISPLAYNAME]) ? $publicData[AccountManager::PROPERTY_DISPLAYNAME]['value'] : '', - 'email' => isset($publicData[AccountManager::PROPERTY_EMAIL]) ? $publicData[AccountManager::PROPERTY_EMAIL]['value'] : '', - 'address' => isset($publicData[AccountManager::PROPERTY_ADDRESS]) ? $publicData[AccountManager::PROPERTY_ADDRESS]['value'] : '', - 'website' => isset($publicData[AccountManager::PROPERTY_WEBSITE]) ? $publicData[AccountManager::PROPERTY_WEBSITE]['value'] : '', - 'twitter' => isset($publicData[AccountManager::PROPERTY_TWITTER]) ? $publicData[AccountManager::PROPERTY_TWITTER]['value'] : '', - 'phone' => isset($publicData[AccountManager::PROPERTY_PHONE]) ? $publicData[AccountManager::PROPERTY_PHONE]['value'] : '', - ], - 'timeout' => 3, - 'connect_timeout' => 3, - ] - ); - - } } diff --git a/core/Controller/OCSController.php b/core/Controller/OCSController.php index b1c4f377a1433..c59b0d7ad3fc1 100644 --- a/core/Controller/OCSController.php +++ b/core/Controller/OCSController.php @@ -151,7 +151,7 @@ public function personCheck($login = '', $password = '') { public function getIdentityProof($cloudId) { $userObject = $this->userManager->get($cloudId); - if($cloudId !== null) { + if($userObject !== null) { $key = $this->keyManager->getKey($userObject); $data = [ 'public' => $key->getPublic(), @@ -159,6 +159,6 @@ public function getIdentityProof($cloudId) { return new DataResponse($data); } - return new DataResponse(101); + return new DataResponse('User not found', 404); } } diff --git a/lib/private/Security/IdentityProof/Manager.php b/lib/private/Security/IdentityProof/Manager.php index 223af05410b9c..d2a9e57e3387e 100644 --- a/lib/private/Security/IdentityProof/Manager.php +++ b/lib/private/Security/IdentityProof/Manager.php @@ -42,13 +42,12 @@ public function __construct(IAppData $appData, } /** - * Generate a key for $user - * Note: If a key already exists it will be overwritten + * Calls the openssl functions to generate a public and private key. + * In a separate function for unit testing purposes. * - * @param IUser $user - * @return Key + * @return array [$publicKey, $privateKey] */ - public function generateKey(IUser $user) { + protected function generateKeyPair() { $config = [ 'digest_alg' => 'sha512', 'private_key_bits' => 2048, @@ -62,10 +61,27 @@ public function generateKey(IUser $user) { $publicKey = openssl_pkey_get_details($res); $publicKey = $publicKey['key']; + return [$publicKey, $privateKey]; + } + + /** + * Generate a key for $user + * Note: If a key already exists it will be overwritten + * + * @param IUser $user + * @return Key + */ + protected function generateKey(IUser $user) { + list($publicKey, $privateKey) = $this->generateKeyPair(); + // Write the private and public key to the disk - $this->appData->getFolder($user->getUID())->newFile('private') + try { + $this->appData->newFolder($user->getUID()); + } catch (\Exception $e) {} + $folder = $this->appData->getFolder($user->getUID()); + $folder->newFile('private') ->putContent($this->crypto->encrypt($privateKey)); - $this->appData->getFolder($user->getUID())->newFile('public') + $folder->newFile('public') ->putContent($publicKey); return new Key($publicKey, $privateKey); @@ -79,8 +95,11 @@ public function generateKey(IUser $user) { */ public function getKey(IUser $user) { try { - $privateKey = $this->crypto->decrypt($this->appData->getFolder($user->getUID())->getFile('private')->getContent()); - $publicKey = $this->appData->getFolder($user->getUID())->getFile('public')->getContent(); + $folder = $this->appData->getFolder($user->getUID()); + $privateKey = $this->crypto->decrypt( + $folder->getFile('private')->getContent() + ); + $publicKey = $folder->getFile('public')->getContent(); return new Key($publicKey, $privateKey); } catch (\Exception $e) { return $this->generateKey($user); diff --git a/lib/private/Security/IdentityProof/Signer.php b/lib/private/Security/IdentityProof/Signer.php new file mode 100644 index 0000000000000..50c36b26966ac --- /dev/null +++ b/lib/private/Security/IdentityProof/Signer.php @@ -0,0 +1,120 @@ + + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * 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 + * along with this program. If not, see . + * + */ + +namespace OC\Security\IdentityProof; + +use OCP\AppFramework\Utility\ITimeFactory; +use OCP\IURLGenerator; +use OCP\IUser; +use OCP\IUserManager; + +class Signer { + /** @var Manager */ + private $keyManager; + /** @var ITimeFactory */ + private $timeFactory; + /** @var IURLGenerator */ + private $urlGenerator; + /** @var IUserManager */ + private $userManager; + + /** + * @param Manager $keyManager + * @param ITimeFactory $timeFactory + * @param IURLGenerator $urlGenerator + * @param IUserManager $userManager + */ + public function __construct(Manager $keyManager, + ITimeFactory $timeFactory, + IURLGenerator $urlGenerator, + IUserManager $userManager) { + $this->keyManager = $keyManager; + $this->timeFactory = $timeFactory; + $this->userManager = $userManager; + } + + /** + * Returns a signed blob for $data + * + * @param string $type + * @param array $data + * @param IUser $user + * @return array ['message', 'signature'] + */ + public function sign($type, array $data, IUser $user) { + $privateKey = $this->keyManager->getKey($user)->getPrivate(); + $data = [ + 'data' => $data, + 'type' => $type, + 'signer' => $user->getCloudId(), + 'timestamp' => $this->timeFactory->getTime(), + ]; + openssl_sign(json_encode($data), $signature, $privateKey, OPENSSL_ALGO_SHA512); + + return [ + 'message' => $data, + 'signature' => base64_encode($signature), + ]; + } + + /** + * @param string $url + * @return string + */ + private function removeProtocolFromUrl($url) { + if (strpos($url, 'https://') === 0) { + return substr($url, strlen('https://')); + } else if (strpos($url, 'http://') === 0) { + return substr($url, strlen('http://')); + } + + return $url; + } + + /** + * Whether the data is signed properly + * + * @param array $data + * @return bool + */ + public function verify(array $data) { + if(isset($data['message']) + && isset($data['signature']) + && isset($data['message']['signer']) + ) { + $server = $this->urlGenerator->getAbsoluteURL('/'); + $postfix = strlen('@' . rtrim($this->removeProtocolFromUrl($server), '/')); + $userId = substr($data['message']['signer'], -$postfix); + + $user = $this->userManager->get($userId); + if($user !== null) { + $key = $this->keyManager->getKey($user); + return (bool)openssl_verify( + json_encode($data['message']), + base64_decode($data['signature']), + $key->getPublic() + ); + } + } + + return false; + } +} diff --git a/settings/Controller/UsersController.php b/settings/Controller/UsersController.php index 94f97a356130f..41f3bac733dea 100644 --- a/settings/Controller/UsersController.php +++ b/settings/Controller/UsersController.php @@ -524,12 +524,18 @@ public function destroy($id) { * @return DataResponse */ public function setUserSettings($avatarScope, - $displayname, $displaynameScope, - $phone, $phoneScope, - $email, $emailScope, - $website, $websiteScope, - $address, $addressScope, - $twitter, $twitterScope + $displayname, + $displaynameScope, + $phone, + $phoneScope, + $email, + $emailScope, + $website, + $websiteScope, + $address, + $addressScope, + $twitter, + $twitterScope ) { diff --git a/tests/lib/AppTest.php b/tests/lib/AppTest.php index 971d86cf6a480..6017c029644fa 100644 --- a/tests/lib/AppTest.php +++ b/tests/lib/AppTest.php @@ -422,6 +422,7 @@ function appConfigValuesProvider() { 'appforgroup2', 'dav', 'federatedfilesharing', + 'lookup_server_connector', 'provisioning_api', 'twofactor_backupcodes', 'workflowengine', diff --git a/tests/lib/Security/IdentityProof/ManagerTest.php b/tests/lib/Security/IdentityProof/ManagerTest.php new file mode 100644 index 0000000000000..d93f675825b8d --- /dev/null +++ b/tests/lib/Security/IdentityProof/ManagerTest.php @@ -0,0 +1,166 @@ + + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * 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 + * along with this program. If not, see . + * + */ + +namespace Test\Security; + +use OC\Security\IdentityProof\Key; +use OC\Security\IdentityProof\Manager; +use OCP\Files\IAppData; +use OCP\Files\SimpleFS\ISimpleFile; +use OCP\Files\SimpleFS\ISimpleFolder; +use OCP\IUser; +use OCP\Security\ICrypto; +use Test\TestCase; + +class ManagerTest extends TestCase { + /** @var IAppData|\PHPUnit_Framework_MockObject_MockObject */ + private $appData; + /** @var ICrypto|\PHPUnit_Framework_MockObject_MockObject */ + private $crypto; + /** @var Manager|\PHPUnit_Framework_MockObject_MockObject */ + private $manager; + + public function setUp() { + parent::setUp(); + $this->appData = $this->createMock(IAppData::class); + $this->crypto = $this->createMock(ICrypto::class); + $this->manager = $this->getMockBuilder(Manager::class) + ->setConstructorArgs([ + $this->appData, + $this->crypto + ]) + ->setMethods(['generateKeyPair']) + ->getMock(); + } + + public function testGetKeyWithExistingKey() { + $user = $this->createMock(IUser::class); + $user + ->expects($this->once()) + ->method('getUID') + ->willReturn('MyUid'); + $folder = $this->createMock(ISimpleFolder::class); + $privateFile = $this->createMock(ISimpleFile::class); + $privateFile + ->expects($this->once()) + ->method('getContent') + ->willReturn('EncryptedPrivateKey'); + $publicFile = $this->createMock(ISimpleFile::class); + $publicFile + ->expects($this->once()) + ->method('getContent') + ->willReturn('MyPublicKey'); + $this->crypto + ->expects($this->once()) + ->method('decrypt') + ->with('EncryptedPrivateKey') + ->willReturn('MyPrivateKey'); + $folder + ->expects($this->at(0)) + ->method('getFile') + ->with('private') + ->willReturn($privateFile); + $folder + ->expects($this->at(1)) + ->method('getFile') + ->with('public') + ->willReturn($publicFile); + $this->appData + ->expects($this->once()) + ->method('getFolder') + ->with('MyUid') + ->willReturn($folder); + + $expected = new Key('MyPublicKey', 'MyPrivateKey'); + $this->assertEquals($expected, $this->manager->getKey($user)); + } + + public function testGetKeyWithNotExistingKey() { + $user = $this->createMock(IUser::class); + $user + ->expects($this->exactly(3)) + ->method('getUID') + ->willReturn('MyUid'); + $this->appData + ->expects($this->at(0)) + ->method('getFolder') + ->with('MyUid') + ->willThrowException(new \Exception()); + $this->manager + ->expects($this->once()) + ->method('generateKeyPair') + ->willReturn(['MyNewPublicKey', 'MyNewPrivateKey']); + $this->appData + ->expects($this->at(1)) + ->method('newFolder') + ->with('MyUid'); + $folder = $this->createMock(ISimpleFolder::class); + $this->crypto + ->expects($this->once()) + ->method('encrypt') + ->with('MyNewPrivateKey') + ->willReturn('MyNewEncryptedPrivateKey'); + $privateFile = $this->createMock(ISimpleFile::class); + $privateFile + ->expects($this->once()) + ->method('putContent') + ->with('MyNewEncryptedPrivateKey'); + $publicFile = $this->createMock(ISimpleFile::class); + $publicFile + ->expects($this->once()) + ->method('putContent') + ->with('MyNewPublicKey'); + $folder + ->expects($this->at(0)) + ->method('newFile') + ->with('private') + ->willReturn($privateFile); + $folder + ->expects($this->at(1)) + ->method('newFile') + ->with('public') + ->willReturn($publicFile); + $this->appData + ->expects($this->at(2)) + ->method('getFolder') + ->with('MyUid') + ->willReturn($folder); + + + $expected = new Key('MyNewPublicKey', 'MyNewPrivateKey'); + $this->assertEquals($expected, $this->manager->getKey($user)); + } + + public function testGenerateKeyPair() { + $manager = new Manager( + $this->appData, + $this->crypto + ); + $data = 'MyTestData'; + + list($resultPublicKey, $resultPrivateKey) = $this->invokePrivate($manager, 'generateKeyPair'); + openssl_sign($data, $signature, $resultPrivateKey); + $details = openssl_pkey_get_details(openssl_pkey_get_public($resultPublicKey)); + + $this->assertSame(1, openssl_verify($data, $signature, $resultPublicKey)); + $this->assertSame(2048, $details['bits']); + } +} From 662dff046d7b287c380656a3c0302cd63736e753 Mon Sep 17 00:00:00 2001 From: Lukas Reschke Date: Fri, 18 Nov 2016 10:20:04 +0100 Subject: [PATCH 33/53] Adjust permission checks Signed-off-by: Lukas Reschke --- settings/Controller/UsersController.php | 14 ++++---------- settings/templates/personal.php | 12 ++++++------ 2 files changed, 10 insertions(+), 16 deletions(-) diff --git a/settings/Controller/UsersController.php b/settings/Controller/UsersController.php index 41f3bac733dea..fa97845dfba07 100644 --- a/settings/Controller/UsersController.php +++ b/settings/Controller/UsersController.php @@ -502,8 +502,6 @@ public function destroy($id) { } /** - * @todo add method description - * * @NoAdminRequired * @NoSubadminRequired * @PasswordConfirmationRequired @@ -673,6 +671,8 @@ public function stats() { * @PasswordConfirmationRequired * @todo merge into saveUserSettings * + * @NoAdminRequired + * * @param string $username * @param string $displayName * @return DataResponse @@ -681,14 +681,8 @@ public function setDisplayName($username, $displayName) { $currentUser = $this->userSession->getUser(); $user = $this->userManager->get($username); - if ($user === null || - !$user->canChangeDisplayName() || - ( - !$this->groupManager->isAdmin($currentUser->getUID()) && - !$this->groupManager->getSubAdmin()->isUserAccessible($currentUser, $user) && - $currentUser->getUID() !== $username - - ) + if (!$this->groupManager->isAdmin($currentUser->getUID()) && + !$this->groupManager->getSubAdmin()->isUserAccessible($currentUser, $user) ) { return new DataResponse([ 'status' => 'error', diff --git a/settings/templates/personal.php b/settings/templates/personal.php index 519ffa273d93b..f5050ab190e0f 100644 --- a/settings/templates/personal.php +++ b/settings/templates/personal.php @@ -114,7 +114,7 @@

- +

t('Password'));?>
- + - + - +
Date: Fri, 18 Nov 2016 11:55:37 +0100 Subject: [PATCH 34/53] Fix changing display names for subadmins Signed-off-by: Lukas Reschke --- settings/Controller/UsersController.php | 10 ++++- tests/Core/Controller/OCSControllerTest.php | 49 ++++++++++++++++++--- 2 files changed, 50 insertions(+), 9 deletions(-) diff --git a/settings/Controller/UsersController.php b/settings/Controller/UsersController.php index fa97845dfba07..8f077270392bc 100644 --- a/settings/Controller/UsersController.php +++ b/settings/Controller/UsersController.php @@ -681,8 +681,14 @@ public function setDisplayName($username, $displayName) { $currentUser = $this->userSession->getUser(); $user = $this->userManager->get($username); - if (!$this->groupManager->isAdmin($currentUser->getUID()) && - !$this->groupManager->getSubAdmin()->isUserAccessible($currentUser, $user) + if ($user === null || + !$user->canChangeDisplayName() || + ( + !$this->groupManager->isAdmin($currentUser->getUID()) && + !$this->groupManager->getSubAdmin()->isUserAccessible($currentUser, $user) && + $currentUser->getUID() !== $username + + ) ) { return new DataResponse([ 'status' => 'error', diff --git a/tests/Core/Controller/OCSControllerTest.php b/tests/Core/Controller/OCSControllerTest.php index 38356483c9544..6c47521786f74 100644 --- a/tests/Core/Controller/OCSControllerTest.php +++ b/tests/Core/Controller/OCSControllerTest.php @@ -24,6 +24,8 @@ use OC\CapabilitiesManager; use OC\Security\Bruteforce\Throttler; +use OC\Security\IdentityProof\Key; +use OC\Security\IdentityProof\Manager; use OCP\AppFramework\Http\DataResponse; use OCP\IRequest; use OCP\IUser; @@ -32,22 +34,18 @@ use Test\TestCase; class OCSControllerTest extends TestCase { - /** @var IRequest|\PHPUnit_Framework_MockObject_MockObject */ private $request; - /** @var CapabilitiesManager|\PHPUnit_Framework_MockObject_MockObject */ private $capabilitiesManager; - /** @var IUserSession|\PHPUnit_Framework_MockObject_MockObject */ private $userSession; - /** @var IUserManager|\PHPUnit_Framework_MockObject_MockObject */ private $userManager; - /** @var Throttler|\PHPUnit_Framework_MockObject_MockObject */ private $throttler; - + /** @var Manager|\PHPUnit_Framework_MockObject_MockObject */ + private $keyManager; /** @var OCSController */ private $controller; @@ -59,6 +57,7 @@ public function setUp() { $this->userSession = $this->createMock(IUserSession::class); $this->userManager = $this->createMock(IUserManager::class); $this->throttler = $this->createMock(Throttler::class); + $this->keyManager = $this->createMock(Manager::class); $this->controller = new OCSController( 'core', @@ -66,7 +65,8 @@ public function setUp() { $this->capabilitiesManager, $this->userSession, $this->userManager, - $this->throttler + $this->throttler, + $this->keyManager ); } @@ -206,4 +206,39 @@ public function testPersonNoLogin() { $this->assertEquals($expected, $this->controller->personCheck('', '')); } + + public function testGetIdentityProofWithNotExistingUser() { + $this->userManager + ->expects($this->once()) + ->method('get') + ->with('NotExistingUser') + ->willReturn(null); + + $expected = new DataResponse('User not found', 404); + $this->assertEquals($expected, $this->controller->getIdentityProof('NotExistingUser')); + } + + public function testGetIdentityProof() { + $user = $this->createMock(IUser::class); + $key = $this->createMock(Key::class); + $this->userManager + ->expects($this->once()) + ->method('get') + ->with('ExistingUser') + ->willReturn($user); + $this->keyManager + ->expects($this->once()) + ->method('getKey') + ->with($user) + ->willReturn($key); + $key + ->expects($this->once()) + ->method('getPublic') + ->willReturn('Existing Users public key'); + + $expected = new DataResponse([ + 'public' => 'Existing Users public key', + ]); + $this->assertEquals($expected, $this->controller->getIdentityProof('ExistingUser')); + } } From f7f70028d19d3191d0091f3c5d1b43aa1ac2d4eb Mon Sep 17 00:00:00 2001 From: Roeland Jago Douma Date: Fri, 18 Nov 2016 14:08:42 +0100 Subject: [PATCH 35/53] Search on lookup server Signed-off-by: Roeland Jago Douma --- .../lib/Controller/ShareesAPIController.php | 44 +++++++++++++++++-- .../lib/UpdateLookupServer.php | 2 +- core/js/sharedialogview.js | 4 +- 3 files changed, 44 insertions(+), 6 deletions(-) diff --git a/apps/files_sharing/lib/Controller/ShareesAPIController.php b/apps/files_sharing/lib/Controller/ShareesAPIController.php index 09912b7758a05..d9540aba1d9dc 100644 --- a/apps/files_sharing/lib/Controller/ShareesAPIController.php +++ b/apps/files_sharing/lib/Controller/ShareesAPIController.php @@ -28,6 +28,7 @@ use OCP\AppFramework\OCS\OCSBadRequestException; use OCP\AppFramework\OCSController; use OCP\Contacts\IManager; +use OCP\Http\Client\IClientService; use OCP\IGroup; use OCP\IGroupManager; use OCP\ILogger; @@ -65,6 +66,9 @@ class ShareesAPIController extends OCSController { /** @var \OCP\Share\IManager */ protected $shareManager; + /** @var IClientService */ + protected $clientService; + /** @var bool */ protected $shareWithGroupOnly = false; @@ -89,6 +93,7 @@ class ShareesAPIController extends OCSController { 'groups' => [], 'remotes' => [], 'emails' => [], + 'lookup' => [], ]; protected $reachedEndFor = []; @@ -104,6 +109,7 @@ class ShareesAPIController extends OCSController { * @param IURLGenerator $urlGenerator * @param ILogger $logger * @param \OCP\Share\IManager $shareManager + * @param IClientService $clientService */ public function __construct($appName, IRequest $request, @@ -114,7 +120,8 @@ public function __construct($appName, IUserSession $userSession, IURLGenerator $urlGenerator, ILogger $logger, - \OCP\Share\IManager $shareManager) { + \OCP\Share\IManager $shareManager, + IClientService $clientService) { parent::__construct($appName, $request); $this->groupManager = $groupManager; @@ -125,6 +132,7 @@ public function __construct($appName, $this->urlGenerator = $urlGenerator; $this->logger = $logger; $this->shareManager = $shareManager; + $this->clientService = $clientService; } /** @@ -414,10 +422,11 @@ protected function fixRemoteURL($remote) { * @param int $page * @param int $perPage * @param int|int[] $shareType + * @param bool $lookup * @return Http\DataResponse * @throws OCSBadRequestException */ - public function search($search = '', $itemType = null, $page = 1, $perPage = 200, $shareType = null) { + public function search($search = '', $itemType = null, $page = 1, $perPage = 200, $shareType = null, $lookup = true) { if ($perPage <= 0) { throw new OCSBadRequestException('Invalid perPage argument'); } @@ -459,7 +468,7 @@ public function search($search = '', $itemType = null, $page = 1, $perPage = 200 $this->limit = (int) $perPage; $this->offset = $perPage * ($page - 1); - return $this->searchSharees($search, $itemType, $shareTypes, $page, $perPage); + return $this->searchSharees($search, $itemType, $shareTypes, $page, $perPage, $lookup); } /** @@ -485,10 +494,11 @@ protected function isRemoteSharingAllowed($itemType) { * @param array $shareTypes * @param int $page * @param int $perPage + * @param bool $lookup * @return Http\DataResponse * @throws OCSBadRequestException */ - protected function searchSharees($search, $itemType, array $shareTypes, $page, $perPage) { + protected function searchSharees($search, $itemType, array $shareTypes, $page, $perPage, $lookup) { // Verify arguments if ($itemType === null) { throw new OCSBadRequestException('Missing itemType'); @@ -510,11 +520,17 @@ protected function searchSharees($search, $itemType, array $shareTypes, $page, $ $remoteResults = $this->getRemote($search); } + // Get emails $mailResults = ['results' => [], 'exact' => [], 'exactIdMatch' => false]; if (in_array(Share::SHARE_TYPE_EMAIL, $shareTypes)) { $mailResults = $this->getEmail($search); } + // Get from lookup server + if ($lookup) { + $this->getLookup($search); + } + // if we have a exact match, either for the federated cloud id or for the // email address we only return the exact match. It is highly unlikely // that the exact same email address and federated cloud id exists @@ -609,6 +625,26 @@ protected function getEmail($search) { return $result; } + protected function getLookup($search) { + $client = $this->clientService->newClient(); + + $response = $client->get('http://127.0.0.1:3000/users?search='.urlencode($search)); + $body = json_decode($response->getBody(), true); + + $result = []; + foreach ($body as $lookup) { + $result[] = [ + 'label' => $lookup['federationId'], + 'value' => [ + 'shareType' => Share::SHARE_TYPE_REMOTE, + 'shareWith' => $lookup['federationId'], + ], + 'extra' => $lookup, + ]; + } + $this->result['lookup'] = $result; + } + /** * Generates a bunch of pagination links for the current page * diff --git a/apps/lookup_server_connector/lib/UpdateLookupServer.php b/apps/lookup_server_connector/lib/UpdateLookupServer.php index fc20ddcd143e9..abbc0e2b2d585 100644 --- a/apps/lookup_server_connector/lib/UpdateLookupServer.php +++ b/apps/lookup_server_connector/lib/UpdateLookupServer.php @@ -49,7 +49,7 @@ class UpdateLookupServer { /** @var Signer */ private $signer; /** @var string URL point to lookup server */ - private $lookupServer = 'http://192.168.176.105/lookup-server/server/'; + private $lookupServer = 'http://127.0.0.1:3000/index2.php/users'; /** * @param AccountManager $accountManager diff --git a/core/js/sharedialogview.js b/core/js/sharedialogview.js index 0a29dec73ca26..6377d16dd4c70 100644 --- a/core/js/sharedialogview.js +++ b/core/js/sharedialogview.js @@ -149,6 +149,7 @@ var users = result.ocs.data.exact.users.concat(result.ocs.data.users); var groups = result.ocs.data.exact.groups.concat(result.ocs.data.groups); var remotes = result.ocs.data.exact.remotes.concat(result.ocs.data.remotes); + var lookup = result.ocs.data.lookup; if (typeof(result.ocs.data.emails) !== 'undefined') { var emails = result.ocs.data.exact.emails.concat(result.ocs.data.emails); } else { @@ -159,6 +160,7 @@ var groupsLength; var remotesLength; var emailsLength; + var lookupLength; var i, j; @@ -224,7 +226,7 @@ } } - var suggestions = users.concat(groups).concat(remotes).concat(emails); + var suggestions = users.concat(groups).concat(remotes).concat(emails).concat(lookup); if (suggestions.length > 0) { $('.shareWithField').removeClass('error') From c20c7f31da84ac1330707b3f23e75b9ade879206 Mon Sep 17 00:00:00 2001 From: Roeland Jago Douma Date: Fri, 18 Nov 2016 14:19:47 +0100 Subject: [PATCH 36/53] Push json to the lookupserver Signed-off-by: Roeland Jago Douma --- apps/lookup_server_connector/lib/UpdateLookupServer.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/lookup_server_connector/lib/UpdateLookupServer.php b/apps/lookup_server_connector/lib/UpdateLookupServer.php index abbc0e2b2d585..e147536bb8652 100644 --- a/apps/lookup_server_connector/lib/UpdateLookupServer.php +++ b/apps/lookup_server_connector/lib/UpdateLookupServer.php @@ -49,7 +49,7 @@ class UpdateLookupServer { /** @var Signer */ private $signer; /** @var string URL point to lookup server */ - private $lookupServer = 'http://127.0.0.1:3000/index2.php/users'; + private $lookupServer = 'http://127.0.0.1:3000/users'; /** * @param AccountManager $accountManager @@ -124,8 +124,8 @@ protected function sendToLookupServer(IUser $user, array $publicData) { $httpClient = $this->clientService->newClient(); $httpClient->post($this->lookupServer, [ - 'body' => $dataArray, - 'timeout' => 3, + 'body' => json_encode($dataArray), + 'timeout' => 10, 'connect_timeout' => 3, ] ); From e53e585e91d39addc95c660d9780adc766a6ebec Mon Sep 17 00:00:00 2001 From: Jan-Christoph Borchardt Date: Fri, 18 Nov 2016 14:27:34 +0100 Subject: [PATCH 37/53] put explanatory text directly in popup Signed-off-by: Jan-Christoph Borchardt --- core/css/apps.css | 6 ++++-- core/css/header.css | 8 ++++---- settings/css/settings.css | 20 ++++++++++++++------ settings/js/federationscopemenu.js | 6 +++--- 4 files changed, 25 insertions(+), 15 deletions(-) diff --git a/core/css/apps.css b/core/css/apps.css index ee640d5464188..bef5c7c4937ec 100644 --- a/core/css/apps.css +++ b/core/css/apps.css @@ -333,7 +333,8 @@ opacity: .5 !important; } .bubble .action:hover, -.bubble .action:focus { +.bubble .action:focus, +.bubble .action.active { -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=100)" !important; filter: alpha(opacity=100) !important; opacity: 1 !important; @@ -646,7 +647,8 @@ em { } .popovermenu .menuitem:hover, -.popovermenu .menuitem:focus { +.popovermenu .menuitem:focus, +.popovermenu .menuitem.active { -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=100)"; filter: alpha(opacity=100); opacity: 1; diff --git a/core/css/header.css b/core/css/header.css index efdd3be7cebea..19ecf77bcd347 100644 --- a/core/css/header.css +++ b/core/css/header.css @@ -227,8 +227,8 @@ #apps-management a:hover span, #apps-management a:focus span, #apps-management a.active span { - -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=75)"; - opacity: .75; + -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=100)"; + opacity: 1; } #navigation .app-icon { @@ -372,8 +372,8 @@ #expanddiv a:focus, #expanddiv a:active, #expanddiv a.active { - -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=75)"; - opacity: .75; + -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=100)"; + opacity: 1; } /* do not show display name when profile picture is present */ diff --git a/settings/css/settings.css b/settings/css/settings.css index 5ed97a61a2580..74e19a220113c 100644 --- a/settings/css/settings.css +++ b/settings/css/settings.css @@ -90,9 +90,11 @@ input#openid, input#webdav { width:20em; } #personal-settings-container > div h2 span[class^="icon-"], #personal-settings-avatar-container h2 span[class^="icon-"] { display: inline-block; - margin-left: 5px; - background-size: 110%; - opacity: 0.3; + padding: 8px; + margin-left: -5px; + margin-bottom: -8px; + background-size: 22px; + opacity: .3; cursor: pointer; } .personal-settings-setting-box input[type="text"], @@ -112,10 +114,16 @@ input#openid, input#webdav { width:20em; } .federationScopeMenu.bubble::after { right: 50%; transform: translate(50%, 0); + margin-right: -8px; +} +.federationScopeMenu.popovermenu a.menuitem, +.federationScopeMenu.popovermenu label.menuitem, +.federationScopeMenu.popovermenu .menuitem { + font-size: 12.8px; + line-height: 1.6em; } -.federationScopeMenu.popovermenu { - font-weight: 100; - font-size: medium; +.federationScopeMenu.popovermenu .menuitem .menuitem-text-detail { + opacity: .75; } #lostpassword, diff --git a/settings/js/federationscopemenu.js b/settings/js/federationscopemenu.js index e129f69fe9e0e..a5da04df918c2 100644 --- a/settings/js/federationscopemenu.js +++ b/settings/js/federationscopemenu.js @@ -16,7 +16,7 @@ '
'; @@ -134,4 +135,3 @@ OC.Settings.FederationScopeMenu = FederationScopeMenu; })(); - From 15c075eba8b28b524b24256169b03e9ec9f2c968 Mon Sep 17 00:00:00 2001 From: Roeland Jago Douma Date: Fri, 18 Nov 2016 14:28:14 +0100 Subject: [PATCH 38/53] Fix lookup url Signed-off-by: Roeland Jago Douma --- apps/files_sharing/lib/Controller/ShareesAPIController.php | 2 +- apps/lookup_server_connector/lib/UpdateLookupServer.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/files_sharing/lib/Controller/ShareesAPIController.php b/apps/files_sharing/lib/Controller/ShareesAPIController.php index d9540aba1d9dc..7b9e10090d2e7 100644 --- a/apps/files_sharing/lib/Controller/ShareesAPIController.php +++ b/apps/files_sharing/lib/Controller/ShareesAPIController.php @@ -628,7 +628,7 @@ protected function getEmail($search) { protected function getLookup($search) { $client = $this->clientService->newClient(); - $response = $client->get('http://127.0.0.1:3000/users?search='.urlencode($search)); + $response = $client->get('https://lookup.nextcloud.com/users?search='.urlencode($search)); $body = json_decode($response->getBody(), true); $result = []; diff --git a/apps/lookup_server_connector/lib/UpdateLookupServer.php b/apps/lookup_server_connector/lib/UpdateLookupServer.php index e147536bb8652..e9f0d069d923d 100644 --- a/apps/lookup_server_connector/lib/UpdateLookupServer.php +++ b/apps/lookup_server_connector/lib/UpdateLookupServer.php @@ -49,7 +49,7 @@ class UpdateLookupServer { /** @var Signer */ private $signer; /** @var string URL point to lookup server */ - private $lookupServer = 'http://127.0.0.1:3000/users'; + private $lookupServer = 'https://lookup.nextcloud.com/users'; /** * @param AccountManager $accountManager From f062c6daeef99167dbe6f6512510d2958c3c5208 Mon Sep 17 00:00:00 2001 From: Jan-Christoph Borchardt Date: Fri, 18 Nov 2016 14:50:07 +0100 Subject: [PATCH 39/53] fix menu positioning hack Signed-off-by: Jan-Christoph Borchardt --- settings/css/settings.css | 1 - settings/js/federationscopemenu.js | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/settings/css/settings.css b/settings/css/settings.css index 74e19a220113c..74d97f21cb69c 100644 --- a/settings/css/settings.css +++ b/settings/css/settings.css @@ -114,7 +114,6 @@ input#openid, input#webdav { width:20em; } .federationScopeMenu.bubble::after { right: 50%; transform: translate(50%, 0); - margin-right: -8px; } .federationScopeMenu.popovermenu a.menuitem, .federationScopeMenu.popovermenu label.menuitem, diff --git a/settings/js/federationscopemenu.js b/settings/js/federationscopemenu.js index a5da04df918c2..d073478ff5f04 100644 --- a/settings/js/federationscopemenu.js +++ b/settings/js/federationscopemenu.js @@ -125,7 +125,7 @@ //Calculate menu position var l = offsetIcon.left - offsetHeading.left; - l = l - (menuWidth / 2) + ($el.width()/2); + l = l - (menuWidth / 2) + ($el.outerWidth()/2); this.$el.css('left', l); } From 6e783f83457b819b9e07ca482ae1f2f3fbafa5ce Mon Sep 17 00:00:00 2001 From: Jan-Christoph Borchardt Date: Fri, 18 Nov 2016 14:55:00 +0100 Subject: [PATCH 40/53] make icons a bit smaller Signed-off-by: Jan-Christoph Borchardt --- settings/css/settings.css | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/settings/css/settings.css b/settings/css/settings.css index 74d97f21cb69c..84d86d0986039 100644 --- a/settings/css/settings.css +++ b/settings/css/settings.css @@ -91,9 +91,9 @@ input#openid, input#webdav { width:20em; } #personal-settings-avatar-container h2 span[class^="icon-"] { display: inline-block; padding: 8px; - margin-left: -5px; - margin-bottom: -8px; - background-size: 22px; + margin-left: -8px; + margin-bottom: -10px; + background-size: 16px; opacity: .3; cursor: pointer; } From dac980115fe33233d56e450c0a33ac72ee1ddfaa Mon Sep 17 00:00:00 2001 From: Lukas Reschke Date: Fri, 18 Nov 2016 15:33:51 +0100 Subject: [PATCH 41/53] Add retry job Signed-off-by: Lukas Reschke --- apps/lookup_server_connector/appinfo/app.php | 3 +- .../lib/BackgroundJobs/RetryJob.php | 50 ++++++++++++++++++- .../lib/UpdateLookupServer.php | 47 +++++++++-------- 3 files changed, 76 insertions(+), 24 deletions(-) diff --git a/apps/lookup_server_connector/appinfo/app.php b/apps/lookup_server_connector/appinfo/app.php index d757284f7d540..6c63e9a04007d 100644 --- a/apps/lookup_server_connector/appinfo/app.php +++ b/apps/lookup_server_connector/appinfo/app.php @@ -39,7 +39,8 @@ new \OC\AppFramework\Utility\TimeFactory(), \OC::$server->getURLGenerator(), \OC::$server->getUserManager() - ) + ), + \OC::$server->getJobList() ); $updateLookupServer->userUpdated($user); }); diff --git a/apps/lookup_server_connector/lib/BackgroundJobs/RetryJob.php b/apps/lookup_server_connector/lib/BackgroundJobs/RetryJob.php index 5c4f1e7325973..f33323b2d4f37 100644 --- a/apps/lookup_server_connector/lib/BackgroundJobs/RetryJob.php +++ b/apps/lookup_server_connector/lib/BackgroundJobs/RetryJob.php @@ -24,10 +24,58 @@ use OC\BackgroundJob\Job; +use OCP\BackgroundJob\IJobList; +use OCP\Http\Client\IClientService; class RetryJob extends Job { + /** @var IClientService */ + private $clientService; + /** @var IJobList */ + private $jobList; + /** @var string */ + private $lookupServer = 'https://lookup.nextcloud.com/users'; + + /** + * @param IClientService|null $clientService + * @param IJobList|null $jobList + */ + public function __construct(IClientService $clientService = null, + IJobList $jobList = null) { + if($clientService !== null) { + $this->clientService = $clientService; + } else { + $this->clientService = \OC::$server->getHTTPClientService(); + } + if($jobList !== null) { + $this->jobList = $jobList; + } else { + $this->jobList = \OC::$server->getJobList(); + } + } protected function run($argument) { - // TODO: Implement run() method. + if($argument['retryNo'] === 5) { + return; + } + + $client = $this->clientService->newClient(); + + try { + $client->post($this->lookupServer, + [ + 'body' => json_encode($argument['dataArray']), + 'timeout' => 10, + 'connect_timeout' => 3, + ] + ); + } catch (\Exception $e) { + $this->jobList->add(RetryJob::class, + [ + 'dataArray' => $argument['dataArray'], + 'retryNo' => $argument['retryNo'] + 1, + ] + ); + + } } } diff --git a/apps/lookup_server_connector/lib/UpdateLookupServer.php b/apps/lookup_server_connector/lib/UpdateLookupServer.php index e9f0d069d923d..a1bc99091ded9 100644 --- a/apps/lookup_server_connector/lib/UpdateLookupServer.php +++ b/apps/lookup_server_connector/lib/UpdateLookupServer.php @@ -25,6 +25,8 @@ use OC\Accounts\AccountManager; use OC\Security\IdentityProof\Manager; use OC\Security\IdentityProof\Signer; +use OCA\LookupServerConnector\BackgroundJobs\RetryJob; +use OCP\BackgroundJob\IJobList; use OCP\Http\Client\IClientService; use OCP\IConfig; use OCP\IUser; @@ -48,6 +50,8 @@ class UpdateLookupServer { private $keyManager; /** @var Signer */ private $signer; + /** @var IJobList */ + private $jobList; /** @var string URL point to lookup server */ private $lookupServer = 'https://lookup.nextcloud.com/users'; @@ -58,19 +62,22 @@ class UpdateLookupServer { * @param IClientService $clientService * @param Manager $manager * @param Signer $signer + * @param IJobList $jobList */ public function __construct(AccountManager $accountManager, IConfig $config, ISecureRandom $secureRandom, IClientService $clientService, Manager $manager, - Signer $signer) { + Signer $signer, + IJobList $jobList) { $this->accountManager = $accountManager; $this->config = $config; $this->secureRandom = $secureRandom; $this->clientService = $clientService; $this->keyManager = $manager; $this->signer = $signer; + $this->jobList = $jobList; } /** @@ -86,24 +93,11 @@ public function userUpdated(IUser $user) { } } - if (empty($publicData) && !empty($authKey)) { - $this->removeFromLookupServer($user); - } else { + if (!empty($publicData) && !empty($authKey)) { $this->sendToLookupServer($user, $publicData); } } - /** - * TODO: FIXME. Implement removal from lookup server. - * - * remove user from lookup server - * - * @param IUser $user - */ - protected function removeFromLookupServer(IUser $user) { - $this->config->deleteUserValue($user->getUID(), 'lookup_server_connector', 'authKey'); - } - /** * send public user data to the lookup server * @@ -122,12 +116,21 @@ protected function sendToLookupServer(IUser $user, array $publicData) { ]; $dataArray = $this->signer->sign('lookupserver', $dataArray, $user); $httpClient = $this->clientService->newClient(); - $httpClient->post($this->lookupServer, - [ - 'body' => json_encode($dataArray), - 'timeout' => 10, - 'connect_timeout' => 3, - ] - ); + try { + $httpClient->post($this->lookupServer, + [ + 'body' => json_encode($dataArray), + 'timeout' => 10, + 'connect_timeout' => 3, + ] + ); + } catch (\Exception $e) { + $this->jobList->add(RetryJob::class, + [ + 'dataArray' => $dataArray, + 'retryNo' => 0, + ] + ); + } } } From c49b0d383481a698b644187ff27cebdbee765444 Mon Sep 17 00:00:00 2001 From: Lukas Reschke Date: Fri, 18 Nov 2016 15:43:41 +0100 Subject: [PATCH 42/53] Remove unused variable Signed-off-by: Lukas Reschke --- apps/lookup_server_connector/lib/UpdateLookupServer.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/lookup_server_connector/lib/UpdateLookupServer.php b/apps/lookup_server_connector/lib/UpdateLookupServer.php index a1bc99091ded9..7ba63c8556715 100644 --- a/apps/lookup_server_connector/lib/UpdateLookupServer.php +++ b/apps/lookup_server_connector/lib/UpdateLookupServer.php @@ -93,7 +93,7 @@ public function userUpdated(IUser $user) { } } - if (!empty($publicData) && !empty($authKey)) { + if (!empty($publicData)) { $this->sendToLookupServer($user, $publicData); } } From 7b3855a3752053a440837fe0999e99cff7291dbc Mon Sep 17 00:00:00 2001 From: Lukas Reschke Date: Fri, 18 Nov 2016 17:39:08 +0100 Subject: [PATCH 43/53] Add config switch Signed-off-by: Lukas Reschke --- .../lib/FederatedShareProvider.php | 10 +++++ .../lib/Settings/Admin.php | 1 + .../templates/settings-admin.php | 7 ++++ .../tests/Settings/AdminTest.php | 5 +++ .../lib/Controller/ShareesAPIController.php | 42 ++++++++++++------- .../Controller/ShareesAPIControllerTest.php | 24 +++++++++-- 6 files changed, 71 insertions(+), 18 deletions(-) diff --git a/apps/federatedfilesharing/lib/FederatedShareProvider.php b/apps/federatedfilesharing/lib/FederatedShareProvider.php index 6481abe32e2c6..61f1b1c8f186e 100644 --- a/apps/federatedfilesharing/lib/FederatedShareProvider.php +++ b/apps/federatedfilesharing/lib/FederatedShareProvider.php @@ -943,4 +943,14 @@ public function isIncomingServer2serverShareEnabled() { $result = $this->config->getAppValue('files_sharing', 'incoming_server2server_share_enabled', 'yes'); return ($result === 'yes') ? true : false; } + + /** + * Check if querying sharees on the lookup server is enabled + * + * @return bool + */ + public function isLookupServerQueriesEnabled() { + $result = $this->config->getAppValue('files_sharing', 'lookupServerEnabled', 'no'); + return ($result === 'yes') ? true : false; + } } diff --git a/apps/federatedfilesharing/lib/Settings/Admin.php b/apps/federatedfilesharing/lib/Settings/Admin.php index 64619e329f371..20ff6ae6f4a62 100644 --- a/apps/federatedfilesharing/lib/Settings/Admin.php +++ b/apps/federatedfilesharing/lib/Settings/Admin.php @@ -43,6 +43,7 @@ public function getForm() { $parameters = [ 'outgoingServer2serverShareEnabled' => $this->fedShareProvider->isOutgoingServer2serverShareEnabled(), 'incomingServer2serverShareEnabled' => $this->fedShareProvider->isIncomingServer2serverShareEnabled(), + 'lookupServerEnabled' => $this->fedShareProvider->isLookupServerQueriesEnabled(), ]; return new TemplateResponse('federatedfilesharing', 'settings-admin', $parameters, ''); diff --git a/apps/federatedfilesharing/templates/settings-admin.php b/apps/federatedfilesharing/templates/settings-admin.php index 3ffd4bd5b2750..0670553e06112 100644 --- a/apps/federatedfilesharing/templates/settings-admin.php +++ b/apps/federatedfilesharing/templates/settings-admin.php @@ -25,4 +25,11 @@ t('Allow users on this server to receive shares from other servers'));?>

+

+ /> +
+

diff --git a/apps/federatedfilesharing/tests/Settings/AdminTest.php b/apps/federatedfilesharing/tests/Settings/AdminTest.php index 60fadca7b568a..c0b35a6427bbb 100644 --- a/apps/federatedfilesharing/tests/Settings/AdminTest.php +++ b/apps/federatedfilesharing/tests/Settings/AdminTest.php @@ -65,10 +65,15 @@ public function testGetForm($state) { ->expects($this->once()) ->method('isIncomingServer2serverShareEnabled') ->willReturn($state); + $this->federatedShareProvider + ->expects($this->once()) + ->method('isLookupServerQueriesEnabled') + ->willReturn($state); $params = [ 'outgoingServer2serverShareEnabled' => $state, 'incomingServer2serverShareEnabled' => $state, + 'lookupServerEnabled' => $state, ]; $expected = new TemplateResponse('federatedfilesharing', 'settings-admin', $params, ''); $this->assertEquals($expected, $this->admin->getForm()); diff --git a/apps/files_sharing/lib/Controller/ShareesAPIController.php b/apps/files_sharing/lib/Controller/ShareesAPIController.php index 7b9e10090d2e7..6b3208dc289cf 100644 --- a/apps/files_sharing/lib/Controller/ShareesAPIController.php +++ b/apps/files_sharing/lib/Controller/ShareesAPIController.php @@ -626,22 +626,36 @@ protected function getEmail($search) { } protected function getLookup($search) { - $client = $this->clientService->newClient(); - - $response = $client->get('https://lookup.nextcloud.com/users?search='.urlencode($search)); - $body = json_decode($response->getBody(), true); - + $isEnabled = $this->config->getAppValue('files_sharing', 'lookupServerEnabled', 'no'); $result = []; - foreach ($body as $lookup) { - $result[] = [ - 'label' => $lookup['federationId'], - 'value' => [ - 'shareType' => Share::SHARE_TYPE_REMOTE, - 'shareWith' => $lookup['federationId'], - ], - 'extra' => $lookup, - ]; + + if($isEnabled === 'yes') { + try { + $client = $this->clientService->newClient(); + $response = $client->get( + 'https://lookup.nextcloud.com/users?search=' . urlencode($search), + [ + 'timeout' => 10, + 'connect_timeout' => 3, + ] + ); + + $body = json_decode($response->getBody(), true); + + $result = []; + foreach ($body as $lookup) { + $result[] = [ + 'label' => $lookup['federationId'], + 'value' => [ + 'shareType' => Share::SHARE_TYPE_REMOTE, + 'shareWith' => $lookup['federationId'], + ], + 'extra' => $lookup, + ]; + } + } catch (\Exception $e) {} } + $this->result['lookup'] = $result; } diff --git a/apps/files_sharing/tests/Controller/ShareesAPIControllerTest.php b/apps/files_sharing/tests/Controller/ShareesAPIControllerTest.php index 336dcb70f0e08..c570cb16980e3 100644 --- a/apps/files_sharing/tests/Controller/ShareesAPIControllerTest.php +++ b/apps/files_sharing/tests/Controller/ShareesAPIControllerTest.php @@ -29,6 +29,7 @@ use OCA\Files_Sharing\Tests\TestCase; use OCP\AppFramework\Http; use OCP\AppFramework\OCS\OCSBadRequestException; +use OCP\Http\Client\IClientService; use OCP\Share; /** @@ -60,6 +61,9 @@ class ShareesAPIControllerTest extends TestCase { /** @var \OCP\Share\IManager|\PHPUnit_Framework_MockObject_MockObject */ protected $shareManager; + /** @var IClientService|\PHPUnit_Framework_MockObject_MockObject */ + private $clientService; + protected function setUp() { parent::setUp(); @@ -87,6 +91,8 @@ protected function setUp() { ->disableOriginalConstructor() ->getMock(); + $this->clientService = $this->createMock(IClientService::class); + $this->sharees = new ShareesAPIController( 'files_sharing', $this->request, @@ -97,7 +103,8 @@ protected function setUp() { $this->session, $this->getMockBuilder('OCP\IURLGenerator')->disableOriginalConstructor()->getMock(), $this->getMockBuilder('OCP\ILogger')->disableOriginalConstructor()->getMock(), - $this->shareManager + $this->shareManager, + $this->clientService ); } @@ -1386,7 +1393,8 @@ public function testSearch($getData, $apiSetting, $enumSetting, $remoteSharingEn $this->session, $this->getMockBuilder('OCP\IURLGenerator')->disableOriginalConstructor()->getMock(), $this->getMockBuilder('OCP\ILogger')->disableOriginalConstructor()->getMock(), - $this->shareManager + $this->shareManager, + $this->clientService ]) ->setMethods(array('searchSharees', 'isRemoteSharingAllowed', 'shareProviderExists')) ->getMock(); @@ -1477,7 +1485,8 @@ public function testSearchInvalid($getData, $message) { $this->session, $this->getMockBuilder('OCP\IURLGenerator')->disableOriginalConstructor()->getMock(), $this->getMockBuilder('OCP\ILogger')->disableOriginalConstructor()->getMock(), - $this->shareManager + $this->shareManager, + $this->clientService ]) ->setMethods(array('searchSharees', 'isRemoteSharingAllowed')) ->getMock(); @@ -1522,6 +1531,7 @@ public function dataSearchSharees() { 'groups' => [], 'remotes' => [], 'emails' => [], + 'lookup' => [], ], false], ['test', 'folder', [Share::SHARE_TYPE_USER, Share::SHARE_TYPE_GROUP, Share::SHARE_TYPE_REMOTE], 1, 2, false, [], [], ['results' => [], 'exact' => [], 'exactIdMatch' => false], [ @@ -1530,6 +1540,7 @@ public function dataSearchSharees() { 'groups' => [], 'remotes' => [], 'emails' => [], + 'lookup' => [], ], false], [ 'test', 'folder', [Share::SHARE_TYPE_USER, Share::SHARE_TYPE_GROUP, Share::SHARE_TYPE_REMOTE], 1, 2, false, [ @@ -1551,6 +1562,7 @@ public function dataSearchSharees() { ['label' => 'testz@remote', 'value' => ['shareType' => Share::SHARE_TYPE_REMOTE, 'shareWith' => 'testz@remote']], ], 'emails' => [], + 'lookup' => [], ], true, ], // No groups requested @@ -1570,6 +1582,7 @@ public function dataSearchSharees() { ['label' => 'testz@remote', 'value' => ['shareType' => Share::SHARE_TYPE_REMOTE, 'shareWith' => 'testz@remote']], ], 'emails' => [], + 'lookup' => [], ], false, ], // Share type restricted to user - Only one user @@ -1585,6 +1598,7 @@ public function dataSearchSharees() { 'groups' => [], 'remotes' => [], 'emails' => [], + 'lookup' => [], ], false, ], // Share type restricted to user - Multipage result @@ -1602,6 +1616,7 @@ public function dataSearchSharees() { 'groups' => [], 'remotes' => [], 'emails' => [], + 'lookup' => [], ], true, ], ]; @@ -1636,7 +1651,8 @@ public function testSearchSharees($searchTerm, $itemType, array $shareTypes, $pa $this->session, $this->getMockBuilder('OCP\IURLGenerator')->disableOriginalConstructor()->getMock(), $this->getMockBuilder('OCP\ILogger')->disableOriginalConstructor()->getMock(), - $this->shareManager + $this->shareManager, + $this->clientService ]) ->setMethods(array('getShareesForShareIds', 'getUsers', 'getGroups', 'getRemote')) ->getMock(); From ccf3e4255a8316d858f14bb08e5a60622700c31a Mon Sep 17 00:00:00 2001 From: Lukas Reschke Date: Fri, 18 Nov 2016 17:40:54 +0100 Subject: [PATCH 44/53] Remove debug code Signed-off-by: Lukas Reschke --- apps/dav/lib/AppInfo/Application.php | 4 ---- 1 file changed, 4 deletions(-) diff --git a/apps/dav/lib/AppInfo/Application.php b/apps/dav/lib/AppInfo/Application.php index 80d2946ab07c6..7c32fda8f5a7e 100644 --- a/apps/dav/lib/AppInfo/Application.php +++ b/apps/dav/lib/AppInfo/Application.php @@ -142,10 +142,6 @@ public function registerHooks() { ); }); - $dispatcher->addListener('OC\AccountManager::userUpdated', function(GenericEvent $event) { - error_log("hello"); - }); - $listener = function(GenericEvent $event, $eventName) { /** @var Backend $backend */ $backend = $this->getContainer()->query(Backend::class); From a0c64044b25ab4d6a80ac9bcf3084a8166de6e19 Mon Sep 17 00:00:00 2001 From: Lukas Reschke Date: Fri, 18 Nov 2016 18:34:01 +0100 Subject: [PATCH 45/53] Make JS aware of password verification Signed-off-by: Lukas Reschke --- settings/Controller/UsersController.php | 1 + settings/js/federationsettingsview.js | 8 ++++++++ 2 files changed, 9 insertions(+) diff --git a/settings/Controller/UsersController.php b/settings/Controller/UsersController.php index 8f077270392bc..77d65877ba342 100644 --- a/settings/Controller/UsersController.php +++ b/settings/Controller/UsersController.php @@ -672,6 +672,7 @@ public function stats() { * @todo merge into saveUserSettings * * @NoAdminRequired + * @PasswordConfirmationRequired * * @param string $username * @param string $displayName diff --git a/settings/js/federationsettingsview.js b/settings/js/federationsettingsview.js index 7aaa225a86102..e42828ca456d5 100644 --- a/settings/js/federationsettingsview.js +++ b/settings/js/federationsettingsview.js @@ -90,10 +90,18 @@ _onInputChanged: function(e) { var self = this; + var $dialog = $('.oc-dialog:visible'); + if (OC.PasswordConfirmation.requiresPasswordConfirmation()) { + if($dialog.length === 0) { + OC.PasswordConfirmation.requirePasswordConfirmation(_.bind(this._onInputChanged, this, e)); + } + return; + } var $target = $(e.target); var value = $target.val(); var field = $target.attr('id'); this._config.set(field, value); + var savingData = this._config.save({ error: function(jqXHR) { OC.msg.finishedSaving('#personal-settings-container .msg', jqXHR); From 5acc3d3c5e8c78ee425dad1dfb3d8d057a6e7203 Mon Sep 17 00:00:00 2001 From: Lukas Reschke Date: Fri, 18 Nov 2016 19:44:21 +0100 Subject: [PATCH 46/53] Highlight current entry Signed-off-by: Lukas Reschke --- settings/js/federationscopemenu.js | 27 +++++++++++++++++++++++---- settings/js/federationsettingsview.js | 3 +++ 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/settings/js/federationscopemenu.js b/settings/js/federationscopemenu.js index d073478ff5f04..36b720e8c45fe 100644 --- a/settings/js/federationscopemenu.js +++ b/settings/js/federationscopemenu.js @@ -9,7 +9,6 @@ */ /* global OC, Handlebars */ - (function() { var TEMPLATE_MENU = @@ -49,19 +48,22 @@ name: 'private', displayName: (this.field == 'avatar' || this.field == 'displayname') ? t('core', 'Local') : t('core', 'Private'), tooltip: (this.field == 'avatar' || this.field == 'displayname') ? t('core', 'Only visible to local users') : t('core', 'Only visible to you'), - icon: OC.imagePath('core', 'actions/password') + icon: OC.imagePath('core', 'actions/password'), + active: false }, { name: 'contacts', displayName: t('core', 'Contacts'), tooltip: t('core', 'Visible to local users and to trusted servers'), - icon: OC.imagePath('core', 'places/contacts-dark') + icon: OC.imagePath('core', 'places/contacts-dark'), + active: false }, { name: 'public', displayName: t('core', 'Public'), tooltip: t('core', 'Will be synced to a global and public address book'), - icon: OC.imagePath('core', 'places/link') + icon: OC.imagePath('core', 'places/link'), + active: false } ]; }, @@ -109,6 +111,23 @@ */ show: function(context) { this._context = context; + var currentlyActiveValue = $('#'+context.target.closest('form').id).find('.icon-checkmark > input')[0].value; + + for(var i = 0 in this._scopes) { + this._scopes[i].active = false; + } + + switch (currentlyActiveValue) { + case "private": + this._scopes[0].active = true; + break; + case "contacts": + this._scopes[1].active = true; + break; + case "public": + this._scopes[2].active = true; + break; + } var $el = $(context.target); var offsetIcon = $el.offset(); diff --git a/settings/js/federationsettingsview.js b/settings/js/federationsettingsview.js index e42828ca456d5..9b38664d2f8a9 100644 --- a/settings/js/federationsettingsview.js +++ b/settings/js/federationsettingsview.js @@ -128,6 +128,9 @@ _onScopeChanged: function(field, scope) { this._config.set(field + 'Scope', scope); + + $('#' + field).parent().find('span > input').val(scope); + // TODO: user loading/success feedback this._config.save(); this._setFieldScopeIcon(field, scope); From e87933bc3d30df6c424edcdc945d1bc012f982bf Mon Sep 17 00:00:00 2001 From: Lukas Reschke Date: Fri, 18 Nov 2016 19:54:45 +0100 Subject: [PATCH 47/53] Fix mail scope Signed-off-by: Lukas Reschke --- settings/templates/personal.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/settings/templates/personal.php b/settings/templates/personal.php index f5050ab190e0f..b81b19d906075 100644 --- a/settings/templates/personal.php +++ b/settings/templates/personal.php @@ -91,10 +91,10 @@ -
t('For password recovery and notifications')); ?>