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
JavaScript
;
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;