circles/lib/Service/FederatedUserService.php
Maxence Lange 1aa24e3bae using probes
Signed-off-by: Maxence Lange <maxence@artificial-owl.com>
2021-07-17 00:32:23 -01:00

1158 lines
32 KiB
PHP

<?php
declare(strict_types=1);
/**
* Circles - Bring cloud-users closer together.
*
* This file is licensed under the Affero General Public License version 3 or
* later. See the COPYING file.
*
* @author Maxence Lange <maxence@artificial-owl.com>
* @copyright 2021
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Circles\Service;
use ArtificialOwl\MySmallPhpTools\Traits\Nextcloud\nc22\TNC22Logger;
use ArtificialOwl\MySmallPhpTools\Traits\TArrayTools;
use ArtificialOwl\MySmallPhpTools\Traits\TStringTools;
use Exception;
use OC;
use OCA\Circles\AppInfo\Application;
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\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\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 OCP\IGroupManager;
use OCP\IUser;
use OCP\IUserManager;
use OCP\IUserSession;
/**
* Class FederatedUserService
*
* @package OCA\Circles\Service
*/
class FederatedUserService {
use TArrayTools;
use TStringTools;
use TNC22Logger;
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 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 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 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,
FederatedEventService $federatedEventService,
MembershipService $membershipService,
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->circleRequest = $circleRequest;
$this->memberRequest = $memberRequest;
$this->remoteService = $remoteService;
$this->remoteStreamService = $remoteStreamService;
$this->contactService = $contactService;
$this->interfaceService = $interfaceService;
$this->configService = $configService;
if (OC::$CLI) {
$this->setInitiatedByOcc(true);
}
}
/**
* @throws FederatedUserException
* @throws FederatedUserNotFoundException
* @throws InvalidIdException
* @throws SingleCircleNotFoundException
* @throws RequestBuilderException
*/
public function initCurrentUser() {
$user = $this->userSession->getUser();
if ($user === null) {
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 FederatedUserException
* @throws InvalidIdException
* @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->hasCurrentUser() && !$this->hasRemoteInstance()) {
throw new InitiatorNotFoundException('Invalid initiator');
}
}
/**
* @param bool $bypass
*/
public function bypassCurrentUserCondition(bool $bypass): void {
$this->bypass = $bypass;
}
/**
* @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->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);
}
/**
* 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): FederatedUser {
$displayName = $userId;
if ($check) {
$user = $this->userManager->get($userId);
if ($user === null) {
throw new FederatedUserNotFoundException('user ' . $userId . ' not found');
}
$userId = $user->getUID();
$displayName = $user->getDisplayName();
}
$federatedUser = new FederatedUser();
$federatedUser->set($userId, '', Member::TYPE_USER, $displayName);
$this->fillSingleCircleId($federatedUser, $check);
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, $appId);
}
$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 !== '') {
$probe = new CircleProbe();
$probe->includeSystemCircles()
->canBeRequestingMembership();
$localCircle = $this->circleRequest->getCircle($circleId, null, $probe);
if ($this->configService->isLocalInstance($localCircle->getInstance())) {
$this->setCurrentUser($localCircle->getOwner());
return;
}
}
if (!$bypass) {
throw new CircleNotFoundException(
'This Circle is not managed from this instance, please use --initiator'
);
}
$this->bypassCurrentUserCondition($bypass);
}
/**
* 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 {
// if type=user, we check that handle@domain is not an actual local user
if ($type === Member::TYPE_USER) {
try {
return $this->getLocalFederatedUser($federatedId);
} catch (Exception $e) {
}
}
[$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);
case Member::TYPE_MAIL:
return $this->getFederatedUser_Mail($federatedId);
case Member::TYPE_CONTACT:
return $this->getFederatedUser_Contact($federatedId);
}
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 {
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);
}
/**
* @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 {
// TODO: implement remote groups
}
}
/**
* @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, '@');
if (strrpos($federatedId, '@') === false) {
$userId = $federatedId;
$instance = $this->interfaceService->getLocalInstance();
} else {
[$userId, $instance] = explode('@', $federatedId);
}
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->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);
// TODO: should not be needed
// $this->membershipService->onUpdate($id);
}
return $this->circleRequest->getSingleCircle($federatedUser);
}
/**
* 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;
}
}