Skip to content

Commit 55be22f

Browse files
committed
fix: additional fixes and tests related to cross-model field comparison
1 parent ed5133c commit 55be22f

File tree

2 files changed

+183
-6
lines changed

2 files changed

+183
-6
lines changed

packages/schema/src/plugins/enhancer/policy/utils.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -483,7 +483,9 @@ function hasCrossModelComparison(expr: Expression) {
483483
}
484484

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

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

500-
// direct field reference
502+
// direct field reference, return the model
501503
if (isDataModelFieldReference(expr)) {
502504
return (expr.target.ref as DataModelField).$container;
503505
}

tests/integration/tests/enhancements/with-policy/cross-model-field-comparison.test.ts

Lines changed: 179 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -769,8 +769,8 @@ describe('Cross-model field comparison', () => {
769769
await expect(db.user.update({ where: { id: 1 }, data: { age: 25 } })).toResolveTruthy();
770770
});
771771

772-
it('with auth', async () => {
773-
const { prisma, enhance } = await loadSchema(
772+
it('with auth case 1', async () => {
773+
const { enhance } = await loadSchema(
774774
`
775775
model User {
776776
id Int @id @default(autoincrement())
@@ -803,8 +803,7 @@ describe('Cross-model field comparison', () => {
803803
level Int
804804
@@allow('all', true)
805805
}
806-
`,
807-
{ preserveTsFiles: true }
806+
`
808807
);
809808

810809
await expect(enhance().post.create({ data: { title: 'P1' } })).toBeRejectedByPolicy();
@@ -820,4 +819,180 @@ describe('Cross-model field comparison', () => {
820819
})
821820
).toResolveTruthy();
822821
});
822+
823+
it('with auth case 2', async () => {
824+
const { prisma, enhance } = await loadSchema(
825+
`
826+
model User {
827+
id Int @id @default(autoincrement())
828+
teamMembership TeamMembership[]
829+
@@allow('all', true)
830+
}
831+
832+
model Team {
833+
id Int @id @default(autoincrement())
834+
permissions Permission[]
835+
assets Asset[]
836+
@@allow('all', true)
837+
}
838+
839+
model Asset {
840+
id Int @id @default(autoincrement())
841+
name String
842+
team Team @relation(fields: [teamId], references: [id])
843+
teamId Int
844+
@@allow('all', auth().teamMembership?[role.permissions?[name == 'ManageTeam' && teamId == this.teamId]])
845+
@@allow('read', true)
846+
}
847+
848+
model TeamMembership {
849+
id Int @id @default(autoincrement())
850+
role TeamRole?
851+
user User @relation(fields: [userId], references: [id])
852+
userId Int
853+
@@allow('all', true)
854+
}
855+
856+
model TeamRole {
857+
id Int @id @default(autoincrement())
858+
permissions Permission[]
859+
membership TeamMembership @relation(fields: [membershipId], references: [id])
860+
membershipId Int @unique
861+
@@allow('all', true)
862+
}
863+
864+
model Permission {
865+
id Int @id @default(autoincrement())
866+
name String
867+
team Team @relation(fields: [teamId], references: [id])
868+
teamId Int
869+
role TeamRole @relation(fields: [roleId], references: [id])
870+
roleId Int
871+
@@allow('all', true)
872+
}
873+
`,
874+
{ preserveTsFiles: true, logPrismaQuery: true }
875+
);
876+
877+
const team1 = await prisma.team.create({ data: {} });
878+
const team2 = await prisma.team.create({ data: {} });
879+
880+
const user = await prisma.user.create({
881+
data: {
882+
teamMembership: {
883+
create: {
884+
role: {
885+
create: {
886+
permissions: { create: [{ name: 'ManageTeam', team: { connect: { id: team1.id } } }] },
887+
},
888+
},
889+
},
890+
},
891+
},
892+
});
893+
894+
const asset = await prisma.asset.create({
895+
data: { name: 'Asset1', team: { connect: { id: team1.id } } },
896+
});
897+
898+
const dbTeam1 = enhance({
899+
id: user.id,
900+
teamMembership: [{ role: { permissions: [{ name: 'ManageTeam', teamId: team1.id }] } }],
901+
});
902+
expect(dbTeam1.asset.update({ where: { id: asset.id }, data: { name: 'Asset2' } })).toResolveTruthy();
903+
904+
const dbTeam2 = enhance({
905+
id: user.id,
906+
teamMembership: [{ role: { permissions: [{ name: 'ManageTeam', teamId: team2.id }] } }],
907+
});
908+
expect(dbTeam2.asset.update({ where: { id: asset.id }, data: { name: 'Asset2' } })).toBeRejectedByPolicy();
909+
});
910+
911+
it('with auth case 3', async () => {
912+
const { prisma, enhance } = await loadSchema(
913+
`
914+
model User {
915+
id Int @id @default(autoincrement())
916+
teamMembership TeamMembership[]
917+
@@allow('all', true)
918+
}
919+
920+
model Team {
921+
id Int @id @default(autoincrement())
922+
permissions Permission[]
923+
assets Asset[]
924+
@@allow('all', true)
925+
}
926+
927+
model Asset {
928+
id Int @id @default(autoincrement())
929+
name String
930+
team Team @relation(fields: [teamId], references: [id])
931+
teamId Int
932+
@@allow('all', auth().teamMembership?[role.permissions?[name == 'ManageTeam' && team == this.team]])
933+
@@allow('read', true)
934+
}
935+
936+
model TeamMembership {
937+
id Int @id @default(autoincrement())
938+
role TeamRole?
939+
user User @relation(fields: [userId], references: [id])
940+
userId Int
941+
@@allow('all', true)
942+
}
943+
944+
model TeamRole {
945+
id Int @id @default(autoincrement())
946+
permissions Permission[]
947+
membership TeamMembership @relation(fields: [membershipId], references: [id])
948+
membershipId Int @unique
949+
@@allow('all', true)
950+
}
951+
952+
model Permission {
953+
id Int @id @default(autoincrement())
954+
name String
955+
team Team @relation(fields: [teamId], references: [id])
956+
teamId Int
957+
role TeamRole @relation(fields: [roleId], references: [id])
958+
roleId Int
959+
@@allow('all', true)
960+
}
961+
`,
962+
{ preserveTsFiles: true, logPrismaQuery: true }
963+
);
964+
965+
const team1 = await prisma.team.create({ data: {} });
966+
const team2 = await prisma.team.create({ data: {} });
967+
968+
const user = await prisma.user.create({
969+
data: {
970+
teamMembership: {
971+
create: {
972+
role: {
973+
create: {
974+
permissions: { create: [{ name: 'ManageTeam', team: { connect: { id: team1.id } } }] },
975+
},
976+
},
977+
},
978+
},
979+
},
980+
});
981+
982+
const asset = await prisma.asset.create({
983+
data: { name: 'Asset1', team: { connect: { id: team1.id } } },
984+
});
985+
986+
const dbTeam1 = enhance({
987+
id: user.id,
988+
teamMembership: [{ role: { permissions: [{ name: 'ManageTeam', teamId: team1.id }] } }],
989+
});
990+
expect(dbTeam1.asset.update({ where: { id: asset.id }, data: { name: 'Asset2' } })).toResolveTruthy();
991+
992+
const dbTeam2 = enhance({
993+
id: user.id,
994+
teamMembership: [{ role: { permissions: [{ name: 'ManageTeam', teamId: team2.id }] } }],
995+
});
996+
expect(dbTeam2.asset.update({ where: { id: asset.id }, data: { name: 'Asset2' } })).toBeRejectedByPolicy();
997+
});
823998
});

0 commit comments

Comments
 (0)