2011-08-06 05:36:56 -04:00
|
|
|
<?php
|
2025-06-30 09:04:05 -04:00
|
|
|
|
2012-02-11 15:24:22 -05:00
|
|
|
/**
|
2024-05-27 11:39:07 -04:00
|
|
|
* SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors
|
|
|
|
|
* SPDX-FileCopyrightText: 2016 ownCloud, Inc.
|
|
|
|
|
* SPDX-License-Identifier: AGPL-3.0-only
|
2012-02-11 15:24:22 -05:00
|
|
|
*/
|
2015-08-30 13:13:01 -04:00
|
|
|
namespace OCA\DAV\Connector\Sabre;
|
2014-12-19 10:50:32 -05:00
|
|
|
|
2021-03-09 15:48:48 -05:00
|
|
|
use OC\KnownUser\KnownUserService;
|
2024-10-10 06:40:31 -04:00
|
|
|
use OCA\Circles\Api\v1\Circles;
|
2021-05-29 07:04:26 -04:00
|
|
|
use OCA\Circles\Exceptions\CircleNotFoundException;
|
2024-10-18 06:04:22 -04:00
|
|
|
use OCA\Circles\Model\Circle;
|
2019-08-06 07:40:52 -04:00
|
|
|
use OCA\DAV\CalDAV\Proxy\ProxyMapper;
|
2019-08-14 07:38:11 -04:00
|
|
|
use OCA\DAV\Traits\PrincipalProxyTrait;
|
2022-02-05 13:55:23 -05:00
|
|
|
use OCP\Accounts\IAccountManager;
|
|
|
|
|
use OCP\Accounts\IAccountProperty;
|
|
|
|
|
use OCP\Accounts\PropertyDoesNotExistException;
|
2019-03-05 10:00:47 -05:00
|
|
|
use OCP\App\IAppManager;
|
2019-03-03 17:07:07 -05:00
|
|
|
use OCP\AppFramework\QueryException;
|
2020-12-11 18:25:10 -05:00
|
|
|
use OCP\Constants;
|
2019-11-26 10:37:57 -05:00
|
|
|
use OCP\IConfig;
|
2021-01-19 15:22:26 -05:00
|
|
|
use OCP\IGroup;
|
2016-01-11 11:29:01 -05:00
|
|
|
use OCP\IGroupManager;
|
2015-11-24 05:15:31 -05:00
|
|
|
use OCP\IUser;
|
2014-12-19 07:28:11 -05:00
|
|
|
use OCP\IUserManager;
|
2017-12-04 09:02:55 -05:00
|
|
|
use OCP\IUserSession;
|
2021-08-16 11:55:27 -04:00
|
|
|
use OCP\L10N\IFactory;
|
2017-12-04 09:02:55 -05:00
|
|
|
use OCP\Share\IManager as IShareManager;
|
2016-01-08 06:11:02 -05:00
|
|
|
use Sabre\DAV\Exception;
|
2017-09-29 17:43:04 -04:00
|
|
|
use Sabre\DAV\PropPatch;
|
2016-01-08 06:11:02 -05:00
|
|
|
use Sabre\DAVACL\PrincipalBackend\BackendInterface;
|
2014-12-19 07:28:11 -05:00
|
|
|
|
2016-01-08 06:11:02 -05:00
|
|
|
class Principal implements BackendInterface {
|
|
|
|
|
|
2016-01-20 15:08:23 -05:00
|
|
|
/** @var string */
|
|
|
|
|
private $principalPrefix;
|
|
|
|
|
|
2016-02-10 04:43:32 -05:00
|
|
|
/** @var bool */
|
|
|
|
|
private $hasGroups;
|
|
|
|
|
|
2019-02-07 18:30:00 -05:00
|
|
|
/** @var bool */
|
|
|
|
|
private $hasCircles;
|
2019-08-14 07:38:11 -04:00
|
|
|
|
2021-03-09 15:48:48 -05:00
|
|
|
/** @var KnownUserService */
|
|
|
|
|
private $knownUserService;
|
|
|
|
|
|
2024-10-18 06:04:22 -04:00
|
|
|
public function __construct(
|
|
|
|
|
private IUserManager $userManager,
|
|
|
|
|
private IGroupManager $groupManager,
|
|
|
|
|
private IAccountManager $accountManager,
|
|
|
|
|
private IShareManager $shareManager,
|
|
|
|
|
private IUserSession $userSession,
|
|
|
|
|
private IAppManager $appManager,
|
2025-05-14 09:29:02 -04:00
|
|
|
private ProxyMapper $proxyMapper,
|
2021-03-09 15:48:48 -05:00
|
|
|
KnownUserService $knownUserService,
|
2024-10-18 06:04:22 -04:00
|
|
|
private IConfig $config,
|
|
|
|
|
private IFactory $languageFactory,
|
|
|
|
|
string $principalPrefix = 'principals/users/',
|
|
|
|
|
) {
|
2016-01-20 15:08:23 -05:00
|
|
|
$this->principalPrefix = trim($principalPrefix, '/');
|
2019-02-07 18:30:00 -05:00
|
|
|
$this->hasGroups = $this->hasCircles = ($principalPrefix === 'principals/users/');
|
2021-03-09 15:48:48 -05:00
|
|
|
$this->knownUserService = $knownUserService;
|
2014-12-19 07:28:11 -05:00
|
|
|
}
|
|
|
|
|
|
2019-08-14 07:38:11 -04:00
|
|
|
use PrincipalProxyTrait {
|
|
|
|
|
getGroupMembership as protected traitGetGroupMembership;
|
|
|
|
|
}
|
|
|
|
|
|
2011-08-06 05:36:56 -04:00
|
|
|
/**
|
|
|
|
|
* Returns a list of principals based on a prefix.
|
|
|
|
|
*
|
|
|
|
|
* This prefix will often contain something like 'principals'. You are only
|
|
|
|
|
* expected to return principals that are in this base path.
|
|
|
|
|
*
|
|
|
|
|
* You are expected to return at least a 'uri' for every user, you can
|
|
|
|
|
* return any additional properties if you wish so. Common properties are:
|
|
|
|
|
* {DAV:}displayname
|
|
|
|
|
*
|
|
|
|
|
* @param string $prefixPath
|
2014-12-19 07:28:11 -05:00
|
|
|
* @return string[]
|
2011-08-06 05:36:56 -04:00
|
|
|
*/
|
2014-12-19 07:28:11 -05:00
|
|
|
public function getPrincipalsByPrefix($prefixPath) {
|
|
|
|
|
$principals = [];
|
2011-08-06 05:36:56 -04:00
|
|
|
|
2016-01-20 15:08:23 -05:00
|
|
|
if ($prefixPath === $this->principalPrefix) {
|
2014-12-19 07:28:11 -05:00
|
|
|
foreach ($this->userManager->search('') as $user) {
|
2015-11-24 05:15:31 -05:00
|
|
|
$principals[] = $this->userToPrincipal($user);
|
2012-02-11 15:09:51 -05:00
|
|
|
}
|
2011-08-06 05:36:56 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return $principals;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Returns a specific principal, specified by it's path.
|
|
|
|
|
* The returned structure should be the exact same as from
|
|
|
|
|
* getPrincipalsByPrefix.
|
|
|
|
|
*
|
|
|
|
|
* @param string $path
|
|
|
|
|
* @return array
|
|
|
|
|
*/
|
|
|
|
|
public function getPrincipalByPath($path) {
|
2025-08-14 11:20:33 -04:00
|
|
|
return $this->getPrincipalPropertiesByPath($path);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Returns a specific principal, specified by its path.
|
|
|
|
|
* The returned structure should be the exact same as from
|
|
|
|
|
* getPrincipalsByPrefix.
|
|
|
|
|
*
|
|
|
|
|
* It is possible to optionally filter retrieved properties in case only a limited set is
|
|
|
|
|
* required. Note that the implementation might return more properties than requested.
|
|
|
|
|
*
|
|
|
|
|
* @param string $path The path of the principal
|
|
|
|
|
* @param string[]|null $propertyFilter A list of properties to be retrieved or all if null. An empty array will cause a very shallow principal to be retrieved.
|
|
|
|
|
*/
|
|
|
|
|
public function getPrincipalPropertiesByPath($path, ?array $propertyFilter = null): ?array {
|
2021-01-12 04:15:48 -05:00
|
|
|
[$prefix, $name] = \Sabre\Uri\split($path);
|
2021-01-19 15:22:26 -05:00
|
|
|
$decodedName = urldecode($name);
|
2011-08-06 05:36:56 -04:00
|
|
|
|
2019-08-06 07:40:52 -04:00
|
|
|
if ($name === 'calendar-proxy-write' || $name === 'calendar-proxy-read') {
|
2021-01-12 04:15:48 -05:00
|
|
|
[$prefix2, $name2] = \Sabre\Uri\split($prefix);
|
2019-08-06 07:40:52 -04:00
|
|
|
|
|
|
|
|
if ($prefix2 === $this->principalPrefix) {
|
|
|
|
|
$user = $this->userManager->get($name2);
|
|
|
|
|
|
|
|
|
|
if ($user !== null) {
|
|
|
|
|
return [
|
|
|
|
|
'uri' => 'principals/users/' . $user->getUID() . '/' . $name,
|
|
|
|
|
];
|
|
|
|
|
}
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2016-01-20 15:08:23 -05:00
|
|
|
if ($prefix === $this->principalPrefix) {
|
2020-09-10 06:55:41 -04:00
|
|
|
// Depending on where it is called, it may happen that this function
|
|
|
|
|
// is called either with a urlencoded version of the name or with a non-urlencoded one.
|
|
|
|
|
// The urldecode function replaces %## and +, both of which are forbidden in usernames.
|
|
|
|
|
// Hence there can be no ambiguity here and it is safe to call urldecode on all usernames
|
2021-01-19 15:22:26 -05:00
|
|
|
$user = $this->userManager->get($decodedName);
|
2011-08-06 05:36:56 -04:00
|
|
|
|
2017-12-04 09:02:55 -05:00
|
|
|
if ($user !== null) {
|
2025-08-14 11:20:33 -04:00
|
|
|
return $this->userToPrincipal($user, $propertyFilter);
|
2016-01-20 15:08:23 -05:00
|
|
|
}
|
2020-04-10 04:35:09 -04:00
|
|
|
} elseif ($prefix === 'principals/circles') {
|
2020-10-23 15:18:02 -04:00
|
|
|
if ($this->userSession->getUser() !== null) {
|
2021-01-19 15:22:26 -05:00
|
|
|
// At the time of writing - 2021-01-19 — a mixed state is possible.
|
|
|
|
|
// The second condition can be removed when this is fixed.
|
|
|
|
|
return $this->circleToPrincipal($decodedName)
|
|
|
|
|
?: $this->circleToPrincipal($name);
|
|
|
|
|
}
|
|
|
|
|
} elseif ($prefix === 'principals/groups') {
|
|
|
|
|
// At the time of writing - 2021-01-19 — a mixed state is possible.
|
|
|
|
|
// The second condition can be removed when this is fixed.
|
|
|
|
|
$group = $this->groupManager->get($decodedName)
|
|
|
|
|
?: $this->groupManager->get($name);
|
|
|
|
|
if ($group instanceof IGroup) {
|
|
|
|
|
return [
|
|
|
|
|
'uri' => 'principals/groups/' . $name,
|
|
|
|
|
'{DAV:}displayname' => $group->getDisplayName(),
|
|
|
|
|
];
|
2020-10-23 15:18:02 -04:00
|
|
|
}
|
2023-07-21 06:33:00 -04:00
|
|
|
} elseif ($prefix === 'principals/system') {
|
|
|
|
|
return [
|
|
|
|
|
'uri' => 'principals/system/' . $name,
|
|
|
|
|
'{DAV:}displayname' => $this->languageFactory->get('dav')->t('Accounts'),
|
|
|
|
|
];
|
2025-04-14 09:13:30 -04:00
|
|
|
} elseif ($prefix === 'principals/shares') {
|
|
|
|
|
return [
|
|
|
|
|
'uri' => 'principals/shares/' . $name,
|
|
|
|
|
'{DAV:}displayname' => $name,
|
|
|
|
|
];
|
2016-01-20 15:08:23 -05:00
|
|
|
}
|
2012-02-11 15:09:51 -05:00
|
|
|
return null;
|
2011-08-06 05:36:56 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Returns the list of groups a principal is a member of
|
|
|
|
|
*
|
|
|
|
|
* @param string $principal
|
2016-03-17 10:39:08 -04:00
|
|
|
* @param bool $needGroups
|
2011-08-06 05:36:56 -04:00
|
|
|
* @return array
|
2016-01-08 06:11:02 -05:00
|
|
|
* @throws Exception
|
2011-08-06 05:36:56 -04:00
|
|
|
*/
|
2016-03-17 05:31:33 -04:00
|
|
|
public function getGroupMembership($principal, $needGroups = false) {
|
2021-01-12 04:15:48 -05:00
|
|
|
[$prefix, $name] = \Sabre\Uri\split($principal);
|
2012-02-11 15:09:51 -05:00
|
|
|
|
2019-08-14 07:38:11 -04:00
|
|
|
if ($prefix !== $this->principalPrefix) {
|
|
|
|
|
return [];
|
2019-08-06 08:41:33 -04:00
|
|
|
}
|
|
|
|
|
|
2019-08-14 07:38:11 -04:00
|
|
|
$user = $this->userManager->get($name);
|
|
|
|
|
if (!$user) {
|
|
|
|
|
throw new Exception('Principal not found');
|
2019-08-06 08:41:33 -04:00
|
|
|
}
|
|
|
|
|
|
2019-08-14 07:38:11 -04:00
|
|
|
$groups = [];
|
2019-08-06 08:41:33 -04:00
|
|
|
|
2019-08-14 07:38:11 -04:00
|
|
|
if ($this->hasGroups || $needGroups) {
|
|
|
|
|
$userGroups = $this->groupManager->getUserGroups($user);
|
|
|
|
|
foreach ($userGroups as $userGroup) {
|
2025-07-30 13:57:16 -04:00
|
|
|
if ($userGroup->hideFromCollaboration()) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
2019-08-14 07:38:11 -04:00
|
|
|
$groups[] = 'principals/groups/' . urlencode($userGroup->getGID());
|
2019-08-06 08:41:33 -04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-08-14 07:38:11 -04:00
|
|
|
$groups = array_unique(array_merge(
|
|
|
|
|
$groups,
|
|
|
|
|
$this->traitGetGroupMembership($principal, $needGroups)
|
|
|
|
|
));
|
|
|
|
|
|
|
|
|
|
return $groups;
|
2011-08-06 05:36:56 -04:00
|
|
|
}
|
2012-10-14 15:04:08 -04:00
|
|
|
|
2014-12-19 10:50:32 -05:00
|
|
|
/**
|
|
|
|
|
* @param string $path
|
2015-02-12 06:29:01 -05:00
|
|
|
* @param PropPatch $propPatch
|
2014-12-19 10:50:32 -05:00
|
|
|
* @return int
|
|
|
|
|
*/
|
2020-04-10 10:51:06 -04:00
|
|
|
public function updatePrincipal($path, PropPatch $propPatch) {
|
2024-02-21 16:29:09 -05:00
|
|
|
// Updating schedule-default-calendar-URL is handled in CustomPropertiesBackend
|
2012-09-10 05:28:09 -04:00
|
|
|
return 0;
|
|
|
|
|
}
|
2012-10-14 15:04:08 -04:00
|
|
|
|
2017-10-01 10:03:30 -04:00
|
|
|
/**
|
|
|
|
|
* Search user principals
|
|
|
|
|
*
|
|
|
|
|
* @param array $searchProperties
|
|
|
|
|
* @param string $test
|
|
|
|
|
* @return array
|
|
|
|
|
*/
|
2017-12-04 09:02:55 -05:00
|
|
|
protected function searchUserPrincipals(array $searchProperties, $test = 'allof') {
|
2017-10-01 10:03:30 -04:00
|
|
|
$results = [];
|
|
|
|
|
|
2018-10-14 06:48:42 -04:00
|
|
|
// If sharing is disabled, return the empty array
|
2018-05-22 09:09:21 -04:00
|
|
|
$shareAPIEnabled = $this->shareManager->shareApiEnabled();
|
2018-10-14 06:48:42 -04:00
|
|
|
if (!$shareAPIEnabled) {
|
2017-12-04 09:02:55 -05:00
|
|
|
return [];
|
|
|
|
|
}
|
|
|
|
|
|
2020-02-20 12:52:23 -05:00
|
|
|
$allowEnumeration = $this->shareManager->allowEnumeration();
|
2021-03-09 15:48:48 -05:00
|
|
|
$limitEnumerationGroup = $this->shareManager->limitEnumerationToGroups();
|
|
|
|
|
$limitEnumerationPhone = $this->shareManager->limitEnumerationToPhone();
|
2021-03-10 11:18:44 -05:00
|
|
|
$allowEnumerationFullMatch = $this->shareManager->allowEnumerationFullMatch();
|
2022-04-13 09:07:27 -04:00
|
|
|
$ignoreSecondDisplayName = $this->shareManager->ignoreSecondDisplayName();
|
2022-04-19 06:54:28 -04:00
|
|
|
$matchEmail = $this->shareManager->matchEmail();
|
2019-11-26 10:37:57 -05:00
|
|
|
|
2017-12-04 09:02:55 -05:00
|
|
|
// If sharing is restricted to group members only,
|
|
|
|
|
// return only members that have groups in common
|
|
|
|
|
$restrictGroups = false;
|
2021-03-09 15:48:48 -05:00
|
|
|
$currentUser = $this->userSession->getUser();
|
2017-12-04 09:02:55 -05:00
|
|
|
if ($this->shareManager->shareWithGroupMembersOnly()) {
|
2021-03-09 15:48:48 -05:00
|
|
|
if (!$currentUser instanceof IUser) {
|
2017-12-04 09:02:55 -05:00
|
|
|
return [];
|
|
|
|
|
}
|
|
|
|
|
|
2021-03-09 15:48:48 -05:00
|
|
|
$restrictGroups = $this->groupManager->getUserGroupIds($currentUser);
|
2017-12-04 09:02:55 -05:00
|
|
|
}
|
|
|
|
|
|
2020-02-20 12:52:23 -05:00
|
|
|
$currentUserGroups = [];
|
2021-03-09 15:48:48 -05:00
|
|
|
if ($limitEnumerationGroup) {
|
|
|
|
|
if ($currentUser instanceof IUser) {
|
2020-02-20 12:52:23 -05:00
|
|
|
$currentUserGroups = $this->groupManager->getUserGroupIds($currentUser);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-12-11 18:25:10 -05:00
|
|
|
$searchLimit = $this->config->getSystemValueInt('sharing.maxAutocompleteResults', Constants::SHARING_MAX_AUTOCOMPLETE_RESULTS_DEFAULT);
|
|
|
|
|
if ($searchLimit <= 0) {
|
|
|
|
|
$searchLimit = null;
|
|
|
|
|
}
|
2017-10-01 10:03:30 -04:00
|
|
|
foreach ($searchProperties as $prop => $value) {
|
|
|
|
|
switch ($prop) {
|
|
|
|
|
case '{http://sabredav.org/ns}email-address':
|
2019-11-26 10:37:57 -05:00
|
|
|
if (!$allowEnumeration) {
|
2022-04-19 06:54:28 -04:00
|
|
|
if ($allowEnumerationFullMatch && $matchEmail) {
|
2021-03-10 11:18:44 -05:00
|
|
|
$users = $this->userManager->getByEmail($value);
|
|
|
|
|
} else {
|
|
|
|
|
$users = [];
|
|
|
|
|
}
|
2021-03-09 15:48:48 -05:00
|
|
|
} else {
|
2021-03-10 11:18:44 -05:00
|
|
|
$users = $this->userManager->getByEmail($value);
|
|
|
|
|
$users = \array_filter($users, function (IUser $user) use ($currentUser, $value, $limitEnumerationPhone, $limitEnumerationGroup, $allowEnumerationFullMatch, $currentUserGroups) {
|
2021-09-01 09:41:02 -04:00
|
|
|
if ($allowEnumerationFullMatch && $user->getSystemEMailAddress() === $value) {
|
2021-03-09 15:48:48 -05:00
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ($limitEnumerationPhone
|
|
|
|
|
&& $currentUser instanceof IUser
|
|
|
|
|
&& $this->knownUserService->isKnownToUser($currentUser->getUID(), $user->getUID())) {
|
|
|
|
|
// Synced phonebook match
|
|
|
|
|
return true;
|
|
|
|
|
}
|
2019-11-26 10:37:57 -05:00
|
|
|
|
2021-03-09 15:48:48 -05:00
|
|
|
if (!$limitEnumerationGroup) {
|
|
|
|
|
// No limitation on enumeration, all allowed
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return !empty($currentUserGroups) && !empty(array_intersect(
|
|
|
|
|
$this->groupManager->getUserGroupIds($user),
|
|
|
|
|
$currentUserGroups
|
|
|
|
|
));
|
2020-02-20 12:52:23 -05:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2020-04-09 07:53:40 -04:00
|
|
|
$results[] = array_reduce($users, function (array $carry, IUser $user) use ($restrictGroups) {
|
2017-12-04 09:02:55 -05:00
|
|
|
// is sharing restricted to groups only?
|
|
|
|
|
if ($restrictGroups !== false) {
|
|
|
|
|
$userGroups = $this->groupManager->getUserGroupIds($user);
|
|
|
|
|
if (count(array_intersect($userGroups, $restrictGroups)) === 0) {
|
|
|
|
|
return $carry;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$carry[] = $this->principalPrefix . '/' . $user->getUID();
|
|
|
|
|
return $carry;
|
|
|
|
|
}, []);
|
2017-10-01 10:03:30 -04:00
|
|
|
break;
|
2017-12-04 09:02:55 -05:00
|
|
|
|
2018-10-17 10:06:31 -04:00
|
|
|
case '{DAV:}displayname':
|
|
|
|
|
|
2019-11-26 10:37:57 -05:00
|
|
|
if (!$allowEnumeration) {
|
2021-03-10 11:18:44 -05:00
|
|
|
if ($allowEnumerationFullMatch) {
|
2022-04-08 04:20:24 -04:00
|
|
|
$lowerSearch = strtolower($value);
|
2021-03-10 11:18:44 -05:00
|
|
|
$users = $this->userManager->searchDisplayName($value, $searchLimit);
|
2022-04-13 09:07:27 -04:00
|
|
|
$users = \array_filter($users, static function (IUser $user) use ($lowerSearch, $ignoreSecondDisplayName) {
|
|
|
|
|
$lowerDisplayName = strtolower($user->getDisplayName());
|
|
|
|
|
return $lowerDisplayName === $lowerSearch || ($ignoreSecondDisplayName && trim(preg_replace('/ \(.*\)$/', '', $lowerDisplayName)) === $lowerSearch);
|
2021-03-10 11:18:44 -05:00
|
|
|
});
|
|
|
|
|
} else {
|
|
|
|
|
$users = [];
|
|
|
|
|
}
|
2021-03-09 15:48:48 -05:00
|
|
|
} else {
|
2021-03-10 11:18:44 -05:00
|
|
|
$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) {
|
2021-03-09 15:48:48 -05:00
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ($limitEnumerationPhone
|
|
|
|
|
&& $currentUser instanceof IUser
|
|
|
|
|
&& $this->knownUserService->isKnownToUser($currentUser->getUID(), $user->getUID())) {
|
|
|
|
|
// Synced phonebook match
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!$limitEnumerationGroup) {
|
|
|
|
|
// No limitation on enumeration, all allowed
|
|
|
|
|
return true;
|
|
|
|
|
}
|
2019-11-26 10:37:57 -05:00
|
|
|
|
2021-03-09 15:48:48 -05:00
|
|
|
return !empty($currentUserGroups) && !empty(array_intersect(
|
|
|
|
|
$this->groupManager->getUserGroupIds($user),
|
|
|
|
|
$currentUserGroups
|
|
|
|
|
));
|
2020-02-20 12:52:23 -05:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2020-04-09 07:53:40 -04:00
|
|
|
$results[] = array_reduce($users, function (array $carry, IUser $user) use ($restrictGroups) {
|
2018-10-17 10:06:31 -04:00
|
|
|
// is sharing restricted to groups only?
|
|
|
|
|
if ($restrictGroups !== false) {
|
|
|
|
|
$userGroups = $this->groupManager->getUserGroupIds($user);
|
|
|
|
|
if (count(array_intersect($userGroups, $restrictGroups)) === 0) {
|
|
|
|
|
return $carry;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$carry[] = $this->principalPrefix . '/' . $user->getUID();
|
|
|
|
|
return $carry;
|
|
|
|
|
}, []);
|
|
|
|
|
break;
|
|
|
|
|
|
2019-02-20 14:22:56 -05:00
|
|
|
case '{urn:ietf:params:xml:ns:caldav}calendar-user-address-set':
|
|
|
|
|
// If you add support for more search properties that qualify as a user-address,
|
|
|
|
|
// please also add them to the array below
|
|
|
|
|
$results[] = $this->searchUserPrincipals([
|
|
|
|
|
// In theory this should also search for principal:principals/users/...
|
|
|
|
|
// but that's used internally only anyway and i don't know of any client querying that
|
|
|
|
|
'{http://sabredav.org/ns}email-address' => $value,
|
|
|
|
|
], 'anyof');
|
|
|
|
|
break;
|
|
|
|
|
|
2017-10-01 10:03:30 -04:00
|
|
|
default:
|
|
|
|
|
$results[] = [];
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-12-04 09:02:55 -05:00
|
|
|
// results is an array of arrays, so this is not the first search result
|
|
|
|
|
// but the results of the first searchProperty
|
|
|
|
|
if (count($results) === 1) {
|
2017-10-01 10:03:30 -04:00
|
|
|
return $results[0];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
switch ($test) {
|
|
|
|
|
case 'anyof':
|
2018-10-17 10:06:31 -04:00
|
|
|
return array_values(array_unique(array_merge(...$results)));
|
2017-10-01 10:03:30 -04:00
|
|
|
|
2017-12-04 09:02:55 -05:00
|
|
|
case 'allof':
|
|
|
|
|
default:
|
2018-10-17 10:06:31 -04:00
|
|
|
return array_values(array_intersect(...$results));
|
2017-12-04 09:02:55 -05:00
|
|
|
}
|
2017-10-01 10:03:30 -04:00
|
|
|
}
|
|
|
|
|
|
2014-12-19 10:50:32 -05:00
|
|
|
/**
|
|
|
|
|
* @param string $prefixPath
|
|
|
|
|
* @param array $searchProperties
|
2015-02-12 06:29:01 -05:00
|
|
|
* @param string $test
|
2014-12-19 10:50:32 -05:00
|
|
|
* @return array
|
|
|
|
|
*/
|
2020-04-10 10:51:06 -04:00
|
|
|
public function searchPrincipals($prefixPath, array $searchProperties, $test = 'allof') {
|
2017-12-04 09:02:55 -05:00
|
|
|
if (count($searchProperties) === 0) {
|
2017-10-01 10:03:30 -04:00
|
|
|
return [];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
switch ($prefixPath) {
|
|
|
|
|
case 'principals/users':
|
|
|
|
|
return $this->searchUserPrincipals($searchProperties, $test);
|
2017-12-04 09:02:55 -05:00
|
|
|
|
2017-10-01 10:03:30 -04:00
|
|
|
default:
|
|
|
|
|
return [];
|
|
|
|
|
}
|
2012-09-10 05:28:09 -04:00
|
|
|
}
|
2015-02-12 06:29:01 -05:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @param string $uri
|
2015-03-17 07:22:29 -04:00
|
|
|
* @param string $principalPrefix
|
2015-02-12 06:29:01 -05:00
|
|
|
* @return string
|
|
|
|
|
*/
|
2020-04-10 10:51:06 -04:00
|
|
|
public function findByUri($uri, $principalPrefix) {
|
2018-10-14 06:48:42 -04:00
|
|
|
// If sharing is disabled, return the empty array
|
2018-05-22 09:09:21 -04:00
|
|
|
$shareAPIEnabled = $this->shareManager->shareApiEnabled();
|
2018-10-14 06:48:42 -04:00
|
|
|
if (!$shareAPIEnabled) {
|
2017-12-04 09:02:55 -05:00
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// If sharing is restricted to group members only,
|
|
|
|
|
// return only members that have groups in common
|
|
|
|
|
$restrictGroups = false;
|
|
|
|
|
if ($this->shareManager->shareWithGroupMembersOnly()) {
|
|
|
|
|
$user = $this->userSession->getUser();
|
|
|
|
|
if (!$user) {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$restrictGroups = $this->groupManager->getUserGroupIds($user);
|
|
|
|
|
}
|
|
|
|
|
|
2023-06-02 08:08:19 -04:00
|
|
|
if (str_starts_with($uri, 'mailto:')) {
|
2017-12-04 09:02:55 -05:00
|
|
|
if ($principalPrefix === 'principals/users') {
|
|
|
|
|
$users = $this->userManager->getByEmail(substr($uri, 7));
|
|
|
|
|
if (count($users) !== 1) {
|
|
|
|
|
return null;
|
2017-10-01 10:03:30 -04:00
|
|
|
}
|
2017-12-04 09:02:55 -05:00
|
|
|
$user = $users[0];
|
|
|
|
|
|
|
|
|
|
if ($restrictGroups !== false) {
|
|
|
|
|
$userGroups = $this->groupManager->getUserGroupIds($user);
|
|
|
|
|
if (count(array_intersect($userGroups, $restrictGroups)) === 0) {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return $this->principalPrefix . '/' . $user->getUID();
|
2016-05-02 08:19:10 -04:00
|
|
|
}
|
|
|
|
|
}
|
2023-07-06 21:07:57 -04:00
|
|
|
if (str_starts_with($uri, 'principal:')) {
|
2018-04-23 07:48:39 -04:00
|
|
|
$principal = substr($uri, 10);
|
|
|
|
|
$principal = $this->getPrincipalByPath($principal);
|
|
|
|
|
if ($principal !== null) {
|
|
|
|
|
return $principal['uri'];
|
|
|
|
|
}
|
|
|
|
|
}
|
2016-05-02 08:19:10 -04:00
|
|
|
|
2017-12-04 09:02:55 -05:00
|
|
|
return null;
|
2015-02-12 06:29:01 -05:00
|
|
|
}
|
2015-11-24 05:15:31 -05:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @param IUser $user
|
2025-08-14 11:20:33 -04:00
|
|
|
* @param string[]|null $propertyFilter
|
2015-11-24 05:15:31 -05:00
|
|
|
* @return array
|
2022-02-05 13:55:23 -05:00
|
|
|
* @throws PropertyDoesNotExistException
|
2015-11-24 05:15:31 -05:00
|
|
|
*/
|
2025-08-14 11:20:33 -04:00
|
|
|
protected function userToPrincipal($user, ?array $propertyFilter = null) {
|
|
|
|
|
$wantsProperty = static function (string $name) use ($propertyFilter) {
|
|
|
|
|
if ($propertyFilter === null) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return in_array($name, $propertyFilter, true);
|
|
|
|
|
};
|
|
|
|
|
|
2015-11-24 05:15:31 -05:00
|
|
|
$userId = $user->getUID();
|
|
|
|
|
$displayName = $user->getDisplayName();
|
|
|
|
|
$principal = [
|
2020-04-09 03:22:29 -04:00
|
|
|
'uri' => $this->principalPrefix . '/' . $userId,
|
|
|
|
|
'{DAV:}displayname' => is_null($displayName) ? $userId : $displayName,
|
|
|
|
|
'{urn:ietf:params:xml:ns:caldav}calendar-user-type' => 'INDIVIDUAL',
|
2015-11-24 05:15:31 -05:00
|
|
|
];
|
|
|
|
|
|
2025-08-14 11:20:33 -04:00
|
|
|
if ($wantsProperty('{http://nextcloud.com/ns}language')) {
|
|
|
|
|
$principal['{http://nextcloud.com/ns}language'] = $this->languageFactory->getUserLanguage($user);
|
|
|
|
|
}
|
2022-02-05 13:55:23 -05:00
|
|
|
|
2025-08-14 11:20:33 -04:00
|
|
|
if ($wantsProperty('{http://sabredav.org/ns}email-address')) {
|
|
|
|
|
$email = $user->getSystemEMailAddress();
|
|
|
|
|
if (!empty($email)) {
|
|
|
|
|
$principal['{http://sabredav.org/ns}email-address'] = $email;
|
|
|
|
|
}
|
2015-11-24 05:15:31 -05:00
|
|
|
}
|
2016-12-23 06:12:38 -05:00
|
|
|
|
2025-08-14 11:20:33 -04:00
|
|
|
if ($wantsProperty('{DAV:}alternate-URI-set')) {
|
|
|
|
|
$account = $this->accountManager->getAccount($user);
|
|
|
|
|
$alternativeEmails = array_map(static fn (IAccountProperty $property) => 'mailto:' . $property->getValue(), $account->getPropertyCollection(IAccountManager::COLLECTION_EMAIL)->getProperties());
|
|
|
|
|
if (!empty($alternativeEmails)) {
|
|
|
|
|
$principal['{DAV:}alternate-URI-set'] = $alternativeEmails;
|
|
|
|
|
}
|
2022-02-05 13:55:23 -05:00
|
|
|
}
|
|
|
|
|
|
2015-11-24 05:15:31 -05:00
|
|
|
return $principal;
|
|
|
|
|
}
|
2016-01-11 14:04:33 -05:00
|
|
|
|
2016-01-20 15:08:23 -05:00
|
|
|
public function getPrincipalPrefix() {
|
|
|
|
|
return $this->principalPrefix;
|
|
|
|
|
}
|
|
|
|
|
|
2019-02-07 18:30:00 -05:00
|
|
|
/**
|
|
|
|
|
* @param string $circleUniqueId
|
|
|
|
|
* @return array|null
|
|
|
|
|
*/
|
|
|
|
|
protected function circleToPrincipal($circleUniqueId) {
|
2019-03-05 10:00:47 -05:00
|
|
|
if (!$this->appManager->isEnabledForUser('circles') || !class_exists('\OCA\Circles\Api\v1\Circles')) {
|
2019-02-07 18:30:00 -05:00
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
2019-03-03 17:07:07 -05:00
|
|
|
try {
|
2024-10-10 06:40:31 -04:00
|
|
|
$circle = Circles::detailsCircle($circleUniqueId, true);
|
2019-03-03 17:07:07 -05:00
|
|
|
} catch (QueryException $ex) {
|
|
|
|
|
return null;
|
2021-05-29 07:04:26 -04:00
|
|
|
} catch (CircleNotFoundException $ex) {
|
2019-03-03 17:07:07 -05:00
|
|
|
return null;
|
|
|
|
|
}
|
2019-02-07 18:30:00 -05:00
|
|
|
|
|
|
|
|
if (!$circle) {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$principal = [
|
|
|
|
|
'uri' => 'principals/circles/' . $circleUniqueId,
|
2021-05-29 07:04:26 -04:00
|
|
|
'{DAV:}displayname' => $circle->getDisplayName(),
|
2019-02-07 18:30:00 -05:00
|
|
|
];
|
|
|
|
|
|
|
|
|
|
return $principal;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Returns the list of circles a principal is a member of
|
|
|
|
|
*
|
|
|
|
|
* @param string $principal
|
|
|
|
|
* @return array
|
|
|
|
|
* @throws Exception
|
2024-10-18 06:04:22 -04:00
|
|
|
* @throws QueryException
|
2019-03-05 09:46:56 -05:00
|
|
|
* @suppress PhanUndeclaredClassMethod
|
2019-02-07 18:30:00 -05:00
|
|
|
*/
|
2019-03-01 07:02:30 -05:00
|
|
|
public function getCircleMembership($principal):array {
|
2019-03-05 10:00:47 -05:00
|
|
|
if (!$this->appManager->isEnabledForUser('circles') || !class_exists('\OCA\Circles\Api\v1\Circles')) {
|
2019-02-07 18:30:00 -05:00
|
|
|
return [];
|
|
|
|
|
}
|
|
|
|
|
|
2021-01-12 04:15:48 -05:00
|
|
|
[$prefix, $name] = \Sabre\Uri\split($principal);
|
2019-02-07 18:30:00 -05:00
|
|
|
if ($this->hasCircles && $prefix === $this->principalPrefix) {
|
|
|
|
|
$user = $this->userManager->get($name);
|
|
|
|
|
if (!$user) {
|
|
|
|
|
throw new Exception('Principal not found');
|
|
|
|
|
}
|
|
|
|
|
|
2024-10-10 06:40:31 -04:00
|
|
|
$circles = Circles::joinedCircles($name, true);
|
2019-02-07 18:30:00 -05:00
|
|
|
|
2020-04-09 07:53:40 -04:00
|
|
|
$circles = array_map(function ($circle) {
|
2024-10-18 06:04:22 -04:00
|
|
|
/** @var Circle $circle */
|
2021-05-29 07:04:26 -04:00
|
|
|
return 'principals/circles/' . urlencode($circle->getSingleId());
|
2019-02-07 18:30:00 -05:00
|
|
|
}, $circles);
|
|
|
|
|
|
|
|
|
|
return $circles;
|
|
|
|
|
}
|
2017-09-29 17:43:04 -04:00
|
|
|
|
2019-02-07 18:30:00 -05:00
|
|
|
return [];
|
|
|
|
|
}
|
2022-11-01 10:49:54 -04:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Get all email addresses associated to a principal.
|
|
|
|
|
*
|
|
|
|
|
* @param array $principal Data from getPrincipal*()
|
|
|
|
|
* @return string[] All email addresses without the mailto: prefix
|
|
|
|
|
*/
|
|
|
|
|
public function getEmailAddressesOfPrincipal(array $principal): array {
|
|
|
|
|
$emailAddresses = [];
|
|
|
|
|
|
2023-01-18 06:10:49 -05:00
|
|
|
if (isset($principal['{http://sabredav.org/ns}email-address'])) {
|
|
|
|
|
$emailAddresses[] = $principal['{http://sabredav.org/ns}email-address'];
|
2022-11-01 10:49:54 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (isset($principal['{DAV:}alternate-URI-set'])) {
|
|
|
|
|
foreach ($principal['{DAV:}alternate-URI-set'] as $address) {
|
|
|
|
|
if (str_starts_with($address, 'mailto:')) {
|
|
|
|
|
$emailAddresses[] = substr($address, 7);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (isset($principal['{urn:ietf:params:xml:ns:caldav}calendar-user-address-set'])) {
|
|
|
|
|
foreach ($principal['{urn:ietf:params:xml:ns:caldav}calendar-user-address-set'] as $address) {
|
|
|
|
|
if (str_starts_with($address, 'mailto:')) {
|
|
|
|
|
$emailAddresses[] = substr($address, 7);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (isset($principal['{http://calendarserver.org/ns/}email-address-set'])) {
|
|
|
|
|
foreach ($principal['{http://calendarserver.org/ns/}email-address-set'] as $address) {
|
|
|
|
|
if (str_starts_with($address, 'mailto:')) {
|
|
|
|
|
$emailAddresses[] = substr($address, 7);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return array_values(array_unique($emailAddresses));
|
|
|
|
|
}
|
2011-08-06 05:36:56 -04:00
|
|
|
}
|