diff --git a/appinfo/info.xml b/appinfo/info.xml index d7cd3c845..297db3ef1 100644 --- a/appinfo/info.xml +++ b/appinfo/info.xml @@ -41,6 +41,11 @@ OCA\Deck\Cron\ScheduledNotifications OCA\Deck\Cron\CardDescriptionActivity + + + OCA\Deck\Migration\DeletedCircleCleanup + + OCA\Deck\Command\UserExport OCA\Deck\Command\BoardImport diff --git a/lib/AppInfo/Application.php b/lib/AppInfo/Application.php index edbd2b3a2..d53f440c6 100644 --- a/lib/AppInfo/Application.php +++ b/lib/AppInfo/Application.php @@ -26,6 +26,7 @@ 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; @@ -43,7 +44,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\CircleEventListener; 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; @@ -143,6 +146,12 @@ 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(CircleDestroyedEvent::class, CircleEventListener::class); } public function registerNotifications(NotificationManager $notificationManager): void { diff --git a/lib/Db/AclMapper.php b/lib/Db/AclMapper.php index 4ffbda695..c271d8d32 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/CircleEventListener.php b/lib/Listeners/CircleEventListener.php new file mode 100644 index 000000000..676c83566 --- /dev/null +++ b/lib/Listeners/CircleEventListener.php @@ -0,0 +1,36 @@ +aclMapper = $aclMapper; + $this->eventDispatcher = $eventDispatcher; + } + + public function handle(Event $event): void { + if ($event instanceof CircleDestroyedEvent) { + $circleId = $event->getCircle()->getSingleId(); + $acls = $this->aclMapper->findByParticipant(Acl::PERMISSION_TYPE_CIRCLE, $circleId); + foreach ($acls as $acl) { + $this->aclMapper->delete($acl); + $this->eventDispatcher->dispatchTyped(new AclDeletedEvent($acl)); + } + } + } +} diff --git a/lib/Listeners/ResourceListener.php b/lib/Listeners/ResourceListener.php new file mode 100644 index 000000000..08045dff8 --- /dev/null +++ b/lib/Listeners/ResourceListener.php @@ -0,0 +1,42 @@ +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 000000000..e90e80e6e --- /dev/null +++ b/lib/Migration/DeletedCircleCleanup.php @@ -0,0 +1,36 @@ +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 eb6730f91..3bde7e22a 100644 --- a/tests/psalm-baseline.xml +++ b/tests/psalm-baseline.xml @@ -116,12 +116,6 @@ $schemaClosure - - - $cardId - $cardId - - @@ -142,11 +136,6 @@ is_resource($content) - - - BadRquestException - - [self::class, 'listenPreShare'] diff --git a/tests/stub.phpstub b/tests/stub.phpstub index 981250adf..a5ab068eb 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 646df29e1..96007cc21 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)); } }