fix(core) : harden review findings (me-provider null guard, audit-ignore plainpassword, rbac self-edit guard, module id dedup, audit pagination guard)
This commit is contained in:
@@ -98,7 +98,9 @@ const entityTypeOptions = computed<{ value: string, label: string }[]>(() =>
|
||||
entityTypes.value.map((value) => ({ value, label: entityTypeLabel(value) })),
|
||||
)
|
||||
|
||||
const hasNextPage = computed(() => page.value * PAGE_SIZE < totalItems.value)
|
||||
// PAGE_SIZE must match the API default page size. The full-page guard keeps the
|
||||
// "next" button accurate even on the last (partial) page.
|
||||
const hasNextPage = computed(() => rows.value.length >= PAGE_SIZE && page.value * PAGE_SIZE < totalItems.value)
|
||||
|
||||
function entityTypeLabel(value: string): string {
|
||||
const key = `audit.entity.${value}`
|
||||
|
||||
@@ -94,6 +94,7 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface, SharedU
|
||||
private ?string $password = null;
|
||||
|
||||
#[Groups(['user:write'])]
|
||||
#[AuditIgnore]
|
||||
private ?string $plainPassword = null;
|
||||
|
||||
#[ORM\Column(type: Types::DATETIME_IMMUTABLE)]
|
||||
|
||||
@@ -8,6 +8,7 @@ use ApiPlatform\Metadata\Operation;
|
||||
use ApiPlatform\State\ProviderInterface;
|
||||
use App\Module\Core\Domain\Entity\User;
|
||||
use Symfony\Bundle\SecurityBundle\Security;
|
||||
use Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException;
|
||||
|
||||
/**
|
||||
* @implements ProviderInterface<User>
|
||||
@@ -20,7 +21,11 @@ final readonly class MeProvider implements ProviderInterface
|
||||
|
||||
public function provide(Operation $operation, array $uriVariables = [], array $context = []): User
|
||||
{
|
||||
// @var User $user
|
||||
return $this->security->getUser();
|
||||
$user = $this->security->getUser();
|
||||
if (!$user instanceof User) {
|
||||
throw new UnauthorizedHttpException('Bearer', 'Not authenticated.');
|
||||
}
|
||||
|
||||
return $user;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,8 @@ use ApiPlatform\Metadata\Operation;
|
||||
use ApiPlatform\State\ProcessorInterface;
|
||||
use App\Module\Core\Domain\Entity\User;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Symfony\Bundle\SecurityBundle\Security;
|
||||
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
|
||||
|
||||
use function assert;
|
||||
|
||||
@@ -16,12 +18,23 @@ use function assert;
|
||||
*/
|
||||
final readonly class UserRbacProcessor implements ProcessorInterface
|
||||
{
|
||||
public function __construct(private EntityManagerInterface $em) {}
|
||||
public function __construct(
|
||||
private EntityManagerInterface $em,
|
||||
private Security $security,
|
||||
) {}
|
||||
|
||||
public function process(mixed $data, Operation $operation, array $uriVariables = [], array $context = []): User
|
||||
{
|
||||
assert($data instanceof User);
|
||||
|
||||
// Defense-in-depth: a user may never edit their OWN RBAC assignment
|
||||
// through this endpoint, even with core.users.manage — this prevents
|
||||
// self-escalation if the permission is ever delegated to a non-admin.
|
||||
$current = $this->security->getUser();
|
||||
if ($current instanceof User && $current->getId() === $data->getId()) {
|
||||
throw new AccessDeniedHttpException('You cannot edit your own RBAC assignment.');
|
||||
}
|
||||
|
||||
$this->em->persist($data);
|
||||
$this->em->flush();
|
||||
|
||||
|
||||
@@ -17,9 +17,14 @@ final class ModuleRegistry
|
||||
{
|
||||
$ids = [];
|
||||
foreach ($moduleClasses as $moduleClass) {
|
||||
if (is_a($moduleClass, ModuleInterface::class, true)) {
|
||||
$ids[] = $moduleClass::id();
|
||||
if (!is_a($moduleClass, ModuleInterface::class, true)) {
|
||||
continue;
|
||||
}
|
||||
$id = $moduleClass::id();
|
||||
if (in_array($id, $ids, true)) {
|
||||
throw new InvalidArgumentException(sprintf('Module ID "%s" déclaré plusieurs fois dans la configuration des modules.', $id));
|
||||
}
|
||||
$ids[] = $id;
|
||||
}
|
||||
|
||||
return $ids;
|
||||
|
||||
Reference in New Issue
Block a user