@nocobase/flow-engine 2.1.30 → 2.1.32

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/acl/Acl.d.ts CHANGED
@@ -5,7 +5,7 @@ interface CheckOptions {
5
5
  actionName: string;
6
6
  fields?: string[];
7
7
  recordPkValue?: string | number;
8
- allowedActions: any[];
8
+ allowedActions?: Record<string, Array<string | number>>;
9
9
  }
10
10
  export declare class ACL {
11
11
  private flowEngine;
@@ -26,6 +26,7 @@ export declare class ACL {
26
26
  verifyScope: (actionName: string, recordPkValue: any, allowedActions: any) => boolean;
27
27
  parseAction(options: CheckOptions): any;
28
28
  parseField(options: CheckOptions): boolean;
29
+ can(options: CheckOptions): boolean;
29
30
  aclCheck(options: CheckOptions): Promise<boolean>;
30
31
  }
31
32
  export {};
package/lib/acl/Acl.js CHANGED
@@ -154,6 +154,34 @@ const _ACL = class _ACL {
154
154
  const allowed = whitelist.includes(fields[0]);
155
155
  return allowed;
156
156
  }
157
+ can(options) {
158
+ var _a;
159
+ const { allowAll } = this.data;
160
+ if (allowAll) {
161
+ return true;
162
+ }
163
+ const { actionName, allowedActions, recordPkValue } = options;
164
+ const hasRecordPkValue = recordPkValue !== void 0 && recordPkValue !== null;
165
+ const recordPermission = hasRecordPkValue && allowedActions ? this.verifyScope(actionName, recordPkValue, allowedActions) : null;
166
+ if (hasRecordPkValue && allowedActions && recordPermission !== true) {
167
+ return false;
168
+ }
169
+ const params = this.parseAction(options);
170
+ if (!params) {
171
+ return false;
172
+ }
173
+ if (!import_lodash.default.isEmpty(params.filter) && recordPermission !== true) {
174
+ return false;
175
+ }
176
+ if (!((_a = options.fields) == null ? void 0 : _a.length)) {
177
+ return true;
178
+ }
179
+ const allowedFields = [].concat(params.whitelist || []).concat(params.fields || []).concat(params.appends || []);
180
+ if (!allowedFields.length) {
181
+ return true;
182
+ }
183
+ return options.fields.every((field) => allowedFields.includes(field));
184
+ }
157
185
  async aclCheck(options) {
158
186
  const { allowAll } = this.data;
159
187
  if (allowAll) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nocobase/flow-engine",
3
- "version": "2.1.30",
3
+ "version": "2.1.32",
4
4
  "private": false,
5
5
  "description": "A standalone flow engine for NocoBase, managing workflows, models, and actions.",
6
6
  "main": "lib/index.js",
@@ -8,8 +8,8 @@
8
8
  "dependencies": {
9
9
  "@formily/antd-v5": "1.x",
10
10
  "@formily/reactive": "2.x",
11
- "@nocobase/sdk": "2.1.30",
12
- "@nocobase/shared": "2.1.30",
11
+ "@nocobase/sdk": "2.1.32",
12
+ "@nocobase/shared": "2.1.32",
13
13
  "ahooks": "^3.7.2",
14
14
  "axios": "^1.7.0",
15
15
  "dayjs": "^1.11.9",
@@ -37,5 +37,5 @@
37
37
  ],
38
38
  "author": "NocoBase Team",
39
39
  "license": "Apache-2.0",
40
- "gitHead": "c1a4bff0fd3c8b30e06fc6f50ad33e054b04061a"
40
+ "gitHead": "737a1324f650d7bc66594203b68eb0180553a551"
41
41
  }
package/src/acl/Acl.tsx CHANGED
@@ -16,7 +16,7 @@ interface CheckOptions {
16
16
  actionName: string;
17
17
  fields?: string[];
18
18
  recordPkValue?: string | number;
19
- allowedActions: any[];
19
+ allowedActions?: Record<string, Array<string | number>>;
20
20
  }
21
21
 
22
22
  export class ACL {
@@ -149,6 +149,41 @@ export class ACL {
149
149
  return allowed;
150
150
  }
151
151
 
152
+ can(options: CheckOptions): boolean {
153
+ const { allowAll } = this.data;
154
+ if (allowAll) {
155
+ return true;
156
+ }
157
+
158
+ const { actionName, allowedActions, recordPkValue } = options;
159
+ const hasRecordPkValue = recordPkValue !== undefined && recordPkValue !== null;
160
+ const recordPermission =
161
+ hasRecordPkValue && allowedActions ? this.verifyScope(actionName, recordPkValue, allowedActions) : null;
162
+ if (hasRecordPkValue && allowedActions && recordPermission !== true) {
163
+ return false;
164
+ }
165
+
166
+ const params = this.parseAction(options);
167
+ if (!params) {
168
+ return false;
169
+ }
170
+ if (!_.isEmpty(params.filter) && recordPermission !== true) {
171
+ return false;
172
+ }
173
+ if (!options.fields?.length) {
174
+ return true;
175
+ }
176
+
177
+ const allowedFields: string[] = []
178
+ .concat(params.whitelist || [])
179
+ .concat(params.fields || [])
180
+ .concat(params.appends || []);
181
+ if (!allowedFields.length) {
182
+ return true;
183
+ }
184
+ return options.fields.every((field) => allowedFields.includes(field));
185
+ }
186
+
152
187
  async aclCheck(options: CheckOptions): Promise<boolean> {
153
188
  // await this.load();
154
189
  const { allowAll } = this.data;
@@ -71,6 +71,76 @@ describe('ACL', () => {
71
71
  expect(notOk).toBe(false);
72
72
  });
73
73
 
74
+ it('checks record update scope before field permission', () => {
75
+ const payload = {
76
+ data: {
77
+ allowAll: false,
78
+ actionAlias: {},
79
+ resources: ['posts'],
80
+ actions: { 'posts:update': { whitelist: ['title'] } },
81
+ strategy: { actions: [] },
82
+ },
83
+ };
84
+ const engine = makeEngine(payload);
85
+ const acl = new ACL(engine);
86
+ acl.setData(payload.data);
87
+
88
+ const options = {
89
+ dataSourceKey: 'main',
90
+ resourceName: 'posts',
91
+ actionName: 'update',
92
+ allowedActions: {
93
+ update: [1],
94
+ },
95
+ };
96
+
97
+ expect(acl.can({ ...options, recordPkValue: 1, fields: ['title'] })).toBe(true);
98
+ expect(acl.can({ ...options, recordPkValue: 2, fields: ['title'] })).toBe(false);
99
+ expect(acl.can({ ...options, recordPkValue: 1, fields: ['body'] })).toBe(false);
100
+ expect(acl.can({ ...options, recordPkValue: 0, fields: ['title'] })).toBe(false);
101
+ expect(
102
+ acl.can({
103
+ dataSourceKey: 'main',
104
+ resourceName: 'posts',
105
+ actionName: 'update',
106
+ fields: ['title'],
107
+ }),
108
+ ).toBe(true);
109
+ });
110
+
111
+ it('allows every field when a scoped action has no field restriction', () => {
112
+ const payload = {
113
+ data: {
114
+ allowAll: false,
115
+ actionAlias: {},
116
+ resources: ['posts'],
117
+ actions: {
118
+ 'posts:update': {
119
+ filter: { createdById: '{{ ctx.state.currentUser.id }}' },
120
+ },
121
+ },
122
+ strategy: { actions: [] },
123
+ },
124
+ };
125
+ const engine = makeEngine(payload);
126
+ const acl = new ACL(engine);
127
+ acl.setData(payload.data);
128
+
129
+ const options = {
130
+ dataSourceKey: 'main',
131
+ resourceName: 'posts',
132
+ actionName: 'update',
133
+ allowedActions: {
134
+ update: [1],
135
+ },
136
+ fields: ['title'],
137
+ };
138
+
139
+ expect(acl.can({ ...options, recordPkValue: 1 })).toBe(true);
140
+ expect(acl.can({ ...options, recordPkValue: 2 })).toBe(false);
141
+ expect(acl.can({ ...options, allowedActions: undefined, recordPkValue: undefined })).toBe(false);
142
+ });
143
+
74
144
  it('reloads permissions when auth token changes', async () => {
75
145
  const payload1 = {
76
146
  data: {