openxiangda 1.0.137 → 1.0.139
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/cli.js +132 -2
- package/lib/design-gates.js +8 -3
- package/openxiangda-skills/references/component-guide.md +7 -1
- package/openxiangda-skills/references/permission-design-patterns.md +37 -6
- package/openxiangda-skills/references/permissions-settings.md +45 -6
- package/openxiangda-skills/references/resource-manifest-cheatsheet.md +7 -3
- package/package.json +1 -1
- package/packages/sdk/dist/{ProcessPreview-Ci8_UsbN.d.mts → ProcessPreview-CZJP8n3e.d.mts} +6 -0
- package/packages/sdk/dist/{ProcessPreview-Ci8_UsbN.d.ts → ProcessPreview-CZJP8n3e.d.ts} +6 -0
- package/packages/sdk/dist/components/index.cjs +143 -16
- package/packages/sdk/dist/components/index.cjs.map +1 -1
- package/packages/sdk/dist/components/index.d.mts +8 -2
- package/packages/sdk/dist/components/index.d.ts +8 -2
- package/packages/sdk/dist/components/index.mjs +143 -16
- package/packages/sdk/dist/components/index.mjs.map +1 -1
- package/packages/sdk/dist/runtime/index.cjs +169 -25
- package/packages/sdk/dist/runtime/index.cjs.map +1 -1
- package/packages/sdk/dist/runtime/index.d.mts +1 -1
- package/packages/sdk/dist/runtime/index.d.ts +1 -1
- package/packages/sdk/dist/runtime/index.mjs +169 -25
- package/packages/sdk/dist/runtime/index.mjs.map +1 -1
- package/packages/sdk/dist/runtime/react.cjs +26 -9
- package/packages/sdk/dist/runtime/react.cjs.map +1 -1
- package/packages/sdk/dist/runtime/react.d.mts +10 -2
- package/packages/sdk/dist/runtime/react.d.ts +10 -2
- package/packages/sdk/dist/runtime/react.mjs +26 -9
- package/packages/sdk/dist/runtime/react.mjs.map +1 -1
package/lib/cli.js
CHANGED
|
@@ -6963,6 +6963,21 @@ function validateResourceItem(kind, item, errors, warnings) {
|
|
|
6963
6963
|
} else {
|
|
6964
6964
|
errors.push(`${label}: 不支持的 viewType: ${definition.viewType}`);
|
|
6965
6965
|
}
|
|
6966
|
+
for (const [index, group] of (item.permissionGroups || []).entries()) {
|
|
6967
|
+
const actions = group.actions || group.operations;
|
|
6968
|
+
if (actions !== undefined) {
|
|
6969
|
+
if (!Array.isArray(actions)) {
|
|
6970
|
+
errors.push(`${label}.permissionGroups[${index}]: actions/operations 必须是数组`);
|
|
6971
|
+
} else {
|
|
6972
|
+
for (const action of actions) {
|
|
6973
|
+
const normalizedAction = normalizePermissionActionValue(action);
|
|
6974
|
+
if (!DATA_VIEW_PERMISSION_ACTIONS.has(normalizedAction)) {
|
|
6975
|
+
errors.push(`${label}.permissionGroups[${index}]: 不支持的数据视图 action "${action}"`);
|
|
6976
|
+
}
|
|
6977
|
+
}
|
|
6978
|
+
}
|
|
6979
|
+
}
|
|
6980
|
+
}
|
|
6966
6981
|
validateDataViewPerformance(label, definition, errors, warnings, storageMode);
|
|
6967
6982
|
}
|
|
6968
6983
|
if (kind === 'storageConfigs') {
|
|
@@ -7064,6 +7079,12 @@ function validateResourceItem(kind, item, errors, warnings) {
|
|
|
7064
7079
|
if (kind === 'scopeGrantSources') {
|
|
7065
7080
|
if (!item.name) warnings.push(`${label}: 未声明 name,将使用 code`);
|
|
7066
7081
|
if (!item.sourceFormUuid && !item.formUuid) errors.push(`${label}: 缺少 sourceFormUuid`);
|
|
7082
|
+
if (
|
|
7083
|
+
item.syncMode !== undefined &&
|
|
7084
|
+
!['on_write', 'manual'].includes(String(item.syncMode))
|
|
7085
|
+
) {
|
|
7086
|
+
errors.push(`${label}: syncMode 只能是 on_write 或 manual`);
|
|
7087
|
+
}
|
|
7067
7088
|
const subjectMappings = item.subjectMappings || item.subjects;
|
|
7068
7089
|
const dimensionMappings = item.dimensionMappings || item.dimensions;
|
|
7069
7090
|
if (!Array.isArray(subjectMappings) || subjectMappings.length === 0) {
|
|
@@ -7104,6 +7125,19 @@ function validateResourceItem(kind, item, errors, warnings) {
|
|
|
7104
7125
|
if (kind === 'formPermissionGroups') {
|
|
7105
7126
|
if (!item.name) errors.push(`${label}: 缺少 name`);
|
|
7106
7127
|
if (!item.formCode && !item.formUuid) errors.push(`${label}: 缺少 formCode 或 formUuid`);
|
|
7128
|
+
const actions = item.actions || item.operations;
|
|
7129
|
+
if (actions !== undefined) {
|
|
7130
|
+
if (!Array.isArray(actions)) {
|
|
7131
|
+
errors.push(`${label}: actions/operations 必须是数组`);
|
|
7132
|
+
} else {
|
|
7133
|
+
for (const action of actions) {
|
|
7134
|
+
const normalizedAction = normalizePermissionActionValue(action);
|
|
7135
|
+
if (!FORM_PERMISSION_ACTIONS.has(normalizedAction)) {
|
|
7136
|
+
errors.push(`${label}: 不支持的表单 action "${action}"`);
|
|
7137
|
+
}
|
|
7138
|
+
}
|
|
7139
|
+
}
|
|
7140
|
+
}
|
|
7107
7141
|
if (item.dataPermission?.type === 'scope_policy' && !getScopePolicyCode(item.dataPermission)) {
|
|
7108
7142
|
errors.push(`${label}: dataPermission.type=scope_policy 时必须声明 policyCode`);
|
|
7109
7143
|
}
|
|
@@ -7131,14 +7165,43 @@ function validateScopePermissionResources(manifest, errors, warnings) {
|
|
|
7131
7165
|
.map(String)
|
|
7132
7166
|
);
|
|
7133
7167
|
const scopePolicyRefs = [];
|
|
7168
|
+
for (const source of manifest.scopeGrantSources || []) {
|
|
7169
|
+
if (String(source.syncMode || '').trim() === 'manual') {
|
|
7170
|
+
warnings.push(`${resourceLabel('scopeGrantSources', source)}: syncMode=manual,运行时来源表单变更不会自动物化;请确认有显式 scope sync 作业`);
|
|
7171
|
+
}
|
|
7172
|
+
}
|
|
7134
7173
|
const inspectPermissionGroup = (kind, item) => {
|
|
7135
7174
|
const groups = kind === 'dataViews'
|
|
7136
7175
|
? (Array.isArray(item.permissionGroups) ? item.permissionGroups : [])
|
|
7137
7176
|
: [item];
|
|
7138
7177
|
for (const group of groups) {
|
|
7178
|
+
const actions = kind === 'dataViews'
|
|
7179
|
+
? normalizeDataViewPermissionGroupActions(group)
|
|
7180
|
+
: normalizeFormPermissionGroupActions(group);
|
|
7139
7181
|
const dataPermission = group?.dataPermission;
|
|
7182
|
+
if (
|
|
7183
|
+
kind === 'formPermissionGroups' &&
|
|
7184
|
+
actions.includes('create') &&
|
|
7185
|
+
(!dataPermission || dataPermission.type !== 'scope_policy')
|
|
7186
|
+
) {
|
|
7187
|
+
warnings.push(`${resourceLabel(kind, item)}: 声明了 create,但未声明 create 对应的 scope_policy;若新增数据必须落在用户业务范围内,请补充 dataPermission.type=scope_policy`);
|
|
7188
|
+
}
|
|
7140
7189
|
if (!dataPermission) continue;
|
|
7141
7190
|
if (dataPermission.type === 'scope_policy') {
|
|
7191
|
+
if (kind === 'formPermissionGroups') {
|
|
7192
|
+
const missing = ['view', 'edit', 'delete', 'export']
|
|
7193
|
+
.filter(action => !actions.includes(action));
|
|
7194
|
+
if (missing.length) {
|
|
7195
|
+
warnings.push(`${resourceLabel(kind, item)}: 使用 scope_policy 但 action 覆盖不完整,缺少 ${missing.join(', ')};请确认每个角色的 action matrix`);
|
|
7196
|
+
}
|
|
7197
|
+
}
|
|
7198
|
+
if (kind === 'dataViews') {
|
|
7199
|
+
const missing = ['query', 'stats', 'export']
|
|
7200
|
+
.filter(action => !actions.includes(action));
|
|
7201
|
+
if (missing.length) {
|
|
7202
|
+
warnings.push(`${resourceLabel(kind, item)}: Data View 使用 scope_policy 但 action 覆盖不完整,缺少 ${missing.join(', ')}`);
|
|
7203
|
+
}
|
|
7204
|
+
}
|
|
7142
7205
|
const policyCode = getScopePolicyCode(dataPermission);
|
|
7143
7206
|
if (!policyCode) {
|
|
7144
7207
|
errors.push(`${resourceLabel(kind, item)}: scope_policy 缺少 policyCode`);
|
|
@@ -10472,11 +10535,20 @@ function normalizeDataViewManifest(bound, dataView) {
|
|
|
10472
10535
|
definition: normalizedDefinition,
|
|
10473
10536
|
refreshConfig: dataView.refreshConfig || dataView.refresh || normalizedDefinition.refresh,
|
|
10474
10537
|
permissionGroups: Array.isArray(dataView.permissionGroups)
|
|
10475
|
-
? dataView.permissionGroups.map(group =>
|
|
10538
|
+
? dataView.permissionGroups.map(group => normalizeDataViewPermissionGroupManifest(group))
|
|
10476
10539
|
: undefined,
|
|
10477
10540
|
});
|
|
10478
10541
|
}
|
|
10479
10542
|
|
|
10543
|
+
function normalizeDataViewPermissionGroupManifest(group = {}) {
|
|
10544
|
+
const body = stripUndefinedValues({
|
|
10545
|
+
...withoutResourceMeta(group),
|
|
10546
|
+
operations: normalizeDataViewPermissionGroupActions(group),
|
|
10547
|
+
});
|
|
10548
|
+
delete body.actions;
|
|
10549
|
+
return body;
|
|
10550
|
+
}
|
|
10551
|
+
|
|
10480
10552
|
function normalizeRouteManifest(route) {
|
|
10481
10553
|
const code = route.code || route.resourceCode;
|
|
10482
10554
|
return stripUndefinedValues({
|
|
@@ -10549,10 +10621,20 @@ function normalizeScopeGrantSourceManifest(source, bound) {
|
|
|
10549
10621
|
effectiveFromField: source.effectiveFromField,
|
|
10550
10622
|
effectiveToField: source.effectiveToField,
|
|
10551
10623
|
filterJson: source.filterJson || source.filter,
|
|
10624
|
+
syncMode: normalizeScopeGrantSourceSyncMode(source.syncMode, source.sync),
|
|
10552
10625
|
enabled: source.enabled !== false,
|
|
10553
10626
|
});
|
|
10554
10627
|
}
|
|
10555
10628
|
|
|
10629
|
+
function normalizeScopeGrantSourceSyncMode(value, legacySync) {
|
|
10630
|
+
const raw = String(value || '').trim().toLowerCase();
|
|
10631
|
+
if (['manual', 'off', 'none', 'disabled', 'false'].includes(raw)) {
|
|
10632
|
+
return 'manual';
|
|
10633
|
+
}
|
|
10634
|
+
if (legacySync === false) return 'manual';
|
|
10635
|
+
return 'on_write';
|
|
10636
|
+
}
|
|
10637
|
+
|
|
10556
10638
|
function normalizeDataScopePolicyManifest(policy) {
|
|
10557
10639
|
const code = policy.code || policy.resourceCode;
|
|
10558
10640
|
return stripUndefinedValues({
|
|
@@ -11161,10 +11243,12 @@ function normalizeFormPermissionGroupManifest(bound, group) {
|
|
|
11161
11243
|
name: group.name || group.code,
|
|
11162
11244
|
type: group.type || 'view',
|
|
11163
11245
|
roles: group.roles || [],
|
|
11246
|
+
operations: normalizeFormPermissionGroupActions(group),
|
|
11164
11247
|
};
|
|
11165
11248
|
delete body.code;
|
|
11166
11249
|
delete body.formCode;
|
|
11167
11250
|
delete body.form;
|
|
11251
|
+
delete body.actions;
|
|
11168
11252
|
return stripUndefinedValues(body);
|
|
11169
11253
|
}
|
|
11170
11254
|
|
|
@@ -11186,6 +11270,52 @@ function sortStringValues(value = []) {
|
|
|
11186
11270
|
return [...(value || [])].map(item => String(item)).sort();
|
|
11187
11271
|
}
|
|
11188
11272
|
|
|
11273
|
+
const FORM_PERMISSION_ACTIONS = new Set([
|
|
11274
|
+
'view',
|
|
11275
|
+
'create',
|
|
11276
|
+
'edit',
|
|
11277
|
+
'delete',
|
|
11278
|
+
'export',
|
|
11279
|
+
'import',
|
|
11280
|
+
'change_records',
|
|
11281
|
+
'workflow',
|
|
11282
|
+
]);
|
|
11283
|
+
|
|
11284
|
+
const DATA_VIEW_PERMISSION_ACTIONS = new Set([
|
|
11285
|
+
'query',
|
|
11286
|
+
'stats',
|
|
11287
|
+
'export',
|
|
11288
|
+
'refresh',
|
|
11289
|
+
]);
|
|
11290
|
+
|
|
11291
|
+
function normalizePermissionActionValue(action) {
|
|
11292
|
+
const value = String(action || '').trim();
|
|
11293
|
+
if (!value) return '';
|
|
11294
|
+
if (value === 'submit') return 'create';
|
|
11295
|
+
return value;
|
|
11296
|
+
}
|
|
11297
|
+
|
|
11298
|
+
function normalizePermissionActionArray(value, allowed, fallback = []) {
|
|
11299
|
+
const source = Array.isArray(value) ? value : fallback;
|
|
11300
|
+
return unique(source.map(normalizePermissionActionValue).filter(action => allowed.has(action)));
|
|
11301
|
+
}
|
|
11302
|
+
|
|
11303
|
+
function normalizeFormPermissionGroupActions(group = {}) {
|
|
11304
|
+
if (group.actions !== undefined || group.operations !== undefined) {
|
|
11305
|
+
return normalizePermissionActionArray(group.actions || group.operations, FORM_PERMISSION_ACTIONS);
|
|
11306
|
+
}
|
|
11307
|
+
return group.type === 'submit'
|
|
11308
|
+
? ['create']
|
|
11309
|
+
: ['view', 'edit', 'delete', 'change_records', 'workflow'];
|
|
11310
|
+
}
|
|
11311
|
+
|
|
11312
|
+
function normalizeDataViewPermissionGroupActions(group = {}) {
|
|
11313
|
+
if (group.actions !== undefined || group.operations !== undefined) {
|
|
11314
|
+
return normalizePermissionActionArray(group.actions || group.operations, DATA_VIEW_PERMISSION_ACTIONS);
|
|
11315
|
+
}
|
|
11316
|
+
return ['query'];
|
|
11317
|
+
}
|
|
11318
|
+
|
|
11189
11319
|
function pickObjectKeys(source = {}, keys = []) {
|
|
11190
11320
|
return keys.reduce((acc, key) => {
|
|
11191
11321
|
acc[key] = source[key];
|
|
@@ -11407,7 +11537,7 @@ function normalizeDataViewPermissionGroupsForPlan(groups = []) {
|
|
|
11407
11537
|
code,
|
|
11408
11538
|
name: group.name || code || '默认权限组',
|
|
11409
11539
|
roles: Array.isArray(group.roles) ? sortStringValues(group.roles) : [],
|
|
11410
|
-
operations:
|
|
11540
|
+
operations: sortStringValues(normalizeDataViewPermissionGroupActions(group)),
|
|
11411
11541
|
fieldPermissions: normalizeFieldPermissionsForPlan(group.fieldPermissions),
|
|
11412
11542
|
dataPermission: group.dataPermission || undefined,
|
|
11413
11543
|
});
|
package/lib/design-gates.js
CHANGED
|
@@ -124,15 +124,19 @@ const DESIGN_GATE_TOPICS = [
|
|
|
124
124
|
'哪些角色可以设置应用角色、分配角色成员、给角色分配接口权限;这些角色是否需要 `app:role:manage`',
|
|
125
125
|
'哪些角色可以维护页面权限组、表单权限组、组织账号;是否需要 `app:page-permission-group:manage`、`app:form-permission-group:manage`、`app:organization:manage`',
|
|
126
126
|
'业务范围字段是什么,哪些可见字段需要派生隐藏 scalar key 供表单权限条件匹配',
|
|
127
|
-
'
|
|
127
|
+
'每个角色的 action matrix 是什么:view/create/edit/delete/export/import/change_records/workflow 是否分别授权',
|
|
128
|
+
'导出/导入是否独立授权;导出范围是否与 view 范围不同,导入是否还需要逐行 create 范围校验',
|
|
129
|
+
'create 是否需要 scope_policy 校验提交数据,防止新增到未授权业务范围',
|
|
130
|
+
'页面/菜单/路由、表单 actions/fieldAccessPolicy/dataPermission/scope_policy 的权限矩阵如何落到 resources',
|
|
128
131
|
'查询参数是否参与授权;如果参与,只能做上下文、筛选或 ticket 输入,敏感数据必须由 public-access grant、角色、表单权限组或 App Function 校验',
|
|
129
132
|
'验收时需要模拟哪些角色、账号状态、业务范围、查询参数篡改和拒绝场景',
|
|
130
133
|
],
|
|
131
134
|
recommendedDefaults: [
|
|
132
135
|
'正式后台优先选择 managed-platform-account;如果平台账号已存在,选择 existing-platform-user-assignment;固定内部门户选择 static-role-permission',
|
|
133
136
|
'query-param-context 仅用于低风险页面上下文、筛选条件或公开 ticket 输入,不作为敏感数据授权依据',
|
|
134
|
-
'角色写 src/resources/roles,页面组写 permissions/page-groups,表单组写 permissions/form-groups,显式声明
|
|
135
|
-
'复杂业务范围优先写 permissions/scope-dimensions、permissions/scope-grant-sources、permissions/data-scope-policies,并在表单权限组或 data-view 权限组使用 dataPermission.type=scope_policy',
|
|
137
|
+
'角色写 src/resources/roles,页面组写 permissions/page-groups,表单组写 permissions/form-groups,显式声明 actions/dataPermission/fieldAccessPolicy;平台内部仍兼容存储为 operations',
|
|
138
|
+
'复杂业务范围优先写 permissions/scope-dimensions、permissions/scope-grant-sources、permissions/data-scope-policies,并在表单权限组或 data-view 权限组使用 dataPermission.type=scope_policy;授权来源默认 syncMode=on_write,由平台在来源表单写入后自动物化',
|
|
139
|
+
'scope_policy 只表达数据范围,actions 表达操作能力;按钮隐藏只是 UX,后端 action check 才是权威',
|
|
136
140
|
'scope_policy 默认语义是个人授权 + 当前应用角色授权;多维 rules 显式 AND;空授权集合拒绝;管理员绕过但可审计',
|
|
137
141
|
'能管理角色或给别人分配角色的应用角色必须在 roles manifest 声明 apiPermissionCodes;至少包含 app:role:manage,按需加入 app:page-permission-group:manage、app:form-permission-group:manage、app:organization:manage',
|
|
138
142
|
'由校区管理员等委托管理员创建的新角色,如果还具备继续管理账号/角色/权限的能力,也必须同步声明并发布对应 apiPermissionCodes',
|
|
@@ -144,6 +148,7 @@ const DESIGN_GATE_TOPICS = [
|
|
|
144
148
|
'不能只做前端权限隐藏,必须有后端角色、表单权限组、public-access grant 或 App Function 校验',
|
|
145
149
|
'查询参数不能作为敏感授权依据,只能作为上下文、筛选或 ticket 输入',
|
|
146
150
|
'只配菜单可见,不配后端数据权限',
|
|
151
|
+
'只隐藏按钮但没有表单权限组 actions 或 App Function 服务端校验',
|
|
147
152
|
'为了省事授予全部数据再在前端过滤',
|
|
148
153
|
'为每个客户/项目/区域/学院/班级/门店创建一个角色或一个权限组',
|
|
149
154
|
'把应用表单里的业务范围强行同步成平台部门、平台角色或大量权限组',
|
|
@@ -64,7 +64,7 @@ AI 在实现成熟交互前必须先判断是否已有可靠组件或库:
|
|
|
64
64
|
| 富文本编辑 | `EditorField` | 含图片上传集成 | 完整工具栏、平台图床、粘贴清洗 |
|
|
65
65
|
| 数字签名 | `DigitalSignatureField` | 平台级签名 | 签名采集、验证、归档 |
|
|
66
66
|
| 地理位置 | `LocationField` | 接入地图服务 | 定位、地图选点、地址解析 |
|
|
67
|
-
| 数据管理列表 | `DataManagementList` | 接入数据查询 API |
|
|
67
|
+
| 数据管理列表 | `DataManagementList` | 接入数据查询 API | 分页、筛选、导出、批量操作、字段权限、后端 action summary 按钮控制 |
|
|
68
68
|
|
|
69
69
|
### 示例
|
|
70
70
|
|
|
@@ -89,6 +89,12 @@ OSS 浏览器直传所需的 CORS 规则在 `src/resources/storage/<code>.json`
|
|
|
89
89
|
|
|
90
90
|
业务应用只能从 `openxiangda`、`openxiangda/runtime`、`openxiangda/runtime/react` 等公开入口导入组件和 SDK。不要为了减小包体积去引用 `openxiangda/packages/sdk/dist/...` 内部文件;React SPA 模板已提供 vendor chunk 分组,SDK 会把 `antd-mobile`、`dayjs` 等大型 UI 依赖交给应用构建器按路由拆分。
|
|
91
91
|
|
|
92
|
+
`DataManagementList` 默认 `permissionMode="auto"`,进入页面会读取表单 action
|
|
93
|
+
summary,并用 `can.create/export/import/delete/workflow/change_records` 隐藏新增、
|
|
94
|
+
导入、导出、删除、流程等按钮。`readonly=true` 仍会强制关闭新增、编辑、删除和
|
|
95
|
+
导入。`actionOverrides` 只能关闭按钮,不能扩大后端权限;敏感授权必须依赖后端
|
|
96
|
+
form actions、`scope_policy` 或 App Function 服务端校验。
|
|
97
|
+
|
|
92
98
|
> ✅ 一句话原则:**这些场景看到了,直接抄上表,不要自己写。**
|
|
93
99
|
|
|
94
100
|
---
|
|
@@ -27,8 +27,8 @@ Required design:
|
|
|
27
27
|
scalar scope keys, platform role member sync state.
|
|
28
28
|
- `src/resources/roles/*.json`: stable app role codes.
|
|
29
29
|
- `src/resources/permissions/page-groups/*.json`: menu, route, and page access.
|
|
30
|
-
- `src/resources/permissions/form-groups/*.json`:
|
|
31
|
-
|
|
30
|
+
- `src/resources/permissions/form-groups/*.json`: `actions`, data scope or
|
|
31
|
+
`dataPermission.type=scope_policy`, and `fieldAccessPolicy`.
|
|
32
32
|
- `apiPermissionCodes` on roles that are allowed to manage roles, role members,
|
|
33
33
|
permission groups, or organization accounts.
|
|
34
34
|
- Sync App Functions or trusted JS_CODE: sync organization units, accounts, and
|
|
@@ -142,8 +142,13 @@ The platform keeps RBAC and data range separate:
|
|
|
142
142
|
|
|
143
143
|
- Roles decide who can enter pages, submit/view forms, use operations, and
|
|
144
144
|
maintain permissions.
|
|
145
|
+
- Form `actions` decide what a role can do: `view`, `create`, `edit`, `delete`,
|
|
146
|
+
`export`, `import`, `change_records`, and `workflow`.
|
|
147
|
+
- Data View `actions` decide query capabilities: `query`, `stats`, `export`,
|
|
148
|
+
and `refresh`.
|
|
145
149
|
- Field access remains `fieldAccessPolicy`.
|
|
146
|
-
- Business scope policies decide which rows
|
|
150
|
+
- Business scope policies decide which rows or submitted scope values a matched
|
|
151
|
+
role/user can access for the current action.
|
|
147
152
|
- App Functions must still check role/scope server-side before sensitive
|
|
148
153
|
writes.
|
|
149
154
|
|
|
@@ -157,21 +162,47 @@ Default semantics:
|
|
|
157
162
|
- Authorization source rows should include an explicit status/enabled field and
|
|
158
163
|
grant sources should use `filterJson` or `enabledField` so drafts, disabled
|
|
159
164
|
rows, or rejected assignments are not materialized.
|
|
160
|
-
- Source forms
|
|
161
|
-
|
|
162
|
-
|
|
165
|
+
- Source authorization forms are synced by the platform after form
|
|
166
|
+
create/update/delete/import when the grant source uses the default
|
|
167
|
+
`syncMode: "on_write"`. Use `syncMode: "manual"` only for externally
|
|
168
|
+
maintained sources or unusual bulk jobs; `sync: true` still performs an
|
|
169
|
+
immediate full sync during resource publish.
|
|
163
170
|
- Policy target fields should normally be hidden scalar keys. If targeting a
|
|
164
171
|
JSONB option/person/department field directly, declare `valuePath: "value"` or
|
|
165
172
|
`componentType`; multi-value fields should be normalized into a scalar key or
|
|
166
173
|
a separate authorization row.
|
|
167
174
|
- App Function form read helpers enforce view permission for real callers. Do
|
|
168
175
|
not grant broad form read permission and then filter in function/page code.
|
|
176
|
+
- `DataManagementList` can hide buttons from the backend action summary, but it
|
|
177
|
+
is not the authority. Sensitive actions must be represented by form actions or
|
|
178
|
+
App Function server checks.
|
|
169
179
|
|
|
170
180
|
Do not model a dynamic business object set by generating dozens or hundreds of
|
|
171
181
|
roles or permission groups. Use a business authorization form and `scope_policy`
|
|
172
182
|
when the object set is maintained by users, imported from another system, or
|
|
173
183
|
expected to grow.
|
|
174
184
|
|
|
185
|
+
Recommended action pattern:
|
|
186
|
+
|
|
187
|
+
```json
|
|
188
|
+
{
|
|
189
|
+
"code": "campus_profile_manage",
|
|
190
|
+
"formCode": "student_profile",
|
|
191
|
+
"name": "校区管理员档案权限",
|
|
192
|
+
"type": "view",
|
|
193
|
+
"roles": ["campus_admin"],
|
|
194
|
+
"actions": ["view", "edit", "export"],
|
|
195
|
+
"dataPermission": {
|
|
196
|
+
"type": "scope_policy",
|
|
197
|
+
"policyCode": "campus_row_scope"
|
|
198
|
+
},
|
|
199
|
+
"fieldAccessPolicy": {
|
|
200
|
+
"defaultAccess": "readonly",
|
|
201
|
+
"fields": [{ "fieldId": "remark", "access": "edit" }]
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
```
|
|
205
|
+
|
|
175
206
|
## Role Setting Delegation
|
|
176
207
|
|
|
177
208
|
Setting application roles is itself permission controlled. A user can be a
|
|
@@ -260,6 +260,7 @@ Declare scope resources:
|
|
|
260
260
|
{ "field": "status", "op": "eq", "value": "enabled" }
|
|
261
261
|
]
|
|
262
262
|
},
|
|
263
|
+
"syncMode": "on_write",
|
|
263
264
|
"sync": true
|
|
264
265
|
}
|
|
265
266
|
```
|
|
@@ -289,7 +290,43 @@ Reference the policy from a form permission group:
|
|
|
289
290
|
"name": "学院查看学生",
|
|
290
291
|
"type": "view",
|
|
291
292
|
"roles": ["college_admin"],
|
|
292
|
-
"
|
|
293
|
+
"actions": ["view", "edit", "export"],
|
|
294
|
+
"dataPermission": {
|
|
295
|
+
"type": "scope_policy",
|
|
296
|
+
"policyCode": "college_data"
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
Unified permission model:
|
|
302
|
+
|
|
303
|
+
- `scope_policy` means data range: which rows this user/current app role can
|
|
304
|
+
access.
|
|
305
|
+
- `actions` means operation ability: what the role can do to rows in that
|
|
306
|
+
range. Form actions are `view`, `create`, `edit`, `delete`, `export`,
|
|
307
|
+
`import`, `change_records`, and `workflow`.
|
|
308
|
+
- The platform still stores actions in the legacy `operations` field. Manifests
|
|
309
|
+
may use either `actions` or `operations`; prefer `actions` in new resources.
|
|
310
|
+
- `type: "submit"` is the legacy create group and maps to `actions:["create"]`.
|
|
311
|
+
- `export` is independent from `view` for new apps. Old apps may temporarily
|
|
312
|
+
rely on view-to-export compatibility, but `permission audit` should be used to
|
|
313
|
+
migrate to explicit `export`.
|
|
314
|
+
- `create` with `scope_policy` validates submitted scope fields; a campus admin
|
|
315
|
+
cannot create a row for another campus just because the create button is
|
|
316
|
+
visible.
|
|
317
|
+
- Frontend button hiding is UX only. The backend action check and action-specific
|
|
318
|
+
data range are authoritative.
|
|
319
|
+
|
|
320
|
+
Create permission with business scope:
|
|
321
|
+
|
|
322
|
+
```json
|
|
323
|
+
{
|
|
324
|
+
"code": "college_student_create",
|
|
325
|
+
"formCode": "student",
|
|
326
|
+
"name": "学院管理员新增本学院学生",
|
|
327
|
+
"type": "submit",
|
|
328
|
+
"roles": ["college_admin"],
|
|
329
|
+
"actions": ["create"],
|
|
293
330
|
"dataPermission": {
|
|
294
331
|
"type": "scope_policy",
|
|
295
332
|
"policyCode": "college_data"
|
|
@@ -298,7 +335,8 @@ Reference the policy from a form permission group:
|
|
|
298
335
|
```
|
|
299
336
|
|
|
300
337
|
For Data View permission groups, use the same `dataPermission` shape inside the
|
|
301
|
-
view's `permissionGroups`.
|
|
338
|
+
view's `permissionGroups`. Data View actions are `query`, `stats`, `export`,
|
|
339
|
+
and `refresh`.
|
|
302
340
|
|
|
303
341
|
Default runtime semantics:
|
|
304
342
|
|
|
@@ -317,10 +355,11 @@ Default runtime semantics:
|
|
|
317
355
|
- `filterJson` filters authorization source rows before materializing grants.
|
|
318
356
|
Use it for enabled/approved/effective authorization rows; unsupported filter
|
|
319
357
|
operations do not match.
|
|
320
|
-
- Source authorization forms
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
358
|
+
- Source authorization forms are synced by the platform after create/update/
|
|
359
|
+
delete/import when the grant source uses the default `syncMode: "on_write"`.
|
|
360
|
+
Use `syncMode: "manual"` only for externally maintained sources or unusual
|
|
361
|
+
bulk jobs that explicitly call `openxiangda scope sync`. Publishing with
|
|
362
|
+
`"sync": true` still performs an immediate full sync during resource publish.
|
|
324
363
|
- App Function `ctx.form.queryOne/queryMany/getById` enforces the caller's view
|
|
325
364
|
permission when there is a real current user. No matching view permission
|
|
326
365
|
group means deny; backend system automations can still use trusted internal
|
|
@@ -334,6 +334,7 @@ Grant source:
|
|
|
334
334
|
{ "field": "status", "op": "eq", "value": "enabled" }
|
|
335
335
|
]
|
|
336
336
|
},
|
|
337
|
+
"syncMode": "on_write",
|
|
337
338
|
"sync": true
|
|
338
339
|
}
|
|
339
340
|
```
|
|
@@ -373,9 +374,12 @@ Data View permission groups use the same `dataPermission` object inside
|
|
|
373
374
|
default; large grant sets use DB `EXISTS` instead of huge `IN` SQL. Prefer
|
|
374
375
|
hidden scalar target fields such as `collegeCode`; if the target is a JSONB
|
|
375
376
|
option/person/department field, set `valuePath: "value"` or `componentType`.
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
`
|
|
377
|
+
Authorization source form changes are synced by the platform after
|
|
378
|
+
create/update/delete/import when the grant source uses the default
|
|
379
|
+
`syncMode: "on_write"`. Use `syncMode: "manual"` only for externally maintained
|
|
380
|
+
sources or special bulk jobs that explicitly call `openxiangda scope sync`.
|
|
381
|
+
Manifest `sync: true` still performs an immediate full sync during resource
|
|
382
|
+
publish.
|
|
379
383
|
|
|
380
384
|
## 3. Connector — `src/resources/connectors/<code>.json`
|
|
381
385
|
|
package/package.json
CHANGED
|
@@ -1021,6 +1021,12 @@ interface ProcessDefinition {
|
|
|
1021
1021
|
interface ViewPermissionSummary {
|
|
1022
1022
|
fieldPermissions: Record<string, 'FORM_FILED_HIDDEN' | 'FORM_FILED_VIEW' | 'FORM_FILED_EDIT'>;
|
|
1023
1023
|
operations: string[];
|
|
1024
|
+
actions?: string[];
|
|
1025
|
+
can?: Record<string, boolean>;
|
|
1026
|
+
fieldAccessPolicy?: any;
|
|
1027
|
+
hasFullAccess?: boolean;
|
|
1028
|
+
resourceType?: string;
|
|
1029
|
+
matchedGroupCodes?: string[];
|
|
1024
1030
|
}
|
|
1025
1031
|
/** 表单实例数据 */
|
|
1026
1032
|
interface FormInstanceData {
|
|
@@ -1021,6 +1021,12 @@ interface ProcessDefinition {
|
|
|
1021
1021
|
interface ViewPermissionSummary {
|
|
1022
1022
|
fieldPermissions: Record<string, 'FORM_FILED_HIDDEN' | 'FORM_FILED_VIEW' | 'FORM_FILED_EDIT'>;
|
|
1023
1023
|
operations: string[];
|
|
1024
|
+
actions?: string[];
|
|
1025
|
+
can?: Record<string, boolean>;
|
|
1026
|
+
fieldAccessPolicy?: any;
|
|
1027
|
+
hasFullAccess?: boolean;
|
|
1028
|
+
resourceType?: string;
|
|
1029
|
+
matchedGroupCodes?: string[];
|
|
1024
1030
|
}
|
|
1025
1031
|
/** 表单实例数据 */
|
|
1026
1032
|
interface FormInstanceData {
|