Skip to content

Polymorphic sub model enhancements ignoring #1710

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

Closed
svetch opened this issue Sep 18, 2024 · 3 comments
Closed

Polymorphic sub model enhancements ignoring #1710

svetch opened this issue Sep 18, 2024 · 3 comments
Assignees
Milestone

Comments

@svetch
Copy link
Contributor

svetch commented Sep 18, 2024

Description and expected behavior
example model:

model Profile {
  id          String   @id @default(cuid())
  createdAt   DateTime @default(now())
  updatedAt   DateTime @updatedAt

  displayName String

  ...

  type        String

  @@delegate(type)
  @@allow('read', true)
}

enum Role {
  ADMIN
  USER
}

model User extends Profile {
  ...
  email    String    @unique @deny('read', true) OR @allow('read', false, true)
  password String    @omit
  role     Role      @default(USER) @deny('read', true)
  token    String?   @omit
}

model Organization extends Profile {...}

Now if I get first user without zenstack context, then its works correctly.

const DB = getEnhancedPrisma(undefined, {
    DATABASE_URL: ...
});

const result = await DB.user.findFirst({
    where: {
        id: 'USER_ID',
    },
});

// result = {
...
displayName: ...
type: ...
...
}

BUT for the profile queries the sub models access policies don't validate, and also includes the omitted fields of submodel.

const DB = getEnhancedPrisma(undefined, {
    DATABASE_URL: ...
});

const result = await DB.profile.findFirst({
    where: {
        id: 'USER_ID',
    },
});

// result = {
...
displayName: ...
password: ...
token: ...
...
};

And also you can update the sub model fields without any permission check.

const DB = getEnhancedPrisma({
    id: 'RANDOM_ID',
}, {
    DATABASE_URL: ...
});

const res = await DB.profile.update({
    where: {
        id: 'user_2frIS4TQmzPgsfL2qCaiee4oyL0',
    },
    data: {
        delegate_aux_user: {
            update: {
              locale: 'test-EN',
            },
        },
    },
});
// result = {
...
locale: "test-EN"
...
};

Possible solution
I figured out that the issue occurs because the model metadata does not include the delegate relational fields. As a result, all enhancements that should be applied to these fields are ignored, and permissions for both reading and writing are automatically granted.

To resolve this problem, the generator CLI plugin needs to be updated to ensure that the delegate relational fields are included in the model metadata, not just in the Prisma schema.

Environment (please complete the following information):

  • ZenStack version: 2.5.1
  • Prisma version: 5.19.1
  • Database type: Postgresql
@ymc9
Copy link
Member

ymc9 commented Sep 19, 2024

Hi @svetch , many thanks for reporting this issue!

You're right, when reading a polymorphic entity via a base model, @omit and field-level policies on the concrete models are not effective. I'm making a fix for it.

As for the update scenario, it's actually not a supported scenario to directly manipulate the delegate_aux fields. An mutation to a concrete model's field should always be done via a concrete model. I'll add some validation logic to throw errors when the aux fields are passed.

@svetch
Copy link
Contributor Author

svetch commented Sep 20, 2024

Thank you for the quick fix, and sorry for my English as well as the mix-up in terminology/naming.

I realized I forgot to mention one more thing. My solution actually resolves another issue because the model-level policies have the same problem.

For example, if you add this line for the user model:

@@deny('read', auth().id != id)

It should exclude all user properties from the profile response when you dont have permission.

But Prisma doesn't apply the model-level policy query:
image

Before I used polymorphism in ZenStack, I handled this manually with the same relationships, and it worked correctly.

Now I can only reproduce the correct query like this:

import policyObj from '../../../node_modules/.zenstack/policy';
const res = await DB.profile.findFirst({
    where: {
        id: 'user_2frIS4TQmzPgsfL2qCaiee4oyL0',
    },
    include: {
        delegate_aux_user: {
            where: {
                ...(policyObj.policy['user'].modelLevel.read.guard as PolicyFunc)(
                    {
                       ...
                       //auth context
                    },
                    {} as Record<string, DbOperations>,
                ),
            },
        },
    },
});

Let me know if I'm wrong about this.

Thank you in advance for your response!

@ymc9
Copy link
Member

ymc9 commented Sep 23, 2024

Fixed in 2.6.0

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants