Skip to content

Commit 805dc06

Browse files
Validate page and pageSize query parameters (#752)
Co-authored-by: Amin Alaee <[email protected]>
1 parent 0dc1e4d commit 805dc06

File tree

3 files changed

+50
-2
lines changed

3 files changed

+50
-2
lines changed

sqladmin/application.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -440,6 +440,14 @@ async def list(self, request: Request) -> Response:
440440
pagination = await model_view.list(request)
441441
pagination.add_pagination_urls(request.url)
442442

443+
if (
444+
pagination.page * pagination.page_size
445+
> pagination.count + pagination.page_size
446+
):
447+
raise HTTPException(
448+
status_code=400, detail="Invalid page or pageSize parameter"
449+
)
450+
443451
context = {"model_view": model_view, "pagination": pagination}
444452
return await self.templates.TemplateResponse(
445453
request, model_view.list_template, context

sqladmin/models.py

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
from sqlalchemy.sql.elements import ClauseElement
2626
from sqlalchemy.sql.expression import Select, select
2727
from starlette.datastructures import URL
28+
from starlette.exceptions import HTTPException
2829
from starlette.requests import Request
2930
from starlette.responses import StreamingResponse
3031
from wtforms import Field, Form
@@ -746,15 +747,26 @@ def _default_formatter(self, value: Any) -> Any:
746747

747748
return value
748749

750+
def validate_page_number(self, number: Union[str, None], default: int) -> int:
751+
if not number:
752+
return default
753+
754+
try:
755+
return int(number)
756+
except ValueError:
757+
raise HTTPException(
758+
status_code=400, detail="Invalid page or pageSize parameter"
759+
)
760+
749761
async def count(self, request: Request, stmt: Optional[Select] = None) -> int:
750762
if stmt is None:
751763
stmt = self.count_query(request)
752764
rows = await self._run_query(stmt)
753765
return rows[0]
754766

755767
async def list(self, request: Request) -> Pagination:
756-
page = int(request.query_params.get("page", 1))
757-
page_size = int(request.query_params.get("pageSize", 0))
768+
page = self.validate_page_number(request.query_params.get("page"), 1)
769+
page_size = self.validate_page_number(request.query_params.get("pageSize"), 0)
758770
page_size = min(page_size or self.page_size, max(self.page_size_options))
759771
search = request.query_params.get("search", None)
760772

tests/test_application.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
from typing import Generator
2+
3+
import pytest
14
from sqlalchemy import Column, Integer, String
25
from sqlalchemy.orm import declarative_base
36
from starlette.applications import Starlette
@@ -27,6 +30,13 @@ class User(Base):
2730
name = Column(String(32), default="SQLAdmin")
2831

2932

33+
@pytest.fixture(autouse=True)
34+
def prepare_database() -> Generator[None, None, None]:
35+
Base.metadata.create_all(engine)
36+
yield
37+
Base.metadata.drop_all(engine)
38+
39+
3040
def test_application_title() -> None:
3141
app = Starlette()
3242
Admin(app=app, engine=engine)
@@ -153,3 +163,21 @@ class DataModelAdmin(ModelView, model=DataModel):
153163
assert admin._denormalize_wtform_data({"data_": "abcdef"}, datamodel) == {
154164
"data": "abcdef"
155165
}
166+
167+
168+
def test_validate_page_and_page_size():
169+
app = Starlette()
170+
admin = Admin(app=app, engine=engine)
171+
172+
class UserAdmin(ModelView, model=User):
173+
...
174+
175+
admin.add_view(UserAdmin)
176+
177+
client = TestClient(app)
178+
179+
response = client.get("/admin/user/list?page=10000")
180+
assert response.status_code == 400
181+
182+
response = client.get("/admin/user/list?page=aaaa")
183+
assert response.status_code == 400

0 commit comments

Comments
 (0)