diff --git a/Api/Extension/AdminSaleUnitScopeExtension.php b/Api/Extension/AdminSaleUnitScopeExtension.php
new file mode 100644
index 0000000..26c50c3
--- /dev/null
+++ b/Api/Extension/AdminSaleUnitScopeExtension.php
@@ -0,0 +1,65 @@
+restrict($query, $resourceClass, $operation);
+ }
+
+ public function applyToItem(ModelCriteria $query, string $resourceClass, ?Operation $operation = null, array $context = []): void
+ {
+ $this->restrict($query, $resourceClass, $operation);
+ }
+
+ private function restrict(ModelCriteria $query, string $resourceClass, ?Operation $operation): void
+ {
+ if (OrderComment::class !== $resourceClass) {
+ return;
+ }
+
+ if (!$operation instanceof HttpOperation || !str_starts_with($operation->getUriTemplate() ?? '', '/admin')) {
+ return;
+ }
+
+ $allowed = $this->scope->allowedSaleUnitIds();
+
+ if (null === $allowed) {
+ return;
+ }
+
+ // The ids are integers read from the database, never from the request.
+ $query->addAnd(OrderCommentTableMap::COL_ORDER_ID, \sprintf(
+ '%s IN (SELECT `order`.`id` FROM `order` WHERE `order`.`sale_unit_id` IN (%s))',
+ OrderCommentTableMap::COL_ORDER_ID,
+ implode(',', array_map('intval', $allowed)) ?: 'NULL',
+ ), Criteria::CUSTOM);
+ }
+}
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000..7506d51
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,14 @@
+# Changelog
+
+## 3.1.1
+
+### Fixed
+
+- The admin `order-comments` API collection is restricted to the comments of the orders placed on the sale units of an administrator restricted to some units (a comment on a national order is left out). An administrator attached to no unit and a superadministrator are unchanged.
+- The `order-comments` admin resource now declares its permission (the order one) in `thelia.api.admin_resources`; without it the admin API refuses every non-super administrator.
+
+### Changed
+
+- The admin permission of the resource is added to `thelia.api.admin_resources` by a compiler pass, so it never erases what another module declared.
+
+- Requires `thelia/core` 3.3.0 or later (sale unit scope).
diff --git a/Config/module.xml b/Config/module.xml
index 78d6305..e039047 100644
--- a/Config/module.xml
+++ b/Config/module.xml
@@ -7,12 +7,12 @@
Permet au client d'ajouter un commentaire à sa commande
- 3.1.0
+ 3.1.1
Vincent Lopes-Vicente
vlopes@openstudio.fr
classic
- 3.0.0
+ 3.3.0
prod
diff --git a/DependencyInjection/AdminApiResourcesPass.php b/DependencyInjection/AdminApiResourcesPass.php
new file mode 100644
index 0000000..3aebb5d
--- /dev/null
+++ b/DependencyInjection/AdminApiResourcesPass.php
@@ -0,0 +1,33 @@
+hasParameter(self::PARAMETER) ? (array) $container->getParameter(self::PARAMETER) : [];
+
+ $container->setParameter(self::PARAMETER, [
+ ...$declared,
+ OrderCommentResource::class => AdminResources::ORDER,
+ ]);
+ }
+}
diff --git a/OrderComment.php b/OrderComment.php
index f827178..5dcbb0d 100644
--- a/OrderComment.php
+++ b/OrderComment.php
@@ -21,6 +21,7 @@
use OrderComment\Model\OrderCommentQuery;
use Propel\Runtime\Connection\ConnectionInterface;
+use OrderComment\DependencyInjection\AdminApiResourcesPass;
use Symfony\Component\DependencyInjection\Loader\Configurator\ServicesConfigurator;
use Thelia\Core\Install\Database;
use Thelia\Module\BaseModule;
@@ -39,10 +40,15 @@ public function postActivation(?ConnectionInterface $con = null): void
}
}
+ public static function getCompilers(): array
+ {
+ return [new AdminApiResourcesPass()];
+ }
+
public static function configureServices(ServicesConfigurator $servicesConfigurator): void
{
$servicesConfigurator->load(self::getModuleCode().'\\', __DIR__)
- ->exclude([__DIR__.'/I18n/*'])
+ ->exclude([__DIR__.'/I18n/*', __DIR__.'/Tests/*'])
->autowire()
->autoconfigure();
}
diff --git a/Tests/Integration/AdminOrderCommentSaleUnitScopeTest.php b/Tests/Integration/AdminOrderCommentSaleUnitScopeTest.php
new file mode 100644
index 0000000..abab35e
--- /dev/null
+++ b/Tests/Integration/AdminOrderCommentSaleUnitScopeTest.php
@@ -0,0 +1,98 @@
+createFixtureFactory();
+ $first = $factory->saleUnit();
+ $second = $factory->saleUnit();
+ $connection = $this->getPropelConnection();
+
+ $myOrder = $factory->order()->setSaleUnitId($first->getId());
+ $myOrder->save($connection);
+ $theirOrder = $factory->order()->setSaleUnitId($second->getId());
+ $theirOrder->save($connection);
+ $nationalOrder = $factory->order();
+
+ $this->mine = $this->comment($myOrder->getId());
+ $this->theirs = $this->comment($theirOrder->getId());
+ $this->national = $this->comment($nationalOrder->getId());
+
+ $restricted = $factory->restrictedAdmin(
+ [AdminResources::ORDER => [AccessManager::VIEW]],
+ ['password' => 'password'],
+ );
+ $factory->adminSaleUnit($restricted, $first);
+ $this->restrictedToken = $this->authenticateAsAdmin($restricted);
+ }
+
+ protected function tearDown(): void
+ {
+ $this->getService(AdminSaleUnitScope::class)->reset();
+
+ parent::tearDown();
+ }
+
+ public function testARestrictedAdministratorReadsTheCommentsOfItsUnitsOnly(): void
+ {
+ self::assertSame([$this->mine], $this->listedIds($this->restrictedToken, [$this->mine, $this->theirs, $this->national]));
+ }
+
+ public function testAnAdministratorOfNoUnitReadsEverything(): void
+ {
+ $ids = $this->listedIds($this->authenticateAsAdmin(), [$this->mine, $this->theirs, $this->national]);
+
+ self::assertSame([$this->mine, $this->theirs, $this->national], $ids);
+ }
+
+ private function comment(int $orderId): int
+ {
+ $comment = (new OrderComment())->setOrderId($orderId)->setComment('comment');
+ $comment->save($this->getPropelConnection());
+
+ return $comment->getId();
+ }
+
+ /**
+ * @param list $among
+ *
+ * @return list
+ */
+ private function listedIds(string $token, array $among): array
+ {
+ $response = $this->jsonRequest('GET', '/api/admin/order-comments', token: $token, format: 'json');
+
+ self::assertSame(200, $response->getStatusCode(), (string) $response->getContent());
+
+ $ids = array_map(
+ static fn (array $row): int => (int) $row['id'],
+ json_decode((string) $response->getContent(), true, flags: \JSON_THROW_ON_ERROR),
+ );
+ $ids = array_values(array_intersect($ids, $among));
+ sort($ids);
+
+ return $ids;
+ }
+}
diff --git a/Tests/Integration/AdminResourcesDeclarationTest.php b/Tests/Integration/AdminResourcesDeclarationTest.php
new file mode 100644
index 0000000..978321f
--- /dev/null
+++ b/Tests/Integration/AdminResourcesDeclarationTest.php
@@ -0,0 +1,47 @@
+setParameter('thelia.api.admin_resources', ['Other\\Resource' => AdminResources::MODULE]);
+
+ foreach (OrderComment::getCompilers() as $pass) {
+ $pass->process($container);
+ }
+
+ self::assertSame(
+ ['Other\\Resource' => AdminResources::MODULE, OrderCommentResource::class => AdminResources::ORDER],
+ $container->getParameter('thelia.api.admin_resources'),
+ );
+ }
+
+ public function testTheResourceIsDeclaredWhenNoOtherModuleDeclaresAny(): void
+ {
+ $container = new ContainerBuilder();
+
+ foreach (OrderComment::getCompilers() as $pass) {
+ $pass->process($container);
+ }
+
+ self::assertSame(
+ [OrderCommentResource::class => AdminResources::ORDER],
+ $container->getParameter('thelia.api.admin_resources'),
+ );
+ }
+}
diff --git a/composer.json b/composer.json
index 8a86fb2..5161cc6 100644
--- a/composer.json
+++ b/composer.json
@@ -3,6 +3,7 @@
"license": "GPL-3.0-or-later",
"type": "thelia-module",
"require": {
+ "thelia/core": ">=3.3.0",
"thelia/installer": "^1.6"
},
"extra": {