diff options
author | Maxence Lange <maxence@artificial-owl.com> | 2020-04-10 09:47:25 +0300 |
---|---|---|
committer | Maxence Lange <maxence@artificial-owl.com> | 2020-04-14 13:59:38 +0300 |
commit | f4ef2b19e65a2e7769477d967fcc603134983fea (patch) | |
tree | 9921f184dd1e18d59097defb6395b7bd7563336d /lib | |
parent | 1d753124975adff0262ae297f8710dae7fc64550 (diff) |
gs+circles
Signed-off-by: Maxence Lange <maxence@artificial-owl.com>
Diffstat (limited to 'lib')
72 files changed, 6366 insertions, 361 deletions
diff --git a/lib/Api/Sharees.php b/lib/Api/Sharees.php index 2755f93c..06856ea6 100644 --- a/lib/Api/Sharees.php +++ b/lib/Api/Sharees.php @@ -111,4 +111,4 @@ class Sharees { } -}
\ No newline at end of file +} diff --git a/lib/AppInfo/Application.php b/lib/AppInfo/Application.php index 1b3c247d..fd3df98b 100644 --- a/lib/AppInfo/Application.php +++ b/lib/AppInfo/Application.php @@ -31,6 +31,8 @@ namespace OCA\Circles\AppInfo; use OC; use OCA\Circles\Api\v1\Circles; +use OCA\Circles\Exceptions\GSStatusException; +use OCA\Circles\GlobalScale\GSMount\MountProvider; use OCA\Circles\Notification\Notifier; use OCA\Circles\Service\ConfigService; use OCA\Circles\Service\DavService; @@ -53,11 +55,18 @@ class Application extends App { const CLIENT_TIMEOUT = 3; + + /** @var ConfigService */ + private $configService; + /** @var IAppContainer */ private $container; /** * @param array $params + * + * @throws GSStatusException + * @throws QueryException */ public function __construct(array $params = array()) { parent::__construct(self::APP_NAME, $params); @@ -67,12 +76,29 @@ class Application extends App { $manager = OC::$server->getNotificationManager(); $manager->registerNotifierService(Notifier::class); + $this->configService = OC::$server->query(ConfigService::class); + + $this->registerMountProvider(); $this->registerHooks(); $this->registerDavHooks(); } /** + * @throws GSStatusException + * @throws QueryException + */ + public function registerMountProvider() { + if (!$this->configService->getGSStatus(ConfigService::GS_ENABLED)) { + return; + } + + $mountProviderCollection = \OC::$server->getMountProviderCollection(); + $mountProviderCollection->registerProvider($this->container->query(MountProvider::class)); + } + + + /** * Register Hooks */ public function registerHooks() { @@ -89,14 +115,7 @@ class Application extends App { * Register Navigation elements */ public function registerNavigation() { - /** @var ConfigService $configService */ - try { - $configService = OC::$server->query(ConfigService::class); - } catch (QueryException $e) { - return; - } - - if (!$configService->stillFrontEnd()) { + if (!$this->configService->stillFrontEnd()) { return; } diff --git a/lib/Circles/FileSharingBroadcaster.php b/lib/Circles/FileSharingBroadcaster.php index f6028991..ed52f207 100644 --- a/lib/Circles/FileSharingBroadcaster.php +++ b/lib/Circles/FileSharingBroadcaster.php @@ -33,7 +33,6 @@ use OC\Share20\Share; use OCA\Circles\AppInfo\Application; use OCA\Circles\Db\SharesRequest; use OCA\Circles\Db\TokensRequest; -use OCA\Circles\Exceptions\TokenDoesNotExistException; use OCA\Circles\IBroadcaster; use OCA\Circles\Model\Circle; use OCA\Circles\Model\Member; @@ -192,6 +191,7 @@ class FileSharingBroadcaster implements IBroadcaster { /** * {@inheritdoc} * @throws IllegalIDChangeException + * @throws Exception */ public function createShareToMember(SharingFrame $frame, Member $member) { if (!$frame->is0Circle()) { @@ -205,9 +205,8 @@ class FileSharingBroadcaster implements IBroadcaster { $share = $this->generateShare($payload['share']); if ($member->getType() === Member::TYPE_MAIL || $member->getType() === Member::TYPE_CONTACT) { + $circle = $frame->getCircle(); try { - $circle = $frame->getCircle(); - // federated shared in contact $clouds = $this->getCloudsFromContact($member->getUserId()); if ($this->federatedEnabled && !empty($clouds)) { diff --git a/lib/Collaboration/v1/CollaboratorSearchPlugin.php b/lib/Collaboration/v1/CollaboratorSearchPlugin.php index 0195266e..703eee62 100644 --- a/lib/Collaboration/v1/CollaboratorSearchPlugin.php +++ b/lib/Collaboration/v1/CollaboratorSearchPlugin.php @@ -79,4 +79,4 @@ class CollaboratorSearchPlugin implements ISearchPlugin { ], ]; } -}
\ No newline at end of file +} diff --git a/lib/Command/CirclesList.php b/lib/Command/CirclesList.php index 029bb4bf..506e7f0e 100644 --- a/lib/Command/CirclesList.php +++ b/lib/Command/CirclesList.php @@ -29,6 +29,7 @@ namespace OCA\Circles\Command; +use daita\MySmallPhpTools\Traits\TArrayTools; use OC\Core\Command\Base; use OCA\Circles\Db\CirclesRequest; use OCA\Circles\Exceptions\ConfigNoCircleAvailableException; @@ -50,6 +51,9 @@ use Symfony\Component\Console\Output\OutputInterface; class CirclesList extends Base { + use TArrayTools; + + /** @var IL10N */ private $l10n; @@ -103,19 +107,23 @@ class CirclesList extends Base { } $table = new Table($output); - $table->setHeaders(['ID', 'Name', 'Type', 'Owner']); + $table->setHeaders(['ID', 'Name', 'Type', 'Owner', 'Instance', 'Limit', 'Description']); $table->render(); $output->writeln(''); $c = 0; foreach ($circles as $circle) { + $owner = $circle->getOwner(); + $settings = $circle->getSettings(); $table->appendRow( [ $circle->getUniqueId(), $circle->getName(), $circle->getTypeLongString(), - $circle->getOwner() - ->getUserId() + $owner->getUserId(), + $owner->getInstance(), + $this->getInt('members_limit', $settings, -1), + substr($circle->getDescription(), 0, 30) ] ); } diff --git a/lib/Command/CirclesSync.php b/lib/Command/CirclesSync.php new file mode 100644 index 00000000..cf5a23f0 --- /dev/null +++ b/lib/Command/CirclesSync.php @@ -0,0 +1,93 @@ +<?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 2017 + * @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\Command; + +use OC\Core\Command\Base; +use OCA\Circles\Exceptions\CircleDoesNotExistException; +use OCA\Circles\Exceptions\GSStatusException; +use OCA\Circles\Service\GSUpstreamService; +use OCP\IL10N; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; + + +/** + * Class CirclesSync + * + * @package OCA\Circles\Command + */ +class CirclesSync extends Base { + + + /** @var IL10N */ + private $l10n; + + /** @var GSUpstreamService */ + private $gsUpstreamService; + + + /** + * CirclesSync constructor. + * + * @param IL10N $l10n + * @param GSUpstreamService $gsUpstreamService + */ + public function __construct(IL10N $l10n, GSUpstreamService $gsUpstreamService) { + parent::__construct(); + $this->l10n = $l10n; + $this->gsUpstreamService = $gsUpstreamService; + } + + + /** + * + */ + protected function configure() { + parent::configure(); + $this->setName('circles:manage:sync') + ->setDescription('sync circles in GlobalScale env'); + } + + + /** + * @param InputInterface $input + * @param OutputInterface $output + * + * @return int + * @throws GSStatusException + */ + protected function execute(InputInterface $input, OutputInterface $output): int { + $this->gsUpstreamService->synchronize(); + + return 0; + } + +} + diff --git a/lib/Command/Clean.php b/lib/Command/Clean.php index 2f55faab..af8fc663 100644 --- a/lib/Command/Clean.php +++ b/lib/Command/Clean.php @@ -34,6 +34,7 @@ use OCA\Circles\Db\MembersRequest; use OCA\Circles\Exceptions\CircleDoesNotExistException; use OCP\IDBConnection; use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; @@ -69,20 +70,32 @@ class Clean extends Base { protected function configure() { parent::configure(); $this->setName('circles:clean') - ->setDescription('remove all extra data from database'); + ->setDescription('remove all extra data from database') + ->addOption('all', '', InputOption::VALUE_NONE, 'remove all data from the app'); } - protected function execute(InputInterface $input, OutputInterface $output) { - try { - $this->fixUserType(); - $this->removeCirclesWithNoOwner(); - $this->removeMembersWithNoCircles(); + /** + * @param InputInterface $input + * @param OutputInterface $output + * + * @return int + */ + protected function execute(InputInterface $input, OutputInterface $output): int { + + if ($input->getOption('all')) { + $this->circlesRequest->cleanDatabase(); - $output->writeln('done'); - } catch (Exception $e) { - $output->writeln($e->getMessage()); + return 0; } + + $this->fixUserType(); + $this->removeCirclesWithNoOwner(); + $this->removeMembersWithNoCircles(); + + $output->writeln('done'); + + return 0; } diff --git a/lib/Command/MembersCreate.php b/lib/Command/MembersCreate.php index 0edaf78a..7f4e0cc1 100644 --- a/lib/Command/MembersCreate.php +++ b/lib/Command/MembersCreate.php @@ -29,11 +29,20 @@ namespace OCA\Circles\Command; +use daita\MySmallPhpTools\Exceptions\RequestContentException; +use daita\MySmallPhpTools\Exceptions\RequestNetworkException; +use daita\MySmallPhpTools\Exceptions\RequestResultNotJsonException; +use daita\MySmallPhpTools\Exceptions\RequestResultSizeException; +use daita\MySmallPhpTools\Exceptions\RequestServerException; +use daita\MySmallPhpTools\Model\Request; +use daita\MySmallPhpTools\Traits\TRequest; use Exception; use OC\Core\Command\Base; use OC\User\NoUserException; use OCA\Circles\Db\MembersRequest; +use OCA\Circles\Exceptions\GSStatusException; use OCA\Circles\Model\Member; +use OCA\Circles\Service\ConfigService; use OCA\Circles\Service\MembersService; use OCP\IL10N; use OCP\IUserManager; @@ -50,6 +59,9 @@ use Symfony\Component\Console\Output\OutputInterface; class MembersCreate extends Base { + use TRequest; + + /** @var IL10N */ private $l10n; @@ -62,6 +74,9 @@ class MembersCreate extends Base { /** @var MembersRequest */ private $membersRequest; + /** @var ConfigService */ + private $configService; + /** * MembersCreate constructor. @@ -70,15 +85,18 @@ class MembersCreate extends Base { * @param IUserManager $userManager * @param MembersService $membersService * @param MembersRequest $membersRequest + * @param ConfigService $configService */ public function __construct( - IL10N $l10n, IUserManager $userManager, MembersService $membersService, MembersRequest $membersRequest + IL10N $l10n, IUserManager $userManager, MembersService $membersService, + MembersRequest $membersRequest, ConfigService $configService ) { parent::__construct(); $this->l10n = $l10n; $this->userManager = $userManager; $this->membersService = $membersService; $this->membersRequest = $membersRequest; + $this->configService = $configService; } @@ -105,11 +123,17 @@ class MembersCreate extends Base { $userId = $input->getArgument('user'); $level = $input->getArgument('level'); + $instance = ''; $user = $this->userManager->get($userId); if ($user === null) { + $userId = $this->findUserFromLookup($userId, $instance); + } else { + $userId = $user->getUID(); + } + + if ($userId === '') { throw new NoUserException('user does not exist'); } - $userId = $user->getUID(); $levels = [ 'member' => Member::LEVEL_MEMBER, @@ -124,14 +148,72 @@ class MembersCreate extends Base { $level = $levels[strtolower($level)]; - $this->membersService->addMember($circleId, $userId, Member::TYPE_USER, true); - $this->membersService->levelMember($circleId, $userId, Member::TYPE_USER, $level, true); + $this->membersService->addMember($circleId, $userId, Member::TYPE_USER, $instance, true); + $this->membersService->levelMember($circleId, $userId, Member::TYPE_USER, $instance, $level, true); - $member = $this->membersRequest->forceGetMember($circleId, $userId, Member::TYPE_USER); + $member = $this->membersRequest->forceGetMember($circleId, $userId, Member::TYPE_USER, $instance); echo json_encode($member, JSON_PRETTY_PRINT) . "\n"; return 0; } + + /** + * @param string $search + * @param string $instance + * + * @return string + */ + private function findUserFromLookup(string $search, string &$instance = ''): string { + $userId = ''; + + /** @var string $lookup */ + try { + $lookup = $this->configService->getGSStatus(ConfigService::GS_LOOKUP); + } catch (GSStatusException $e) { + return ''; + } + + $request = new Request('/users', Request::TYPE_GET); + $request->setProtocols(['https', 'http']); + $request->addData('search', $search); + $request->setAddressFromUrl($lookup); + + try { + $users = $this->retrieveJson($request); + } catch ( + RequestContentException | + RequestNetworkException | + RequestResultSizeException | + RequestServerException | + RequestResultNotJsonException $e + ) { + return ''; + } + + $result = []; + foreach ($users as $user) { + if (!array_key_exists('userid', $user)) { + continue; + } + + list(, $host) = explode('@', $user['federationId']); + if (strtolower($user['userid']['value']) === strtolower($search)) { + $userId = $user['userid']['value']; + $instance = $host; + } + + $result[] = $user['userid']['value'] . ' <info>@' . $host . '</info>'; + } + +// if ($userId === '') { +// foreach($result as $item) { +// $output->writeln($item); +// } +// } + + return $userId; + } + } diff --git a/lib/Command/MembersLevel.php b/lib/Command/MembersLevel.php index 6d2b2e2b..f0eab421 100644 --- a/lib/Command/MembersLevel.php +++ b/lib/Command/MembersLevel.php @@ -118,11 +118,12 @@ class MembersLevel extends Base { $member = $this->membersService->getMemberById($memberId); $this->membersService->levelMember( - $member->getCircleId(), $member->getUserId(), Member::TYPE_USER, $level, true + $member->getCircleId(), $member->getUserId(), Member::TYPE_USER, $member->getInstance(), $level, + true ); $member = $this->membersRequest->forceGetMember( - $member->getCircleId(), $member->getUserId(), Member::TYPE_USER + $member->getCircleId(), $member->getUserId(), Member::TYPE_USER, $member->getInstance() ); echo json_encode($member, JSON_PRETTY_PRINT) . "\n"; diff --git a/lib/Command/MembersList.php b/lib/Command/MembersList.php index ce956ad4..2c5c8536 100644 --- a/lib/Command/MembersList.php +++ b/lib/Command/MembersList.php @@ -109,7 +109,7 @@ class MembersList extends Base { $output = $output->section(); $table = new Table($output); - $table->setHeaders(['ID', 'Name', 'Level']); + $table->setHeaders(['ID', 'Username', 'Instance', 'Level']); $table->render(); $output->writeln(''); @@ -119,6 +119,7 @@ class MembersList extends Base { [ $member->getMemberId(), $member->getUserId(), + $member->getInstance(), $member->getLevelString(), ] ); diff --git a/lib/Command/MembersRemove.php b/lib/Command/MembersRemove.php index 990bb4a1..9cf97944 100644 --- a/lib/Command/MembersRemove.php +++ b/lib/Command/MembersRemove.php @@ -87,7 +87,7 @@ class MembersRemove extends Base { $memberId = $input->getArgument('member_id'); $member = $this->membersService->getMemberById($memberId); - $this->membersService->removeMember($member->getCircleId(), $member->getUserId(), $member->getType(), true); + $this->membersService->removeMember($member->getCircleId(), $member->getUserId(), $member->getType(), $member->getInstance(), true); return 0; } diff --git a/lib/Controller/BaseController.php b/lib/Controller/BaseController.php index 35630feb..58ef1b0c 100644 --- a/lib/Controller/BaseController.php +++ b/lib/Controller/BaseController.php @@ -33,8 +33,9 @@ use OCA\Circles\Service\CirclesService; use OCA\Circles\Service\ConfigService; use OCA\Circles\Service\FederatedLinkService; use OCA\Circles\Service\GroupsService; +use OCA\Circles\Service\GSDownstreamService; +use OCA\Circles\Service\GSUpstreamService; use OCA\Circles\Service\MembersService; - use OCA\Circles\Service\MiscService; use OCA\Circles\Service\SearchService; use OCA\Circles\Service\SharingFrameService; @@ -63,6 +64,12 @@ class BaseController extends Controller { /** @var MembersService */ protected $membersService; + /** @var GSUpstreamService */ + protected $gsUpstreamService; + + /** @var GSDownstreamService */ + protected $gsDownstreamService; + /** @var GroupsService */ protected $groupsService; @@ -90,6 +97,7 @@ class BaseController extends Controller { * @param CirclesService $circlesService * @param SearchService $searchService * @param MembersService $membersService + * @param GSUpstreamService $gsUpstreamService * @param GroupsService $groupsService * @param SharingFrameService $sharingFrameService * @param BroadcastService $broadcastService @@ -105,6 +113,8 @@ class BaseController extends Controller { CirclesService $circlesService, SearchService $searchService, MembersService $membersService, + GSUpstreamService $gsUpstreamService, + GSDownstreamService $gsDownstreamService, GroupsService $groupsService, SharingFrameService $sharingFrameService, BroadcastService $broadcastService, @@ -113,12 +123,15 @@ class BaseController extends Controller { ) { parent::__construct($appName, $request); + $this->userId = $userId; $this->l10n = $l10n; $this->configService = $configService; $this->circlesService = $circlesService; $this->searchService = $searchService; $this->membersService = $membersService; + $this->gsUpstreamService = $gsUpstreamService; + $this->gsDownstreamService = $gsDownstreamService; $this->groupsService = $groupsService; $this->sharingFrameService = $sharingFrameService; $this->broadcastService = $broadcastService; @@ -166,5 +179,3 @@ class BaseController extends Controller { } } - - diff --git a/lib/Controller/CirclesController.php b/lib/Controller/CirclesController.php index 256da074..45dbac3e 100644 --- a/lib/Controller/CirclesController.php +++ b/lib/Controller/CirclesController.php @@ -30,7 +30,6 @@ use Exception; use OCA\Circles\Exceptions\CircleNameFirstCharException; use OCA\Circles\Exceptions\CircleNameTooShortException; use OCA\Circles\Exceptions\CircleTypeDisabledException; -use OCA\Circles\Exceptions\FederatedCircleNotAllowedException; use OCP\AppFramework\Http\DataResponse; class CirclesController extends BaseController { @@ -148,7 +147,6 @@ class CirclesController extends BaseController { return $this->success(['circle_id' => $uniqueId, 'member' => $data]); } catch (Exception $e) { - return $this->fail(['circle_id' => $uniqueId, 'error' => $e->getMessage()]); } } diff --git a/lib/Controller/GlobalScaleController.php b/lib/Controller/GlobalScaleController.php new file mode 100644 index 00000000..bfc32be4 --- /dev/null +++ b/lib/Controller/GlobalScaleController.php @@ -0,0 +1,159 @@ +<?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 2017 + * @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\Controller; + + +use daita\MySmallPhpTools\Traits\TAsync; +use daita\MySmallPhpTools\Traits\TStringTools; +use Exception; +use OCA\Circles\Exceptions\GSStatusException; +use OCA\Circles\Model\GlobalScale\GSEvent; +use OCP\AppFramework\Http\DataResponse; + + +/** + * Class GlobalScaleController + * + * @package OCA\Circles\Controller + */ +class GlobalScaleController extends BaseController { + + + use TStringTools; + use TAsync; + + + /** + * Event is generated by any instance of GS and sent to the instance that owns the Circles, that + * will broadcast the event to other if ok + * + * @PublicPage + * @NoCSRFRequired + * + * @return DataResponse + */ + public function event(): DataResponse { + $data = file_get_contents('php://input'); + + try { + $event = new GSEvent(); + $event->importFromJson($data); + $this->gsDownstreamService->requestedEvent($event); + + return $this->success(['success' => $event]); + } catch (Exception $e) { + return $this->fail(['data' => $data, 'error' => $e->getMessage()]); + } + } + + + /** + * Async process and broadcast the event to every instances of GS + * This should be initiated by the instance that owns the Circles. + * + * @PublicPage + * @NoCSRFRequired + * + * @param string $token + * + * @throws Exception + */ + public function asyncBroadcast(string $token) { + try { + $wrappers = $this->gsUpstreamService->getEventsByToken($token); + } catch (Exception $e) { + $this->miscService->log( + 'exception during async: ' . ['token' => $token, 'error' => $e->getMessage()] + ); + $this->fail(['token' => $token, 'error' => $e->getMessage()]); + } + + $this->async(); + foreach ($wrappers as $wrapper) { + try { + $this->gsUpstreamService->broadcastWrapper($wrapper, $this->request->getServerProtocol()); + } catch (GSStatusException $e) { + } + } + + $this->gsUpstreamService->manageResults($token); + + exit(); + } + + + /** + * Event is sent by instance that owns the Circles. + * + * @PublicPage + * @NoCSRFRequired + * + * @return DataResponse + */ + public function broadcast(): DataResponse { + $data = file_get_contents('php://input'); + + try { + $event = new GSEvent(); + $event->importFromJson($data); + + $this->gsDownstreamService->onNewEvent($event); + + return $this->success(['result' => $event->getResult()]); + } catch (Exception $e) { + return $this->fail(['data' => $data, 'error' => $e->getMessage()]); + } + } + + + /** + * Status Event. This is an event to check status of items between instances. + * + * @PublicPage + * @NoCSRFRequired + * + * @return DataResponse + */ + public function status(): DataResponse { + $data = file_get_contents('php://input'); + + try { + $event = new GSEvent(); + $event->importFromJson($data); + $this->gsDownstreamService->statusEvent($event); + + return $this->success(['success' => $event]); + } catch (Exception $e) { + return $this->fail(['data' => $data, 'error' => $e->getMessage()]); + } + } + +} + diff --git a/lib/Controller/MembersController.php b/lib/Controller/MembersController.php index 6fc212cf..ac21b16e 100644 --- a/lib/Controller/MembersController.php +++ b/lib/Controller/MembersController.php @@ -40,15 +40,16 @@ class MembersController extends BaseController { * @param string $uniqueId * @param $ident * @param $type + * @param string $instance * * @return DataResponse */ - public function addMember($uniqueId, $ident, $type) { + public function addMember($uniqueId, $ident, $type, $instance) { try { $this->mustHaveFrontEndEnabled(); - $data = $this->membersService->addMember($uniqueId, $ident, (int)$type); + $data = $this->membersService->addMember($uniqueId, $ident, (int)$type, $instance); } catch (\Exception $e) { return $this->fail( [ @@ -114,22 +115,24 @@ class MembersController extends BaseController { * @param string $uniqueId * @param string $member * @param int $type + * @param string $instance * @param int $level * * @return DataResponse */ - public function levelMember($uniqueId, $member, $type, $level) { + public function levelMember($uniqueId, $member, $type, $instance, $level) { try { $this->mustHaveFrontEndEnabled(); - $data = $this->membersService->levelMember($uniqueId, $member, (int)$type, $level); + $data = $this->membersService->levelMember($uniqueId, $member, (int)$type, $instance, $level); } catch (\Exception $e) { return $this->fail( [ 'circle_id' => $uniqueId, 'user_id' => $member, + 'instance' => $instance, 'user_type' => (int)$type, 'display' => MiscService::getDisplay($member, (int)$type), 'level' => $level, @@ -158,15 +161,16 @@ class MembersController extends BaseController { * @param string $uniqueId * @param string $member * @param int $type + * @param $instance * * @return DataResponse */ - public function removeMember($uniqueId, $member, $type) { + public function removeMember($uniqueId, $member, $type, $instance) { try { $this->mustHaveFrontEndEnabled(); - $data = $this->membersService->removeMember($uniqueId, $member, (int)$type); + $data = $this->membersService->removeMember($uniqueId, $member, (int)$type, $instance); } catch (\Exception $e) { return $this->fail( diff --git a/lib/Cron/GlobalSync.php b/lib/Cron/GlobalSync.php new file mode 100644 index 00000000..ef25f420 --- /dev/null +++ b/lib/Cron/GlobalSync.php @@ -0,0 +1,74 @@ +<?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 2017 + * @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\Cron; + + +use OC\BackgroundJob\TimedJob; +use OCA\Circles\AppInfo\Application; +use OCA\Circles\Exceptions\GSStatusException; +use OCA\Circles\Service\GSUpstreamService; +use OCP\AppFramework\QueryException; + + +/** + * Class GlobalSync + * + * @package OCA\Cicles\Cron + */ +class GlobalSync extends TimedJob { + + + /** + * Cache constructor. + */ + public function __construct() { + $this->setInterval(10); + } + + + /** + * @param $argument + * + * @throws QueryException + */ + protected function run($argument) { + $app = new Application(); + $c = $app->getContainer(); + + /** @var GSUpstreamService $gsUpstreamService */ + $gsUpstreamService = $c->query(GSUpstreamService::class); + try { + $gsUpstreamService->synchronize(); + } catch (GSStatusException $e) { + } + } + +} + diff --git a/lib/Db/CircleProviderRequestBuilder.php b/lib/Db/CircleProviderRequestBuilder.php index 1a8e0d90..b65c30e3 100644 --- a/lib/Db/CircleProviderRequestBuilder.php +++ b/lib/Db/CircleProviderRequestBuilder.php @@ -308,6 +308,7 @@ class CircleProviderRequestBuilder extends CoreRequestBuilder { $andX->add($expr->eq('mcm.user_id', $qb->createNamedParameter($userId))); $andX->add($expr->gt('mcm.level', $qb->createNamedParameter(0))); + $andX->add($expr->eq('mcm.instance', $qb->createNamedParameter(''))); $andX->add($expr->eq('mcm.user_type', $qb->createNamedParameter(Member::TYPE_USER))); $subQb->andWhere($andX); diff --git a/lib/Db/CirclesRequest.php b/lib/Db/CirclesRequest.php index aca9d7fe..d97b17c9 100644 --- a/lib/Db/CirclesRequest.php +++ b/lib/Db/CirclesRequest.php @@ -1,4 +1,6 @@ -<?php +<?php declare(strict_types=1); + + /** * Circles - Bring cloud-users closer together. * @@ -64,9 +66,7 @@ class CirclesRequest extends CirclesRequestBuilder { throw new CircleDoesNotExistException($this->l10n->t('Circle not found')); } - $entry = $this->parseCirclesSelectSql($data); - - return $entry; + return $this->parseCirclesSelectSql($data); } @@ -132,6 +132,7 @@ class CirclesRequest extends CirclesRequestBuilder { /** * @param string $userId + * @param string $instanceId * @param int $type * @param string $name * @param int $level @@ -141,13 +142,16 @@ class CirclesRequest extends CirclesRequestBuilder { * @return Circle[] * @throws ConfigNoCircleAvailableException */ - public function getCircles($userId, $type = 0, $name = '', $level = 0, $forceAll = false, string $ownerId = '') { + public function getCircles( + string $userId, int $type = 0, string $name = '', int $level = 0, bool $forceAll = false, + string $ownerId = '' + ) { if ($type === 0) { $type = Circle::CIRCLES_ALL; } $qb = $this->getCirclesSelectSql(); - $this->leftJoinUserIdAsViewer($qb, $userId); + $this->leftJoinUserIdAsViewer($qb, $userId, ''); $this->leftJoinOwner($qb, $ownerId); $this->leftJoinNCGroupAndUser($qb, $userId, '`c`.`unique_id`'); @@ -173,20 +177,25 @@ class CirclesRequest extends CirclesRequestBuilder { * * @param string $circleUniqueId * @param string $viewerId + * @param string $instanceId * @param bool $forceAll * * @return Circle * @throws CircleDoesNotExistException * @throws ConfigNoCircleAvailableException */ - public function getCircle($circleUniqueId, $viewerId, $forceAll = false) { + public function getCircle( + string $circleUniqueId, string $viewerId, string $instanceId = '', bool $forceAll = false + ) { $qb = $this->getCirclesSelectSql(); $this->limitToShortenUniqueId($qb, $circleUniqueId, Circle::SHORT_UNIQUE_ID_LENGTH); - $this->leftJoinUserIdAsViewer($qb, $viewerId); + $this->leftJoinUserIdAsViewer($qb, $viewerId, $instanceId); $this->leftJoinOwner($qb); - $this->leftJoinNCGroupAndUser($qb, $viewerId, '`c`.`unique_id`'); + if ($instanceId === '') { + $this->leftJoinNCGroupAndUser($qb, $viewerId, '`c`.`unique_id`'); + } $this->limitRegardingCircleType($qb, $viewerId, $circleUniqueId, Circle::CIRCLES_ALL, '', $forceAll); @@ -199,9 +208,11 @@ class CirclesRequest extends CirclesRequestBuilder { } $circle = $this->parseCirclesSelectSql($data); - $circle->setGroupViewer( - $this->membersRequest->forceGetHigherLevelGroupFromUser($circleUniqueId, $viewerId) - ); + if ($instanceId === '') { + $circle->setGroupViewer( + $this->membersRequest->forceGetHigherLevelGroupFromUser($circleUniqueId, $viewerId) + ); + } return $circle; } @@ -214,19 +225,8 @@ class CirclesRequest extends CirclesRequestBuilder { * Will returns the circle * * @param Circle $circle - * @param $userId - * - * @throws CircleAlreadyExistsException */ - public function createCircle(Circle &$circle, $userId) { - - if (!$this->isCircleUnique($circle, $userId)) { - throw new CircleAlreadyExistsException( - $this->l10n->t('A circle with that name exists') - ); - } - - $circle->generateUniqueId(); + public function createCircle(Circle $circle) { $qb = $this->getCirclesInsertSql(); $qb->setValue('unique_id', $qb->createNamedParameter($circle->getUniqueId(true))) ->setValue('name', $qb->createNamedParameter($circle->getName())) @@ -236,13 +236,6 @@ class CirclesRequest extends CirclesRequestBuilder { ->setValue('settings', $qb->createNamedParameter($circle->getSettings(true))) ->setValue('type', $qb->createNamedParameter($circle->getType())); $qb->execute(); - - $owner = new Member($userId, Member::TYPE_USER); - $owner->setCircleId($circle->getUniqueId()) - ->setLevel(Member::LEVEL_OWNER) - ->setStatus(Member::STATUS_MEMBER); - $circle->setOwner($owner) - ->setViewer($owner); } @@ -265,9 +258,9 @@ class CirclesRequest extends CirclesRequestBuilder { * @param string $userId * * @return bool + * @throws ConfigNoCircleAvailableException */ - private function isCircleUnique(Circle $circle, $userId) { - + public function isCircleUnique(Circle $circle, $userId = '') { if ($circle->getType() === Circle::CIRCLES_PERSONAL) { return $this->isPersonalCircleUnique($circle, $userId); } @@ -295,8 +288,12 @@ class CirclesRequest extends CirclesRequestBuilder { * @param string $userId * * @return bool + * @throws ConfigNoCircleAvailableException */ - private function isPersonalCircleUnique(Circle $circle, $userId) { + private function isPersonalCircleUnique(Circle $circle, $userId = '') { + if ($userId === '') { + return true; + } $list = $this->getCircles( $userId, Circle::CIRCLES_PERSONAL, $circle->getName(), @@ -320,8 +317,7 @@ class CirclesRequest extends CirclesRequestBuilder { * * @throws CircleAlreadyExistsException */ - public function updateCircle(Circle $circle, $userId) { - + public function updateCircle(Circle $circle, $userId = '') { if (!$this->isCircleUnique($circle, $userId)) { throw new CircleAlreadyExistsException( $this->l10n->t('A circle with that name exists') diff --git a/lib/Db/CirclesRequestBuilder.php b/lib/Db/CirclesRequestBuilder.php index 9f773c8f..7e6da65a 100644 --- a/lib/Db/CirclesRequestBuilder.php +++ b/lib/Db/CirclesRequestBuilder.php @@ -91,7 +91,7 @@ class CirclesRequestBuilder extends CoreRequestBuilder { /** * @param IQueryBuilder $qb - * @param $userId + * @param string $userId * @param string $circleUniqueId * @param $type * @param $name @@ -100,7 +100,8 @@ class CirclesRequestBuilder extends CoreRequestBuilder { * @throws ConfigNoCircleAvailableException */ protected function limitRegardingCircleType( - IQueryBuilder &$qb, $userId, $circleUniqueId, $type, $name, $forceAll = false + IQueryBuilder &$qb, string $userId, $circleUniqueId, int $type, + string $name, bool $forceAll = false ) { $orTypes = $this->generateLimit($qb, $circleUniqueId, $userId, $type, $name, $forceAll); if (sizeof($orTypes) === 0) { @@ -160,6 +161,7 @@ class CirclesRequestBuilder extends CoreRequestBuilder { $andX = $expr->andX(); $andX->add($expr->eq('c.type', $qb->createNamedParameter(Circle::CIRCLES_PERSONAL))); + $andX->add($expr->eq('o.instance', $qb->createNamedParameter(''))); if (!$forceAll) { $andX->add($expr->eq('o.user_id', $qb->createNamedParameter((string)$userId))); @@ -252,8 +254,9 @@ class CirclesRequestBuilder extends CoreRequestBuilder { * * @param IQueryBuilder $qb * @param string $userId + * @param string $instanceId */ - public function leftJoinUserIdAsViewer(IQueryBuilder &$qb, $userId) { + public function leftJoinUserIdAsViewer(IQueryBuilder &$qb, string $userId, string $instanceId) { if ($qb->getType() !== QueryBuilder::SELECT) { return; @@ -264,6 +267,7 @@ class CirclesRequestBuilder extends CoreRequestBuilder { /** @noinspection PhpMethodParametersCountMismatchInspection */ $qb->selectAlias('u.user_id', 'viewer_userid') + ->selectAlias('u.instance', 'viewer_instance') ->selectAlias('u.status', 'viewer_status') ->selectAlias('u.member_id', 'viewer_member_id') ->selectAlias('u.level', 'viewer_level') @@ -278,6 +282,7 @@ class CirclesRequestBuilder extends CoreRequestBuilder { ) ), $expr->eq('u.user_id', $qb->createNamedParameter($userId)), + $expr->eq('u.instance', $qb->createNamedParameter($instanceId)), $expr->eq('u.user_type', $qb->createNamedParameter(Member::TYPE_USER)) ) ); @@ -301,6 +306,7 @@ class CirclesRequestBuilder extends CoreRequestBuilder { /** @noinspection PhpMethodParametersCountMismatchInspection */ $qb->selectAlias('o.user_id', 'owner_userid') + ->selectAlias('o.instance', 'owner_instance') ->selectAlias('o.status', 'owner_status') ->selectAlias('o.level', 'owner_level') ->leftJoin( @@ -427,6 +433,7 @@ class CirclesRequestBuilder extends CoreRequestBuilder { $user = new Member($data['viewer_userid'], Member::TYPE_USER, $circle->getUniqueId()); $user->setStatus($data['viewer_status']); $user->setMemberId($data['viewer_member_id']); + $user->setInstance($data['viewer_instance']); $user->setLevel($data['viewer_level']); $circle->setViewer($user); } @@ -434,6 +441,7 @@ class CirclesRequestBuilder extends CoreRequestBuilder { if (key_exists('owner_level', $data)) { $owner = new Member($data['owner_userid'], Member::TYPE_USER, $circle->getUniqueId()); $owner->setStatus($data['owner_status']); + $owner->setInstance($data['owner_instance']); $owner->setLevel($data['owner_level']); $circle->setOwner($owner); } diff --git a/lib/Db/CoreRequestBuilder.php b/lib/Db/CoreRequestBuilder.php index 65309f67..33846a0a 100644 --- a/lib/Db/CoreRequestBuilder.php +++ b/lib/Db/CoreRequestBuilder.php @@ -30,9 +30,26 @@ class CoreRequestBuilder { const TABLE_SHARES = 'circles_shares'; const TABLE_LINKS = 'circles_links'; const TABLE_TOKENS = 'circles_tokens'; + const TABLE_GSEVENTS = 'circles_gsevents'; + const TABLE_GSSHARES = 'circles_gsshares'; + const TABLE_GSSHARES_MOUNTPOINT = 'circles_gsshares_mp'; const NC_TABLE_GROUP_USER = 'group_user'; + /** @var array */ + private $tables = [ + self::TABLE_CIRCLES, + self::TABLE_MEMBERS, + self::TABLE_GROUPS, + self::TABLE_SHARES, + self::TABLE_LINKS, + self::TABLE_TOKENS, + self::TABLE_GSEVENTS, + self::TABLE_GSSHARES, + self::TABLE_GSSHARES_MOUNTPOINT + ]; + + /** @var IDBConnection */ protected $dbConnection; @@ -153,6 +170,17 @@ class CoreRequestBuilder { /** + * Limit the request to the owner + * + * @param IQueryBuilder $qb + * @param $owner + */ + protected function limitToOwner(IQueryBuilder &$qb, $owner) { + $this->limitToDBField($qb, 'owner', $owner); + } + + + /** * Limit the request to the Member by its Id. * * @param IQueryBuilder $qb @@ -175,6 +203,17 @@ class CoreRequestBuilder { /** + * Limit the request to the Instance. + * + * @param IQueryBuilder $qb + * @param string $instance + */ + protected function limitToInstance(IQueryBuilder &$qb, string $instance) { + $this->limitToDBField($qb, 'instance', $instance); + } + + + /** * Limit the request to the Circle by its Id. * * @param IQueryBuilder $qb @@ -197,6 +236,27 @@ class CoreRequestBuilder { /** + * Limit the request to the Circle by its Id. + * + * @param IQueryBuilder $qb + * @param string $mountpoint + */ + protected function limitToMountpoint(IQueryBuilder &$qb, string $mountpoint) { + $this->limitToDBField($qb, 'share_id', $mountpoint); + } + + /** + * Limit the request to the Circle by its Id. + * + * @param IQueryBuilder $qb + * @param string $hash + */ + protected function limitToMountpointHash(IQueryBuilder &$qb, string $hash) { + $this->limitToDBField($qb, 'share_id', $hash); + } + + + /** * Limit the request to the Circle by its Shorten Unique Id. * * @param IQueryBuilder $qb @@ -473,6 +533,25 @@ class CoreRequestBuilder { $this->leftJoinedNCGroupAndUser = true; } + + + /** + * + */ + public function cleanDatabase(): void { + foreach ($this->tables as $table) { + $qb = $this->dbConnection->getQueryBuilder(); + $qb->delete($table); + $qb->execute(); + } + + $qb = $this->dbConnection->getQueryBuilder(); + $expr = $qb->expr(); + $qb->delete(self::TABLE_FILE_SHARES); + $qb->where($expr->eq('share_type', $qb->createNamedParameter(self::SHARE_TYPE))); + $qb->execute(); + } + } diff --git a/lib/Db/GSEventsRequest.php b/lib/Db/GSEventsRequest.php new file mode 100644 index 00000000..13e6a1e1 --- /dev/null +++ b/lib/Db/GSEventsRequest.php @@ -0,0 +1,100 @@ +<?php +/** + * 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 2017 + * @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\Db; + + +use OCA\Circles\Exceptions\JsonException; +use OCA\Circles\Exceptions\ModelException; +use OCA\Circles\Model\GlobalScale\GSWrapper; + + +/** + * Class GSEventsRequest + * + * @package OCA\Circles\Db + */ +class GSEventsRequest extends GSEventsRequestBuilder { + + + /** + * @param GSWrapper $wrapper + * + * @return GSWrapper + */ + public function create(GSWrapper $wrapper): GSWrapper { + $qb = $this->getGSEventsInsertSql(); + $qb->setValue('token', $qb->createNamedParameter($wrapper->getToken())) + ->setValue('event', $qb->createNamedParameter(json_encode($wrapper->getEvent()))) + ->setValue('instance', $qb->createNamedParameter($wrapper->getInstance())) + ->setValue('severity', $qb->createNamedParameter($wrapper->getSeverity())) + ->setValue('status', $qb->createNamedParameter($wrapper->getStatus())) + ->setValue('creation', $qb->createNamedParameter($wrapper->getCreation())); + + $qb->execute(); + + return $wrapper; + } + + /** + * @param GSWrapper $wrapper + */ + public function update(GSWrapper $wrapper): void { + $qb = $this->getGSEventsUpdateSql(); + $qb->set('event', $qb->createNamedParameter(json_encode($wrapper->getEvent()))) + ->set('status', $qb->createNamedParameter($wrapper->getStatus())); + + $this->limitToInstance($qb, $wrapper->getInstance()); + $this->limitToToken($qb, $wrapper->getToken()); + + $qb->execute(); + } + + + /** + * @param string $token + * + * @return GSWrapper[] + * @throws JsonException + * @throws ModelException + */ + public function getByToken(string $token): array { + $qb = $this->getGSEventsSelectSql(); + $this->limitToToken($qb, $token); + + $wrappers = []; + $cursor = $qb->execute(); + while ($data = $cursor->fetch()) { + $wrappers[] = $this->parseGSEventsSelectSql($data); + } + $cursor->closeCursor(); + + return $wrappers; + } + +} + diff --git a/lib/Db/GSEventsRequestBuilder.php b/lib/Db/GSEventsRequestBuilder.php new file mode 100644 index 00000000..7297ed6c --- /dev/null +++ b/lib/Db/GSEventsRequestBuilder.php @@ -0,0 +1,115 @@ +<?php + +/** + * 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 2017 + * @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\Db; + + +use OCA\Circles\Exceptions\JsonException; +use OCA\Circles\Exceptions\ModelException; +use OCA\Circles\Model\GlobalScale\GSWrapper; +use OCP\DB\QueryBuilder\IQueryBuilder; + + +/** + * Class GSEventsRequestBuilder + * + * @package OCA\Circles\Db + */ +class GSEventsRequestBuilder extends CoreRequestBuilder { + + + /** + * Base of the Sql Insert request for Shares + * + * @return IQueryBuilder + */ + protected function getGSEventsInsertSql() { + $qb = $this->dbConnection->getQueryBuilder(); + $qb->insert(self::TABLE_GSEVENTS); + + return $qb; + } + + + /** + * Base of the Sql Update request for Groups + * + * @return IQueryBuilder + */ + protected function getGSEventsUpdateSql() { + $qb = $this->dbConnection->getQueryBuilder(); + $qb->update(self::TABLE_GSEVENTS); + + return $qb; + } + + + /** + * @return IQueryBuilder + */ + protected function getGSEventsSelectSql() { + $qb = $this->dbConnection->getQueryBuilder(); + + /** @noinspection PhpMethodParametersCountMismatchInspection */ + $qb->select('gse.token', 'gse.event', 'gse.instance', 'gse.severity', 'gse.status', 'gse.creation') + ->from(self::TABLE_GSEVENTS, 'gse'); + + $this->default_select_alias = 'gse'; + + return $qb; + } + + + /** + * Base of the Sql Delete request + * + * @return IQueryBuilder + */ + protected function getGSEventsDeleteSql() { + $qb = $this->dbConnection->getQueryBuilder(); + $qb->delete(self::TABLE_GSEVENTS); + + return $qb; + } + + + /** + * @param array $data + * + * @return GSWrapper + * @throws JsonException + * @throws ModelException + */ + protected function parseGSEventsSelectSql($data): GSWrapper { + $wrapper = new GSWrapper(); + $wrapper->import($data); + + return $wrapper; + } + +} diff --git a/lib/Db/GSSharesRequest.php b/lib/Db/GSSharesRequest.php new file mode 100644 index 00000000..278117e8 --- /dev/null +++ b/lib/Db/GSSharesRequest.php @@ -0,0 +1,225 @@ +<?php +/** + * 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 2017 + * @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\Db; + + +use daita\MySmallPhpTools\Traits\TStringTools; +use OCA\Circles\Model\GlobalScale\GSShare; +use OCA\Circles\Model\GlobalScale\GSShareMountpoint; +use OCA\Circles\Model\Member; +use OCP\DB\QueryBuilder\IQueryBuilder; +use OCP\Share\Exceptions\ShareNotFound; + + +/** + * Class GSSharesRequest + * + * @package OCA\Circles\Db + */ +class GSSharesRequest extends GSSharesRequestBuilder { + + + use TStringTools; + + + /** + * @param GSShare $gsShare + */ + public function create(GSShare $gsShare): void { + $hash = $this->token(); + $qb = $this->getGSSharesInsertSql(); + $qb->setValue('circle_id', $qb->createNamedParameter($gsShare->getCircleId())) + ->setValue('owner', $qb->createNamedParameter($gsShare->getOwner())) + ->setValue('instance', $qb->createNamedParameter($gsShare->getInstance())) + ->setValue('token', $qb->createNamedParameter($gsShare->getToken())) + ->setValue('parent', $qb->createNamedParameter($gsShare->getParent())) + ->setValue('mountpoint', $qb->createNamedParameter($gsShare->getMountPoint())) + ->setValue('mountpoint_hash', $qb->createNamedParameter($hash)); + $qb->execute(); + } + + + /** + * @param string $userId + * + * @return GSShare[] + */ + public function getForUser(string $userId): array { + $qb = $this->getGSSharesSelectSql(); + + $this->joinMembership($qb, $userId); + $this->leftJoinMountPoint($qb, $userId); + + $shares = []; + $cursor = $qb->execute(); + while ($data = $cursor->fetch()) { + $shares[] = $this->parseGSSharesSelectSql($data); + } + $cursor->closeCursor(); + + return $shares; + } + + + /** + * @param Member $member + */ + public function removeGSSharesFromMember(Member $member) { + $qb = $this->getGSSharesDeleteSql(); + $this->limitToCircleId($qb, $member->getCircleId()); + $this->limitToInstance($qb, $member->getInstance()); + $this->limitToOwner($qb, $member->getUserId()); + + $qb->execute(); + } + + + /** + * @param IQueryBuilder $qb + * @param string $userId + */ + private function joinMembership(IQueryBuilder $qb, string $userId) { + $qb->from(CoreRequestBuilder::TABLE_MEMBERS, 'm'); + + $expr = $qb->expr(); + $andX = $expr->andX(); + + $andX->add($expr->eq('m.user_id', $qb->createNamedParameter($userId))); + $andX->add($expr->eq('m.instance', $qb->createNamedParameter(''))); + $andX->add($expr->gt('m.level', $qb->createNamedParameter(0))); + $andX->add($expr->eq('m.user_type', $qb->createNamedParameter(Member::TYPE_USER))); + $andX->add($expr->eq('m.circle_id', 'gsh.circle_id')); + + $qb->andWhere($andX); + } + + + private function leftJoinMountPoint(IQueryBuilder $qb, string $userId) { + $expr = $qb->expr(); + $pf = '' . $this->default_select_alias . '.'; + + $on = $expr->andX(); + $on->add($expr->eq('mp.user_id', $qb->createNamedParameter($userId))); + $on->add($expr->eq('mp.share_id', $pf . 'id')); + + /** @noinspection PhpMethodParametersCountMismatchInspection */ + $qb->selectAlias('mp.mountPoint', 'gsshares_mountpoint') + ->leftJoin($this->default_select_alias, CoreRequestBuilder::TABLE_GSSHARES_MOUNTPOINT, 'mp', $on); + } + + + /** + * @param string $userId + * @param string $target + * + * @return GSShareMountpoint + * @throws ShareNotFound + */ + public function getShareMountPointByPath(string $userId, string $target): GSShareMountpoint { + $qb = $this->getGSSharesMountpointSelectSql(); + + $targetHash = md5($target); + $this->limitToUserId($qb, $userId); + $this->limitToMountpointHash($qb, $targetHash); + + $shares = []; + $cursor = $qb->execute(); + $data = $cursor->fetch(); + + if ($data === false) { + throw new ShareNotFound(); + } + + return $this->parseGSSharesMountpointSelectSql($data); + } + + + /** + * @param int $gsShareId + * @param string $userId + * + * @return GSShareMountpoint + * @throws ShareNotFound + */ + public function getShareMountPointById(int $gsShareId, string $userId): GSShareMountpoint { + $qb = $this->getGSSharesMountpointSelectSql(); + + $this->limitToShareId($qb, $gsShareId); + $this->limitToUserId($qb, $userId); + + $shares = []; + $cursor = $qb->execute(); + $data = $cursor->fetch(); + if ($data === false) { + throw new ShareNotFound(); + } + + return $this->parseGSSharesMountpointSelectSql($data); + } + + + /** + * @param GSShareMountpoint $mountpoint + */ + public function generateShareMountPoint(GSShareMountpoint $mountpoint) { + $qb = $this->getGSSharesMountpointInsertSql(); + + $hash = ($mountpoint->getMountPoint() === '-') ? '' : md5($mountpoint->getMountPoint()); + + $qb->setValue('user_id', $qb->createNamedParameter($mountpoint->getUserId())) + ->setValue('share_id', $qb->createNamedParameter($mountpoint->getShareId())) + ->setValue('mountpoint', $qb->createNamedParameter($mountpoint->getMountPoint())) + ->setValue('mountpoint_hash', $qb->createNamedParameter($hash)); + $qb->execute(); + } + + + /** + * @param GSShareMountpoint $mountpoint + * + * @return bool + */ + public function updateShareMountPoint(GSShareMountpoint $mountpoint) { + $qb = $this->getGSSharesMountpointUpdateSql(); + + $hash = ($mountpoint->getMountPoint() === '-') ? '' : md5($mountpoint->getMountPoint()); + + $qb->set('mountpoint', $qb->createNamedParameter($mountpoint->getMountPoint())) + ->set('mountpoint_hash', $qb->createNamedParameter($hash)); + + $this->limitToShareId($qb, $mountpoint->getShareId()); + $this->limitToUserId($qb, $mountpoint->getUserId()); + $nb = $qb->execute(); + + return ($nb === 1); + } + + + +} + diff --git a/lib/Db/GSSharesRequestBuilder.php b/lib/Db/GSSharesRequestBuilder.php new file mode 100644 index 00000000..b7d42d30 --- /dev/null +++ b/lib/Db/GSSharesRequestBuilder.php @@ -0,0 +1,183 @@ +<?php + +/** + * 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 2017 + * @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\Db; + + +use OCA\Circles\Model\GlobalScale\GSShare; +use OCA\Circles\Model\GlobalScale\GSShareMountpoint; +use OCP\DB\QueryBuilder\IQueryBuilder; + + +/** + * Class GSSharesRequestBuilder + * + * @package OCA\Circles\Db + */ +class GSSharesRequestBuilder extends CoreRequestBuilder { + + + /** + * Base of the Sql Insert request for Shares + * + * @return IQueryBuilder + */ + protected function getGSSharesInsertSql() { + $qb = $this->dbConnection->getQueryBuilder(); + $qb->insert(self::TABLE_GSSHARES); + + return $qb; + } + + + /** + * Base of the Sql Insert request for Shares Mountpoint + * + * @return IQueryBuilder + */ + protected function getGSSharesMountpointInsertSql() { + $qb = $this->dbConnection->getQueryBuilder(); + $qb->insert(self::TABLE_GSSHARES_MOUNTPOINT); + + return $qb; + } + + + /** + * Base of the Sql Update request + * + * @return IQueryBuilder + */ + protected function getGSSharesUpdateSql() { + $qb = $this->dbConnection->getQueryBuilder(); + $qb->update(self::TABLE_GSSHARES); + + return $qb; + } + + + /** + * @return IQueryBuilder + */ + protected function getGSSharesMountpointUpdateSql() { + $qb = $this->dbConnection->getQueryBuilder(); + $qb->update(self::TABLE_GSSHARES_MOUNTPOINT); + + return $qb; + } + + + /** + * @return IQueryBuilder + */ + protected function getGSSharesSelectSql() { + $qb = $this->dbConnection->getQueryBuilder(); + + /** @noinspection PhpMethodParametersCountMismatchInspection */ + $qb->select( + 'gsh.id', 'gsh.circle_id', 'gsh.owner', 'gsh.instance', 'gsh.token', 'gsh.parent', + 'gsh.mountpoint', 'gsh.mountpoint_hash' + ) + ->from(self::TABLE_GSSHARES, 'gsh'); + + $this->default_select_alias = 'gsh'; + + return $qb; + } + + + /** + * @return IQueryBuilder + */ + protected function getGSSharesMountpointSelectSql() { + $qb = $this->dbConnection->getQueryBuilder(); + + /** @noinspection PhpMethodParametersCountMismatchInspection */ + $qb->select( + 'gsmp.user_id', 'gsmp.share_id', 'gsmp.mountpoint', 'gsmp.mountpoint_hash' + ) + ->from(self::TABLE_GSSHARES_MOUNTPOINT, 'gsmp'); + + $this->default_select_alias = 'gsmp'; + + return $qb; + } + + + /** + * Base of the Sql Delete request + * + * @return IQueryBuilder + */ + protected function getGSSharesDeleteSql() { + $qb = $this->dbConnection->getQueryBuilder(); + $qb->delete(self::TABLE_GSSHARES); + + return $qb; + } + + + /** + * Base of the Sql Delete request + * + * @return IQueryBuilder + */ + protected function getGSSharesMountpointDeleteSql() { + $qb = $this->dbConnection->getQueryBuilder(); + $qb->delete(self::TABLE_GSSHARES_MOUNTPOINT); + + return $qb; + } + + + /** + * @param array $data + * + * @return GSShare + */ + protected function parseGSSharesSelectSql($data): GSShare { + $share = new GSShare(); + $share->importFromDatabase($data); + + return $share; + } + + + /** + * @param array $data + * + * @return GSShareMountpoint + */ + protected function parseGSSharesMountpointSelectSql($data): GSShareMountpoint { + $share = new GSShareMountpoint(); + $share->importFromDatabase($data); + + return $share; + } + +} diff --git a/lib/Db/MembersRequest.php b/lib/Db/MembersRequest.php index 54bd6961..f07fdcc7 100644 --- a/lib/Db/MembersRequest.php +++ b/lib/Db/MembersRequest.php @@ -51,14 +51,21 @@ class MembersRequest extends MembersRequestBuilder { * @param string $userId * @param $type * + * @param string $instance + * * @return Member * @throws MemberDoesNotExistException */ - public function forceGetMember($circleUniqueId, $userId, $type) { + public function forceGetMember($circleUniqueId, $userId, $type, string $instance = '') { $qb = $this->getMembersSelectSql(); + if ($instance === $this->configService->getLocalCloudId()) { + $instance = ''; + } + $this->limitToUserId($qb, $userId); $this->limitToUserType($qb, $type); + $this->limitToInstance($qb, $instance); $this->limitToCircleId($qb, $circleUniqueId); $cursor = $qb->execute(); @@ -187,7 +194,9 @@ class MembersRequest extends MembersRequestBuilder { * @param bool $check */ public function checkMember(Member $member, bool $check) { - $qb = $this->getMembersUpdateSql($member->getCircleId(), $member); + $qb = $this->getMembersUpdateSql( + $member->getCircleId(), $member->getUserId(), $member->getInstance(), $member->getType() + ); $qb->set('contact_checked', $qb->createNamedParameter(($check) ? 1 : 0)); $qb->execute(); @@ -321,21 +330,19 @@ class MembersRequest extends MembersRequestBuilder { * * @param string $circleUniqueId * @param string $name - * @param $type + * @param int $type + * + * @param string $instance * * @return Member - * @throws MemberAlreadyExistsException - * @throws \Exception */ - public function getFreshNewMember($circleUniqueId, $name, $type) { + public function getFreshNewMember($circleUniqueId, string $name, int $type, string $instance) { try { - - $member = $this->forceGetMember($circleUniqueId, $name, $type); - + $member = $this->forceGetMember($circleUniqueId, $name, $type, $instance); } catch (MemberDoesNotExistException $e) { $member = new Member($name, $type, $circleUniqueId); - $this->createMember($member); + $member->setInstance($instance); } // if ($member->alreadyExistOrJoining()) { @@ -454,12 +461,18 @@ class MembersRequest extends MembersRequestBuilder { $member->setMemberId($this->token(14)); } + $instance = $member->getInstance(); + if ($instance === $this->configService->getLocalCloudId()) { + $instance = ''; + } + try { $qb = $this->getMembersInsertSql(); $qb->setValue('circle_id', $qb->createNamedParameter($member->getCircleId())) ->setValue('user_id', $qb->createNamedParameter($member->getUserId())) ->setValue('member_id', $qb->createNamedParameter($member->getMemberId())) ->setValue('user_type', $qb->createNamedParameter($member->getType())) + ->setValue('instance', $qb->createNamedParameter($instance)) ->setValue('level', $qb->createNamedParameter($member->getLevel())) ->setValue('status', $qb->createNamedParameter($member->getStatus())) ->setValue('contact_id', $qb->createNamedParameter($member->getContactId())) @@ -535,7 +548,14 @@ class MembersRequest extends MembersRequestBuilder { * @param Member $member */ public function updateMember(Member $member) { - $qb = $this->getMembersUpdateSql($member->getCircleId(), $member); + $instance = $member->getInstance(); + if ($instance === $this->configService->getLocalCloudId()) { + $instance = ''; + } + + $qb = $this->getMembersUpdateSql( + $member->getCircleId(), $member->getUserId(), $instance, $member->getType() + ); $qb->set('level', $qb->createNamedParameter($member->getLevel())) ->set('status', $qb->createNamedParameter($member->getStatus())); @@ -548,7 +568,9 @@ class MembersRequest extends MembersRequestBuilder { * @param Member $member */ public function updateContactMeta(Member $member) { - $qb = $this->getMembersUpdateSql($member->getCircleId(), $member); + $qb = $this->getMembersUpdateSql( + $member->getCircleId(), $member->getUserId(), $member->getInstance(), $member->getType() + ); $qb->set('contact_meta', $qb->createNamedParameter(json_encode($member->getContactMeta()))); $qb->execute(); @@ -604,9 +626,15 @@ class MembersRequest extends MembersRequestBuilder { * @param Member $member */ public function removeMember(Member $member) { + $instance = $member->getInstance(); + if ($instance === $this->configService->getLocalCloudId()) { + $instance = ''; + } + $qb = $this->getMembersDeleteSql(); $this->limitToCircleId($qb, $member->getCircleId()); $this->limitToUserId($qb, $member->getUserId()); + $this->limitToInstance($qb, $instance); $this->limitToUserType($qb, $member->getType()); if ($member->getContactId() !== '') { $this->limitToContactId($qb, $member->getContactId()); @@ -731,4 +759,3 @@ class MembersRequest extends MembersRequestBuilder { } - diff --git a/lib/Db/MembersRequestBuilder.php b/lib/Db/MembersRequestBuilder.php index 35d437a5..1e306ec5 100644 --- a/lib/Db/MembersRequestBuilder.php +++ b/lib/Db/MembersRequestBuilder.php @@ -81,8 +81,8 @@ class MembersRequestBuilder extends CoreRequestBuilder { /** @noinspection PhpMethodParametersCountMismatchInspection */ $qb->select( - 'm.user_id', 'm.user_type', 'm.circle_id', 'm.level', 'm.status', 'm.note', 'm.contact_id', - 'm.member_id', 'm.contact_meta', 'm.joined' + 'm.user_id', 'm.instance', 'm.user_type', 'm.circle_id', 'm.level', 'm.status', 'm.note', + 'm.contact_id', 'm.member_id', 'm.contact_meta', 'm.joined' ) ->from(self::TABLE_MEMBERS, 'm') ->orderBy('m.joined'); @@ -147,12 +147,14 @@ class MembersRequestBuilder extends CoreRequestBuilder { /** * Base of the Sql Updte request for Members * - * @param int $circleId - * @param Member $member + * @param string /$circleId + * @param string $userId + * @param string $instance + * @param int $type * * @return IQueryBuilder */ - protected function getMembersUpdateSql($circleId, Member $member) { + protected function getMembersUpdateSql(string $circleId, string $userId, string $instance, int $type) { $qb = $this->dbConnection->getQueryBuilder(); $expr = $qb->expr(); @@ -161,8 +163,9 @@ class MembersRequestBuilder extends CoreRequestBuilder { ->where( $expr->andX( $expr->eq('circle_id', $qb->createNamedParameter($circleId)), - $expr->eq('user_id', $qb->createNamedParameter($member->getUserId())), - $expr->eq('user_type', $qb->createNamedParameter($member->getType())) + $expr->eq('user_id', $qb->createNamedParameter($userId)), + $expr->eq('instance', $qb->createNamedParameter($instance)), + $expr->eq('user_type', $qb->createNamedParameter($type)) ) ); @@ -217,6 +220,7 @@ class MembersRequestBuilder extends CoreRequestBuilder { } $member->setLevel($data['level']); + $member->setInstance($data['instance']); $member->setStatus($data['status']); $member->setJoined($this->timezoneService->convertTimeForCurrentUser($data['joined'])); diff --git a/lib/Exceptions/GSKeyException.php b/lib/Exceptions/GSKeyException.php new file mode 100644 index 00000000..da4fa59b --- /dev/null +++ b/lib/Exceptions/GSKeyException.php @@ -0,0 +1,34 @@ +<?php +/** + * 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 2017 + * @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\Exceptions; + + +class GSKeyException extends \Exception { + +} + + diff --git a/lib/Exceptions/GSStatusException.php b/lib/Exceptions/GSStatusException.php new file mode 100644 index 00000000..0e3de475 --- /dev/null +++ b/lib/Exceptions/GSStatusException.php @@ -0,0 +1,34 @@ +<?php +/** + * 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 2017 + * @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\Exceptions; + + +class GSStatusException extends \Exception { + +} + + diff --git a/lib/Exceptions/GlobalScaleDSyncException.php b/lib/Exceptions/GlobalScaleDSyncException.php new file mode 100644 index 00000000..e485f212 --- /dev/null +++ b/lib/Exceptions/GlobalScaleDSyncException.php @@ -0,0 +1,32 @@ +<?php +/** + * 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 2017 + * @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\Exceptions; + +class GlobalScaleDSyncException extends \Exception { + +} + diff --git a/lib/Exceptions/GlobalScaleEventException.php b/lib/Exceptions/GlobalScaleEventException.php new file mode 100644 index 00000000..cf721d09 --- /dev/null +++ b/lib/Exceptions/GlobalScaleEventException.php @@ -0,0 +1,32 @@ +<?php +/** + * 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 2017 + * @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\Exceptions; + +class GlobalScaleEventException extends \Exception { + +} + diff --git a/lib/Exceptions/JsonException.php b/lib/Exceptions/JsonException.php new file mode 100644 index 00000000..2760a1c7 --- /dev/null +++ b/lib/Exceptions/JsonException.php @@ -0,0 +1,34 @@ +<?php +/** + * 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 2017 + * @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\Exceptions; + + +class JsonException extends \Exception { + +} + + diff --git a/lib/Exceptions/ModelException.php b/lib/Exceptions/ModelException.php new file mode 100644 index 00000000..ad64949e --- /dev/null +++ b/lib/Exceptions/ModelException.php @@ -0,0 +1,34 @@ +<?php +/** + * 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 2017 + * @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\Exceptions; + + +class ModelException extends \Exception { + +} + + diff --git a/lib/GlobalScale/AGlobalScaleEvent.php b/lib/GlobalScale/AGlobalScaleEvent.php new file mode 100644 index 00000000..432a8f15 --- /dev/null +++ b/lib/GlobalScale/AGlobalScaleEvent.php @@ -0,0 +1,265 @@ +<?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 2017 + * @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\GlobalScale; + + +use OCA\Circles\Db\CirclesRequest; +use OCA\Circles\Db\GSSharesRequest; +use OCA\Circles\Db\MembersRequest; +use OCA\Circles\Db\SharesRequest; +use OCA\Circles\Db\TokensRequest; +use OCA\Circles\Exceptions\CircleDoesNotExistException; +use OCA\Circles\Exceptions\ConfigNoCircleAvailableException; +use OCA\Circles\Exceptions\GlobalScaleDSyncException; +use OCA\Circles\Exceptions\GlobalScaleEventException; +use OCA\Circles\Model\Circle; +use OCA\Circles\Model\GlobalScale\GSEvent; +use OCA\Circles\Model\Member; +use OCA\Circles\Service\CirclesService; +use OCA\Circles\Service\ConfigService; +use OCA\Circles\Service\EventsService; +use OCA\Circles\Service\MembersService; +use OCA\Circles\Service\MiscService; +use OCP\Files\IRootFolder; +use OCP\IUserManager; + + +/** + * Class AGlobalScaleEvent + * + * @package OCA\Circles\GlobalScale + */ +abstract class AGlobalScaleEvent { + + + /** @var IRootFolder */ + protected $rootFolder; + + /** @var IUserManager */ + protected $userManager; + + /** @var SharesRequest */ + protected $sharesRequest; + + /** @var TokensRequest */ + protected $tokensRequest; + + /** @var CirclesRequest */ + protected $circlesRequest; + + /** @var MembersRequest */ + protected $membersRequest; + + /** @var GSSharesRequest */ + protected $gsSharesRequest; + + /** @var CirclesService */ + protected $circlesService; + + /** @var MembersService */ + protected $membersService; + + /** @var EventsService */ + protected $eventsService; + + /** @var ConfigService */ + protected $configService; + + /** @var MiscService */ + protected $miscService; + + + /** + * AGlobalScaleEvent constructor. + * + * @param IRootFolder $rootFolder + * @param IUserManager $userManager + * @param SharesRequest $sharesRequest + * @param TokensRequest $tokensRequest + * @param CirclesRequest $circlesRequest + * @param MembersRequest $membersRequest + * @param GSSharesRequest $gsSharesRequest + * @param CirclesService $circlesService + * @param MembersService $membersService + * @param EventsService $eventsService + * @param ConfigService $configService + * @param MiscService $miscService + */ + public function __construct( + IRootFolder $rootFolder, + IUserManager $userManager, + SharesRequest $sharesRequest, + TokensRequest $tokensRequest, + CirclesRequest $circlesRequest, + MembersRequest $membersRequest, + GSSharesRequest $gsSharesRequest, + CirclesService $circlesService, + MembersService $membersService, + EventsService $eventsService, + ConfigService $configService, + MiscService $miscService + ) { + $this->rootFolder = $rootFolder; + $this->userManager = $userManager; + $this->sharesRequest = $sharesRequest; + $this->tokensRequest = $tokensRequest; + $this->circlesRequest = $circlesRequest; + $this->membersRequest = $membersRequest; + $this->gsSharesRequest = $gsSharesRequest; + $this->circlesService = $circlesService; + $this->membersService = $membersService; + $this->eventsService = $eventsService; + $this->configService = $configService; + $this->miscService = $miscService; + } + + + /** + * @param GSEvent $event + * @param bool $localCheck + * + * @param bool $mustBeCheck + * + * @throws CircleDoesNotExistException + * @throws ConfigNoCircleAvailableException + * @throws GlobalScaleDSyncException + * @throws GlobalScaleEventException + */ + public function verify(GSEvent $event, bool $localCheck = false, bool $mustBeCheck = false): void { + if ($localCheck && !$event->isForced()) { + $this->checkViewer($event, $mustBeCheck); + } + } + + + /** + * @param GSEvent $event + */ + abstract public function manage(GSEvent $event): void; + + + /** + * @param GSEvent[] $events + */ + abstract public function result(array $events): void; + + + /** + * @param GSEvent $event + * @param bool $mustBeChecked + * + * @throws CircleDoesNotExistException + * @throws ConfigNoCircleAvailableException + * @throws GlobalScaleDSyncException + * @throws GlobalScaleEventException + */ + private function checkViewer(GSEvent $event, bool $mustBeChecked) { + if (!$event->hasCircle() + || !$event->getCircle() + ->hasViewer()) { + if ($mustBeChecked) { + throw new GlobalScaleEventException('GSEvent cannot be checked'); + } else { + return; + } + } + + $circle = $event->getCircle(); + $viewer = $circle->getViewer(); + $this->cleanMember($viewer); + + $localCircle = $this->circlesRequest->getCircle( + $circle->getUniqueId(), $viewer->getUserId(), $viewer->getInstance() + ); + + if (!$this->compareMembers($viewer, $localCircle->getViewer())) { + throw new GlobalScaleDSyncException('Viewer seems DSync'); + } + + $event->setCircle($localCircle); + } + + + /** + * @param Member $member1 + * @param Member $member2 + * + * @return bool + */ + protected function compareMembers(Member $member1, Member $member2) { + if ($member1->getInstance() === '') { + $member1->setInstance($this->configService->getLocalCloudId()); + } + + if ($member2->getInstance() === '') { + $member2->setInstance($this->configService->getLocalCloudId()); + } + + + if ($member1->getCircleId() !== $member2->getCircleId() + || $member1->getUserId() !== $member2->getUserId() + || $member1->getType() <> $member2->getType() + || $member1->getLevel() <> $member2->getLevel() + || $member1->getStatus() !== $member2->getStatus() + || $member1->getInstance() !== $member2->getInstance()) { + return false; + } + + return true; + } + + + /** + * @param Circle $circle1 + * @param Circle $circle2 + * + * @return bool + */ + protected function compareCircles(Circle $circle1, Circle $circle2): bool { + if ($circle1->getName() !== $circle2->getName() + || $circle1->getDescription() !== $circle2->getDescription() + || $circle1->getSettings(true) !== $circle2->getSettings(true) + || $circle1->getType() !== $circle2->getType() + || $circle1->getUniqueId() !== $circle2->getUniqueId()) { + return false; + } + + return true; + } + + + protected function cleanMember(Member $member) { + if ($member->getInstance() === $this->configService->getLocalCloudId()) { + $member->setInstance(''); + } + } + +} + diff --git a/lib/GlobalScale/CircleCreate.php b/lib/GlobalScale/CircleCreate.php new file mode 100644 index 00000000..8314032a --- /dev/null +++ b/lib/GlobalScale/CircleCreate.php @@ -0,0 +1,88 @@ +<?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 2017 + * @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\GlobalScale; + + +use OCA\Circles\Exceptions\CircleAlreadyExistsException; +use OCA\Circles\Exceptions\MemberAlreadyExistsException; +use OCA\Circles\Model\GlobalScale\GSEvent; + + +/** + * Class CircleCreate + * + * @package OCA\Circles\GlobalScale + */ +class CircleCreate extends AGlobalScaleEvent { + + + /** + * Circles are created on the original instance, so return false; + * + * @param GSEvent $event + * @param bool $localCheck + * @param bool $mustBeChecked + */ + public function verify(GSEvent $event, bool $localCheck = false, bool $mustBeChecked = false): void { + //parent::verify($event, $localCheck, $mustBeChecked); + } + + + /** + * @param GSEvent $event + * + * @throws CircleAlreadyExistsException + * @throws MemberAlreadyExistsException + */ + public function manage(GSEvent $event): void { + if (!$event->hasCircle()) { + return; + } + + $circle = $event->getCircle(); + $this->circlesRequest->createCircle($circle); + + $owner = $circle->getOwner(); + if ($owner->getInstance() === '') { + $owner->setInstance($event->getSource()); + } + $this->membersRequest->createMember($owner); + + $this->eventsService->onCircleCreation($circle); + } + + + /** + * @param GSEvent[] $events + */ + public function result(array $events): void { + } +} + diff --git a/lib/GlobalScale/CircleDestroy.php b/lib/GlobalScale/CircleDestroy.php new file mode 100644 index 00000000..5c0b76fc --- /dev/null +++ b/lib/GlobalScale/CircleDestroy.php @@ -0,0 +1,82 @@ +<?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 2017 + * @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\GlobalScale; + + +use OCA\Circles\Exceptions\CircleAlreadyExistsException; +use OCA\Circles\Exceptions\MemberAlreadyExistsException; +use OCA\Circles\Model\GlobalScale\GSEvent; +use OCA\Circles\Model\Member; + + +/** + * Class CircleDestroy + * + * @package OCA\Circles\GlobalScale + */ +class CircleDestroy extends AGlobalScaleEvent { + + + /** + * Circles are destroyed from the original instance, or by admin + * + * @param GSEvent $event + * @param bool $localCheck + * @param bool $mustBeChecked + */ + public function verify(GSEvent $event, bool $localCheck = false, bool $mustBeChecked = false): void { + //parent::verify($event, $localCheck, $mustBeChecked); + } + + + /** + * @param GSEvent $event + */ + public function manage(GSEvent $event): void { + if (!$event->hasCircle()) { + return; + } + + $circle = $event->getCircle(); + $this->eventsService->onCircleDestruction($circle); + + $this->membersRequest->removeAllFromCircle($circle->getUniqueId()); + $this->circlesRequest->destroyCircle($circle->getUniqueId()); + } + + + /** + * @param GSEvent[] $events + */ + public function result(array $events): void { + } + +} + diff --git a/lib/GlobalScale/CircleStatus.php b/lib/GlobalScale/CircleStatus.php new file mode 100644 index 00000000..8480d7be --- /dev/null +++ b/lib/GlobalScale/CircleStatus.php @@ -0,0 +1,96 @@ +<?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 2017 + * @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\GlobalScale; + + +use OCA\Circles\Exceptions\CircleDoesNotExistException; +use OCA\Circles\Model\GlobalScale\GSEvent; +use OCA\Circles\Model\Member; + + +/** + * Class CircleStatus + * + * @package OCA\Circles\GlobalScale + */ +class CircleStatus extends AGlobalScaleEvent { + + + const STATUS_ERROR = -1; + const STATUS_OK = 1; + const STATUS_NOT_OWNER = 8; + const STATUS_NOT_FOUND = 404; + + + /** + * @param GSEvent $event + * @param bool $localCheck + * @param bool $mustBeChecked + */ + public function verify(GSEvent $event, bool $localCheck = false, bool $mustBeChecked = false): void { + } + + + /** + * @param GSEvent $event + */ + public function manage(GSEvent $event): void { + $circle = $event->getCircle(); + $status = self::STATUS_ERROR; + + try { + $this->circlesRequest->forceGetCircle($circle->getUniqueId()); + $owners = $this->membersRequest->forceGetMembers($circle->getUniqueId(), Member::LEVEL_OWNER); + if (!empty($owners)) { + $owner = $owners[0]; + if ($owner->getInstance() === '') { + $status = self::STATUS_OK; + } else { + $status = self::STATUS_NOT_OWNER; + $event->getData() + ->sObj('supposedOwner', $owner); + } + } + } catch (CircleDoesNotExistException $e) { + $status = self::STATUS_NOT_FOUND; + } + + $event->getData() + ->sInt('status', $status); + } + + + /** + * @param GSEvent[] $events + */ + public function result(array $events): void { + } +} + diff --git a/lib/GlobalScale/CircleUpdate.php b/lib/GlobalScale/CircleUpdate.php new file mode 100644 index 00000000..86847bb7 --- /dev/null +++ b/lib/GlobalScale/CircleUpdate.php @@ -0,0 +1,111 @@ +<?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 2017 + * @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\GlobalScale; + + +use OCA\Circles\Exceptions\CircleAlreadyExistsException; +use OCA\Circles\Exceptions\CircleDoesNotExistException; +use OCA\Circles\Exceptions\ConfigNoCircleAvailableException; +use OCA\Circles\Exceptions\GlobalScaleDSyncException; +use OCA\Circles\Exceptions\GlobalScaleEventException; +use OCA\Circles\Model\GlobalScale\GSEvent; +use OCA\Circles\Model\Member; + + +/** + * Class CircleUpdate + * + * @package OCA\Circles\GlobalScale + */ +class CircleUpdate extends AGlobalScaleEvent { + + + /** + * @param GSEvent $event + * @param bool $localCheck + * @param bool $mustBeChecked + * + * @throws CircleDoesNotExistException + * @throws ConfigNoCircleAvailableException + * @throws GlobalScaleDSyncException + * @throws GlobalScaleEventException + */ + public function verify(GSEvent $event, bool $localCheck = false, bool $mustBeChecked = false): void { + parent::verify($event, $localCheck, true); + + $circle = $event->getCircle(); + $data = $event->getData(); + $viewer = $circle->getHigherViewer(); + if (!$data->gBool('local_admin') && $viewer->getLevel() !== Member::LEVEL_OWNER) { + throw new GlobalScaleDSyncException('Member is not Owner'); + } + } + + + /** + * @param GSEvent $event + * + * @throws CircleAlreadyExistsException + */ + public function manage(GSEvent $event): void { + if (!$event->hasCircle()) { + return; + } + + $circle = $event->getCircle(); + $settings = $event->getData() + ->gArray('settings'); + $ak = array_keys($settings); + foreach ($ak AS $k) { + $circle->setSetting($k, $settings[$k]); + } + + $owner = $circle->getHigherViewer(); + $this->circlesRequest->updateCircle($circle, $owner->getUserId()); + + $oldSettings = array_merge( + $circle->getSettings(), + [ + 'circle_name' => $circle->getName(), + 'circle_desc' => $circle->getDescription(), + ] + ); + + $this->eventsService->onSettingsChange($circle, $oldSettings); + } + + + /** + * @param GSEvent[] $events + */ + public function result(array $events): void { + } +} + diff --git a/lib/GlobalScale/FileShare.php b/lib/GlobalScale/FileShare.php new file mode 100644 index 00000000..da8b4bd5 --- /dev/null +++ b/lib/GlobalScale/FileShare.php @@ -0,0 +1,281 @@ +<?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 2017 + * @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\GlobalScale; + + +use daita\MySmallPhpTools\Model\SimpleDataStore; +use Exception; +use OC\Share20\Share; +use OCA\Circles\Exceptions\BroadcasterIsNotCompatibleException; +use OCA\Circles\Exceptions\CircleDoesNotExistException; +use OCA\Circles\Exceptions\GSStatusException; +use OCA\Circles\Exceptions\TokenDoesNotExistException; +use OCA\Circles\IBroadcaster; +use OCA\Circles\Model\Circle; +use OCA\Circles\Model\GlobalScale\GSEvent; +use OCA\Circles\Model\GlobalScale\GSShare; +use OCA\Circles\Model\Member; +use OCA\Circles\Model\SharingFrame; +use OCA\Circles\Service\ConfigService; +use OCA\Circles\Service\MiscService; +use OCP\AppFramework\QueryException; +use OCP\Files\NotFoundException; +use OCP\Share\Exceptions\IllegalIDChangeException; +use OCP\Share\Exceptions\ShareNotFound; +use OCP\Share\IShare; + + +/** + * Class FileShare + * + * @package OCA\Circles\GlobalScale + */ +class FileShare extends AGlobalScaleEvent { + + + /** + * @param GSEvent $event + * @param bool $localCheck + * @param bool $mustBeChecked + * + * @throws GSStatusException + * @throws TokenDoesNotExistException + */ + public function verify(GSEvent $event, bool $localCheck = false, bool $mustBeChecked = false): void { + // TODO: might be a bad idea, all process of the sharing should be managed from here. + // Even if we are not in a GS setup. + // The reason is that if a mail needs to be send, all mail address associated to the circle needs to be retrieved + if (!$this->configService->getGSStatus(ConfigService::GS_ENABLED)) { + return; + } + \OC::$server->getLogger()->log(3, '### 0'); + // if event/file is local, we generate a federate share for the same circle on other instances + if ($event->getSource() === $this->configService->getLocalCloudId()) { + $circle = $event->getCircle(); + + \OC::$server->getLogger()->log(3, '### 1'); + + try { + $share = $this->getShareFromData($event->getData()); + } catch (Exception $e) { + return; + } + + \OC::$server->getLogger()->log(3, '### 2'); + + + $this->miscService->log('### ' . json_encode($event->getData())); + + try { + $node = $share->getNode(); + $filename = $node->getName(); + } catch (NotFoundException $e) { + $filename = '/testTest.md'; + } + + $event->getData() + ->s('gs_federated', $share->getToken()) + ->s('gs_filename', '/' . $filename); + } + + } + + + /** + * @param GSEvent $event + * + * @throws GSStatusException + */ + public function manage(GSEvent $event): void { + // TODO: might be a bad idea, all process of the sharing should be managed from here. + // Even if we are not in a GS setup. + // The reason is that if a mail needs to be send, all mail address associated to the circle needs to be retrieved + if (!$this->configService->getGSStatus(ConfigService::GS_ENABLED)) { + return; + } + + // if event is not local, we create a federated file to the right instance of Nextcloud, using the right token + if ($event->getSource() !== $this->configService->getLocalCloudId()) { + try { + $share = $this->getShareFromData($event->getData()); + } catch (Exception $e) { + return; + } + + $data = $event->getData(); + $token = $data->g('gs_federated'); + $filename = $data->g('gs_filename'); + + $gsShare = new GSShare($share->getSharedWith(), $token); + $gsShare->setOwner($share->getShareOwner()); + $gsShare->setInstance($event->getSource()); + $gsShare->setParent(-1); + $gsShare->setMountPoint($filename); + + $this->gsSharesRequest->create($gsShare); + } + + } + + + /** + * @param GSEvent[] $events + */ + public function result(array $events): void { + } + + + /** + * @param GSEvent $event + * + * @throws BroadcasterIsNotCompatibleException + * @throws GSStatusException + */ + private function generateFederatedShare(GSEvent $event) { + $data = $event->getData(); + $frame = SharingFrame::fromJSON(json_encode($data->gAll())); + + try { + $broadcaster = \OC::$server->query((string)$frame->getHeader('broadcast')); + if (!($broadcaster instanceof IBroadcaster)) { + throw new BroadcasterIsNotCompatibleException(); + } + + $frameCircle = $frame->getCircle(); + $circle = $this->circlesRequest->forceGetCircle($frameCircle->getUniqueId()); + } catch (QueryException | CircleDoesNotExistException $e) { + return; + } + + $this->feedBroadcaster($broadcaster, $frame, $circle); + } + + + /** + * @param IBroadcaster $broadcaster + * @param SharingFrame $frame + * @param Circle $circle + */ + private function feedBroadcaster(IBroadcaster $broadcaster, SharingFrame $frame, Circle $circle) { + $broadcaster->init(); + + if ($circle->getType() !== Circle::CIRCLES_PERSONAL) { + $broadcaster->createShareToCircle($frame, $circle); + } + + $members = + $this->membersRequest->forceGetMembers($circle->getUniqueId(), Member::LEVEL_MEMBER, true); + foreach ($members AS $member) { + $this->parseMember($member); + + if ($member->isBroadcasting()) { + $broadcaster->createShareToMember($frame, $member); + } + + if ($member->getInstance() !== '') { + $this->miscService->log('#### GENERATE FEDERATED CIRCLES SHARE ' . $member->getInstance()); + } + } + } + + + /** + * @param Member $member + */ + private function parseMember(Member &$member) { + $this->parseMemberFromContact($member); + } + + + /** + * on Type Contact, we convert the type to MAIL and retrieve the first mail of the list. + * If no email, we set the member as not broadcasting. + * + * @param Member $member + */ + private function parseMemberFromContact(Member &$member) { + if ($member->getType() !== Member::TYPE_CONTACT) { + return; + } + + $contact = MiscService::getContactData($member->getUserId()); + if (!key_exists('EMAIL', $contact)) { + $member->broadcasting(false); + + return; + } + + $member->setType(Member::TYPE_MAIL); + $member->setUserId(array_shift($contact['EMAIL'])); + } + + + /** + * @param SimpleDataStore $data + * + * @return IShare + * @throws ShareNotFound + * @throws IllegalIDChangeException + */ + private function getShareFromData(SimpleDataStore $data) { + $frame = SharingFrame::fromArray($data->gArray('frame')); + $payload = $frame->getPayload(); + if (!key_exists('share', $payload)) { + throw new ShareNotFound(); + } + + return $this->generateShare($payload['share']); + } + + + /** + * recreate the share from the JSON payload. + * + * @param array $data + * + * @return IShare + * @throws IllegalIDChangeException + */ + private function generateShare($data): IShare { + $share = new Share($this->rootFolder, $this->userManager); + $share->setId($data['id']); + $share->setSharedBy($data['sharedBy']); + $share->setSharedWith($data['sharedWith']); + $share->setNodeId($data['nodeId']); + $share->setShareOwner($data['shareOwner']); + $share->setPermissions($data['permissions']); + $share->setToken($data['token']); + $share->setPassword($data['password']); + + return $share; + } + + +} diff --git a/lib/GlobalScale/GSMount/Mount.php b/lib/GlobalScale/GSMount/Mount.php new file mode 100644 index 00000000..6c269768 --- /dev/null +++ b/lib/GlobalScale/GSMount/Mount.php @@ -0,0 +1,111 @@ +<?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 2020 + * @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\GlobalScale\GSMount; + + +use daita\MySmallPhpTools\Traits\TArrayTools; +use Exception; +use OC\Files\Mount\MountPoint; +use OC\Files\Mount\MoveableMount; + + +/** + * Class Mount + * + * @package OCA\Circles\GlobalScale\GSMount + */ +class Mount extends MountPoint implements MoveableMount { + + + use TArrayTools; + + + /** @var MountManager */ + protected $mountManager; + + /** @var int */ + private $gsShareId = -1; + + /** + * Mount constructor. + * + * @param $storage + * @param string $mountPoint + * @param array $options + * @param MountManager $manager + * @param null $loader + * + * @throws Exception + */ + public function __construct( + $storage, string $mountPoint, array $options, MountManager $manager, $loader = null + ) { + parent::__construct($storage, $mountPoint, $options, $loader); + $this->gsShareId = $this->getInt('gsShareId', $options); + $this->mountManager = $manager; + } + + + /** + * Move the mount point to $target + * + * @param string $target the target mount point + * + * @return bool + */ + public function moveMount($target) { + $result = $this->mountManager->renameShare($this->gsShareId, $target); + $this->setMountPoint($target); + + return $result; + } + + /** + * Remove the mount points + * + * @return mixed + * @return bool + */ + public function removeMount() { + return $this->mountManager->unshare($this->gsShareId); + } + + + /** + * Get the type of mount point, used to distinguish things like shares and external storages + * in the web interface + * + * @return string + */ + public function getMountType() { + return 'shared'; + } +} + diff --git a/lib/GlobalScale/GSMount/MountManager.php b/lib/GlobalScale/GSMount/MountManager.php new file mode 100644 index 00000000..92f88f41 --- /dev/null +++ b/lib/GlobalScale/GSMount/MountManager.php @@ -0,0 +1,118 @@ +<?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 2020 + * @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\GlobalScale\GSMount; + + +use OCA\Circles\Db\GSSharesRequest; +use OCA\Circles\Model\GlobalScale\GSShareMountpoint; +use OCP\Share\Exceptions\ShareNotFound; + + +/** + * Class MountManager + * + * @package OCA\Circles\GlobalScale\GSMount + */ +class MountManager { + + + /** @var string */ + private $userId; + + /** @var GSSharesRequest */ + private $gsSharesRequest; + + + /** + * MountManager constructor. + * + * @param string $userId + * @param GSSharesRequest $gsSharesRequest + */ + public function __construct($userId, GSSharesRequest $gsSharesRequest) { + $this->userId = $userId; + $this->gsSharesRequest = $gsSharesRequest; + } + + + /** + * @param int $gsShareId + * @param string $target + * + * @return bool + */ + public function renameShare(int $gsShareId, string $target) { + try { + if ($target !== '-') { + $target = $this->stripPath($target); + $this->gsSharesRequest->getShareMountPointByPath($this->userId, $target); + + return false; + } + } catch (ShareNotFound $e) { + } + + $mountPoint = new GSShareMountpoint($gsShareId, $this->userId, $target); + try { + $this->gsSharesRequest->getShareMountPointById($gsShareId, $this->userId); + $this->gsSharesRequest->updateShareMountPoint($mountPoint); + } catch (ShareNotFound $e) { + $this->gsSharesRequest->generateShareMountPoint($mountPoint); + } + + return true; + } + + + /** + * @param int $gsShareId + * + * @return bool + */ + public function unshare(int $gsShareId) { + return $this->renameShare($gsShareId, '-'); + } + + + /** + * remove '/user/files' from the path and trailing slashes + * + * @param string $path + * + * @return string + */ + protected function stripPath($path) { + $prefix = '/' . $this->userId . '/files'; + + return rtrim(substr($path, strlen($prefix)), '/'); + } + + +} diff --git a/lib/GlobalScale/GSMount/MountProvider.php b/lib/GlobalScale/GSMount/MountProvider.php new file mode 100644 index 00000000..85574f57 --- /dev/null +++ b/lib/GlobalScale/GSMount/MountProvider.php @@ -0,0 +1,131 @@ +<?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 2020 + * @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\GlobalScale\GSMount; + + +use Exception; +use OC; +use OCA\Circles\Db\GSSharesRequest; +use OCA\Circles\Model\GlobalScale\GSShare; +use OCP\AppFramework\QueryException; +use OCP\Federation\ICloudIdManager; +use OCP\Files\Config\IMountProvider; +use OCP\Files\Mount\IMountPoint; +use OCP\Files\Storage\IStorageFactory; +use OCP\IUser; + + +/** + * Class MountProvider + * + * @package OCA\Circles\GlobalScale\GSMount + */ +class MountProvider implements IMountProvider { + + + const STORAGE = '\OCA\Files_Sharing\External\Storage'; + + + /** @var MountManager */ + private $mountManager; + + /** @var ICloudIdManager */ + private $cloudIdManager; + + /** @var GSSharesRequest */ + private $gsSharesRequest; + + + /** + * MountProvider constructor. + * + * @param MountManager $mountManager + * @param ICloudIdManager $cloudIdManager + * @param GSSharesRequest $gsSharesRequest + */ + public function __construct( + MountManager $mountManager, ICloudIdManager $cloudIdManager, GSSharesRequest $gsSharesRequest + ) { + $this->mountManager = $mountManager; + $this->cloudIdManager = $cloudIdManager; + $this->gsSharesRequest = $gsSharesRequest; + } + + + /** + * @param IUser $user + * @param IStorageFactory $loader + * + * @return IMountPoint[] + * @throws QueryException + */ + public function getMountsForUser(IUser $user, IStorageFactory $loader): array { + $shares = $this->gsSharesRequest->getForUser($user->getUID()); + + $mounts = []; + foreach ($shares as $share) { + try { + if ($share->getMountPoint() !== '-') { + $mounts[] = $this->generateMount($share, $user->getUID(), $loader); + } + } catch (Exception $e) { + } + } + + return $mounts; + } + + + /** + * @param GSShare $share + * @param string $userId + * @param IStorageFactory $storageFactory + * + * @return Mount + * @throws Exception + */ + public function generateMount( + GSShare $share, string $userId, IStorageFactory $storageFactory + ) { + // might be better to test https first, then http. as this would only work from webclient ! + $data = $share->toMount($userId, $_SERVER['REQUEST_SCHEME']); + $data['manager'] = $this->mountManager; + $data['gsShareId'] = $share->getId(); + $data['cloudId'] = $this->cloudIdManager->getCloudId($data['owner'], $data['remote']); + $data['certificateManager'] = OC::$server->getCertificateManager($userId); + $data['HttpClientService'] = OC::$server->getHTTPClientService(); + + return new Mount( + self::STORAGE, $share->getMountPoint($userId), $data, $this->mountManager, $storageFactory + ); + } + +} + diff --git a/lib/GlobalScale/GlobalSync.php b/lib/GlobalScale/GlobalSync.php new file mode 100644 index 00000000..466ab61d --- /dev/null +++ b/lib/GlobalScale/GlobalSync.php @@ -0,0 +1,173 @@ +<?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 2017 + * @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\GlobalScale; + + +use OCA\Circles\Exceptions\CircleAlreadyExistsException; +use OCA\Circles\Exceptions\CircleDoesNotExistException; +use OCA\Circles\Exceptions\MemberAlreadyExistsException; +use OCA\Circles\Exceptions\MemberDoesNotExistException; +use OCA\Circles\Model\Circle; +use OCA\Circles\Model\GlobalScale\GSEvent; +use OCA\Circles\Model\Member; + + +/** + * Class MemberCreate + * + * @package OCA\Circles\GlobalScale + */ +class GlobalSync extends AGlobalScaleEvent { + + + /** + * @param GSEvent $event + * @param bool $localCheck + * @param bool $mustBeChecked + */ + public function verify(GSEvent $event, bool $localCheck = false, bool $mustBeChecked = false): void { + } + + + /** + * @param GSEvent $event + */ + public function manage(GSEvent $event): void { + $data = $event->getData(); + $circles = []; + foreach ($data->gAll() as $circle) { + $circle = Circle::fromArray($circle); + $circles[] = $circle; + + $this->syncCircle($circle, $event->getSource()); + $this->removeDeprecateMembers($circle, $event->getSource()); + } + + } + + + /** + * @param GSEvent[] $events + */ + public function result(array $events): void { + } + + + /** + * @param Circle $circle + * @param string $source + */ + private function syncCircle(Circle $circle, string $source): void { + try { + $knownCircle = $this->circlesRequest->forceGetCircle($circle->getUniqueId()); + + if (!$this->compareCircles($knownCircle, $circle)) { + try { + $this->circlesRequest->updateCircle($circle); + } catch (CircleAlreadyExistsException $e) { + } + } + } catch (CircleDoesNotExistException $e) { + try { + $this->circlesRequest->createCircle($circle); + } catch (CircleAlreadyExistsException $e) { + } + } + + foreach ($circle->getMembers() as $member) { + if ($member->getInstance() === '') { + $member->setInstance($source); + } + + try { + $knownMember = $this->membersRequest->forceGetMember( + $circle->getUniqueId(), $member->getUserId(), $member->getType(), $member->getInstance() + ); + + if ($this->compareMembers($knownMember, $member)) { + continue; + } + + $this->miscService->log( + 'updating member :' . json_encode($member) . ' from ' . json_encode($knownMember), 2 + ); + $this->membersRequest->updateMember($member); + } catch (MemberDoesNotExistException $e) { + try { + $this->miscService->log( + 'creating member :' . json_encode($member), 2 + ); + $this->membersRequest->createMember($member); + } catch (MemberAlreadyExistsException $e) { + } + } + } + + } + + + private function removeDeprecateMembers(Circle $circle, string $source): void { + $knownMembers = $this->membersRequest->forceGetMembers($circle->getUniqueId()); + + foreach ($knownMembers as $knownItem) { + try { + $this->getMember($knownItem, $circle->getMembers(), $source); + } catch (MemberDoesNotExistException $e) { + $this->miscService->log('removing deprecated member :' . json_encode($knownItem), 2); + $this->membersRequest->removeMember($knownItem); + $this->gsSharesRequest->removeGSSharesFromMember($knownItem); + } + } + } + + + /** + * @param Member $item + * @param Member[] $members + * @param string $source + * + * @throws MemberDoesNotExistException + */ + private function getMember(Member $item, array $members, string $source) { + foreach ($members as $member) { + if ($member->getInstance() === '') { + $member->setInstance($source); + } + + if ($this->compareMembers($member, $item)) { + return; + } + } + + throw new MemberDoesNotExistException(); + } + +} + diff --git a/lib/GlobalScale/MemberAdd.php b/lib/GlobalScale/MemberAdd.php new file mode 100644 index 00000000..5332dbba --- /dev/null +++ b/lib/GlobalScale/MemberAdd.php @@ -0,0 +1,137 @@ +<?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 2017 + * @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\GlobalScale; + + +use daita\MySmallPhpTools\Model\SimpleDataStore; +use OC\User\NoUserException; +use OCA\Circles\Exceptions\CircleDoesNotExistException; +use OCA\Circles\Exceptions\CircleTypeNotValidException; +use OCA\Circles\Exceptions\ConfigNoCircleAvailableException; +use OCA\Circles\Exceptions\EmailAccountInvalidFormatException; +use OCA\Circles\Exceptions\GlobalScaleDSyncException; +use OCA\Circles\Exceptions\GlobalScaleEventException; +use OCA\Circles\Exceptions\MemberAlreadyExistsException; +use OCA\Circles\Exceptions\MemberCantJoinCircleException; +use OCA\Circles\Exceptions\MemberIsNotModeratorException; +use OCA\Circles\Exceptions\MembersLimitException; +use OCA\Circles\Model\GlobalScale\GSEvent; + + +/** + * Class MemberAdd + * + * @package OCA\Circles\GlobalScale + */ +class MemberAdd extends AGlobalScaleEvent { + + + /** + * @param GSEvent $event + * @param bool $localCheck + * @param bool $mustBeChecked + * + * @throws CircleDoesNotExistException + * @throws ConfigNoCircleAvailableException + * @throws EmailAccountInvalidFormatException + * @throws GlobalScaleDSyncException + * @throws GlobalScaleEventException + * @throws MemberAlreadyExistsException + * @throws MemberCantJoinCircleException + * @throws MembersLimitException + * @throws NoUserException + * @throws CircleTypeNotValidException + * @throws MemberIsNotModeratorException + */ + public function verify(GSEvent $event, bool $localCheck = false, bool $mustBeChecked = false): void { + parent::verify($event, $localCheck, true); + + $eventMember = $event->getMember(); + $this->cleanMember($eventMember); + + if ($eventMember->getInstance() === '') { + $eventMember->setInstance($event->getSource()); + } + + $ident = $eventMember->getUserId(); + $this->membersService->verifyIdentBasedOnItsType( + $ident, $eventMember->getType(), $eventMember->getInstance() + ); + + $circle = $event->getCircle(); + if (!$event->isForced()) { + $circle->getHigherViewer() + ->hasToBeModerator(); + } + + $member = $this->membersRequest->getFreshNewMember( + $circle->getUniqueId(), $ident, $eventMember->getType(), $eventMember->getInstance() + ); + $member->hasToBeInviteAble(); + + $this->circlesService->checkThatCircleIsNotFull($circle); + $this->membersService->addMemberBasedOnItsType($circle, $member); + + $event->setMember($member); + + } + + + /** + * @param GSEvent $event + * + * @throws MemberAlreadyExistsException + */ + public function manage(GSEvent $event): void { + $circle = $event->getCircle(); + $member = $event->getMember(); + if ($member->getJoined() === '') { + $this->membersRequest->createMember($member); + } else { + $this->membersRequest->updateMember($member); + } + + $this->eventsService->onMemberNew($circle, $member); + } + + + /** + * @param GSEvent[] $events + */ + public function result(array $events): void { + // $instances = array_keys($events); +// foreach ($instances as $instance) { +// $event = $events[$instance]; +// $this->miscService->log('---- ' . $instance . ' -- ' . json_encode($event->getResult())); +// } + } + +} + diff --git a/lib/GlobalScale/MemberJoin.php b/lib/GlobalScale/MemberJoin.php new file mode 100644 index 00000000..5848e7c4 --- /dev/null +++ b/lib/GlobalScale/MemberJoin.php @@ -0,0 +1,116 @@ +<?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 2017 + * @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\GlobalScale; + + +use daita\MySmallPhpTools\Model\SimpleDataStore; +use OCA\Circles\Exceptions\CircleDoesNotExistException; +use OCA\Circles\Exceptions\ConfigNoCircleAvailableException; +use OCA\Circles\Exceptions\GlobalScaleDSyncException; +use OCA\Circles\Exceptions\GlobalScaleEventException; +use OCA\Circles\Exceptions\MemberAlreadyExistsException; +use OCA\Circles\Exceptions\MemberCantJoinCircleException; +use OCA\Circles\Exceptions\MemberIsBlockedException; +use OCA\Circles\Exceptions\MembersLimitException; +use OCA\Circles\Model\GlobalScale\GSEvent; +use OCA\Circles\Model\Member; + + +/** + * Class MemberJoin + * + * @package OCA\Circles\GlobalScale + */ +class MemberJoin extends AGlobalScaleEvent { + + + /** + * @param GSEvent $event + * @param bool $localCheck + * @param bool $mustBeChecked + * + * @throws MemberAlreadyExistsException + * @throws MemberCantJoinCircleException + * @throws MemberIsBlockedException + * @throws MembersLimitException + * @throws CircleDoesNotExistException + * @throws ConfigNoCircleAvailableException + * @throws GlobalScaleDSyncException + * @throws GlobalScaleEventException + */ + public function verify(GSEvent $event, bool $localCheck = false, bool $mustBeChecked = false): void { + parent::verify($event, false, false); + + $circle = $event->getCircle(); + $eventMember = $event->getMember(); + + $member = $this->membersRequest->getFreshNewMember( + $circle->getUniqueId(), $eventMember->getUserId(), Member::TYPE_USER, $eventMember->getInstance() + ); + $member->hasToBeAbleToJoinTheCircle(); + $member->joinCircle($circle->getType()); + + $this->circlesService->checkThatCircleIsNotFull($circle); + $event->setMember($member); + } + + + /** + * @param GSEvent $event + * + * @throws MemberAlreadyExistsException + */ + public function manage(GSEvent $event): void { + $circle = $event->getCircle(); + $member = $event->getMember(); + + if ($member->getJoined() === '') { + $this->membersRequest->createMember($member); + } else { + $this->membersRequest->updateMember($member); + } + + $this->eventsService->onMemberNew($circle, $member); + } + + + /** + * @param GSEvent[] $events + */ + public function result(array $events): void { +// $instances = array_keys($events); +// foreach ($instances as $instance) { +// $event = $events[$instance]; +// $this->miscService->log('---- ' . $instance . ' -- ' . json_encode($event->getResult())); +// } + } + +} + diff --git a/lib/GlobalScale/MemberLeave.php b/lib/GlobalScale/MemberLeave.php new file mode 100644 index 00000000..faab541b --- /dev/null +++ b/lib/GlobalScale/MemberLeave.php @@ -0,0 +1,94 @@ +<?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 2017 + * @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\GlobalScale; + + +use OCA\Circles\Exceptions\CircleDoesNotExistException; +use OCA\Circles\Exceptions\ConfigNoCircleAvailableException; +use OCA\Circles\Exceptions\GlobalScaleDSyncException; +use OCA\Circles\Exceptions\GlobalScaleEventException; +use OCA\Circles\Exceptions\MemberDoesNotExistException; +use OCA\Circles\Exceptions\MemberIsOwnerException; +use OCA\Circles\Model\GlobalScale\GSEvent; + + +/** + * Class MemberLeave + * + * @package OCA\Circles\GlobalScale + */ +class MemberLeave extends AGlobalScaleEvent { + + + /** + * @param GSEvent $event + * @param bool $localCheck + * @param bool $mustBeChecked + * + * @throws MemberDoesNotExistException + * @throws MemberIsOwnerException + * @throws CircleDoesNotExistException + * @throws ConfigNoCircleAvailableException + * @throws GlobalScaleDSyncException + * @throws GlobalScaleEventException + */ + public function verify(GSEvent $event, bool $localCheck = false, bool $mustBeChecked = false): void { + parent::verify($event, $localCheck, true); + + $member = $event->getMember(); + + $member->hasToBeMemberOrAlmost(); + $member->cantBeOwner(); + } + + + /** + * @param GSEvent $event + */ + public function manage(GSEvent $event): void { + $circle = $event->getCircle(); + $member = $event->getMember(); + + $this->eventsService->onMemberLeaving($circle, $member); + + $this->membersRequest->removeMember($member); + $this->sharesRequest->removeSharesFromMember($member); + $this->gsSharesRequest->removeGSSharesFromMember($member); + } + + + /** + * @param GSEvent[] $events + */ + public function result(array $events): void { + } + +} + diff --git a/lib/GlobalScale/MemberLevel.php b/lib/GlobalScale/MemberLevel.php new file mode 100644 index 00000000..d2f69b90 --- /dev/null +++ b/lib/GlobalScale/MemberLevel.php @@ -0,0 +1,182 @@ +<?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 2017 + * @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\GlobalScale; + + +use Exception; +use OCA\Circles\Exceptions\CircleDoesNotExistException; +use OCA\Circles\Exceptions\ConfigNoCircleAvailableException; +use OCA\Circles\Exceptions\GlobalScaleDSyncException; +use OCA\Circles\Exceptions\GlobalScaleEventException; +use OCA\Circles\Exceptions\MemberDoesNotExistException; +use OCA\Circles\Exceptions\MemberIsNotModeratorException; +use OCA\Circles\Exceptions\MemberIsNotOwnerException; +use OCA\Circles\Exceptions\MemberIsOwnerException; +use OCA\Circles\Exceptions\MemberTypeCantEditLevelException; +use OCA\Circles\Exceptions\ModeratorIsNotHighEnoughException; +use OCA\Circles\Model\Circle; +use OCA\Circles\Model\GlobalScale\GSEvent; +use OCA\Circles\Model\Member; + + +/** + * Class MemberLevel + * + * @package OCA\Circles\GlobalScale + */ +class MemberLevel extends AGlobalScaleEvent { + + + /** + * @param GSEvent $event + * @param bool $localCheck + * + * @param bool $mustBeChecked + * + * @throws CircleDoesNotExistException + * @throws ConfigNoCircleAvailableException + * @throws GlobalScaleDSyncException + * @throws GlobalScaleEventException + * @throws MemberTypeCantEditLevelException + * @throws Exception + */ + public function verify(GSEvent $event, bool $localCheck = false, bool $mustBeChecked = true): void { + parent::verify($event, $localCheck, true); + + $member = $event->getMember(); + $level = $event->getData() + ->gInt('level'); + if ($member->getLevel() === $level) { + throw new GlobalScaleDSyncException('level is not changed during the process'); + } + + $member->levelHasToBeEditable(); + $circle = $event->getCircle(); + + if ($level === Member::LEVEL_OWNER) { + $this->verifySwitchOwner($event, $circle, $member); + } else { + $this->verifyMemberLevel($event, $circle, $member, $level); + } + + $this->miscService->log('$$$$ 3 ' . json_encode($event)); + + } + + + /** + * @param GSEvent $event + * + * @throws Exception + */ + public function manage(GSEvent $event): void { + $level = $event->getData() + ->gInt('level'); + + $this->miscService->log('$$$$ 4 ' . json_encode($event)); + + $member = $event->getMember(); + $this->cleanMember($member); + $this->miscService->log('$$$$ 5 ' . json_encode($event)); + + $member->setLevel($level); + $this->membersRequest->updateMember($member); + + $this->miscService->log('#### ' . json_encode($event)); + if ($level === Member::LEVEL_OWNER) { + $circle = $event->getCircle(); + $isMod = $circle->getOwner(); + if ($isMod->getInstance() === '') { + $isMod->setInstance($event->getSource()); + } + + $this->miscService->log('???? ' . $isMod->getInstance()); + + $isMod->setLevel(Member::LEVEL_ADMIN); + $this->miscService->log('#### 001 ' . json_encode($event)); + + $this->membersRequest->updateMember($isMod); + $this->miscService->log('#### 002 ' . json_encode($event)); + + } + } + + + /** + * @param GSEvent[] $events + */ + public function result(array $events): void { + } + + + /** + * @param GSEvent $event + * @param Circle $circle + * @param Member $member + * @param int $level + * + * @throws MemberDoesNotExistException + * @throws MemberIsOwnerException + * @throws MemberIsNotModeratorException + * @throws ModeratorIsNotHighEnoughException + */ + private function verifyMemberLevel(GSEvent $event, Circle $circle, Member &$member, int $level) { + $member->hasToBeMember(); + $member->cantBeOwner(); + + if (!$event->isForced()) { + $isMod = $circle->getHigherViewer(); + $isMod->hasToBeModerator(); + $isMod->hasToBeHigherLevel($level); + $isMod->hasToBeHigherLevel($member->getLevel()); + } + } + + /** + * @param GSEvent $event + * @param Circle $circle + * @param Member $member + * + * @throws MemberDoesNotExistException + * @throws MemberIsNotOwnerException + * @throws MemberIsOwnerException + */ + private function verifySwitchOwner(GSEvent $event, Circle $circle, Member &$member) { + if (!$event->isForced()) { + $isMod = $circle->getHigherViewer(); + $this->circlesService->hasToBeOwner($isMod); + } + + $member->hasToBeMember(); + $member->cantBeOwner(); + } + +} + diff --git a/lib/GlobalScale/MemberRemove.php b/lib/GlobalScale/MemberRemove.php new file mode 100644 index 00000000..34580461 --- /dev/null +++ b/lib/GlobalScale/MemberRemove.php @@ -0,0 +1,110 @@ +<?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 2017 + * @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\GlobalScale; + + +use OCA\Circles\Exceptions\CircleDoesNotExistException; +use OCA\Circles\Exceptions\ConfigNoCircleAvailableException; +use OCA\Circles\Exceptions\GlobalScaleDSyncException; +use OCA\Circles\Exceptions\GlobalScaleEventException; +use OCA\Circles\Exceptions\MemberAlreadyExistsException; +use OCA\Circles\Exceptions\MemberDoesNotExistException; +use OCA\Circles\Exceptions\MemberIsNotModeratorException; +use OCA\Circles\Exceptions\MemberIsOwnerException; +use OCA\Circles\Exceptions\ModeratorIsNotHighEnoughException; +use OCA\Circles\Model\GlobalScale\GSEvent; + + +/** + * Class MemberDelete + * + * @package OCA\Circles\GlobalScale + */ +class MemberRemove extends AGlobalScaleEvent { + + + /** + * @param GSEvent $event + * @param bool $localCheck + * @param bool $mustBeChecked + * + * @throws MemberDoesNotExistException + * @throws MemberIsNotModeratorException + * @throws MemberIsOwnerException + * @throws ModeratorIsNotHighEnoughException + * @throws CircleDoesNotExistException + * @throws ConfigNoCircleAvailableException + * @throws GlobalScaleDSyncException + * @throws GlobalScaleEventException + */ + public function verify(GSEvent $event, bool $localCheck = false, bool $mustBeChecked = false): void { + parent::verify($event, $localCheck, true); + + $circle = $event->getCircle(); + $member = $event->getMember(); + + $member->hasToBeMemberOrAlmost(); + $member->cantBeOwner(); + + if (!$event->isForced()) { + $circle->getHigherViewer() + ->hasToBeModerator(); + $circle->getHigherViewer() + ->hasToBeHigherLevel($member->getLevel()); + } + } + + + /** + * @param GSEvent[] $events + */ + public function result(array $events): void { + } + + + /** + * @param GSEvent $event + * + * @throws MemberAlreadyExistsException + */ + public function manage(GSEvent $event): void { + $circle = $event->getCircle(); + $member = $event->getMember(); + + $this->eventsService->onMemberLeaving($circle, $member); + $this->membersRequest->removeMember($member); + + $this->gsSharesRequest->removeGSSharesFromMember($member); + $this->sharesRequest->removeSharesFromMember($member); + $this->tokensRequest->removeTokensFromMember($member); + } + +} + diff --git a/lib/Migration/Version0017Date20191210153032.php b/lib/Migration/Version0017Date20191210153032.php new file mode 100644 index 00000000..e9843bc7 --- /dev/null +++ b/lib/Migration/Version0017Date20191210153032.php @@ -0,0 +1,198 @@ +<?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 2019 + * @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\Migration; + + +use Closure; +use Doctrine\DBAL\Schema\SchemaException; +use OCP\DB\ISchemaWrapper; +use OCP\Migration\IOutput; +use OCP\Migration\SimpleMigrationStep; + + +class Version0017Date20191210153032 extends SimpleMigrationStep { + + /** + * @param IOutput $output + * @param Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper` + * @param array $options + */ + public function preSchemaChange(IOutput $output, Closure $schemaClosure, array $options) { + } + + /** + * @param IOutput $output + * @param Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper` + * @param array $options + * + * @return null|ISchemaWrapper + * @throws SchemaException + */ + public function changeSchema(IOutput $output, Closure $schemaClosure, array $options) { + /** @var ISchemaWrapper $schema */ + $schema = $schemaClosure(); + + if (!$schema->hasTable('circles_gsevents')) { + $table = $schema->createTable('circles_gsevents'); + $table->addColumn( + 'token', 'string', [ + 'notnull' => false, + 'length' => 63, + ] + ); + $table->addColumn( + 'event', 'text', [ + 'notnull' => false + ] + ); + $table->addColumn( + 'instance', 'string', [ + 'length' => 255, + 'notnull' => false + ] + ); + $table->addColumn( + 'severity', 'integer', [ + 'length' => 3, + 'notnull' => false + ] + ); + $table->addColumn( + 'status', 'integer', [ + 'length' => 3, + 'notnull' => false + ] + ); + $table->addColumn( + 'creation', 'bigint', [ + 'length' => 14, + 'notnull' => false + ] + ); + $table->addUniqueIndex(['token', 'instance']); + } + + if (!$schema->hasTable('circles_gsshares')) { + $table = $schema->createTable('circles_gsshares'); + $table->addColumn( + 'id', 'integer', [ + 'notnull' => false, + 'length' => 11, + 'autoincrement' => true, + 'unsigned' => true + ] + ); + $table->addColumn( + 'circle_id', 'string', [ + 'length' => 15, + 'notnull' => false + ] + ); + $table->addColumn( + 'owner', 'string', [ + 'length' => 15, + 'notnull' => false + ] + ); + $table->addColumn( + 'instance', 'string', [ + 'length' => 255, + 'notnull' => false + ] + ); + $table->addColumn( + 'token', 'string', [ + 'notnull' => false, + 'length' => 63 + ] + ); + $table->addColumn( + 'parent', 'integer', [ + 'notnull' => false, + 'length' => 11, + ] + ); + $table->addColumn( + 'mountpoint', 'text', [ + 'notnull' => false + ] + ); + $table->addColumn( + 'mountpoint_hash', 'string', [ + 'length' => 64, + 'notnull' => false + ] + ); + $table->setPrimaryKey(['id']); + $table->addUniqueIndex(['circle_id', 'mountpoint_hash']); + } + + if (!$schema->hasTable('circles_gsshares_mp')) { + $table = $schema->createTable('circles_gsshares_mp'); + $table->addColumn( + 'share_id', 'integer', [ + 'length' => 11, + 'notnull' => false + ] + ); + $table->addColumn( + 'user_id', 'string', [ + 'length' => 127, + 'notnull' => false + ] + ); + $table->addColumn( + 'mountpoint', 'text', [ + 'notnull' => false + ] + ); + $table->addColumn( + 'mountpoint_hash', 'string', [ + 'length' => 64, + 'notnull' => false + ] + ); + $table->setPrimaryKey(['share_id', 'user_id']); + } + + return $schema; + } + + + /** + * @param IOutput $output + * @param Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper` + * @param array $options + */ + public function postSchemaChange(IOutput $output, Closure $schemaClosure, array $options) { + } + +} + diff --git a/lib/Model/BaseCircle.php b/lib/Model/BaseCircle.php index 523662cf..b8802589 100644 --- a/lib/Model/BaseCircle.php +++ b/lib/Model/BaseCircle.php @@ -53,7 +53,7 @@ class BaseCircle { protected $l10n; /** @var string */ - private $uniqueId; + private $uniqueId = ''; /** @var string */ private $name; @@ -62,7 +62,7 @@ class BaseCircle { private $owner; /** @var Member */ - private $viewer; + private $viewer = null; /** @var Member */ private $viewerGroup; @@ -210,6 +210,12 @@ class BaseCircle { return $this; } + + public function hasViewer(): bool { + return ($this->viewer !== null); + } + + /** * @return Member */ @@ -436,7 +442,7 @@ class BaseCircle { } /** - * @return array + * @return Member[] */ public function getMembers() { return $this->members; diff --git a/lib/Model/BaseMember.php b/lib/Model/BaseMember.php index d37596ee..af1cb21c 100644 --- a/lib/Model/BaseMember.php +++ b/lib/Model/BaseMember.php @@ -87,7 +87,10 @@ class BaseMember implements \JsonSerializable { private $note; /** @var string */ - private $joined; + private $instance = ''; + + /** @var string */ + private $joined = ''; /** @var int */ private $joinedSince; @@ -228,6 +231,17 @@ class BaseMember implements \JsonSerializable { } + public function setInstance($instance) { + $this->instance = $instance; + + return $this; + } + + public function getInstance() { + return $this->instance; + } + + public function setContactId($contactId) { $this->contactId = $contactId; @@ -367,13 +381,16 @@ class BaseMember implements \JsonSerializable { $member = new Member(); $member->setCircleId($arr['circle_id']); + $member->setMemberId($arr['member_id']); $member->setLevel($arr['level']); $member->setType(MiscService::get($arr, 'user_type')); $member->setType(MiscService::get($arr, 'type', $member->getType())); + $member->setInstance($arr['instance']); $member->setUserId($arr['user_id']); $member->setStatus($arr['status']); + $member->setInstance($arr['instance']); $member->setNote($arr['note']); $member->setJoined($arr['joined']); @@ -402,6 +419,7 @@ class BaseMember implements \JsonSerializable { 'level' => $this->getLevel(), 'level_string' => $this->getLevelString(), 'status' => $this->getStatus(), + 'instance' => $this->getInstance(), 'note' => $this->getNote(), 'joined' => $this->getJoined() ]; diff --git a/lib/Model/Circle.php b/lib/Model/Circle.php index 506ee313..82868777 100644 --- a/lib/Model/Circle.php +++ b/lib/Model/Circle.php @@ -26,11 +26,16 @@ namespace OCA\Circles\Model; +use daita\MySmallPhpTools\Traits\TArrayTools; use OCA\Circles\Exceptions\CircleTypeNotValidException; use OCA\Circles\Exceptions\FederatedCircleNotAllowedException; class Circle extends BaseCircle implements \JsonSerializable { + + use TArrayTools; + + /** @var bool */ private $fullJson = false; @@ -65,6 +70,18 @@ class Circle extends BaseCircle implements \JsonSerializable { } + /** + * @param bool $fullJson + * + * @return $this + */ + public function setFullJson(bool $fullJson): self { + $this->fullJson = $fullJson; + + return $this; + } + + public function jsonSerialize() { $json = [ 'id' => $this->getId(), @@ -123,7 +140,7 @@ class Circle extends BaseCircle implements \JsonSerializable { * @return $this */ public static function fromArray($arr) { - if ($arr === null) { + if ($arr === null || empty($arr)) { return new Circle(); } @@ -140,6 +157,15 @@ class Circle extends BaseCircle implements \JsonSerializable { $circle->setViewer(self::getMemberFromArray($arr, 'user')); $circle->setOwner(self::getMemberFromArray($arr, 'owner')); + if (array_key_exists('members', $arr) && is_array($arr['members'])) { + $members = []; + foreach ($arr['members'] as $item) { + $members[] = Member::fromArray($item); + } + $circle->setMembers($members); + } + + return $circle; } diff --git a/lib/Model/GlobalScale/GSEvent.php b/lib/Model/GlobalScale/GSEvent.php new file mode 100644 index 00000000..41748335 --- /dev/null +++ b/lib/Model/GlobalScale/GSEvent.php @@ -0,0 +1,428 @@ +<?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 2017 + * @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\Model\GlobalScale; + + +use daita\MySmallPhpTools\Model\SimpleDataStore; +use daita\MySmallPhpTools\Traits\TArrayTools; +use JsonSerializable; +use OCA\Circles\Exceptions\JsonException; +use OCA\Circles\Exceptions\ModelException; +use OCA\Circles\Model\Circle; +use OCA\Circles\Model\Member; + + +/** + * Class GSEvent + * + * @package OCA\Circles\Model\GlobalScale + */ +class GSEvent implements JsonSerializable { + + + const SEVERITY_LOW = 1; + const SEVERITY_HIGH = 3; + + const GLOBAL_SYNC = 'GlobalScale\GlobalSync'; + const CIRCLE_STATUS = 'GlobalScale\CircleStatus'; + + const CIRCLE_CREATE = 'GlobalScale\CircleCreate'; + const CIRCLE_UPDATE = 'GlobalScale\CircleUpdate'; + const CIRCLE_DESTROY = 'GlobalScale\CircleDestroy'; + const MEMBER_ADD = 'GlobalScale\MemberAdd'; + const MEMBER_JOIN = 'GlobalScale\MemberJoin'; + const MEMBER_INVITE = 'GlobalScale\MemberInvite'; + const MEMBER_LEAVE = 'GlobalScale\MemberLeave'; + const MEMBER_LEVEL = 'GlobalScale\MemberLevel'; + const MEMBER_UPDATE = 'GlobalScale\MemberUpdate'; + const MEMBER_REMOVE = 'GlobalScale\MemberRemove'; + + const FILE_SHARE = 'GlobalScale\FileShare'; + + + use TArrayTools; + + + /** @var string */ + private $type = ''; + + /** @var string */ + private $source = ''; + + /** @var Circle */ + private $circle; + + /** @var Member */ + private $member; + + /** @var SimpleDataStore */ + private $data; + + /** @var int */ + private $severity = self::SEVERITY_LOW; + + /** @var SimpleDataStore */ + private $result; + + /** @var string */ + private $key = ''; + + /** @var bool */ + private $local = false; + + /** @var bool */ + private $force = false; + + + /** + * GSEvent constructor. + * + * @param string $type + * @param bool $local + * @param bool $force + */ + function __construct(string $type = '', bool $local = false, bool $force = false) { + $this->type = $type; + $this->local = $local; + $this->force = $force; + $this->data = new SimpleDataStore(); + $this->result = new SimpleDataStore(); + } + + + /** + * @return string + */ + public function getType(): string { + return $this->type; + } + + /** + * @param mixed $type + * + * @return GSEvent + */ + public function setType($type): self { + $this->type = $type; + + return $this; + } + + + /** + * @return string + */ + public function getSource(): string { + return $this->source; + } + + /** + * @param string $source + * + * @return GSEvent + */ + public function setSource(string $source): self { + $this->source = $source; + + if ($this->hasMember() && $this->member->getInstance() === '') { + $this->member->setInstance($source); + } + + if ($this->hasCircle() + && $this->getCircle() + ->hasViewer() + && $this->getCircle() + ->getViewer() + ->getInstance() === '') { + $this->getCircle() + ->getViewer() + ->setInstance($source); + } + + return $this; + } + + + /** + * @return bool + */ + public function isLocal(): bool { + return $this->local; + } + + /** + * @param bool $local + * + * @return GSEvent + */ + public function setLocal(bool $local): self { + $this->local = $local; + + return $this; + } + + + /** + * @return bool + */ + public function isForced(): bool { + return $this->force; + } + + /** + * @param bool $force + * + * @return GSEvent + */ + public function setForced(bool $force): self { + $this->force = $force; + + return $this; + } + + + /** + * @return Circle + */ + public function getCircle(): Circle { + return $this->circle; + } + + /** + * @param Circle $circle + * + * @return GSEvent + */ + public function setCircle(Circle $circle): self { + $this->circle = $circle; + + return $this; + } + + /** + * @return bool + */ + public function hasCircle(): bool { + return ($this->circle !== null); + } + + + /** + * @return Member + */ + public function getMember(): Member { + return $this->member; + } + + /** + * @param Member $member + * + * @return GSEvent + */ + public function setMember(Member $member): self { + $this->member = $member; + + return $this; + } + + /** + * @return bool + */ + public function hasMember(): bool { + return ($this->member !== null); + } + + + /** + * @param SimpleDataStore $data + * + * @return GSEvent + */ + public function setData(SimpleDataStore $data): self { + $this->data = $data; + + return $this; + } + + /** + * @return SimpleDataStore + */ + public function getData(): SimpleDataStore { + return $this->data; + } + + + /** + * @return int + */ + public function getSeverity(): int { + return $this->severity; + } + + /** + * @param int $severity + * + * @return GSEvent + */ + public function setSeverity(int $severity): self { + $this->severity = $severity; + + return $this; + } + + + /** + * @return SimpleDataStore + */ + public function getResult(): SimpleDataStore { + return $this->result; + } + + /** + * @param SimpleDataStore $result + * + * @return GSEvent + */ + public function setResult(SimpleDataStore $result): self { + $this->result = $result; + + return $this; + } + + + /** + * @return string + */ + public function getKey(): string { + return $this->key; + } + + /** + * @param string $key + * + * @return GSEvent + */ + public function setKey(string $key): self { + $this->key = $key; + + return $this; + } + + + /** + * @return bool + */ + public function isValid(): bool { + if ($this->getType() === '') { + return false; + } + + return true; + } + + + /** + * @param string $json + * + * @return GSEvent + * @throws JsonException + * @throws ModelException + */ + public function importFromJson(string $json): self { + $data = json_decode($json, true); + + if (!is_array($data)) { + throw new JsonException('invalid JSON'); + } + + return $this->import($data); + } + + + /** + * @param array $data + * + * @return GSEvent + * @throws ModelException + */ + public function import(array $data): self { + $this->setType($this->get('type', $data)); + $this->setSeverity($this->getInt('severity', $data)); + $this->setData(new SimpleDataStore($this->getArray('data', $data))); + $this->setResult(new SimpleDataStore($this->getArray('result', $data))); + $this->setSource($this->get('source', $data)); + $this->setKey($this->get('key', $data)); + $this->setForced($this->getBool('force', $data)); + + if (array_key_exists('circle', $data)) { + $this->setCircle(Circle::fromArray($data['circle'])); + } + + if (array_key_exists('member', $data)) { + $this->setMember(Member::fromArray($data['member'])); + } + + if (!$this->isValid()) { + throw new ModelException('invalid GSEvent'); + } + + return $this; + } + + + /** + * @return array + */ + function jsonSerialize(): array { + $arr = [ + 'type' => $this->getType(), + 'severity' => $this->getSeverity(), + 'data' => $this->getData(), + 'result' => $this->getResult(), + 'key' => $this->getKey(), + 'source' => $this->getSource(), + 'force' => $this->isForced() + ]; + + if ($this->hasCircle()) { + $arr['circle'] = $this->getCircle(); + } + if ($this->hasMember()) { + $arr['member'] = $this->getMember(); + } + + $this->cleanArray($arr); + + return $arr; + } + + +} + diff --git a/lib/Model/GlobalScale/GSShare.php b/lib/Model/GlobalScale/GSShare.php new file mode 100644 index 00000000..bc3472d6 --- /dev/null +++ b/lib/Model/GlobalScale/GSShare.php @@ -0,0 +1,322 @@ +<?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 2017 + * @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\Model\GlobalScale; + + +use daita\MySmallPhpTools\Traits\TArrayTools; +use JsonSerializable; + + +/** + * Class GSShare + * + * @package OCA\Circles\Model\GlobalScale + */ +class GSShare implements JsonSerializable { + + + use TArrayTools; + + + /** @var int */ + private $id = 0; + + /** @var string */ + private $circleId = ''; + + /** @var string */ + private $defaultMountPoint = ''; + + /** @var string */ + private $mountPoint = ''; + + /** @var int */ + private $parent = -1; + + /** @var string */ + private $owner = ''; + + /** @var string */ + private $instance = ''; + + /** @var string */ + private $token = ''; + + /** @var string */ + private $password = ''; + + + /** + * GSShare constructor. + * + * @param string $circleId + * @param string $token + */ + public function __construct(string $circleId = '', string $token = '') { + $this->circleId = $circleId; + $this->token = $token; + } + + + /** + * @return int + */ + public function getId(): int { + return $this->id; + } + + public function setId(int $id): self { + $this->id = $id; + + return $this; + } + + + /** + * + * @return string + */ + public function getCircleId(): string { + return $this->circleId; + } + + /** + * @param string $circleId + * + * @return GSShare + */ + public function setCircleId(string $circleId): self { + $this->circleId = $circleId; + + return $this; + } + + + /** + * @return string + */ + public function getDefaultMountPoint(): string { + return $this->defaultMountPoint; + } + + /** + * @param string $mountPoint + * + * @return GSShare + */ + public function setDefaultMountPoint(string $mountPoint): self { + $this->defaultMountPoint = $mountPoint; + + return $this; + } + + + /** + * @param string $userId + * + * @return string + */ + public function getMountPoint(string $userId = ''): string { + $mountPoint = $this->mountPoint; + + if ($mountPoint === '') { + $mountPoint = $this->defaultMountPoint; + } + + if ($userId === '') { + return $mountPoint; + } + + return '/' . $userId . '/files/' . ltrim($mountPoint, '/'); + } + + /** + * @param string $mountPoint + * + * @return GSShare + */ + public function setMountPoint(string $mountPoint): self { + $this->mountPoint = $mountPoint; + + return $this; + } + + + /** + * @return int + */ + public function getParent(): int { + return $this->parent; + } + + /** + * @param int $parent + * + * @return GSShare + */ + public function setParent(int $parent): self { + $this->parent = $parent; + + return $this; + } + + + /** + * @return string + */ + public function getOwner(): string { + return $this->owner; + } + + /** + * @param string $owner + * + * @return GSShare + */ + public function setOwner(string $owner): self { + $this->owner = $owner; + + return $this; + } + + + /** + * @return string + */ + public function getInstance(): string { + return $this->instance; + } + + /** + * @param string $instance + * + * @return GSShare + */ + public function setInstance(string $instance): self { + $this->instance = $instance; + + return $this; + } + + + /** + * @return string + */ + public function getToken(): string { + return $this->token; + } + + + /** + * @param string $token + * + * @return GSShare + */ + public function setToken(string $token): self { + $this->token = $token; + + return $this; + } + + + /** + * @return string + */ + public function getPassword(): string { + return $this->password; + } + + /** + * @param string $password + * + * @return GSShare + */ + public function setPassword(string $password): self { + $this->password = $password; + + return $this; + } + + + /** + * @param array $data + * + * @return GSShare + */ + public function importFromDatabase(array $data): self { + $this->setId($this->getInt('id', $data)); + $this->setCircleId($this->get('circle_id', $data)); + $this->setOwner($this->get('owner', $data)); + $this->setInstance($this->get('instance', $data)); + $this->setToken($this->get('token', $data)); + $this->setParent($this->getInt('parent', $data)); + $this->setMountPoint($this->get('gsshares_mountpoint', $data)); + $this->setDefaultMountPoint($this->get('mountpoint', $data)); + + return $this; + } + + + /** + * @param string $userId + * @param string $protocol + * + * @return array + */ + public function toMount(string $userId, string $protocol = 'https'): array { + return [ + 'owner' => $this->getOwner(), + 'remote' => $protocol . '://' . $this->getInstance(), + 'token' => $this->getToken(), + 'share_token' => $this->getToken(), + 'password' => $this->getPassword(), + 'mountpoint' => $this->getMountPoint($userId) + ]; + } + + + /** + * @return array + */ + function jsonSerialize(): array { + return [ + 'id' => $this->getId(), + 'defaultMountPoint' => $this->getDefaultMountPoint(), + 'mountPoint' => $this->getMountPoint(), + 'parent' => $this->getParent(), + 'owner' => $this->getOwner(), + 'instance' => $this->getInstance(), + 'token' => $this->getToken(), + 'password' => $this->getPassword() + ]; + } + +} + diff --git a/lib/Model/GlobalScale/GSShareMountpoint.php b/lib/Model/GlobalScale/GSShareMountpoint.php new file mode 100644 index 00000000..c6f69ec2 --- /dev/null +++ b/lib/Model/GlobalScale/GSShareMountpoint.php @@ -0,0 +1,155 @@ +<?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 2017 + * @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\Model\GlobalScale; + + +use daita\MySmallPhpTools\Traits\TArrayTools; +use JsonSerializable; + + +/** + * Class GSShare + * + * @package OCA\Circles\Model\GlobalScale + */ +class GSShareMountpoint implements JsonSerializable { + + + use TArrayTools; + + + /** @var int */ + private $shareId = 0; + + /** @var string */ + private $userId = ''; + + /** @var string */ + private $mountPoint = ''; + + + /** + * GSShareMountpoint constructor. + * + * @param int $shareId + * @param string $userId + * @param string $mountPoint + */ + public function __construct(int $shareId = 0, string $userId = '', string $mountPoint = '') { + $this->shareId = $shareId; + $this->userId = $userId; + $this->mountPoint = $mountPoint; + } + + + /** + * @return string + */ + public function getUserId(): string { + return $this->userId; + } + + /** + * @param string $userId + * + * @return GSShareMountpoint + */ + public function setUserId(string $userId): self { + $this->userId = $userId; + + return $this; + } + + + /** + * @return int + */ + public function getShareId(): int { + return $this->shareId; + } + + /** + * @param int $shareId + * + * @return $this + */ + public function setShareId(int $shareId): self { + $this->shareId = $shareId; + + return $this; + } + + + /** + * @return string + */ + public function getMountPoint(): string { + return $this->mountPoint; + } + + /** + * @param string $mountPoint + * + * @return GSShareMountpoint + */ + public function setMountPoint(string $mountPoint): self { + $this->mountPoint = $mountPoint; + + return $this; + } + + + /** + * @param array $data + * + * @return GSShareMountpoint + */ + public function importFromDatabase(array $data): self { + $this->setShareId($this->getInt('share_id', $data)); + $this->setUserId($this->get('user_id', $data)); + $this->setMountPoint($this->get('mountpoint', $data)); + + return $this; + } + + + /** + * @return array + */ + function jsonSerialize(): array { + return [ + 'userId' => $this->getUserId(), + 'shareId' => $this->getShareId(), + 'mountPoint' => $this->getMountPoint(), + ]; + } + +} + diff --git a/lib/Model/GlobalScale/GSWrapper.php b/lib/Model/GlobalScale/GSWrapper.php new file mode 100644 index 00000000..436df679 --- /dev/null +++ b/lib/Model/GlobalScale/GSWrapper.php @@ -0,0 +1,242 @@ +<?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 2017 + * @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\Model\GlobalScale; + + +use daita\MySmallPhpTools\Traits\TArrayTools; +use JsonSerializable; +use OCA\Circles\Exceptions\JsonException; +use OCA\Circles\Exceptions\ModelException; + + +/** + * Class GSEvent + * + * @package OCA\Circles\Model\GlobalScale + */ +class GSWrapper implements JsonSerializable { + + + use TArrayTools; + + + const STATUS_INIT = 0; + const STATUS_FAILED = 1; + const STATUS_DONE = 8; + const STATUS_OVER = 9; + + + /** @var string */ + private $token = ''; + + /** @var GSEvent */ + private $event; + + /** @var string */ + private $instance = ''; + + /** @var int */ + private $severity = GSEvent::SEVERITY_LOW; + + /** @var int */ + private $status = 0; + + /** @var int */ + private $creation; + + + function __construct() { + } + + + /** + * @return string + */ + public function getToken(): string { + return $this->token; + } + + /** + * @param string $token + * + * @return GSWrapper + */ + public function setToken(string $token): self { + $this->token = $token; + + return $this; + } + + + /** + * @return GSEvent + */ + public function getEvent(): GSEvent { + return $this->event; + } + + /** + * @param GSEvent $event + * + * @return GSWrapper + */ + public function setEvent(GSEvent $event): self { + $this->event = $event; + + return $this; + } + + /** + * @return bool + */ + public function hasEvent(): bool { + return ($this->event !== null); + } + + + /** + * @return string + */ + public function getInstance(): string { + return $this->instance; + } + + /** + * @param string $instance + * + * @return GSWrapper + */ + public function setInstance(string $instance): self { + $this->instance = $instance; + + return $this; + } + + + /** + * @return int + */ + public function getSeverity(): int { + return $this->severity; + } + + /** + * @param int $severity + * + * @return GSWrapper + */ + public function setSeverity(int $severity): self { + $this->severity = $severity; + + return $this; + } + + + /** + * @return int + */ + public function getStatus(): int { + return $this->status; + } + + /** + * @param int $status + * + * @return GSWrapper + */ + public function setStatus(int $status): self { + $this->status = $status; + + return $this; + } + + + /** + * @return int + */ + public function getCreation(): int { + return $this->creation; + } + + /** + * @param int $creation + * + * @return GSWrapper + */ + public function setCreation(int $creation): self { + $this->creation = $creation; + + return $this; + } + + + /** + * @param array $data + * + * @return GSWrapper + * @throws JsonException + * @throws ModelException + */ + public function import(array $data): self { + $this->setToken($this->get('token', $data)); + $this->setInstance($this->get('instance', $data)); + $this->setSeverity($this->getInt('severity', $data, GSEvent::SEVERITY_LOW)); + $this->setStatus($this->getInt('status', $data, GSWrapper::STATUS_INIT)); + + $event = new GSEvent(); + $event->importFromJson($this->get('event', $data)); + + $this->setEvent($event); + + $this->setCreation($this->getInt('creation', $data)); + + return $this; + } + + + /** + * @return array + */ + function jsonSerialize(): array { + $arr = [ + 'id' => $this->getToken(), + 'event' => $this->getEvent(), + 'severity' => $this->getSeverity(), + 'status' => $this->getStatus(), + 'creation' => $this->getCreation() + ]; + + $this->cleanArray($arr); + + return $arr; + } + +} + diff --git a/lib/Model/Member.php b/lib/Model/Member.php index af6f8c1f..be17d640 100644 --- a/lib/Model/Member.php +++ b/lib/Model/Member.php @@ -139,7 +139,6 @@ class Member extends BaseMember { * @throws ModeratorIsNotHighEnoughException */ public function hasToBeHigherLevel($level) { - if ($this->getLevel() <= $level) { throw new ModeratorIsNotHighEnoughException( $this->l10n->t('Insufficient privileges') diff --git a/lib/Model/SearchResult.php b/lib/Model/SearchResult.php index e1bee72f..e6c6fc92 100644 --- a/lib/Model/SearchResult.php +++ b/lib/Model/SearchResult.php @@ -27,18 +27,6 @@ namespace OCA\Circles\Model; -use OCA\Circles\Exceptions\CircleTypeNotValidException; -use OCA\Circles\Exceptions\MemberAlreadyExistsException; -use OCA\Circles\Exceptions\MemberCantJoinCircleException; -use OCA\Circles\Exceptions\MemberDoesNotExistException; -use OCA\Circles\Exceptions\MemberIsBlockedException; -use OCA\Circles\Exceptions\MemberIsNotAdminException; -use OCA\Circles\Exceptions\MemberIsNotModeratorException; -use OCA\Circles\Exceptions\MemberIsNotOwnerException; -use OCA\Circles\Exceptions\MemberIsOwnerException; -use OCA\Circles\Exceptions\MemberTypeCantEditLevelException; -use OCA\Circles\Exceptions\ModeratorIsNotHighEnoughException; - class SearchResult implements \JsonSerializable { /** @var string */ @@ -47,6 +35,9 @@ class SearchResult implements \JsonSerializable { /** @var int */ private $type; + /** @var string */ + private $instance = ''; + /** @var array */ private $data = []; @@ -58,9 +49,10 @@ class SearchResult implements \JsonSerializable { * @param int $type * @param array $data */ - function __construct($ident = '', $type = 0, $data = []) { + function __construct($ident = '', $type = 0, $instance = '', $data = []) { $this->setIdent($ident); $this->setType($type); + $this->setInstance($instance); $this->setData($data); } @@ -81,6 +73,21 @@ class SearchResult implements \JsonSerializable { /** + * @param string $instance + */ + public function setInstance($instance) { + $this->instance = $instance; + } + + /** + * @return string + */ + public function getInstance() { + return $this->instance; + } + + + /** * @param int $type */ public function setType($type) { @@ -125,10 +132,11 @@ class SearchResult implements \JsonSerializable { function jsonSerialize() { return [ - 'ident' => $this->getIdent(), - 'type' => $this->getType(), - 'data' => $this->getData() + 'ident' => $this->getIdent(), + 'instance' => $this->getInstance(), + 'type' => $this->getType(), + 'data' => $this->getData() ]; } -}
\ No newline at end of file +} diff --git a/lib/Model/SharingFrame.php b/lib/Model/SharingFrame.php index 988897e4..904c8913 100644 --- a/lib/Model/SharingFrame.php +++ b/lib/Model/SharingFrame.php @@ -283,6 +283,12 @@ class SharingFrame implements \JsonSerializable { public static function fromJSON($json) { $arr = json_decode($json, true); + + return self::fromArray($arr); + } + + + public static function fromArray($arr) { if (!is_array($arr) || !key_exists('source', $arr)) { return null; } diff --git a/lib/Search/Contacts.php b/lib/Search/Contacts.php index c28ea46e..0663cc16 100644 --- a/lib/Search/Contacts.php +++ b/lib/Search/Contacts.php @@ -49,7 +49,7 @@ class Contacts implements ISearch { } $data = $this->generateDataArray($contact); - $result[] = new SearchResult($contact['UID'], Member::TYPE_CONTACT, $data); + $result[] = new SearchResult($contact['UID'], Member::TYPE_CONTACT, '', $data); } return $result; diff --git a/lib/Search/GlobalScaleUsers.php b/lib/Search/GlobalScaleUsers.php new file mode 100644 index 00000000..55cf1e04 --- /dev/null +++ b/lib/Search/GlobalScaleUsers.php @@ -0,0 +1,124 @@ +<?php +/** + * 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 2017 + * @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\Search; + +use daita\MySmallPhpTools\Exceptions\RequestContentException; +use daita\MySmallPhpTools\Exceptions\RequestNetworkException; +use daita\MySmallPhpTools\Exceptions\RequestResultNotJsonException; +use daita\MySmallPhpTools\Exceptions\RequestResultSizeException; +use daita\MySmallPhpTools\Exceptions\RequestServerException; +use daita\MySmallPhpTools\Model\Request; +use daita\MySmallPhpTools\Traits\TArrayTools; +use daita\MySmallPhpTools\Traits\TRequest; +use OCA\Circles\Exceptions\GSStatusException; +use OCA\Circles\ISearch; +use OCA\Circles\Model\Member; +use OCA\Circles\Model\SearchResult; +use OCA\Circles\Service\ConfigService; +use OCA\Circles\Service\MiscService; + + +/** + * Class GlobalScaleUsers + * + * @package OCA\Circles\Search + */ +class GlobalScaleUsers implements ISearch { + + + use TRequest; + use TArrayTools; + + + /** @var ConfigService */ + private $configService; + + /** @var MiscService */ + private $miscService; + + + /** + * GlobalScaleUsers constructor. + * + * @param ConfigService $configService + * @param MiscService $miscService + */ + public function __construct(ConfigService $configService, MiscService $miscService) { + $this->configService = $configService; + $this->miscService = $miscService; + } + + /** + * {@inheritdoc} + */ + public function search($search) { + + /** @var string $lookup */ + try { + $lookup = $this->configService->getGSStatus(ConfigService::GS_LOOKUP); + } catch (GSStatusException $e) { + return; + } + + $request = new Request('/users', Request::TYPE_GET); + $request->setProtocols(['https', 'http']); + $request->addData('search', $search); + $request->setAddressFromUrl($lookup); + + try { + $users = $this->retrieveJson($request); + } catch ( + RequestContentException | + RequestNetworkException | + RequestResultSizeException | + RequestServerException | + RequestResultNotJsonException $e + ) { + $this->miscService->log('Issue while retrieving instances from lookup: ' . $e->getMessage()); + + return []; + } + + $result = []; + foreach ($users as $user) { + list(, $instance) = explode('@', $this->get('federationId', $user), 2); + if ($instance === $this->configService->getLocalCloudId()) { + continue; + } + + $result[] = + new SearchResult( + $this->get('userid.value', $user), Member::TYPE_USER, $instance, + ['display' => $this->get('name.value', $user)] + ); + } + + return $result; + } +} + + diff --git a/lib/Search/LocalUsers.php b/lib/Search/LocalUsers.php index bb46e7e0..94a7829b 100644 --- a/lib/Search/LocalUsers.php +++ b/lib/Search/LocalUsers.php @@ -44,7 +44,7 @@ class LocalUsers implements ISearch { foreach ($users as $user) { $result[] = new SearchResult( - $user->getUID(), Member::TYPE_USER, ['display' => $user->getDisplayName()] + $user->getUID(), Member::TYPE_USER, '', ['display' => $user->getDisplayName()] ); } diff --git a/lib/Service/BroadcastService.php b/lib/Service/BroadcastService.php index 2a1d41b7..2ad94542 100644 --- a/lib/Service/BroadcastService.php +++ b/lib/Service/BroadcastService.php @@ -27,21 +27,26 @@ namespace OCA\Circles\Service; +use daita\MySmallPhpTools\Model\SimpleDataStore; use Exception; use OCA\Circles\Db\CirclesRequest; use OCA\Circles\Db\MembersRequest; -use OCA\Circles\Exceptions\BroadcasterIsNotCompatibleException; use OCA\Circles\IBroadcaster; use OCA\Circles\Model\Circle; +use OCA\Circles\Model\GlobalScale\GSEvent; use OCA\Circles\Model\Member; use OCA\Circles\Model\SharingFrame; class BroadcastService { + /** @var string */ private $userId; + /** @var GSUpstreamService */ + private $gsUpstreamService; + /** @var ConfigService */ private $configService; @@ -62,6 +67,7 @@ class BroadcastService { * @param ConfigService $configService * @param CirclesRequest $circlesRequest * @param MembersRequest $membersRequest + * @param GSUpstreamService $gsUpstreamService * @param MiscService $miscService */ public function __construct( @@ -69,12 +75,14 @@ class BroadcastService { ConfigService $configService, CirclesRequest $circlesRequest, MembersRequest $membersRequest, + GSUpstreamService $gsUpstreamService, MiscService $miscService ) { $this->userId = $userId; $this->configService = $configService; $this->circlesRequest = $circlesRequest; $this->membersRequest = $membersRequest; + $this->gsUpstreamService = $gsUpstreamService; $this->miscService = $miscService; } @@ -97,19 +105,22 @@ class BroadcastService { return; } - try { - $broadcaster = \OC::$server->query((string)$frame->getHeader('broadcast')); - if (!($broadcaster instanceof IBroadcaster)) { - throw new BroadcasterIsNotCompatibleException(); - } - - $frameCircle = $frame->getCircle(); - $circle = $this->circlesRequest->forceGetCircle($frameCircle->getUniqueId()); - - $this->feedBroadcaster($broadcaster, $frame, $circle); - } catch (Exception $e) { - throw $e; - } + $event = new GSEvent(GSEvent::FILE_SHARE, true); + $event->setSeverity(GSEvent::SEVERITY_HIGH); + $event->setCircle($frame->getCircle()); + $event->setSource($this->configService->getLocalCloudId()); + $event->setData(new SimpleDataStore(['frame' => json_decode(json_encode($frame), true)])); + $this->gsUpstreamService->newEvent($event); + +// $broadcaster = \OC::$server->query((string)$frame->getHeader('broadcast')); +// if (!($broadcaster instanceof IBroadcaster)) { +// throw new BroadcasterIsNotCompatibleException(); +// } +// +// $frameCircle = $frame->getCircle(); +// $circle = $this->circlesRequest->forceGetCircle($frameCircle->getUniqueId()); +// +// $this->feedBroadcaster($broadcaster, $frame, $circle); } @@ -170,3 +181,4 @@ class BroadcastService { } } + diff --git a/lib/Service/CirclesService.php b/lib/Service/CirclesService.php index 0cd6aaaa..7a4d4c2d 100644 --- a/lib/Service/CirclesService.php +++ b/lib/Service/CirclesService.php @@ -31,6 +31,7 @@ namespace OCA\Circles\Service; use Exception; +use OC; use OCA\Circles\AppInfo\Application; use OCA\Circles\Db\CircleProviderRequest; use OCA\Circles\Db\CirclesRequest; @@ -40,12 +41,14 @@ use OCA\Circles\Db\SharesRequest; use OCA\Circles\Exceptions\CircleAlreadyExistsException; use OCA\Circles\Exceptions\CircleDoesNotExistException; use OCA\Circles\Exceptions\CircleTypeDisabledException; +use OCA\Circles\Exceptions\ConfigNoCircleAvailableException; use OCA\Circles\Exceptions\FederatedCircleNotAllowedException; +use OCA\Circles\Exceptions\MemberAlreadyExistsException; use OCA\Circles\Exceptions\MemberDoesNotExistException; -use OCA\Circles\Exceptions\MemberIsNotModeratorException; use OCA\Circles\Exceptions\MemberIsNotOwnerException; use OCA\Circles\Exceptions\MembersLimitException; use OCA\Circles\Model\Circle; +use OCA\Circles\Model\GlobalScale\GSEvent; use OCA\Circles\Model\Member; use OCP\IGroupManager; use OCP\IL10N; @@ -76,6 +79,9 @@ class CirclesService { /** @var FederatedLinksRequest */ private $federatedLinksRequest; + /** @var GSUpstreamService */ + private $gsUpstreamService; + /** @var EventsService */ private $eventsService; @@ -97,6 +103,7 @@ class CirclesService { * @param MembersRequest $membersRequest * @param SharesRequest $sharesRequest * @param FederatedLinksRequest $federatedLinksRequest + * @param GSUpstreamService $gsUpstreamService * @param EventsService $eventsService * @param CircleProviderRequest $circleProviderRequest * @param MiscService $miscService @@ -110,6 +117,7 @@ class CirclesService { MembersRequest $membersRequest, SharesRequest $sharesRequest, FederatedLinksRequest $federatedLinksRequest, + GSUpstreamService $gsUpstreamService, EventsService $eventsService, CircleProviderRequest $circleProviderRequest, MiscService $miscService @@ -122,6 +130,7 @@ class CirclesService { $this->membersRequest = $membersRequest; $this->sharesRequest = $sharesRequest; $this->federatedLinksRequest = $federatedLinksRequest; + $this->gsUpstreamService = $gsUpstreamService; $this->eventsService = $eventsService; $this->circleProviderRequest = $circleProviderRequest; $this->miscService = $miscService; @@ -139,7 +148,8 @@ class CirclesService { * @return Circle * @throws CircleAlreadyExistsException * @throws CircleTypeDisabledException - * @throws \OCA\Circles\Exceptions\MemberAlreadyExistsException + * @throws MemberAlreadyExistsException + * @throws Exception */ public function createCircle($type, $name, string $ownerId = '') { $type = $this->convertTypeStringToBitValue($type); @@ -163,14 +173,24 @@ class CirclesService { $ownerId = $this->userId; } - try { - $this->circlesRequest->createCircle($circle, $ownerId); - $this->membersRequest->createMember($circle->getOwner()); - } catch (CircleAlreadyExistsException $e) { - throw $e; + if (!$this->circlesRequest->isCircleUnique($circle, $ownerId)) { + throw new CircleAlreadyExistsException( + $this->l10n->t('A circle with that name exists') + ); } - $this->eventsService->onCircleCreation($circle); + $circle->generateUniqueId(); + + $owner = new Member($ownerId, Member::TYPE_USER); + $owner->setCircleId($circle->getUniqueId()) + ->setLevel(Member::LEVEL_OWNER) + ->setStatus(Member::STATUS_MEMBER); + $circle->setOwner($owner) + ->setViewer($owner); + + $event = new GSEvent(GSEvent::CIRCLE_CREATE, true); + $event->setCircle($circle); + $this->gsUpstreamService->newEvent($event); return $circle; } @@ -225,7 +245,7 @@ class CirclesService { public function detailsCircle($circleUniqueId, $forceAll = false) { try { - $circle = $this->circlesRequest->getCircle($circleUniqueId, $this->userId, $forceAll); + $circle = $this->circlesRequest->getCircle($circleUniqueId, $this->userId, '', $forceAll); if ($this->viewerIsAdmin() || $circle->getHigherViewer() ->isLevel(Member::LEVEL_MEMBER) @@ -235,7 +255,7 @@ class CirclesService { $this->detailsCircleLinkedGroups($circle); $this->detailsCircleFederatedCircles($circle); } - } catch (\Exception $e) { + } catch (Exception $e) { throw $e; } @@ -264,6 +284,7 @@ class CirclesService { /** + * // TODO - check this on GS setup * get the Linked Group list and add the result to the Circle. * * @param Circle $circle @@ -310,37 +331,24 @@ class CirclesService { * @param array $settings * * @return Circle - * @throws \Exception + * @throws Exception */ - public function settingsCircle($circleUniqueId, $settings) { - - try { - $circle = $this->circlesRequest->getCircle($circleUniqueId, $this->userId); - $this->hasToBeOwner($circle->getHigherViewer()); - - $oldSettings = array_merge( - $circle->getSettings(), - [ - 'circle_name' => $circle->getName(), - 'circle_desc' => $circle->getDescription(), - ] - ); - - if (!$this->viewerIsAdmin()) { - $settings['members_limit'] = $circle->getSetting('members_limit'); - } + public function settingsCircle(string $circleUniqueId, array $settings) { + $circle = $this->circlesRequest->getCircle($circleUniqueId, $this->userId); + $this->hasToBeOwner($circle->getHigherViewer()); - $ak = array_keys($settings); - foreach ($ak AS $k) { - $circle->setSetting($k, $settings[$k]); - } + if (!$this->viewerIsAdmin()) { + $settings['members_limit'] = $circle->getSetting('members_limit'); + } - $this->circlesRequest->updateCircle($circle, $this->userId); + // can only be run from the instance of the circle's owner. + $event = new GSEvent(GSEvent::CIRCLE_UPDATE); + $event->setCircle($circle); + $event->getData() + ->sBool('local_admin', $this->viewerIsAdmin()) + ->sArray('settings', $settings); - $this->eventsService->onSettingsChange($circle, $oldSettings); - } catch (\Exception $e) { - throw $e; - } + $this->gsUpstreamService->newEvent($event); return $circle; } @@ -352,24 +360,20 @@ class CirclesService { * @param string $circleUniqueId * * @return null|Member - * @throws \Exception + * @throws Exception */ public function joinCircle($circleUniqueId) { - try { $circle = $this->circlesRequest->getCircle($circleUniqueId, $this->userId); - $member = $this->membersRequest->getFreshNewMember( - $circleUniqueId, $this->userId, Member::TYPE_USER + $circleUniqueId, $this->userId, Member::TYPE_USER, '' ); - $member->hasToBeAbleToJoinTheCircle(); - $this->checkThatCircleIsNotFull($circle); - - $member->joinCircle($circle->getType()); - $this->membersRequest->updateMember($member); - $this->eventsService->onMemberNew($circle, $member); - } catch (\Exception $e) { + $event = new GSEvent(GSEvent::MEMBER_JOIN); + $event->setCircle($circle); + $event->setMember($member); + $this->gsUpstreamService->newEvent($event); + } catch (Exception $e) { throw $e; } @@ -383,20 +387,16 @@ class CirclesService { * @param string $circleUniqueId * * @return null|Member - * @throws \Exception + * @throws Exception */ public function leaveCircle($circleUniqueId) { - $circle = $this->circlesRequest->getCircle($circleUniqueId, $this->userId); $member = $circle->getViewer(); - $member->hasToBeMemberOrAlmost(); - $member->cantBeOwner(); - - $this->eventsService->onMemberLeaving($circle, $member); - - $this->membersRequest->removeMember($member); - $this->sharesRequest->removeSharesFromMember($member); + $event = new GSEvent(GSEvent::MEMBER_LEAVE); + $event->setCircle($circle); + $event->setMember($member); + $this->gsUpstreamService->newEvent($event); return $member; } @@ -411,10 +411,10 @@ class CirclesService { * * @throws CircleDoesNotExistException * @throws MemberIsNotOwnerException - * @throws \OCA\Circles\Exceptions\ConfigNoCircleAvailableException + * @throws ConfigNoCircleAvailableException + * @throws Exception */ public function removeCircle($circleUniqueId, bool $force = false) { - if ($force) { $circle = $this->circlesRequest->forceGetCircle($circleUniqueId); } else { @@ -422,11 +422,12 @@ class CirclesService { $this->hasToBeOwner($circle->getHigherViewer()); } + // removing a Circle is done only by owner, so can already be done by local user, or admin, or occ + // at this point, we already know that all condition are filled. we can force it. + $event = new GSEvent(GSEvent::CIRCLE_DESTROY, false, true); + $event->setCircle($circle); - $this->eventsService->onCircleDestruction($circle); - - $this->membersRequest->removeAllFromCircle($circleUniqueId); - $this->circlesRequest->destroyCircle($circleUniqueId); + $this->gsUpstreamService->newEvent($event); } @@ -442,6 +443,7 @@ class CirclesService { /** + * // TODO - check this on GS setup * When a user is removed. * Before deleting a user from the cloud, we assign a new owner to his Circles. * Remove the Circle if it has no admin. @@ -467,6 +469,7 @@ class CirclesService { /** + * // TODO - check this on GS setup * switchOlderAdminToOwner(); * * @param Circle $circle @@ -528,7 +531,7 @@ class CirclesService { $ext = '.png'; } - $urlGen = \OC::$server->getURLGenerator(); + $urlGen = OC::$server->getURLGenerator(); switch ($type) { case Circle::CIRCLES_PERSONAL: return $urlGen->getAbsoluteURL( @@ -580,7 +583,6 @@ class CirclesService { * @throws MembersLimitException */ public function checkThatCircleIsNotFull(Circle $circle) { - $members = $this->membersRequest->forceGetMembers( $circle->getUniqueId(), Member::LEVEL_MEMBER, true ); @@ -604,7 +606,7 @@ class CirclesService { /** * @return bool */ - public function viewerIsAdmin() { + public function viewerIsAdmin(): bool { if ($this->userId === '') { return false; } diff --git a/lib/Service/ConfigService.php b/lib/Service/ConfigService.php index 1b490cd6..7aee0684 100644 --- a/lib/Service/ConfigService.php +++ b/lib/Service/ConfigService.php @@ -26,6 +26,7 @@ namespace OCA\Circles\Service; +use OCA\Circles\Exceptions\GSStatusException; use OCA\Circles\Model\Circle; use OCP\IConfig; use OCP\IRequest; @@ -39,6 +40,7 @@ class ConfigService { const CIRCLES_STILL_FRONTEND = 'still_frontend'; const CIRCLES_SWAP_TO_TEAMS = 'swap_to_teams'; const CIRCLES_ALLOW_FEDERATED_CIRCLES = 'allow_federated'; + const CIRCLES_GS_ENABLED = 'gs_enabled'; const CIRCLES_MEMBERS_LIMIT = 'members_limit'; const CIRCLES_ACCOUNTS_ONLY = 'accounts_only'; const CIRCLES_ALLOW_LINKED_GROUPS = 'allow_linked_groups'; @@ -52,6 +54,12 @@ class ConfigService { const CIRCLES_TEST_ASYNC_HAND = 'test_async_hand'; const CIRCLES_TEST_ASYNC_COUNT = 'test_async_count'; + const GS_ENABLED = 'enabled'; + const GS_MODE = 'mode'; + const GS_KEY = 'key'; + const GS_LOOKUP = 'lookup'; + + private $defaults = [ self::CIRCLES_ALLOW_CIRCLES => Circle::CIRCLES_ALL, self::CIRCLES_CONTACT_BACKEND => '0', @@ -62,6 +70,7 @@ class ConfigService { self::CIRCLES_MEMBERS_LIMIT => '50', self::CIRCLES_ALLOW_LINKED_GROUPS => '0', self::CIRCLES_ALLOW_FEDERATED_CIRCLES => '0', + self::CIRCLES_GS_ENABLED => '0', self::CIRCLES_ALLOW_NON_SSL_LINKS => '0', self::CIRCLES_NON_SSL_LOCAL => '0', self::CIRCLES_SELF_SIGNED => '0', @@ -428,8 +437,72 @@ class ConfigService { } + /** + * @param string $type + * + * @throws GSStatusException + */ + public function getGSStatus(string $type = '') { + $enabled = $this->config->getSystemValueBool('gs.enabled', false); + $lookup = $this->config->getSystemValue('lookup_server', ''); + + if ($lookup === '' || !$enabled) { + if ($type === self::GS_ENABLED) { + return false; + } + + throw new GSStatusException('GS and lookup are not configured : ' . $lookup . ', ' . $enabled); + } + + $clef = $this->config->getSystemValue('gss.jwt.key', ''); + $mode = $this->config->getSystemValue('gss.mode', ''); + + switch ($type) { + case self::GS_ENABLED: + return $enabled; + + case self::GS_MODE: + return $mode; + + case self::GS_KEY: + return $clef; + + case self::GS_LOOKUP: + return $lookup; + } + + return [ + self::GS_ENABLED => $enabled, + self::GS_LOOKUP => $lookup, + self::GS_MODE => $clef, + self::GS_KEY => $mode, + ]; + } + + + /** + * @return array + */ + public function getTrustedDomains(): array { + $domains = []; + foreach ($this->config->getSystemValue('trusted_domains', []) as $v) { + $domains[] = $v; + } + + return $domains; + } + + + /** + * @return string + */ + public function getLocalCloudId(): string { + return $this->getTrustedDomains()[0]; + } + public function getInstanceId() { return $this->config->getSystemValue('instanceid'); } } + diff --git a/lib/Service/DavService.php b/lib/Service/DavService.php index 448bb221..54b19689 100644 --- a/lib/Service/DavService.php +++ b/lib/Service/DavService.php @@ -400,7 +400,7 @@ class DavService { $circle->setContactGroupName($group); try { - $this->circlesRequest->createCircle($circle, $davCard->getOwner()); + $this->circlesRequest->createCircle($circle); $member = new Member($davCard->getOwner(), Member::TYPE_USER, $circle->getUniqueId()); $member->setLevel(Member::LEVEL_OWNER); $member->setStatus(Member::STATUS_MEMBER); diff --git a/lib/Service/GSDownstreamService.php b/lib/Service/GSDownstreamService.php new file mode 100644 index 00000000..3b8cf7bc --- /dev/null +++ b/lib/Service/GSDownstreamService.php @@ -0,0 +1,164 @@ +<?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 2017 + * @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 Exception; +use OCA\Circles\Db\CirclesRequest; +use OCA\Circles\Db\GSEventsRequest; +use OCA\Circles\Exceptions\CircleDoesNotExistException; +use OCA\Circles\Exceptions\ConfigNoCircleAvailableException; +use OCA\Circles\Exceptions\GlobalScaleDSyncException; +use OCA\Circles\Exceptions\GlobalScaleEventException; +use OCA\Circles\Exceptions\GSKeyException; +use OCA\Circles\Exceptions\GSStatusException; +use OCA\Circles\Model\GlobalScale\GSEvent; +use OCP\IURLGenerator; + + +/** + * Class GSDownstreamService + * + * @package OCA\Circles\Service + */ +class GSDownstreamService { + + + /** @var string */ + private $userId = ''; + + /** @var IURLGenerator */ + private $urlGenerator; + + /** @var GSEventsRequest */ + private $gsEventsRequest; + + /** @var CirclesRequest */ + private $circlesRequest; + + /** @var GlobalScaleService */ + private $globalScaleService; + + /** @var ConfigService */ + private $configService; + + /** @var MiscService */ + private $miscService; + + + /** + * GSUpstreamService constructor. + * + * @param $userId + * @param IURLGenerator $urlGenerator + * @param GSEventsRequest $gsEventsRequest + * @param CirclesRequest $circlesRequest + * @param GlobalScaleService $globalScaleService + * @param ConfigService $configService + * @param MiscService $miscService + */ + public function __construct( + $userId, + IURLGenerator $urlGenerator, + GSEventsRequest $gsEventsRequest, + CirclesRequest $circlesRequest, + GlobalScaleService $globalScaleService, + ConfigService $configService, + MiscService $miscService + ) { + $this->userId = $userId; + $this->urlGenerator = $urlGenerator; + $this->gsEventsRequest = $gsEventsRequest; + $this->circlesRequest = $circlesRequest; + $this->globalScaleService = $globalScaleService; + $this->configService = $configService; + $this->miscService = $miscService; + } + + + /** + * @param GSEvent $event + * + * @throws GSKeyException + * @throws GSStatusException + * @throws GlobalScaleEventException + * @throws CircleDoesNotExistException + * @throws ConfigNoCircleAvailableException + * @throws GlobalScaleDSyncException + */ + public function requestedEvent(GSEvent $event) { + $this->globalScaleService->checkEvent($event); + + $gs = $this->globalScaleService->getGlobalScaleEvent($event); + $gs->verify($event, true); + + $this->miscService->log('&&&& 1 ' . json_encode($event)); + + $gs->manage($event); + + $this->miscService->log('&&&& 2 ' . json_encode($event)); + + $this->globalScaleService->asyncBroadcast($event); + } + + + /** + * @param GSEvent $event + * + * @throws CircleDoesNotExistException + * @throws ConfigNoCircleAvailableException + * @throws GSKeyException + * @throws GlobalScaleDSyncException + * @throws GlobalScaleEventException + */ + public function statusEvent(GSEvent $event) { + $this->globalScaleService->checkEvent($event); + + $gs = $this->globalScaleService->getGlobalScaleEvent($event); + $gs->verify($event, false); + $gs->manage($event); + } + + + /** + * @param GSEvent $event + */ + public function onNewEvent(GSEvent $event) { + try { + $this->globalScaleService->checkEvent($event); + + $gs = $this->globalScaleService->getGlobalScaleEvent($event); + $gs->manage($event); + } catch (Exception $e) { + } + } + +} + diff --git a/lib/Service/GSUpstreamService.php b/lib/Service/GSUpstreamService.php new file mode 100644 index 00000000..4fbd0091 --- /dev/null +++ b/lib/Service/GSUpstreamService.php @@ -0,0 +1,534 @@ +<?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 2017 + * @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 daita\MySmallPhpTools\Exceptions\RequestContentException; +use daita\MySmallPhpTools\Exceptions\RequestNetworkException; +use daita\MySmallPhpTools\Exceptions\RequestResultNotJsonException; +use daita\MySmallPhpTools\Exceptions\RequestResultSizeException; +use daita\MySmallPhpTools\Exceptions\RequestServerException; +use daita\MySmallPhpTools\Model\Request; +use daita\MySmallPhpTools\Model\SimpleDataStore; +use daita\MySmallPhpTools\Traits\TArrayTools; +use daita\MySmallPhpTools\Traits\TRequest; +use Exception; +use OCA\Circles\Db\CirclesRequest; +use OCA\Circles\Db\GSEventsRequest; +use OCA\Circles\Db\MembersRequest; +use OCA\Circles\Exceptions\GlobalScaleEventException; +use OCA\Circles\Exceptions\GSStatusException; +use OCA\Circles\Exceptions\JsonException; +use OCA\Circles\Exceptions\ModelException; +use OCA\Circles\GlobalScale\CircleStatus; +use OCA\Circles\Model\Circle; +use OCA\Circles\Model\GlobalScale\GSEvent; +use OCA\Circles\Model\GlobalScale\GSWrapper; +use OCP\IURLGenerator; + + +/** + * Class GSUpstreamService + * + * @package OCA\Circles\Service + */ +class GSUpstreamService { + + + use TRequest; + use TArrayTools; + + + /** @var string */ + private $userId = ''; + + /** @var IURLGenerator */ + private $urlGenerator; + + /** @var GSEventsRequest */ + private $gsEventsRequest; + + /** @var CirclesRequest */ + private $circlesRequest; + + /** @var MembersRequest */ + private $membersRequest; + + /** @var GlobalScaleService */ + private $globalScaleService; + + /** @var ConfigService */ + private $configService; + + /** @var MiscService */ + private $miscService; + + + /** + * GSUpstreamService constructor. + * + * @param $userId + * @param IURLGenerator $urlGenerator + * @param GSEventsRequest $gsEventsRequest + * @param CirclesRequest $circlesRequest + * @param MembersRequest $membersRequest + * @param GlobalScaleService $globalScaleService + * @param ConfigService $configService + * @param MiscService $miscService + */ + public function __construct( + $userId, + IURLGenerator $urlGenerator, + GSEventsRequest $gsEventsRequest, + CirclesRequest $circlesRequest, + MembersRequest $membersRequest, + GlobalScaleService $globalScaleService, + ConfigService $configService, + MiscService $miscService + ) { + $this->userId = $userId; + $this->urlGenerator = $urlGenerator; + $this->gsEventsRequest = $gsEventsRequest; + $this->circlesRequest = $circlesRequest; + $this->membersRequest = $membersRequest; + $this->globalScaleService = $globalScaleService; + $this->configService = $configService; + $this->miscService = $miscService; + } + + + /** + * @param GSEvent $event + * + * @throws Exception + */ + public function newEvent(GSEvent $event) { + try { + $gs = $this->globalScaleService->getGlobalScaleEvent($event); + + $this->fillEvent($event); + if ($this->isLocalEvent($event)) { + $gs->verify($event, true); + $gs->manage($event); + + $this->globalScaleService->asyncBroadcast($event); + } else { + $gs->verify($event); // needed ? as we check event on the 'master' of the circle + $this->confirmEvent($event); + $gs->manage($event); + } + } catch (Exception $e) { + $this->miscService->log( + get_class($e) . ' on new event: ' . $e->getMessage() . ' - ' . json_encode($event), 1 + ); + throw $e; + } + } + + + /** + * @param GSWrapper $wrapper + * @param string $protocol + */ + public function broadcastWrapper(GSWrapper $wrapper, string $protocol): void { + $status = GSWrapper::STATUS_FAILED; + + try { + $this->broadcastEvent($wrapper->getEvent(), $wrapper->getInstance(), $protocol); + $status = GSWrapper::STATUS_DONE; + } catch (RequestContentException | RequestNetworkException | RequestResultSizeException | RequestServerException | RequestResultNotJsonException $e) { + } + + if ($wrapper->getSeverity() === GSEvent::SEVERITY_HIGH) { + $wrapper->setStatus($status); + } else { + $wrapper->setStatus(GSWrapper::STATUS_OVER); + } + + $this->gsEventsRequest->update($wrapper); + } + + + /** + * @param GSEvent $event + * @param string $instance + * @param string $protocol + * + * @throws RequestContentException + * @throws RequestNetworkException + * @throws RequestResultNotJsonException + * @throws RequestResultSizeException + * @throws RequestServerException + */ + public function broadcastEvent(GSEvent $event, string $instance, string $protocol = ''): void { + $this->signEvent($event); + + $path = $this->urlGenerator->linkToRoute('circles.GlobalScale.broadcast'); + $request = new Request($path, Request::TYPE_POST); + + $protocols = ['https', 'http']; + if ($protocol !== '') { + $protocols = [$protocol]; + } + + $request->setProtocols($protocols); + $request->setDataSerialize($event); + + $request->setAddress($instance); + + $data = $this->retrieveJson($request); + $event->setResult(new SimpleDataStore($this->getArray('result', $data, []))); + } + + + /** + * @param GSEvent $event + * + * @throws RequestContentException + * @throws RequestNetworkException + * @throws RequestResultSizeException + * @throws RequestServerException + * @throws RequestResultNotJsonException + * @throws GlobalScaleEventException + */ + public function confirmEvent(GSEvent $event): void { + $this->signEvent($event); + + $circle = $event->getCircle(); + $owner = $circle->getOwner(); + $path = $this->urlGenerator->linkToRoute('circles.GlobalScale.event'); + + $request = new Request($path, Request::TYPE_POST); + if ($this->get('REQUEST_SCHEME', $_SERVER) !== '') { + $request->setProtocols([$_SERVER['REQUEST_SCHEME']]); + } else { + $request->setProtocols(['https', 'http']); + } + $request->setAddressFromUrl($owner->getInstance()); + $request->setDataSerialize($event); + + $result = $this->retrieveJson($request); + $this->miscService->log('result ' . json_encode($result)); + if ($this->getInt('status', $result) === 0) { + throw new GlobalScaleEventException($this->get('error', $result)); + } + } + + + /** + * @param GSEvent $event + * + * @throws GSStatusException + */ + private function fillEvent(GSEvent $event): void { + if (!$this->configService->getGSStatus(ConfigService::GS_ENABLED)) { + return; + } + + $event->setSource($this->configService->getLocalCloudId()); + } + + + /** + * @param GSEvent $event + */ + private function signEvent(GSEvent $event) { + $event->setKey($this->globalScaleService->getKey()); + } + + + /** + * We check that the event can be managed/checked locally or if the owner of the circle belongs to + * an other instance of Nextcloud + * + * @param GSEvent $event + *asyncBroadcast + * + * @return bool + */ + private function isLocalEvent(GSEvent $event): bool { + if ($event->isLocal()) { + return true; + } + + $circle = $event->getCircle(); + $owner = $circle->getOwner(); + if ($owner->getInstance() === '' + || in_array( + $owner->getInstance(), $this->configService->getTrustedDomains() + )) { + return true; + } + + return false; + } + + + /** + * @param string $token + * + * @return GSWrapper[] + * @throws JsonException + * @throws ModelException + */ + public function getEventsByToken(string $token): array { + return $this->gsEventsRequest->getByToken($token); + } + + + /** + * Deprecated ? + * should be used to manage results from events, like sending mails on user creation + * + * @param string $token + */ + public function manageResults(string $token): void { + try { + $wrappers = $this->gsEventsRequest->getByToken($token); + } catch (JsonException | ModelException $e) { + return; + } + + $event = null; + $events = []; + foreach ($wrappers as $wrapper) { + if ($wrapper->getStatus() !== GSWrapper::STATUS_DONE) { + return; + } + + $events[$wrapper->getInstance()] = $event = $wrapper->getEvent(); + } + + if ($event === null) { + return; + } + + try { + $gs = $this->globalScaleService->getGlobalScaleEvent($event); + $gs->result($events); + } catch (GlobalScaleEventException $e) { + } + } + + + /** + * @throws GSStatusException + */ + public function synchronize() { + $this->configService->getGSStatus(); + + $sync = $this->getCirclesToSync(); + $this->synchronizeCircles($sync); + $this->removeDeprecatedCircles(); + + $this->removeDeprecatedEvents(); + } + + + /** + * @param array $circles + * + * @throws GSStatusException + */ + public function synchronizeCircles(array $circles): void { + $event = new GSEvent(GSEvent::GLOBAL_SYNC, true); + $event->setSource($this->configService->getLocalCloudId()); + $event->setData(new SimpleDataStore($circles)); + + foreach ($this->globalScaleService->getInstances() as $instance) { + try { + $this->broadcastEvent($event, $instance); + } catch (RequestContentException | RequestNetworkException | RequestResultSizeException | RequestServerException | RequestResultNotJsonException $e) { + } + } + } + + + /** + * @return Circle[] + */ + private function getCirclesToSync(): array { + $circles = $this->circlesRequest->forceGetCircles(); + + $sync = []; + foreach ($circles as $circle) { + if ($circle->getOwner() + ->getInstance() !== '' + || $circle->getType() === Circle::CIRCLES_PERSONAL) { + continue; + } + + $members = $this->membersRequest->forceGetMembers($circle->getUniqueId()); + $circle->setMembers($members); + + $sync[] = $circle; + } + + return $sync; + } + + + /** + * + */ + private function removeDeprecatedCircles() { + $knownCircles = $this->circlesRequest->forceGetCircles(); + foreach ($knownCircles as $knownItem) { + if ($knownItem->getOwner() + ->getInstance() === '') { + continue; + } + + try { + $this->checkCircle($knownItem); + } catch (GSStatusException $e) { + } + } + } + + + /** + * @param Circle $circle + * + * @throws GSStatusException + */ + private function checkCircle(Circle $circle): void { + $status = $this->confirmCircleStatus($circle); + + if (!$status) { + $this->circlesRequest->destroyCircle($circle->getUniqueId()); + $this->membersRequest->removeAllFromCircle($circle->getUniqueId()); + } + } + + + /** + * @param Circle $circle + * + * @return bool + * @throws GSStatusException + */ + public function confirmCircleStatus(Circle $circle): bool { + $event = new GSEvent(GSEvent::CIRCLE_STATUS, true); + $event->setSource($this->configService->getLocalCloudId()); + $event->setCircle($circle); + + $this->signEvent($event); + + $path = $this->urlGenerator->linkToRoute('circles.GlobalScale.status'); + $request = new Request($path, Request::TYPE_POST); + + $request->setProtocols(['https', 'http']); + $request->setDataSerialize($event); + + $requestIssue = false; + $notFound = false; + $foundWithNoOwner = false; + foreach ($this->globalScaleService->getInstances() as $instance) { + $request->setAddress($instance); + + try { + $result = $this->retrieveJson($request); + $this->miscService->log('result: ' . json_encode($result)); + if ($this->getInt('status', $result, 0) !== 1) { + throw new RequestContentException('result status is not good'); + } + + $status = $this->getInt('success.data.status', $result); + + // if error, we assume the circle might still exist. + if ($status === CircleStatus::STATUS_ERROR) { + return true; + } + + if ($status === CircleStatus::STATUS_OK) { + return true; + } + + // TODO: check the data.supposedOwner entry. + if ($status === CircleStatus::STATUS_NOT_OWNER) { + $foundWithNoOwner = true; + } + + if ($status === CircleStatus::STATUS_NOT_FOUND) { + $notFound = true; + } + + } catch (RequestContentException + | RequestNetworkException + | RequestResultNotJsonException + | RequestResultSizeException + | RequestServerException $e) { + $requestIssue = true; + // TODO: log instances that have network issue, after too many tries (7d), remove this circle. + continue; + } + } + + // if no request issue, we can imagine that the instance that owns the circle is down. + // We'll wait for more information (cf request exceptions management); + if ($requestIssue) { + return true; + } + + // circle were not found in any other instances, we can easily says that the circle does not exists anymore + if ($notFound && !$foundWithNoOwner) { + return false; + } + + // circle were found everywhere but with no owner on every instance. we need to assign a new owner. + // This should be done by checking admin rights. if no admin rights, let's assume that circle should be removed. + if (!$notFound && $foundWithNoOwner) { + // TODO: assign a new owner and check that when changing owner, we do check that the destination instance is updated FOR SURE! + return true; + } + + // some instances returned notFound, some returned circle with no owner. let's assume the circle is deprecated. + return false; + } + + /** + * @throws GSStatusException + */ + public function syncEvents() { + + } + + /** + * + */ + private function removeDeprecatedEvents() { +// $this->deprecatedEvents(); + + } + + +} + diff --git a/lib/Service/GlobalScaleService.php b/lib/Service/GlobalScaleService.php new file mode 100644 index 00000000..4b5ddf07 --- /dev/null +++ b/lib/Service/GlobalScaleService.php @@ -0,0 +1,226 @@ +<?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 2019 + * @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 daita\MySmallPhpTools\Exceptions\RequestContentException; +use daita\MySmallPhpTools\Exceptions\RequestNetworkException; +use daita\MySmallPhpTools\Exceptions\RequestResultNotJsonException; +use daita\MySmallPhpTools\Exceptions\RequestResultSizeException; +use daita\MySmallPhpTools\Exceptions\RequestServerException; +use daita\MySmallPhpTools\Model\Request; +use daita\MySmallPhpTools\Model\SimpleDataStore; +use daita\MySmallPhpTools\Traits\TRequest; +use daita\MySmallPhpTools\Traits\TStringTools; +use OC; +use OCA\Circles\Db\GSEventsRequest; +use OCA\Circles\Exceptions\GlobalScaleEventException; +use OCA\Circles\Exceptions\GSKeyException; +use OCA\Circles\Exceptions\GSStatusException; +use OCA\Circles\GlobalScale\AGlobalScaleEvent; +use OCA\Circles\Model\Circle; +use OCA\Circles\Model\GlobalScale\GSEvent; +use OCA\Circles\Model\GlobalScale\GSWrapper; +use OCP\AppFramework\QueryException; +use OCP\IURLGenerator; + + +/** + * Class GlobalScaleService + * + * @package OCA\Circles\Service + */ +class GlobalScaleService { + + + use TRequest; + use TStringTools; + + + /** @var IURLGenerator */ + private $urlGenerator; + + /** @var GSEventsRequest */ + private $gsEventsRequest; + + /** @var ConfigService */ + private $configService; + + /** @var MiscService */ + private $miscService; + + + /** + * GlobalScaleService constructor. + * + * @param IURLGenerator $urlGenerator + * @param GSEventsRequest $gsEventsRequest + * @param ConfigService $configService + * @param MiscService $miscService + */ + public function __construct( + IURLGenerator $urlGenerator, + GSEventsRequest $gsEventsRequest, + ConfigService $configService, + MiscService $miscService + ) { + $this->urlGenerator = $urlGenerator; + $this->gsEventsRequest = $gsEventsRequest; + $this->configService = $configService; + $this->miscService = $miscService; + } + + + /** + * @param GSEvent $event + * + * @throws GSStatusException + */ + public function asyncBroadcast(GSEvent $event): void { + if (!$this->configService->getGSStatus(ConfigService::GS_ENABLED)) { + return; + } + + $wrapper = new GSWrapper(); + $wrapper->setEvent($event); + $wrapper->setToken($this->uuid()); + $wrapper->setCreation(time()); + $wrapper->setSeverity($event->getSeverity()); + + foreach ($this->getInstances() as $instance) { + $wrapper->setInstance($instance); + $wrapper = $this->gsEventsRequest->create($wrapper); + } + + $path = $this->urlGenerator->linkToRoute( + 'circles.GlobalScale.asyncBroadcast', ['token' => $wrapper->getToken()] + ); + + $request = new Request($path, Request::TYPE_PUT); + + $baseUrl = $this->urlGenerator->getBaseUrl(); + if (substr($baseUrl, 0, 16) === 'http://localhost') { + $request->setBaseUrl(substr($baseUrl, 16)); + $request->setAddress($this->configService->getLocalCloudId()); + $request->setProtocols(['https', 'http']); + } else { + $request->setAddressFromUrl($baseUrl); + } + + try { + $this->doRequest($request); + } catch (RequestContentException | RequestNetworkException | RequestResultSizeException | RequestServerException $e) { + } + } + + + /** + * @param GSEvent $event + * + * @return AGlobalScaleEvent + * @throws GlobalScaleEventException + */ + public function getGlobalScaleEvent(GSEvent $event): AGlobalScaleEvent { + $class = '\OCA\Circles\\' . $event->getType(); + try { + $gs = OC::$server->query($class); + if (!$gs instanceof AGlobalScaleEvent) { + throw new GlobalScaleEventException($class . ' not an AGlobalScaleEvent'); + } + + return $gs; + } catch (QueryException $e) { + throw new GlobalScaleEventException('AGlobalScaleEvent ' . $class . ' not found'); + } + } + + + /** + * @return string + */ + public function getKey(): string { + // TODO: sign event with real and temp key. + return 'abcd'; + } + + + /** + * @param string $key + * + * @throws GSKeyException + */ + public function checkKey(string $key) { + if ($key !== $this->getKey()) { + throw new GSKeyException('invalid key'); + } + } + + + /** + * @param GSEvent $event + * + * @throws GSKeyException + */ + public function checkEvent(GSEvent $event): void { + $this->checkKey($event->getKey()); + } + + + /** + * @param bool $all + * + * @return array + * @throws GSStatusException + */ + public function getInstances(bool $all = false): array { + /** @var string $lookup */ + $lookup = $this->configService->getGSStatus(ConfigService::GS_LOOKUP); + + $request = new Request('/instances', Request::TYPE_GET); + $request->setAddressFromUrl($lookup); + + try { + $instances = $this->retrieveJson($request); + } catch (RequestContentException | RequestNetworkException | RequestResultSizeException | RequestServerException | RequestResultNotJsonException $e) { + $this->miscService->log('Issue while retrieving instances from lookup: ' . $e->getMessage()); + + return []; + } + + if ($all) { + return $instances; + } + + return array_diff($instances, $this->configService->getTrustedDomains()); + } + + + +} diff --git a/lib/Service/MembersService.php b/lib/Service/MembersService.php index d4246ad6..60b85c09 100644 --- a/lib/Service/MembersService.php +++ b/lib/Service/MembersService.php @@ -41,12 +41,11 @@ use OCA\Circles\Exceptions\ConfigNoCircleAvailableException; use OCA\Circles\Exceptions\EmailAccountInvalidFormatException; use OCA\Circles\Exceptions\GroupDoesNotExistException; use OCA\Circles\Exceptions\MemberAlreadyExistsException; +use OCA\Circles\Exceptions\MemberCantJoinCircleException; use OCA\Circles\Exceptions\MemberDoesNotExistException; use OCA\Circles\Exceptions\MemberIsNotModeratorException; -use OCA\Circles\Exceptions\MemberIsOwnerException; -use OCA\Circles\Exceptions\MemberTypeCantEditLevelException; -use OCA\Circles\Exceptions\ModeratorIsNotHighEnoughException; use OCA\Circles\Model\Circle; +use OCA\Circles\Model\GlobalScale\GSEvent; use OCA\Circles\Model\Member; use OCP\IL10N; use OCP\IUserManager; @@ -89,6 +88,9 @@ class MembersService { /** @var EventsService */ private $eventsService; + /** @var GSUpstreamService */ + private $gsUpstreamService; + /** @var FileSharingBroadcaster */ private $fileSharingBroadcaster; @@ -115,7 +117,8 @@ class MembersService { $userId, IL10N $l10n, IUserManager $userManager, ConfigService $configService, CirclesRequest $circlesRequest, MembersRequest $membersRequest, SharesRequest $sharesRequest, TokensRequest $tokensRequest, CirclesService $circlesService, EventsService $eventsService, - FileSharingBroadcaster $fileSharingBroadcaster, MiscService $miscService + GSUpstreamService $gsUpstreamService, FileSharingBroadcaster $fileSharingBroadcaster, + MiscService $miscService ) { $this->userId = $userId; $this->l10n = $l10n; @@ -127,6 +130,7 @@ class MembersService { $this->tokensRequest = $tokensRequest; $this->circlesService = $circlesService; $this->eventsService = $eventsService; + $this->gsUpstreamService = $gsUpstreamService; $this->fileSharingBroadcaster = $fileSharingBroadcaster; $this->miscService = $miscService; } @@ -140,14 +144,14 @@ class MembersService { * @param string $circleUniqueId * @param $ident * @param int $type + * @param string $instance * * @param bool $force * * @return array * @throws Exception */ - public function addMember($circleUniqueId, $ident, $type, bool $force = false) { - + public function addMember($circleUniqueId, $ident, $type, string $instance, bool $force = false) { if ($force === true) { $circle = $this->circlesRequest->forceGetCircle($circleUniqueId); } else { @@ -157,7 +161,7 @@ class MembersService { } if (!$this->addMassiveMembers($circle, $ident, $type)) { - $this->addSingleMember($circle, $ident, $type); + $this->addSingleMember($circle, $ident, $type, $instance, $force); } return $this->membersRequest->getMembers($circle->getUniqueId(), $circle->getHigherViewer(), $force); @@ -171,23 +175,24 @@ class MembersService { * @param string $ident * @param int $type * - * @throws MemberAlreadyExistsException - * @throws Exception + * @param string $instance + * @param bool $force + * + * @throws EmailAccountInvalidFormatException + * @throws NoUserException */ - private function addSingleMember(Circle $circle, $ident, $type) { - $this->verifyIdentBasedOnItsType($ident, $type); - - $member = $this->membersRequest->getFreshNewMember($circle->getUniqueId(), $ident, $type); - $member->hasToBeInviteAble(); + private function addSingleMember(Circle $circle, $ident, $type, $instance = '', bool $force = false) { - $this->circlesService->checkThatCircleIsNotFull($circle); + $this->verifyIdentBasedOnItsType($ident, $type, $instance); - $this->addMemberBasedOnItsType($circle, $member); + $member = $this->membersRequest->getFreshNewMember($circle->getUniqueId(), $ident, $type, $instance); - $this->membersRequest->updateMember($member); - $this->fileSharingBroadcaster->sendMailAboutExistingShares($circle, $member); + $event = new GSEvent(GSEvent::MEMBER_ADD, false, $force); + $event->setSeverity(GSEvent::SEVERITY_HIGH); - $this->eventsService->onMemberNew($circle, $member); + $event->setCircle($circle); + $event->setMember($member); + $this->gsUpstreamService->newEvent($event); } @@ -202,7 +207,6 @@ class MembersService { * @throws Exception */ private function addMassiveMembers(Circle $circle, $ident, $type) { - if ($type === Member::TYPE_GROUP) { return $this->addGroupMembers($circle, $ident); } @@ -221,9 +225,10 @@ class MembersService { * @param Circle $circle * @param Member $member * - * @throws Exception + * @throws CircleTypeNotValidException + * @throws MemberCantJoinCircleException */ - private function addMemberBasedOnItsType(Circle $circle, Member &$member) { + public function addMemberBasedOnItsType(Circle $circle, Member &$member) { $this->addLocalMember($circle, $member); $this->addEmailAddress($member); $this->addContact($member); @@ -234,7 +239,8 @@ class MembersService { * @param Circle $circle * @param Member $member * - * @throws Exception + * @throws CircleTypeNotValidException + * @throws MemberCantJoinCircleException */ private function addLocalMember(Circle $circle, Member $member) { @@ -254,8 +260,6 @@ class MembersService { * add mail address as contact. * * @param Member $member - * - * @throws Exception */ private function addEmailAddress(Member $member) { @@ -268,11 +272,10 @@ class MembersService { /** + * // TODO - check this on GS setup * Add contact as member. * * @param Member $member - * - * @throws Exception */ private function addContact(Member $member) { @@ -285,15 +288,22 @@ class MembersService { /** + * // TODO - check this on GS setup * Verify the availability of an ident, based on its type. * * @param string $ident * @param int $type + * @param string $instance * - * @throws Exception + * @throws EmailAccountInvalidFormatException + * @throws NoUserException */ - private function verifyIdentBasedOnItsType(&$ident, $type) { - $this->verifyIdentLocalMember($ident, $type); + public function verifyIdentBasedOnItsType(&$ident, $type, string $instance = '') { + if ($instance === $this->configService->getLocalCloudId()) { + $instance = ''; + } + + $this->verifyIdentLocalMember($ident, $type, $instance); $this->verifyIdentEmailAddress($ident, $type); $this->verifyIdentContact($ident, $type); } @@ -305,17 +315,21 @@ class MembersService { * @param $ident * @param $type * + * @param string $instance + * * @throws NoUserException */ - private function verifyIdentLocalMember(&$ident, $type) { + private function verifyIdentLocalMember(&$ident, $type, string $instance = '') { if ($type !== Member::TYPE_USER) { return; } - try { - $ident = $this->miscService->getRealUserId($ident); - } catch (NoUserException $e) { - throw new NoUserException($this->l10n->t("This user does not exist")); + if ($instance === '') { + try { + $ident = $this->miscService->getRealUserId($ident); + } catch (NoUserException $e) { + throw new NoUserException($this->l10n->t("This user does not exist")); + } } } @@ -407,6 +421,7 @@ class MembersService { /** + * // TODO - check this on GS setup * @param Circle $circle * @param string $mails * @@ -480,6 +495,7 @@ class MembersService { * @param string $circleUniqueId * @param string $name * @param int $type + * @param string $instance * @param int $level * @param bool $force * @@ -488,11 +504,11 @@ class MembersService { * @throws CircleTypeNotValidException * @throws ConfigNoCircleAvailableException * @throws MemberDoesNotExistException - * @throws MemberTypeCantEditLevelException * @throws Exception */ - public function levelMember($circleUniqueId, $name, $type, $level, bool $force = false) { - + public function levelMember( + string $circleUniqueId, string $name, int $type, string $instance, int $level, bool $force = false + ) { $level = (int)$level; if ($force === false) { $circle = $this->circlesRequest->getCircle($circleUniqueId, $this->userId); @@ -506,9 +522,16 @@ class MembersService { ); } - $member = $this->membersRequest->forceGetMember($circle->getUniqueId(), $name, $type); - $member->levelHasToBeEditable(); - $this->updateMemberLevel($circle, $member, $level, $force); + $member = $this->membersRequest->forceGetMember($circle->getUniqueId(), $name, $type, $instance); + if ($member->getLevel() !== $level) { + $event = new GSEvent(GSEvent::MEMBER_LEVEL, false, $force); + $event->setCircle($circle); + + $event->getData() + ->sInt('level', $level); + $event->setMember($member); + $this->gsUpstreamService->newEvent($event); + } if ($force === false) { return $this->membersRequest->getMembers( @@ -522,95 +545,22 @@ class MembersService { /** - * @param Circle $circle - * @param Member $member - * @param $level - * @param bool $force - * - * @throws Exception - */ - private function updateMemberLevel(Circle $circle, Member $member, $level, bool $force = false) { - if ($member->getLevel() === $level) { - return; - } - - if ($level === Member::LEVEL_OWNER) { - $this->switchOwner($circle, $member, $force); - } else { - $this->editMemberLevel($circle, $member, $level, $force); - } - - $this->eventsService->onMemberLevel($circle, $member); - } - - - /** - * @param Circle $circle - * @param Member $member - * @param $level - * @param bool $force - * - * @throws Exception - */ - private function editMemberLevel(Circle $circle, Member &$member, $level, bool $force = false) { - if ($force === false) { - $isMod = $circle->getHigherViewer(); - $isMod->hasToBeModerator(); - $isMod->hasToBeHigherLevel($level); - - $member->hasToBeMember(); - $isMod->hasToBeHigherLevel($member->getLevel()); - } - - $member->cantBeOwner(); - - $member->setLevel($level); - $this->membersRequest->updateMember($member); - } - - /** - * @param Circle $circle - * @param Member $member - * @param bool $force - * - * @throws Exception - */ - private function switchOwner(Circle $circle, Member &$member, bool $force = false) { - if ($force === false) { - $isMod = $circle->getHigherViewer(); - - // should already be possible from an NCAdmin, but not enabled in the frontend. - $this->circlesService->hasToBeOwner($isMod); - } else { - $isMod = $circle->getOwner(); - } - - $member->hasToBeMember(); - $member->cantBeOwner(); - - $member->setLevel(Member::LEVEL_OWNER); - $this->membersRequest->updateMember($member); - - $isMod->setLevel(Member::LEVEL_ADMIN); - $this->membersRequest->updateMember($isMod); - } - - - /** * @param string $circleUniqueId * @param string $name - * @param $type + * @param int $type + * @param string $instance * @param bool $force * - * @return array + * @return Member[] * @throws CircleDoesNotExistException * @throws ConfigNoCircleAvailableException * @throws MemberDoesNotExistException * @throws MemberIsNotModeratorException - * @throws MemberIsOwnerException - * @throws ModeratorIsNotHighEnoughException + * @throws Exception */ - public function removeMember($circleUniqueId, $name, $type, bool $force = false) { + public function removeMember( + string $circleUniqueId, string $name, int $type, string $instance, bool $force = false + ): array { if ($force === false) { $circle = $this->circlesRequest->getCircle($circleUniqueId, $this->userId); @@ -620,20 +570,12 @@ class MembersService { $circle = $this->circlesRequest->forceGetCircle($circleUniqueId); } - $member = $this->membersRequest->forceGetMember($circleUniqueId, $name, $type); - $member->hasToBeMemberOrAlmost(); - $member->cantBeOwner(); - - if ($force === false) { - $circle->getHigherViewer() - ->hasToBeHigherLevel($member->getLevel()); - } - - $this->eventsService->onMemberLeaving($circle, $member); + $member = $this->membersRequest->forceGetMember($circleUniqueId, $name, $type, $instance); - $this->membersRequest->removeMember($member); - $this->sharesRequest->removeSharesFromMember($member); - $this->tokensRequest->removeTokensFromMember($member); + $event = new GSEvent(GSEvent::MEMBER_REMOVE, false, $force); + $event->setCircle($circle); + $event->setMember($member); + $this->gsUpstreamService->newEvent($event); if ($force === false) { return $this->membersRequest->getMembers( @@ -646,11 +588,13 @@ class MembersService { /** + * // TODO - check this on GS setup * When a user is removed, remove him from all Circles * * @param $userId */ public function onUserRemoved($userId) { + // TODO: broadcast the event to all instances $this->membersRequest->removeAllMembershipsFromUser($userId); } diff --git a/lib/Service/SearchService.php b/lib/Service/SearchService.php index 2fff3d30..96112d59 100644 --- a/lib/Service/SearchService.php +++ b/lib/Service/SearchService.php @@ -75,6 +75,7 @@ class SearchService { public function loadSearch() { $this->searchList = [ 'OCA\Circles\Search\LocalUsers', + 'OCA\Circles\Search\GlobalScaleUsers', 'OCA\Circles\Search\LocalGroups', 'OCA\Circles\Search\Contacts' ]; @@ -82,7 +83,6 @@ class SearchService { public function searchGlobal($str) { - $result = []; foreach ($this->searchList as $container) { $searcher = \OC::$server->query((string)$container); @@ -98,4 +98,4 @@ class SearchService { return $result; } -}
\ No newline at end of file +} diff --git a/lib/Service/ShareService.php b/lib/Service/ShareService.php index 5de9ecd8..fa41acf6 100644 --- a/lib/Service/ShareService.php +++ b/lib/Service/ShareService.php @@ -57,8 +57,6 @@ class ShareService { $this->l10n = $l10n; $this->configService = $configService; $this->miscService = $miscService; - - $this->loadSearch(); } @@ -72,4 +70,4 @@ class ShareService { } -}
\ No newline at end of file +} diff --git a/lib/ShareByCircleProvider.php b/lib/ShareByCircleProvider.php index ea38b82d..f4554a2b 100644 --- a/lib/ShareByCircleProvider.php +++ b/lib/ShareByCircleProvider.php @@ -167,7 +167,7 @@ class ShareByCircleProvider extends CircleProviderRequest implements IShareProvi throw $this->errorShareAlreadyExist($share); } -// $share->setToken($this->miscService->uuid(15)); + $share->setToken($this->token(15)); // if ($this->configService->enforcePasswordProtection()) { // $share->setPassword($this->miscService->uuid(15)); // } @@ -413,8 +413,8 @@ class ShareByCircleProvider extends CircleProviderRequest implements IShareProvi $data['share_with'] = sprintf( '%s (%s, %s) [%s]', $data['circle_name'], - $this->l10n->t(Circle::TypeLongString($data['circle_type'])), - $this->miscService->getDisplayName($data['circle_owner']), $data['share_with'] + Circle::TypeLongString($data['circle_type']), + $this->miscService->getDisplayName($data['circle_owner'], true), $data['share_with'] ); @@ -761,8 +761,8 @@ class ShareByCircleProvider extends CircleProviderRequest implements IShareProvi ->setSharedWithDisplayName( sprintf( '%s (%s, %s)', $data['circle_name'], - $this->l10n->t(Circle::TypeLongString($data['circle_type'])), - $this->miscService->getDisplayName($data['circle_owner']) + Circle::TypeLongString($data['circle_type']), + $this->miscService->getDisplayName($data['circle_owner'], true) ) ); } @@ -928,19 +928,44 @@ class ShareByCircleProvider extends CircleProviderRequest implements IShareProvi $qb = $this->dbConnection->getQueryBuilder(); $qb->select('*') - ->from('share') - ->where( - $qb->expr()->orX( - $qb->expr()->eq('share_type', $qb->createNamedParameter(IShare::TYPE_CIRCLE)) - ) - ); + ->from('share') + ->where( + $qb->expr() + ->orX( + $qb->expr() + ->eq('share_type', $qb->createNamedParameter(IShare::TYPE_CIRCLE)) + ) + ); $cursor = $qb->execute(); - while($data = $cursor->fetch()) { + while ($data = $cursor->fetch()) { $share = $this->createShareObject($data); yield $share; } $cursor->closeCursor(); } + + + /** + * @param int $length + * + * @return string + */ + protected function token(int $length = 15): string { + $chars = 'qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM1234567890'; + + $str = ''; + $max = strlen($chars); + for ($i = 0; $i < $length; $i++) { + try { + $str .= $chars[random_int(0, $max - 1)]; + } catch (Exception $e) { + } + } + + return $str; + } + + } |