@nocobase/plugin-acl 2.1.0-alpha.20 → 2.1.0-alpha.22
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/dist/externalVersion.js +10 -10
- package/dist/server/actions/apply-data-permissions.d.ts +10 -0
- package/dist/server/actions/apply-data-permissions.js +208 -0
- package/dist/server/actions/data-source-compat.d.ts +13 -0
- package/dist/server/actions/data-source-compat.js +189 -0
- package/dist/server/server.js +16 -0
- package/dist/swagger/index.d.ts +149 -0
- package/dist/swagger/index.js +139 -4
- package/package.json +2 -2
package/dist/externalVersion.js
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
module.exports = {
|
|
11
|
-
"@nocobase/client": "2.1.0-alpha.
|
|
11
|
+
"@nocobase/client": "2.1.0-alpha.22",
|
|
12
12
|
"antd": "5.24.2",
|
|
13
13
|
"react": "18.2.0",
|
|
14
14
|
"react-i18next": "11.18.6",
|
|
@@ -17,16 +17,16 @@ module.exports = {
|
|
|
17
17
|
"@formily/react": "2.3.7",
|
|
18
18
|
"@ant-design/icons": "5.6.1",
|
|
19
19
|
"lodash": "4.18.1",
|
|
20
|
-
"@nocobase/utils": "2.1.0-alpha.
|
|
21
|
-
"@nocobase/client-v2": "2.1.0-alpha.
|
|
22
|
-
"@nocobase/actions": "2.1.0-alpha.
|
|
23
|
-
"@nocobase/cache": "2.1.0-alpha.
|
|
24
|
-
"@nocobase/database": "2.1.0-alpha.
|
|
25
|
-
"@nocobase/server": "2.1.0-alpha.
|
|
26
|
-
"@nocobase/acl": "2.1.0-alpha.
|
|
27
|
-
"@nocobase/test": "2.1.0-alpha.
|
|
20
|
+
"@nocobase/utils": "2.1.0-alpha.22",
|
|
21
|
+
"@nocobase/client-v2": "2.1.0-alpha.22",
|
|
22
|
+
"@nocobase/actions": "2.1.0-alpha.22",
|
|
23
|
+
"@nocobase/cache": "2.1.0-alpha.22",
|
|
24
|
+
"@nocobase/database": "2.1.0-alpha.22",
|
|
25
|
+
"@nocobase/server": "2.1.0-alpha.22",
|
|
26
|
+
"@nocobase/acl": "2.1.0-alpha.22",
|
|
27
|
+
"@nocobase/test": "2.1.0-alpha.22",
|
|
28
28
|
"@formily/core": "2.3.7",
|
|
29
29
|
"@formily/antd-v5": "1.2.3",
|
|
30
30
|
"antd-style": "3.7.1",
|
|
31
|
-
"@nocobase/flow-engine": "2.1.0-alpha.
|
|
31
|
+
"@nocobase/flow-engine": "2.1.0-alpha.22"
|
|
32
32
|
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This file is part of the NocoBase (R) project.
|
|
3
|
+
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
|
4
|
+
* Authors: NocoBase Team.
|
|
5
|
+
*
|
|
6
|
+
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
|
7
|
+
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
|
+
*/
|
|
9
|
+
import type { Context, Next } from '@nocobase/actions';
|
|
10
|
+
export declare function applyDataPermissions(ctx: Context, next: Next): Promise<void>;
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This file is part of the NocoBase (R) project.
|
|
3
|
+
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
|
4
|
+
* Authors: NocoBase Team.
|
|
5
|
+
*
|
|
6
|
+
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
|
7
|
+
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
var __defProp = Object.defineProperty;
|
|
11
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
12
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
13
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
14
|
+
var __export = (target, all) => {
|
|
15
|
+
for (var name in all)
|
|
16
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
17
|
+
};
|
|
18
|
+
var __copyProps = (to, from, except, desc) => {
|
|
19
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
20
|
+
for (let key of __getOwnPropNames(from))
|
|
21
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
22
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
23
|
+
}
|
|
24
|
+
return to;
|
|
25
|
+
};
|
|
26
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
27
|
+
var apply_data_permissions_exports = {};
|
|
28
|
+
__export(apply_data_permissions_exports, {
|
|
29
|
+
applyDataPermissions: () => applyDataPermissions
|
|
30
|
+
});
|
|
31
|
+
module.exports = __toCommonJS(apply_data_permissions_exports);
|
|
32
|
+
function ensureString(value, fieldName) {
|
|
33
|
+
if (typeof value !== "string" || !value.trim()) {
|
|
34
|
+
throw new Error(`Invalid ${fieldName}`);
|
|
35
|
+
}
|
|
36
|
+
return value.trim();
|
|
37
|
+
}
|
|
38
|
+
function normalizeFields(fields) {
|
|
39
|
+
if (!Array.isArray(fields)) {
|
|
40
|
+
return void 0;
|
|
41
|
+
}
|
|
42
|
+
const normalized = [
|
|
43
|
+
...new Set(fields.filter((field) => typeof field === "string" && !!field.trim()))
|
|
44
|
+
];
|
|
45
|
+
return normalized.length ? normalized : [];
|
|
46
|
+
}
|
|
47
|
+
function normalizeScopeId(scopeId) {
|
|
48
|
+
if (scopeId === null) {
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
if (scopeId === void 0) {
|
|
52
|
+
return void 0;
|
|
53
|
+
}
|
|
54
|
+
const parsed = Number(scopeId);
|
|
55
|
+
if (!Number.isInteger(parsed) || parsed <= 0) {
|
|
56
|
+
throw new Error(`Invalid scopeId: ${scopeId}`);
|
|
57
|
+
}
|
|
58
|
+
return parsed;
|
|
59
|
+
}
|
|
60
|
+
async function resolveScopeIdByKey(options) {
|
|
61
|
+
const scope = await options.ctx.db.getRepository("dataSourcesRolesResourcesScopes").findOne({
|
|
62
|
+
filter: {
|
|
63
|
+
dataSourceKey: options.dataSourceKey,
|
|
64
|
+
key: options.scopeKey
|
|
65
|
+
},
|
|
66
|
+
transaction: options.transaction
|
|
67
|
+
});
|
|
68
|
+
if (!scope) {
|
|
69
|
+
throw new Error(`Scope key "${options.scopeKey}" not found in data source "${options.dataSourceKey}"`);
|
|
70
|
+
}
|
|
71
|
+
return scope.get("id");
|
|
72
|
+
}
|
|
73
|
+
async function normalizeAction(options) {
|
|
74
|
+
const name = ensureString(options.action.name, "action.name");
|
|
75
|
+
let scopeId = normalizeScopeId(options.action.scopeId);
|
|
76
|
+
if (scopeId === void 0 && options.action.scope && typeof options.action.scope.id === "number") {
|
|
77
|
+
scopeId = normalizeScopeId(options.action.scope.id);
|
|
78
|
+
}
|
|
79
|
+
let scopeKey;
|
|
80
|
+
if (typeof options.action.scopeKey === "string" && options.action.scopeKey.trim()) {
|
|
81
|
+
scopeKey = options.action.scopeKey.trim();
|
|
82
|
+
} else if (options.action.scope && typeof options.action.scope.key === "string" && options.action.scope.key.trim()) {
|
|
83
|
+
scopeKey = options.action.scope.key.trim();
|
|
84
|
+
}
|
|
85
|
+
if (scopeId === void 0 && scopeKey) {
|
|
86
|
+
scopeId = await resolveScopeIdByKey({
|
|
87
|
+
ctx: options.ctx,
|
|
88
|
+
dataSourceKey: options.dataSourceKey,
|
|
89
|
+
scopeKey,
|
|
90
|
+
transaction: options.transaction
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
if (scopeId === void 0) {
|
|
94
|
+
scopeId = null;
|
|
95
|
+
}
|
|
96
|
+
const normalized = {
|
|
97
|
+
name,
|
|
98
|
+
fields: normalizeFields(options.action.fields),
|
|
99
|
+
scopeId
|
|
100
|
+
};
|
|
101
|
+
const output = {
|
|
102
|
+
name: normalized.name
|
|
103
|
+
};
|
|
104
|
+
if (normalized.fields !== void 0) {
|
|
105
|
+
output.fields = normalized.fields;
|
|
106
|
+
}
|
|
107
|
+
if (normalized.scopeId !== void 0) {
|
|
108
|
+
output.scopeId = normalized.scopeId;
|
|
109
|
+
}
|
|
110
|
+
return output;
|
|
111
|
+
}
|
|
112
|
+
async function applyDataPermissions(ctx, next) {
|
|
113
|
+
const roleName = ensureString(ctx.action.params.filterByTk, "filterByTk");
|
|
114
|
+
const values = ctx.action.params.values || {};
|
|
115
|
+
const dataSourceKey = ensureString(values.dataSourceKey || "main", "dataSourceKey");
|
|
116
|
+
const resources = values.resources;
|
|
117
|
+
if (!Array.isArray(resources) || resources.length === 0) {
|
|
118
|
+
throw new Error("`resources` must be a non-empty array");
|
|
119
|
+
}
|
|
120
|
+
const role = await ctx.db.getRepository("roles").findOne({
|
|
121
|
+
filterByTk: roleName
|
|
122
|
+
});
|
|
123
|
+
if (!role) {
|
|
124
|
+
throw new Error(`Role "${roleName}" not found`);
|
|
125
|
+
}
|
|
126
|
+
const roleResourceRepository = ctx.db.getRepository("roles.resources", roleName);
|
|
127
|
+
const appliedResources = [];
|
|
128
|
+
await ctx.db.sequelize.transaction(async (transaction) => {
|
|
129
|
+
for (const resourceInput of resources) {
|
|
130
|
+
const resourceName = ensureString(resourceInput == null ? void 0 : resourceInput.name, "resource.name");
|
|
131
|
+
const usingActionsConfig = (resourceInput == null ? void 0 : resourceInput.usingActionsConfig) ?? true;
|
|
132
|
+
const actionsInput = Array.isArray(resourceInput == null ? void 0 : resourceInput.actions) ? resourceInput.actions : [];
|
|
133
|
+
const normalizedActions = [];
|
|
134
|
+
for (const action of actionsInput) {
|
|
135
|
+
normalizedActions.push(
|
|
136
|
+
await normalizeAction({
|
|
137
|
+
ctx,
|
|
138
|
+
dataSourceKey,
|
|
139
|
+
action,
|
|
140
|
+
transaction
|
|
141
|
+
})
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
const existingResource = await roleResourceRepository.findOne({
|
|
145
|
+
filter: {
|
|
146
|
+
name: resourceName,
|
|
147
|
+
dataSourceKey
|
|
148
|
+
},
|
|
149
|
+
appends: ["actions"],
|
|
150
|
+
transaction
|
|
151
|
+
});
|
|
152
|
+
const writeValues = {
|
|
153
|
+
name: resourceName,
|
|
154
|
+
dataSourceKey,
|
|
155
|
+
usingActionsConfig,
|
|
156
|
+
actions: normalizedActions
|
|
157
|
+
};
|
|
158
|
+
let savedResource;
|
|
159
|
+
if (existingResource) {
|
|
160
|
+
await roleResourceRepository.update({
|
|
161
|
+
filterByTk: existingResource.get("id"),
|
|
162
|
+
values: writeValues,
|
|
163
|
+
updateAssociationValues: ["actions"],
|
|
164
|
+
transaction
|
|
165
|
+
});
|
|
166
|
+
savedResource = await roleResourceRepository.findOne({
|
|
167
|
+
filterByTk: existingResource.get("id"),
|
|
168
|
+
appends: ["actions"],
|
|
169
|
+
transaction
|
|
170
|
+
});
|
|
171
|
+
} else {
|
|
172
|
+
const created = await roleResourceRepository.create({
|
|
173
|
+
values: writeValues,
|
|
174
|
+
transaction
|
|
175
|
+
});
|
|
176
|
+
savedResource = await roleResourceRepository.findOne({
|
|
177
|
+
filterByTk: created.get("id"),
|
|
178
|
+
appends: ["actions"],
|
|
179
|
+
transaction
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
const actionModels = savedResource.get("actions") || [];
|
|
183
|
+
appliedResources.push({
|
|
184
|
+
id: savedResource.get("id"),
|
|
185
|
+
name: savedResource.get("name"),
|
|
186
|
+
dataSourceKey: savedResource.get("dataSourceKey"),
|
|
187
|
+
usingActionsConfig: !!savedResource.get("usingActionsConfig"),
|
|
188
|
+
actions: actionModels.map((actionModel) => ({
|
|
189
|
+
id: actionModel.get("id"),
|
|
190
|
+
name: actionModel.get("name"),
|
|
191
|
+
fields: actionModel.get("fields") || [],
|
|
192
|
+
scopeId: actionModel.get("scopeId") ?? null
|
|
193
|
+
}))
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
});
|
|
197
|
+
ctx.body = {
|
|
198
|
+
roleName,
|
|
199
|
+
dataSourceKey,
|
|
200
|
+
resources: appliedResources,
|
|
201
|
+
count: appliedResources.length
|
|
202
|
+
};
|
|
203
|
+
await next();
|
|
204
|
+
}
|
|
205
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
206
|
+
0 && (module.exports = {
|
|
207
|
+
applyDataPermissions
|
|
208
|
+
});
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This file is part of the NocoBase (R) project.
|
|
3
|
+
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
|
4
|
+
* Authors: NocoBase Team.
|
|
5
|
+
*
|
|
6
|
+
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
|
7
|
+
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
|
+
*/
|
|
9
|
+
import type { Context, Next } from '@nocobase/actions';
|
|
10
|
+
export declare function guardRolesDataSourcesCollectionsList(ctx: Context, next: Next): Promise<void>;
|
|
11
|
+
export declare function guardRolesDataSourceResourcesCreate(ctx: Context, next: Next): Promise<void>;
|
|
12
|
+
export declare function guardRolesDataSourceResourcesGet(ctx: Context, next: Next): Promise<void>;
|
|
13
|
+
export declare function guardRolesDataSourceResourcesUpdate(ctx: Context, next: Next): Promise<void>;
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This file is part of the NocoBase (R) project.
|
|
3
|
+
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
|
4
|
+
* Authors: NocoBase Team.
|
|
5
|
+
*
|
|
6
|
+
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
|
7
|
+
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
var __create = Object.create;
|
|
11
|
+
var __defProp = Object.defineProperty;
|
|
12
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
13
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
14
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
15
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
16
|
+
var __export = (target, all) => {
|
|
17
|
+
for (var name in all)
|
|
18
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
19
|
+
};
|
|
20
|
+
var __copyProps = (to, from, except, desc) => {
|
|
21
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
22
|
+
for (let key of __getOwnPropNames(from))
|
|
23
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
24
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
25
|
+
}
|
|
26
|
+
return to;
|
|
27
|
+
};
|
|
28
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
29
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
30
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
31
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
32
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
33
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
34
|
+
mod
|
|
35
|
+
));
|
|
36
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
37
|
+
var data_source_compat_exports = {};
|
|
38
|
+
__export(data_source_compat_exports, {
|
|
39
|
+
guardRolesDataSourceResourcesCreate: () => guardRolesDataSourceResourcesCreate,
|
|
40
|
+
guardRolesDataSourceResourcesGet: () => guardRolesDataSourceResourcesGet,
|
|
41
|
+
guardRolesDataSourceResourcesUpdate: () => guardRolesDataSourceResourcesUpdate,
|
|
42
|
+
guardRolesDataSourcesCollectionsList: () => guardRolesDataSourcesCollectionsList
|
|
43
|
+
});
|
|
44
|
+
module.exports = __toCommonJS(data_source_compat_exports);
|
|
45
|
+
var import_lodash = __toESM(require("lodash"));
|
|
46
|
+
function normalizeString(value) {
|
|
47
|
+
if (typeof value !== "string") {
|
|
48
|
+
return void 0;
|
|
49
|
+
}
|
|
50
|
+
const trimmed = value.trim();
|
|
51
|
+
return trimmed ? trimmed : void 0;
|
|
52
|
+
}
|
|
53
|
+
function normalizeFilter(input) {
|
|
54
|
+
if (!import_lodash.default.isPlainObject(input)) {
|
|
55
|
+
return {};
|
|
56
|
+
}
|
|
57
|
+
return { ...input };
|
|
58
|
+
}
|
|
59
|
+
function applyLocatorFromQuery(params, filter) {
|
|
60
|
+
const dataSourceKeyFromQuery = normalizeString(params.dataSourceKey);
|
|
61
|
+
if (dataSourceKeyFromQuery) {
|
|
62
|
+
filter.dataSourceKey = filter.dataSourceKey || dataSourceKeyFromQuery;
|
|
63
|
+
}
|
|
64
|
+
const nameFromQuery = normalizeString(params.name);
|
|
65
|
+
if (nameFromQuery) {
|
|
66
|
+
filter.name = filter.name || nameFromQuery;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
function normalizeNumericTk(value) {
|
|
70
|
+
if (typeof value === "number" && Number.isInteger(value) && value >= 0) {
|
|
71
|
+
return value;
|
|
72
|
+
}
|
|
73
|
+
const normalized = normalizeString(value);
|
|
74
|
+
if (!normalized || !/^\d+$/.test(normalized)) {
|
|
75
|
+
return void 0;
|
|
76
|
+
}
|
|
77
|
+
return normalized;
|
|
78
|
+
}
|
|
79
|
+
function deriveNameFromPrefixedTk(value) {
|
|
80
|
+
const normalized = normalizeString(value);
|
|
81
|
+
if (!normalized) {
|
|
82
|
+
return void 0;
|
|
83
|
+
}
|
|
84
|
+
const matched = normalized.match(/^[a-zA-Z]+_(.+)$/);
|
|
85
|
+
if (!matched) {
|
|
86
|
+
return void 0;
|
|
87
|
+
}
|
|
88
|
+
return normalizeString(matched[1]);
|
|
89
|
+
}
|
|
90
|
+
async function resolveLocatorFromFilterByTk(ctx, roleName, filter) {
|
|
91
|
+
const rawFilterByTk = ctx.action.params.filterByTk;
|
|
92
|
+
if (rawFilterByTk === void 0 || rawFilterByTk === null || rawFilterByTk === "") {
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
const numericFilterByTk = normalizeNumericTk(rawFilterByTk);
|
|
96
|
+
if (numericFilterByTk === void 0) {
|
|
97
|
+
if (!normalizeString(filter.name)) {
|
|
98
|
+
const derivedName = deriveNameFromPrefixedTk(rawFilterByTk);
|
|
99
|
+
if (derivedName) {
|
|
100
|
+
filter.name = derivedName;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
const resource = await ctx.db.getRepository("dataSourcesRolesResources").findOne({
|
|
106
|
+
filterByTk: numericFilterByTk
|
|
107
|
+
});
|
|
108
|
+
if (!resource) {
|
|
109
|
+
ctx.throw(404, `Resource permission not found by filterByTk "${rawFilterByTk}"`);
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
const targetRoleName = resource.get("roleName");
|
|
113
|
+
if (targetRoleName !== roleName) {
|
|
114
|
+
ctx.throw(400, `Resource permission "${rawFilterByTk}" does not belong to role "${roleName}"`);
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
if (!normalizeString(filter.dataSourceKey)) {
|
|
118
|
+
filter.dataSourceKey = resource.get("dataSourceKey");
|
|
119
|
+
}
|
|
120
|
+
if (!normalizeString(filter.name)) {
|
|
121
|
+
filter.name = resource.get("name");
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
async function normalizeRoleDataSourceResourceLocator(ctx) {
|
|
125
|
+
const roleName = normalizeString(ctx.action.params.associatedIndex);
|
|
126
|
+
if (!roleName) {
|
|
127
|
+
ctx.throw(400, "Role name is required");
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
const filter = normalizeFilter(ctx.action.params.filter);
|
|
131
|
+
applyLocatorFromQuery(ctx.action.params, filter);
|
|
132
|
+
await resolveLocatorFromFilterByTk(ctx, roleName, filter);
|
|
133
|
+
const dataSourceKey = normalizeString(filter.dataSourceKey);
|
|
134
|
+
const name = normalizeString(filter.name);
|
|
135
|
+
if (!dataSourceKey || !name) {
|
|
136
|
+
ctx.throw(
|
|
137
|
+
400,
|
|
138
|
+
"Missing resource locator: provide --filter-by-tk, or both --data-source-key and --name (or filter.{dataSourceKey,name})"
|
|
139
|
+
);
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
ctx.action.params.filter = {
|
|
143
|
+
...filter,
|
|
144
|
+
dataSourceKey,
|
|
145
|
+
name
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
async function guardRolesDataSourcesCollectionsList(ctx, next) {
|
|
149
|
+
const filter = normalizeFilter(ctx.action.params.filter);
|
|
150
|
+
applyLocatorFromQuery(ctx.action.params, filter);
|
|
151
|
+
const dataSourceKey = normalizeString(filter.dataSourceKey);
|
|
152
|
+
if (!dataSourceKey) {
|
|
153
|
+
ctx.throw(400, "dataSourceKey is required: pass --data-source-key or filter.dataSourceKey");
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
ctx.action.params.filter = {
|
|
157
|
+
...filter,
|
|
158
|
+
dataSourceKey
|
|
159
|
+
};
|
|
160
|
+
await next();
|
|
161
|
+
}
|
|
162
|
+
async function guardRolesDataSourceResourcesCreate(ctx, next) {
|
|
163
|
+
const values = normalizeFilter(ctx.action.params.values);
|
|
164
|
+
const dataSourceKeyFromQuery = normalizeString(ctx.action.params.dataSourceKey);
|
|
165
|
+
if (!values.dataSourceKey && dataSourceKeyFromQuery) {
|
|
166
|
+
values.dataSourceKey = dataSourceKeyFromQuery;
|
|
167
|
+
}
|
|
168
|
+
if (!normalizeString(values.dataSourceKey)) {
|
|
169
|
+
ctx.throw(400, "dataSourceKey is required for roles.dataSourceResources:create");
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
ctx.action.params.values = values;
|
|
173
|
+
await next();
|
|
174
|
+
}
|
|
175
|
+
async function guardRolesDataSourceResourcesGet(ctx, next) {
|
|
176
|
+
await normalizeRoleDataSourceResourceLocator(ctx);
|
|
177
|
+
await next();
|
|
178
|
+
}
|
|
179
|
+
async function guardRolesDataSourceResourcesUpdate(ctx, next) {
|
|
180
|
+
await normalizeRoleDataSourceResourceLocator(ctx);
|
|
181
|
+
await next();
|
|
182
|
+
}
|
|
183
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
184
|
+
0 && (module.exports = {
|
|
185
|
+
guardRolesDataSourceResourcesCreate,
|
|
186
|
+
guardRolesDataSourceResourcesGet,
|
|
187
|
+
guardRolesDataSourceResourcesUpdate,
|
|
188
|
+
guardRolesDataSourcesCollectionsList
|
|
189
|
+
});
|
package/dist/server/server.js
CHANGED
|
@@ -46,6 +46,8 @@ var import_server = require("@nocobase/server");
|
|
|
46
46
|
var import_lodash = __toESM(require("lodash"));
|
|
47
47
|
var import_path = require("path");
|
|
48
48
|
var import_available_actions = require("./actions/available-actions");
|
|
49
|
+
var import_apply_data_permissions = require("./actions/apply-data-permissions");
|
|
50
|
+
var import_data_source_compat = require("./actions/data-source-compat");
|
|
49
51
|
var import_role_check = require("./actions/role-check");
|
|
50
52
|
var import_role_collections = require("./actions/role-collections");
|
|
51
53
|
var import_user_setDefaultRole = require("./actions/user-setDefaultRole");
|
|
@@ -181,7 +183,21 @@ class PluginACLServer extends import_server.Plugin {
|
|
|
181
183
|
this.app.resourcer.define(import_available_actions.availableActionResource);
|
|
182
184
|
this.app.resourcer.define(import_role_collections.roleCollectionsResource);
|
|
183
185
|
this.app.resourcer.registerActionHandler("roles:setSystemRoleMode", import_union_role.setSystemRoleMode);
|
|
186
|
+
this.app.resourcer.registerActionHandler("roles:applyDataPermissions", import_apply_data_permissions.applyDataPermissions);
|
|
184
187
|
this.app.resourcer.registerActionHandler("roles:check", import_role_check.checkAction);
|
|
188
|
+
this.app.resourcer.registerPreActionHandler(
|
|
189
|
+
"roles.dataSourcesCollections:list",
|
|
190
|
+
import_data_source_compat.guardRolesDataSourcesCollectionsList
|
|
191
|
+
);
|
|
192
|
+
this.app.resourcer.registerPreActionHandler(
|
|
193
|
+
"roles.dataSourceResources:create",
|
|
194
|
+
import_data_source_compat.guardRolesDataSourceResourcesCreate
|
|
195
|
+
);
|
|
196
|
+
this.app.resourcer.registerPreActionHandler("roles.dataSourceResources:get", import_data_source_compat.guardRolesDataSourceResourcesGet);
|
|
197
|
+
this.app.resourcer.registerPreActionHandler(
|
|
198
|
+
"roles.dataSourceResources:update",
|
|
199
|
+
import_data_source_compat.guardRolesDataSourceResourcesUpdate
|
|
200
|
+
);
|
|
185
201
|
this.app.resourcer.registerActionHandler(`users:setDefaultRole`, import_user_setDefaultRole.setDefaultRole);
|
|
186
202
|
this.db.on("users.afterCreateWithAssociations", async (model, options) => {
|
|
187
203
|
const { transaction } = options;
|
package/dist/swagger/index.d.ts
CHANGED
|
@@ -198,6 +198,38 @@ declare const _default: {
|
|
|
198
198
|
};
|
|
199
199
|
};
|
|
200
200
|
};
|
|
201
|
+
'/roles:applyDataPermissions': {
|
|
202
|
+
post: {
|
|
203
|
+
tags: string[];
|
|
204
|
+
summary: string;
|
|
205
|
+
description: string;
|
|
206
|
+
parameters: {
|
|
207
|
+
$ref: string;
|
|
208
|
+
}[];
|
|
209
|
+
requestBody: {
|
|
210
|
+
required: boolean;
|
|
211
|
+
content: {
|
|
212
|
+
'application/json': {
|
|
213
|
+
schema: {
|
|
214
|
+
$ref: string;
|
|
215
|
+
};
|
|
216
|
+
};
|
|
217
|
+
};
|
|
218
|
+
};
|
|
219
|
+
responses: {
|
|
220
|
+
200: {
|
|
221
|
+
description: string;
|
|
222
|
+
content: {
|
|
223
|
+
'application/json': {
|
|
224
|
+
schema: {
|
|
225
|
+
$ref: string;
|
|
226
|
+
};
|
|
227
|
+
};
|
|
228
|
+
};
|
|
229
|
+
};
|
|
230
|
+
};
|
|
231
|
+
};
|
|
232
|
+
};
|
|
201
233
|
'/roles/{roleName}/users:list': {
|
|
202
234
|
get: {
|
|
203
235
|
tags: string[];
|
|
@@ -744,6 +776,14 @@ declare const _default: {
|
|
|
744
776
|
type: string;
|
|
745
777
|
};
|
|
746
778
|
};
|
|
779
|
+
DataSourceKeyQuery: {
|
|
780
|
+
name: string;
|
|
781
|
+
in: string;
|
|
782
|
+
description: string;
|
|
783
|
+
schema: {
|
|
784
|
+
type: string;
|
|
785
|
+
};
|
|
786
|
+
};
|
|
747
787
|
MembershipUserFilterByTkQuery: {
|
|
748
788
|
name: string;
|
|
749
789
|
in: string;
|
|
@@ -789,6 +829,14 @@ declare const _default: {
|
|
|
789
829
|
type: string;
|
|
790
830
|
};
|
|
791
831
|
};
|
|
832
|
+
DataSourceResourceNameQuery: {
|
|
833
|
+
name: string;
|
|
834
|
+
in: string;
|
|
835
|
+
description: string;
|
|
836
|
+
schema: {
|
|
837
|
+
type: string;
|
|
838
|
+
};
|
|
839
|
+
};
|
|
792
840
|
ScopePkQuery: {
|
|
793
841
|
name: string;
|
|
794
842
|
in: string;
|
|
@@ -951,6 +999,107 @@ declare const _default: {
|
|
|
951
999
|
};
|
|
952
1000
|
required: string[];
|
|
953
1001
|
};
|
|
1002
|
+
RoleDataPermissionActionWrite: {
|
|
1003
|
+
type: string;
|
|
1004
|
+
properties: {
|
|
1005
|
+
name: {
|
|
1006
|
+
type: string;
|
|
1007
|
+
description: string;
|
|
1008
|
+
};
|
|
1009
|
+
fields: {
|
|
1010
|
+
type: string;
|
|
1011
|
+
items: {
|
|
1012
|
+
type: string;
|
|
1013
|
+
};
|
|
1014
|
+
description: string;
|
|
1015
|
+
};
|
|
1016
|
+
scopeId: {
|
|
1017
|
+
type: string;
|
|
1018
|
+
nullable: boolean;
|
|
1019
|
+
description: string;
|
|
1020
|
+
};
|
|
1021
|
+
scopeKey: {
|
|
1022
|
+
type: string;
|
|
1023
|
+
description: string;
|
|
1024
|
+
};
|
|
1025
|
+
scope: {
|
|
1026
|
+
type: string;
|
|
1027
|
+
properties: {
|
|
1028
|
+
id: {
|
|
1029
|
+
type: string;
|
|
1030
|
+
};
|
|
1031
|
+
key: {
|
|
1032
|
+
type: string;
|
|
1033
|
+
};
|
|
1034
|
+
};
|
|
1035
|
+
additionalProperties: boolean;
|
|
1036
|
+
description: string;
|
|
1037
|
+
};
|
|
1038
|
+
};
|
|
1039
|
+
required: string[];
|
|
1040
|
+
additionalProperties: boolean;
|
|
1041
|
+
};
|
|
1042
|
+
RoleDataPermissionResourceWrite: {
|
|
1043
|
+
type: string;
|
|
1044
|
+
properties: {
|
|
1045
|
+
name: {
|
|
1046
|
+
type: string;
|
|
1047
|
+
description: string;
|
|
1048
|
+
};
|
|
1049
|
+
usingActionsConfig: {
|
|
1050
|
+
type: string;
|
|
1051
|
+
description: string;
|
|
1052
|
+
};
|
|
1053
|
+
actions: {
|
|
1054
|
+
type: string;
|
|
1055
|
+
items: {
|
|
1056
|
+
$ref: string;
|
|
1057
|
+
};
|
|
1058
|
+
description: string;
|
|
1059
|
+
};
|
|
1060
|
+
};
|
|
1061
|
+
required: string[];
|
|
1062
|
+
additionalProperties: boolean;
|
|
1063
|
+
};
|
|
1064
|
+
RoleDataPermissionsApplyWrite: {
|
|
1065
|
+
type: string;
|
|
1066
|
+
properties: {
|
|
1067
|
+
dataSourceKey: {
|
|
1068
|
+
type: string;
|
|
1069
|
+
description: string;
|
|
1070
|
+
};
|
|
1071
|
+
resources: {
|
|
1072
|
+
type: string;
|
|
1073
|
+
items: {
|
|
1074
|
+
$ref: string;
|
|
1075
|
+
};
|
|
1076
|
+
description: string;
|
|
1077
|
+
};
|
|
1078
|
+
};
|
|
1079
|
+
required: string[];
|
|
1080
|
+
additionalProperties: boolean;
|
|
1081
|
+
};
|
|
1082
|
+
RoleDataPermissionsApplyResult: {
|
|
1083
|
+
type: string;
|
|
1084
|
+
properties: {
|
|
1085
|
+
roleName: {
|
|
1086
|
+
type: string;
|
|
1087
|
+
};
|
|
1088
|
+
dataSourceKey: {
|
|
1089
|
+
type: string;
|
|
1090
|
+
};
|
|
1091
|
+
count: {
|
|
1092
|
+
type: string;
|
|
1093
|
+
};
|
|
1094
|
+
resources: {
|
|
1095
|
+
type: string;
|
|
1096
|
+
items: {
|
|
1097
|
+
$ref: string;
|
|
1098
|
+
};
|
|
1099
|
+
};
|
|
1100
|
+
};
|
|
1101
|
+
additionalProperties: boolean;
|
|
1102
|
+
};
|
|
954
1103
|
DataSourceRole: {
|
|
955
1104
|
type: string;
|
|
956
1105
|
properties: {
|
package/dist/swagger/index.js
CHANGED
|
@@ -213,6 +213,36 @@ var swagger_default = {
|
|
|
213
213
|
}
|
|
214
214
|
}
|
|
215
215
|
},
|
|
216
|
+
"/roles:applyDataPermissions": {
|
|
217
|
+
post: {
|
|
218
|
+
tags: ["roles"],
|
|
219
|
+
summary: "Apply collection-level independent ACL permissions in batch for a role",
|
|
220
|
+
description: "Apply one or more collection-level independent ACL permission configs for a role in one request. Supports action scope binding by `scopeId` or `scopeKey`.",
|
|
221
|
+
parameters: [{ $ref: "#/components/parameters/RoleNameQuery" }],
|
|
222
|
+
requestBody: {
|
|
223
|
+
required: true,
|
|
224
|
+
content: {
|
|
225
|
+
"application/json": {
|
|
226
|
+
schema: {
|
|
227
|
+
$ref: "#/components/schemas/RoleDataPermissionsApplyWrite"
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
},
|
|
232
|
+
responses: {
|
|
233
|
+
200: {
|
|
234
|
+
description: "OK",
|
|
235
|
+
content: {
|
|
236
|
+
"application/json": {
|
|
237
|
+
schema: {
|
|
238
|
+
$ref: "#/components/schemas/RoleDataPermissionsApplyResult"
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
},
|
|
216
246
|
"/roles/{roleName}/users:list": {
|
|
217
247
|
get: {
|
|
218
248
|
tags: ["roles.users"],
|
|
@@ -412,9 +442,10 @@ var swagger_default = {
|
|
|
412
442
|
get: {
|
|
413
443
|
tags: ["roles.dataSourcesCollections"],
|
|
414
444
|
summary: "List data-source collections in role permission configuration",
|
|
415
|
-
description: "List collections in the target data source for the given role and indicate whether they use global permissions or collection-level independent permissions.",
|
|
445
|
+
description: "List collections in the target data source for the given role and indicate whether they use global permissions or collection-level independent permissions. You can pass `dataSourceKey` directly, or through `filter.dataSourceKey` for compatibility.",
|
|
416
446
|
parameters: [
|
|
417
447
|
{ $ref: "#/components/parameters/RoleNamePath" },
|
|
448
|
+
{ $ref: "#/components/parameters/DataSourceKeyQuery" },
|
|
418
449
|
{ $ref: "#/components/parameters/PageQuery" },
|
|
419
450
|
{ $ref: "#/components/parameters/PageSizeQuery" },
|
|
420
451
|
{ $ref: "#/components/parameters/SortQuery" },
|
|
@@ -483,9 +514,12 @@ var swagger_default = {
|
|
|
483
514
|
get: {
|
|
484
515
|
tags: ["roles.dataSourceResources"],
|
|
485
516
|
summary: "Get one collection-level independent ACL permission for a role",
|
|
486
|
-
description: "Get one collection-level independent permission config.
|
|
517
|
+
description: "Get one collection-level independent permission config. You can target it by `filterByTk`, or by `name` + `dataSourceKey`. `filter` with `{ name, dataSourceKey }` is still supported for compatibility.",
|
|
487
518
|
parameters: [
|
|
488
519
|
{ $ref: "#/components/parameters/RoleNamePath" },
|
|
520
|
+
{ $ref: "#/components/parameters/ResourcePermissionTkQuery" },
|
|
521
|
+
{ $ref: "#/components/parameters/DataSourceKeyQuery" },
|
|
522
|
+
{ $ref: "#/components/parameters/DataSourceResourceNameQuery" },
|
|
489
523
|
{ $ref: "#/components/parameters/FilterQuery" },
|
|
490
524
|
{ $ref: "#/components/parameters/AppendsQuery" }
|
|
491
525
|
],
|
|
@@ -507,8 +541,14 @@ var swagger_default = {
|
|
|
507
541
|
post: {
|
|
508
542
|
tags: ["roles.dataSourceResources"],
|
|
509
543
|
summary: "Update collection-level independent ACL permissions for a role",
|
|
510
|
-
description: "Update one collection-level independent permission config.
|
|
511
|
-
parameters: [
|
|
544
|
+
description: "Update one collection-level independent permission config. You can target it by `filterByTk`, or by `name` + `dataSourceKey`. `filter` with `{ name, dataSourceKey }` is still supported for compatibility.",
|
|
545
|
+
parameters: [
|
|
546
|
+
{ $ref: "#/components/parameters/RoleNamePath" },
|
|
547
|
+
{ $ref: "#/components/parameters/ResourcePermissionTkQuery" },
|
|
548
|
+
{ $ref: "#/components/parameters/DataSourceKeyQuery" },
|
|
549
|
+
{ $ref: "#/components/parameters/DataSourceResourceNameQuery" },
|
|
550
|
+
{ $ref: "#/components/parameters/FilterQuery" }
|
|
551
|
+
],
|
|
512
552
|
requestBody: {
|
|
513
553
|
required: true,
|
|
514
554
|
content: {
|
|
@@ -765,6 +805,12 @@ var swagger_default = {
|
|
|
765
805
|
required: true,
|
|
766
806
|
schema: { type: "string" }
|
|
767
807
|
},
|
|
808
|
+
DataSourceKeyQuery: {
|
|
809
|
+
name: "dataSourceKey",
|
|
810
|
+
in: "query",
|
|
811
|
+
description: "Data source key, for example `main`.",
|
|
812
|
+
schema: { type: "string" }
|
|
813
|
+
},
|
|
768
814
|
MembershipUserFilterByTkQuery: {
|
|
769
815
|
name: "filterByTk",
|
|
770
816
|
in: "query",
|
|
@@ -807,6 +853,12 @@ var swagger_default = {
|
|
|
807
853
|
required: true,
|
|
808
854
|
schema: { type: "string" }
|
|
809
855
|
},
|
|
856
|
+
DataSourceResourceNameQuery: {
|
|
857
|
+
name: "name",
|
|
858
|
+
in: "query",
|
|
859
|
+
description: "Resource name, typically the collection name.",
|
|
860
|
+
schema: { type: "string" }
|
|
861
|
+
},
|
|
810
862
|
ScopePkQuery: {
|
|
811
863
|
name: "filterByTk",
|
|
812
864
|
in: "query",
|
|
@@ -919,6 +971,89 @@ var swagger_default = {
|
|
|
919
971
|
},
|
|
920
972
|
required: ["roleMode"]
|
|
921
973
|
},
|
|
974
|
+
RoleDataPermissionActionWrite: {
|
|
975
|
+
type: "object",
|
|
976
|
+
properties: {
|
|
977
|
+
name: {
|
|
978
|
+
type: "string",
|
|
979
|
+
description: "ACL action name, such as `view`, `create`, `update`, or `destroy`."
|
|
980
|
+
},
|
|
981
|
+
fields: {
|
|
982
|
+
type: "array",
|
|
983
|
+
items: { type: "string" },
|
|
984
|
+
description: "Optional allowed field list for field-configurable actions."
|
|
985
|
+
},
|
|
986
|
+
scopeId: {
|
|
987
|
+
type: "integer",
|
|
988
|
+
nullable: true,
|
|
989
|
+
description: "Optional scope id. Use `null` to clear the scope binding."
|
|
990
|
+
},
|
|
991
|
+
scopeKey: {
|
|
992
|
+
type: "string",
|
|
993
|
+
description: "Optional built-in/custom scope key. When provided, server resolves it to `scopeId`."
|
|
994
|
+
},
|
|
995
|
+
scope: {
|
|
996
|
+
type: "object",
|
|
997
|
+
properties: {
|
|
998
|
+
id: { type: "integer" },
|
|
999
|
+
key: { type: "string" }
|
|
1000
|
+
},
|
|
1001
|
+
additionalProperties: true,
|
|
1002
|
+
description: "Optional compatibility payload from existing role-resource readback."
|
|
1003
|
+
}
|
|
1004
|
+
},
|
|
1005
|
+
required: ["name"],
|
|
1006
|
+
additionalProperties: true
|
|
1007
|
+
},
|
|
1008
|
+
RoleDataPermissionResourceWrite: {
|
|
1009
|
+
type: "object",
|
|
1010
|
+
properties: {
|
|
1011
|
+
name: {
|
|
1012
|
+
type: "string",
|
|
1013
|
+
description: "Collection name."
|
|
1014
|
+
},
|
|
1015
|
+
usingActionsConfig: {
|
|
1016
|
+
type: "boolean",
|
|
1017
|
+
description: "Whether the collection uses independent action config."
|
|
1018
|
+
},
|
|
1019
|
+
actions: {
|
|
1020
|
+
type: "array",
|
|
1021
|
+
items: { $ref: "#/components/schemas/RoleDataPermissionActionWrite" },
|
|
1022
|
+
description: "Independent action config list for the collection."
|
|
1023
|
+
}
|
|
1024
|
+
},
|
|
1025
|
+
required: ["name"],
|
|
1026
|
+
additionalProperties: true
|
|
1027
|
+
},
|
|
1028
|
+
RoleDataPermissionsApplyWrite: {
|
|
1029
|
+
type: "object",
|
|
1030
|
+
properties: {
|
|
1031
|
+
dataSourceKey: {
|
|
1032
|
+
type: "string",
|
|
1033
|
+
description: "Data source key, default is `main` when omitted."
|
|
1034
|
+
},
|
|
1035
|
+
resources: {
|
|
1036
|
+
type: "array",
|
|
1037
|
+
items: { $ref: "#/components/schemas/RoleDataPermissionResourceWrite" },
|
|
1038
|
+
description: "Collection-level independent permission payloads to apply in batch."
|
|
1039
|
+
}
|
|
1040
|
+
},
|
|
1041
|
+
required: ["resources"],
|
|
1042
|
+
additionalProperties: true
|
|
1043
|
+
},
|
|
1044
|
+
RoleDataPermissionsApplyResult: {
|
|
1045
|
+
type: "object",
|
|
1046
|
+
properties: {
|
|
1047
|
+
roleName: { type: "string" },
|
|
1048
|
+
dataSourceKey: { type: "string" },
|
|
1049
|
+
count: { type: "integer" },
|
|
1050
|
+
resources: {
|
|
1051
|
+
type: "array",
|
|
1052
|
+
items: { $ref: "#/components/schemas/RoleDataSourceResource" }
|
|
1053
|
+
}
|
|
1054
|
+
},
|
|
1055
|
+
additionalProperties: true
|
|
1056
|
+
},
|
|
922
1057
|
DataSourceRole: {
|
|
923
1058
|
type: "object",
|
|
924
1059
|
properties: {
|
package/package.json
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
"description": "Based on roles, resources, and actions, access control can precisely manage interface configuration permissions, data operation permissions, menu access permissions, and plugin permissions.",
|
|
7
7
|
"description.ru-RU": "На основе ролей, ресурсов и действий система контроля доступа может точно управлять разрешениями на изменение интерфейса, работу с данными, доступ к меню и разрешениями для подключаемых модулей.",
|
|
8
8
|
"description.zh-CN": "基于角色、资源和操作的权限控制,可以精确控制界面配置权限、数据操作权限、菜单访问权限、插件权限。",
|
|
9
|
-
"version": "2.1.0-alpha.
|
|
9
|
+
"version": "2.1.0-alpha.22",
|
|
10
10
|
"license": "Apache-2.0",
|
|
11
11
|
"main": "./dist/server/index.js",
|
|
12
12
|
"homepage": "https://docs.nocobase.com/handbook/acl",
|
|
@@ -46,5 +46,5 @@
|
|
|
46
46
|
"url": "git+https://github.com/nocobase/nocobase.git",
|
|
47
47
|
"directory": "packages/plugins/acl"
|
|
48
48
|
},
|
|
49
|
-
"gitHead": "
|
|
49
|
+
"gitHead": "81ed83f158f172cca607b36beaf8428b14ba16ad"
|
|
50
50
|
}
|