Merge pull request #25973 from nextcloud/backport/25417/stable20-fix-account-data-visibility-after-disabling-public-addressbook-upload

[stable20] Fix account data visibility after disabling public addressbook upload
This commit is contained in:
Roeland Jago Douma 2021-04-29 20:52:38 +02:00 committed by GitHub
commit f902adf188
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 771 additions and 104 deletions

View File

@ -1155,6 +1155,31 @@ trigger:
- pull_request
- push
---
kind: pipeline
name: integration-contacts-menu
steps:
- name: submodules
image: docker:git
commands:
- git submodule update --init
- name: integration-contacts-menu
image: nextcloudci/integration-php7.3:integration-php7.3-2
commands:
- bash tests/drone-run-integration-tests.sh || exit 0
- ./occ maintenance:install --admin-pass=admin --data-dir=/dev/shm/nc_int
- cd build/integration
- ./run.sh features/contacts-menu.feature
trigger:
branch:
- master
- stable*
event:
- pull_request
- push
---
kind: pipeline
name: integration-favorites

View File

@ -403,6 +403,16 @@ select {
font-weight: bold;
}
}
&.disabled {
opacity: .5;
cursor: default;
span {
cursor: default;
}
}
}
}
}

View File

@ -117,6 +117,17 @@
break;
}
var lookupServerUploadEnabled = $('#lookupServerUploadEnabled').val();
if (!lookupServerUploadEnabled && !this._scopes[2].active) {
this._scopes[2].hidden = true
} else if (!lookupServerUploadEnabled && this._scopes[2].active) {
this._scopes[2].hidden = false
this._scopes[2].disabled = true
} else {
this._scopes[2].hidden = false
this._scopes[2].disabled = false
}
this.render();
this.$el.removeClass('hidden');

View File

@ -43,7 +43,14 @@
if (!scopeOnly) {
self._config.set(field, $('#' + field).val());
}
self._config.set(field + 'Scope', $('#' + field + 'scope').val());
// A scope could have been stored as null due to a previous bug.
// Null values should be kept as such until the user explicitly
// sets the right value, but they will be returned as empty
// strings in the template (which would overwrite the null value
// if sent). Due to this an extra class is needed to
// differentiate them.
var initialScopeValue = $('#' + field + 'scope').hasClass('corrupted-scope-value') ? undefined : $('#' + field + 'scope').val();
self._config.set(field + 'Scope', initialScopeValue);
// Set inputs whenever model values change
if (!scopeOnly) {
@ -219,6 +226,12 @@
$icon.addClass('icon-link');
$icon.removeClass('hidden');
break;
case '':
case null:
case undefined:
$icon.addClass('icon-error');
$icon.removeClass('hidden');
break;
}
}
});

View File

