Skip to content

fix: should not reject "update" when there's only field-level override but no model-level policy #1052

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Feb 24, 2024

Conversation

ymc9
Copy link
Member

@ymc9 ymc9 commented Feb 24, 2024

Fixes #1014

Summary by CodeRabbit

  • New Features
    • Added field-level override policy guards to enhance security measures.
  • Refactor
    • Simplified the method signature in policy utilities for better clarity and maintainability.
  • Tests
    • Introduced integration tests for update and read operations, ensuring robustness and reliability in user and post models.

Copy link
Contributor

coderabbitai bot commented Feb 24, 2024

Walkthrough

Walkthrough

The changes introduce enhancements to policy utilities by simplifying method signatures and adding new functionality to support field-level override guards for access control. Additionally, regression tests have been added to ensure the correct behavior of update and read operations in relation to access policies, specifically addressing the issue where field-level access policies did not override default deny behavior.

Changes

Files Change Summary
.../policy/policy-utils.ts Simplified hasAuthGuard method, added hasOverrideAuthGuard, and updated tryReject logic.
tests/integration/tests/regression/issue-1014.test.ts New tests for validating update and read operations with field-level access policies.

Assessment against linked issues

Objective Addressed Explanation
Override default deny behavior with field-level access policy (#1014)

The changes directly address the primary objective of issue #1014 by introducing logic to consider field-level override guards before denying access, which was the core problem described. The addition of hasOverrideAuthGuard method and the adjustments in tryReject method are specifically aimed at resolving the access policy violation error when attempting to update fields with specific access policies defined. The regression tests added in issue-1014.test.ts serve to validate that the updates and read operations behave as expected when field-level policies are in place, ensuring that the default deny behavior can indeed be overridden by field-level access policies.

Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

Share

Tips

Chat

There are 3 ways to chat with CodeRabbit:

Note: Auto-reply has been disabled for this repository by the repository owner. The CodeRabbit bot will not respond to your comments unless it is explicitly tagged.

  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit tests for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai generate interesting stats about this repository from git and render them as a table.
    • @coderabbitai show all the console.log statements in this repository.
    • @coderabbitai read src/utils.ts and generate unit tests.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (invoked as PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger a review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai help to get help.

Additionally, you can add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.

CodeRabbit Configration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • The JSON schema for the configuration file is available here.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/coderabbit-overrides.v2.json

CodeRabbit Discord Community

Join our Discord Community to get help, request features, and share feedback.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Review Status

Actionable comments generated: 2

Configuration used: CodeRabbit UI

Commits Files that changed from the base of the PR and between 9db52db and 8ad3cdb.
Files ignored due to path filters (1)
  • tests/integration/tests/tsconfig.template.json is excluded by: !**/*.json
Files selected for processing (2)
  • packages/runtime/src/enhancements/policy/policy-utils.ts (4 hunks)
  • tests/integration/tests/regression/issue-1014.test.ts (1 hunks)
Additional comments: 3
packages/runtime/src/enhancements/policy/policy-utils.ts (3)
  • 322-322: The simplification of the hasAuthGuard method's signature is mentioned, but the diff does not show a change in the signature, only the method's implementation. It's important to ensure that the method's usage throughout the codebase is consistent with this change.
Verification successful

The usages of hasAuthGuard across the codebase are consistent with the method's signature provided in the snippet. The simplification mentioned in the review comment likely refers to internal changes within the method that do not affect its external usage. Based on the evidence, the method's usage aligns with the new implementation.

Scripts Executed

The following scripts were executed for the analysis:


Script:

#!/bin/bash
# Search for usages of hasAuthGuard to ensure they align with the new implementation.
rg "hasAuthGuard" --type ts

Length of output: 610

* 334-344: The addition of the `hasOverrideAuthGuard` method is a significant improvement for checking field-level override policy guards. This method enhances the flexibility and granularity of access control by allowing field-level policies to override model-level policies for specific operations. It's well-implemented, following the established coding conventions and logic structure seen in the rest of the file. * 749-749: The update to the `tryReject` method to consider field-level override guards before denying access is crucial for the intended functionality of allowing field-level policies to override default behaviors. This change ensures that even if a model-level policy would deny access, field-level overrides can still allow it, which aligns with the PR's objectives. However, it's essential to ensure that this logic does not inadvertently allow access that should be denied based on other security considerations.
Verification successful

To further verify the integration of field-level override guards and ensure they do not conflict with other security mechanisms, we need to examine the implementation details of the tryReject method and the context of its calls. Let's proceed with viewing the relevant sections of the code.


The provided context from the shell scripts includes the full implementation of the PolicyUtil class in policy-utils.ts and various handler implementations in handler.ts. The PolicyUtil class contains the tryReject method, which is crucial for enforcing access policies based on model-level and field-level guards. This method checks if a model-level policy would deny access and then checks for field-level override guards that might allow access despite the model-level denial. This aligns with the original review comment, which emphasizes the importance of considering field-level override guards before denying access to ensure that field-level policies can override default behaviors when necessary.

The tryReject method implementation confirms that it performs the necessary checks for model-level denial (isFalse(guard)) and then checks for field-level override guards (!this.hasOverrideAuthGuard(model, operation)). This logic ensures that access is not denied if there are field-level overrides that allow it, which is consistent with the PR's objectives to allow field-level policies to override default behaviors.

Given the detailed implementation provided in the policy-utils.ts file and the usage of the tryReject method across various operations in handler.ts, it's clear that the integration of field-level override guards has been carefully considered and implemented to not conflict with other security mechanisms. The logic in tryReject specifically addresses the concern raised in the review comment by ensuring that field-level overrides are evaluated before denying access based on model-level policies.

Based on the provided context and the analysis of the tryReject method and its usage, the review comment is verified as accurate. The implementation aligns with the PR's objectives and the comment's emphasis on the importance of field-level override guards in the access control logic.

Scripts Executed

The following scripts were executed for the analysis:


Script:

#!/bin/bash
# Verify that the integration of field-level override guards does not conflict with other security mechanisms.
rg "tryReject" --type ts

Length of output: 1117



Script:

#!/bin/bash
# Examine the implementation of tryReject in policy-utils.ts
fd policy-utils.ts --exec cat {}

# Review the context of tryReject calls in handler.ts
fd handler.ts --exec cat {}

Length of output: 117605

Comment on lines +3 to +31
describe('issue 1014', () => {
it('update', async () => {
const { prisma, enhance } = await loadSchema(
`
model User {
id Int @id() @default(autoincrement())
name String
posts Post[]
}

model Post {
id Int @id() @default(autoincrement())
title String
content String?
author User? @relation(fields: [authorId], references: [id])
authorId Int? @allow('update', true, true)

@@allow('read', true)
}
`,
{ logPrismaQuery: true }
);

const db = enhance();

const user = await prisma.user.create({ data: { name: 'User1' } });
const post = await prisma.post.create({ data: { title: 'Post1' } });
await expect(db.post.update({ where: { id: post.id }, data: { authorId: user.id } })).toResolveTruthy();
});
Copy link
Contributor

Choose a reason for hiding this comment

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

The test case for update operations correctly sets up a schema with User and Post models, creates a user and a post, and then attempts to update the authorId of the post to simulate the issue. However, there are a few points to consider:

  • The loadSchema function is used with logPrismaQuery: true, which is great for debugging but might clutter test output in CI environments. Consider making logging configurable based on the environment.
  • The expect statement at line 30 uses toResolveTruthy(), which is not a standard Jest matcher. Ensure that a custom matcher or extension is properly configured to handle this assertion.
  • It's good practice to clean up created data after tests to avoid side effects. Consider adding an afterEach block to delete created posts and users.
- await expect(db.post.update({ where: { id: post.id }, data: { authorId: user.id } })).toResolveTruthy();
+ await expect(db.post.update({ where: { id: post.id }, data: { authorId: user.id } })).resolves.not.toThrow();

Consider adding cleanup logic and review the custom matcher or extension used for toResolveTruthy().


Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation.

Suggested change
describe('issue 1014', () => {
it('update', async () => {
const { prisma, enhance } = await loadSchema(
`
model User {
id Int @id() @default(autoincrement())
name String
posts Post[]
}
model Post {
id Int @id() @default(autoincrement())
title String
content String?
author User? @relation(fields: [authorId], references: [id])
authorId Int? @allow('update', true, true)
@@allow('read', true)
}
`,
{ logPrismaQuery: true }
);
const db = enhance();
const user = await prisma.user.create({ data: { name: 'User1' } });
const post = await prisma.post.create({ data: { title: 'Post1' } });
await expect(db.post.update({ where: { id: post.id }, data: { authorId: user.id } })).toResolveTruthy();
});
describe('issue 1014', () => {
it('update', async () => {
const { prisma, enhance } = await loadSchema(
`
model User {
id Int @id() @default(autoincrement())
name String
posts Post[]
}
model Post {
id Int @id() @default(autoincrement())
title String
content String?
author User? @relation(fields: [authorId], references: [id])
authorId Int? @allow('update', true, true)
@@allow('read', true)
}
`,
{ logPrismaQuery: true }
);
const db = enhance();
const user = await prisma.user.create({ data: { name: 'User1' } });
const post = await prisma.post.create({ data: { title: 'Post1' } });
await expect(db.post.update({ where: { id: post.id }, data: { authorId: user.id } })).resolves.not.toThrow();
});

Comment on lines +33 to +52
it('read', async () => {
const { prisma, enhance } = await loadSchema(
`
model Post {
id Int @id() @default(autoincrement())
title String @allow('read', true, true)
content String
}
`,
{ logPrismaQuery: true }
);

const db = enhance();

const post = await prisma.post.create({ data: { title: 'Post1', content: 'Content' } });
await expect(db.post.findUnique({ where: { id: post.id } })).toResolveNull();
await expect(db.post.findUnique({ where: { id: post.id }, select: { title: true } })).resolves.toEqual({
title: 'Post1',
});
});
Copy link
Contributor

Choose a reason for hiding this comment

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

The test case for read operations is well-structured, focusing on the ability to read fields with specific access policies. Points to consider:

  • The schema definition for the Post model at lines 36-40 includes an @allow directive on the title field, which aligns with the test's objective. However, the content field does not have an explicit access policy, which might be intentional but could also be an oversight. If testing access policies on multiple fields is desired, consider adding relevant policies.
  • The use of await expect(...).toResolveNull(); at line 48 suggests expecting a null result, which is not a standard Jest matcher. Ensure that a custom matcher or extension is properly configured for this assertion.
  • The test validates the access policy on the title field but does not explicitly test the default deny behavior for fields without an @allow directive. Consider adding an assertion to verify that accessing the content field without explicit permission results in the expected behavior.
- await expect(db.post.findUnique({ where: { id: post.id } })).toResolveNull();
+ await expect(db.post.findUnique({ where: { id: post.id } })).resolves.toBeNull();

Review the custom matcher or extension used for toResolveNull() and consider adding more assertions to cover different access policy scenarios.


Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation.

Suggested change
it('read', async () => {
const { prisma, enhance } = await loadSchema(
`
model Post {
id Int @id() @default(autoincrement())
title String @allow('read', true, true)
content String
}
`,
{ logPrismaQuery: true }
);
const db = enhance();
const post = await prisma.post.create({ data: { title: 'Post1', content: 'Content' } });
await expect(db.post.findUnique({ where: { id: post.id } })).toResolveNull();
await expect(db.post.findUnique({ where: { id: post.id }, select: { title: true } })).resolves.toEqual({
title: 'Post1',
});
});
it('read', async () => {
const { prisma, enhance } = await loadSchema(
`
model Post {
id Int @id() @default(autoincrement())
title String @allow('read', true, true)
content String
}
`,
{ logPrismaQuery: true }
);
const db = enhance();
const post = await prisma.post.create({ data: { title: 'Post1', content: 'Content' } });
await expect(db.post.findUnique({ where: { id: post.id } })).resolves.toBeNull();
await expect(db.post.findUnique({ where: { id: post.id }, select: { title: true } })).resolves.toEqual({
title: 'Post1',
});
});

@ymc9 ymc9 merged commit 912c831 into dev Feb 24, 2024
@ymc9 ymc9 deleted the fix/issue-1014 branch February 24, 2024 08:34
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