Hacktoberfest 2026: los issues que los mantenedores marcaron para octubre, abiertos y aptos para principiantes. Explorar issues de Hacktoberfest

Laravel: Disabling Pagination Causes 403 on Policy-Protected Collection Endpoints

Cerrado
#8,482 0 comentarios 0 reacciones 0 asignados Ver en GitHub

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
Stack tecnológico
laravel, php

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.php
  • ResourceAccessChecker: vendor/api-platform/laravel/Security/ResourceAccessChecker.php
  • AccessCheckerProvider (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 BooleanFilter validation bug (see docs/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: false from 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

Primeros pasos

  1. Lee el issue completo y luego la guía de contribución del proyecto.
  2. Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
  3. Haz un fork del repositorio y trabaja en una rama.
  4. Abre un pull request que haga referencia al número del issue.

Más de api-platform/core

Todos los issues de api-platform/core

Issues similares

Más issues de PHP

Recibe los nuevos issues en tu correo

Un resumen breve de issues de GitHub para principiantes.