diff options
-rw-r--r-- | appinfo/info.xml | 5 | ||||
-rw-r--r-- | lib/AppInfo/Application.php | 65 | ||||
-rw-r--r-- | lib/Db/AclMapper.php | 8 | ||||
-rw-r--r-- | lib/Listeners/ParticipantCleanupListener.php | 57 | ||||
-rw-r--r-- | lib/Listeners/ResourceListener.php | 42 | ||||
-rw-r--r-- | lib/Migration/DeletedCircleCleanup.php | 36 | ||||
-rw-r--r-- | tests/psalm-baseline.xml | 11 | ||||
-rw-r--r-- | tests/stub.phpstub | 6 | ||||
-rw-r--r-- | tests/unit/Service/BoardServiceTest.php | 8 |
9 files changed, 173 insertions, 65 deletions
diff --git a/appinfo/info.xml b/appinfo/info.xml index d7cd3c84..297db3ef 100644 --- a/appinfo/info.xml +++ b/appinfo/info.xml @@ -41,6 +41,11 @@ <job>OCA\Deck\Cron\ScheduledNotifications</job> <job>OCA\Deck\Cron\CardDescriptionActivity</job> </background-jobs> + <repair-steps> + <live-migration> + <step>OCA\Deck\Migration\DeletedCircleCleanup</step> + </live-migration> + </repair-steps> <commands> <command>OCA\Deck\Command\UserExport</command> <command>OCA\Deck\Command\BoardImport</command> diff --git a/lib/AppInfo/Application.php b/lib/AppInfo/Application.php index edbd2b3a..1f973619 100644 --- a/lib/AppInfo/Application.php +++ b/lib/AppInfo/Application.php @@ -26,15 +26,13 @@ namespace OCA\Deck\AppInfo; use Closure; use Exception; use OC\EventDispatcher\SymfonyAdapter; +use OCA\Circles\Events\CircleDestroyedEvent; use OCA\Deck\Activity\CommentEventHandler; use OCA\Deck\Capabilities; use OCA\Deck\Collaboration\Resources\ResourceProvider; use OCA\Deck\Collaboration\Resources\ResourceProviderCard; use OCA\Deck\Dashboard\DeckWidget; use OCA\Deck\Db\Acl; -use OCA\Deck\Db\AclMapper; -use OCA\Deck\Db\AssignmentMapper; -use OCA\Deck\Db\BoardMapper; use OCA\Deck\Db\CardMapper; use OCA\Deck\Event\AclCreatedEvent; use OCA\Deck\Event\AclDeletedEvent; @@ -43,7 +41,9 @@ use OCA\Deck\Event\CardCreatedEvent; use OCA\Deck\Event\CardDeletedEvent; use OCA\Deck\Event\CardUpdatedEvent; use OCA\Deck\Listeners\BeforeTemplateRenderedListener; +use OCA\Deck\Listeners\ParticipantCleanupListener; use OCA\Deck\Listeners\FullTextSearchEventListener; +use OCA\Deck\Listeners\ResourceListener; use OCA\Deck\Middleware\DefaultBoardMiddleware; use OCA\Deck\Middleware\ExceptionMiddleware; use OCA\Deck\Notification\Notifier; @@ -62,15 +62,12 @@ use OCP\Collaboration\Reference\RenderReferenceEvent; use OCP\Collaboration\Resources\IProviderManager; use OCP\Comments\CommentsEntityEvent; use OCP\Comments\ICommentsManager; -use OCP\EventDispatcher\Event; use OCP\EventDispatcher\IEventDispatcher; use OCP\Group\Events\GroupDeletedEvent; use OCP\IConfig; use OCP\IDBConnection; -use OCP\IGroupManager; use OCP\IRequest; use OCP\Server; -use OCP\IUserManager; use OCP\Notification\IManager as NotificationManager; use OCP\Share\IManager; use OCP\User\Events\UserDeletedEvent; @@ -95,7 +92,6 @@ class Application extends App implements IBootstrap { } public function boot(IBootContext $context): void { - $context->injectFn(Closure::fromCallable([$this, 'registerUserGroupHooks'])); $context->injectFn(Closure::fromCallable([$this, 'registerCommentsEntity'])); $context->injectFn(Closure::fromCallable([$this, 'registerCommentsEventHandler'])); $context->injectFn(Closure::fromCallable([$this, 'registerNotifications'])); @@ -143,59 +139,20 @@ class Application extends App implements IBootstrap { $context->registerEventListener(AclCreatedEvent::class, FullTextSearchEventListener::class); $context->registerEventListener(AclUpdatedEvent::class, FullTextSearchEventListener::class); $context->registerEventListener(AclDeletedEvent::class, FullTextSearchEventListener::class); + + // Handling cache invalidation for collections + $context->registerEventListener(AclCreatedEvent::class, ResourceListener::class); + $context->registerEventListener(AclDeletedEvent::class, ResourceListener::class); + + $context->registerEventListener(UserDeletedEvent::class, ParticipantCleanupListener::class); + $context->registerEventListener(GroupDeletedEvent::class, ParticipantCleanupListener::class); + $context->registerEventListener(CircleDestroyedEvent::class, ParticipantCleanupListener::class); } public function registerNotifications(NotificationManager $notificationManager): void { $notificationManager->registerNotifierService(Notifier::class); } - private function registerUserGroupHooks(IUserManager $userManager, IGroupManager $groupManager): void { - $container = $this->getContainer(); - /** @var IEventDispatcher $eventDispatcher */ - $eventDispatcher = $container->get(IEventDispatcher::class); - // Delete user/group acl entries when they get deleted - $eventDispatcher->addListener(UserDeletedEvent::class, static function (Event $event) use ($container): void { - if (!($event instanceof UserDeletedEvent)) { - return; - } - $user = $event->getUser(); - // delete existing acl entries for deleted user - /** @var AclMapper $aclMapper */ - $aclMapper = $container->get(AclMapper::class); - $acls = $aclMapper->findByParticipant(Acl::PERMISSION_TYPE_USER, $user->getUID()); - foreach ($acls as $acl) { - $aclMapper->delete($acl); - } - // delete existing user assignments - $assignmentMapper = $container->get(AssignmentMapper::class); - $assignments = $assignmentMapper->findByParticipant($user->getUID()); - foreach ($assignments as $assignment) { - $assignmentMapper->delete($assignment); - } - - /** @var BoardMapper $boardMapper */ - $boardMapper = $container->get(BoardMapper::class); - $boards = $boardMapper->findAllByOwner($user->getUID()); - foreach ($boards as $board) { - $boardMapper->delete($board); - } - }); - - $eventDispatcher->addListener(GroupDeletedEvent::class, static function (Event $event) use ($container): void { - if (!($event instanceof GroupDeletedEvent)) { - return; - } - $group = $event->getGroup(); - /** @var AclMapper $aclMapper */ - $aclMapper = $container->get(AclMapper::class); - $aclMapper->findByParticipant(Acl::PERMISSION_TYPE_GROUP, $group->getGID()); - $acls = $aclMapper->findByParticipant(Acl::PERMISSION_TYPE_GROUP, $group->getGID()); - foreach ($acls as $acl) { - $aclMapper->delete($acl); - } - }); - } - public function registerCommentsEntity(IEventDispatcher $eventDispatcher): void { $eventDispatcher->addListener(CommentsEntityEvent::EVENT_ENTITY, function (CommentsEntityEvent $event) { $event->addEntityCollection(self::COMMENT_ENTITY_TYPE, function ($name) { diff --git a/lib/Db/AclMapper.php b/lib/Db/AclMapper.php index 4ffbda69..c271d8d3 100644 --- a/lib/Db/AclMapper.php +++ b/lib/Db/AclMapper.php @@ -110,4 +110,12 @@ class AclMapper extends DeckMapper implements IPermissionMapper { ->andWhere($qb->expr()->eq('board_id', $qb->createNamedParameter($boardId, IQueryBuilder::PARAM_INT))); $qb->executeStatement(); } + + public function findByType(int $type): array { + $qb = $this->db->getQueryBuilder(); + $qb->select('*') + ->from('deck_board_acl') + ->where($qb->expr()->eq('type', $qb->createNamedParameter($type, IQueryBuilder::PARAM_INT))); + return $this->findEntities($qb); + } } diff --git a/lib/Listeners/ParticipantCleanupListener.php b/lib/Listeners/ParticipantCleanupListener.php new file mode 100644 index 00000000..aa159107 --- /dev/null +++ b/lib/Listeners/ParticipantCleanupListener.php @@ -0,0 +1,57 @@ +<?php + +namespace OCA\Deck\Listeners; + +use OCA\Circles\Events\CircleDestroyedEvent; +use OCA\Deck\Db\Acl; +use OCA\Deck\Db\AclMapper; +use OCA\Deck\Db\AssignmentMapper; +use OCA\Deck\Db\BoardMapper; +use OCP\EventDispatcher\Event; +use OCP\EventDispatcher\IEventListener; +use OCP\Group\Events\GroupDeletedEvent; +use OCP\User\Events\UserDeletedEvent; + +class ParticipantCleanupListener implements IEventListener { + private AclMapper $aclMapper; + private AssignmentMapper $assignmentMapper; + private BoardMapper $boardMapper; + + public function __construct(AclMapper $aclMapper, AssignmentMapper $assignmentMapper, BoardMapper $boardMapper) { + $this->aclMapper = $aclMapper; + $this->assignmentMapper = $assignmentMapper; + $this->boardMapper = $boardMapper; + } + + public function handle(Event $event): void { + if ($event instanceof UserDeletedEvent) { + $boards = $this->boardMapper->findAllByOwner($event->getUser()->getUID()); + foreach ($boards as $board) { + $this->boardMapper->delete($board); + } + + $this->cleanupByParticipant(Acl::PERMISSION_TYPE_USER, $event->getUser()->getUID()); + } + + if ($event instanceof GroupDeletedEvent) { + $this->cleanupByParticipant(Acl::PERMISSION_TYPE_GROUP, $event->getGroup()->getGID()); + } + + if ($event instanceof CircleDestroyedEvent) { + $circleId = $event->getCircle()->getSingleId(); + $this->cleanupByParticipant(Acl::PERMISSION_TYPE_CIRCLE, $circleId); + } + } + + private function cleanupByParticipant(int $type, string $participant): void { + $acls = $this->aclMapper->findByParticipant($type, $participant); + foreach ($acls as $acl) { + $this->aclMapper->delete($acl); + } + + $assignments = $this->assignmentMapper->findByParticipant($participant, $type); + foreach ($assignments as $assignment) { + $this->assignmentMapper->delete($assignment); + } + } +} diff --git a/lib/Listeners/ResourceListener.php b/lib/Listeners/ResourceListener.php new file mode 100644 index 00000000..08045dff --- /dev/null +++ b/lib/Listeners/ResourceListener.php @@ -0,0 +1,42 @@ +<?php + +namespace OCA\Deck\Listeners; + +use OCA\Deck\Collaboration\Resources\ResourceProvider; +use OCA\Deck\Collaboration\Resources\ResourceProviderCard; +use OCA\Deck\Event\AclCreatedEvent; +use OCA\Deck\Event\AclDeletedEvent; +use OCP\Collaboration\Resources\IManager; +use OCP\Collaboration\Resources\ResourceException; +use OCP\EventDispatcher\Event; +use OCP\EventDispatcher\IEventListener; + +class ResourceListener implements IEventListener { + + /** @var IManager */ + private $resourceManager; + /** @var ResourceProviderCard */ + private $resourceProviderCard; + + public function __construct(IManager $resourceManager, ResourceProviderCard $resourceProviderCard) { + $this->resourceManager = $resourceManager; + $this->resourceProviderCard = $resourceProviderCard; + } + + public function handle(Event $event): void { + if (!$event instanceof AclDeletedEvent && !$event instanceof AclCreatedEvent) { + return; + } + + $boardId = $event->getAcl()->getBoardId(); + + $this->resourceManager->invalidateAccessCacheForProvider($this->resourceProviderCard); + + try { + $resource = $this->resourceManager->getResourceForUser(ResourceProvider::RESOURCE_TYPE, $boardId, null); + $this->resourceManager->invalidateAccessCacheForResource($resource); + } catch (ResourceException $e) { + // If there is no resource we don't need to invalidate anything, but this should not happen anyways + } + } +} diff --git a/lib/Migration/DeletedCircleCleanup.php b/lib/Migration/DeletedCircleCleanup.php new file mode 100644 index 00000000..e90e80e6 --- /dev/null +++ b/lib/Migration/DeletedCircleCleanup.php @@ -0,0 +1,36 @@ +<?php + +namespace OCA\Deck\Migration; + +use OCA\Deck\Db\Acl; +use OCA\Deck\Db\AclMapper; +use OCA\Deck\Service\CirclesService; +use OCP\Migration\IOutput; +use OCP\Migration\IRepairStep; + +class DeletedCircleCleanup implements IRepairStep { + private AclMapper $aclMapper; + private CirclesService $circleService; + + public function __construct(AclMapper $aclMapper, CirclesService $circlesService) { + $this->aclMapper = $aclMapper; + $this->circleService = $circlesService; + } + + public function getName() { + return 'Cleanup Deck ACL entries for circles which have been already deleted'; + } + + public function run(IOutput $output) { + if (!$this->circleService->isCirclesEnabled()) { + return; + } + + foreach ($this->aclMapper->findByType(Acl::PERMISSION_TYPE_CIRCLE) as $acl) { + if ($this->circleService->getCircle($acl->getParticipant()) === null) { + $this->aclMapper->delete($acl); + $output->info('Removed circle with id ' . $acl->getParticipant()); + } + } + } +} diff --git a/tests/psalm-baseline.xml b/tests/psalm-baseline.xml index eb6730f9..3bde7e22 100644 --- a/tests/psalm-baseline.xml +++ b/tests/psalm-baseline.xml @@ -116,12 +116,6 @@ <code>$schemaClosure</code> </MoreSpecificImplementedParamType> </file> - <file src="lib/Service/AssignmentService.php"> - <InvalidScalarArgument occurrences="2"> - <code>$cardId</code> - <code>$cardId</code> - </InvalidScalarArgument> - </file> <file src="lib/Service/AttachmentService.php"> <InvalidCatch occurrences="1"/> </file> @@ -142,11 +136,6 @@ <code>is_resource($content)</code> </RedundantCondition> </file> - <file src="lib/Service/StackService.php"> - <UndefinedClass occurrences="1"> - <code>BadRquestException</code> - </UndefinedClass> - </file> <file src="lib/Sharing/Listener.php"> <InvalidArgument occurrences="1"> <code>[self::class, 'listenPreShare']</code> diff --git a/tests/stub.phpstub b/tests/stub.phpstub index 981250ad..a5ab068e 100644 --- a/tests/stub.phpstub +++ b/tests/stub.phpstub @@ -59,6 +59,12 @@ namespace OCA\Circles\Model { } } +namespace OCA\Circles\Events { + class CircleDestroyedEvent extends \OCP\EventDispatcher\Event { + public function __construct(FederatedEvent $federatedEvent, array $results) {} + abstract public function getCircle(): \OCA\Circles\Model\Circle {} + } +} namespace OCA\Circles\Model\Probes { class CircleProbe { public function __construct() {} diff --git a/tests/unit/Service/BoardServiceTest.php b/tests/unit/Service/BoardServiceTest.php index 646df29e..96007cc2 100644 --- a/tests/unit/Service/BoardServiceTest.php +++ b/tests/unit/Service/BoardServiceTest.php @@ -35,6 +35,8 @@ use OCA\Deck\Db\CardMapper; use OCA\Deck\Db\ChangeHelper; use OCA\Deck\Db\LabelMapper; use OCA\Deck\Db\StackMapper; +use OCA\Deck\Event\AclCreatedEvent; +use OCA\Deck\Event\AclDeletedEvent; use OCA\Deck\NoPermissionException; use OCA\Deck\Notification\NotificationHelper; use OCP\EventDispatcher\IEventDispatcher; @@ -375,6 +377,9 @@ class BoardServiceTest extends TestCase { ->method('insert') ->with($acl) ->willReturn($acl); + $this->eventDispatcher->expects(self::once()) + ->method('dispatchTyped') + ->with(new AclCreatedEvent($acl)); $this->assertEquals($expected, $this->service->addAcl( 123, 'user', 'admin', $providedAcl[0], $providedAcl[1], $providedAcl[2] )); @@ -432,6 +437,9 @@ class BoardServiceTest extends TestCase { ->method('delete') ->with($acl) ->willReturn($acl); + $this->eventDispatcher->expects(self::once()) + ->method('dispatchTyped') + ->with(new AclDeletedEvent($acl)); $this->assertEquals($acl, $this->service->deleteAcl(123)); } } |