Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions packages/schema/src/plugins/enhancer/policy/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -483,7 +483,9 @@ function hasCrossModelComparison(expr: Expression) {
}

function getSourceModelOfFieldAccess(expr: Expression) {
if (isDataModel(expr.$resolvedType?.decl)) {
// an expression that resolves to a data model and is part of a member access, return the model
// e.g.: profile.age => Profile
if (isDataModel(expr.$resolvedType?.decl) && isMemberAccessExpr(expr.$container)) {
return expr.$resolvedType?.decl;
}

Expand All @@ -497,7 +499,7 @@ function getSourceModelOfFieldAccess(expr: Expression) {
return getContainerOfType(expr, isDataModel);
}

// direct field reference
// direct field reference, return the model
if (isDataModelFieldReference(expr)) {
return (expr.target.ref as DataModelField).$container;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -769,8 +769,8 @@ describe('Cross-model field comparison', () => {
await expect(db.user.update({ where: { id: 1 }, data: { age: 25 } })).toResolveTruthy();
});

it('with auth', async () => {
const { prisma, enhance } = await loadSchema(
it('with auth case 1', async () => {
const { enhance } = await loadSchema(
`
model User {
id Int @id @default(autoincrement())
Expand Down Expand Up @@ -803,8 +803,7 @@ describe('Cross-model field comparison', () => {
level Int
@@allow('all', true)
}
`,
{ preserveTsFiles: true }
`
);

await expect(enhance().post.create({ data: { title: 'P1' } })).toBeRejectedByPolicy();
Expand All @@ -820,4 +819,180 @@ describe('Cross-model field comparison', () => {
})
).toResolveTruthy();
});

it('with auth case 2', async () => {
const { prisma, enhance } = await loadSchema(
`
model User {
id Int @id @default(autoincrement())
teamMembership TeamMembership[]
@@allow('all', true)
}

model Team {
id Int @id @default(autoincrement())
permissions Permission[]
assets Asset[]
@@allow('all', true)
}

model Asset {
id Int @id @default(autoincrement())
name String
team Team @relation(fields: [teamId], references: [id])
teamId Int
@@allow('all', auth().teamMembership?[role.permissions?[name == 'ManageTeam' && teamId == this.teamId]])
@@allow('read', true)
}

model TeamMembership {
id Int @id @default(autoincrement())
role TeamRole?
user User @relation(fields: [userId], references: [id])
userId Int
@@allow('all', true)
}

model TeamRole {
id Int @id @default(autoincrement())
permissions Permission[]
membership TeamMembership @relation(fields: [membershipId], references: [id])
membershipId Int @unique
@@allow('all', true)
}

model Permission {
id Int @id @default(autoincrement())
name String
team Team @relation(fields: [teamId], references: [id])
teamId Int
role TeamRole @relation(fields: [roleId], references: [id])
roleId Int
@@allow('all', true)
}
`,
{ preserveTsFiles: true, logPrismaQuery: true }
);

const team1 = await prisma.team.create({ data: {} });
const team2 = await prisma.team.create({ data: {} });

const user = await prisma.user.create({
data: {
teamMembership: {
create: {
role: {
create: {
permissions: { create: [{ name: 'ManageTeam', team: { connect: { id: team1.id } } }] },
},
},
},
},
},
});

const asset = await prisma.asset.create({
data: { name: 'Asset1', team: { connect: { id: team1.id } } },
});

const dbTeam1 = enhance({
id: user.id,
teamMembership: [{ role: { permissions: [{ name: 'ManageTeam', teamId: team1.id }] } }],
});
expect(dbTeam1.asset.update({ where: { id: asset.id }, data: { name: 'Asset2' } })).toResolveTruthy();

const dbTeam2 = enhance({
id: user.id,
teamMembership: [{ role: { permissions: [{ name: 'ManageTeam', teamId: team2.id }] } }],
});
expect(dbTeam2.asset.update({ where: { id: asset.id }, data: { name: 'Asset2' } })).toBeRejectedByPolicy();
});

it('with auth case 3', async () => {
const { prisma, enhance } = await loadSchema(
`
model User {
id Int @id @default(autoincrement())
teamMembership TeamMembership[]
@@allow('all', true)
}

model Team {
id Int @id @default(autoincrement())
permissions Permission[]
assets Asset[]
@@allow('all', true)
}

model Asset {
id Int @id @default(autoincrement())
name String
team Team @relation(fields: [teamId], references: [id])
teamId Int
@@allow('all', auth().teamMembership?[role.permissions?[name == 'ManageTeam' && team == this.team]])
@@allow('read', true)
}

model TeamMembership {
id Int @id @default(autoincrement())
role TeamRole?
user User @relation(fields: [userId], references: [id])
userId Int
@@allow('all', true)
}

model TeamRole {
id Int @id @default(autoincrement())
permissions Permission[]
membership TeamMembership @relation(fields: [membershipId], references: [id])
membershipId Int @unique
@@allow('all', true)
}

model Permission {
id Int @id @default(autoincrement())
name String
team Team @relation(fields: [teamId], references: [id])
teamId Int
role TeamRole @relation(fields: [roleId], references: [id])
roleId Int
@@allow('all', true)
}
`,
{ preserveTsFiles: true, logPrismaQuery: true }
);

const team1 = await prisma.team.create({ data: {} });
const team2 = await prisma.team.create({ data: {} });

const user = await prisma.user.create({
data: {
teamMembership: {
create: {
role: {
create: {
permissions: { create: [{ name: 'ManageTeam', team: { connect: { id: team1.id } } }] },
},
},
},
},
},
});

const asset = await prisma.asset.create({
data: { name: 'Asset1', team: { connect: { id: team1.id } } },
});

const dbTeam1 = enhance({
id: user.id,
teamMembership: [{ role: { permissions: [{ name: 'ManageTeam', teamId: team1.id }] } }],
});
expect(dbTeam1.asset.update({ where: { id: asset.id }, data: { name: 'Asset2' } })).toResolveTruthy();

const dbTeam2 = enhance({
id: user.id,
teamMembership: [{ role: { permissions: [{ name: 'ManageTeam', teamId: team2.id }] } }],
});
expect(dbTeam2.asset.update({ where: { id: asset.id }, data: { name: 'Asset2' } })).toBeRejectedByPolicy();
});
});