Skip to content

Commit 5844673

Browse files
fix(auth): gate the search surface on read:search instead of basic (#13739) to release v4.5 (#13740)
Co-authored-by: Justin Tahara <105671973+justin-tahara@users.noreply.github.com>
1 parent 1c102ad commit 5844673

4 files changed

Lines changed: 62 additions & 7 deletions

File tree

backend/ee/onyx/server/query_and_chat/search_backend.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@
5252
@router.post("/search-flow-classification")
5353
def search_flow_classification(
5454
request: SearchFlowClassificationRequest,
55-
_: User = Depends(require_permission(Permission.BASIC_ACCESS)),
55+
_: User = Depends(require_permission(Permission.READ_SEARCH)),
5656
db_session: Session = Depends(get_session),
5757
) -> SearchFlowClassificationResponse:
5858
query = request.user_query
@@ -92,7 +92,7 @@ def search_flow_classification(
9292
)
9393
def handle_send_search_message(
9494
request: SendSearchQueryRequest,
95-
user: User = Depends(require_permission(Permission.BASIC_ACCESS)),
95+
user: User = Depends(require_permission(Permission.READ_SEARCH)),
9696
db_session: Session = Depends(get_session),
9797
) -> StreamingResponse | SearchFullResponse:
9898
"""
@@ -142,7 +142,7 @@ def stream_generator() -> Generator[str, None, None]:
142142
def get_search_history(
143143
limit: int = 100,
144144
filter_days: int | None = None,
145-
user: User = Depends(require_permission(Permission.BASIC_ACCESS)),
145+
user: User = Depends(require_permission(Permission.READ_SEARCH)),
146146
db_session: Session = Depends(get_session),
147147
) -> SearchHistoryResponse:
148148
"""

backend/onyx/server/features/web_search/api.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -231,7 +231,7 @@ def _open_urls(
231231
@router.post("/search", response_model=WebSearchWithContentResponse)
232232
def execute_web_search(
233233
request: WebSearchToolRequest,
234-
_: User = Depends(require_permission(Permission.BASIC_ACCESS)),
234+
_: User = Depends(require_permission(Permission.READ_SEARCH)),
235235
db_session: Session = Depends(get_session),
236236
) -> WebSearchWithContentResponse:
237237
"""
@@ -274,7 +274,7 @@ def execute_web_search(
274274
@router.post("/search-lite", response_model=WebSearchToolResponse)
275275
def execute_web_search_lite(
276276
request: WebSearchToolRequest,
277-
_: User = Depends(require_permission(Permission.BASIC_ACCESS)),
277+
_: User = Depends(require_permission(Permission.READ_SEARCH)),
278278
db_session: Session = Depends(get_session),
279279
) -> WebSearchToolResponse:
280280
"""
@@ -290,7 +290,7 @@ def execute_web_search_lite(
290290
@router.post("/open-urls", response_model=OpenUrlsToolResponse)
291291
def execute_open_urls(
292292
request: OpenUrlsToolRequest,
293-
_: User = Depends(require_permission(Permission.BASIC_ACCESS)),
293+
_: User = Depends(require_permission(Permission.READ_SEARCH)),
294294
db_session: Session = Depends(get_session),
295295
) -> OpenUrlsToolResponse:
296296
"""

backend/onyx/server/query_and_chat/query_backend.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ def get_tags(
8787
sources: list[DocumentSource] | None = None,
8888
allow_prefix: bool = True, # This is currently the only option
8989
limit: int = 50,
90-
_: User = Depends(require_permission(Permission.BASIC_ACCESS)),
90+
_: User = Depends(require_permission(Permission.READ_SEARCH)),
9191
db_session: Session = Depends(get_session),
9292
) -> TagResponse:
9393
if not allow_prefix:
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
"""
2+
Pins the search surface's route gates to READ_SEARCH so a read:search-scoped
3+
PAT can reach every endpoint the scope's description promises ("Use search and
4+
query endpoints"). Scoped PATs can never satisfy BASIC_ACCESS (implication
5+
only flows basic -> read:search), so a route in this surface regressing to
6+
BASIC_ACCESS silently locks out scoped tokens while unscoped ones still work.
7+
"""
8+
9+
import inspect
10+
from collections.abc import Callable
11+
from typing import Any
12+
13+
import pytest
14+
15+
from ee.onyx.server.query_and_chat.search_backend import (
16+
get_search_history,
17+
handle_send_search_message,
18+
search_flow_classification,
19+
)
20+
from onyx.db.enums import Permission
21+
from onyx.server.features.web_search.api import (
22+
execute_open_urls,
23+
execute_web_search,
24+
execute_web_search_lite,
25+
)
26+
from onyx.server.query_and_chat.query_backend import get_tags
27+
28+
_SEARCH_SURFACE_HANDLERS: list[Callable[..., Any]] = [
29+
handle_send_search_message,
30+
get_search_history,
31+
search_flow_classification,
32+
get_tags,
33+
execute_web_search,
34+
execute_web_search_lite,
35+
execute_open_urls,
36+
]
37+
38+
39+
def _permission_gates(handler: Callable[..., Any]) -> list[Permission | None]:
40+
gates: list[Permission | None] = []
41+
for param in inspect.signature(handler).parameters.values():
42+
dependency = getattr(param.default, "dependency", None)
43+
if dependency is not None and getattr(
44+
dependency, "_is_require_permission", False
45+
):
46+
gates.append(getattr(dependency, "_required_permission", None))
47+
return gates
48+
49+
50+
@pytest.mark.parametrize("handler", _SEARCH_SURFACE_HANDLERS, ids=lambda h: h.__name__)
51+
def test_search_surface_route_admits_read_search_scope(
52+
handler: Callable[..., Any],
53+
) -> None:
54+
gates = _permission_gates(handler)
55+
assert gates == [Permission.READ_SEARCH]

0 commit comments

Comments
 (0)