Skip to content

fix(api): enforce dataset service API token scope - #44840

Merged
laipz8200 merged 1 commit into
langgenius:mainfrom
guishiw:fix/issue-41316-dataset-service-api-permissions
Nov 2, 2026
Merged

laipz8200 merged 1 commit into
langgenius:mainfrom
guishiw:fix/issue-41316-dataset-service-api-permissions

Conversation

@guishiw

@guishiw guishiw commented Oct 26, 2026

Copy link
Copy Markdown
Contributor

Summary

Fixes #41316.

This PR fixes inconsistent Dataset Service API authorization caused by evaluating dataset visibility against the workspace Owner injected into current_user, rather than the permissions granted to the API token.

The change:

  • Allows legacy workspace-scoped Dataset API keys to list datasets across their tenant without inheriting the Owner's per-user dataset visibility filters.
  • Restricts resource access tokens to active datasets explicitly bound to the token.
  • Prevents include_all=true from widening a resource token's authorization scope.
  • Preserves tenant isolation and fails closed when the grant and requested tenant do not match.
  • Keeps legacy bound keys denied on collection endpoints without a dataset ID.
  • Adds regression coverage across the token resolver, authentication decorator, controller, and dataset service layers.

No new runtime dependencies are introduced.

Validation completed:

  • 318 targeted unit tests passed.
  • Ruff lint and formatting checks passed.
  • Python compilation and git diff --check passed.

Screenshots

This is a backend-only authorization change, so screenshots are not applicable.

Before After
Dataset listing was evaluated as the workspace Owner, causing per-user filtering and inconsistent behavior with direct dataset access. Dataset listing is evaluated using an explicit token grant: workspace keys receive tenant scope, while resource tokens remain limited to their bound datasets.

