修改了RelationAuth中updateFree的结构

This commit is contained in:
Xu Chang 2023-10-19 19:38:43 +08:00
parent ccf11e87d5
commit 1595dbb51a
14 changed files with 240 additions and 558 deletions

View File

@ -1,10 +1,2 @@
import { AuthCascadePath, AuthDeduceRelationMap } from "../types/Entity";
import { EntityDict } from "./EntityDict";
import { CreateOperationData as Relation } from "./Relation/Schema";
export declare const ActionCascadePathGraph: AuthCascadePath<EntityDict>[];
export declare const RelationCascadePathGraph: AuthCascadePath<EntityDict>[];
export declare const relations: Relation[];
export declare const deducedRelationMap: AuthDeduceRelationMap<EntityDict>;
export declare const selectFreeEntities: (keyof EntityDict)[];
export declare const updateFreeEntities: (keyof EntityDict)[];
export declare const createFreeEntities: (keyof EntityDict)[];

View File

@ -1,10 +1,4 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.createFreeEntities = exports.updateFreeEntities = exports.selectFreeEntities = exports.deducedRelationMap = exports.relations = exports.RelationCascadePathGraph = exports.ActionCascadePathGraph = void 0;
exports.ActionCascadePathGraph = [];
exports.RelationCascadePathGraph = [];
exports.relations = void 0;
exports.relations = [];
exports.deducedRelationMap = {};
exports.selectFreeEntities = [];
exports.updateFreeEntities = [];
exports.createFreeEntities = [];

View File

@ -1,11 +1,27 @@
/**
* 使
* @param map
*/
export declare function registerIgnoredForeignKeyMap(map: Record<string, string[]>): void;
/**
* 使
* @param map
*/
export declare function registerFreeEntities(selectFreeEntities?: string[], createFreeEntities?: string[], updateFreeEntities?: string[]): void;
/**
* 使
* @param map
*/
export declare function registerIgnoredRelationPathMap(map: Record<string, string[]>): void;
/**
* path
* @param map
*/
export declare function registerFixedDestinationPathMap(map: Record<string, string[]>): void;
/**
* 使
* @param map
*/
export declare function registerDeducedRelationMap(map: Record<string, string>): void;
export declare function analyzeEntities(inputDir: string, relativePath?: string): void;
export declare function buildSchema(outputDir: string): void;

View File

