%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/CircleService.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 OCA\Circles\AppInfo\Application; use OCA\Circles\Db\CircleRequest; use OCA\Circles\Db\MemberRequest; use OCA\Circles\Exceptions\CircleNameTooShortException; use OCA\Circles\Exceptions\CircleNotFoundException; use OCA\Circles\Exceptions\FederatedEventException; use OCA\Circles\Exceptions\FederatedItemException; use OCA\Circles\Exceptions\InitiatorNotConfirmedException; use OCA\Circles\Exceptions\InitiatorNotFoundException; use OCA\Circles\Exceptions\MembersLimitException; 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\UnknownRemoteException; use OCA\Circles\FederatedItems\CircleConfig; use OCA\Circles\FederatedItems\CircleCreate; use OCA\Circles\FederatedItems\CircleDestroy; use OCA\Circles\FederatedItems\CircleEdit; use OCA\Circles\FederatedItems\CircleJoin; use OCA\Circles\FederatedItems\CircleLeave; use OCA\Circles\FederatedItems\CircleSetting; use OCA\Circles\IEntity; use OCA\Circles\IFederatedUser; use OCA\Circles\Model\Circle; use OCA\Circles\Model\Federated\FederatedEvent; use OCA\Circles\Model\FederatedUser; use OCA\Circles\Model\ManagedModel; use OCA\Circles\Model\Member; use OCA\Circles\Model\Probes\CircleProbe; use OCA\Circles\Model\Probes\DataProbe; use OCA\Circles\Model\Probes\MemberProbe; use OCA\Circles\StatusCode; use OCA\Circles\Tools\Exceptions\InvalidItemException; use OCA\Circles\Tools\Model\SimpleDataStore; 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\IL10N; use OCP\Security\IHasher; class CircleService { use TArrayTools; use TStringTools; use TNCLogger; use TDeserialize; public const CACHE_GET_CIRCLES = 'circles/getCircles'; public const CACHE_GET_CIRCLES_TTL = 300; /** @var IL10N */ private $l10n; /** @var IHasher */ private $hasher; /** @var ICache $cache */ private $cache; /** @var CircleRequest */ private $circleRequest; /** @var MemberRequest */ private $memberRequest; /** @var RemoteStreamService */ private $remoteStreamService; /** @var FederatedUserService */ private $federatedUserService; /** @var FederatedEventService */ private $federatedEventService; /** @var MemberService */ private $memberService; /** @var PermissionService */ private $permissionService; /** @var ConfigService */ private $configService; /** * @param IL10N $l10n * @param IHasher $hasher * @param CircleRequest $circleRequest * @param MemberRequest $memberRequest * @param RemoteStreamService $remoteStreamService * @param FederatedUserService $federatedUserService * @param FederatedEventService $federatedEventService * @param MemberService $memberService * @param PermissionService $permissionService * @param ConfigService $configService */ public function __construct( IL10N $l10n, IHasher $hasher, ICacheFactory $cacheFactory, CircleRequest $circleRequest, MemberRequest $memberRequest, RemoteStreamService $remoteStreamService, FederatedUserService $federatedUserService, FederatedEventService $federatedEventService, MemberService $memberService, PermissionService $permissionService, ConfigService $configService ) { $this->l10n = $l10n; $this->hasher = $hasher; $this->cache = $cacheFactory->createDistributed(self::CACHE_GET_CIRCLES); $this->circleRequest = $circleRequest; $this->memberRequest = $memberRequest; $this->remoteStreamService = $remoteStreamService; $this->federatedUserService = $federatedUserService; $this->federatedEventService = $federatedEventService; $this->memberService = $memberService; $this->permissionService = $permissionService; $this->configService = $configService; $this->setup('app', Application::APP_ID); } /** * @param string $name * @param FederatedUser|null $owner * @param bool $personal * @param bool $local * * @return array * @throws FederatedEventException * @throws FederatedItemException * @throws InitiatorNotConfirmedException * @throws InitiatorNotFoundException * @throws OwnerNotFoundException * @throws RemoteInstanceException * @throws RemoteNotFoundException * @throws RemoteResourceNotFoundException * @throws UnknownRemoteException * @throws RequestBuilderException * @throws CircleNameTooShortException */ public function create( string $name, ?FederatedUser $owner = null, bool $personal = false, bool $local = false ): array { $this->federatedUserService->mustHaveCurrentUser(); if (is_null($owner)) { $owner = $this->federatedUserService->getCurrentUser(); } if (is_null($owner)) { $owner = $this->federatedUserService->getCurrentApp(); } if (is_null($owner)) { throw new OwnerNotFoundException('owner not defined'); } $circle = new Circle(); $circle->setName($this->cleanCircleName($name)) ->setSingleId($this->token(ManagedModel::ID_LENGTH)) ->setSource(Member::TYPE_CIRCLE); if (strlen($circle->getName()) < 3) { throw new CircleNameTooShortException('Circle name is too short'); } if ($personal) { $circle->setConfig(Circle::CFG_PERSONAL); } if ($local) { $circle->addConfig(Circle::CFG_LOCAL); } $this->confirmName($circle); $this->permissionService->confirmAllowedCircleTypes($circle); $member = new Member(); $member->importFromIFederatedUser($owner); $member->setId($this->token(ManagedModel::ID_LENGTH)) ->setCircleId($circle->getSingleId()) ->setLevel(Member::LEVEL_OWNER) ->setStatus(Member::STATUS_MEMBER); $this->federatedUserService->setMemberPatron($member); $circle->setOwner($member) ->setInitiator($member); $event = new FederatedEvent(CircleCreate::class); $event->setCircle($circle); $this->federatedEventService->newEvent($event); return $event->getOutcome(); } /** * @param string $circleId * @param bool $forceSync * * @return array * @throws CircleNotFoundException * @throws FederatedEventException * @throws FederatedItemException * @throws InitiatorNotConfirmedException * @throws InitiatorNotFoundException * @throws OwnerNotFoundException * @throws RemoteInstanceException * @throws RemoteNotFoundException * @throws RemoteResourceNotFoundException * @throws RequestBuilderException * @throws UnknownRemoteException */ public function destroy(string $circleId, bool $forceSync = false): array { $this->federatedUserService->mustHaveCurrentUser(); $circle = $this->getCircle($circleId); $event = new FederatedEvent(CircleDestroy::class); $event->setCircle($circle); $event->forceSync($forceSync); $this->federatedEventService->newEvent($event); return $event->getOutcome(); } /** * @param string $circleId * @param int $config * @param bool $superSession * * @return array * @throws CircleNotFoundException * @throws FederatedEventException * @throws FederatedItemException * @throws InitiatorNotConfirmedException * @throws InitiatorNotFoundException * @throws OwnerNotFoundException * @throws RemoteInstanceException * @throws RemoteNotFoundException * @throws RemoteResourceNotFoundException * @throws RequestBuilderException * @throws UnknownRemoteException */ public function updateConfig(string $circleId, int $config): array { $this->federatedUserService->mustHaveCurrentUser(); $circle = $this->getCircle($circleId); $event = new FederatedEvent(CircleConfig::class); $event->setCircle($circle); $event->setParams( new SimpleDataStore( [ 'config' => $config, 'superSession' => $this->federatedUserService->canBypassCurrentUserCondition() ] ) ); $this->federatedEventService->newEvent($event); return $event->getOutcome(); } /** * if $value is null, setting is unset * * @param string $circleId * @param string $setting * @param string|null $value * * @return array * @throws CircleNotFoundException * @throws FederatedEventException * @throws FederatedItemException * @throws InitiatorNotConfirmedException * @throws InitiatorNotFoundException * @throws OwnerNotFoundException * @throws RemoteInstanceException * @throws RemoteNotFoundException * @throws RemoteResourceNotFoundException * @throws RequestBuilderException * @throws UnknownRemoteException */ public function updateSetting(string $circleId, string $setting, ?string $value): array { $circle = $this->getCircle($circleId); if (strtolower($setting) === 'password_single' && !is_null($value)) { $value = $this->hasher->hash($value); } $event = new FederatedEvent(CircleSetting::class); $event->setCircle($circle); $event->setParams( new SimpleDataStore( [ 'setting' => $setting, 'value' => $value, 'unset' => is_null($value) ] ) ); $this->federatedEventService->newEvent($event); return $event->getOutcome(); } /** * @param string $circleId * @param string $name * * @return array * @throws CircleNotFoundException * @throws FederatedEventException * @throws FederatedItemException * @throws InitiatorNotConfirmedException * @throws InitiatorNotFoundException * @throws OwnerNotFoundException * @throws RemoteInstanceException * @throws RemoteNotFoundException * @throws RemoteResourceNotFoundException * @throws RequestBuilderException * @throws UnknownRemoteException */ public function updateName(string $circleId, string $name): array { $circle = $this->getCircle($circleId); $event = new FederatedEvent(CircleEdit::class); $event->setCircle($circle); $event->setParams(new SimpleDataStore(['name' => $name])); $this->federatedEventService->newEvent($event); return $event->getOutcome(); } /** * @param string $circleId * @param string $description * * @return array * @throws CircleNotFoundException * @throws FederatedEventException * @throws FederatedItemException * @throws InitiatorNotConfirmedException * @throws InitiatorNotFoundException * @throws OwnerNotFoundException * @throws RemoteInstanceException * @throws RemoteNotFoundException * @throws RemoteResourceNotFoundException * @throws RequestBuilderException * @throws UnknownRemoteException */ public function updateDescription(string $circleId, string $description): array { $circle = $this->getCircle($circleId); $event = new FederatedEvent(CircleEdit::class); $event->setCircle($circle); $event->setParams(new SimpleDataStore(['description' => $description])); $this->federatedEventService->newEvent($event); return $event->getOutcome(); } /** * @param string $circleId * * @return array * @throws CircleNotFoundException * @throws FederatedEventException * @throws FederatedItemException * @throws InitiatorNotConfirmedException * @throws InitiatorNotFoundException * @throws OwnerNotFoundException * @throws RemoteInstanceException * @throws RemoteNotFoundException * @throws RemoteResourceNotFoundException * @throws UnknownRemoteException * @throws RequestBuilderException */ public function circleJoin(string $circleId): array { $this->federatedUserService->mustHaveCurrentUser(); $probe = new CircleProbe(); $probe->includeNonVisibleCircles() ->emulateVisitor(); $circle = $this->circleRequest->getCircle( $circleId, $this->federatedUserService->getCurrentUser(), $probe ); if (!$circle->getInitiator()->hasInvitedBy()) { $this->federatedUserService->setMemberPatron($circle->getInitiator()); } $event = new FederatedEvent(CircleJoin::class); $event->setCircle($circle); $this->federatedEventService->newEvent($event); return $event->getOutcome(); } /** * @param string $circleId * @param bool $force * * @return array * @throws CircleNotFoundException * @throws FederatedEventException * @throws FederatedItemException * @throws InitiatorNotConfirmedException * @throws InitiatorNotFoundException * @throws OwnerNotFoundException * @throws RemoteInstanceException * @throws RemoteNotFoundException * @throws RemoteResourceNotFoundException * @throws RequestBuilderException * @throws UnknownRemoteException */ public function circleLeave(string $circleId, bool $force = false): array { $this->federatedUserService->mustHaveCurrentUser(); $probe = new CircleProbe(); $probe->includeNonVisibleCircles() ->emulateVisitor(); $circle = $this->circleRequest->getCircle( $circleId, $this->federatedUserService->getCurrentUser(), $probe ); $event = new FederatedEvent(CircleLeave::class); $event->setCircle($circle); $event->getParams()->sBool('force', $force); $this->federatedEventService->newEvent($event); return $event->getOutcome(); } /** * @param string $circleId * @param CircleProbe|null $probe * * @return Circle * @throws CircleNotFoundException * @throws InitiatorNotFoundException * @throws RequestBuilderException */ public function getCircle( string $circleId, ?CircleProbe $probe = null ): Circle { $this->federatedUserService->mustHaveCurrentUser(); return $this->circleRequest->getCircle( $circleId, $this->federatedUserService->getCurrentEntity(), $probe ); } /** * @return Circle[] * @throws InitiatorNotFoundException * @throws RequestBuilderException */ public function getCircles(CircleProbe $probe, bool $caching = false): array { $this->federatedUserService->mustHaveCurrentUser(); // This is a quick solution before implementation of DataProbe if ($caching && !is_null($this->federatedUserService->getCurrentUser())) { $key = $this->generateGetCirclesCacheKey( $this->federatedUserService->getCurrentUser(), $probe->getChecksum() ); $cachedData = $this->cache->get($key); try { if (!is_string($cachedData)) { throw new InvalidItemException(); } return $this->deserializeList($cachedData, Circle::class); } catch (InvalidItemException $e) { } } $circles = $this->circleRequest->getCircles( $this->federatedUserService->getCurrentUser(), $probe ); if ($caching && !is_null($this->federatedUserService->getCurrentUser())) { $this->cache->set($key, json_encode($circles), self::CACHE_GET_CIRCLES_TTL); } return $circles; } /** * @param Circle $circle * * @throws RequestBuilderException */ public function confirmName(Circle $circle): void { if ($circle->isConfig(Circle::CFG_SYSTEM) || $circle->isConfig(Circle::CFG_SINGLE)) { return; } $this->confirmDisplayName($circle); $this->generateSanitizedName($circle); } /** * @param Circle $circle * * @throws RequestBuilderException */ private function confirmDisplayName(Circle $circle) { $baseDisplayName = $circle->getName(); $i = 1; while (true) { $testDisplayName = $baseDisplayName . (($i > 1) ? ' (' . $i . ')' : ''); $test = new Circle(); $test->setDisplayName($testDisplayName); try { $stored = $this->circleRequest->searchCircle($test); if ($stored->getSingleId() === $circle->getSingleId()) { throw new CircleNotFoundException(); } } catch (CircleNotFoundException $e) { $circle->setDisplayName($testDisplayName); return; } $i++; } } /** * @param Circle $circle * * @throws RequestBuilderException */ public function generateSanitizedName(Circle $circle) { $baseSanitizedName = $this->sanitizeName($circle->getName()); if ($baseSanitizedName === '') { $baseSanitizedName = substr($circle->getSingleId(), 0, 3); } $i = 1; while (true) { $testSanitizedName = $baseSanitizedName . (($i > 1) ? ' (' . $i . ')' : ''); $test = new Circle(); $test->setSanitizedName($testSanitizedName); try { $stored = $this->circleRequest->searchCircle($test); if ($stored->getSingleId() === $circle->getSingleId()) { throw new CircleNotFoundException(); } } catch (CircleNotFoundException $e) { $circle->setSanitizedName($testSanitizedName); return; } $i++; } } /** * @param string $name * * @return string */ public function sanitizeName(string $name): string { // replace '/' with '-' to prevent directory traversal // replacing instead of stripping seems the better tradeoff here $sanitized = str_replace('/', '-', $name); // remove characters which are illegal on Windows (includes illegal characters on Unix/Linux) // see also \OC\Files\Storage\Common::verifyPosixPath(...) /** @noinspection CascadeStringReplacementInspection */ $sanitized = str_replace(['*', '|', '\\', ':', '"', '<', '>', '?'], '', $sanitized); // remove leading+trailing spaces and dots to prevent hidden files return trim($sanitized, ' .'); } /** * @param Circle $circle * * @throws MembersLimitException */ public function confirmCircleNotFull(Circle $circle): void { if ($this->isCircleFull($circle)) { throw new MembersLimitException(StatusCode::$MEMBER_ADD[121], 121); } } /** * @param Circle $circle * * @return bool * @throws RequestBuilderException */ public function isCircleFull(Circle $circle): bool { $filterMember = new Member(); $filterMember->setLevel(Member::LEVEL_MEMBER); $probe = new MemberProbe(); $probe->setFilterMember($filterMember); $members = $this->memberRequest->getMembers($circle->getSingleId(), null, $probe); $limit = $this->getInt('members_limit', $circle->getSettings()); if ($limit === 0) { $limit = $this->configService->getAppValueInt(ConfigService::MEMBERS_LIMIT); } if ($limit === -1) { return false; } return (sizeof($members) >= $limit); } /** * @param string $name * * @return string */ public function cleanCircleName(string $name): string { $name = preg_replace('/\s+/', ' ', $name); return trim($name); } /** * @param IEntity $entity * * @return string */ public function getDefinition(IEntity $entity): string { if ($entity instanceof Circle) { return $this->getDefinitionCircle($entity); } if ($entity instanceof IFederatedUser) { return $this->getDefinitionUser($entity); } return ''; } /** * @param Circle $circle * * @return string */ public function getDefinitionCircle(Circle $circle): string { $source = Circle::$DEF_SOURCE[$circle->getSource()]; if ($circle->isConfig(Circle::CFG_NO_OWNER) || $circle->isConfig(Circle::CFG_SINGLE)) { return $this->l10n->t('%s', [$source]); } if ($circle->isConfig(Circle::CFG_PERSONAL)) { return $this->l10n->t('Personal team'); } if ($circle->hasOwner()) { return $this->l10n->t( '%s owned by %s', [ $source, $this->configService->displayFederatedUser($circle->getOwner(), true) ] ); } return $source; } /** * @param IFederatedUser $federatedUser * * @return string */ public function getDefinitionUser(IFederatedUser $federatedUser): string { return $this->l10n->t('%s', [Circle::$DEF_SOURCE[$federatedUser->getUserType()]]); } private function generateGetCirclesCacheKey(FederatedUser $federatedUser, string $probeSum): string { return $federatedUser->getSingleId() . '#' . $probeSum; } /** * @param string $circleId * @param CircleProbe $circleProbe * @param DataProbe|null $dataProbe * * @return Circle * @throws InitiatorNotFoundException * @throws RequestBuilderException * @throws CircleNotFoundException */ public function probeCircle( string $circleId, ?CircleProbe $circleProbe = null, ?DataProbe $dataProbe = null ): Circle { $this->federatedUserService->mustHaveCurrentUser(); if (is_null($circleProbe)) { $circleProbe = new CircleProbe(); $circleProbe->includeSystemCircles(); } if (is_null($dataProbe)) { $dataProbe = new DataProbe(); } return $this->circleRequest->probeCircle( $circleId, $this->federatedUserService->getCurrentUser(), $circleProbe, $dataProbe ); } /** * @param CircleProbe $circleProbe * @param DataProbe|null $dataProbe * * @return Circle[] * @throws InitiatorNotFoundException * @throws RequestBuilderException */ public function probeCircles(CircleProbe $circleProbe, ?DataProbe $dataProbe = null): array { $this->federatedUserService->mustHaveCurrentUser(); if (is_null($dataProbe)) { $dataProbe = new DataProbe(); } return $this->circleRequest->probeCircles( $this->federatedUserService->getCurrentUser(), $circleProbe, $dataProbe ); } }