Add a setting to restrict returning a full match unless in phonebook or same group
Signed-off-by: Joas Schilling <coding@schilljs.com>
This commit is contained in:
parent
8069c52a85
commit
77f6d768bc
|
@ -263,6 +263,7 @@ class Principal implements BackendInterface {
|
||||||
$allowEnumeration = $this->shareManager->allowEnumeration();
|
$allowEnumeration = $this->shareManager->allowEnumeration();
|
||||||
$limitEnumerationGroup = $this->shareManager->limitEnumerationToGroups();
|
$limitEnumerationGroup = $this->shareManager->limitEnumerationToGroups();
|
||||||
$limitEnumerationPhone = $this->shareManager->limitEnumerationToPhone();
|
$limitEnumerationPhone = $this->shareManager->limitEnumerationToPhone();
|
||||||
|
$allowEnumerationFullMatch = $this->shareManager->allowEnumerationFullMatch();
|
||||||
|
|
||||||
// If sharing is restricted to group members only,
|
// If sharing is restricted to group members only,
|
||||||
// return only members that have groups in common
|
// return only members that have groups in common
|
||||||
|
@ -290,15 +291,19 @@ class Principal implements BackendInterface {
|
||||||
foreach ($searchProperties as $prop => $value) {
|
foreach ($searchProperties as $prop => $value) {
|
||||||
switch ($prop) {
|
switch ($prop) {
|
||||||
case '{http://sabredav.org/ns}email-address':
|
case '{http://sabredav.org/ns}email-address':
|
||||||
$users = $this->userManager->getByEmail($value);
|
|
||||||
|
|
||||||
if (!$allowEnumeration) {
|
if (!$allowEnumeration) {
|
||||||
$users = \array_filter($users, static function (IUser $user) use ($value) {
|
if ($allowEnumerationFullMatch) {
|
||||||
return $user->getEMailAddress() === $value;
|
$users = $this->userManager->getByEmail($value);
|
||||||
});
|
$users = \array_filter($users, static function (IUser $user) use ($value) {
|
||||||
|
return $user->getEMailAddress() === $value;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
$users = [];
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
$users = \array_filter($users, function (IUser $user) use ($currentUser, $value, $limitEnumerationPhone, $limitEnumerationGroup, $currentUserGroups) {
|
$users = $this->userManager->getByEmail($value);
|
||||||
if ($user->getEMailAddress() === $value) {
|
$users = \array_filter($users, function (IUser $user) use ($currentUser, $value, $limitEnumerationPhone, $limitEnumerationGroup, $allowEnumerationFullMatch, $currentUserGroups) {
|
||||||
|
if ($allowEnumerationFullMatch && $user->getEMailAddress() === $value) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -336,15 +341,20 @@ class Principal implements BackendInterface {
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case '{DAV:}displayname':
|
case '{DAV:}displayname':
|
||||||
$users = $this->userManager->searchDisplayName($value, $searchLimit);
|
|
||||||
|
|
||||||
if (!$allowEnumeration) {
|
if (!$allowEnumeration) {
|
||||||
$users = \array_filter($users, static function (IUser $user) use ($value) {
|
if ($allowEnumerationFullMatch) {
|
||||||
return $user->getDisplayName() === $value;
|
$users = $this->userManager->searchDisplayName($value, $searchLimit);
|
||||||
});
|
$users = \array_filter($users, static function (IUser $user) use ($value) {
|
||||||
|
return $user->getDisplayName() === $value;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
$users = [];
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
$users = \array_filter($users, function (IUser $user) use ($currentUser, $value, $limitEnumerationPhone, $limitEnumerationGroup, $currentUserGroups) {
|
$users = $this->userManager->searchDisplayName($value, $searchLimit);
|
||||||
if ($user->getDisplayName() === $value) {
|
$users = \array_filter($users, function (IUser $user) use ($currentUser, $value, $limitEnumerationPhone, $limitEnumerationGroup, $allowEnumerationFullMatch, $currentUserGroups) {
|
||||||
|
if ($allowEnumerationFullMatch && $user->getDisplayName() === $value) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -570,6 +570,10 @@ class PrincipalTest extends TestCase {
|
||||||
->method('shareWithGroupMembersOnly')
|
->method('shareWithGroupMembersOnly')
|
||||||
->willReturn(false);
|
->willReturn(false);
|
||||||
|
|
||||||
|
$this->shareManager->expects($this->once())
|
||||||
|
->method('allowEnumerationFullMatch')
|
||||||
|
->willReturn(true);
|
||||||
|
|
||||||
$user2 = $this->createMock(IUser::class);
|
$user2 = $this->createMock(IUser::class);
|
||||||
$user2->method('getUID')->willReturn('user2');
|
$user2->method('getUID')->willReturn('user2');
|
||||||
$user2->method('getDisplayName')->willReturn('User 2');
|
$user2->method('getDisplayName')->willReturn('User 2');
|
||||||
|
@ -592,6 +596,27 @@ class PrincipalTest extends TestCase {
|
||||||
['{DAV:}displayname' => 'User 2']));
|
['{DAV:}displayname' => 'User 2']));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testSearchPrincipalWithEnumerationDisabledDisplaynameOnFullMatch() {
|
||||||
|
$this->shareManager->expects($this->once())
|
||||||
|
->method('shareAPIEnabled')
|
||||||
|
->willReturn(true);
|
||||||
|
|
||||||
|
$this->shareManager->expects($this->once())
|
||||||
|
->method('allowEnumeration')
|
||||||
|
->willReturn(false);
|
||||||
|
|
||||||
|
$this->shareManager->expects($this->once())
|
||||||
|
->method('shareWithGroupMembersOnly')
|
||||||
|
->willReturn(false);
|
||||||
|
|
||||||
|
$this->shareManager->expects($this->once())
|
||||||
|
->method('allowEnumerationFullMatch')
|
||||||
|
->willReturn(false);
|
||||||
|
|
||||||
|
$this->assertEquals([], $this->connector->searchPrincipals('principals/users',
|
||||||
|
['{DAV:}displayname' => 'User 2']));
|
||||||
|
}
|
||||||
|
|
||||||
public function testSearchPrincipalWithEnumerationDisabledEmail() {
|
public function testSearchPrincipalWithEnumerationDisabledEmail() {
|
||||||
$this->shareManager->expects($this->once())
|
$this->shareManager->expects($this->once())
|
||||||
->method('shareAPIEnabled')
|
->method('shareAPIEnabled')
|
||||||
|
@ -605,6 +630,10 @@ class PrincipalTest extends TestCase {
|
||||||
->method('shareWithGroupMembersOnly')
|
->method('shareWithGroupMembersOnly')
|
||||||
->willReturn(false);
|
->willReturn(false);
|
||||||
|
|
||||||
|
$this->shareManager->expects($this->once())
|
||||||
|
->method('allowEnumerationFullMatch')
|
||||||
|
->willReturn(true);
|
||||||
|
|
||||||
$user2 = $this->createMock(IUser::class);
|
$user2 = $this->createMock(IUser::class);
|
||||||
$user2->method('getUID')->willReturn('user2');
|
$user2->method('getUID')->willReturn('user2');
|
||||||
$user2->method('getDisplayName')->willReturn('User 2');
|
$user2->method('getDisplayName')->willReturn('User 2');
|
||||||
|
@ -627,6 +656,28 @@ class PrincipalTest extends TestCase {
|
||||||
['{http://sabredav.org/ns}email-address' => 'user2@foo.bar']));
|
['{http://sabredav.org/ns}email-address' => 'user2@foo.bar']));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testSearchPrincipalWithEnumerationDisabledEmailOnFullMatch() {
|
||||||
|
$this->shareManager->expects($this->once())
|
||||||
|
->method('shareAPIEnabled')
|
||||||
|
->willReturn(true);
|
||||||
|
|
||||||
|
$this->shareManager->expects($this->once())
|
||||||
|
->method('allowEnumeration')
|
||||||
|
->willReturn(false);
|
||||||
|
|
||||||
|
$this->shareManager->expects($this->once())
|
||||||
|
->method('shareWithGroupMembersOnly')
|
||||||
|
->willReturn(false);
|
||||||
|
|
||||||
|
$this->shareManager->expects($this->once())
|
||||||
|
->method('allowEnumerationFullMatch')
|
||||||
|
->willReturn(false);
|
||||||
|
|
||||||
|
|
||||||
|
$this->assertEquals([], $this->connector->searchPrincipals('principals/users',
|
||||||
|
['{http://sabredav.org/ns}email-address' => 'user2@foo.bar']));
|
||||||
|
}
|
||||||
|
|
||||||
public function testSearchPrincipalWithEnumerationLimitedDisplayname() {
|
public function testSearchPrincipalWithEnumerationLimitedDisplayname() {
|
||||||
$this->shareManager->expects($this->at(0))
|
$this->shareManager->expects($this->at(0))
|
||||||
->method('shareAPIEnabled')
|
->method('shareAPIEnabled')
|
||||||
|
|
|
@ -74,6 +74,7 @@ class Sharing implements ISettings {
|
||||||
'allowShareDialogUserEnumeration' => $this->config->getAppValue('core', 'shareapi_allow_share_dialog_user_enumeration', 'yes'),
|
'allowShareDialogUserEnumeration' => $this->config->getAppValue('core', 'shareapi_allow_share_dialog_user_enumeration', 'yes'),
|
||||||
'restrictUserEnumerationToGroup' => $this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_to_group', 'no'),
|
'restrictUserEnumerationToGroup' => $this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_to_group', 'no'),
|
||||||
'restrictUserEnumerationToPhone' => $this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_to_phone', 'no'),
|
'restrictUserEnumerationToPhone' => $this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_to_phone', 'no'),
|
||||||
|
'restrictUserEnumerationFullMatch' => $this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_full_match', 'yes'),
|
||||||
'enforceLinkPassword' => Util::isPublicLinkPasswordRequired(),
|
'enforceLinkPassword' => Util::isPublicLinkPasswordRequired(),
|
||||||
'onlyShareWithGroupMembers' => $this->shareManager->shareWithGroupMembersOnly(),
|
'onlyShareWithGroupMembers' => $this->shareManager->shareWithGroupMembersOnly(),
|
||||||
'shareAPIEnabled' => $this->config->getAppValue('core', 'shareapi_enabled', 'yes'),
|
'shareAPIEnabled' => $this->config->getAppValue('core', 'shareapi_enabled', 'yes'),
|
||||||
|
|
|
@ -163,7 +163,7 @@
|
||||||
<?php if ($_['allowShareDialogUserEnumeration'] === 'yes') {
|
<?php if ($_['allowShareDialogUserEnumeration'] === 'yes') {
|
||||||
print_unescaped('checked="checked"');
|
print_unescaped('checked="checked"');
|
||||||
} ?> />
|
} ?> />
|
||||||
<label for="shareapi_allow_share_dialog_user_enumeration"><?php p($l->t('Allow username autocompletion in share dialog (if this is disabled the full username or email address needs to be entered)'));?></label><br />
|
<label for="shareapi_allow_share_dialog_user_enumeration"><?php p($l->t('Allow username autocompletion in share dialog'));?></label><br />
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p id="shareapi_restrict_user_enumeration_to_group_setting" class="indent <?php if ($_['shareAPIEnabled'] === 'no' || $_['allowShareDialogUserEnumeration'] === 'no') {
|
<p id="shareapi_restrict_user_enumeration_to_group_setting" class="indent <?php if ($_['shareAPIEnabled'] === 'no' || $_['allowShareDialogUserEnumeration'] === 'no') {
|
||||||
|
@ -190,6 +190,15 @@
|
||||||
}?>">
|
}?>">
|
||||||
<em><?php p($l->t('If autocompletion "same group" and "phonebook matches" are enabled a match in either is enough to show the user.'));?></em><br />
|
<em><?php p($l->t('If autocompletion "same group" and "phonebook matches" are enabled a match in either is enough to show the user.'));?></em><br />
|
||||||
</p>
|
</p>
|
||||||
|
<p id="shareapi_restrict_user_enumeration_full_match_setting" class="indent <?php if ($_['shareAPIEnabled'] === 'no') {
|
||||||
|
p('hidden');
|
||||||
|
}?>">
|
||||||
|
<input type="checkbox" name="shareapi_restrict_user_enumeration_full_match" value="1" id="shareapi_restrict_user_enumeration_full_match" class="checkbox"
|
||||||
|
<?php if ($_['restrictUserEnumerationFullMatch'] === 'yes') {
|
||||||
|
print_unescaped('checked="checked"');
|
||||||
|
} ?> />
|
||||||
|
<label for="shareapi_restrict_user_enumeration_full_match"><?php p($l->t('Allow username autocompletion when entering the full name or email address (ignoring missing phonebook match and being in the same group)'));?></label><br />
|
||||||
|
</p>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
<input type="checkbox" id="publicShareDisclaimer" class="checkbox noJSAutoUpdate"
|
<input type="checkbox" id="publicShareDisclaimer" class="checkbox noJSAutoUpdate"
|
||||||
|
|
|
@ -74,6 +74,7 @@ class SharingTest extends TestCase {
|
||||||
['core', 'shareapi_allow_share_dialog_user_enumeration', 'yes', 'yes'],
|
['core', 'shareapi_allow_share_dialog_user_enumeration', 'yes', 'yes'],
|
||||||
['core', 'shareapi_restrict_user_enumeration_to_group', 'no', 'no'],
|
['core', 'shareapi_restrict_user_enumeration_to_group', 'no', 'no'],
|
||||||
['core', 'shareapi_restrict_user_enumeration_to_phone', 'no', 'no'],
|
['core', 'shareapi_restrict_user_enumeration_to_phone', 'no', 'no'],
|
||||||
|
['core', 'shareapi_restrict_user_enumeration_full_match', 'yes', 'yes'],
|
||||||
['core', 'shareapi_enabled', 'yes', 'yes'],
|
['core', 'shareapi_enabled', 'yes', 'yes'],
|
||||||
['core', 'shareapi_default_expire_date', 'no', 'no'],
|
['core', 'shareapi_default_expire_date', 'no', 'no'],
|
||||||
['core', 'shareapi_expire_after_n_days', '7', '7'],
|
['core', 'shareapi_expire_after_n_days', '7', '7'],
|
||||||
|
@ -98,6 +99,7 @@ class SharingTest extends TestCase {
|
||||||
'allowShareDialogUserEnumeration' => 'yes',
|
'allowShareDialogUserEnumeration' => 'yes',
|
||||||
'restrictUserEnumerationToGroup' => 'no',
|
'restrictUserEnumerationToGroup' => 'no',
|
||||||
'restrictUserEnumerationToPhone' => 'no',
|
'restrictUserEnumerationToPhone' => 'no',
|
||||||
|
'restrictUserEnumerationFullMatch' => 'yes',
|
||||||
'enforceLinkPassword' => false,
|
'enforceLinkPassword' => false,
|
||||||
'onlyShareWithGroupMembers' => false,
|
'onlyShareWithGroupMembers' => false,
|
||||||
'shareAPIEnabled' => 'yes',
|
'shareAPIEnabled' => 'yes',
|
||||||
|
@ -132,6 +134,7 @@ class SharingTest extends TestCase {
|
||||||
['core', 'shareapi_allow_share_dialog_user_enumeration', 'yes', 'yes'],
|
['core', 'shareapi_allow_share_dialog_user_enumeration', 'yes', 'yes'],
|
||||||
['core', 'shareapi_restrict_user_enumeration_to_group', 'no', 'no'],
|
['core', 'shareapi_restrict_user_enumeration_to_group', 'no', 'no'],
|
||||||
['core', 'shareapi_restrict_user_enumeration_to_phone', 'no', 'no'],
|
['core', 'shareapi_restrict_user_enumeration_to_phone', 'no', 'no'],
|
||||||
|
['core', 'shareapi_restrict_user_enumeration_full_match', 'yes', 'yes'],
|
||||||
['core', 'shareapi_enabled', 'yes', 'yes'],
|
['core', 'shareapi_enabled', 'yes', 'yes'],
|
||||||
['core', 'shareapi_default_expire_date', 'no', 'no'],
|
['core', 'shareapi_default_expire_date', 'no', 'no'],
|
||||||
['core', 'shareapi_expire_after_n_days', '7', '7'],
|
['core', 'shareapi_expire_after_n_days', '7', '7'],
|
||||||
|
@ -156,6 +159,7 @@ class SharingTest extends TestCase {
|
||||||
'allowShareDialogUserEnumeration' => 'yes',
|
'allowShareDialogUserEnumeration' => 'yes',
|
||||||
'restrictUserEnumerationToGroup' => 'no',
|
'restrictUserEnumerationToGroup' => 'no',
|
||||||
'restrictUserEnumerationToPhone' => 'no',
|
'restrictUserEnumerationToPhone' => 'no',
|
||||||
|
'restrictUserEnumerationFullMatch' => 'yes',
|
||||||
'enforceLinkPassword' => false,
|
'enforceLinkPassword' => false,
|
||||||
'onlyShareWithGroupMembers' => false,
|
'onlyShareWithGroupMembers' => false,
|
||||||
'shareAPIEnabled' => 'yes',
|
'shareAPIEnabled' => 'yes',
|
||||||
|
|
|
@ -3,6 +3,7 @@ Feature: autocomplete
|
||||||
Given using api version "2"
|
Given using api version "2"
|
||||||
And group "commongroup" exists
|
And group "commongroup" exists
|
||||||
And user "admin" belongs to group "commongroup"
|
And user "admin" belongs to group "commongroup"
|
||||||
|
And user "auto" exists
|
||||||
And user "autocomplete" exists
|
And user "autocomplete" exists
|
||||||
And user "autocomplete2" exists
|
And user "autocomplete2" exists
|
||||||
And user "autocomplete2" belongs to group "commongroup"
|
And user "autocomplete2" belongs to group "commongroup"
|
||||||
|
@ -20,9 +21,15 @@ Feature: autocomplete
|
||||||
When parameter "shareapi_allow_share_dialog_user_enumeration" of app "core" is set to "no"
|
When parameter "shareapi_allow_share_dialog_user_enumeration" of app "core" is set to "no"
|
||||||
Then get autocomplete for "auto"
|
Then get autocomplete for "auto"
|
||||||
| id | source |
|
| id | source |
|
||||||
|
| auto | users |
|
||||||
Then get autocomplete for "autocomplete"
|
Then get autocomplete for "autocomplete"
|
||||||
| id | source |
|
| id | source |
|
||||||
| autocomplete | users |
|
| autocomplete | users |
|
||||||
|
When parameter "shareapi_restrict_user_enumeration_full_match" of app "core" is set to "no"
|
||||||
|
Then get autocomplete for "auto"
|
||||||
|
| id | source |
|
||||||
|
Then get autocomplete for "autocomplete"
|
||||||
|
| id | source |
|
||||||
|
|
||||||
|
|
||||||
Scenario: getting autocomplete with limited enumeration by group
|
Scenario: getting autocomplete with limited enumeration by group
|
||||||
|
@ -30,6 +37,7 @@ Feature: autocomplete
|
||||||
When parameter "shareapi_restrict_user_enumeration_to_group" of app "core" is set to "yes"
|
When parameter "shareapi_restrict_user_enumeration_to_group" of app "core" is set to "yes"
|
||||||
Then get autocomplete for "auto"
|
Then get autocomplete for "auto"
|
||||||
| id | source |
|
| id | source |
|
||||||
|
| auto | users |
|
||||||
| autocomplete2 | users |
|
| autocomplete2 | users |
|
||||||
Then get autocomplete for "autocomplete"
|
Then get autocomplete for "autocomplete"
|
||||||
| id | source |
|
| id | source |
|
||||||
|
@ -38,6 +46,13 @@ Feature: autocomplete
|
||||||
Then get autocomplete for "autocomplete2"
|
Then get autocomplete for "autocomplete2"
|
||||||
| id | source |
|
| id | source |
|
||||||
| autocomplete2 | users |
|
| autocomplete2 | users |
|
||||||
|
When parameter "shareapi_restrict_user_enumeration_full_match" of app "core" is set to "no"
|
||||||
|
Then get autocomplete for "autocomplete"
|
||||||
|
| id | source |
|
||||||
|
| autocomplete2 | users |
|
||||||
|
Then get autocomplete for "autocomplete2"
|
||||||
|
| id | source |
|
||||||
|
| autocomplete2 | users |
|
||||||
|
|
||||||
|
|
||||||
Scenario: getting autocomplete with limited enumeration by phone
|
Scenario: getting autocomplete with limited enumeration by phone
|
||||||
|
@ -45,6 +60,7 @@ Feature: autocomplete
|
||||||
When parameter "shareapi_restrict_user_enumeration_to_phone" of app "core" is set to "yes"
|
When parameter "shareapi_restrict_user_enumeration_to_phone" of app "core" is set to "yes"
|
||||||
Then get autocomplete for "auto"
|
Then get autocomplete for "auto"
|
||||||
| id | source |
|
| id | source |
|
||||||
|
| auto | users |
|
||||||
|
|
||||||
# autocomplete stores their phone number
|
# autocomplete stores their phone number
|
||||||
Given As an "autocomplete"
|
Given As an "autocomplete"
|
||||||
|
@ -57,10 +73,17 @@ Feature: autocomplete
|
||||||
Given As an "admin"
|
Given As an "admin"
|
||||||
Then get autocomplete for "auto"
|
Then get autocomplete for "auto"
|
||||||
| id | source |
|
| id | source |
|
||||||
|
| auto | users |
|
||||||
|
|
||||||
# admin populates they have the phone number
|
# admin populates they have the phone number
|
||||||
When search users by phone for region "DE" with
|
When search users by phone for region "DE" with
|
||||||
| random-string1 | 0711 / 252 428-90 |
|
| random-string1 | 0711 / 252 428-90 |
|
||||||
|
Then get autocomplete for "auto"
|
||||||
|
| id | source |
|
||||||
|
| auto | users |
|
||||||
|
| autocomplete | users |
|
||||||
|
|
||||||
|
When parameter "shareapi_restrict_user_enumeration_full_match" of app "core" is set to "no"
|
||||||
Then get autocomplete for "auto"
|
Then get autocomplete for "auto"
|
||||||
| id | source |
|
| id | source |
|
||||||
| autocomplete | users |
|
| autocomplete | users |
|
||||||
|
@ -83,6 +106,13 @@ Feature: autocomplete
|
||||||
When search users by phone for region "DE" with
|
When search users by phone for region "DE" with
|
||||||
| random-string1 | 0711 / 252 428-90 |
|
| random-string1 | 0711 / 252 428-90 |
|
||||||
|
|
||||||
|
Then get autocomplete for "auto"
|
||||||
|
| id | source |
|
||||||
|
| auto | users |
|
||||||
|
| autocomplete | users |
|
||||||
|
| autocomplete2 | users |
|
||||||
|
|
||||||
|
When parameter "shareapi_restrict_user_enumeration_full_match" of app "core" is set to "no"
|
||||||
Then get autocomplete for "auto"
|
Then get autocomplete for "auto"
|
||||||
| id | source |
|
| id | source |
|
||||||
| autocomplete | users |
|
| autocomplete | users |
|
||||||
|
@ -108,6 +138,7 @@ Feature: autocomplete
|
||||||
|
|
||||||
Then get autocomplete for "auto"
|
Then get autocomplete for "auto"
|
||||||
| id | source |
|
| id | source |
|
||||||
|
| auto | users |
|
||||||
| autocomplete | users |
|
| autocomplete | users |
|
||||||
| autocomplete2 | users |
|
| autocomplete2 | users |
|
||||||
When parameter "shareapi_only_share_with_group_members" of app "core" is set to "yes"
|
When parameter "shareapi_only_share_with_group_members" of app "core" is set to "yes"
|
||||||
|
@ -121,6 +152,7 @@ Feature: autocomplete
|
||||||
When parameter "shareapi_restrict_user_enumeration_to_phone" of app "core" is set to "yes"
|
When parameter "shareapi_restrict_user_enumeration_to_phone" of app "core" is set to "yes"
|
||||||
Then get autocomplete for "auto"
|
Then get autocomplete for "auto"
|
||||||
| id | source |
|
| id | source |
|
||||||
|
| auto | users |
|
||||||
|
|
||||||
# autocomplete stores their phone number
|
# autocomplete stores their phone number
|
||||||
Given As an "autocomplete"
|
Given As an "autocomplete"
|
||||||
|
@ -133,12 +165,14 @@ Feature: autocomplete
|
||||||
Given As an "admin"
|
Given As an "admin"
|
||||||
Then get autocomplete for "auto"
|
Then get autocomplete for "auto"
|
||||||
| id | source |
|
| id | source |
|
||||||
|
| auto | users |
|
||||||
|
|
||||||
# admin populates they have the phone number
|
# admin populates they have the phone number
|
||||||
When search users by phone for region "DE" with
|
When search users by phone for region "DE" with
|
||||||
| random-string1 | 0711 / 252 428-90 |
|
| random-string1 | 0711 / 252 428-90 |
|
||||||
Then get autocomplete for "auto"
|
Then get autocomplete for "auto"
|
||||||
| id | source |
|
| id | source |
|
||||||
|
| auto | users |
|
||||||
| autocomplete | users |
|
| autocomplete | users |
|
||||||
|
|
||||||
# autocomplete changes their phone number
|
# autocomplete changes their phone number
|
||||||
|
@ -152,12 +186,14 @@ Feature: autocomplete
|
||||||
Given As an "admin"
|
Given As an "admin"
|
||||||
Then get autocomplete for "auto"
|
Then get autocomplete for "auto"
|
||||||
| id | source |
|
| id | source |
|
||||||
|
| auto | users |
|
||||||
|
|
||||||
# admin populates they have the new phone number
|
# admin populates they have the new phone number
|
||||||
When search users by phone for region "DE" with
|
When search users by phone for region "DE" with
|
||||||
| random-string1 | 0711 / 252 428-91 |
|
| random-string1 | 0711 / 252 428-91 |
|
||||||
Then get autocomplete for "auto"
|
Then get autocomplete for "auto"
|
||||||
| id | source |
|
| id | source |
|
||||||
|
| auto | users |
|
||||||
| autocomplete | users |
|
| autocomplete | users |
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -66,6 +66,7 @@ class CollaborationContext implements Context {
|
||||||
$this->deleteServerConfig('core', 'shareapi_allow_share_dialog_user_enumeration');
|
$this->deleteServerConfig('core', 'shareapi_allow_share_dialog_user_enumeration');
|
||||||
$this->deleteServerConfig('core', 'shareapi_restrict_user_enumeration_to_group');
|
$this->deleteServerConfig('core', 'shareapi_restrict_user_enumeration_to_group');
|
||||||
$this->deleteServerConfig('core', 'shareapi_restrict_user_enumeration_to_phone');
|
$this->deleteServerConfig('core', 'shareapi_restrict_user_enumeration_to_phone');
|
||||||
|
$this->deleteServerConfig('core', 'shareapi_restrict_user_enumeration_full_match');
|
||||||
$this->deleteServerConfig('core', 'shareapi_only_share_with_group_members');
|
$this->deleteServerConfig('core', 'shareapi_only_share_with_group_members');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -49,6 +49,8 @@ class MailPlugin implements ISearchPlugin {
|
||||||
protected $shareeEnumerationInGroupOnly;
|
protected $shareeEnumerationInGroupOnly;
|
||||||
/* @var bool */
|
/* @var bool */
|
||||||
protected $shareeEnumerationPhone;
|
protected $shareeEnumerationPhone;
|
||||||
|
/* @var bool */
|
||||||
|
protected $shareeEnumerationFullMatch;
|
||||||
|
|
||||||
/** @var IManager */
|
/** @var IManager */
|
||||||
private $contactsManager;
|
private $contactsManager;
|
||||||
|
@ -81,6 +83,7 @@ class MailPlugin implements ISearchPlugin {
|
||||||
$this->shareWithGroupOnly = $this->config->getAppValue('core', 'shareapi_only_share_with_group_members', 'no') === 'yes';
|
$this->shareWithGroupOnly = $this->config->getAppValue('core', 'shareapi_only_share_with_group_members', 'no') === 'yes';
|
||||||
$this->shareeEnumerationInGroupOnly = $this->shareeEnumeration && $this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_to_group', 'no') === 'yes';
|
$this->shareeEnumerationInGroupOnly = $this->shareeEnumeration && $this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_to_group', 'no') === 'yes';
|
||||||
$this->shareeEnumerationPhone = $this->shareeEnumeration && $this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_to_phone', 'no') === 'yes';
|
$this->shareeEnumerationPhone = $this->shareeEnumeration && $this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_to_phone', 'no') === 'yes';
|
||||||
|
$this->shareeEnumerationFullMatch = $this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_full_match', 'yes') === 'yes';
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -137,7 +140,7 @@ class MailPlugin implements ISearchPlugin {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ($exactEmailMatch) {
|
if ($exactEmailMatch && $this->shareeEnumerationFullMatch) {
|
||||||
try {
|
try {
|
||||||
$cloud = $this->cloudIdManager->resolveCloudId($contact['CLOUD'][0]);
|
$cloud = $this->cloudIdManager->resolveCloudId($contact['CLOUD'][0]);
|
||||||
} catch (\InvalidArgumentException $e) {
|
} catch (\InvalidArgumentException $e) {
|
||||||
|
|
|
@ -53,6 +53,8 @@ class UserPlugin implements ISearchPlugin {
|
||||||
protected $shareeEnumerationInGroupOnly;
|
protected $shareeEnumerationInGroupOnly;
|
||||||
/* @var bool */
|
/* @var bool */
|
||||||
protected $shareeEnumerationPhone;
|
protected $shareeEnumerationPhone;
|
||||||
|
/* @var bool */
|
||||||
|
protected $shareeEnumerationFullMatch;
|
||||||
|
|
||||||
/** @var IConfig */
|
/** @var IConfig */
|
||||||
private $config;
|
private $config;
|
||||||
|
@ -85,6 +87,7 @@ class UserPlugin implements ISearchPlugin {
|
||||||
$this->shareeEnumeration = $this->config->getAppValue('core', 'shareapi_allow_share_dialog_user_enumeration', 'yes') === 'yes';
|
$this->shareeEnumeration = $this->config->getAppValue('core', 'shareapi_allow_share_dialog_user_enumeration', 'yes') === 'yes';
|
||||||
$this->shareeEnumerationInGroupOnly = $this->shareeEnumeration && $this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_to_group', 'no') === 'yes';
|
$this->shareeEnumerationInGroupOnly = $this->shareeEnumeration && $this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_to_group', 'no') === 'yes';
|
||||||
$this->shareeEnumerationPhone = $this->shareeEnumeration && $this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_to_phone', 'no') === 'yes';
|
$this->shareeEnumerationPhone = $this->shareeEnumeration && $this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_to_phone', 'no') === 'yes';
|
||||||
|
$this->shareeEnumerationFullMatch = $this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_full_match', 'yes') === 'yes';
|
||||||
}
|
}
|
||||||
|
|
||||||
public function search($search, $limit, $offset, ISearchResult $searchResult) {
|
public function search($search, $limit, $offset, ISearchResult $searchResult) {
|
||||||
|
@ -150,6 +153,7 @@ class UserPlugin implements ISearchPlugin {
|
||||||
|
|
||||||
|
|
||||||
if (
|
if (
|
||||||
|
$this->shareeEnumerationFullMatch &&
|
||||||
$lowerSearch !== '' && (strtolower($uid) === $lowerSearch ||
|
$lowerSearch !== '' && (strtolower($uid) === $lowerSearch ||
|
||||||
strtolower($userDisplayName) === $lowerSearch ||
|
strtolower($userDisplayName) === $lowerSearch ||
|
||||||
strtolower($userEmail) === $lowerSearch)
|
strtolower($userEmail) === $lowerSearch)
|
||||||
|
@ -202,7 +206,7 @@ class UserPlugin implements ISearchPlugin {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($offset === 0 && !$foundUserById) {
|
if ($this->shareeEnumerationFullMatch && $offset === 0 && !$foundUserById) {
|
||||||
// On page one we try if the search result has a direct hit on the
|
// On page one we try if the search result has a direct hit on the
|
||||||
// user id and if so, we add that to the exact match list
|
// user id and if so, we add that to the exact match list
|
||||||
$user = $this->userManager->get($search);
|
$user = $this->userManager->get($search);
|
||||||
|
|
|
@ -124,6 +124,7 @@ class ContactsStore implements IContactsStore {
|
||||||
$disallowEnumeration = $this->config->getAppValue('core', 'shareapi_allow_share_dialog_user_enumeration', 'yes') !== 'yes';
|
$disallowEnumeration = $this->config->getAppValue('core', 'shareapi_allow_share_dialog_user_enumeration', 'yes') !== 'yes';
|
||||||
$restrictEnumerationGroup = $this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_to_group', 'no') === 'yes';
|
$restrictEnumerationGroup = $this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_to_group', 'no') === 'yes';
|
||||||
$restrictEnumerationPhone = $this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_to_phone', 'no') === 'yes';
|
$restrictEnumerationPhone = $this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_to_phone', 'no') === 'yes';
|
||||||
|
$allowEnumerationFullMatch = $this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_full_match', 'yes') === 'yes';
|
||||||
$excludedGroups = $this->config->getAppValue('core', 'shareapi_exclude_groups', 'no') === 'yes';
|
$excludedGroups = $this->config->getAppValue('core', 'shareapi_exclude_groups', 'no') === 'yes';
|
||||||
|
|
||||||
// whether to filter out local users
|
// whether to filter out local users
|
||||||
|
@ -146,7 +147,7 @@ class ContactsStore implements IContactsStore {
|
||||||
|
|
||||||
$selfUID = $self->getUID();
|
$selfUID = $self->getUID();
|
||||||
|
|
||||||
return array_values(array_filter($entries, function (IEntry $entry) use ($skipLocal, $ownGroupsOnly, $selfGroups, $selfUID, $disallowEnumeration, $restrictEnumerationGroup, $restrictEnumerationPhone, $filter) {
|
return array_values(array_filter($entries, function (IEntry $entry) use ($skipLocal, $ownGroupsOnly, $selfGroups, $selfUID, $disallowEnumeration, $restrictEnumerationGroup, $restrictEnumerationPhone, $allowEnumerationFullMatch, $filter) {
|
||||||
if ($entry->getProperty('UID') === $selfUID) {
|
if ($entry->getProperty('UID') === $selfUID) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -160,6 +161,10 @@ class ContactsStore implements IContactsStore {
|
||||||
|
|
||||||
// Prevent enumerating local users
|
// Prevent enumerating local users
|
||||||
if ($disallowEnumeration) {
|
if ($disallowEnumeration) {
|
||||||
|
if (!$allowEnumerationFullMatch) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
$filterUser = true;
|
$filterUser = true;
|
||||||
|
|
||||||
$mailAddresses = $entry->getEMailAddresses();
|
$mailAddresses = $entry->getEMailAddresses();
|
||||||
|
|
|
@ -1827,6 +1827,10 @@ class Manager implements IManager {
|
||||||
$this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_to_phone', 'no') === 'yes';
|
$this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_to_phone', 'no') === 'yes';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function allowEnumerationFullMatch(): bool {
|
||||||
|
return $this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_full_match', 'yes') === 'yes';
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Copied from \OC_Util::isSharingDisabledForUser
|
* Copied from \OC_Util::isSharingDisabledForUser
|
||||||
*
|
*
|
||||||
|
|
|
@ -392,6 +392,14 @@ interface IManager {
|
||||||
*/
|
*/
|
||||||
public function limitEnumerationToPhone(): bool;
|
public function limitEnumerationToPhone(): bool;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if user enumeration is allowed to return on full match
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
* @since 21.0.1
|
||||||
|
*/
|
||||||
|
public function allowEnumerationFullMatch(): bool;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if sharing is disabled for the given user
|
* Check if sharing is disabled for the given user
|
||||||
*
|
*
|
||||||
|
|
|
@ -683,9 +683,12 @@ class ContactsStoreTest extends TestCase {
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testGetContactsWithFilter() {
|
public function testGetContactsWithFilter() {
|
||||||
$this->config->expects($this->at(0))->method('getAppValue')
|
$this->config
|
||||||
->with($this->equalTo('core'), $this->equalTo('shareapi_allow_share_dialog_user_enumeration'), $this->equalTo('yes'))
|
->method('getAppValue')
|
||||||
->willReturn('no');
|
->willReturnMap([
|
||||||
|
['core', 'shareapi_allow_share_dialog_user_enumeration', 'yes', 'no'],
|
||||||
|
['core', 'shareapi_restrict_user_enumeration_full_match', 'yes', 'yes'],
|
||||||
|
]);
|
||||||
|
|
||||||
/** @var IUser|\PHPUnit\Framework\MockObject\MockObject $user */
|
/** @var IUser|\PHPUnit\Framework\MockObject\MockObject $user */
|
||||||
$user = $this->createMock(IUser::class);
|
$user = $this->createMock(IUser::class);
|
||||||
|
@ -766,6 +769,90 @@ class ContactsStoreTest extends TestCase {
|
||||||
], $entry[0]->getEMailAddresses());
|
], $entry[0]->getEMailAddresses());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testGetContactsWithFilterWithoutFullMatch() {
|
||||||
|
$this->config
|
||||||
|
->method('getAppValue')
|
||||||
|
->willReturnMap([
|
||||||
|
['core', 'shareapi_allow_share_dialog_user_enumeration', 'yes', 'no'],
|
||||||
|
['core', 'shareapi_restrict_user_enumeration_full_match', 'yes', 'no'],
|
||||||
|
]);
|
||||||
|
|
||||||
|
/** @var IUser|\PHPUnit\Framework\MockObject\MockObject $user */
|
||||||
|
$user = $this->createMock(IUser::class);
|
||||||
|
$this->contactsManager->expects($this->any())
|
||||||
|
->method('search')
|
||||||
|
->willReturn([
|
||||||
|
[
|
||||||
|
'UID' => 'a567',
|
||||||
|
'FN' => 'Darren Roner',
|
||||||
|
'EMAIL' => [
|
||||||
|
'darren@roner.au',
|
||||||
|
],
|
||||||
|
'isLocalSystemBook' => true,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'UID' => 'john',
|
||||||
|
'FN' => 'John Doe',
|
||||||
|
'EMAIL' => [
|
||||||
|
'john@example.com',
|
||||||
|
],
|
||||||
|
'isLocalSystemBook' => true,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'FN' => 'Anne D',
|
||||||
|
'EMAIL' => [
|
||||||
|
'anne@example.com',
|
||||||
|
],
|
||||||
|
'isLocalSystemBook' => false,
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
$user->expects($this->any())
|
||||||
|
->method('getUID')
|
||||||
|
->willReturn('user123');
|
||||||
|
|
||||||
|
// Complete match on UID should not match
|
||||||
|
$entry = $this->contactsStore->getContacts($user, 'a567');
|
||||||
|
$this->assertSame(1, count($entry));
|
||||||
|
$this->assertEquals([
|
||||||
|
'anne@example.com'
|
||||||
|
], $entry[0]->getEMailAddresses());
|
||||||
|
|
||||||
|
// Partial match on UID should not match
|
||||||
|
$entry = $this->contactsStore->getContacts($user, 'a56');
|
||||||
|
$this->assertSame(1, count($entry));
|
||||||
|
$this->assertEquals([
|
||||||
|
'anne@example.com'
|
||||||
|
], $entry[0]->getEMailAddresses());
|
||||||
|
|
||||||
|
// Complete match on email should not match
|
||||||
|
$entry = $this->contactsStore->getContacts($user, 'john@example.com');
|
||||||
|
$this->assertSame(1, count($entry));
|
||||||
|
$this->assertEquals([
|
||||||
|
'anne@example.com'
|
||||||
|
], $entry[0]->getEMailAddresses());
|
||||||
|
|
||||||
|
// Partial match on email should not match
|
||||||
|
$entry = $this->contactsStore->getContacts($user, 'john@example.co');
|
||||||
|
$this->assertSame(1, count($entry));
|
||||||
|
$this->assertEquals([
|
||||||
|
'anne@example.com'
|
||||||
|
], $entry[0]->getEMailAddresses());
|
||||||
|
|
||||||
|
// Match on FN should not match
|
||||||
|
$entry = $this->contactsStore->getContacts($user, 'Darren Roner');
|
||||||
|
$this->assertSame(1, count($entry));
|
||||||
|
$this->assertEquals([
|
||||||
|
'anne@example.com'
|
||||||
|
], $entry[0]->getEMailAddresses());
|
||||||
|
|
||||||
|
// Don't filter users in local addressbook
|
||||||
|
$entry = $this->contactsStore->getContacts($user, 'Anne D');
|
||||||
|
$this->assertSame(1, count($entry));
|
||||||
|
$this->assertEquals([
|
||||||
|
'anne@example.com'
|
||||||
|
], $entry[0]->getEMailAddresses());
|
||||||
|
}
|
||||||
|
|
||||||
public function testFindOneUser() {
|
public function testFindOneUser() {
|
||||||
$this->config->expects($this->at(0))->method('getAppValue')
|
$this->config->expects($this->at(0))->method('getAppValue')
|
||||||
->with($this->equalTo('core'), $this->equalTo('shareapi_allow_share_dialog_user_enumeration'), $this->equalTo('yes'))
|
->with($this->equalTo('core'), $this->equalTo('shareapi_allow_share_dialog_user_enumeration'), $this->equalTo('yes'))
|
||||||
|
|
Loading…
Reference in New Issue