zenstackhq / zenstack

Fullstack TypeScript toolkit that enhances Prisma ORM with flexible Authorization layer for RBAC/ABAC/PBAC/ReBAC, offering auto-generated type-safe APIs and frontend hooks.
https://zenstack.dev
MIT License
2.07k stars 89 forks source link

[Feature Request] Stop certain access control rules not to be applied while connecting/disconnecting relationship #1471

Open Milan-Bhl opened 4 months ago

Milan-Bhl commented 4 months ago

Is your feature request related to a problem? Please describe. I have zmodels structured like this:

model User {
    ...
    roles              Role[]

    modifiedById String? @default(auth().id)

    modifiedBy         User?        @relation("modifiedBy", fields: [modifiedById], references: [id])
    modifiedUsers      User[]       @relation("modifiedBy")

    @@deny('update', future().modifiedById != auth().id)
}

model Role {
    name String
    description String
    users       User[]
    permissions Permission[]
    modifiedById String? @default(auth().id)
    modifiedBy  User?        @relation("ModifiedRole", fields: [modifiedById], references: [id])
    @@deny('update', future().modifiedById != auth().id)
}

model Permission {
    name String
    description String
    roles       Role[]
    modifiedById String? @default(auth().id)
    modifiedBy  User?   @relation("ModifiedPermission", fields: [modifiedById], references: [id])
    @@deny('update', future().modifiedById != auth().id)
}

This works fine for normal scenarios of editing/updating/creating roles or permissions or users. But this causes us issues when we are just trying to associate Permissions (connect/disconnect) to a Role OR connecting/disconnecting roles from users.

The issue here is that UPDATE is rejected with error of ACCESS_POLICY_VIOLATION for e.g. while trying to assign a role to a user with

const { mutateAsync: addRoles } = useUpdateUser({
    onSuccess: async () => {
      ...
    },
  });

addRoles({
      where: {
        id: '......',
      },
      data: {
        modifiedById: '......',
        roles: {
          connect: newRoles,
        },
      },
    });

will fail with error

{
    "error": {
        "message": "denied by policy: Role entities failed 'postUpdate' check, entity {\"id\":\"......................................\"} failed policy check",
        "reason": "ACCESS_POLICY_VIOLATION",
        "rejectedByPolicy": true,
        "prisma": true,
        "code": "P2004"
    }
}

The problem is being caused by post-update check on modifiedById on both sides of the relationship.

Describe the solution you'd like Stop the post update checks when the modification is just connecting/disconnecting and not actual update of the model

Describe alternatives you've considered ...

Additional context ...

ymc9 commented 4 months ago

Hi @mbhele-xrdigital , thanks for bringing this up.

The current design of policy behavior is explained here: https://zenstack.dev/docs/the-complete-guide/part1/access-policy/model-level#evaluation-of-model-level-policies.

Basically, for relation connect/disconnect, the side that gets the foreign key field update is required to be "updatable". However, implicit many-to-many is a special case because there's an implied "join table" that connects both sides. Currently, ZenStack is conservative and requires both sides to be updatable.

There was a suggestion for utilizing the field-level policies to fine-tune this behavior: https://github.com/zenstackhq/zenstack/issues/856

It hasn't been implemented yet.