Skip to content

Commit b0d3a19

Browse files
authored
fix: authenticate ajax lookup endpoint (#1035)
1 parent 984b745 commit b0d3a19

2 files changed

Lines changed: 86 additions & 2 deletions

File tree

sqladmin/application.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -730,6 +730,11 @@ async def logout(self, request: Request) -> Response:
730730
async def ajax_lookup(self, request: Request) -> Response:
731731
"""Ajax lookup route."""
732732

733+
if self.authentication_backend is not None:
734+
authenticated = await self.authentication_backend.authenticate(request)
735+
if not authenticated or isinstance(authenticated, Response):
736+
return RedirectResponse(request.url_for("admin:login"), status_code=302)
737+
733738
identity = request.path_params["identity"]
734739
model_view = self._find_model_view(identity)
735740

tests/test_authentication.py

Lines changed: 81 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
from typing import Generator
22

33
import pytest
4-
from sqlalchemy import Column, Integer
4+
from sqlalchemy import Column, ForeignKey, Integer, String
55
from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker
6-
from sqlalchemy.orm import declarative_base
6+
from sqlalchemy.orm import declarative_base, relationship
77
from starlette.applications import Starlette
88
from starlette.requests import Request
99
from starlette.responses import JSONResponse, RedirectResponse
@@ -118,3 +118,82 @@ def test_action_access_login_required_views(client: TestClient) -> None:
118118

119119
response = client.get("/admin/movie/action/test")
120120
assert {"status": "ok"} == response.json()
121+
122+
123+
class Artist(Base):
124+
__tablename__ = "artists_auth"
125+
126+
id = Column(Integer, primary_key=True)
127+
name = Column(String(50))
128+
129+
songs = relationship("SongAuth", back_populates="artist")
130+
131+
def __str__(self) -> str:
132+
return f"Artist {self.id}"
133+
134+
135+
class SongAuth(Base):
136+
__tablename__ = "songs_auth"
137+
138+
id = Column(Integer, primary_key=True)
139+
artist_id = Column(Integer, ForeignKey("artists_auth.id"))
140+
141+
artist = relationship("Artist", back_populates="songs")
142+
143+
144+
class ArtistAdmin(ModelView, model=Artist):
145+
pass
146+
147+
148+
class SongAuthAdmin(ModelView, model=SongAuth):
149+
form_ajax_refs = {
150+
"artist": {
151+
"fields": ("name",),
152+
"order_by": "name",
153+
}
154+
}
155+
156+
157+
admin.add_view(ArtistAdmin)
158+
admin.add_view(SongAuthAdmin)
159+
160+
161+
@pytest.fixture(autouse=False)
162+
def prepare_ajax_tables() -> Generator[None, None, None]:
163+
Base.metadata.create_all(engine)
164+
yield
165+
Base.metadata.drop_all(engine)
166+
167+
168+
def test_ajax_lookup_unauthenticated_redirects_to_login(
169+
client: TestClient,
170+
) -> None:
171+
response = client.get("/admin/song-auth/ajax/lookup?name=artist&term=test")
172+
assert response.url == "http://testserver/admin/login"
173+
174+
175+
def test_ajax_lookup_authenticated_returns_200(
176+
client: TestClient,
177+
prepare_ajax_tables: None,
178+
) -> None:
179+
client.post(
180+
"/admin/login",
181+
data={"username": "a", "password": "b"},
182+
)
183+
184+
response = client.get("/admin/song-auth/ajax/lookup?name=artist&term=test")
185+
assert response.status_code == 200
186+
assert "results" in response.json()
187+
188+
189+
def test_ajax_lookup_after_logout_redirects_to_login(
190+
client: TestClient,
191+
) -> None:
192+
client.post(
193+
"/admin/login",
194+
data={"username": "a", "password": "b"},
195+
)
196+
client.get("/admin/logout")
197+
198+
response = client.get("/admin/song-auth/ajax/lookup?name=artist&term=test")
199+
assert response.url == "http://testserver/admin/login"

0 commit comments

Comments
 (0)