UNPKG

database-proxy

Version:

Through a set of access control rules configuration database access to realize the client directly access the database via HTTP.

348 lines (347 loc) 12.8 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.Policy = exports.PermissionType = exports.PermissionTypeV1 = void 0; const assert = __importStar(require("assert")); const types_1 = require("../types"); const processor_1 = require("../processor"); const BUILT_IN_VALIDATORS = __importStar(require("../validators")); const logger_1 = require("../logger"); /** * 访问规则结构: * DatabaseRule: * -> CollectionRule: * -> read: PermissionRule[] * -> add: PermissionRule[] * -> update: PermissionRule[] * -> remove: PermissionRule[] * -> count: PermissionRule[] * -> watch: PermissionRule[] */ var PermissionTypeV1; (function (PermissionTypeV1) { PermissionTypeV1["READ"] = ".read"; PermissionTypeV1["UPDATE"] = ".update"; PermissionTypeV1["ADD"] = ".add"; PermissionTypeV1["REMOVE"] = ".remove"; PermissionTypeV1["COUNT"] = ".count"; })(PermissionTypeV1 || (exports.PermissionTypeV1 = PermissionTypeV1 = {})); var PermissionType; (function (PermissionType) { PermissionType["READ"] = "read"; PermissionType["UPDATE"] = "update"; PermissionType["ADD"] = "add"; PermissionType["REMOVE"] = "remove"; PermissionType["COUNT"] = "count"; PermissionType["WATCH"] = "watch"; PermissionType["AGGREGATE"] = "aggregate"; })(PermissionType || (exports.PermissionType = PermissionType = {})); class Policy { get logger() { if (!this._logger) { this._logger = new logger_1.DefaultLogger(0); } return this._logger; } setLogger(logger) { this._logger = logger; } get accessor() { return this._accessor; } setAccessor(accessor) { this._accessor = accessor; } get collections() { if (!this.rules) return []; return Object.keys(this.rules); } constructor(accessor) { this.version = 2; this._accessor = accessor; this.validators = {}; this.rules = {}; this.loadBuiltins(); } /** * 加载 rules in json * @param rules any * @returns */ load(rules) { this.logger.debug(`load rules: `, JSON.stringify(rules)); assert.equal(typeof rules, 'object', "invalid 'rules'"); // 处理每张数据库表的访问规则 for (const collection in rules) { this.add(collection, rules[collection]); } this.logger.info(`all rules loaded`); return true; } /** * 添加一个集合的访问规则,同 {set()},但当集合已存在时,则添加失败 * @param collection 集合名称 * @param rules 集合的访问规则,是一个对象, like { "read": {...}, 'update': {...} } */ add(collection, rules) { if (this.collections.includes(collection)) { throw new Error(`add collection rules failed: ${collection} already exists`); } this.set(collection, rules); } /** * 设置一个集合的访问规则,若集合规则已存在,则替换其规则 * @param collection 集合名称 * @param rules 集合的访问规则,是一个对象, like { "read": {...}, 'update': {...} } */ set(collection, rules) { this.logger.info(`set collection rules: ${collection}...`); rules = this.convertPermissionConfig(rules); // 集合权限,是一个对象, like { "read": {...}, 'update': {...} } const collectionRule = {}; // 处理每种权限规则, like ['read', 'update' ...] const perm_types = Object.keys(rules); for (const ptype of perm_types) { // skip non-permisstion-type item, like '$schema' if (ptype === '$schema') { continue; } // 权限对应的验证器配置, like { 'condition': true, 'data': {...} } const permissionConfig = rules[ptype]; const permissionConfigArr = this.wrapRawPermissionRuleToArray(permissionConfig); // add schema config if ADD or UPDATE if ([PermissionType.ADD, PermissionType.UPDATE].includes(ptype)) { permissionConfigArr.forEach((pmc) => { pmc['schema'] = rules['$schema']; }); } // instantiate validators collectionRule[ptype] = this.instantiateValidators(permissionConfigArr); } this.rules[collection] = collectionRule; } /** * 转换 v1 版本的权限名到 v2 * example: * ".read" -> "read" * ".update" -> ".update" * ... * @param rules * @returns */ convertPermissionConfig(rules) { const newRules = {}; for (const key in rules) { let type = key; switch (key) { case PermissionTypeV1.READ: type = PermissionType.READ; break; case PermissionTypeV1.UPDATE: type = PermissionType.UPDATE; break; case PermissionTypeV1.ADD: type = PermissionType.ADD; break; case PermissionTypeV1.COUNT: type = PermissionType.COUNT; break; case PermissionTypeV1.REMOVE: type = PermissionType.REMOVE; break; } newRules[type] = rules[key]; } return newRules; } /** * normalize:将输入规则格式转为内部统一形式,即对象数组 * 1. boolean -> [{ condition: "bool string"}] * 2. string -> [{ condition: "expression string" }] * 3. object -> [ object ] * 4. array -> array * @param permissionRules * @returns */ wrapRawPermissionRuleToArray(permissionRules) { assert.notEqual(permissionRules, undefined, 'permissionRules is undefined'); let rules = permissionRules; // 权限规则为布尔时,默认使用 condition 验证器 if ([true, false].includes(rules)) { rules = [{ condition: `${rules}` }]; } // 权限规则为字符串时,默认使用 condition 验证器 if (typeof rules === 'string') rules = [{ condition: rules }]; // 权限规则不为数组时,转为数组 if (!(rules instanceof Array)) rules = [rules]; return rules; } /** * 实例化验证器 * @param permissionRules 权限规则 */ instantiateValidators(rules) { const result = rules.map((raw_rule) => { const prule = {}; // 检查用户配置的验证器是否已注册 for (const vname in raw_rule) { const handler = this.validators[vname]; if (!handler) { throw new Error(`unknown validator '${vname}' in your rules`); } } // 逐一实例化验证器 for (const vname in this.validators) { const handler = this.validators[vname]; // 如果用户并未配置此验证器,则其配置缺省为 undefined,验证器实现时需处理缺省情况 const config = raw_rule[vname]; prule[vname] = new processor_1.Processor(vname, handler, config); } return prule; }); return result; } /** * 验证访问规则 * @param params * @param injections */ async validate(params, injections) { const { collection, action: actionType } = params; this.logger.debug(`ruler validate with injections: `, injections); const errors = []; // 判断所访问的集合是否配置规则 if (!this.collections.includes(collection)) { this.logger.debug(`validate() ${collection} not in rules`); const err = { type: 0, error: `collection "${collection}" not found`, }; errors.push(err); return { errors }; } // action 是否合法 const action = (0, types_1.getAction)(actionType); if (!action) { const err = { type: 0, error: `action "${actionType}" invalid`, }; errors.push(err); return { errors }; } const permName = this.getPermissionName(action.type); const permRules = this.rules[collection][permName]; // if no permission rules if (!permRules) { const err = { type: 0, error: `${collection} ${actionType} don't has any rules`, }; errors.push(err); return { errors }; } this.logger.trace(`${actionType} -> ${collection} permission rules: `, permRules); // loop for validating every permission rule let matched = null; const context = { ruler: this, params, injections }; for (const validtrs of permRules) { let error = null; // 执行一条规则的所有验证器 for (const vname in validtrs) { const result = await validtrs[vname].run(context); // 任一验证器执行不通过,则跳过本条规则 if (result) { error = { type: vname, error: result }; break; } } if (error) errors.push(error); // 本条规则验证通过 if (!error) { matched = validtrs; break; } } // return error if no permission rule matched if (!matched) { this.logger.debug(`validate rejected: ${actionType} -> ${collection} `); this.logger.trace(`validate errors: `, errors); return { errors }; } this.logger.debug(`validate passed: ${actionType} -> ${collection} `); this.logger.trace(`matched: `, matched); return { matched }; } /** * 注册验证器 * @param name * @param handler */ register(name, handler) { assert.ok(name, `register error: name must not be empty`); assert.ok(handler instanceof Function, `${name} register error: 'handler' must be a callable function`); const exists = Object.keys(this.validators).filter((vn) => vn === name); assert.ok(!exists.length, `validator's name: '${name}' duplicated`); this.validators[name] = handler; } /** * 加载内置验证器 */ loadBuiltins() { for (const name in BUILT_IN_VALIDATORS) { const handler = BUILT_IN_VALIDATORS[name]; this.register(name, handler); } } /** * 获取指定 ActionType 对应的权限名 * @param action ActionType * @returns */ getPermissionName(action) { switch (action) { case types_1.ActionType.ADD: return PermissionType.ADD; case types_1.ActionType.READ: return PermissionType.READ; case types_1.ActionType.AGGREGATE: return PermissionType.AGGREGATE; case types_1.ActionType.UPDATE: return PermissionType.UPDATE; case types_1.ActionType.REMOVE: return PermissionType.REMOVE; case types_1.ActionType.COUNT: return PermissionType.COUNT; default: throw new Error('getPermissionName() unknown action'); } } } exports.Policy = Policy;