%PDF- %PDF-
Direktori : /www/varak.net/nextcloud.varak.net/apps_old/apps/circles/lib/Service/ |
Current File : //www/varak.net/nextcloud.varak.net/apps_old/apps/circles/lib/Service/FederatedUserService.php |
<?php declare(strict_types=1); /** * SPDX-FileCopyrightText: 2021 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later */ namespace OCA\Circles\Service; use Exception; use OC; use OCA\Circles\AppInfo\Application; use OCA\Circles\Db\AccountsRequest; use OCA\Circles\Db\CircleRequest; use OCA\Circles\Db\MemberRequest; use OCA\Circles\Exceptions\CircleNotFoundException; use OCA\Circles\Exceptions\ContactAddressBookNotFoundException; use OCA\Circles\Exceptions\ContactFormatException; use OCA\Circles\Exceptions\ContactNotFoundException; use OCA\Circles\Exceptions\FederatedEventException; use OCA\Circles\Exceptions\FederatedItemException; use OCA\Circles\Exceptions\FederatedUserException; use OCA\Circles\Exceptions\FederatedUserNotFoundException; use OCA\Circles\Exceptions\GroupNotFoundException; use OCA\Circles\Exceptions\InitiatorNotConfirmedException; use OCA\Circles\Exceptions\InitiatorNotFoundException; use OCA\Circles\Exceptions\InvalidIdException; use OCA\Circles\Exceptions\MemberNotFoundException; use OCA\Circles\Exceptions\OwnerNotFoundException; use OCA\Circles\Exceptions\RemoteCircleException; use OCA\Circles\Exceptions\RemoteInstanceException; use OCA\Circles\Exceptions\RemoteNotFoundException; use OCA\Circles\Exceptions\RemoteResourceNotFoundException; use OCA\Circles\Exceptions\RequestBuilderException; use OCA\Circles\Exceptions\SingleCircleNotFoundException; use OCA\Circles\Exceptions\SuperSessionException; use OCA\Circles\Exceptions\UnknownRemoteException; use OCA\Circles\Exceptions\UserTypeNotFoundException; use OCA\Circles\FederatedItems\CircleCreate; use OCA\Circles\IFederatedUser; use OCA\Circles\Model\Circle; use OCA\Circles\Model\Federated\FederatedEvent; use OCA\Circles\Model\Federated\RemoteInstance; use OCA\Circles\Model\FederatedUser; use OCA\Circles\Model\ManagedModel; use OCA\Circles\Model\Member; use OCA\Circles\Model\Probes\CircleProbe; use OCA\Circles\Tools\Exceptions\InvalidItemException; use OCA\Circles\Tools\Traits\TArrayTools; use OCA\Circles\Tools\Traits\TDeserialize; use OCA\Circles\Tools\Traits\TNCLogger; use OCA\Circles\Tools\Traits\TStringTools; use OCP\ICache; use OCP\ICacheFactory; use OCP\IGroupManager; use OCP\IUser; use OCP\IUserManager; use OCP\IUserSession; /** * Class FederatedUserService * * @package OCA\Circles\Service */ class FederatedUserService { use TArrayTools; use TStringTools; use TNCLogger; use TDeserialize; public const CACHE_SINGLE_CIRCLE = 'circles/singleCircle'; public const CACHE_SINGLE_CIRCLE_TTL = 604800; // one week public const CONFLICT_001 = 1; public const CONFLICT_002 = 2; public const CONFLICT_003 = 3; public const CONFLICT_004 = 4; public const CONFLICT_005 = 5; /** @var IUserSession */ private $userSession; /** @var IUserManager */ private $userManager; /** @var AccountsRequest */ private $accountRequest; /** @var IGroupManager */ private $groupManager; /** @var FederatedEventService */ private $federatedEventService; /** @var MembershipService */ private $membershipService; /** @var CircleRequest */ private $circleRequest; /** @var MemberRequest */ private $memberRequest; /** @var RemoteService */ private $remoteService; /** @var RemoteStreamService */ private $remoteStreamService; /** @var ContactService */ private $contactService; /** @var InterfaceService */ private $interfaceService; /** @var ConfigService */ private $configService; /** @var ICache */ private $cache; /** @var FederatedUser */ private $currentUser = null; /** @var FederatedUser */ private $currentApp = null; /** @var RemoteInstance */ private $remoteInstance = null; /** @var bool */ private $bypass = false; /** @var bool */ private $initiatedByOcc = false; /** @var FederatedUser */ private $initiatedByAdmin = null; /** * FederatedUserService constructor. * * @param IUserSession $userSession * @param IUserManager $userManager * @param IGroupManager $groupManager * @param ICacheFactory $cacheFactory * @param FederatedEventService $federatedEventService * @param MembershipService $membershipService * @param CircleRequest $circleRequest * @param MemberRequest $memberRequest * @param RemoteService $remoteService * @param RemoteStreamService $remoteStreamService * @param ContactService $contactService * @param InterfaceService $interfaceService * @param ConfigService $configService */ public function __construct( IUserSession $userSession, IUserManager $userManager, IGroupManager $groupManager, ICacheFactory $cacheFactory, FederatedEventService $federatedEventService, MembershipService $membershipService, AccountsRequest $accountRequest, CircleRequest $circleRequest, MemberRequest $memberRequest, RemoteService $remoteService, RemoteStreamService $remoteStreamService, ContactService $contactService, InterfaceService $interfaceService, ConfigService $configService ) { $this->userSession = $userSession; $this->userManager = $userManager; $this->groupManager = $groupManager; $this->federatedEventService = $federatedEventService; $this->membershipService = $membershipService; $this->accountRequest = $accountRequest; $this->circleRequest = $circleRequest; $this->memberRequest = $memberRequest; $this->remoteService = $remoteService; $this->remoteStreamService = $remoteStreamService; $this->contactService = $contactService; $this->interfaceService = $interfaceService; $this->configService = $configService; $this->cache = $cacheFactory->createDistributed(self::CACHE_SINGLE_CIRCLE); if (OC::$CLI) { $this->setInitiatedByOcc(true); } } /** * specify $defaultUser in case a session is not opened but user needs to be emulated (ie. cron) * * @throws FederatedUserException * @throws FederatedUserNotFoundException * @throws InvalidIdException * @throws SingleCircleNotFoundException * @throws RequestBuilderException */ public function initCurrentUser(string $defaultUser = ''): void { $user = $this->userSession->getUser(); if ($user === null) { if ($defaultUser !== '') { $this->setLocalCurrentUserId($defaultUser); } return; } $this->setLocalCurrentUser($user); } /** * @param IUser|null $user * * @throws FederatedUserException * @throws FederatedUserNotFoundException * @throws InvalidIdException * @throws SingleCircleNotFoundException * @throws RequestBuilderException */ public function setLocalCurrentUser(?IUser $user): void { if ($user === null) { return; } $this->setLocalCurrentUserId($user->getUID()); } /** * @param string $userId * * @throws FederatedUserException * @throws FederatedUserNotFoundException * @throws InvalidIdException * @throws RequestBuilderException * @throws SingleCircleNotFoundException */ public function setLocalCurrentUserId(string $userId): void { $this->currentUser = $this->getLocalFederatedUser($userId); } /** * @param string $appId * @param int $appNumber * * @throws ContactAddressBookNotFoundException * @throws ContactFormatException * @throws ContactNotFoundException * @throws FederatedUserException * @throws InvalidIdException * @throws RequestBuilderException * @throws SingleCircleNotFoundException */ public function setLocalCurrentApp(string $appId, int $appNumber): void { $this->currentApp = $this->getAppInitiator($appId, $appNumber); } /** * set a CurrentUser, based on a IFederatedUser. * CurrentUser is mainly used to manage rights when requesting the database. * * @param IFederatedUser $federatedUser * * @throws FederatedUserException */ public function setCurrentUser(IFederatedUser $federatedUser): void { if (!($federatedUser instanceof FederatedUser)) { $tmp = new FederatedUser(); $tmp->importFromIFederatedUser($federatedUser); $federatedUser = $tmp; } $this->confirmFederatedUser($federatedUser); $this->currentUser = $federatedUser; } /** * */ public function unsetCurrentUser(): void { $this->currentUser = null; } /** * @return FederatedUser|null */ public function getCurrentUser(): ?FederatedUser { return $this->currentUser; } /** * @return bool */ public function hasCurrentUser(): bool { return !is_null($this->currentUser); } /** * @throws InitiatorNotFoundException */ public function mustHaveCurrentUser(): void { if ($this->bypass) { return; } if (!$this->hasCurrentEntity() && !$this->hasRemoteInstance()) { throw new InitiatorNotFoundException('Invalid initiator'); } } /** * @param bool $bypass */ public function bypassCurrentUserCondition(bool $bypass): void { $this->bypass = $bypass; } /** * @return bool */ public function canBypassCurrentUserCondition(): bool { return $this->bypass; } /** * @throws SuperSessionException */ public function confirmSuperSession(): void { if ($this->canBypassCurrentUserCondition()) { return; } throw new SuperSessionException('Must initialise Super Session'); } /** * @param bool $initiatedByOcc */ public function setInitiatedByOcc(bool $initiatedByOcc): void { $this->initiatedByOcc = $initiatedByOcc; } /** * @return bool */ public function isInitiatedByOcc(): bool { return $this->initiatedByOcc; } /** * @return bool */ public function isInitiatedByAdmin(): bool { return !is_null($this->initiatedByAdmin); } /** * @param FederatedUser $patron */ public function setInitiatedByAdmin(FederatedUser $patron): void { $this->initiatedByAdmin = $patron; } /** * @return FederatedUser */ public function getInitiatedByAdmin(): FederatedUser { return $this->initiatedByAdmin; } /** * @param IUser $user * * @throws ContactAddressBookNotFoundException * @throws ContactFormatException * @throws ContactNotFoundException * @throws FederatedUserException * @throws FederatedUserNotFoundException * @throws InvalidIdException * @throws RequestBuilderException * @throws SingleCircleNotFoundException */ public function setCurrentPatron(string $userId): void { $patron = $this->getLocalFederatedUser($userId, false); $this->setInitiatedByAdmin($patron); } /** * @param Member $member * * @throws ContactAddressBookNotFoundException * @throws ContactFormatException * @throws ContactNotFoundException * @throws FederatedUserException * @throws InvalidIdException * @throws RequestBuilderException * @throws SingleCircleNotFoundException */ public function setMemberPatron(Member $member): void { if ($this->hasCurrentApp()) { $member->setInvitedBy($this->getCurrentApp()); } elseif ($this->isInitiatedByOcc()) { $member->setInvitedBy($this->getAppInitiator('occ', Member::APP_OCC)); } elseif ($this->isInitiatedByAdmin()) { $member->setInvitedBy($this->getInitiatedByAdmin()); } else { $member->setInvitedBy($this->getCurrentUser()); } } /** * @return FederatedUser|null */ public function getCurrentApp(): ?FederatedUser { return $this->currentApp; } /** * @return bool */ public function hasCurrentApp(): bool { return !is_null($this->currentApp); } /** * @return FederatedUser|null */ public function getCurrentEntity(): ?FederatedUser { if ($this->hasCurrentUser()) { return $this->getCurrentUser(); } return $this->getCurrentApp(); } /** * @return bool */ public function hasCurrentEntity(): bool { return !is_null($this->currentApp) || !is_null($this->currentUser); } /** * set a RemoteInstance, mostly from a remote request (RemoteController) * Used to limit rights in some request in the local database. * * @param RemoteInstance $remoteInstance */ public function setRemoteInstance(RemoteInstance $remoteInstance): void { $this->remoteInstance = $remoteInstance; } /** * @return RemoteInstance|null */ public function getRemoteInstance(): ?RemoteInstance { return $this->remoteInstance; } /** * @return bool */ public function hasRemoteInstance(): bool { return !is_null($this->remoteInstance); } /** * Get the full FederatedUser for a local user. * Will generate the SingleId if none exist * * @param string $userId * @param bool $check * * @return FederatedUser * @throws ContactAddressBookNotFoundException * @throws ContactFormatException * @throws ContactNotFoundException * @throws FederatedUserException * @throws FederatedUserNotFoundException * @throws InvalidIdException * @throws RequestBuilderException * @throws SingleCircleNotFoundException */ public function getLocalFederatedUser(string $userId, bool $check = true, bool $generate = false): FederatedUser { $displayName = $userId; if ($check) { $user = $this->userManager->get($userId); if ($user === null) { throw new FederatedUserNotFoundException('user ' . $userId . ' not found'); } $displayName = $this->userManager->getDisplayName($userId); } else { $accountData = $this->accountRequest->getAccountData($userId); if (array_key_exists('displayName', $accountData)) { $displayName = $accountData['displayName']; } } $federatedUser = new FederatedUser(); $federatedUser->set($userId, '', Member::TYPE_USER, $displayName); $this->fillSingleCircleId($federatedUser, ($check || $generate)); return $federatedUser; } /** * Get the full FederatedUser for a local user. * Will generate the SingleId if none exist * * @param string $appId * @param int $appNumber * * @return FederatedUser * @throws ContactAddressBookNotFoundException * @throws ContactFormatException * @throws ContactNotFoundException * @throws FederatedUserException * @throws InvalidIdException * @throws RequestBuilderException * @throws SingleCircleNotFoundException */ public function getAppInitiator( string $appId, int $appNumber, string $appDisplayName = '' ): FederatedUser { if ($appDisplayName === '') { $appDisplayName = $this->get( (string)$appNumber, Circle::$DEF_SOURCE, Circle::$DEF_SOURCE[Member::APP_DEFAULT] ); } $circle = new Circle(); $circle->setSource($appNumber); $federatedUser = new FederatedUser(); $federatedUser->set($appId, '', Member::TYPE_APP, $appDisplayName, $circle); $this->fillSingleCircleId($federatedUser); return $federatedUser; } /** * some ./occ commands allows to add an Initiator, or force the PoV from the local circles' owner * * TODO: manage non-user type ? * * @param string $userId * @param int $userType * @param string $circleId * @param bool $bypass * * @throws CircleNotFoundException * @throws FederatedItemException * @throws FederatedUserException * @throws FederatedUserNotFoundException * @throws InvalidIdException * @throws MemberNotFoundException * @throws OwnerNotFoundException * @throws RemoteInstanceException * @throws RemoteNotFoundException * @throws RemoteResourceNotFoundException * @throws RequestBuilderException * @throws SingleCircleNotFoundException * @throws UnknownRemoteException * @throws UserTypeNotFoundException */ public function commandLineInitiator( string $userId, int $userType = Member::TYPE_SINGLE, string $circleId = '', bool $bypass = false ): void { if ($userId !== '') { $this->setCurrentUser($this->getFederatedUser($userId, $userType)); return; } if ($circleId !== '') { try { $this->setOwnerAsCurrentUser($circleId); return; } catch (RemoteCircleException $e) { } } if (!$bypass) { throw new CircleNotFoundException( 'This Circle is not managed from this instance, please use --initiator' ); } $this->bypassCurrentUserCondition($bypass); } /** * @param string $circleId * * @throws CircleNotFoundException * @throws FederatedUserException * @throws OwnerNotFoundException * @throws RemoteCircleException * @throws RequestBuilderException */ public function setOwnerAsCurrentUser(string $circleId): void { $probe = new CircleProbe(); $probe->includeSystemCircles() ->includePersonalCircles(); $localCircle = $this->circleRequest->getCircle($circleId, null, $probe); if ($this->configService->isLocalInstance($localCircle->getInstance())) { $this->setCurrentUser($localCircle->getOwner()); } else { throw new RemoteCircleException(); } } /** * Works like getFederatedUser, but returns a member. * Allow to specify a level: handle@instance,level * * Used for filters when searching for Circles * TODO: Used outside of ./occ circles:manage:list ? * * @param string $userId * @param int $level * * @return Member * @throws CircleNotFoundException * @throws FederatedItemException * @throws FederatedUserException * @throws FederatedUserNotFoundException * @throws InvalidIdException * @throws MemberNotFoundException * @throws OwnerNotFoundException * @throws RemoteInstanceException * @throws RemoteNotFoundException * @throws RemoteResourceNotFoundException * @throws SingleCircleNotFoundException * @throws UnknownRemoteException * @throws UserTypeNotFoundException */ public function getFederatedMember(string $userId, int $level = Member::LEVEL_MEMBER): Member { $userId = trim($userId, ','); if (strpos($userId, ',') !== false) { [$userId, $level] = explode(',', $userId); } $federatedUser = $this->getFederatedUser($userId, Member::TYPE_USER); $member = new Member(); $member->importFromIFederatedUser($federatedUser); $member->setLevel((int)$level); return $member; } /** * get a valid FederatedUser, based on the federatedId (userId@instance) and its type. * If instance is local, get the local valid FederatedUser * If instance is not local, get the remote valid FederatedUser * * @param string $federatedId * @param int $type * * @return FederatedUser * @throws CircleNotFoundException * @throws FederatedItemException * @throws FederatedUserException * @throws FederatedUserNotFoundException * @throws InvalidIdException * @throws MemberNotFoundException * @throws OwnerNotFoundException * @throws RemoteInstanceException * @throws RemoteNotFoundException * @throws RemoteResourceNotFoundException * @throws SingleCircleNotFoundException * @throws UnknownRemoteException * @throws UserTypeNotFoundException * @throws RequestBuilderException */ public function getFederatedUser(string $federatedId, int $type = Member::TYPE_SINGLE): FederatedUser { // first testing federatedId string as a whole; try { switch ($type) { case Member::TYPE_USER: return $this->getLocalFederatedUser($federatedId); case Member::TYPE_GROUP: return $this->getFederatedUser_Group($federatedId); case Member::TYPE_MAIL: return $this->getFederatedUser_Mail($federatedId); case Member::TYPE_CONTACT: return $this->getFederatedUser_Contact($federatedId); } } catch (Exception $e) { } // then if nothing found, extract remote instance from string [$singleId, $instance] = $this->extractIdAndInstance($federatedId); switch ($type) { case Member::TYPE_SINGLE: case Member::TYPE_CIRCLE: return $this->getFederatedUser_SingleId($singleId, $instance); case Member::TYPE_USER: return $this->getFederatedUser_User($singleId, $instance); case Member::TYPE_GROUP: return $this->getFederatedUser_Group($singleId, $instance); } throw new UserTypeNotFoundException(); } /** * Generate a FederatedUser based on local data. * WARNING: There is no confirmation that the returned FederatedUser exists or is valid at this point. * Use getFederatedUser() instead if a valid and confirmed FederatedUser is needed. * * if $federatedId is a known SingleId, will returns data from the local database. * if $federatedId is a local username, will returns data from the local database. * Otherwise, the FederatedUser will not contains a SingleId. * * @param string $federatedId * @param int $type * * @return FederatedUser */ public function generateFederatedUser(string $federatedId, int $type = 0): FederatedUser { if ($type === Member::TYPE_MAIL) { $federatedId = strtolower($federatedId); } try { return $this->getFederatedUser($federatedId, $type); } catch (Exception $e) { } [$userId, $instance] = $this->extractIdAndInstance($federatedId); $federatedUser = new FederatedUser(); $federatedUser->set($userId, $instance, $type); return $federatedUser; } /** * @param FederatedUser $federatedUser */ public function deleteFederatedUser(FederatedUser $federatedUser): void { $this->circleRequest->deleteFederatedUser($federatedUser); $this->memberRequest->deleteFederatedUser($federatedUser); $this->membershipService->deleteFederatedUser($federatedUser); $this->cache->remove($this->generateCacheKey($federatedUser)); } /** * @param string $singleId * @param string $instance * * @return FederatedUser * @throws CircleNotFoundException * @throws FederatedItemException * @throws FederatedUserException * @throws FederatedUserNotFoundException * @throws MemberNotFoundException * @throws OwnerNotFoundException * @throws RemoteInstanceException * @throws RemoteNotFoundException * @throws RemoteResourceNotFoundException * @throws UnknownRemoteException * @throws RequestBuilderException */ public function getFederatedUser_SingleId(string $singleId, string $instance): FederatedUser { if ($this->configService->isLocalInstance($instance)) { return $this->circleRequest->getFederatedUserBySingleId($singleId); } else { $federatedUser = $this->remoteService->getFederatedUserFromInstance( $singleId, $instance, Member::TYPE_SINGLE ); $this->confirmSingleIdUniqueness($federatedUser); return $federatedUser; } } /** * @param string $userId * @param string $instance * * @return FederatedUser * @throws FederatedUserException * @throws FederatedUserNotFoundException * @throws InvalidIdException * @throws RemoteInstanceException * @throws RemoteNotFoundException * @throws RemoteResourceNotFoundException * @throws SingleCircleNotFoundException * @throws UnknownRemoteException * @throws FederatedItemException * @throws RequestBuilderException */ private function getFederatedUser_User(string $userId, string $instance): FederatedUser { if ($this->configService->isLocalInstance($instance)) { return $this->getLocalFederatedUser($userId); } else { $federatedUser = $this->remoteService->getFederatedUserFromInstance( $userId, $instance, Member::TYPE_USER ); $this->confirmSingleIdUniqueness($federatedUser); return $federatedUser; } } /** * @param string $groupName * @param string $instance * * @return FederatedUser * @throws FederatedEventException * @throws FederatedItemException * @throws FederatedUserException * @throws GroupNotFoundException * @throws InitiatorNotConfirmedException * @throws InvalidIdException * @throws OwnerNotFoundException * @throws RemoteInstanceException * @throws RemoteNotFoundException * @throws RemoteResourceNotFoundException * @throws SingleCircleNotFoundException * @throws UnknownRemoteException * @throws RequestBuilderException */ public function getFederatedUser_Group(string $groupName, string $instance = ''): FederatedUser { if ($this->configService->isLocalInstance($instance)) { $circle = $this->getGroupCircle($groupName); $federatedGroup = new FederatedUser(); return $federatedGroup->importFromCircle($circle); } else { throw new FederatedUserNotFoundException('remote group not supported yet. Use singleId'); } } /** * @param string $mailAddress * * @return FederatedUser * @throws ContactAddressBookNotFoundException * @throws ContactFormatException * @throws ContactNotFoundException * @throws FederatedUserException * @throws InvalidIdException * @throws RequestBuilderException * @throws SingleCircleNotFoundException */ public function getFederatedUser_Mail(string $mailAddress): FederatedUser { $federatedUser = new FederatedUser(); $federatedUser->set($mailAddress, '', Member::TYPE_MAIL); $this->fillSingleCircleId($federatedUser); return $federatedUser; } /** * @param string $contactPath * * @return FederatedUser * @throws ContactAddressBookNotFoundException * @throws ContactFormatException * @throws ContactNotFoundException * @throws FederatedUserException * @throws InvalidIdException * @throws RequestBuilderException * @throws SingleCircleNotFoundException */ public function getFederatedUser_Contact(string $contactPath): FederatedUser { $federatedUser = new FederatedUser(); $federatedUser->set( $contactPath, '', Member::TYPE_CONTACT, $this->contactService->getDisplayName($contactPath) ); $this->fillSingleCircleId($federatedUser); return $federatedUser; } /** * extract userID and instance from a federatedId * * @param string $federatedId * * @return array */ public function extractIdAndInstance(string $federatedId): array { $federatedId = trim($federatedId, '@'); $pos = strrpos($federatedId, '@'); if ($pos === false) { $userId = $federatedId; $instance = $this->interfaceService->getLocalInstance(); } else { $userId = substr($federatedId, 0, $pos); $instance = substr($federatedId, $pos + 1); } return [$userId, $instance]; } /** * @param FederatedUser $federatedUser * @param bool $generate * * @throws ContactAddressBookNotFoundException * @throws ContactFormatException * @throws ContactNotFoundException * @throws FederatedUserException * @throws InvalidIdException * @throws RequestBuilderException * @throws SingleCircleNotFoundException */ private function fillSingleCircleId(FederatedUser $federatedUser, bool $generate = true): void { if ($federatedUser->getSingleId() !== '') { return; } $circle = $this->getSingleCircle($federatedUser, $generate); $federatedUser->setSingleId($circle->getSingleId()); $federatedUser->setDisplayName($circle->getDisplayName()); $federatedUser->setBasedOn($circle); } /** * get the Single Circle from a local user * * @param FederatedUser $federatedUser * @param bool $generate * * @return Circle * @throws ContactAddressBookNotFoundException * @throws ContactFormatException * @throws ContactNotFoundException * @throws FederatedUserException * @throws InvalidIdException * @throws RequestBuilderException * @throws SingleCircleNotFoundException */ private function getSingleCircle(FederatedUser $federatedUser, bool $generate = true): Circle { if (!$this->configService->isLocalInstance($federatedUser->getInstance())) { throw new FederatedUserException('FederatedUser must be local'); } try { return $this->getCachedSingleCircle($federatedUser); } catch (SingleCircleNotFoundException $e) { } try { $singleCircle = $this->circleRequest->getSingleCircle($federatedUser); } catch (SingleCircleNotFoundException $e) { if (!$generate) { throw new SingleCircleNotFoundException(); } $circle = new Circle(); $id = $this->token(ManagedModel::ID_LENGTH); if ($federatedUser->hasBasedOn()) { $source = $federatedUser->getBasedOn()->getSource(); } else { $source = $federatedUser->getUserType(); } $prefix = ($federatedUser->getUserType() === Member::TYPE_APP) ? 'app' : Member::$TYPE[$federatedUser->getUserType()]; $circle->setName($prefix . ':' . $federatedUser->getUserId() . ':' . $id) ->setDisplayName($federatedUser->getDisplayName()) ->setSingleId($id) ->setSource($source); if ($federatedUser->getUserType() === Member::TYPE_APP) { $circle->setConfig(Circle::CFG_SINGLE | Circle::CFG_ROOT); } else { $circle->setConfig(Circle::CFG_SINGLE); } $this->circleRequest->save($circle); $owner = new Member(); $owner->importFromIFederatedUser($federatedUser); $owner->setLevel(Member::LEVEL_OWNER) ->setCircleId($id) ->setSingleId($id) ->setId($id) ->setDisplayName($owner->getDisplayName()) ->setStatus('Member'); if ($federatedUser->getUserType() !== Member::TYPE_APP) { $owner->setInvitedBy( $this->getAppInitiator( Application::APP_ID, Member::APP_CIRCLES, Application::APP_NAME ) ); } $this->memberRequest->save($owner); $this->membershipService->onUpdate($id); $singleCircle = $this->circleRequest->getSingleCircle($federatedUser); } $this->cacheSingleCircle($federatedUser, $singleCircle); return $singleCircle; } /** * Confirm that all field of a FederatedUser are filled. * * @param FederatedUser $federatedUser * * @throws FederatedUserException */ private function confirmFederatedUser(FederatedUser $federatedUser): void { if ($federatedUser->getUserId() === '' || $federatedUser->getSingleId() === '' || $federatedUser->getUserType() === 0 || $federatedUser->getInstance() === '') { $this->debug('FederatedUser is not empty', ['federatedUser' => $federatedUser]); throw new FederatedUserException('FederatedUser is not complete'); } } /** * Confirm that the singleId of a FederatedUser is unique and not used to any other member of the * database. * * @param FederatedUser $federatedUser * * @throws FederatedUserException * @throws RequestBuilderException * @deprecated: use confirmSingleIdUniqueness() */ public function confirmLocalSingleId(IFederatedUser $federatedUser): void { $members = $this->memberRequest->getMembersBySingleId($federatedUser->getSingleId()); foreach ($members as $member) { if (!$federatedUser->compareWith($member)) { $this->debug( 'uniqueness of SingleId could not be confirmed', ['federatedUser' => $federatedUser, 'localMember' => $member] ); throw new FederatedUserException('uniqueness of SingleId could not be confirmed'); } } } /** * // TODO: implement this check in a maintenance background job * * @param IFederatedUser $federatedUser * * @throws FederatedUserException * @throws RemoteNotFoundException * @throws RequestBuilderException * @throws UnknownRemoteException */ public function confirmSingleIdUniqueness(IFederatedUser $federatedUser): void { // TODO: check also with Circles singleId $remote = null; if (!$this->configService->isLocalInstance($federatedUser->getInstance())) { $remote = $this->remoteStreamService->getCachedRemoteInstance($federatedUser->getInstance()); } $knownMembers = $this->memberRequest->getAlternateSingleId($federatedUser); foreach ($knownMembers as $knownMember) { if ($this->configService->isLocalInstance($federatedUser->getInstance())) { if ($this->configService->isLocalInstance($knownMember->getInstance())) { return; } else { $this->markConflict($federatedUser, $knownMember, self::CONFLICT_001); } } if (!$knownMember->hasRemoteInstance()) { $this->markConflict($federatedUser, $knownMember, self::CONFLICT_002); } $knownRemote = $knownMember->getRemoteInstance(); if ($this->interfaceService->isInterfaceInternal($knownRemote->getInterface()) && !in_array($federatedUser->getInstance(), $knownRemote->getAliases())) { $this->markConflict($federatedUser, $knownMember, self::CONFLICT_003); } if (is_null($remote)) { $this->markConflict($federatedUser, $knownMember, self::CONFLICT_004); } if ($this->interfaceService->isInterfaceInternal($remote->getInterface()) && !in_array($knownMember->getInstance(), $remote->getAliases())) { $this->markConflict($federatedUser, $knownMember, self::CONFLICT_005); } } } /** * @param IFederatedUser $federatedUser * @param Member $knownMember * @param int $conflict * * @throws FederatedUserException */ private function markConflict(IFederatedUser $federatedUser, Member $knownMember, int $conflict): void { switch ($conflict) { case self::CONFLICT_001: $message = 'duplicate singleId from another instance'; break; case self::CONFLICT_002: $message = 'duplicate singleId has no known source'; break; case self::CONFLICT_003: $message = 'federatedUser is not an alias from duplicate singleId'; break; case self::CONFLICT_004: $message = 'federatedUser has no known source'; break; case self::CONFLICT_005: $message = 'duplicate singleId is not an alias of federatedUser'; break; default: $message = 'uniqueness of SingleId could not be confirmed'; } // TODO: log conflict into database $this->log( 3, $message, false, [ 'federatedUser' => $federatedUser, 'knownMember' => $knownMember ] ); throw new FederatedUserException($message); } /** * @param string $groupId * * @return Circle * @throws GroupNotFoundException * @throws FederatedEventException * @throws FederatedItemException * @throws FederatedUserException * @throws InitiatorNotConfirmedException * @throws InvalidIdException * @throws OwnerNotFoundException * @throws RemoteInstanceException * @throws RemoteNotFoundException * @throws RemoteResourceNotFoundException * @throws SingleCircleNotFoundException * @throws UnknownRemoteException * @throws RequestBuilderException */ public function getGroupCircle(string $groupId): Circle { $group = $this->groupManager->get($groupId); if ($group === null) { throw new GroupNotFoundException('group not found'); } $this->setLocalCurrentApp(Application::APP_ID, Member::APP_CIRCLES); $owner = $this->getCurrentApp(); $circle = new Circle(); $circle->setName('group:' . $groupId) ->setConfig(Circle::CFG_SYSTEM | Circle::CFG_NO_OWNER | Circle::CFG_HIDDEN) ->setSingleId($this->token(ManagedModel::ID_LENGTH)) ->setSource(Member::TYPE_GROUP); $member = new Member(); $member->importFromIFederatedUser($owner); $member->setId($this->token(ManagedModel::ID_LENGTH)) ->setCircleId($circle->getSingleId()) ->setLevel(Member::LEVEL_OWNER) ->setStatus(Member::STATUS_MEMBER); $circle->setOwner($member) ->setInitiator($member); try { return $this->circleRequest->searchCircle($circle, $owner); } catch (CircleNotFoundException $e) { } $circle->setDisplayName($groupId); $event = new FederatedEvent(CircleCreate::class); $event->setCircle($circle); $this->federatedEventService->newEvent($event); return $circle; } /** * @param FederatedUser $federatedUser * * @return Circle * @throws SingleCircleNotFoundException */ private function getCachedSingleCircle(FederatedUser $federatedUser): Circle { $key = $this->generateCacheKey($federatedUser); $cachedData = $this->cache->get($key); if (!is_string($cachedData)) { throw new SingleCircleNotFoundException(); } try { /** @var Circle $singleCircle */ $singleCircle = $this->deserializeJson($cachedData, Circle::class); } catch (InvalidItemException $e) { throw new SingleCircleNotFoundException(); } return $singleCircle; } /** * @param FederatedUser $federatedUser * @param Circle $singleCircle */ private function cacheSingleCircle(FederatedUser $federatedUser, Circle $singleCircle): void { $key = $this->generateCacheKey($federatedUser); $this->cache->set($key, json_encode($singleCircle), self::CACHE_SINGLE_CIRCLE_TTL); } /** * @param FederatedUser $federatedUser * * @return string */ private function generateCacheKey(FederatedUser $federatedUser): string { return $federatedUser->getInstance() . '#' . $federatedUser->getUserType() . '#' . $federatedUser->getUserId(); } }