Signed-off-by: Joas Schilling <coding@schilljs.com>tags/v22.0.0beta1
$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 | ||||
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) { | |||||
return $user->getEMailAddress() === $value; | |||||
}); | |||||
if ($allowEnumerationFullMatch) { | |||||
$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) { | |||||
if ($user->getEMailAddress() === $value) { | |||||
$users = $this->userManager->getByEmail($value); | |||||
$users = \array_filter($users, function (IUser $user) use ($currentUser, $value, $limitEnumerationPhone, $limitEnumerationGroup, $allowEnumerationFullMatch, $currentUserGroups) { | |||||
if ($allowEnumerationFullMatch && $user->getEMailAddress() === $value) { | |||||
return true; | return true; | ||||
} | } | ||||
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) { | |||||
return $user->getDisplayName() === $value; | |||||
}); | |||||
if ($allowEnumerationFullMatch) { | |||||
$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) { | |||||
if ($user->getDisplayName() === $value) { | |||||
$users = $this->userManager->searchDisplayName($value, $searchLimit); | |||||
$users = \array_filter($users, function (IUser $user) use ($currentUser, $value, $limitEnumerationPhone, $limitEnumerationGroup, $allowEnumerationFullMatch, $currentUserGroups) { | |||||
if ($allowEnumerationFullMatch && $user->getDisplayName() === $value) { | |||||
return true; | return true; | ||||
} | } | ||||
->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'); | ||||
['{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') | ||||
->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'); | ||||
['{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') |
'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'), |
<?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') { | ||||
}?>"> | }?>"> | ||||
<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" |
['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'], | ||||
'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', | ||||
['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'], | ||||
'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', |
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" | ||||
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 | ||||
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 | | ||||
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 | ||||
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" | ||||
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 | | ||||
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 | | ||||
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" | ||||
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" | ||||
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 | ||||
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 | | ||||
$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'); | ||||
} | } | ||||
} | } |
protected $shareeEnumerationInGroupOnly; | protected $shareeEnumerationInGroupOnly; | ||||
/* @var bool */ | /* @var bool */ | ||||
protected $shareeEnumerationPhone; | protected $shareeEnumerationPhone; | ||||
/* @var bool */ | |||||
protected $shareeEnumerationFullMatch; | |||||
/** @var IManager */ | /** @var IManager */ | ||||
private $contactsManager; | private $contactsManager; | ||||
$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'; | |||||
} | } | ||||
/** | /** | ||||
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) { |
protected $shareeEnumerationInGroupOnly; | protected $shareeEnumerationInGroupOnly; | ||||
/* @var bool */ | /* @var bool */ | ||||
protected $shareeEnumerationPhone; | protected $shareeEnumerationPhone; | ||||
/* @var bool */ | |||||
protected $shareeEnumerationFullMatch; | |||||
/** @var IConfig */ | /** @var IConfig */ | ||||
private $config; | private $config; | ||||
$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) { | ||||
if ( | if ( | ||||
$this->shareeEnumerationFullMatch && | |||||
$lowerSearch !== '' && (strtolower($uid) === $lowerSearch || | $lowerSearch !== '' && (strtolower($uid) === $lowerSearch || | ||||
strtolower($userDisplayName) === $lowerSearch || | strtolower($userDisplayName) === $lowerSearch || | ||||
strtolower($userEmail) === $lowerSearch) | strtolower($userEmail) === $lowerSearch) | ||||
} | } | ||||
} | } | ||||
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); |
$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 | ||||
$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; | ||||
} | } | ||||
// 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(); |
$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 | ||||
* | * |
*/ | */ | ||||
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 | ||||
* | * |
} | } | ||||
public function testGetContactsWithFilter() { | public function testGetContactsWithFilter() { | ||||
$this->config->expects($this->at(0))->method('getAppValue') | |||||
->with($this->equalTo('core'), $this->equalTo('shareapi_allow_share_dialog_user_enumeration'), $this->equalTo('yes')) | |||||
->willReturn('no'); | |||||
$this->config | |||||
->method('getAppValue') | |||||
->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); | ||||
], $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')) |