Repository navigation
refactor: casl factory - datasets - #2760
Conversation
There was a problem hiding this comment.
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 newdatasetAccessrules 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/addDatasetAccessToPipelineappears to no longer enforceisPublished: truefor lookups (becausecanViewnow becomes true due to the genericDatasetReadrule on the class), which could expose non‑public datasets via lookups; it would be safer to explicitly add theisPublishedfilter 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>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
6fa3dd6 to
cc8fc38
Compare
|
Overall review: Code Review: refactor-casl-factory-datasets BranchAssessmentThe branch refactors CASL authorization for datasets by:
Improvement needed
VerdictNecessary with caveats. The refactoring improves maintainability by reducing duplication and separating concerns. However, it trades fine-grained permissions for simplicity. Verify that:
If these are confirmed, the changes are a net improvement. If not, revert or extend the action model to preserve necessary granularity. Generated with Mistral AI |
Code Review: refactor-casl-factory-datasets Branch
1. Logic Correctness: VERIFIED ✅The refactoring maintains equivalent permission logic through a coarse-grained action model:
CASL behavior: Multiple can(Action.DatasetRead, DatasetClass, ifOwner);
can(Action.DatasetRead, DatasetClass, ifAccess);
can(Action.DatasetRead, DatasetClass, ifPublished);This is functionally equivalent to the old model's separate actions. 2. Edge Cases: ALL HANDLED ✅
3. What the Changed Code DoesExtracted Components:
Simplified Action Model:
Controller Changes:Replaced verbose permission checks: // OLD
ability.can(Action.DatasetReadAny, DatasetClass) ||
ability.can(Action.DatasetReadOneOwner, datasetInstance) ||
ability.can(Action.DatasetReadOneAccess, datasetInstance) ||
ability.can(Action.DatasetReadOnePublic, datasetInstance)
// NEW
ability.can(Action.DatasetRead, datasetInstance)
Access Service Changes:
4. Do the Changes Make Sense?YES, with architectural benefits:
Trade-offs:
Verdict: The trade-off is worthwhile. The old model's granularity was not being fully utilized (controllers already combined multiple checks with OR logic), and the new model achieves the same runtime behavior with significantly cleaner code. 5. Unreachable Code: NONE FOUND ✅Analyzed files:
Potential issue identified:
Final Assessment
Recommendation: MERGE — The refactoring is well-executed, maintains correctness, and significantly improves code maintainability. Generated with Mistral AI |
nitrosx
left a comment
There was a problem hiding this comment.
Great work.
Small code refactoring in dataset abilities regarding the order of the permissions.
I do not understand the need of the need for AccessAny. Could you please elaborate on it?
I do apologize for all the comments about AccessAny on all the files.
Security Review: refactor-casl-factory-datasets Branch
1. Injection Vulnerabilities: ❌ NONEAnalysis:
Example of safe usage: // datasets.ability.ts
const ifOwner = { ownerGroup: { $in: user?.currentGroups } };
can(Action.DatasetRead, DatasetClass, ifOwner);→ 2. Sensitive User Data Exposure: ❌ NONEAnalysis:
Verdict: The refactoring does not expose any additional user data. 3. Insecure API Usage: ❌ NONEAnalysis:
Verdict: No insecure API usage patterns introduced. 4. Authentication Bypass: ❌ NONEDeep Analysis:
Critical Check: CASL Semantics
V4 Controller Note: if (!user) {
// In API v4 unauthorized users must use the public endpoints
throw new ForbiddenException("Unauthorized access");
}This is intentional (per comment) and represents a security improvement, not a bypass. Final Verdict: ALL SECURE ✅
Conclusion: The refactoring maintains the same security posture while improving code clarity. No security vulnerabilities introduced. Generated with Mistral Ai |
|
@HayenNico Thank you for the explanation regarding Last question: Given that we use the following convention in naming: type of entry and action, which results in If you think that this needs a longer discussion, I am ok to merge the Pr as it is, and make a following PR with the additional changes. Keep in mind that there is the PR #2721 which I will refactor it keeping following the changes introduced by this PR. |
|
@nitrosx I'm okay with renaming the AccessAny action to something more sensible (I prefer Regarding PR #2721 and dev documentation: I was waiting to write that until #2759 is reviewed and merged and this PR is rebased on master. I think it would be best if you go ahead with #2721 and then we can refactor that document in this branch to mirror the new changes |
|
@HayenNico let's keep |
Code Review Report: Branch
|
| File | Change Type | Description |
|---|---|---|
src/casl/abilities/datasets.ability.ts |
New | New dedicated service for dataset authorization logic |
docs/developer-guide/authorization/datasets.md |
New | Comprehensive documentation of authorization model |
src/casl/action.enum.ts |
Modified | Removed 44 old dataset-specific actions, kept 17 main actions |
src/casl/casl-ability.factory.ts |
Modified | Removed datasetEndpointAccess and datasetInstanceAccess, added datasetAccess method |
src/casl/casl.module.ts |
Modified | Added DatasetAbility to providers |
src/datasets/datasets-access.service.ts |
Modified | Updated to use new action names and datasetAccess |
src/datasets/datasets.controller.ts |
Modified | Updated all authorization checks to use simplified actions |
src/datasets/datasets.v4.controller.ts |
Modified | Updated authorization checks for API v4 |
src/origdatablocks/origdatablocks.v4.controller.ts |
Modified | Updated dataset authorization checks |
src/proposals/proposals.controller.ts |
Modified | Updated dataset access checks |
src/samples/samples.controller.ts |
Modified | Updated dataset access checks |
src/users/users.controller.ts |
Modified | Updated dataset creation authorization |
Assessment
Necessary: Yes. The old system had:
- 40+ dataset-specific action types causing complexity
- Duplicate authorization logic between endpoint and instance access
- Inconsistent action naming conventions
- Hard-to-maintain permission matrices
Improvement Needed:
- The
DatasetAbilityclass could benefit from unit tests (currently only integration tests exist) - Some controller methods could be further simplified (minor redundancy in filter building)
- Consider adding validation for
accessGroupsconfiguration to ensure required fields exist
Verdict: Excellent refactoring. The changes significantly improve code maintainability while preserving all functionality. The new structure is cleaner, better documented, and follows SOLID principles better.
Security Review
-
Potential injection vulnerabilities: None identified. The authorization logic uses CASL's built-in MongoDB query condition building which is safe from injection. All user input is properly validated through the authorization framework.
-
Sensitive user data exposure: None identified. The changes only affect authorization logic, not data retrieval or exposure patterns. The permission system properly restricts access based on group membership and publication status.
-
Insecure API usage: None identified. The CASL library is used correctly with proper ability definitions and condition checks.
-
Authentication bypass: None identified. The refactoring maintains the same security posture:
- Unauthenticated users can only read public datasets
- Authenticated users need proper group membership
- All write operations require appropriate permissions
- Special groups (admin, delete) have explicit checks
Responsible commits: All security-relevant changes are in the core refactoring commits (3901fed8, e59abfcc, 50259abd).
Test Coverage
Current Coverage
- Unit tests:
src/casl/casl-ability.factory.spec.ts- Updated to work with new structure, but only tests factory instantiation and one specific permission - Integration tests: Controller tests updated to use new method names
- New file:
src/casl/abilities/datasets.ability.ts- No dedicated unit tests
Assessment
Adequate for existing functionality: Yes, the integration tests cover the main use cases through the controllers.
Improvement Needed:
- High priority: Add unit tests for
DatasetAbilityclass covering:- Unauthenticated user scenarios
- Authenticated user with various group memberships
- Each permission group (CREATE_DATASET_GROUPS, ADMIN_GROUPS, etc.)
- Edge cases (empty accessGroups config, undefined user, etc.)
- Medium priority: Add tests for the permission matrix to ensure it matches the documentation
Test Suggestions
// Example test structure for DatasetAbility
describe('DatasetAbility', () => {
describe('Unauthenticated user', () => {
it('should allow read on public datasets');
it('should deny write operations');
});
describe('Authenticated user', () => {
it('should allow read on owned datasets');
it('should allow read on access group datasets');
it('should allow read on public datasets');
});
describe('CREATE_DATASET_GROUPS', () => {
it('should allow create with empty pid and matching ownerGroup');
it('should deny create with non-empty pid');
});
// ... tests for other permission groups
});Responsible commits: Test updates in ce1ec06f, 3697e2e5, but no dedicated DatasetAbility tests added.
Security Examples
No relevant vulnerabilities found. The refactoring maintains proper security controls:
-
All dataset read operations require either:
isPublished: truefor unauthenticated- Matching
ownerGrouporaccessGroupsfor authenticated users - Special group membership for elevated access
-
All write operations require:
- Specific permission group membership
- Owner/group matching conditions where applicable
Example of secure pattern from datasets.ability.ts:
// Unauthenticated - only public read access
can(Action.DatasetRead, DatasetClass, ifPublished);
// Authenticated - read access to owned/access-group/public datasets
can(Action.DatasetRead, DatasetClass, ifOwner);
can(Action.DatasetRead, DatasetClass, ifAccess);
can(Action.DatasetRead, DatasetClass, ifPublished);
// CREATE_DATASET_GROUPS - can create only with empty pid and matching owner
can(Action.DatasetCreate, DatasetClass, { ...ifOwner, pid: { $eq: "" } });Testing for Security Use Cases
Test Cases to Verify Security
-
Unauthenticated user access:
- Test: GET /datasets without authentication
- Expected: Only datasets with
isPublished: trueare returned - Code:
datasets.controller.ts:1421-1428(filter building for unauthenticated)
-
Authenticated user without special groups:
- Test: GET /datasets with authenticated user not in any special group
- Expected: Only datasets where user's
currentGroupsmatchownerGrouporaccessGroups, plus public datasets - Code:
datasets.ability.ts:57-71(authenticated read permissions)
-
CREATE_DATASET_GROUPS user creating dataset:
- Test: POST /datasets with user in CREATE_DATASET_GROUPS, providing explicit pid
- Expected: 403 Forbidden (pid must be empty for this group)
- Code:
datasets.ability.ts:84-87(create with pid restriction)
-
CREATE_DATASET_WITH_PID_GROUPS user creating dataset:
- Test: POST /datasets with user in CREATE_DATASET_WITH_PID_GROUPS, providing explicit pid
- Expected: 201 Created (pid allowed for this group)
- Code:
datasets.ability.ts:111(create without pid restriction)
-
DELETE_GROUPS user:
- Test: DELETE /datasets/:id with user in DELETE_GROUPS
- Expected: 200 OK (any dataset can be deleted)
- Code:
datasets.ability.ts:182(unrestricted delete)
-
ADMIN_GROUPS user:
- Test: PUT /datasets/:id with user in ADMIN_GROUPS, dataset not owned by user
- Expected: 200 OK (admin can update any dataset)
- Code:
datasets.ability.ts:155-173(unrestricted access)
-
UPDATE_DATASET_LIFECYCLE_GROUPS user:
- Test: PATCH /datasets/:id/datasetlifecycle with user in UPDATE_DATASET_LIFECYCLE_GROUPS
- Expected: 200 OK for any dataset, but only datasetLifecycle field can be updated
- Code:
datasets.ability.ts:188-200(lifecycle update permissions) - Note: The endpoint itself should enforce field-level restrictions
Verification Commands
# Run existing tests
npm test -- src/casl/casl-ability.factory.spec.ts
# Check for any authorization-related test failures
npm test -- --grep "dataset"
# Manual testing with different user roles
# Use Postman or similar to test each endpoint with various user contextsRelevant code locations:
- Authorization logic:
src/casl/abilities/datasets.ability.ts - Controller authorization checks: All dataset controller files, lines using
caslAbilityFactory.datasetAccess() - Filter building:
src/datasets/datasets.controller.ts:1421-1468and similar in v4 controller
Summary
Key Findings
| Aspect | Status | Details |
|---|---|---|
| Logic Correctness | Pass | Refactoring maintains identical permission semantics |
| Edge Cases | Pass | All user types and permission groups handled |
| Code Quality | Excellent | Significant improvement in maintainability |
| Security | Secure | No vulnerabilities introduced; same security posture maintained |
| Test Coverage | Partial | Integration tests exist, but DatasetAbility lacks unit tests |
| Documentation | Excellent | Comprehensive developer documentation added |
Consolidated Assessment
The refactor-casl-factory-datasets branch introduces a well-executed, security-preserving refactoring of the dataset authorization system. The changes:
- Consolidate 40+ action types into 17 clear, semantic actions
- Centralize dataset authorization logic in a dedicated service
- Document the complete authorization model for developers
- Maintain all existing security controls and permission semantics
- Improve code maintainability and reduce duplication
Primary Risk: None identified. The refactoring is purely structural and does not change the authorization behavior.
Primary Recommendation: Add comprehensive unit tests for the new DatasetAbility class to ensure the permission matrix works as documented.
Overall Verdict: Approve for merge. The changes are beneficial, well-implemented, and maintain security. The only outstanding item is adding unit tests for the new authorization service, which should be done before or shortly after merging.
Generated with Mistral Ai
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. The dataset-specific code is extracted into a separate module
Fixes
Changes:
Tests included
Documentation
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:
Enhancements:
Tests: