Laravel: Disabling Pagination Causes 403 on Policy-Protected Collection Endpoints
Los mantenedores suelen responder en 1 día
Nadie ha tomado este issue todavía.
Evaluación
- Dificultad
- 3/5
- Tiempo estimado
- 1-2 días
- Aptitud para principiantes
- 72/100
- Tipo de issue
- Error
- Claridad
- Bien especificado
- Estado de actividad
- Activo
- Área
- api, authorization, backend
Línea de trabajo
Comienza con CollectionProvider.php y ResourceAccessChecker.php; después inspecciona AccessCheckerProvider.php en la ruta de denegación de acceso. Reproduce las dos solicitudes de colección con la paginación activada y desactivada usando una policy cuyo viewAny() devuelva true. La tarea estará terminada cuando los endpoints de colección protegidos por policy ya no devuelvan 403 únicamente porque la paginación está desactivada, con cobertura de regresión para pagination=false y pagination=0.
Escrito por el modelo de indexación a partir del texto del issue.
Descripción
API Platform version(s) affected: 4.3.17 (api-platform/laravel)
Description
Any GetCollection operation that has a Policy/security expression (resolved via a Laravel Policy's viewAny() method, e.g. through the standard Gate::getPolicyFor($model) naming-convention resolution) unconditionally returns 403 Access Denied when the request disables client-side pagination (?pagination=false or ?pagination=0), regardless of authentication state or the policy's actual logic — even when viewAny() unconditionally returns true.
Root cause: ApiPlatform\Laravel\Security\ResourceAccessChecker::isGranted() special-cases the Paginator wrapper class when deciding what object to pass to Gate::allows():
public function isGranted(string $resourceClass, string $expression, array $extraVariables = []): bool
{
$object = $extraVariables['object'] ?? null;
return Gate::allows(
$expression,
($object instanceof Paginator || null === $object) ? $resourceClass : $object
);
}
When pagination is enabled, ApiPlatform\Laravel\Eloquent\State\CollectionProvider::provide() returns an ApiPlatform\Laravel\Eloquent\Paginator instance, so $object instanceof Paginator is true, and Gate::allows($expression, $resourceClass) is called with the resource class string — which correctly resolves to the model's Policy class and calls viewAny().
When pagination is disabled, the same provider instead returns a plain Illuminate\Database\Eloquent\Collection (see CollectionProvider::provide(), the false === $this->pagination->isEnabled(...) branch: return $query->get();). This is not a Paginator, so ResourceAccessChecker passes the Collection object itself to Gate::allows($expression, $collection). Laravel's Gate then tries to resolve a policy based on get_class($collection) (Illuminate\Database\Eloquent\Collection), finds no policy registered for that class, and — absent any other applicable ability/Gate::before() override — denies the request by default.
The result: disabling pagination on any policy-protected collection endpoint always 403s, independent of the actual authorization rule.
How to reproduce
Any Eloquent resource with a Policy providing viewAny():
class PaymentMethodPolicy
{
public function viewAny(?RemoteUser $user): bool
{
return true; // unconditionally public
}
}
GET /api/payment_methods
→ 200 OK (Gate::allows('viewAny', PaymentMethod::class) — resolves the policy correctly)
GET /api/payment_methods?pagination=false
→ 403 {"detail":"Access Denied."}
(reproduces identically authenticated or unauthenticated)
GET /api/payment_methods?pagination=0
→ 403 (same)
Possible Solution
ResourceAccessChecker::isGranted() should pass the resource class string whenever $object is a bare collection of resources (i.e., not a single instance of the resource itself) rather than only special-casing Paginator. For example, also treat PartialPaginator and plain Illuminate\Support\Collection/Illuminate\Database\Eloquent\Collection instances the same way Paginator is treated:
$isCollectionLike = $object instanceof Paginator
|| $object instanceof PartialPaginator
|| $object instanceof \Illuminate\Support\Collection;
return Gate::allows(
$expression,
($isCollectionLike || null === $object) ? $resourceClass : $object
);
Additional Context
CollectionProvider:vendor/api-platform/laravel/Eloquent/State/CollectionProvider.phpResourceAccessChecker:vendor/api-platform/laravel/Security/ResourceAccessChecker.phpAccessCheckerProvider(throw site):vendor/api-platform/laravel/State/AccessCheckerProvider.php- This was found while debugging a checkout page that couldn't load any payment methods; it was initially conflated with an unrelated
BooleanFiltervalidation bug (seedocs/bug-reports/api-platform-boolean-filter-validation.md) because both bugs happened to be triggered by parameters on the same request (?enabled=true&pagination=false) and both produce failure responses that look superficially similar during triage (422 vs 403) unless isolated parameter-by-parameter. - Workaround applied in our app: stopped disabling client-side pagination on this endpoint (removed
pagination: falsefrom the frontend query) — the resource's default page size (20) is more than sufficient for a payment-methods list, so no functional loss.
- Lenguaje dominante
- PHP
- Estrellas
- 2.6k
- Forks
- 987
- Merge medio
- 1 d 8 h
- PR fusionados (30 d)
- 80
Preparar el entorno
- Sin Dockerfile ni archivo de Docker Compose
- Tiene una plantilla de pull request
- Leer la guía de contribución
Primeros pasos
- Lee el issue completo y luego la guía de contribución del proyecto.
- Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
- Haz un fork del repositorio y trabaja en una rama.
- Abre un pull request que haga referencia al número del issue.
Más de api-platform/core
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 75/100
api-platform/core#8665 ·
Los mantenedores suelen responder en 1 día
-
Doctrine\Orm\OrderExtension fails on SortDirectionPosiblemente ocupada Un pull request vinculado a esta issue está abierto o ya se fusionó. Abierto
Dificultad 2/5 1-3 horas Aptitud para principiantes 72/100
api-platform/core#8660 ·
Los mantenedores suelen responder en 1 día
-
DeserializeProvider calls PartialDenormalizationException::getErrors(), deprecated in Symfony 8.1Posiblemente ocupada Un pull request vinculado a esta issue está abierto o ya se fusionó. Abierto
Dificultad 2/5 1-3 horas Aptitud para principiantes 76/100
api-platform/core#8650 ·
Los mantenedores suelen responder en 1 día
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 75/100
api-platform/core#8649 ·
Los mantenedores suelen responder en 1 día
-
`OrderExtension` and `OrderFilter` pass string sort directions, deprecated since `doctrine/orm` 3.7Abierto
Dificultad 2/5 1-3 horas Aptitud para principiantes 78/100
api-platform/core#8648 · 1 comentario ·
Los mantenedores suelen responder en 1 día
Todos los issues de api-platform/core
Issues similares
-
bug
Dificultad 1/5 Menos de una hora Aptitud para principiantes 85/100
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 63/100
smarty-php/smarty#1215 ·
-
sync-en
Dificultad 2/5 1-3 horas Aptitud para principiantes 75/100
Los mantenedores suelen responder en 1 día
-
sync-en
Dificultad 2/5 1-3 horas Aptitud para principiantes 72/100
Los mantenedores suelen responder en 4 días
-
bug
Dificultad 2/5 1-3 horas Aptitud para principiantes 65/100
ProfessionalWiki/NeoWiki#1637 ·
Los mantenedores suelen responder en 1 día