@ -1,6 +1,15 @@
(function() {
var template = Handlebars.template, templates = OC.Settings.Templates = OC.Settings.Templates || {};
templates['federationscopemenu'] = template({"1":function(container,depth0,helpers,partials,data) {
var stack1, lookupProperty = container.lookupProperty || function(parent, propertyName) {
if (Object.prototype.hasOwnProperty.call(parent, propertyName)) {
return parent[propertyName];
}
return undefined
};
return ((stack1 = lookupProperty(helpers,"unless").call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? lookupProperty(depth0,"hidden") : depth0),{"name":"unless","hash":{},"fn":container.program(2, data, 0),"inverse":container.noop,"data":data,"loc":{"start":{"line":3,"column":2},"end":{"line":25,"column":13}}})) != null ? stack1 : "");
},"2":function(container,depth0,helpers,partials,data) {
var stack1, helper, alias1=depth0 != null ? depth0 : (container.nullContext || {}), alias2=container.hooks.helperMissing, alias3="function", alias4=container.escapeExpression, lookupProperty = container.lookupProperty || function(parent, propertyName) {
if (Object.prototype.hasOwnProperty.call(parent, propertyName)) {
return parent[propertyName];
@ -8,22 +17,49 @@ templates['federationscopemenu'] = template({"1":function(container,depth0,helpe
return undefined
};
return " <li tabindex=\"0\">\n <a href=\"#\" class=\"menuitem action action-"
+ alias4(((helper = (helper = lookupProperty(helpers,"name") || (depth0 != null ? lookupProperty(depth0,"name") : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"name","hash":{},"data":data,"loc":{"start":{"line":4,"column":45},"end":{"line":4,"column":53}}}) : helper)))
+ " permanent "
+ ((stack1 = lookupProperty(helpers,"if").call(alias1,(depth0 != null ? lookupProperty(depth0,"active") : depth0),{"name":"if","hash":{},"fn":container.program(2, data, 0),"inverse":container.noop,"data":data,"loc":{"start":{"line":4,"column":64},"end":{"line":4,"column":91}}})) != null ? stack1 : "")
+ "\" data-action=\""
+ alias4(((helper = (helper = lookupProperty(helpers,"name") || (depth0 != null ? lookupProperty(depth0,"name") : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"name","hash":{},"data":data,"loc":{"start":{"line":4,"column":106},"end":{"line":4,"column":114}}}) : helper)))
+ "\">\n"
+ ((stack1 = lookupProperty(helpers,"if").call(alias1,(depth0 != null ? lookupProperty(depth0,"iconClass") : depth0),{"name":"if","hash":{},"fn":container.program(4, data, 0),"inverse":container.program(6, data, 0),"data":data,"loc":{"start":{"line":5,"column":4},"end":{"line":9,"column":11}}})) != null ? stack1 : "")
return " <li tabindex=\"0\">\n"
+ ((stack1 = lookupProperty(helpers,"if").call(alias1,(depth0 != null ? lookupProperty(depth0,"disabled") : depth0),{"name":"if","hash":{},"fn":container.program(3, data, 0),"inverse":container.program(6, data, 0),"data":data,"loc":{"start":{"line":5,"column":3},"end":{"line":9,"column":10}}})) != null ? stack1 : "")
+ ((stack1 = lookupProperty(helpers,"if").call(alias1,(depth0 != null ? lookupProperty(depth0,"iconClass") : depth0),{"name":"if","hash":{},"fn":container.program(8, data, 0),"inverse":container.program(10, data, 0),"data":data,"loc":{"start":{"line":10,"column":4},"end":{"line":14,"column":11}}})) != null ? stack1 : "")
+ " <p>\n <strong class=\"menuitem-text\">"
+ alias4(((helper = (helper = lookupProperty(helpers,"displayName") || (depth0 != null ? lookupProperty(depth0,"displayName") : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"displayName","hash":{},"data":data,"loc":{"start":{"line":11,"column":35},"end":{"line":11,"column":50}}}) : helper)))
+ alias4(((helper = (helper = lookupProperty(helpers,"displayName") || (depth0 != null ? lookupProperty(depth0,"displayName") : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"displayName","hash":{},"data":data,"loc":{"start":{"line":16,"column":35},"end":{"line":16,"column":50}}}) : helper)))
+ "</strong><br>\n <span class=\"menuitem-text-detail\">"
+ alias4(((helper = (helper = lookupProperty(helpers,"tooltip") || (depth0 != null ? lookupProperty(depth0,"tooltip") : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"tooltip","hash":{},"data":data,"loc":{"start":{"line":12,"column":40},"end":{"line":12,"column":51}}}) : helper)))
+ "</span>\n </p>\n </a>\n </li>\n";
},"2":function(container,depth0,helpers,partials,data) {
return "active";
+ alias4(((helper = (helper = lookupProperty(helpers,"tooltip") || (depth0 != null ? lookupProperty(depth0,"tooltip") : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"tooltip","hash":{},"data":data,"loc":{"start":{"line":17,"column":40},"end":{"line":17,"column":51}}}) : helper)))
+ "</span>\n </p>\n"
+ ((stack1 = lookupProperty(helpers,"if").call(alias1,(depth0 != null ? lookupProperty(depth0,"disabled") : depth0),{"name":"if","hash":{},"fn":container.program(12, data, 0),"inverse":container.program(14, data, 0),"data":data,"loc":{"start":{"line":19,"column":3},"end":{"line":23,"column":10}}})) != null ? stack1 : "")
+ " </li>\n";
},"3":function(container,depth0,helpers,partials,data) {
var stack1, helper, alias1=depth0 != null ? depth0 : (container.nullContext || {}), alias2=container.hooks.helperMissing, alias3="function", alias4=container.escapeExpression, lookupProperty = container.lookupProperty || function(parent, propertyName) {
if (Object.prototype.hasOwnProperty.call(parent, propertyName)) {
return parent[propertyName];
}
return undefined
};
return " <div class=\"menuitem action action-"
+ alias4(((helper = (helper = lookupProperty(helpers,"name") || (depth0 != null ? lookupProperty(depth0,"name") : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"name","hash":{},"data":data,"loc":{"start":{"line":6,"column":38},"end":{"line":6,"column":46}}}) : helper)))
+ " permanent "
+ ((stack1 = lookupProperty(helpers,"if").call(alias1,(depth0 != null ? lookupProperty(depth0,"active") : depth0),{"name":"if","hash":{},"fn":container.program(4, data, 0),"inverse":container.noop,"data":data,"loc":{"start":{"line":6,"column":57},"end":{"line":6,"column":84}}})) != null ? stack1 : "")
+ " disabled\" data-action=\""
+ alias4(((helper = (helper = lookupProperty(helpers,"name") || (depth0 != null ? lookupProperty(depth0,"name") : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"name","hash":{},"data":data,"loc":{"start":{"line":6,"column":108},"end":{"line":6,"column":116}}}) : helper)))
+ "\">\n";
},"4":function(container,depth0,helpers,partials,data) {
return "active";
},"6":function(container,depth0,helpers,partials,data) {
var stack1, helper, alias1=depth0 != null ? depth0 : (container.nullContext || {}), alias2=container.hooks.helperMissing, alias3="function", alias4=container.escapeExpression, lookupProperty = container.lookupProperty || function(parent, propertyName) {
if (Object.prototype.hasOwnProperty.call(parent, propertyName)) {
return parent[propertyName];
}
return undefined
};
return " <a href=\"#\" class=\"menuitem action action-"
+ alias4(((helper = (helper = lookupProperty(helpers,"name") || (depth0 != null ? lookupProperty(depth0,"name") : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"name","hash":{},"data":data,"loc":{"start":{"line":8,"column":45},"end":{"line":8,"column":53}}}) : helper)))
+ " permanent "
+ ((stack1 = lookupProperty(helpers,"if").call(alias1,(depth0 != null ? lookupProperty(depth0,"active") : depth0),{"name":"if","hash":{},"fn":container.program(4, data, 0),"inverse":container.noop,"data":data,"loc":{"start":{"line":8,"column":64},"end":{"line":8,"column":91}}})) != null ? stack1 : "")
+ "\" data-action=\""
+ alias4(((helper = (helper = lookupProperty(helpers,"name") || (depth0 != null ? lookupProperty(depth0,"name") : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"name","hash":{},"data":data,"loc":{"start":{"line":8,"column":106},"end":{"line":8,"column":114}}}) : helper)))
+ "\">\n";
},"8":function(container,depth0,helpers,partials,data) {
var helper, lookupProperty = container.lookupProperty || function(parent, propertyName) {
if (Object.prototype.hasOwnProperty.call(parent, propertyName)) {
return parent[propertyName];
@ -32,10 +68,14 @@ templates['federationscopemenu'] = template({"1":function(container,depth0,helpe
};
return " <span class=\"icon "
+ container.escapeExpression(((helper = (helper = lookupProperty(helpers,"iconClass") || (depth0 != null ? lookupProperty(depth0,"iconClass") : depth0)) != null ? helper : container.hooks.helperMissing),(typeof helper === "function" ? helper.call(depth0 != null ? depth0 : (container.nullContext || {}),{"name":"iconClass","hash":{},"data":data,"loc":{"start":{"line":6,"column":23},"end":{"line":6,"column":36}}}) : helper)))
+ container.escapeExpression(((helper = (helper = lookupProperty(helpers,"iconClass") || (depth0 != null ? lookupProperty(depth0,"iconClass") : depth0)) != null ? helper : container.hooks.helperMissing),(typeof helper === "function" ? helper.call(depth0 != null ? depth0 : (container.nullContext || {}),{"name":"iconClass","hash":{},"data":data,"loc":{"start":{"line":11,"column":23},"end":{"line":11,"column":36}}}) : helper)))
+ "\"></span>\n";
},"6":function(container,depth0,helpers,partials,data) {
},"10":function(container,depth0,helpers,partials,data) {
return " <span class=\"no-icon\"></span>\n";
},"12":function(container,depth0,helpers,partials,data) {
return " </div>\n";
},"14":function(container,depth0,helpers,partials,data) {
return " </a>\n";
},"compiler":[8,">= 4.3.0"],"main":function(container,depth0,helpers,partials,data) {
var stack1, lookupProperty = container.lookupProperty || function(parent, propertyName) {
if (Object.prototype.hasOwnProperty.call(parent, propertyName)) {
@ -45,7 +85,7 @@ templates['federationscopemenu'] = template({"1":function(container,depth0,helpe
};
return "<ul>\n"
+ ((stack1 = lookupProperty(helpers,"each").call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? lookupProperty(depth0,"items") : depth0),{"name":"each","hash":{},"fn":container.program(1, data, 0),"inverse":container.noop,"data":data,"loc":{"start":{"line":2,"column":1},"end":{"line":16,"column":10}}})) != null ? stack1 : "")
+ ((stack1 = lookupProperty(helpers,"each").call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? lookupProperty(depth0,"items") : depth0),{"name":"each","hash":{},"fn":container.program(1, data, 0),"inverse":container.noop,"data":data,"loc":{"start":{"line":2,"column":1},"end":{"line":26,"column":10}}})) != null ? stack1 : "")
+ "</ul>\n";
},"useData":true});
})();

View File

@ -1,7 +1,12 @@
<ul>
{{#each items}}
{{#unless hidden}}
<li tabindex="0">
{{#if disabled}}
<div class="menuitem action action-{{name}} permanent {{#if active}}active{{/if}} disabled" data-action="{{name}}">
{{else}}
<a href="#" class="menuitem action action-{{name}} permanent {{#if active}}active{{/if}}" data-action="{{name}}">
{{/if}}
{{#if iconClass}}
<span class="icon {{iconClass}}"></span>
{{else}}
@ -11,7 +16,12 @@
<strong class="menuitem-text">{{displayName}}</strong><br>
<span class="menuitem-text-detail">{{tooltip}}</span>
</p>
{{#if disabled}}
</div>
{{else}}
</a>
{{/if}}
</li>
{{/unless}}
{{/each}}
</ul>

View File

@ -46,6 +46,7 @@ use OCA\FederatedFileSharing\FederatedShareProvider;
use OCA\Settings\BackgroundJobs\VerifyUserData;
use OCA\Settings\Events\BeforeTemplateRenderedEvent;
use OCA\User_LDAP\User_Proxy;
use OCP\Accounts\IAccountManager;
use OCP\App\IAppManager;
use OCP\AppFramework\Controller;
use OCP\AppFramework\Http\DataResponse;
@ -360,7 +361,7 @@ class UsersController extends Controller {
$twitter,
$twitterScope
) {
$email = strtolower($email);
$email = !is_null($email) ? strtolower($email) : $email;
if (!empty($email) && !$this->mailer->validateMailAddress($email)) {
return new DataResponse(
[
@ -374,18 +375,50 @@ class UsersController extends Controller {
}
$user = $this->userSession->getUser();
$data = $this->accountManager->getUser($user);
$data[AccountManager::PROPERTY_AVATAR] = ['scope' => $avatarScope];
if (!is_null($avatarScope)) {
$data[IAccountManager::PROPERTY_AVATAR]['scope'] = $avatarScope;
}
if ($this->config->getSystemValue('allow_user_to_change_display_name', true) !== false) {
$data[AccountManager::PROPERTY_DISPLAYNAME] = ['value' => $displayname, 'scope' => $displaynameScope];
$data[AccountManager::PROPERTY_EMAIL] = ['value' => $email, 'scope' => $emailScope];
if (!is_null($displayname)) {
$data[IAccountManager::PROPERTY_DISPLAYNAME]['value'] = $displayname;
}
if (!is_null($displaynameScope)) {
$data[IAccountManager::PROPERTY_DISPLAYNAME]['scope'] = $displaynameScope;
}
if (!is_null($email)) {
$data[IAccountManager::PROPERTY_EMAIL]['value'] = $email;
}
if (!is_null($emailScope)) {
$data[IAccountManager::PROPERTY_EMAIL]['scope'] = $emailScope;
}
}
if ($this->appManager->isEnabledForUser('federatedfilesharing')) {
$shareProvider = \OC::$server->query(FederatedShareProvider::class);
if ($shareProvider->isLookupServerUploadEnabled()) {
$data[AccountManager::PROPERTY_WEBSITE] = ['value' => $website, 'scope' => $websiteScope];
$data[AccountManager::PROPERTY_ADDRESS] = ['value' => $address, 'scope' => $addressScope];
$data[AccountManager::PROPERTY_PHONE] = ['value' => $phone, 'scope' => $phoneScope];
$data[AccountManager::PROPERTY_TWITTER] = ['value' => $twitter, 'scope' => $twitterScope];
if (!is_null($website)) {
$data[IAccountManager::PROPERTY_WEBSITE]['value'] = $website;
}
if (!is_null($websiteScope)) {
$data[IAccountManager::PROPERTY_WEBSITE]['scope'] = $websiteScope;
}
if (!is_null($address)) {
$data[IAccountManager::PROPERTY_ADDRESS]['value'] = $address;
}
if (!is_null($addressScope)) {
$data[IAccountManager::PROPERTY_ADDRESS]['scope'] = $addressScope;
}
if (!is_null($phone)) {
$data[IAccountManager::PROPERTY_PHONE]['value'] = $phone;
}
if (!is_null($phoneScope)) {
$data[IAccountManager::PROPERTY_PHONE]['scope'] = $phoneScope;
}
if (!is_null($twitter)) {
$data[IAccountManager::PROPERTY_TWITTER]['value'] = $twitter;
}
if (!is_null($twitterScope)) {
$data[IAccountManager::PROPERTY_TWITTER]['scope'] = $twitterScope;
}
}
}
try {
@ -395,15 +428,15 @@ class UsersController extends Controller {
'status' => 'success',
'data' => [
'userId' => $user->getUID(),
'avatarScope' => $data[AccountManager::PROPERTY_AVATAR]['scope'],
'displayname' => $data[AccountManager::PROPERTY_DISPLAYNAME]['value'],
'displaynameScope' => $data[AccountManager::PROPERTY_DISPLAYNAME]['scope'],
'email' => $data[AccountManager::PROPERTY_EMAIL]['value'],
'emailScope' => $data[AccountManager::PROPERTY_EMAIL]['scope'],
'website' => $data[AccountManager::PROPERTY_WEBSITE]['value'],
'websiteScope' => $data[AccountManager::PROPERTY_WEBSITE]['scope'],
'address' => $data[AccountManager::PROPERTY_ADDRESS]['value'],
'addressScope' => $data[AccountManager::PROPERTY_ADDRESS]['scope'],
'avatarScope' => $data[IAccountManager::PROPERTY_AVATAR]['scope'],
'displayname' => $data[IAccountManager::PROPERTY_DISPLAYNAME]['value'],
'displaynameScope' => $data[IAccountManager::PROPERTY_DISPLAYNAME]['scope'],
'email' => $data[IAccountManager::PROPERTY_EMAIL]['value'],
'emailScope' => $data[IAccountManager::PROPERTY_EMAIL]['scope'],
'website' => $data[IAccountManager::PROPERTY_WEBSITE]['value'],
'websiteScope' => $data[IAccountManager::PROPERTY_WEBSITE]['scope'],
'address' => $data[IAccountManager::PROPERTY_ADDRESS]['value'],
'addressScope' => $data[IAccountManager::PROPERTY_ADDRESS]['scope'],
'message' => $this->l10n->t('Settings saved')
]
],

View File

@ -34,6 +34,9 @@ script('settings', [
]);
?>
<!-- config hints for javascript -->
<input type="hidden" name="lookupServerUploadEnabled" id="lookupServerUploadEnabled" value="<?php p($_['lookupServerUploadEnabled']); ?>" />
<div id="personal-settings">
<div id="personal-settings-avatar-container" class="personal-settings-container">
<div>
@ -67,9 +70,7 @@ script('settings', [
</div>
</div>
<span class="icon-checkmark hidden"></span>
<?php if ($_['lookupServerUploadEnabled']) { ?>
<input type="hidden" id="avatarscope" value="<?php p($_['avatarScope']) ?>">
<?php } ?>
<input type="hidden" id="avatarscope" value="<?php p($_['avatarScope']) ?>"<?php if (is_null($_['avatarScope'])): ?> class="corrupted-scope-value"<?php endif ?>>
</form>
</div>
<div class="personal-settings-setting-box personal-settings-group-box section">
@ -123,9 +124,7 @@ script('settings', [
<?php } ?>
<span class="icon-checkmark hidden"></span>
<span class="icon-error hidden" ></span>
<?php if ($_['lookupServerUploadEnabled']) { ?>
<input type="hidden" id="displaynamescope" value="<?php p($_['displayNameScope']) ?>">
<?php } ?>
<input type="hidden" id="displaynamescope" value="<?php p($_['displayNameScope']) ?>"<?php if (is_null($_['displayNameScope'])): ?> class="corrupted-scope-value"<?php endif ?>>
</form>
</div>
<div class="personal-settings-setting-box">
@ -173,9 +172,7 @@ script('settings', [
<?php if ($_['displayNameChangeSupported']) { ?>
<em><?php p($l->t('For password reset and notifications')); ?></em>
<?php } ?>
<?php if ($_['lookupServerUploadEnabled']) { ?>
<input type="hidden" id="emailscope" value="<?php p($_['emailScope']) ?>">
<?php } ?>
<input type="hidden" id="emailscope" value="<?php p($_['emailScope']) ?>"<?php if (is_null($_['emailScope'])): ?> class="corrupted-scope-value"<?php endif ?>>
</form>
</div>
<?php if (!empty($_['phone']) || $_['lookupServerUploadEnabled']) { ?>
@ -196,9 +193,7 @@ script('settings', [
placeholder="<?php p($l->t('Your phone number')); ?>"
autocomplete="on" autocapitalize="none" autocorrect="off" />
<span class="icon-checkmark hidden"></span>
<?php if ($_['lookupServerUploadEnabled']) { ?>
<input type="hidden" id="phonescope" value="<?php p($_['phoneScope']) ?>">
<?php } ?>
<input type="hidden" id="phonescope" value="<?php p($_['phoneScope']) ?>"<?php if (is_null($_['phoneScope'])): ?> class="corrupted-scope-value"<?php endif ?>>
</form>
</div>
<?php } ?>
@ -220,9 +215,7 @@ script('settings', [
value="<?php p($_['address']) ?>"
autocomplete="on" autocapitalize="none" autocorrect="off" />
<span class="icon-checkmark hidden"></span>
<?php if ($_['lookupServerUploadEnabled']) { ?>
<input type="hidden" id="addressscope" value="<?php p($_['addressScope']) ?>">
<?php } ?>
<input type="hidden" id="addressscope" value="<?php p($_['addressScope']) ?>"<?php if (is_null($_['addressScope'])): ?> class="corrupted-scope-value"<?php endif ?>>
</form>
</div>
<?php } ?>
@ -275,9 +268,7 @@ script('settings', [
} ?>
/>
<span class="icon-checkmark hidden"></span>
<?php if ($_['lookupServerUploadEnabled']) { ?>
<input type="hidden" id="websitescope" value="<?php p($_['websiteScope']) ?>">
<?php } ?>
<input type="hidden" id="websitescope" value="<?php p($_['websiteScope']) ?>"<?php if (is_null($_['websiteScope'])): ?> class="corrupted-scope-value"<?php endif ?>>
</form>
</div>
<?php } ?>
@ -330,9 +321,7 @@ script('settings', [
} ?>
/>
<span class="icon-checkmark hidden"></span>
<?php if ($_['lookupServerUploadEnabled']) { ?>
<input type="hidden" id="twitterscope" value="<?php p($_['twitterScope']) ?>">
<?php } ?>
<input type="hidden" id="twitterscope" value="<?php p($_['twitterScope']) ?>"<?php if (is_null($_['twitterScope'])): ?> class="corrupted-scope-value"<?php endif ?>>
</form>
</div>
<?php } ?>

View File

@ -31,6 +31,7 @@ use OC\Accounts\AccountManager;
use OC\Encryption\Exceptions\ModuleDoesNotExistsException;
use OC\Group\Manager;
use OCA\Settings\Controller\UsersController;
use OCP\Accounts\IAccountManager;
use OCP\App\IAppManager;
use OCP\AppFramework\Http;
use OCP\BackgroundJob\IJobList;
@ -171,6 +172,51 @@ class UsersControllerTest extends \Test\TestCase {
}
}
protected function getDefaultAccountManagerUserData() {
return [
IAccountManager::PROPERTY_DISPLAYNAME =>
[
'value' => 'Default display name',
'scope' => IAccountManager::VISIBILITY_CONTACTS_ONLY,
'verified' => IAccountManager::VERIFIED,
],
IAccountManager::PROPERTY_ADDRESS =>
[
'value' => 'Default address',
'scope' => IAccountManager::VISIBILITY_PRIVATE,
'verified' => IAccountManager::VERIFIED,
],
IAccountManager::PROPERTY_WEBSITE =>
[
'value' => 'Default website',
'scope' => IAccountManager::VISIBILITY_PRIVATE,
'verified' => IAccountManager::VERIFIED,
],
IAccountManager::PROPERTY_EMAIL =>
[
'value' => 'Default email',
'scope' => IAccountManager::VISIBILITY_CONTACTS_ONLY,
'verified' => IAccountManager::VERIFIED,
],
IAccountManager::PROPERTY_AVATAR =>
[
'scope' => IAccountManager::VISIBILITY_CONTACTS_ONLY
],
IAccountManager::PROPERTY_PHONE =>
[
'value' => 'Default phone',
'scope' => IAccountManager::VISIBILITY_PRIVATE,
'verified' => IAccountManager::VERIFIED,
],
IAccountManager::PROPERTY_TWITTER =>
[
'value' => 'Default twitter',
'scope' => IAccountManager::VISIBILITY_PRIVATE,
'verified' => IAccountManager::VERIFIED,
],
];
}
/**
* @dataProvider dataTestSetUserSettings
*
@ -195,48 +241,7 @@ class UsersControllerTest extends \Test\TestCase {
$this->accountManager->expects($this->once())
->method('getUser')
->with($user)
->willReturn([
AccountManager::PROPERTY_DISPLAYNAME =>
[
'value' => 'Display name',
'scope' => AccountManager::VISIBILITY_CONTACTS_ONLY,
'verified' => AccountManager::NOT_VERIFIED,
],
AccountManager::PROPERTY_ADDRESS =>
[
'value' => '',
'scope' => AccountManager::VISIBILITY_PRIVATE,
'verified' => AccountManager::NOT_VERIFIED,
],
AccountManager::PROPERTY_WEBSITE =>
[
'value' => '',
'scope' => AccountManager::VISIBILITY_PRIVATE,
'verified' => AccountManager::NOT_VERIFIED,
],
AccountManager::PROPERTY_EMAIL =>
[
'value' => '',
'scope' => AccountManager::VISIBILITY_CONTACTS_ONLY,
'verified' => AccountManager::NOT_VERIFIED,
],
AccountManager::PROPERTY_AVATAR =>
[
'scope' => AccountManager::VISIBILITY_CONTACTS_ONLY
],
AccountManager::PROPERTY_PHONE =>
[
'value' => '',
'scope' => AccountManager::VISIBILITY_PRIVATE,
'verified' => AccountManager::NOT_VERIFIED,
],
AccountManager::PROPERTY_TWITTER =>
[
'value' => '',
'scope' => AccountManager::VISIBILITY_PRIVATE,
'verified' => AccountManager::NOT_VERIFIED,
],
]);
->willReturn($this->getDefaultAccountManagerUserData());
$controller->expects($this->once())->method('saveUserSettings');
} else {
@ -271,6 +276,269 @@ class UsersControllerTest extends \Test\TestCase {
];
}
public function testSetUserSettingsWhenUserDisplayNameChangeNotAllowed() {
$controller = $this->getController(false, ['saveUserSettings']);
$user = $this->createMock(IUser::class);
$this->userSession->method('getUser')->willReturn($user);
$defaultProperties = $this->getDefaultAccountManagerUserData();
$this->accountManager->expects($this->once())
->method('getUser')
->with($user)
->willReturn($defaultProperties);
$this->config->expects($this->once())
->method('getSystemValue')
->with('allow_user_to_change_display_name')
->willReturn(false);
$this->appManager->expects($this->once())
->method('isEnabledForUser')
->with('federatedfilesharing')
->willReturn(true);
$avatarScope = IAccountManager::VISIBILITY_PUBLIC;
$displayName = 'Display name';
$displayNameScope = IAccountManager::VISIBILITY_PUBLIC;
$phone = '47658468';
$phoneScope = IAccountManager::VISIBILITY_PUBLIC;
$email = 'john@example.com';
$emailScope = IAccountManager::VISIBILITY_PUBLIC;
$website = 'nextcloud.com';
$websiteScope = IAccountManager::VISIBILITY_PUBLIC;
$address = 'street and city';
$addressScope = IAccountManager::VISIBILITY_PUBLIC;
$twitter = '@nextclouders';
$twitterScope = IAccountManager::VISIBILITY_PUBLIC;
// Display name and email are not changed.
$expectedProperties = $defaultProperties;
$expectedProperties[IAccountManager::PROPERTY_AVATAR]['scope'] = $avatarScope;
$expectedProperties[IAccountManager::PROPERTY_PHONE]['value'] = $phone;
$expectedProperties[IAccountManager::PROPERTY_PHONE]['scope'] = $phoneScope;
$expectedProperties[IAccountManager::PROPERTY_WEBSITE]['value'] = $website;
$expectedProperties[IAccountManager::PROPERTY_WEBSITE]['scope'] = $websiteScope;
$expectedProperties[IAccountManager::PROPERTY_ADDRESS]['value'] = $address;
$expectedProperties[IAccountManager::PROPERTY_ADDRESS]['scope'] = $addressScope;
$expectedProperties[IAccountManager::PROPERTY_TWITTER]['value'] = $twitter;
$expectedProperties[IAccountManager::PROPERTY_TWITTER]['scope'] = $twitterScope;
$this->mailer->expects($this->once())->method('validateMailAddress')
->willReturn(true);
$controller->expects($this->once())
->method('saveUserSettings')
->with($user, $expectedProperties)
->willReturnArgument(1);
$result = $controller->setUserSettings(
$avatarScope,
$displayName,
$displayNameScope,
$phone,
$phoneScope,
$email,
$emailScope,
$website,
$websiteScope,
$address,
$addressScope,
$twitter,
$twitterScope
);
}
public function testSetUserSettingsWhenFederatedFilesharingNotEnabled() {
$controller = $this->getController(false, ['saveUserSettings']);
$user = $this->createMock(IUser::class);
$this->userSession->method('getUser')->willReturn($user);
$defaultProperties = $this->getDefaultAccountManagerUserData();
$this->accountManager->expects($this->once())
->method('getUser')
->with($user)
->willReturn($defaultProperties);
$this->appManager->expects($this->once())
->method('isEnabledForUser')
->with('federatedfilesharing')
->willReturn(false);
$avatarScope = IAccountManager::VISIBILITY_PUBLIC;
$displayName = 'Display name';
$displayNameScope = IAccountManager::VISIBILITY_PUBLIC;
$phone = '47658468';
$phoneScope = IAccountManager::VISIBILITY_PUBLIC;
$email = 'john@example.com';
$emailScope = IAccountManager::VISIBILITY_PUBLIC;
$website = 'nextcloud.com';
$websiteScope = IAccountManager::VISIBILITY_PUBLIC;
$address = 'street and city';
$addressScope = IAccountManager::VISIBILITY_PUBLIC;
$twitter = '@nextclouders';
$twitterScope = IAccountManager::VISIBILITY_PUBLIC;
// Phone, website, address and twitter are not changed.
$expectedProperties = $defaultProperties;
$expectedProperties[IAccountManager::PROPERTY_AVATAR]['scope'] = $avatarScope;
$expectedProperties[IAccountManager::PROPERTY_DISPLAYNAME]['value'] = $displayName;
$expectedProperties[IAccountManager::PROPERTY_DISPLAYNAME]['scope'] = $displayNameScope;
$expectedProperties[IAccountManager::PROPERTY_EMAIL]['value'] = $email;
$expectedProperties[IAccountManager::PROPERTY_EMAIL]['scope'] = $emailScope;
$this->mailer->expects($this->once())->method('validateMailAddress')
->willReturn(true);
$controller->expects($this->once())
->method('saveUserSettings')
->with($user, $expectedProperties)
->willReturnArgument(1);
$result = $controller->setUserSettings(
$avatarScope,
$displayName,
$displayNameScope,
$phone,
$phoneScope,
$email,
$emailScope,
$website,
$websiteScope,
$address,
$addressScope,
$twitter,
$twitterScope
);
}
/**
* @dataProvider dataTestSetUserSettingsSubset
*
* @param string $property
* @param string $propertyValue
*/
public function testSetUserSettingsSubset($property, $propertyValue) {
$controller = $this->getController(false, ['saveUserSettings']);
$user = $this->createMock(IUser::class);
$this->userSession->method('getUser')->willReturn($user);
$defaultProperties = $this->getDefaultAccountManagerUserData();
$this->accountManager->expects($this->once())
->method('getUser')
->with($user)
->willReturn($defaultProperties);
$this->appManager->expects($this->once())
->method('isEnabledForUser')
->with('federatedfilesharing')
->willReturn(true);
$avatarScope = ($property === 'avatarScope') ? $propertyValue : null;
$displayName = ($property === 'displayName') ? $propertyValue : null;
$displayNameScope = ($property === 'displayNameScope') ? $propertyValue : null;
$phone = ($property === 'phone') ? $propertyValue : null;
$phoneScope = ($property === 'phoneScope') ? $propertyValue : null;
$email = ($property === 'email') ? $propertyValue : null;
$emailScope = ($property === 'emailScope') ? $propertyValue : null;
$website = ($property === 'website') ? $propertyValue : null;
$websiteScope = ($property === 'websiteScope') ? $propertyValue : null;
$address = ($property === 'address') ? $propertyValue : null;
$addressScope = ($property === 'addressScope') ? $propertyValue : null;
$twitter = ($property === 'twitter') ? $propertyValue : null;
$twitterScope = ($property === 'twitterScope') ? $propertyValue : null;
$expectedProperties = $defaultProperties;
if ($property === 'avatarScope') {
$expectedProperties[IAccountManager::PROPERTY_AVATAR]['scope'] = $propertyValue;
}
if ($property === 'displayName') {
$expectedProperties[IAccountManager::PROPERTY_DISPLAYNAME]['value'] = $propertyValue;
}
if ($property === 'displayNameScope') {
$expectedProperties[IAccountManager::PROPERTY_DISPLAYNAME]['scope'] = $propertyValue;
}
if ($property === 'phone') {
$expectedProperties[IAccountManager::PROPERTY_PHONE]['value'] = $propertyValue;
}
if ($property === 'phoneScope') {
$expectedProperties[IAccountManager::PROPERTY_PHONE]['scope'] = $propertyValue;
}
if ($property === 'email') {
$expectedProperties[IAccountManager::PROPERTY_EMAIL]['value'] = $propertyValue;
}
if ($property === 'emailScope') {
$expectedProperties[IAccountManager::PROPERTY_EMAIL]['scope'] = $propertyValue;
}
if ($property === 'website') {
$expectedProperties[IAccountManager::PROPERTY_WEBSITE]['value'] = $propertyValue;
}
if ($property === 'websiteScope') {
$expectedProperties[IAccountManager::PROPERTY_WEBSITE]['scope'] = $propertyValue;
}
if ($property === 'address') {
$expectedProperties[IAccountManager::PROPERTY_ADDRESS]['value'] = $propertyValue;
}
if ($property === 'addressScope') {
$expectedProperties[IAccountManager::PROPERTY_ADDRESS]['scope'] = $propertyValue;
}
if ($property === 'twitter') {
$expectedProperties[IAccountManager::PROPERTY_TWITTER]['value'] = $propertyValue;
}
if ($property === 'twitterScope') {
$expectedProperties[IAccountManager::PROPERTY_TWITTER]['scope'] = $propertyValue;
}
if (!empty($email)) {
$this->mailer->expects($this->once())->method('validateMailAddress')
->willReturn(true);
}
$controller->expects($this->once())
->method('saveUserSettings')
->with($user, $expectedProperties)
->willReturnArgument(1);
$result = $controller->setUserSettings(
$avatarScope,
$displayName,
$displayNameScope,
$phone,
$phoneScope,
$email,
$emailScope,
$website,
$websiteScope,
$address,
$addressScope,
$twitter,
$twitterScope
);
}
public function dataTestSetUserSettingsSubset() {
return [
['avatarScope', IAccountManager::VISIBILITY_PUBLIC],
['displayName', 'Display name'],
['displayNameScope', IAccountManager::VISIBILITY_PUBLIC],
['phone', '47658468'],
['phoneScope', IAccountManager::VISIBILITY_PUBLIC],
['email', 'john@example.com'],
['emailScope', IAccountManager::VISIBILITY_PUBLIC],
['website', 'nextcloud.com'],
['websiteScope', IAccountManager::VISIBILITY_PUBLIC],
['address', 'street and city'],
['addressScope', IAccountManager::VISIBILITY_PUBLIC],
['twitter', '@nextclouders'],
['twitterScope', IAccountManager::VISIBILITY_PUBLIC],
];
}
/**
* @dataProvider dataTestSaveUserSettings
*

View File

@ -307,21 +307,31 @@ trait BasicStructure {
* @When Sending a :method to :url with requesttoken
* @param string $method
* @param string $url
* @param TableNode|array|null $body
*/
public function sendingAToWithRequesttoken($method, $url) {
public function sendingAToWithRequesttoken($method, $url, $body = null) {
$baseUrl = substr($this->baseUrl, 0, -5);
$options = [
'cookies' => $this->cookieJar,
'headers' => [
'requesttoken' => $this->requestToken
],
];
if ($body instanceof TableNode) {
$fd = $body->getRowsHash();
$options['form_params'] = $fd;
} elseif ($body) {
$options = array_merge($options, $body);
}
$client = new Client();
try {
$this->response = $client->request(
$method,
$baseUrl . $url,
[
'cookies' => $this->cookieJar,
'headers' => [
'requesttoken' => $this->requestToken
]
]
$options
);
} catch (ClientException $e) {
$this->response = $e->getResponse();

View File

@ -0,0 +1,69 @@
<?php
/**
* @copyright Copyright (c) 2021 Daniel Calviño Sánchez <danxuliu@gmail.com>
*
* @author Daniel Calviño Sánchez <danxuliu@gmail.com>
*
* @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 <http://www.gnu.org/licenses/>.
*
*/
use PHPUnit\Framework\Assert;
trait ContactsMenu {
// BasicStructure trait is expected to be used in the class that uses this
// trait.
/**
* @When /^searching for contacts matching with "([^"]*)"$/
*
* @param string $filter
*/
public function searchingForContactsMatchingWith(string $filter) {
$url = '/index.php/contactsmenu/contacts';
$parameters[] = 'filter=' . $filter;
$url .= '?' . implode('&', $parameters);
$this->sendingAToWithRequesttoken('POST', $url);
}
/**
* @Then /^the list of searched contacts has "(\d+)" contacts$/
*/
public function theListOfSearchedContactsHasContacts(int $count) {
$this->theHTTPStatusCodeShouldBe(200);
$searchedContacts = json_decode($this->response->getBody(), $asAssociativeArray = true)['contacts'];
Assert::assertEquals($count, count($searchedContacts));
}
/**
* @Then /^searched contact "(\d+)" is named "([^"]*)"$/
*
* @param int $index
* @param string $expectedName
*/
public function searchedContactXIsNamed(int $index, string $expectedName) {
$searchedContacts = json_decode($this->response->getBody(), $asAssociativeArray = true)['contacts'];
$searchedContact = $searchedContacts[$index];
Assert::assertEquals($expectedName, $searchedContact['fullName']);
}
}

View File

@ -33,6 +33,7 @@ require __DIR__ . '/../../vendor/autoload.php';
* Features context.
*/
class FeatureContext implements Context, SnippetAcceptingContext {
use ContactsMenu;
use Search;
use WebDav;
}

View File

@ -0,0 +1,188 @@
Feature: contacts-menu
Scenario: users can be searched by display name
Given user "user0" exists
And user "user1" exists
And As an "admin"
And sending "PUT" to "/cloud/users/user1" with
| key | displayname |
| value | Test name |
When Logging in using web as "user0"
And searching for contacts matching with "test"
Then the list of searched contacts has "1" contacts
And searched contact "0" is named "Test name"
Scenario: users can be searched by email
Given user "user0" exists
And user "user1" exists
And As an "admin"
And sending "PUT" to "/cloud/users/user1" with
| key | email |
| value | test@example.com |
When Logging in using web as "user0"
And searching for contacts matching with "test"
Then the list of searched contacts has "1" contacts
And searched contact "0" is named "user1"
Scenario: users can not be searched by id
Given user "user0" exists
And user "user1" exists
And As an "admin"
And sending "PUT" to "/cloud/users/user1" with
| key | displayname |
| value | Test name |
When Logging in using web as "user0"
And searching for contacts matching with "user"
Then the list of searched contacts has "0" contacts
Scenario: search several users
Given user "user0" exists
And user "user1" exists
And user "user2" exists
And user "user3" exists
And user "user4" exists
And user "user5" exists
And As an "admin"
And sending "PUT" to "/cloud/users/user1" with
| key | displayname |
| value | Test name |
And sending "PUT" to "/cloud/users/user2" with
| key | email |
| value | test@example.com |
And sending "PUT" to "/cloud/users/user3" with
| key | displayname |
| value | Unmatched name |
And sending "PUT" to "/cloud/users/user4" with
| key | email |
| value | unmatched@example.com |
And sending "PUT" to "/cloud/users/user5" with
| key | displayname |
| value | Another test name |
And sending "PUT" to "/cloud/users/user5" with
| key | email |
| value | another_test@example.com |
When Logging in using web as "user0"
And searching for contacts matching with "test"
Then the list of searched contacts has "3" contacts
# Results are sorted alphabetically
And searched contact "0" is named "Another test name"
And searched contact "1" is named "Test name"
And searched contact "2" is named "user2"
Scenario: users can not be found by display name if visibility is private
Given user "user0" exists
And user "user1" exists
And user "user2" exists
And Logging in using web as "user1"
And Sending a "PUT" to "/settings/users/user1/settings" with requesttoken
| displayname | Test name |
| displaynameScope | private |
And Logging in using web as "user2"
And Sending a "PUT" to "/settings/users/user2/settings" with requesttoken
| displayname | Another test name |
| displaynameScope | contacts |
When Logging in using web as "user0"
And searching for contacts matching with "test"
Then the list of searched contacts has "1" contacts
And searched contact "0" is named "Another test name"
Scenario: users can not be found by email if visibility is private
Given user "user0" exists
And user "user1" exists
And user "user2" exists
And Logging in using web as "user1"
And Sending a "PUT" to "/settings/users/user1/settings" with requesttoken
| email | test@example.com |
| emailScope | private |
And Logging in using web as "user2"
And Sending a "PUT" to "/settings/users/user2/settings" with requesttoken
| email | another_test@example.com |
| emailScope | contacts |
When Logging in using web as "user0"
And searching for contacts matching with "test"
Then the list of searched contacts has "1" contacts
And searched contact "0" is named "user2"
Scenario: users can be found by other properties if the visibility of one is private
Given user "user0" exists
And user "user1" exists
And user "user2" exists
And Logging in using web as "user1"
And Sending a "PUT" to "/settings/users/user1/settings" with requesttoken
| displayname | Test name |
| displaynameScope | contacts |
| email | test@example.com |
| emailScope | private |
And Logging in using web as "user2"
And Sending a "PUT" to "/settings/users/user2/settings" with requesttoken
| displayname | Another test name |
| displaynameScope | private |
| email | another_test@example.com |
| emailScope | contacts |
When Logging in using web as "user0"
And searching for contacts matching with "test"
Then the list of searched contacts has "2" contacts
And searched contact "0" is named ""
And searched contact "1" is named "Test name"
Scenario: users can be searched by display name if visibility is increased again
Given user "user0" exists
And user "user1" exists
And Logging in using web as "user1"
And Sending a "PUT" to "/settings/users/user1/settings" with requesttoken
| displayname | Test name |
| displaynameScope | private |
And Sending a "PUT" to "/settings/users/user1/settings" with requesttoken
| displaynameScope | contacts |
When Logging in using web as "user0"
And searching for contacts matching with "test"
Then the list of searched contacts has "1" contacts
And searched contact "0" is named "Test name"
Scenario: users can be searched by email if visibility is increased again
Given user "user0" exists
And user "user1" exists
And Logging in using web as "user1"
And Sending a "PUT" to "/settings/users/user1/settings" with requesttoken
| email | test@example.com |
| emailScope | private |
And Sending a "PUT" to "/settings/users/user1/settings" with requesttoken
| emailScope | contacts |
When Logging in using web as "user0"
And searching for contacts matching with "test"
Then the list of searched contacts has "1" contacts
And searched contact "0" is named "user1"
Scenario: users can not be searched by display name if visibility is private even if updated with provisioning
Given user "user0" exists
And user "user1" exists
And Logging in using web as "user1"
And Sending a "PUT" to "/settings/users/user1/settings" with requesttoken
| displaynameScope | private |
And As an "admin"
And sending "PUT" to "/cloud/users/user1" with
| key | displayname |
| value | Test name |
When Logging in using web as "user0"
And searching for contacts matching with "test"
Then the list of searched contacts has "0" contacts
Scenario: users can not be searched by email if visibility is private even if updated with provisioning
Given user "user0" exists
And user "user1" exists
And Logging in using web as "user1"
And Sending a "PUT" to "/settings/users/user1/settings" with requesttoken
| emailScope | private |
And As an "admin"
And sending "PUT" to "/cloud/users/user1" with
| key | email |
| value | test@example.com |
When Logging in using web as "user0"
And searching for contacts matching with "test"
Then the list of searched contacts has "0" contacts