@ -3255,6 +3255,10 @@ function analyzeInModi() {
}
}
}
/**
* 此部分功能不再使用
* @param map
*/
let IGNORED_FOREIGN_KEY_MAP = {};
let IGNORED_RELATION_PATH_MAP = {};
let DEDUCED_RELATION_MAP = {};
@ -3263,16 +3267,28 @@ let CREATE_FREE_ENTITIES = [];
let UPDATE_FREE_ENTITIES = [];
let FIXED_DESTINATION_PATH_MAP = {};
let FIXED_FOR_ALL_DESTINATION_PATH_ENTITIES = [];
/**
* 此函数不再使用
* @param map
*/
function registerIgnoredForeignKeyMap(map) {
IGNORED_FOREIGN_KEY_MAP = map;
}
exports.registerIgnoredForeignKeyMap = registerIgnoredForeignKeyMap;
/**
* 此函数不再使用
* @param map
*/
function registerFreeEntities(selectFreeEntities = [], createFreeEntities = [], updateFreeEntities = []) {
SELECT_FREE_ENTITIES = selectFreeEntities;
CREATE_FREE_ENTITIES = createFreeEntities;
UPDATE_FREE_ENTITIES = updateFreeEntities;
}
exports.registerFreeEntities = registerFreeEntities;
/**
* 此函数不再使用
* @param map
*/
function registerIgnoredRelationPathMap(map) {
for (const k in map) {
IGNORED_RELATION_PATH_MAP[(0, string_1.firstLetterUpperCase)(k)] = map[k];
@ -3297,6 +3313,10 @@ function registerFixedDestinationPathMap(map) {
}
}
exports.registerFixedDestinationPathMap = registerFixedDestinationPathMap;
/**
* 此函数不再使用
* @param map
*/
function registerDeducedRelationMap(map) {
for (const k in map) {
const entity = (0, string_1.firstLetterUpperCase)(k);
@ -3314,6 +3334,7 @@ function registerDeducedRelationMap(map) {
exports.registerDeducedRelationMap = registerDeducedRelationMap;
/**
* 输出所有和User相关的对象的后继
* 此函数不再使用
*/
function outputRelation(outputDir, printer) {
const ExcludedEntities = ['Oper', 'User', 'OperEntity', 'Modi', 'ModiEntity', 'UserRelation', 'Relation', 'RelationAuth', 'ActionAuth'];
@ -3464,6 +3485,47 @@ function outputRelation(outputDir, printer) {
const filename = path_1.default.join(outputDir, 'Relation.ts');
(0, fs_1.writeFileSync)(filename, result, { flag: 'w' });
}
/**
* 输出oak-app-domain中的Relation.ts文件
* 不再输出actionAuthGraph和relationAuthGraph两个复杂的对象
* @param outputDir
* @param printer
*/
function outputRelation2(outputDir, printer) {
const entityRelations = [];
for (const entity in Schema) {
const { hasRelationDef } = Schema[entity];
if (hasRelationDef) {
const { type } = hasRelationDef;
if (ts.isUnionTypeNode(type)) {
const { types } = type;
const relations = types.map(ele => {
(0, assert_1.default)(ts.isLiteralTypeNode(ele) && ts.isStringLiteral(ele.literal));
return ele.literal.text;
});
entityRelations.push([(0, string_1.firstLetterLowerCase)(entity), relations]);
}
else {
(0, assert_1.default)(ts.isLiteralTypeNode(type));
(0, assert_1.default)(ts.isStringLiteral(type.literal));
const relations = [type.literal.text];
entityRelations.push([(0, string_1.firstLetterLowerCase)(entity), relations]);
}
}
}
const stmts = [
factory.createImportDeclaration(undefined, factory.createImportClause(false, undefined, factory.createNamedImports([factory.createImportSpecifier(false, undefined, factory.createIdentifier("EntityDict"))])), factory.createStringLiteral("./EntityDict"), undefined),
factory.createImportDeclaration(undefined, factory.createImportClause(false, undefined, factory.createNamedImports([factory.createImportSpecifier(false, factory.createIdentifier("CreateOperationData"), factory.createIdentifier("Relation"))])), factory.createStringLiteral("./Relation/Schema"), undefined),
factory.createVariableStatement([factory.createToken(ts.SyntaxKind.ExportKeyword)], factory.createVariableDeclarationList([factory.createVariableDeclaration(factory.createIdentifier("relations"), undefined, factory.createArrayTypeNode(factory.createTypeReferenceNode(factory.createIdentifier("Relation"), undefined)), factory.createArrayLiteralExpression((0, lodash_1.flatten)(entityRelations.map(([entity, relations]) => relations.map((relation) => factory.createObjectLiteralExpression([
factory.createPropertyAssignment(factory.createIdentifier("id"), factory.createStringLiteral((0, uuid_1.formUuid)(entity, relation))),
factory.createPropertyAssignment(factory.createIdentifier("entity"), factory.createStringLiteral(entity)),
factory.createPropertyAssignment(factory.createIdentifier("name"), factory.createStringLiteral(relation))
], true)))), true))], ts.NodeFlags.Const))
];
const result = printer.printList(ts.ListFormat.SourceFileStatements, factory.createNodeArray(stmts), ts.createSourceFile("someFileName.ts", "", ts.ScriptTarget.Latest, /*setParentNodes*/ false, ts.ScriptKind.TS));
const filename = path_1.default.join(outputDir, 'Relation.ts');
(0, fs_1.writeFileSync)(filename, result, { flag: 'w' });
}
function analyzeEntities(inputDir, relativePath) {
const files = (0, fs_1.readdirSync)(inputDir);
const fullFilenames = files.map(ele => {
@ -3492,7 +3554,7 @@ function buildSchema(outputDir) {
outputAction(outputDir, printer);
outputEntityDict(outputDir, printer);
outputStorage(outputDir, printer);
outputRelation(outputDir, printer);
outputRelation2(outputDir, printer);
outputIndexTs(outputDir);
if (!process.env.COMPLING_AS_LIB) {
outputPackageJson(outputDir);

2
lib/index.d.ts vendored
View File

@ -1,4 +1,4 @@
export { storageSchema, selectFreeEntities } from './base-app-domain';
export { storageSchema } from './base-app-domain';
export type { EntityDict as BaseEntityDict } from './base-app-domain';
export * from './store/AsyncRowStore';
export * from './store/SyncRowStore';

View File

@ -1,10 +1,9 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.expandUuidTo36Bytes = exports.shrinkUuidTo32Bytes = exports.generateNewId = exports.generateNewIdAsync = exports.compareVersion = exports.checkAttributesScope = exports.checkAttributesNotNull = exports.composeUrl = exports.assert = exports.SimpleConnector = exports.selectFreeEntities = exports.storageSchema = void 0;
exports.expandUuidTo36Bytes = exports.shrinkUuidTo32Bytes = exports.generateNewId = exports.generateNewIdAsync = exports.compareVersion = exports.checkAttributesScope = exports.checkAttributesNotNull = exports.composeUrl = exports.assert = exports.SimpleConnector = exports.storageSchema = void 0;
const tslib_1 = require("tslib");
var base_app_domain_1 = require("./base-app-domain");
Object.defineProperty(exports, "storageSchema", { enumerable: true, get: function () { return base_app_domain_1.storageSchema; } });
Object.defineProperty(exports, "selectFreeEntities", { enumerable: true, get: function () { return base_app_domain_1.selectFreeEntities; } });
tslib_1.__exportStar(require("./store/AsyncRowStore"), exports);
tslib_1.__exportStar(require("./store/SyncRowStore"), exports);
tslib_1.__exportStar(require("./store/CascadeStore"), exports);

View File

@ -1,27 +1,18 @@
import { EntityDict } from "../base-app-domain";
import { StorageSchema } from "../types";
import { AuthCascadePath, EntityDict as BaseEntityDict, AuthDeduceRelationMap } from "../types/Entity";
import { EntityDict as BaseEntityDict, AuthDeduceRelationMap } from "../types/Entity";
import { AsyncContext } from "./AsyncRowStore";
import { SyncContext } from "./SyncRowStore";
export declare class RelationAuth<ED extends EntityDict & BaseEntityDict> {
private actionCascadePathGraph;
private relationCascadePathGraph;
private authDeduceRelationMap;
private schema;
static SPECIAL_ENTITIES: string[];
private selectFreeEntities;
private createFreeEntities;
private updateFreeEntities;
constructor(schema: StorageSchema<ED>, actionCascadePathGraph: AuthCascadePath<ED>[], relationCascadePathGraph: AuthCascadePath<ED>[], authDeduceRelationMap: AuthDeduceRelationMap<ED>, selectFreeEntities?: (keyof ED)[], createFreeEntities?: (keyof ED)[], updateFreeEntities?: (keyof ED)[]);
private updateFreeDict;
constructor(schema: StorageSchema<ED>, authDeduceRelationMap: AuthDeduceRelationMap<ED>, selectFreeEntities?: (keyof ED)[], updateFreeDict?: {
[A in keyof ED]?: string[];
});
checkRelationSync<T extends keyof ED, Cxt extends SyncContext<ED>>(entity: T, operation: Omit<ED[T]['Operation'] | ED[T]['Selection'], 'id'>, context: Cxt): void;
/**
* entity上可以操作的relationIds
* @param entity
* @param entityId
* @param context
* @returns
*/
private getGrantedRelationIds;
checkRelationAsync<T extends keyof ED, Cxt extends AsyncContext<ED>>(entity: T, operation: Omit<ED[T]['Operation'] | ED[T]['Selection'], 'id'>, context: Cxt): Promise<void>;
private checkUserRelation;
private checkOperateSpecialEntities2;

View File

@ -11,21 +11,15 @@ const action_1 = require("../actions/action");
const lodash_1 = require("../utils/lodash");
const env_1 = require("../compiler/env");
class RelationAuth {
actionCascadePathGraph;
relationCascadePathGraph;
authDeduceRelationMap;
schema;
static SPECIAL_ENTITIES = env_1.SYSTEM_RESERVE_ENTITIES;
selectFreeEntities;
createFreeEntities;
updateFreeEntities;
constructor(schema, actionCascadePathGraph, relationCascadePathGraph, authDeduceRelationMap, selectFreeEntities, createFreeEntities, updateFreeEntities) {
this.actionCascadePathGraph = actionCascadePathGraph;
this.relationCascadePathGraph = relationCascadePathGraph;
updateFreeDict;
constructor(schema, authDeduceRelationMap, selectFreeEntities, updateFreeDict) {
this.schema = schema;
this.selectFreeEntities = selectFreeEntities || [];
this.createFreeEntities = createFreeEntities || [];
this.updateFreeEntities = updateFreeEntities || [];
this.updateFreeDict = updateFreeDict || {};
this.authDeduceRelationMap = Object.assign({}, authDeduceRelationMap, {
modi: 'entity',
});
@ -37,52 +31,6 @@ class RelationAuth {
}
this.checkActions2(entity, operation, context);
}
/**
* 查询当前用户在对应entity上可以操作的relationIds
* @param entity
* @param entityId
* @param context
* @returns
*/
getGrantedRelationIds(entity, entityId, context) {
const result = context.select('relationAuth', {
data: {
id: 1,
destRelationId: 1,
destRelation: {
id: 1,
name: 1,
entity: 1,
entityId: 1,
display: 1,
},
},
filter: {
sourceRelation: {
userRelation$relation: {
userId: context.getCurrentUserId(),
}
},
destRelation: {
entity: entity,
$or: [
{
entityId,
},
{
entityId: {
$exists: false,
},
}
],
},
},
}, {});
if (result instanceof Promise) {
return result.then((r2) => r2.map(ele => ele.destRelation));
}
return result.map(ele => ele.destRelation);
}
// 后台检查filter是否满足relation约束
async checkRelationAsync(entity, operation, context) {
if (context.isRoot()) {
@ -141,10 +89,6 @@ class RelationAuth {
}, { dontCollect: true });
const checkRelationAuth = (relationAuth) => {
const { destRelation, sourceRelationId, path } = relationAuth;
if (action === 'create' && path === '') {
// 自己建立自己,一定可以通过
return 1;
}
const destEntityFilter = {};
if (path) {
(0, lodash_1.set)(destEntityFilter, path, {
@ -962,204 +906,9 @@ class RelationAuth {
};
return checkNode(tree);
}
/* private checkOperationTree<Cxt extends AsyncContext<ED> | SyncContext<ED>>(tree: OperationTree<ED>, context: Cxt) {
const actionAuths2 = this.findActionAuthsOnNode(tree, context);
const checkChildNode = (actionAuths: ED['actionAuth']['Schema'][] | Promise<ED['actionAuth']['Schema'][]>, node: OperationTree<ED>): boolean | Promise<boolean> => {
const checkChildNodeInner = (legalAuths: ED['actionAuth']['Schema'][]) => {
// 因为如果children是数组的话会把数组中所有的action并起来查询所以在这里还要再确认一次
const realLegalPaths = legalAuths.filter(
(ele) => {
if (ele.destEntity === node.entity && ele.deActions.includes(node.action)) {
return true;
}
// 有一种例外情况是在tree的根结点findActionAuthsOnNode时deduce出了另外一个对象的权限此时肯定可以通过但不能再使用这条路径对children进行进一步判断了
if (node === tree) {
return true;
}
return false;
}
);
const checkChildren = () => {
const { children } = node;
const childPath = Object.keys(children);
if (childPath.length === 0) {
return true;
}
const selfLegalPaths = realLegalPaths.filter(
(ele) => {
if (ele.destEntity === node.entity && ele.deActions.includes(node.action)) {
return true;
}
return false;
}
);
// assert(selfLegalPaths.length > 0, `对象${node.entity as string}的权限检查是用deduce的对象通过的无法再进一步对子对象加以判断`);
const childResult = childPath.map(
(childPath) => {
const child = children[childPath];
const childEntity = child instanceof Array ? child[0].entity : child.entity;
// 这里如果该子结点能deduce到父则直接通过
if (this.authDeduceRelationMap[childEntity]) {
assert(this.authDeduceRelationMap[childEntity] === 'entity');
const rel = judgeRelation(this.schema, childEntity, childPath);
if (rel === 2) {
return true;
}
}
const pathToParent = childPath.endsWith('$entity') ? node.entity as string : childPath.split('$')[1];
if (child instanceof Array) {
const childActions = child.map(ele => ele.action);
const childLegalAuths = selfLegalPaths.map(
(ele) => {
const { paths, relationId } = ele;
const paths2 = paths.map(
(path) => path ? `${pathToParent}.${path}` : pathToParent
);
return context.select('actionAuth', {
data: {
id: 1,
},
filter: {
paths: {
$overlaps: paths2,
},
destEntity: childEntity as string,
deActions: {
$overlaps: childActions,
},
relationId: relationId || {
$exists: false,
},
}
}, { dontCollect: true })
}
).flat() as ED['actionAuth']['Schema'][] | Promise<ED['actionAuth']['Schema']>[];
if (childLegalAuths[0] instanceof Promise) {
return Promise.all(childLegalAuths).then(
(clas) => child.map(
(c) => checkChildNode(clas, c)
)
)
}
return child.map(
(c) => checkChildNode(childLegalAuths as ED['actionAuth']['Schema'][], c)
);
}
const childLegalAuths = realLegalPaths.map(
(ele) => {
const { paths, relationId } = ele;
const paths2 = paths.map(
(path) => path ? `${pathToParent}.${path}` : pathToParent
);
return context.select('actionAuth', {
data: {
id: 1,
},
filter: {
paths: {
$overlaps: paths2,
},
destEntity: childEntity as string,
deActions: {
$overlaps: child.action,
},
relationId: relationId || {
$exists: false,
},
}
}, { dontCollect: true })
}
).flat() as ED['actionAuth']['Schema'][] | Promise<ED['actionAuth']['Schema']>[];
if (childLegalAuths[0] instanceof Promise) {
return Promise.all(childLegalAuths).then(
(clas) => checkChildNode(clas.flat(), child)
);
}
return checkChildNode(childLegalAuths as ED['actionAuth']['Schema'][], child);
}
).flat();
if (childResult[0] instanceof Promise) {
return Promise.all(childResult).then(
(r) => !r.includes(false)
);
}
return !childResult.includes(false);
};
if (RelationAuth.SPECIAL_ENTITIES.includes(node.entity as string)) {
// 特殊entity走特别的路径判断
const result = this.checkOperateSpecialEntities2(node.entity, node.action, node.filter, context);
if (result instanceof Promise) {
return result.then(
(r) => {
if (r) {
return checkChildren();
}
return false;
}
);
}
if (result) {
if (node.entity === 'user') {
// 如果当前是对user对象操作需要加上一个指向它自身的actionAuth否则剩下的子对象会判定不过
// user的操作权限由应用自己决定如果user的操作最终过不去这里放过也没关系
assert(node === tree && realLegalPaths.length === 0); // user不可能是非根结点
realLegalPaths.push({
id: 'temp',
paths: [''],
$$createAt$$: 1,
$$updateAt$$: 1,
$$seq$$: 'temp',
destEntity: 'user',
deActions: [node.action],
});
}
return checkChildren();
}
if (process.env.NODE_ENV === 'development') {
console.warn('对象operate权限检查不通过', node);
}
return false;
}
if (realLegalPaths.length === 0) {
if (node === tree) {
if (process.env.NODE_ENV === 'development') {
console.warn('对象operate权限检查不通过', node);
}
return false;
}
// 如果不是tree的根结点相对路径上的actionAuth找不到还可以尝试从自身的filter去重试其它路径
return this.checkOperationTree(node, context);
}
return checkChildren();
};
if (actionAuths instanceof Promise) {
return actionAuths.then(
(aars) => checkChildNodeInner(aars)
);
}
return checkChildNodeInner(actionAuths);
};
return checkChildNode(actionAuths2, tree);
} */
checkOperation(entity, operation, context) {
const { action, filter, data } = operation;
if (action === 'create' && this.createFreeEntities.includes(entity)) {
return true;
}
else if (action === 'update' && this.updateFreeEntities.includes(entity)) {
if (this.updateFreeDict[entity] && this.updateFreeDict[entity].includes(action)) {
return true;
}
const userId = context.getCurrentUserId();

View File

@ -185,11 +185,13 @@ export type Configuration = {
actionType?: ActionType;
static?: boolean;
};
export type AuthCascadePath<ED extends EntityDict> = [keyof ED, string, keyof ED, boolean];
export type AuthDeduceRelationMap<ED extends EntityDict> = {
[T in keyof ED]?: keyof ED[T]['OpSchema'];
};
export type SelectFreeEntities<ED extends EntityDict> = (keyof ED)[];
export type UpdateFreeDict<ED extends EntityDict> = {
[A in keyof ED]?: string[];
};
export type OtmKey<K extends string> = K | `${K}$${number}`;
export interface SubDataDef<ED extends EntityDict, T extends keyof ED> {
id: string;

View File

@ -3,7 +3,7 @@ import { AsyncContext } from '../store/AsyncRowStore';
import { createRemoveCheckers, createCreateCheckers } from '../store/checker';
import { createModiRelatedCheckers } from '../store/modi';
import { SyncContext } from '../store/SyncRowStore';
import { StorageSchema, EntityDict as BaseEntityDict, Checker, AuthCascadePath, CascadeRemoveDefDict } from '../types';
import { StorageSchema, EntityDict as BaseEntityDict, Checker } from '../types';
export function createDynamicCheckers<ED extends EntityDict & BaseEntityDict, Cxt extends AsyncContext<ED> | SyncContext<ED>>(schema: StorageSchema<ED>) {
const checkers: Checker<ED, keyof ED, Cxt>[] = [];

View File

@ -6236,6 +6236,10 @@ function analyzeInModi() {
}
/**
* 使
* @param map
*/
let IGNORED_FOREIGN_KEY_MAP: Record<string, string[]> = {};
let IGNORED_RELATION_PATH_MAP: Record<string, string[]> = {};
let DEDUCED_RELATION_MAP: Record<string, string> = {};
@ -6245,10 +6249,18 @@ let UPDATE_FREE_ENTITIES: string[] = [];
let FIXED_DESTINATION_PATH_MAP: Record<string, string[]> = {};
let FIXED_FOR_ALL_DESTINATION_PATH_ENTITIES: string[] = [];
/**
* 使
* @param map
*/
export function registerIgnoredForeignKeyMap(map: Record<string, string[]>) {
IGNORED_FOREIGN_KEY_MAP = map;
}
/**
* 使
* @param map
*/
export function registerFreeEntities(
selectFreeEntities: string[] = [],
createFreeEntities: string[] = [],
@ -6258,6 +6270,10 @@ export function registerFreeEntities(
UPDATE_FREE_ENTITIES = updateFreeEntities;
}
/**
* 使
* @param map
*/
export function registerIgnoredRelationPathMap(map: Record<string, string[]>) {
for (const k in map) {
IGNORED_RELATION_PATH_MAP[firstLetterUpperCase(k)] = map[k];
@ -6282,6 +6298,10 @@ export function registerFixedDestinationPathMap(map: Record<string, string[]>) {
}
}
/**
* 使
* @param map
*/
export function registerDeducedRelationMap(map: Record<string, string>) {
for (const k in map) {
const entity = firstLetterUpperCase(k);
@ -6303,6 +6323,7 @@ export function registerDeducedRelationMap(map: Record<string, string>) {
/**
* User相关的对象的后继
* 使
*/
function outputRelation(outputDir: string, printer: ts.Printer) {
const ExcludedEntities = ['Oper', 'User', 'OperEntity', 'Modi', 'ModiEntity', 'UserRelation', 'Relation', 'RelationAuth', 'ActionAuth'];
@ -6719,6 +6740,114 @@ function outputRelation(outputDir: string, printer: ts.Printer) {
writeFileSync(filename, result, { flag: 'w' });
}
/**
* oak-app-domain中的Relation.ts文件
* actionAuthGraph和relationAuthGraph两个复杂的对象
* @param outputDir
* @param printer
*/
function outputRelation2(outputDir: string, printer: ts.Printer) {
const entityRelations: [string, string[]][] = [];
for (const entity in Schema) {
const { hasRelationDef } = Schema[entity];
if (hasRelationDef) {
const { type } = hasRelationDef;
if (ts.isUnionTypeNode(type)) {
const { types } = type;
const relations = types.map(
ele => {
assert(ts.isLiteralTypeNode(ele) && ts.isStringLiteral(ele.literal));
return ele.literal.text;
}
);
entityRelations.push([firstLetterLowerCase(entity), relations]);
}
else {
assert(ts.isLiteralTypeNode(type));
assert(ts.isStringLiteral(type.literal));
const relations = [type.literal.text];
entityRelations.push([firstLetterLowerCase(entity), relations]);
}
}
}
const stmts: ts.Statement[] = [
factory.createImportDeclaration(
undefined,
factory.createImportClause(
false,
undefined,
factory.createNamedImports([factory.createImportSpecifier(
false,
undefined,
factory.createIdentifier("EntityDict")
)])
),
factory.createStringLiteral("./EntityDict"),
undefined
),
factory.createImportDeclaration(
undefined,
factory.createImportClause(
false,
undefined,
factory.createNamedImports([factory.createImportSpecifier(
false,
factory.createIdentifier("CreateOperationData"),
factory.createIdentifier("Relation")
)])
),
factory.createStringLiteral("./Relation/Schema"),
undefined
),
factory.createVariableStatement(
[factory.createToken(ts.SyntaxKind.ExportKeyword)],
factory.createVariableDeclarationList(
[factory.createVariableDeclaration(
factory.createIdentifier("relations"),
undefined,
factory.createArrayTypeNode(factory.createTypeReferenceNode(
factory.createIdentifier("Relation"),
undefined
)),
factory.createArrayLiteralExpression(
flatten(entityRelations.map(
([entity, relations]) => relations.map(
(relation) => factory.createObjectLiteralExpression(
[
factory.createPropertyAssignment(
factory.createIdentifier("id"),
factory.createStringLiteral(formUuid(entity, relation))
),
factory.createPropertyAssignment(
factory.createIdentifier("entity"),
factory.createStringLiteral(entity)
),
factory.createPropertyAssignment(
factory.createIdentifier("name"),
factory.createStringLiteral(relation)
)
],
true
)
)
)),
true
)
)],
ts.NodeFlags.Const
)
)
];
const result = printer.printList(
ts.ListFormat.SourceFileStatements,
factory.createNodeArray(stmts),
ts.createSourceFile("someFileName.ts", "", ts.ScriptTarget.Latest, /*setParentNodes*/ false, ts.ScriptKind.TS));
const filename = PathLib.join(outputDir, 'Relation.ts');
writeFileSync(filename, result, { flag: 'w' });
}
export function analyzeEntities(inputDir: string, relativePath?: string) {
const files = readdirSync(inputDir);
const fullFilenames = files.map(
@ -6755,7 +6884,7 @@ export function buildSchema(outputDir: string): void {
outputAction(outputDir, printer);
outputEntityDict(outputDir, printer);
outputStorage(outputDir, printer);
outputRelation(outputDir, printer);
outputRelation2(outputDir, printer);
outputIndexTs(outputDir);
if (!process.env.COMPLING_AS_LIB) {

View File

@ -1,5 +1,5 @@
export { storageSchema, selectFreeEntities } from './base-app-domain';
export { storageSchema } from './base-app-domain';
export type { EntityDict as BaseEntityDict } from './base-app-domain';

View File

@ -1,7 +1,7 @@
import assert from "assert";
import { EntityDict } from "../base-app-domain";
import { OakException, OakUniqueViolationException, OakUnloggedInException, OakUserInvisibleException, OakUserUnpermittedException, StorageSchema } from "../types";
import { AuthCascadePath, EntityDict as BaseEntityDict, AuthDeduceRelationMap } from "../types/Entity";
import { OakUnloggedInException, OakUserInvisibleException, OakUserUnpermittedException, StorageSchema } from "../types";
import { EntityDict as BaseEntityDict, AuthDeduceRelationMap } from "../types/Entity";
import { AsyncContext } from "./AsyncRowStore";
import { checkFilterContains, combineFilters } from "./filter";
import { judgeRelation } from "./relation";
@ -34,30 +34,25 @@ type CheckRelationResult = {
};
export class RelationAuth<ED extends EntityDict & BaseEntityDict>{
private actionCascadePathGraph: AuthCascadePath<ED>[];
private relationCascadePathGraph: AuthCascadePath<ED>[];
private authDeduceRelationMap: AuthDeduceRelationMap<ED>;
private schema: StorageSchema<ED>;
static SPECIAL_ENTITIES = SYSTEM_RESERVE_ENTITIES;
private selectFreeEntities: (keyof ED)[];
private createFreeEntities: (keyof ED)[];
private updateFreeEntities: (keyof ED)[];
private updateFreeDict: {
[A in keyof ED]?: string[];
};
constructor(schema: StorageSchema<ED>,
actionCascadePathGraph: AuthCascadePath<ED>[],
relationCascadePathGraph: AuthCascadePath<ED>[],
authDeduceRelationMap: AuthDeduceRelationMap<ED>,
selectFreeEntities?: (keyof ED)[],
createFreeEntities?: (keyof ED)[],
updateFreeEntities?: (keyof ED)[]) {
this.actionCascadePathGraph = actionCascadePathGraph;
this.relationCascadePathGraph = relationCascadePathGraph;
updateFreeDict?: {
[A in keyof ED]?: string[];
}) {
this.schema = schema;
this.selectFreeEntities = selectFreeEntities || [];
this.createFreeEntities = createFreeEntities || [];
this.updateFreeEntities = updateFreeEntities || [];
this.updateFreeDict = updateFreeDict || {};
this.authDeduceRelationMap = Object.assign({}, authDeduceRelationMap, {
modi: 'entity',
});
@ -77,55 +72,6 @@ export class RelationAuth<ED extends EntityDict & BaseEntityDict>{
this.checkActions2(entity, operation, context);
}
/**
* entity上可以操作的relationIds
* @param entity
* @param entityId
* @param context
* @returns
*/
private getGrantedRelationIds<Cxt extends AsyncContext<ED> | SyncContext<ED>>(entity: keyof ED, entityId: string, context: Cxt) {
const result = context.select('relationAuth', {
data: {
id: 1,
destRelationId: 1,
destRelation: {
id: 1,
name: 1,
entity: 1,
entityId: 1,
display: 1,
},
},
filter: {
sourceRelation: {
userRelation$relation: {
userId: context.getCurrentUserId(),
}
},
destRelation: {
entity: entity as string,
$or: [
{
entityId,
},
{
entityId: {
$exists: false,
},
}
],
},
},
}, {});
if (result instanceof Promise) {
return result.then(
(r2) => r2.map(ele => ele.destRelation!)
);
}
return result.map(ele => ele.destRelation!);
}
// 后台检查filter是否满足relation约束
async checkRelationAsync<T extends keyof ED, Cxt extends AsyncContext<ED>>(entity: T, operation: Omit<ED[T]['Operation'] | ED[T]['Selection'], 'id'>, context: Cxt) {
@ -190,11 +136,6 @@ export class RelationAuth<ED extends EntityDict & BaseEntityDict>{
const checkRelationAuth = (relationAuth: ED['relationAuth']['Schema']) => {
const { destRelation, sourceRelationId, path } = relationAuth;
if (action === 'create' && path === '') {
// 自己建立自己,一定可以通过
return 1;
}
const destEntityFilter: ED[keyof ED]['Selection']['filter'] = {};
if (path) {
@ -1266,198 +1207,6 @@ export class RelationAuth<ED extends EntityDict & BaseEntityDict>{
return checkNode(tree);
}
/* private checkOperationTree<Cxt extends AsyncContext<ED> | SyncContext<ED>>(tree: OperationTree<ED>, context: Cxt) {
const actionAuths2 = this.findActionAuthsOnNode(tree, context);
const checkChildNode = (actionAuths: ED['actionAuth']['Schema'][] | Promise<ED['actionAuth']['Schema'][]>, node: OperationTree<ED>): boolean | Promise<boolean> => {
const checkChildNodeInner = (legalAuths: ED['actionAuth']['Schema'][]) => {
// 因为如果children是数组的话会把数组中所有的action并起来查询所以在这里还要再确认一次
const realLegalPaths = legalAuths.filter(
(ele) => {
if (ele.destEntity === node.entity && ele.deActions.includes(node.action)) {
return true;
}
// 有一种例外情况是在tree的根结点findActionAuthsOnNode时deduce出了另外一个对象的权限此时肯定可以通过但不能再使用这条路径对children进行进一步判断了
if (node === tree) {
return true;
}
return false;
}
);
const checkChildren = () => {
const { children } = node;
const childPath = Object.keys(children);
if (childPath.length === 0) {
return true;
}
const selfLegalPaths = realLegalPaths.filter(
(ele) => {
if (ele.destEntity === node.entity && ele.deActions.includes(node.action)) {
return true;
}
return false;
}
);
// assert(selfLegalPaths.length > 0, `对象${node.entity as string}的权限检查是用deduce的对象通过的无法再进一步对子对象加以判断`);
const childResult = childPath.map(
(childPath) => {
const child = children[childPath];
const childEntity = child instanceof Array ? child[0].entity : child.entity;
// 这里如果该子结点能deduce到父则直接通过
if (this.authDeduceRelationMap[childEntity]) {
assert(this.authDeduceRelationMap[childEntity] === 'entity');
const rel = judgeRelation(this.schema, childEntity, childPath);
if (rel === 2) {
return true;
}
}
const pathToParent = childPath.endsWith('$entity') ? node.entity as string : childPath.split('$')[1];
if (child instanceof Array) {
const childActions = child.map(ele => ele.action);
const childLegalAuths = selfLegalPaths.map(
(ele) => {
const { paths, relationId } = ele;
const paths2 = paths.map(
(path) => path ? `${pathToParent}.${path}` : pathToParent
);
return context.select('actionAuth', {
data: {
id: 1,
},
filter: {
paths: {
$overlaps: paths2,
},
destEntity: childEntity as string,
deActions: {
$overlaps: childActions,
},
relationId: relationId || {
$exists: false,
},
}
}, { dontCollect: true })
}
).flat() as ED['actionAuth']['Schema'][] | Promise<ED['actionAuth']['Schema']>[];
if (childLegalAuths[0] instanceof Promise) {
return Promise.all(childLegalAuths).then(
(clas) => child.map(
(c) => checkChildNode(clas, c)
)
)
}
return child.map(
(c) => checkChildNode(childLegalAuths as ED['actionAuth']['Schema'][], c)
);
}
const childLegalAuths = realLegalPaths.map(
(ele) => {
const { paths, relationId } = ele;
const paths2 = paths.map(
(path) => path ? `${pathToParent}.${path}` : pathToParent
);
return context.select('actionAuth', {
data: {
id: 1,
},
filter: {
paths: {
$overlaps: paths2,
},
destEntity: childEntity as string,
deActions: {
$overlaps: child.action,
},
relationId: relationId || {
$exists: false,
},
}
}, { dontCollect: true })
}
).flat() as ED['actionAuth']['Schema'][] | Promise<ED['actionAuth']['Schema']>[];
if (childLegalAuths[0] instanceof Promise) {
return Promise.all(childLegalAuths).then(
(clas) => checkChildNode(clas.flat(), child)
);
}
return checkChildNode(childLegalAuths as ED['actionAuth']['Schema'][], child);
}
).flat();
if (childResult[0] instanceof Promise) {
return Promise.all(childResult).then(
(r) => !r.includes(false)
);
}
return !childResult.includes(false);
};
if (RelationAuth.SPECIAL_ENTITIES.includes(node.entity as string)) {
// 特殊entity走特别的路径判断
const result = this.checkOperateSpecialEntities2(node.entity, node.action, node.filter, context);
if (result instanceof Promise) {
return result.then(
(r) => {
if (r) {
return checkChildren();
}
return false;
}
);
}
if (result) {
if (node.entity === 'user') {
// 如果当前是对user对象操作需要加上一个指向它自身的actionAuth否则剩下的子对象会判定不过
// user的操作权限由应用自己决定如果user的操作最终过不去这里放过也没关系
assert(node === tree && realLegalPaths.length === 0); // user不可能是非根结点
realLegalPaths.push({
id: 'temp',
paths: [''],
$$createAt$$: 1,
$$updateAt$$: 1,
$$seq$$: 'temp',
destEntity: 'user',
deActions: [node.action],
});
}
return checkChildren();
}
if (process.env.NODE_ENV === 'development') {
console.warn('对象operate权限检查不通过', node);
}
return false;
}
if (realLegalPaths.length === 0) {
if (node === tree) {
if (process.env.NODE_ENV === 'development') {
console.warn('对象operate权限检查不通过', node);
}
return false;
}
// 如果不是tree的根结点相对路径上的actionAuth找不到还可以尝试从自身的filter去重试其它路径
return this.checkOperationTree(node, context);
}
return checkChildren();
};
if (actionAuths instanceof Promise) {
return actionAuths.then(
(aars) => checkChildNodeInner(aars)
);
}
return checkChildNodeInner(actionAuths);
};
return checkChildNode(actionAuths2, tree);
} */
private checkOperation<T extends keyof ED, Cxt extends AsyncContext<ED> | SyncContext<ED>>(
entity: T,
@ -1465,11 +1214,8 @@ export class RelationAuth<ED extends EntityDict & BaseEntityDict>{
context: Cxt,
) {
const { action, filter, data } = operation;
if (action === 'create' && this.createFreeEntities.includes(entity)) {
return true;
}
else if (action === 'update' && this.updateFreeEntities.includes(entity)) {
return true;
if (this.updateFreeDict[entity] && this.updateFreeDict[entity]!.includes(action)) {
return true;
}
const userId = context.getCurrentUserId();
if (!userId) {

View File

@ -262,11 +262,13 @@ export type Configuration = {
static?: boolean; // 标识是维表(变动较小,相对独立)
};
export type AuthCascadePath<ED extends EntityDict> = [keyof ED, string, keyof ED, boolean];
export type AuthDeduceRelationMap<ED extends EntityDict> = {
[T in keyof ED]?: keyof ED[T]['OpSchema'];
};
export type SelectFreeEntities<ED extends EntityDict> = (keyof ED)[];
export type UpdateFreeDict<ED extends EntityDict> = {
[A in keyof ED]?: string[];
};
// 一对多的键值的扩展
export type OtmKey<K extends string> = K | `${K}$${number}`;