Checklist

  • This change requires a documentation update, included: Dify Document — Not applicable; this is a backend authorization bug fix with no public API contract change.
  • I understand that this PR may be closed in case there was no previous discussion or issues. (This doesn't apply to typos!)
  • I've verified the change and added or updated tests where meaningful regression risk justifies coverage.
  • I've updated the documentation accordingly. — Not applicable.
  • I ran make lint && make type-check (backend). vp staged is not applicable because this PR contains no frontend changes.

@github-actions github-actions Bot added the size:L This PR changes 100-499 lines, ignoring generated files. label Oct 26, 2026
@guishiw

guishiw commented Oct 26, 2026 •

Copy link
Copy Markdown
Contributor Author

Hi maintainers, the branch has been updated and all local backend checks passed (make lint, make type-check, and 318 targeted unit tests). Could you please approve the workflows when convenient? Thank you!

@github-actions

github-actions Bot commented Oct 26, 2026 •

Copy link
Copy Markdown
Contributor

Pyrefly Diff

base → PR
--- /tmp/pyrefly_base.txt	2026-11-02 21:00:46.550580213 +0000
+++ /tmp/pyrefly_pr.txt	2026-11-02 21:00:34.949520749 +0000
@@ -2884,8 +2884,14 @@
    --> tests/unit_tests/controllers/service_api/test_wraps.py:415:16
 ERROR Argument `Literal['abnormal']` is not assignable to parameter `value` with type `AppStatus | SQLCoreOperations[AppStatus]` in function `sqlalchemy.orm.base.Mapped.__set__` [bad-argument-type]
    --> tests/unit_tests/controllers/service_api/test_wraps.py:450:28
+ERROR Object of class `NoneType` has no attribute `tenant_id` [missing-attribute]
+   --> tests/unit_tests/controllers/service_api/test_wraps.py:893:16
+ERROR Object of class `NoneType` has no attribute `dataset_ids` [missing-attribute]
+   --> tests/unit_tests/controllers/service_api/test_wraps.py:894:16
+ERROR Object of class `NoneType` has no attribute `dataset_ids` [missing-attribute]
+   --> tests/unit_tests/controllers/service_api/test_wraps.py:961:16
 ERROR Object of class `NoneType` has no attribute `last_used_at` [missing-attribute]
-   --> tests/unit_tests/controllers/service_api/test_wraps.py:951:16
+   --> tests/unit_tests/controllers/service_api/test_wraps.py:963:16
 ERROR `object` is not assignable to attribute `request` with type `Request` [bad-assignment]
   --> tests/unit_tests/controllers/trigger/test_trigger.py:11:22
 ERROR Object of class `tuple` has no attribute `status_code` [missing-attribute]
@@ -5939,41 +5945,41 @@
 ERROR `None` is not subscriptable [unsupported-operation]
    --> tests/unit_tests/services/knowledge/metadata/test_application.py:434:20
 ERROR Object of class `NoneType` has no attribute `keyword_number` [missing-attribute]
-    --> tests/unit_tests/services/knowledge/test_dataset_service.py:1438:20
+    --> tests/unit_tests/services/knowledge/test_dataset_service.py:1519:20
 ERROR Argument `str` is not assignable to parameter `data_source_type` with type `DataSourceType | SQLCoreOperations[DataSourceType]` in function `models.dataset.Document.__init__` [bad-argument-type]
-    --> tests/unit_tests/services/knowledge/test_dataset_service.py:1753:26
+    --> tests/unit_tests/services/knowledge/test_dataset_service.py:1834:26
 ERROR Argument `str` is not assignable to parameter `indexing_status` with type `IndexingStatus | SQLCoreOperations[IndexingStatus]` in function `models.dataset.Document.__init__` [bad-argument-type]
-    --> tests/unit_tests/services/knowledge/test_dataset_service.py:1759:25
+    --> tests/unit_tests/services/knowledge/test_dataset_service.py:1840:25
 ERROR Argument `Literal['opendal']` is not assignable to parameter `storage_type` with type `StorageType` in function `tests.helpers.unit.model_factories.make_upload_file` [bad-argument-type]
-    --> tests/unit_tests/services/knowledge/test_dataset_service.py:1776:22
+    --> tests/unit_tests/services/knowledge/test_dataset_service.py:1857:22
 ERROR Argument `str` is not assignable to parameter `mode` with type `ProcessRuleMode | SQLCoreOperations[ProcessRuleMode]` in function `models.dataset.DatasetProcessRule.__init__` [bad-argument-type]
-    --> tests/unit_tests/services/knowledge/test_dataset_service.py:1797:37
+    --> tests/unit_tests/services/knowledge/test_dataset_service.py:1878:37
 ERROR Argument `Literal['opendal']` is not assignable to parameter `storage_type` with type `StorageType` in function `models.model.UploadFile.__init__` [bad-argument-type]
-    --> tests/unit_tests/services/knowledge/test_dataset_service.py:2037:30
+    --> tests/unit_tests/services/knowledge/test_dataset_service.py:2118:30
 ERROR Argument `Literal['account']` is not assignable to parameter `created_by_role` with type `CreatorUserRole` in function `models.model.UploadFile.__init__` [bad-argument-type]
-    --> tests/unit_tests/services/knowledge/test_dataset_service.py:2043:33
+    --> tests/unit_tests/services/knowledge/test_dataset_service.py:2124:33
 ERROR `None` is not subscriptable [unsupported-operation]
-    --> tests/unit_tests/services/knowledge/test_dataset_service.py:2068:20
+    --> tests/unit_tests/services/knowledge/test_dataset_service.py:2149:20
 ERROR Object of class `NoneType` has no attribute `name` [missing-attribute]
-    --> tests/unit_tests/services/knowledge/test_dataset_service.py:2069:20
+    --> tests/unit_tests/services/knowledge/test_dataset_service.py:2150:20
 ERROR Argument `Literal['opendal']` is not assignable to parameter `storage_type` with type `StorageType` in function `models.model.UploadFile.__init__` [bad-argument-type]
-    --> tests/unit_tests/services/knowledge/test_dataset_service.py:2511:30
+    --> tests/unit_tests/services/knowledge/test_dataset_service.py:2592:30
 ERROR Argument `Literal['account']` is not assignable to parameter `created_by_role` with type `CreatorUserRole` in function `models.model.UploadFile.__init__` [bad-argument-type]
-    --> tests/unit_tests/services/knowledge/test_dataset_service.py:2517:33
+    --> tests/unit_tests/services/knowledge/test_dataset_service.py:2598:33
 ERROR Object of class `NoneType` has no attribute `status` [missing-attribute]
-    --> tests/unit_tests/services/knowledge/test_dataset_service.py:2596:20
+    --> tests/unit_tests/services/knowledge/test_dataset_service.py:2677:20
 ERROR Object of class `NoneType` has no attribute `status` [missing-attribute]
-    --> tests/unit_tests/services/knowledge/test_dataset_service.py:2695:20
+    --> tests/unit_tests/services/knowledge/test_dataset_service.py:2776:20
 ERROR Argument `list[PreProcessingRule] | None` is not assignable to parameter `obj` with type `Sized` in function `len` [bad-argument-type]
-    --> tests/unit_tests/services/knowledge/test_dataset_service.py:2799:24
+    --> tests/unit_tests/services/knowledge/test_dataset_service.py:2880:24
 ERROR `None` is not subscriptable [unsupported-operation]
-    --> tests/unit_tests/services/knowledge/test_dataset_service.py:2800:20
+    --> tests/unit_tests/services/knowledge/test_dataset_service.py:2881:20
 ERROR Object of class `NoneType` has no attribute `rules` [missing-attribute]
-    --> tests/unit_tests/services/knowledge/test_dataset_service.py:3800:42
+    --> tests/unit_tests/services/knowledge/test_dataset_service.py:3881:42
 ERROR Argument `Literal['completed']` is not assignable to parameter `indexing_status` with type `IndexingStatus | SQLCoreOperations[IndexingStatus]` in function `models.dataset.Document.__init__` [bad-argument-type]
-    --> tests/unit_tests/services/knowledge/test_dataset_service.py:4235:25
+    --> tests/unit_tests/services/knowledge/test_dataset_service.py:4316:25
 ERROR Argument `str` is not assignable to parameter `doc_form` with type `IndexStructureType | SQLCoreOperations[IndexStructureType]` in function `models.dataset.Document.__init__` [bad-argument-type]
-    --> tests/unit_tests/services/knowledge/test_dataset_service.py:4236:18
+    --> tests/unit_tests/services/knowledge/test_dataset_service.py:4317:18
 ERROR Object of class `FromClause` has no attribute `create` [missing-attribute]
   --> tests/unit_tests/services/plugin/test_plugin_parameter_service.py:47:9
 ERROR Object of class `NoneType` has no attribute `marked_name` [missing-attribute]

@github-actions

github-actions Bot commented Oct 26, 2026 •

Copy link
Copy Markdown
Contributor

Pyrefly Type Coverage

Metric Base PR Delta
Type coverage 82.88% 82.88% +0.00%
Strict coverage 82.53% 82.54% +0.00%
Typed symbols 85,443 85,468 +25
Untyped symbols 17,724 17,725 +1
Modules 4372 4372 0

@codecov

codecov Bot commented Oct 26, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 89.77%. Comparing base (a8631e5) to head (109dde3).

Additional details and impacted files
@@            Coverage Diff             @@
##             main   #44840      +/-   ##
==========================================
- Coverage   89.77%   89.77%   -0.01%     
==========================================
  Files        6239     6239              
  Lines      390902   390924      +22     
  Branches    79285    79291       +6     
==========================================
+ Hits       350934   350950      +16     
- Misses      33870    33875       +5     
- Partials     6098     6099       +1     
Flag Coverage Δ
api 89.63% <96.66%> (-0.01%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@laipz8200
laipz8200 force-pushed the fix/issue-41316-dataset-service-api-permissions branch from a341277 to 109dde3 Compare November 2, 2026 20:58

@laipz8200 laipz8200 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. Thank you for your contribution!

@laipz8200
laipz8200 added this pull request to the merge queue Nov 2, 2026
Merged via the queue into langgenius:main with commit b5722ed Nov 2, 2026
47 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:L This PR changes 100-499 lines, ignoring generated files.

Projects

None yet

2 participants