diff --git a/lib/AppInfo/Application.php b/lib/AppInfo/Application.php index b5298554c..58aac0e8a 100644 --- a/lib/AppInfo/Application.php +++ b/lib/AppInfo/Application.php @@ -41,6 +41,7 @@ use OCA\Circles\Events\CircleMemberAddedEvent; use OCA\Circles\Events\MembershipsCreatedEvent; use OCA\Circles\Events\MembershipsRemovedEvent; +use OCA\Circles\Events\RemovingCircleMemberEvent; use OCA\Circles\Events\RequestingCircleMemberEvent; use OCA\Circles\Handlers\WebfingerHandler; use OCA\Circles\Listeners\DeprecatedListener; @@ -50,7 +51,7 @@ use OCA\Circles\Listeners\Examples\ExampleRequestingCircleMember; use OCA\Circles\Listeners\Files\AddingMember as ListenerFilesAddingMember; use OCA\Circles\Listeners\Files\MemberAdded as ListenerFilesMemberAdded; -use OCA\Circles\Listeners\Files\MembershipsRemoved as ListenerFilesMembershipsRemoved; +use OCA\Circles\Listeners\Files\RemovingMember as ListenerFilesRemovingMember; use OCA\Circles\Listeners\GroupCreated; use OCA\Circles\Listeners\GroupDeleted; use OCA\Circles\Listeners\GroupMemberAdded; @@ -62,13 +63,11 @@ use OCA\Circles\Notification\Notifier; use OCA\Circles\Service\ConfigService; use OCA\Circles\Service\DavService; -//use OCA\Files\App as FilesApp; use OCP\App\ManagerEvent; use OCP\AppFramework\App; use OCP\AppFramework\Bootstrap\IBootContext; use OCP\AppFramework\Bootstrap\IBootstrap; use OCP\AppFramework\Bootstrap\IRegistrationContext; -use OCP\AppFramework\IAppContainer; use OCP\Files\Config\IMountProviderCollection; use OCP\Group\Events\GroupCreatedEvent; use OCP\Group\Events\GroupDeletedEvent; @@ -81,6 +80,8 @@ use Symfony\Component\EventDispatcher\GenericEvent; use Throwable; +//use OCA\Files\App as FilesApp; + require_once __DIR__ . '/../../vendor/autoload.php'; @@ -107,9 +108,6 @@ class Application extends App implements IBootstrap { /** @var ConfigService */ private $configService; - /** @var IAppContainer */ - private $container; - /** * Application constructor. @@ -143,9 +141,7 @@ public function register(IRegistrationContext $context): void { // Local Events (for Files/Shares/Notifications management) $context->registerEventListener(AddingCircleMemberEvent::class, ListenerFilesAddingMember::class); $context->registerEventListener(CircleMemberAddedEvent::class, ListenerFilesMemberAdded::class); - $context->registerEventListener( - MembershipsRemovedEvent::class, ListenerFilesMembershipsRemoved::class - ); + $context->registerEventListener(RemovingCircleMemberEvent::class, ListenerFilesRemovingMember::class); $context->registerEventListener( RequestingCircleMemberEvent::class, ListenerNotificationsRequestingMember::class ); diff --git a/lib/Command/CirclesMemberships.php b/lib/Command/CirclesMemberships.php index 14e164f6f..39e729159 100644 --- a/lib/Command/CirclesMemberships.php +++ b/lib/Command/CirclesMemberships.php @@ -162,7 +162,7 @@ protected function configure() { ->setDescription('index and display memberships for local and federated users') ->addArgument('userId', InputArgument::OPTIONAL, 'userId to generate memberships', '') ->addOption('display-name', '', InputOption::VALUE_NONE, 'display the displayName') - ->addOption('reset', '', InputOption::VALUE_NONE, 'reset memberships') +// ->addOption('reset', '', InputOption::VALUE_NONE, 'reset memberships') ->addOption('all', '', InputOption::VALUE_NONE, 'refresh memberships for all entities') ->addOption( 'type', '', InputOption::VALUE_REQUIRED, 'type of the user', @@ -208,11 +208,11 @@ protected function execute(InputInterface $input, OutputInterface $output): int $type = Member::parseTypeString($input->getOption('type')); $federatedUser = $this->federatedUserService->getFederatedUser($userId, (int)$type); - if ($this->input->getOption('reset')) { - $this->membershipsService->resetMemberships($federatedUser->getSingleId()); - - return 0; - } +// if ($this->input->getOption('reset')) { +// $this->membershipsService->resetMemberships($federatedUser->getSingleId()); +// +// return 0; +// } $output->writeln('Id: ' . $federatedUser->getUserId() . ''); $output->writeln('Instance: ' . $federatedUser->getInstance() . ''); @@ -361,11 +361,11 @@ public function displayLeaf(SimpleDataStore $data, int $lineNumber): string { * @throws RequestBuilderException */ private function manageAllMemberships() { - if ($this->input->getOption('reset')) { - $this->membershipsService->resetMemberships('', true); - - return; - } +// if ($this->input->getOption('reset')) { +// $this->membershipsService->resetMemberships('', true); +// +// return; +// } $this->federatedUserService->bypassCurrentUserCondition(true); diff --git a/lib/Db/ShareTokenRequest.php b/lib/Db/ShareTokenRequest.php index e8030747f..ccf3048a5 100644 --- a/lib/Db/ShareTokenRequest.php +++ b/lib/Db/ShareTokenRequest.php @@ -65,6 +65,22 @@ public function save(ShareToken $token): void { } + /** + * @param ShareToken $shareToken + * + * @return ShareToken + * @throws ShareTokenNotFoundException + */ + public function search(ShareToken $shareToken): ShareToken { + $qb = $this->getTokenSelectSql(); + $qb->limitInt('share_id', $shareToken->getshareId()); + $qb->limitToCircleId($shareToken->getCircleId()); + $qb->limitToSingleId($shareToken->getSingleId()); + + return $this->getItemFromRequest($qb); + } + + /** * @param string $token * @@ -78,5 +94,18 @@ public function getByToken(string $token): ShareToken { return $this->getItemFromRequest($qb); } + + /** + * @param string $singleId + * @param string $circleId + */ + public function removeTokens(string $singleId, string $circleId) { + $qb = $this->getTokenDeleteSql(); + $qb->limitToSingleId($singleId); + $qb->limitToCircleId($circleId); + + $qb->execute(); + } + } diff --git a/lib/Db/ShareWrapperRequest.php b/lib/Db/ShareWrapperRequest.php index aaa7f2870..70bf6ec89 100644 --- a/lib/Db/ShareWrapperRequest.php +++ b/lib/Db/ShareWrapperRequest.php @@ -168,6 +168,7 @@ public function getSharesToCircle( $qb->leftJoinCircle(CoreQueryBuilder::SHARE, null, 'share_with'); + // TODO: filter direct-shares ? $aliasUpstreamMembership = $qb->generateAlias(CoreQueryBuilder::SHARE, CoreQueryBuilder::UPSTREAM_MEMBERSHIPS); $qb->limitToInheritedMemberships(CoreQueryBuilder::SHARE, $circleId, 'share_with'); @@ -176,20 +177,18 @@ public function getSharesToCircle( // $qb->limitToInitiator(CoreRequestBuilder::SHARE, $shareRecipient, 'share_with'); // } - $qb->leftJoinInheritedMembers( - $aliasUpstreamMembership, - 'circle_id', - $qb->generateAlias(CoreQueryBuilder::SHARE, CoreQueryBuilder::INHERITED_BY) - ); - - $aliasMembership = $qb->generateAlias($aliasUpstreamMembership, CoreQueryBuilder::MEMBERSHIPS); - $qb->leftJoinFileCache(CoreQueryBuilder::SHARE); - $qb->leftJoinShareChild(CoreQueryBuilder::SHARE, $aliasMembership); + // TODO: add shareInitiator and shareRecipient to filter the request + if (!is_null($shareRecipient) || $completeDetails) { + $qb->leftJoinInheritedMembers( + $aliasUpstreamMembership, + 'circle_id', + $qb->generateAlias(CoreQueryBuilder::SHARE, CoreQueryBuilder::INHERITED_BY) + ); - if (!is_null($shareInitiator)) { - } + $aliasMembership = $qb->generateAlias($aliasUpstreamMembership, CoreQueryBuilder::MEMBERSHIPS); + $qb->leftJoinFileCache(CoreQueryBuilder::SHARE); + $qb->leftJoinShareChild(CoreQueryBuilder::SHARE, $aliasMembership); - if ($completeDetails) { $qb->generateGroupBy( self::$tables[self::TABLE_MEMBERSHIP], $aliasMembership, diff --git a/lib/Events/AddingCircleMemberEvent.php b/lib/Events/AddingCircleMemberEvent.php index 75c704092..1aeb05a9a 100644 --- a/lib/Events/AddingCircleMemberEvent.php +++ b/lib/Events/AddingCircleMemberEvent.php @@ -47,7 +47,7 @@ * This is a good place if anything needs to be executed when a new member have been added to a Circle. * * If anything needs to be managed on the master instance of the Circle (ie. CircleMemberAddedEvent), please use: - * $event->getFederatedEvent()->addResult(string $key, array $data); + * $event->getFederatedEvent()->setResultEntry(string $key, array $data); * * @package OCA\Circles\Events */ diff --git a/lib/Events/CreatingCircleEvent.php b/lib/Events/CreatingCircleEvent.php index 1702a2a89..7d2a1003e 100644 --- a/lib/Events/CreatingCircleEvent.php +++ b/lib/Events/CreatingCircleEvent.php @@ -48,7 +48,7 @@ * This is a good place if anything needs to be executed when a new Circle has been created. * * If anything needs to be managed on the master instance of the Circle (ie. CircleCreatedEvent), please use: - * $event->getFederatedEvent()->addResult(string $key, array $data); + * $event->getFederatedEvent()->setResultEntry(string $key, array $data); * * @package OCA\Circles\Events */ diff --git a/lib/Events/DestroyingCircleEvent.php b/lib/Events/DestroyingCircleEvent.php index ed1395401..d096dda2f 100644 --- a/lib/Events/DestroyingCircleEvent.php +++ b/lib/Events/DestroyingCircleEvent.php @@ -48,7 +48,7 @@ * This is a good place if anything needs to be executed when a Circle has been destroyed. * * If anything needs to be managed on the master instance of the Circle (ie. CircleDestroyedEvent), please use: - * $event->getFederatedEvent()->addResult(string $key, array $data); + * $event->getFederatedEvent()->setResultEntry(string $key, array $data); * * * @package OCA\Circles\Events */ diff --git a/lib/Events/EditingCircleEvent.php b/lib/Events/EditingCircleEvent.php index d8c2ab76a..2280c0cdc 100644 --- a/lib/Events/EditingCircleEvent.php +++ b/lib/Events/EditingCircleEvent.php @@ -47,7 +47,7 @@ * This is a good place if anything needs to be executed when a circle is edited. * * If anything needs to be managed on the master instance of the Circle (ie. CircleEditedEvent), please use: - * $event->getFederatedEvent()->addResult(string $key, array $data); + * $event->getFederatedEvent()->setResultEntry(string $key, array $data); * * @package OCA\Circles\Events */ diff --git a/lib/Events/EditingCircleMemberEvent.php b/lib/Events/EditingCircleMemberEvent.php index ac2c250b7..5deb30d8f 100644 --- a/lib/Events/EditingCircleMemberEvent.php +++ b/lib/Events/EditingCircleMemberEvent.php @@ -48,7 +48,7 @@ * * If anything needs to be managed on the master instance of the Circle (ie. CircleMemberEditedEvent), please * use: - * $event->getFederatedEvent()->addResult(string $key, array $data); + * $event->getFederatedEvent()->setResultEntry(string $key, array $data); * * @package OCA\Circles\Events */ diff --git a/lib/Events/RemovingCircleMemberEvent.php b/lib/Events/RemovingCircleMemberEvent.php index 0619ae94c..e6b273f4d 100644 --- a/lib/Events/RemovingCircleMemberEvent.php +++ b/lib/Events/RemovingCircleMemberEvent.php @@ -47,7 +47,7 @@ * This is a good place if anything needs to be executed when a member have been removed from a Circle. * * If anything needs to be managed on the master instance of the Circle (ie. CircleMemberRemovedEvent), please use: - * $event->getFederatedEvent()->addResult(string $key, array $data); + * $event->getFederatedEvent()->setResultEntry(string $key, array $data); * * @package OCA\Circles\Events */ diff --git a/lib/Events/RequestingCircleMemberEvent.php b/lib/Events/RequestingCircleMemberEvent.php index fae4d8aa7..75233cb1e 100644 --- a/lib/Events/RequestingCircleMemberEvent.php +++ b/lib/Events/RequestingCircleMemberEvent.php @@ -46,7 +46,7 @@ * This is a good place if anything needs to be executed when a member requests or is invited to a Circle. * * If anything needs to be managed on the master instance of the Circle (ie. CircleMemberRequestedEvent), please use: - * $event->getFederatedEvent()->addResult(string $key, array $data); + * $event->getFederatedEvent()->setResultEntry(string $key, array $data); * * @package OCA\Circles\Events */ diff --git a/lib/Exceptions/ShareTokenAlreadyExistException.php b/lib/Exceptions/ShareTokenAlreadyExistException.php new file mode 100644 index 000000000..70597bfcc --- /dev/null +++ b/lib/Exceptions/ShareTokenAlreadyExistException.php @@ -0,0 +1,45 @@ + + * @copyright 2021 + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OCA\Circles\Exceptions; + + +use Exception; + + +/** + * Class ShareTokenAlreadyExistException + * + * @package OCA\Circles\Exceptions + */ +class ShareTokenAlreadyExistException extends Exception { + +} + diff --git a/lib/FederatedItems/SingleMemberAdd.php b/lib/FederatedItems/SingleMemberAdd.php index 467044624..dcd6a7f3d 100644 --- a/lib/FederatedItems/SingleMemberAdd.php +++ b/lib/FederatedItems/SingleMemberAdd.php @@ -633,6 +633,7 @@ private function memberIsMailbox( } try { + $template = $this->generateMailExitingShares($author, $circle->getName()); $this->fillMailExistingShares($template, $links); $this->sendMailExistingShares($template, $author, $recipient); diff --git a/lib/Listeners/Files/AddingMember.php b/lib/Listeners/Files/AddingMember.php index 1874ee4dd..92ec83a4b 100644 --- a/lib/Listeners/Files/AddingMember.php +++ b/lib/Listeners/Files/AddingMember.php @@ -32,10 +32,17 @@ namespace OCA\Circles\Listeners\Files; +use ArtificialOwl\MySmallPhpTools\Traits\Nextcloud\nc22\TNC22Logger; use ArtificialOwl\MySmallPhpTools\Traits\TStringTools; +use Exception; +use OCA\Circles\AppInfo\Application; use OCA\Circles\Events\AddingCircleMemberEvent; use OCA\Circles\Exceptions\RequestBuilderException; +use OCA\Circles\Exceptions\ShareTokenAlreadyExistException; use OCA\Circles\Model\Member; +use OCA\Circles\Service\ConfigService; +use OCA\Circles\Service\ContactService; +use OCA\Circles\Service\ShareTokenService; use OCA\Circles\Service\ShareWrapperService; use OCP\EventDispatcher\Event; use OCP\EventDispatcher\IEventListener; @@ -50,19 +57,42 @@ class AddingMember implements IEventListener { use TStringTools; + use TNC22Logger; /** @var ShareWrapperService */ private $shareWrapperService; + /** @var ShareTokenService */ + private $shareTokenService; + + /** @var ConfigService */ + private $configService; + + /** @var ContactService */ + private $contactService; + /** * AddingMember constructor. * * @param ShareWrapperService $shareWrapperService + * @param ShareTokenService $shareTokenService + * @param ContactService $contactService + * @param ConfigService $configService */ - public function __construct(ShareWrapperService $shareWrapperService) { + public function __construct( + ShareWrapperService $shareWrapperService, + ShareTokenService $shareTokenService, + ContactService $contactService, + ConfigService $configService + ) { $this->shareWrapperService = $shareWrapperService; + $this->shareTokenService = $shareTokenService; + $this->contactService = $contactService; + $this->configService = $configService; + + $this->setup('app', Application::APP_ID); } @@ -76,20 +106,64 @@ public function handle(Event $event): void { return; } - $bypass = true; + $result = []; $member = $event->getMember(); - if ($member->getUserType() === Member::TYPE_MAIL) { - $bypass = false; - } - if ($bypass) { - return; + if ($member->getUserType() === Member::TYPE_CIRCLE) { + $members = $member->getBasedOn()->getInheritedMembers(); + } else { + $members = [$member]; } $circle = $event->getCircle(); - $files = $this->shareWrapperService->getSharesToCircle($circle->getSingleId()); + $shares = $this->shareWrapperService->getSharesToCircle($circle->getSingleId()); + + /** @var Member[] $members */ + foreach ($members as $member) { + if ($member->getUserType() !== Member::TYPE_MAIL + && $member->getUserType() !== Member::TYPE_CONTACT + ) { + continue; + } + + $files = []; + foreach ($shares as $share) { + try { + $shareToken = $this->shareTokenService->generateShareToken($share, $member); + } catch (ShareTokenAlreadyExistException $e) { + continue; + } + + $share->setShareToken($shareToken); + $files[] = $share; + } + + $result[$member->getId()] = [ + 'shares' => $files, + 'mails' => $this->getMailAddressesFromContact($member) + ]; + } + + $event->getFederatedEvent()->setResultEntry('files', $result); + } + + + /** + * @param Member $member + * + * @return array + */ + private function getMailAddressesFromContact(Member $member): array { + if ($member->getUserType() !== Member::TYPE_CONTACT + || !$this->configService->isLocalInstance($member->getInstance())) { + return []; + } - $event->getFederatedEvent()->addResult('files', $files); + try { + return $this->contactService->getMailAddresses($member->getUserId()); + } catch (Exception $e) { + return []; + } } } diff --git a/lib/Listeners/Files/MemberAdded.php b/lib/Listeners/Files/MemberAdded.php index 146f379af..385202aa4 100644 --- a/lib/Listeners/Files/MemberAdded.php +++ b/lib/Listeners/Files/MemberAdded.php @@ -32,11 +32,23 @@ namespace OCA\Circles\Listeners\Files; +use ArtificialOwl\MySmallPhpTools\Traits\Nextcloud\nc22\TNC22Logger; use ArtificialOwl\MySmallPhpTools\Traits\TStringTools; +use Exception; +use OCA\Circles\AppInfo\Application; use OCA\Circles\Events\CircleMemberAddedEvent; +use OCA\Circles\Exceptions\RequestBuilderException; +use OCA\Circles\Model\Circle; use OCA\Circles\Model\Member; +use OCA\Circles\Model\ShareWrapper; +use OCA\Circles\Service\ShareWrapperService; +use OCP\Defaults; use OCP\EventDispatcher\Event; use OCP\EventDispatcher\IEventListener; +use OCP\IL10N; +use OCP\Mail\IEMailTemplate; +use OCP\Mail\IMailer; +use OCP\Util; /** @@ -48,33 +60,193 @@ class MemberAdded implements IEventListener { use TStringTools; + use TNC22Logger; + + + /** @var IL10N */ + private $l10n; + + /** @var IMailer */ + private $mailer; + + /** @var Defaults */ + private $defaults; + + /** @var ShareWrapperService */ + private $shareWrapperService; + + + /** + * MemberAdded constructor. + * + * @param IL10N $l10n + * @param IMailer $mailer + * @param Defaults $defaults + * @param ShareWrapperService $shareWrapperService + */ + public function __construct( + IL10N $l10n, + IMailer $mailer, + Defaults $defaults, + ShareWrapperService $shareWrapperService + ) { + $this->l10n = $l10n; + $this->mailer = $mailer; + $this->defaults = $defaults; + $this->shareWrapperService = $shareWrapperService; + + $this->setup('app', Application::APP_ID); + } /** * @param Event $event + * + * @throws RequestBuilderException */ public function handle(Event $event): void { if (!$event instanceof CircleMemberAddedEvent) { return; } - $federatedUsers = []; $member = $event->getMember(); + $circle = $event->getCircle(); + + if ($member->getUserType() === Member::TYPE_CIRCLE) { + $members = $member->getBasedOn()->getInheritedMembers(); + } else { + $members = [$member]; + } + + /** @var Member[] $members */ + foreach ($members as $member) { + if ($member->getUserType() !== Member::TYPE_MAIL + && $member->getUserType() !== Member::TYPE_CONTACT + ) { + continue; + } + + $mails = []; + $shares = []; + foreach ($event->getResults() as $origin => $item) { + $files = $item->gData('files'); + if (!$files->hasKey($member->getId())) { + continue; + } + + $data = $files->gData($member->getId()); + $shares = array_merge($shares, $data->gObjs('shares', ShareWrapper::class)); + + // TODO: is it safe to use $origin to compare getInstance() ? + if ($member->getUserType() === Member::TYPE_CONTACT && $member->getInstance() === $origin) { + $mails = $data->gArray('mails'); + } + } + + $this->generateMail($circle, $member, $shares, $mails); + } + } + + + /** + * @param Circle $circle + * @param Member $member + * @param ShareWrapper[] $shares + * @param array $mails + */ + private function generateMail(Circle $circle, Member $member, array $shares, array $mails): void { + if (empty($shares)) { + return; + } + if ($member->getUserType() === Member::TYPE_MAIL) { - $federatedUsers[] = $member; + $mails = [$member->getUserId()]; } - if (empty($federatedUsers)) { + if (empty($mails)) { return; } - $result = []; - foreach ($event->getResults() as $instance => $item) { - $result[$instance] = $item->gData('files'); + if ($member->hasInvitedBy()) { + $invitedBy = $member->getInvitedBy()->getDisplayName(); + } else { + $invitedBy = 'someone'; + } + + $links = []; + foreach ($shares as $share) { + $links[] = [ + 'filename' => $share->getFileTarget(), + 'link' => $share->getShareToken()->getLink() + ]; + } + + $template = $this->generateMailExitingShares($invitedBy, $circle->getDisplayName()); + $this->fillMailExistingShares($template, $links); + foreach ($mails as $mail) { + try { + $this->sendMailExistingShares($template, $invitedBy, $mail); + } catch (Exception $e) { + } } + } + + + /** + * @param string $author + * @param string $circleName + * + * @return IEMailTemplate + */ + private function generateMailExitingShares(string $author, string $circleName): IEMailTemplate { + $emailTemplate = $this->mailer->createEMailTemplate('circles.ExistingShareNotification', []); + $emailTemplate->addHeader(); + + $text = $this->l10n->t('%s shared multiple files with "%s".', [$author, $circleName]); + $emailTemplate->addBodyText(htmlspecialchars($text), $text); + + return $emailTemplate; + } + + /** + * @param IEMailTemplate $emailTemplate + * @param array $links + */ + private function fillMailExistingShares(IEMailTemplate $emailTemplate, array $links) { + foreach ($links as $item) { + $emailTemplate->addBodyButton( + $this->l10n->t('Open »%s«', [htmlspecialchars($item['filename'])]), $item['link'] + ); + } + } + + + /** + * @param IEMailTemplate $emailTemplate + * @param string $author + * @param string $recipient + * + * @throws Exception + */ + private function sendMailExistingShares( + IEMailTemplate $emailTemplate, + string $author, + string $recipient + ) { + $subject = $this->l10n->t('%s shared multiple files with you.', [$author]); + + $instanceName = $this->defaults->getName(); + $senderName = $this->l10n->t('%s on %s', [$author, $instanceName]); + + $message = $this->mailer->createMessage(); + + $message->setFrom([Util::getDefaultEmailAddress($instanceName) => $senderName]); + $message->setSubject($subject); + $message->setPlainBody($emailTemplate->renderText()); + $message->setHtmlBody($emailTemplate->renderHtml()); + $message->setTo([$recipient]); - \OC::$server->getLogger()->log(3, 'FILES: ' . json_encode($result)); - \OC::$server->getLogger()->log(3, 'MAILS: ' . json_encode($federatedUsers)); + $this->mailer->send($message); } } diff --git a/lib/Listeners/Files/RemovingMember.php b/lib/Listeners/Files/RemovingMember.php new file mode 100644 index 000000000..6e34a0e5f --- /dev/null +++ b/lib/Listeners/Files/RemovingMember.php @@ -0,0 +1,131 @@ + + * @copyright 2021 + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + + +namespace OCA\Circles\Listeners\Files; + + +use ArtificialOwl\MySmallPhpTools\Traits\Nextcloud\nc22\TNC22Logger; +use ArtificialOwl\MySmallPhpTools\Traits\TStringTools; +use OCA\Circles\AppInfo\Application; +use OCA\Circles\Events\RemovingCircleMemberEvent; +use OCA\Circles\Exceptions\MembershipNotFoundException; +use OCA\Circles\Model\Member; +use OCA\Circles\Model\Membership; +use OCA\Circles\Service\MemberService; +use OCA\Circles\Service\ShareTokenService; +use OCP\EventDispatcher\Event; +use OCP\EventDispatcher\IEventListener; + + +/** + * Class RemovingMember + * + * @package OCA\Circles\Listeners\Files + */ +class RemovingMember implements IEventListener { + + + use TStringTools; + use TNC22Logger; + + + /** @var MemberService */ + private $memberService; + + /** @var ShareTokenService */ + private $shareTokenService; + + + /** + * RemovingMember constructor. + * + * @param MemberService $memberService + * @param ShareTokenService $shareTokenService + */ + public function __construct( + MemberService $memberService, + ShareTokenService $shareTokenService + ) { + $this->memberService = $memberService; + $this->shareTokenService = $shareTokenService; + + $this->setup('app', Application::APP_ID); + } + + + /** + * @param Event $event + */ + public function handle(Event $event): void { + if (!$event instanceof RemovingCircleMemberEvent) { + return; + } + + $member = $event->getMember(); + + if ($member->getUserType() === Member::TYPE_CIRCLE) { + $members = $member->getBasedOn()->getInheritedMembers(); + } else { + $members = [$member]; + } + + $circle = $event->getCircle(); + $singleIds = array_merge( + [$circle->getSingleId()], + array_map( + function(Membership $membership) { + return $membership->getCircleId(); + }, $circle->getMemberships() + ) + ); + + /** @var Member[] $members */ + foreach ($members as $member) { + if ($member->getUserType() !== Member::TYPE_MAIL + && $member->getUserType() !== Member::TYPE_CONTACT + ) { + continue; + } + + foreach ($singleIds as $singleId) { + try { + $member->getMembership($singleId); + continue; + } catch (MembershipNotFoundException $e) { + } + + $this->shareTokenService->removeTokens($member->getSingleId(), $singleId); + } + } + } + +} + diff --git a/lib/Model/Federated/FederatedEvent.php b/lib/Model/Federated/FederatedEvent.php index 4187b568f..441b7bc5a 100644 --- a/lib/Model/Federated/FederatedEvent.php +++ b/lib/Model/Federated/FederatedEvent.php @@ -494,7 +494,23 @@ public function resetResult(): self { * * @return $this */ - public function addResult(string $key, array $result): self { + public function setResultEntry(string $key, array $result): self { + if (is_null($this->result)) { + $this->result = new SimpleDataStore(); + } + + $this->result->sData($key, new SimpleDataStore($result)); + + return $this; + } + + /** + * @param string $key + * @param array $result + * + * @return $this + */ + public function addResultEntry(string $key, array $result): self { if (is_null($this->result)) { $this->result = new SimpleDataStore(); } diff --git a/lib/Model/Member.php b/lib/Model/Member.php index 27b9003cf..92f4287a3 100644 --- a/lib/Model/Member.php +++ b/lib/Model/Member.php @@ -40,6 +40,7 @@ use JsonSerializable; use OCA\Circles\AppInfo\Capabilities; use OCA\Circles\Exceptions\MemberNotFoundException; +use OCA\Circles\Exceptions\MembershipNotFoundException; use OCA\Circles\Exceptions\ParseMemberLevelException; use OCA\Circles\Exceptions\UnknownInterfaceException; use OCA\Circles\Exceptions\UserTypeNotFoundException; @@ -726,6 +727,22 @@ public function getMemberships(): array { return $this->memberships; } + /** + * @param string $circleId + * + * @return Membership + * @throws MembershipNotFoundException + */ + public function getMembership(string $circleId): Membership { + foreach ($this->getMemberships() as $membership) { + if ($membership->getCircleId() === $circleId) { + return $membership; + } + } + + throw new MembershipNotFoundException(); + } + /** * @param Member $member diff --git a/lib/Model/ShareToken.php b/lib/Model/ShareToken.php index a25b993eb..47af80517 100644 --- a/lib/Model/ShareToken.php +++ b/lib/Model/ShareToken.php @@ -33,6 +33,7 @@ use ArtificialOwl\MySmallPhpTools\Db\Nextcloud\nc22\INC22QueryRow; +use ArtificialOwl\MySmallPhpTools\Exceptions\InvalidItemException; use ArtificialOwl\MySmallPhpTools\IDeserializable; use ArtificialOwl\MySmallPhpTools\Traits\TArrayTools; use JsonSerializable; @@ -69,6 +70,9 @@ class ShareToken implements IDeserializable, INC22QueryRow, JsonSerializable { /** @var int */ private $accepted = IShare::STATUS_PENDING; + /** @var string */ + private $link = ''; + /** * ShareToken constructor. @@ -229,12 +233,36 @@ public function getAccepted(): int { } + /** + * @param string $link + * + * @return ShareToken + */ + public function setLink(string $link): self { + $this->link = $link; + + return $this; + } + + /** + * @return string + */ + public function getLink(): string { + return $this->link; + } + + /** * @param array $data * * @return ShareToken + * @throws InvalidItemException */ public function import(array $data): IDeserializable { + if ($this->getInt('shareId', $data) === 0) { + throw new InvalidItemException(); + } + $this->setShareId($this->getInt('shareId', $data)); $this->setCircleId($this->get('circleId', $data)); $this->setSingleId($this->get('singleId', $data)); @@ -242,6 +270,7 @@ public function import(array $data): IDeserializable { $this->setToken($this->get('token', $data)); $this->setPassword($this->get('password', $data)); $this->setAccepted($this->getInt('accepted', $data, IShare::STATUS_PENDING)); + $this->setLink($this->get('link', $data)); return $this; } @@ -276,7 +305,8 @@ function jsonSerialize(): array { 'memberId' => $this->getMemberId(), 'token' => $this->getToken(), 'password' => $this->getPassword(), - 'accepted' => $this->getAccepted() + 'accepted' => $this->getAccepted(), + 'link' => $this->getLink() ]; } diff --git a/lib/Model/ShareWrapper.php b/lib/Model/ShareWrapper.php index 4b3f0a38b..bb7cbd2d6 100644 --- a/lib/Model/ShareWrapper.php +++ b/lib/Model/ShareWrapper.php @@ -128,6 +128,9 @@ class ShareWrapper extends ManagedModel implements IDeserializable, INC22QueryRo /** @var Member */ private $owner; + /** @var ShareToken */ + private $shareToken; + /** * @param string $id @@ -574,6 +577,32 @@ public function hasOwner(): bool { } + /** + * @param ShareToken $shareToken + * + * @return ShareWrapper + */ + public function setShareToken(ShareToken $shareToken): self { + $this->shareToken = $shareToken; + + return $this; + } + + /** + * @return ShareToken + */ + public function getShareToken(): ShareToken { + return $this->shareToken; + } + + /** + * @return bool + */ + public function hasShareToken(): bool { + return !is_null($this->shareToken); + } + + /** * @param IRootFolder $rootFolder * @param IUserManager $userManager @@ -680,7 +709,17 @@ private function setShareDisplay(IShare $share, IURLGenerator $urlGenerator) { } + /** + * @param array $data + * + * @return IDeserializable + * @throws InvalidItemException + */ public function import(array $data): IDeserializable { + if ($this->getInt('id', $data) === 0) { + throw new InvalidItemException(); + } + $shareTime = new DateTime(); $shareTime->setTimestamp($this->getInt('shareTime', $data)); @@ -728,6 +767,12 @@ public function import(array $data): IDeserializable { } catch (InvalidItemException $e) { } + try { + $shareToken = new ShareToken(); + $this->setShareToken($shareToken->import($this->getArray('shareToken', $data))); + } catch (InvalidItemException $e) { + } + return $this; } @@ -815,6 +860,10 @@ public function jsonSerialize(): array { $arr['fileCache'] = $this->getFileCache(); } + if ($this->hasShareToken()) { + $arr['shareToken'] = $this->getShareToken(); + } + return $arr; } diff --git a/lib/Service/MembershipService.php b/lib/Service/MembershipService.php index cd7400eb4..69650f6f3 100644 --- a/lib/Service/MembershipService.php +++ b/lib/Service/MembershipService.php @@ -159,6 +159,7 @@ public function manageAll(): void { * @param string $singleId * * @return int + * @throws RequestBuilderException */ public function manageMemberships(string $singleId): int { $memberships = $this->generateMemberships($singleId); diff --git a/lib/Service/MigrationService.php b/lib/Service/MigrationService.php index 47e50bb7c..e18f82a0d 100644 --- a/lib/Service/MigrationService.php +++ b/lib/Service/MigrationService.php @@ -218,10 +218,11 @@ private function migrationTo22(): void { $this->migrationTo22_Circles(); $this->migrationTo22_Members(); - $this->migrationTo22_Tokens(); $this->membershipService->resetMemberships('', true); $this->membershipService->manageAll(); + $this->migrationTo22_Tokens(); + $this->configService->setAppValue(ConfigService::MIGRATION_22, '1'); } @@ -404,7 +405,6 @@ private function generateMemberFrom21(SimpleDataStore $data): Member { // "cached_update":"2021-05-02 12:13:22", // "joined":"2021-05-02 12:13:22", // "contact_checked":null," -// single_id":"wt6WQYYCry3EOud", // "circle_source":null} return $member; @@ -525,6 +525,11 @@ private function generateShareTokenFrom21(SimpleDataStore $data): ShareToken { $shareToken = new ShareToken(); $member = $this->memberRequest->getMemberById($data->g('member_id')); + if ($member->getUserType() !== Member::TYPE_MAIL + && $member->getUserType() !== Member::TYPE_CONTACT) { + throw new MemberNotFoundException(); + } + $shareToken->setShareId($data->gInt('share_id')) ->setCircleId($data->g('circle_id')) ->setSingleId($member->getSingleId()) diff --git a/lib/Service/ShareTokenService.php b/lib/Service/ShareTokenService.php new file mode 100644 index 000000000..c5e61e622 --- /dev/null +++ b/lib/Service/ShareTokenService.php @@ -0,0 +1,138 @@ + + * @copyright 2021 + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + + +namespace OCA\Circles\Service; + + +use ArtificialOwl\MySmallPhpTools\Traits\TStringTools; +use OCA\Circles\Db\ShareTokenRequest; +use OCA\Circles\Exceptions\ShareTokenAlreadyExistException; +use OCA\Circles\Exceptions\ShareTokenNotFoundException; +use OCA\Circles\Model\Member; +use OCA\Circles\Model\ShareToken; +use OCA\Circles\Model\ShareWrapper; +use OCP\IURLGenerator; +use OCP\Share\IShare; + + +/** + * Class ShareTokenService + * + * @package OCA\Circles\Service + */ +class ShareTokenService { + + + use TStringTools; + + + /** @var IURLGenerator */ + private $urlGenerator; + + /** @var ShareTokenRequest */ + private $shareTokenRequest; + + + /** + * ShareTokenService constructor. + * + * @param IURLGenerator $urlGenerator + * @param ShareTokenRequest $shareTokenRequest + */ + public function __construct( + IURLGenerator $urlGenerator, + ShareTokenRequest $shareTokenRequest + ) { + $this->urlGenerator = $urlGenerator; + $this->shareTokenRequest = $shareTokenRequest; + } + + + /** + * @param ShareWrapper $share + * @param Member $member + * @param string $password + * + * @return ShareToken + * @throws ShareTokenAlreadyExistException + */ + public function generateShareToken( + ShareWrapper $share, + Member $member, + string $password = '' + ): ShareToken { + $token = $this->token(19); + + $shareToken = new ShareToken(); + $shareToken->setShareId((int)$share->getId()) + ->setCircleId($share->getSharedWith()) + ->setSingleId($member->getSingleId()) + ->setMemberId($member->getId()) + ->setToken($token) + ->setPassword($password) + ->setAccepted(IShare::STATUS_ACCEPTED); + + try { + $this->shareTokenRequest->search($shareToken); + throw new ShareTokenAlreadyExistException(); + } catch (ShareTokenNotFoundException $e) { + } + + $this->shareTokenRequest->save($shareToken); + $this->setShareTokenLink($shareToken); + + return $shareToken; + } + + + /** + * @param ShareToken $shareToken + */ + public function setShareTokenLink(ShareToken $shareToken): void { + $link = $this->urlGenerator->linkToRouteAbsolute( + 'files_sharing.sharecontroller.showShare', + ['token' => $shareToken->getToken()] + ); + + $shareToken->setLink($link); + } + + + /** + * @param string $singleId + * @param string $circleId + */ + public function removeTokens(string $singleId, string $circleId) { + $this->shareTokenRequest->removeTokens($singleId, $circleId); + } + +} + diff --git a/lib/Service/ShareWrapperService.php b/lib/Service/ShareWrapperService.php index 12767a145..a9540d20f 100644 --- a/lib/Service/ShareWrapperService.php +++ b/lib/Service/ShareWrapperService.php @@ -32,6 +32,7 @@ namespace OCA\Circles\Service; +use ArtificialOwl\MySmallPhpTools\Traits\TStringTools; use OCA\Circles\Db\ShareWrapperRequest; use OCA\Circles\Exceptions\RequestBuilderException; use OCA\Circles\Exceptions\ShareWrapperNotFoundException; @@ -49,6 +50,9 @@ class ShareWrapperService { + use TStringTools; + + /** @var ShareWrapperRequest */ private $shareWrapperRequest; @@ -64,10 +68,11 @@ public function __construct(ShareWrapperRequest $shareWrapperRequest) { /** - * @param $singleId - * @param $nodeId + * @param string $singleId + * @param int $nodeId * * @return ShareWrapper + * @throws RequestBuilderException * @throws ShareWrapperNotFoundException */ public function searchShare(string $singleId, int $nodeId): ShareWrapper { @@ -140,6 +145,7 @@ public function getShareById(int $shareId, ?FederatedUser $federatedUser = null) /** * @param int $fileId + * @param bool $getData * * @return ShareWrapper[] * @throws RequestBuilderException