Skip to content

Commit

Permalink
merge dev to main (v2.2.2) (#1511)
Browse files Browse the repository at this point in the history
  • Loading branch information
ymc9 authored Jun 14, 2024
2 parents 28eca18 + 484b920 commit a2d3377
Show file tree
Hide file tree
Showing 22 changed files with 236 additions and 149 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "zenstack-monorepo",
"version": "2.2.1",
"version": "2.2.2",
"description": "",
"scripts": {
"build": "pnpm -r build",
Expand Down
2 changes: 1 addition & 1 deletion packages/ide/jetbrains/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ plugins {
}

group = "dev.zenstack"
version = "2.2.1"
version = "2.2.2"

repositories {
mavenCentral()
Expand Down
2 changes: 1 addition & 1 deletion packages/ide/jetbrains/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "jetbrains",
"version": "2.2.1",
"version": "2.2.2",
"displayName": "ZenStack JetBrains IDE Plugin",
"description": "ZenStack JetBrains IDE plugin",
"homepage": "https://zenstack.dev",
Expand Down
2 changes: 1 addition & 1 deletion packages/language/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@zenstackhq/language",
"version": "2.2.1",
"version": "2.2.2",
"displayName": "ZenStack modeling language compiler",
"description": "ZenStack modeling language compiler",
"homepage": "https://zenstack.dev",
Expand Down
2 changes: 1 addition & 1 deletion packages/misc/redwood/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@zenstackhq/redwood",
"displayName": "ZenStack RedwoodJS Integration",
"version": "2.2.1",
"version": "2.2.2",
"description": "CLI and runtime for integrating ZenStack with RedwoodJS projects.",
"repository": {
"type": "git",
Expand Down
2 changes: 1 addition & 1 deletion packages/plugins/openapi/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@zenstackhq/openapi",
"displayName": "ZenStack Plugin and Runtime for OpenAPI",
"version": "2.2.1",
"version": "2.2.2",
"description": "ZenStack plugin and runtime supporting OpenAPI",
"main": "index.js",
"repository": {
Expand Down
2 changes: 1 addition & 1 deletion packages/plugins/swr/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@zenstackhq/swr",
"displayName": "ZenStack plugin for generating SWR hooks",
"version": "2.2.1",
"version": "2.2.2",
"description": "ZenStack plugin for generating SWR hooks",
"main": "index.js",
"repository": {
Expand Down
2 changes: 1 addition & 1 deletion packages/plugins/tanstack-query/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@zenstackhq/tanstack-query",
"displayName": "ZenStack plugin for generating tanstack-query hooks",
"version": "2.2.1",
"version": "2.2.2",
"description": "ZenStack plugin for generating tanstack-query hooks",
"main": "index.js",
"exports": {
Expand Down
2 changes: 1 addition & 1 deletion packages/plugins/trpc/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@zenstackhq/trpc",
"displayName": "ZenStack plugin for tRPC",
"version": "2.2.1",
"version": "2.2.2",
"description": "ZenStack plugin for tRPC",
"main": "index.js",
"repository": {
Expand Down
2 changes: 1 addition & 1 deletion packages/runtime/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@zenstackhq/runtime",
"displayName": "ZenStack Runtime Library",
"version": "2.2.1",
"version": "2.2.2",
"description": "Runtime of ZenStack for both client-side and server-side environments.",
"repository": {
"type": "git",
Expand Down
15 changes: 11 additions & 4 deletions packages/runtime/src/enhancements/policy/policy-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1074,10 +1074,17 @@ export class PolicyUtil extends QueryUtils {
// can then cause infinite recursion when we visit relation later

// recurse into relation fields
for (const [k, v] of Object.entries<any>(args.select ?? args.include ?? {})) {
const field = resolveField(this.modelMeta, model, k);
if (field?.isDataModel && v && typeof v === 'object') {
this.injectReadCheckSelect(field.type, v);
const visitTarget = args.select ?? args.include;
if (visitTarget) {
for (const key of Object.keys(visitTarget)) {
const field = resolveField(this.modelMeta, model, key);
if (field?.isDataModel && visitTarget[key]) {
if (typeof visitTarget[key] !== 'object') {
// v is "true", ensure it's an object
visitTarget[key] = {};
}
this.injectReadCheckSelect(field.type, visitTarget[key]);
}
}
}

Expand Down
2 changes: 1 addition & 1 deletion packages/schema/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"publisher": "zenstack",
"displayName": "ZenStack Language Tools",
"description": "Build scalable web apps with minimum code by defining authorization and validation rules inside the data schema that closer to the database",
"version": "2.2.1",
"version": "2.2.2",
"author": {
"name": "ZenStack Team"
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
DataModelAttribute,
Expression,
ExpressionType,
isArrayExpr,
isDataModel,
isDataModelAttribute,
isDataModelField,
Expand Down Expand Up @@ -82,6 +83,8 @@ export default class ExpressionValidator implements AstValidator<Expression> {
node: expr.right,
});
}

this.validateCrossModelFieldComparison(expr, accept);
break;
}

Expand Down Expand Up @@ -137,6 +140,7 @@ export default class ExpressionValidator implements AstValidator<Expression> {
accept('error', 'incompatible operand types', { node: expr });
}

this.validateCrossModelFieldComparison(expr, accept);
break;
}

Expand All @@ -158,43 +162,8 @@ export default class ExpressionValidator implements AstValidator<Expression> {
break;
}

// not supported:
// - foo.a == bar
// - foo.user.id == userId
// except:
// - future().userId == userId
if (
(isMemberAccessExpr(expr.left) &&
isDataModelField(expr.left.member.ref) &&
expr.left.member.ref.$container != getContainingDataModel(expr)) ||
(isMemberAccessExpr(expr.right) &&
isDataModelField(expr.right.member.ref) &&
expr.right.member.ref.$container != getContainingDataModel(expr))
) {
// foo.user.id == auth().id
// foo.user.id == "123"
// foo.user.id == null
// foo.user.id == EnumValue
if (!(this.isNotModelFieldExpr(expr.left) || this.isNotModelFieldExpr(expr.right))) {
const containingPolicyAttr = findUpAst(
expr,
(node) => isDataModelAttribute(node) && ['@@allow', '@@deny'].includes(node.decl.$refText)
) as DataModelAttribute | undefined;

if (containingPolicyAttr) {
const operation = getAttributeArgLiteral<string>(containingPolicyAttr, 'operation');
if (operation?.split(',').includes('all') || operation?.split(',').includes('read')) {
accept(
'error',
'comparison between fields of different models is not supported in model-level "read" rules',
{
node: expr,
}
);
break;
}
}
}
if (!this.validateCrossModelFieldComparison(expr, accept)) {
break;
}

if (
Expand Down Expand Up @@ -262,6 +231,49 @@ export default class ExpressionValidator implements AstValidator<Expression> {
}
}

private validateCrossModelFieldComparison(expr: BinaryExpr, accept: ValidationAcceptor) {
// not supported in "read" rules:
// - foo.a == bar
// - foo.user.id == userId
// except:
// - future().userId == userId
if (
(isMemberAccessExpr(expr.left) &&
isDataModelField(expr.left.member.ref) &&
expr.left.member.ref.$container != getContainingDataModel(expr)) ||
(isMemberAccessExpr(expr.right) &&
isDataModelField(expr.right.member.ref) &&
expr.right.member.ref.$container != getContainingDataModel(expr))
) {
// foo.user.id == auth().id
// foo.user.id == "123"
// foo.user.id == null
// foo.user.id == EnumValue
if (!(this.isNotModelFieldExpr(expr.left) || this.isNotModelFieldExpr(expr.right))) {
const containingPolicyAttr = findUpAst(
expr,
(node) => isDataModelAttribute(node) && ['@@allow', '@@deny'].includes(node.decl.$refText)
) as DataModelAttribute | undefined;

if (containingPolicyAttr) {
const operation = getAttributeArgLiteral<string>(containingPolicyAttr, 'operation');
if (operation?.split(',').includes('all') || operation?.split(',').includes('read')) {
accept(
'error',
'comparison between fields of different models is not supported in model-level "read" rules',
{
node: expr,
}
);
return false;
}
}
}
}

return true;
}

private validateCollectionPredicate(expr: BinaryExpr, accept: ValidationAcceptor) {
if (!expr.$resolvedType) {
accept('error', 'collection predicate can only be used on an array of model type', { node: expr });
Expand All @@ -273,9 +285,18 @@ export default class ExpressionValidator implements AstValidator<Expression> {
return findUpAst(node, (n) => isDataModelAttribute(n) && n.decl.$refText === '@@validate');
}

private isNotModelFieldExpr(expr: Expression) {
private isNotModelFieldExpr(expr: Expression): boolean {
return (
isLiteralExpr(expr) || isEnumFieldReference(expr) || isNullExpr(expr) || this.isAuthOrAuthMemberAccess(expr)
// literal
isLiteralExpr(expr) ||
// enum field
isEnumFieldReference(expr) ||
// null
isNullExpr(expr) ||
// `auth()` access
this.isAuthOrAuthMemberAccess(expr) ||
// array
(isArrayExpr(expr) && expr.items.every((item) => this.isNotModelFieldExpr(item)))
);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -241,15 +241,15 @@ export class PolicyGenerator {
? '!(' +
denies
.map((deny) => {
return transformer.transform(deny);
return transformer.transform(deny, false);
})
.join(' || ') +
')'
: undefined;

const allowStmt = allows
.map((allow) => {
return transformer.transform(allow);
return transformer.transform(allow, false);
})
.join(' || ');

Expand Down Expand Up @@ -607,79 +607,6 @@ export class PolicyGenerator {
writer.writeLine(',');
}

private generateFieldReadCheckerFunction(
sourceFile: SourceFile,
field: DataModelField,
allows: Expression[],
denies: Expression[]
) {
const statements: (string | WriterFunction)[] = [];

generateNormalizedAuthRef(field.$container as DataModel, allows, denies, statements);

// compile rules down to typescript expressions
statements.push((writer) => {
const transformer = new TypeScriptExpressionTransformer({
context: ExpressionContext.AccessPolicy,
fieldReferenceContext: 'input',
});

const denyStmt =
denies.length > 0
? '!(' +
denies
.map((deny) => {
return transformer.transform(deny);
})
.join(' || ') +
')'
: undefined;

const allowStmt =
allows.length > 0
? '(' +
allows
.map((allow) => {
return transformer.transform(allow);
})
.join(' || ') +
')'
: undefined;

let expr: string | undefined;

if (denyStmt && allowStmt) {
expr = `${denyStmt} && ${allowStmt}`;
} else if (denyStmt) {
expr = denyStmt;
} else if (allowStmt) {
expr = allowStmt;
} else {
throw new Error('should not happen');
}

writer.write('return ' + expr);
});

const func = sourceFile.addFunction({
name: `${field.$container.name}$${field.name}_read`,
returnType: 'boolean',
parameters: [
{
name: 'input',
type: 'any',
},
{
name: 'context',
type: 'QueryContext',
},
],
statements,
});

return func;
}

// #endregion

//#region Auth selector
Expand Down
Loading

0 comments on commit a2d3377

Please sign in to comment.