Skip to content

refactor: casl factory - datasets#2760

Open
HayenNico wants to merge 5 commits into
refactor-casl-factory-cleanupfrom
refactor-casl-factory-datasets
Open

refactor: casl factory - datasets#2760
HayenNico wants to merge 5 commits into
refactor-casl-factory-cleanupfrom
refactor-casl-factory-datasets

Conversation

@HayenNico
Copy link
Copy Markdown
Member

@HayenNico HayenNico commented May 26, 2026

Description

Subsection of PR #2748 for datasets. PR depends on #2759 to be merged first.

This unifies the datasetEndpointAccess and datasetInstanceAccess functions in CaslAbilityFactory, removes instance-level Action elements and adjusts all affected controllers to accommodate the change.

Fixes

  • The endpoints /proposals/:id/datasets and /samples/:id/datasets would not apply correct filters due to a mismatch between the used casl ability and checked Action elements

Changes:

  • Replace CaslAbilityFactory.datasetsInstanceAccess and CaslAbilityFactory.datasetsEndpointAccess with one function CaslAbilityFactory.datasetAccess
  • Remove all instance-level dataset Action elements
  • Adjust endpoint and instance auth logic in dataset controllers
  • Remove unused readOwner checks, treat readPublic as always true (datasets-access.service)
  • Adjust auth logic in samples, proposals, users and origdatablocks controllers where dataset abilities are used

Tests included

  • Included for each change/fix?
  • Passing?

Documentation

  • swagger documentation updated (required for API changes)
  • official documentation updated

official documentation info

Summary by Sourcery

Consolidate dataset authorization into a single CASL ability and align controllers and access services with the simplified permission model.

Bug Fixes:

  • Ensure /proposals/:id/datasets, /samples/:id/datasets, and other dataset-related endpoints apply correct filters based on unified dataset permissions for authenticated and unauthenticated users.

Enhancements:

  • Replace separate dataset endpoint and instance access factories with a single datasetAccess ability using unified actions and conditions.
  • Simplify dataset-related controllers, V4 controllers, and access services to rely on generic Dataset* actions instead of owner/access/public-specific variants.
  • Remove numerous instance-level dataset Action enum values in favor of a smaller, clearer action set, adding AccessAny for elevated roles.
  • Treat public dataset reads consistently by defaulting unauthenticated access to published datasets and tightening v4 behavior to require public endpoints for anonymous users.
  • Update Swagger-guarded policies and related controllers (datasets, samples, proposals, origdatablocks, users) to use the new datasetAccess checks.

Tests:

  • Adjust dataset controller specs to mock the new datasetAccess factory method and keep authorization tests passing.

@HayenNico HayenNico requested a review from a team as a code owner May 26, 2026 17:51
Copy link
Copy Markdown

@sourcery-ai sourcery-ai Bot left a comment

Choose a reason for hiding this comment

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

Hey - I've found 1 issue, and left some high level feedback:

  • In several places you now rely on ability.can(Action.DatasetRead, DatasetClass) (or other dataset actions) with the class as subject, but the new datasetAccess rules are all condition-based; CASL ignores conditions when checking against a type, so these checks effectively become unconditional and may grant broader access than intended—consider switching those checks to use an instance or reintroducing explicit owner/access/published predicates where you need them.
  • The new unauthenticated behaviour in DatasetsAccessService.addDatasetAccess / addDatasetAccessToPipeline appears to no longer enforce isPublished: true for lookups (because canView now becomes true due to the generic DatasetRead rule on the class), which could expose non‑public datasets via lookups; it would be safer to explicitly add the isPublished filter for unauthenticated users instead of relying on the generic ability.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In several places you now rely on `ability.can(Action.DatasetRead, DatasetClass)` (or other dataset actions) with the class as subject, but the new `datasetAccess` rules are all condition-based; CASL ignores conditions when checking against a type, so these checks effectively become unconditional and may grant broader access than intended—consider switching those checks to use an instance or reintroducing explicit owner/access/published predicates where you need them.
- The new unauthenticated behaviour in `DatasetsAccessService.addDatasetAccess` / `addDatasetAccessToPipeline` appears to no longer enforce `isPublished: true` for lookups (because `canView` now becomes true due to the generic `DatasetRead` rule on the class), which could expose non‑public datasets via lookups; it would be safer to explicitly add the `isPublished` filter for unauthenticated users instead of relying on the generic ability.

## Individual Comments

### Comment 1
<location path="src/datasets/datasets-access.service.ts" line_range="121-126" />
<code_context>
-      Action.DatasetReadManyAccess,
-      DatasetClass,
-    );
+    const ability = this.caslAbilityFactory.datasetAccess(currentUser);
+    const canViewAny = ability.can(Action.AccessAny, DatasetClass);
+    const canView = ability.can(Action.DatasetRead, DatasetClass);

     if (!canViewAny) {
-      if (canViewAccess) {
+      if (canView) {
         fieldValue.$lookup.pipeline?.unshift({
           $match: {
</code_context>
<issue_to_address>
**issue (bug_risk):** Potential runtime error in `addDatasetAccess` when `currentUser` is undefined.

In `addDatasetAccess`, `currentUser` is still dereferenced when it may be `undefined`:

```ts
const currentUser = this.request.user as JWTUser;
const ability = this.caslAbilityFactory.datasetAccess(currentUser);
const canViewAny = ability.can(Action.AccessAny, DatasetClass);
const canView = ability.can(Action.DatasetRead, DatasetClass);

if (!canViewAny) {
  if (canView) {
    fieldValue.$lookup.pipeline?.unshift({
      $match: {
        $or: [
          { ownerGroup: { $in: currentUser.currentGroups } },
          { accessGroups: { $in: currentUser.currentGroups } },
          { sharedWith: { $in: [currentUser.email] } },
          { isPublished: true },
        ],
      },
    });
  }
}
```

With the new `datasetAccess` behavior, unauthenticated users can have `canView === true` for `DatasetRead` on `DatasetClass`, so this branch will run with `currentUser === undefined`, causing a runtime error. Previously, the branch depended on `canViewAccess`, which was `false` for unauthenticated users.

Please gate this branch on the presence of a user (e.g. `if (currentUser && canView)`) or handle the unauthenticated case separately, similar to `getAccessDetailsForLookup`.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread src/datasets/datasets-access.service.ts Outdated
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant