UNPKG

casbin

Version:

An authorization library that supports access control models like ACL, RBAC, ABAC in Node.JS

588 lines (587 loc) 20.9 kB
"use strict"; // Copyright 2018 The Casbin Authors. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. 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.newModelFromString = exports.newModelFromFile = exports.newModel = exports.Model = exports.requiredSections = exports.PolicyOp = exports.sectionNameMap = void 0; const util = __importStar(require("../util")); const config_1 = require("../config"); const assertion_1 = require("./assertion"); const log_1 = require("../log"); const rbac_1 = require("../rbac"); const defaultDomain = ''; const defaultSeparator = '::'; exports.sectionNameMap = { r: 'request_definition', p: 'policy_definition', g: 'role_definition', e: 'policy_effect', m: 'matchers', }; var PolicyOp; (function (PolicyOp) { PolicyOp[PolicyOp["PolicyAdd"] = 0] = "PolicyAdd"; PolicyOp[PolicyOp["PolicyRemove"] = 1] = "PolicyRemove"; })(PolicyOp = exports.PolicyOp || (exports.PolicyOp = {})); exports.requiredSections = ['r', 'p', 'e', 'm']; class Model { /** * constructor is the constructor for Model. */ constructor() { this.model = new Map(); } loadAssertion(cfg, sec, key) { const secName = exports.sectionNameMap[sec]; const value = cfg.getString(`${secName}::${key}`); return this.addDef(sec, key, value); } getKeySuffix(i) { if (i === 1) { return ''; } return i.toString(); } loadSection(cfg, sec) { let i = 1; for (;;) { if (!this.loadAssertion(cfg, sec, sec + this.getKeySuffix(i))) { break; } else { i++; } } } // addDef adds an assertion to the model. addDef(sec, key, value) { if (value === '') { return false; } const ast = new assertion_1.Assertion(); ast.key = key; ast.value = value; ast.fieldIndexMap = new Map(); if (sec === 'r' || sec === 'p') { const tokens = value.split(',').map((n) => n.trim()); for (let i = 0; i < tokens.length; i++) { tokens[i] = key + '_' + tokens[i]; } ast.tokens = tokens; } else if (sec === 'm') { const stringArguments = value.match(/\"(.*?)\"/g) || []; stringArguments.forEach((n, index) => { value = value.replace(n, `$<${index}>`); }); value = util.escapeAssertion(value); stringArguments.forEach((n, index) => { value = value.replace(`$<${index}>`, n); }); const invalidOperators = /(?<![&|])&(?!&)|(?<![&|])\|(?!\|)|&{3,}|\|{3,}/g; if (invalidOperators.test(value)) { throw new Error(`Invalid operator in matcher`); } ast.value = value; } else { ast.value = util.escapeAssertion(value); } const nodeMap = this.model.get(sec); if (nodeMap) { nodeMap.set(key, ast); } else { const assertionMap = new Map(); assertionMap.set(key, ast); this.model.set(sec, assertionMap); } return true; } /** * loadModel loads the model from model CONF file. * @param path the model file path * @param fs {@link FileSystem} * @deprecated {@link loadModelFromFile} */ loadModel(path, fs) { this.loadModelFromFile(path, fs); } /** * loadModelFromFile loads the model from model CONF file. * @param path the model file path * @param fs {@link FileSystem} */ loadModelFromFile(path, fs) { const cfg = config_1.Config.newConfigFromFile(path, fs); this.loadModelFromConfig(cfg); } // loadModelFromText loads the model from the text. loadModelFromText(text) { const cfg = config_1.Config.newConfigFromText(text); this.loadModelFromConfig(cfg); } loadModelFromConfig(cfg) { for (const s in exports.sectionNameMap) { this.loadSection(cfg, s); } const ms = []; exports.requiredSections.forEach((n) => { if (!this.hasSection(n)) { ms.push(exports.sectionNameMap[n]); } }); if (ms.length > 0) { throw new Error(`missing required sections: ${ms.join(',')}`); } } hasSection(sec) { return this.model.get(sec) !== undefined; } // printModel prints the model to the log. printModel() { (0, log_1.logPrint)('Model:'); this.model.forEach((value, key) => { value.forEach((ast, astKey) => { (0, log_1.logPrint)(`${key}.${astKey}: ${ast.value}`); }); }); } // buildIncrementalRoleLinks provides incremental build the role inheritance relations. async buildIncrementalRoleLinks(rm, op, sec, ptype, rules) { var _a, _b; if (sec === 'g') { await ((_b = (_a = this.model.get(sec)) === null || _a === void 0 ? void 0 : _a.get(ptype)) === null || _b === void 0 ? void 0 : _b.buildIncrementalRoleLinks(rm, op, rules)); } } // buildRoleLinks initializes the roles in RBAC. async buildRoleLinks(rmMap) { const astMap = this.model.get('g'); if (!astMap) { return; } for (const key of astMap.keys()) { const ast = astMap.get(key); let rm = rmMap.get(key); if (!rm) { rm = new rbac_1.DefaultRoleManager(10); rmMap.set(key, rm); } await (ast === null || ast === void 0 ? void 0 : ast.buildRoleLinks(rm)); } } // clearPolicy clears all current policy. clearPolicy() { this.model.forEach((value, key) => { if (key === 'p' || key === 'g') { value.forEach((ast) => { ast.policy = []; }); } }); } // getPolicy gets all rules in a policy. getPolicy(sec, key) { var _a; const policy = []; const ast = (_a = this.model.get(sec)) === null || _a === void 0 ? void 0 : _a.get(key); if (ast) { policy.push(...ast.policy); } return policy; } // hasPolicy determines whether a model has the specified policy rule. hasPolicy(sec, key, rule) { var _a; const ast = (_a = this.model.get(sec)) === null || _a === void 0 ? void 0 : _a.get(key); if (!ast) { return false; } return ast.policy.some((n) => util.arrayEquals(n, rule)); } // addPolicy adds a policy rule to the model. addPolicy(sec, key, rule) { var _a; if (!this.hasPolicy(sec, key, rule)) { const ast = (_a = this.model.get(sec)) === null || _a === void 0 ? void 0 : _a.get(key); if (!ast) { return false; } const policy = ast.policy; const tokens = ast.tokens; const priorityIndex = tokens.indexOf(`${key}_priority`); if (priorityIndex !== -1) { const priorityRule = rule[priorityIndex]; const insertIndex = policy.findIndex((oneRule) => oneRule[priorityIndex] >= priorityRule); if (priorityIndex === -1) { policy.push(rule); } else { policy.splice(insertIndex, 0, rule); } } else { policy.push(rule); } return true; } return false; } // addPolicies adds policy rules to the model. addPolicies(sec, ptype, rules) { var _a; const ast = (_a = this.model.get(sec)) === null || _a === void 0 ? void 0 : _a.get(ptype); if (!ast) { return [false, []]; } for (const rule of rules) { if (this.hasPolicy(sec, ptype, rule)) { return [false, []]; } } const priorityFlag = ast.tokens.indexOf(`${ptype}_priority`) !== -1; if (priorityFlag) { rules.forEach((rule) => { this.addPolicy(sec, ptype, rule); }); } else { ast.policy = ast.policy.concat(rules); } return [true, rules]; } // updatePolicy updates a policy from the model updatePolicy(sec, ptype, oldRule, newRule) { var _a; const ast = (_a = this.model.get(sec)) === null || _a === void 0 ? void 0 : _a.get(ptype); if (!ast) { return false; } const index = ast.policy.findIndex((r) => util.arrayEquals(r, oldRule)); if (index === -1) { return false; } const priorityIndex = ast.tokens.indexOf(`${ptype}_priority`); if (priorityIndex !== -1) { if (oldRule[priorityIndex] === newRule[priorityIndex]) { ast.policy[index] = newRule; } else { // this.removePolicy(sec, ptype, oldRule); // this.addPolicy(sec, ptype, newRule); throw new Error('new rule should have the same priority with old rule.'); } } else { ast.policy[index] = newRule; } return true; } // removePolicy removes a policy rule from the model. removePolicy(sec, key, rule) { var _a; if (this.hasPolicy(sec, key, rule)) { const ast = (_a = this.model.get(sec)) === null || _a === void 0 ? void 0 : _a.get(key); if (!ast) { return false; } ast.policy = ast.policy.filter((r) => !util.arrayEquals(rule, r)); return true; } return false; } // removePolicies removes policy rules from the model. removePolicies(sec, ptype, rules) { var _a; const effects = []; const ast = (_a = this.model.get(sec)) === null || _a === void 0 ? void 0 : _a.get(ptype); if (!ast) { return [false, []]; } for (const rule of rules) { if (!this.hasPolicy(sec, ptype, rule)) { return [false, []]; } } for (const rule of rules) { ast.policy = ast.policy.filter((r) => { const equals = util.arrayEquals(rule, r); if (equals) { effects.push(r); } return !equals; }); } return [true, effects]; } // getFilteredPolicy gets rules based on field filters from a policy. getFilteredPolicy(sec, key, fieldIndex, ...fieldValues) { var _a; const res = []; const ast = (_a = this.model.get(sec)) === null || _a === void 0 ? void 0 : _a.get(key); if (!ast) { return res; } for (const rule of ast.policy) { let matched = true; for (let i = 0; i < fieldValues.length; i++) { const fieldValue = fieldValues[i]; if (fieldValue !== '' && rule[fieldIndex + i] !== fieldValue) { matched = false; break; } } if (matched) { res.push(rule); } } return res; } // removeFilteredPolicy removes policy rules based on field filters from the model. removeFilteredPolicy(sec, key, fieldIndex, ...fieldValues) { var _a; const res = []; const effects = []; let bool = false; if (fieldValues.length === 0) { return [false, effects]; } const ast = (_a = this.model.get(sec)) === null || _a === void 0 ? void 0 : _a.get(key); if (!ast) { return [false, []]; } for (const rule of ast.policy) { let matched = true; for (let i = 0; i < fieldValues.length; i++) { const fieldValue = fieldValues[i]; if (fieldValue !== '' && rule[fieldIndex + i] !== fieldValue) { matched = false; break; } } if (matched) { bool = true; effects.push(rule); } else { res.push(rule); } } if (effects.length !== 0) { ast.policy = res; } return [bool, effects]; } // getValuesForFieldInPolicy gets all values for a field for all rules in a policy, duplicated values are removed. getValuesForFieldInPolicy(sec, key, fieldIndex) { var _a; const values = []; const ast = (_a = this.model.get(sec)) === null || _a === void 0 ? void 0 : _a.get(key); if (!ast) { return values; } return util.arrayRemoveDuplicates(ast.policy.map((n) => n[fieldIndex])); } // getValuesForFieldInPolicyAllTypes gets all values for a field for all rules in a policy of all ptypes, duplicated values are removed. getValuesForFieldInPolicyAllTypes(sec, fieldIndex) { const values = []; const ast = this.model.get(sec); if (!ast) { return values; } for (const ptype of ast.keys()) { values.push(...this.getValuesForFieldInPolicy(sec, ptype, fieldIndex)); } return util.arrayRemoveDuplicates(values); } // printPolicy prints the policy to log. printPolicy() { if (!(0, log_1.getLogger)().isEnable()) { return; } (0, log_1.logPrint)('Policy:'); this.model.forEach((map, key) => { if (key === 'p' || key === 'g') { map.forEach((ast) => { (0, log_1.logPrint)(`key, : ${ast.value}, : , ${ast.policy}`); }); } }); } /** * return the field index in fieldMap, if no this field in fieldMap, add it. */ getFieldIndex(ptype, field) { var _a; const assertion = (_a = this.model.get('p')) === null || _a === void 0 ? void 0 : _a.get(ptype); if (!assertion) { return -1; } let index = assertion.fieldIndexMap.get(field); if (index) { return index; } const pattern = ptype + '_' + field; index = -1; for (let i = 0; i < assertion.tokens.length; i++) { if (assertion.tokens[i] === pattern) { index = i; break; } } if (index === -1) { return index; } assertion.fieldIndexMap.set(field, index); return index; } /** * sort policies by subject hieraichy */ sortPoliciesBySubjectHierarchy() { var _a, _b, _c; if (((_b = (_a = this.model.get('e')) === null || _a === void 0 ? void 0 : _a.get('e')) === null || _b === void 0 ? void 0 : _b.value) !== "subjectPriority(p_eft) || deny" /* EffectExpress.SUBJECT_PRIORITY */) { return; } (_c = this.model.get('p')) === null || _c === void 0 ? void 0 : _c.forEach((assertion, ptype) => { const domainIndex = this.getFieldIndex(ptype, "dom" /* FieldIndex.Domain */); const subIndex = this.getFieldIndex(ptype, "sub" /* FieldIndex.Subject */); // eslint-disable-next-line const subjectHierarchyMap = this.getSubjectHierarchyMap(this.model.get('g').get('g').policy); assertion.policy.sort((policyA, policyB) => { const domainA = domainIndex === -1 ? defaultDomain : policyA[domainIndex]; const domainB = domainIndex === -1 ? defaultDomain : policyB[domainIndex]; // eslint-disable-next-line const priorityA = subjectHierarchyMap.get(this.getNameWithDomain(domainA, policyA[subIndex])); // eslint-disable-next-line const priorityB = subjectHierarchyMap.get(this.getNameWithDomain(domainB, policyB[subIndex])); return priorityB - priorityA; }); }); } /** * Calculate the priority of each policy store in Map<string, number> */ getSubjectHierarchyMap(groupPolicies) { const subjectHierarchyMap = new Map(); if (!groupPolicies) { return subjectHierarchyMap; } const policyMap = new Map(); let domain = defaultDomain; groupPolicies.forEach((policy) => { if (policy.length !== 2) domain = policy[this.getFieldIndex('p', "dom" /* FieldIndex.Domain */)]; const child = this.getNameWithDomain(domain, policy[this.getFieldIndex('p', "sub" /* FieldIndex.Subject */)]); const parent = this.getNameWithDomain(domain, policy[this.getFieldIndex('p', "obj" /* FieldIndex.Object */)]); policyMap.set(child, parent); if (!subjectHierarchyMap.has(child)) { subjectHierarchyMap.set(child, 0); } if (!subjectHierarchyMap.has(parent)) { subjectHierarchyMap.set(parent, 0); } subjectHierarchyMap.set(child, 1); }); const set = new Set(); subjectHierarchyMap.forEach((_, key) => { if (subjectHierarchyMap.get(key) !== 0) set.add(key); }); while (set.size !== 0) { for (const child of set.values()) { this.findHierarchy(policyMap, subjectHierarchyMap, set, child); } } return subjectHierarchyMap; } findHierarchy(policyMap, subjectHierarchyMap, set, child) { set.delete(child); // eslint-disable-next-line const parent = policyMap.get(child); if (set.has(parent)) { this.findHierarchy(policyMap, subjectHierarchyMap, set, parent); } // eslint-disable-next-line subjectHierarchyMap.set(child, subjectHierarchyMap.get(parent) + 10); } /** * get full name with domain */ getNameWithDomain(domain, name) { return domain + defaultSeparator + name; } } exports.Model = Model; /** * newModel creates a model. */ function newModel(...text) { const m = new Model(); if (text.length === 2) { if (text[0] !== '') { m.loadModelFromFile(text[0]); } } else if (text.length === 1) { m.loadModelFromText(text[0]); } else if (text.length !== 0) { throw new Error('Invalid parameters for model.'); } return m; } exports.newModel = newModel; /** * newModelFromFile creates a model from a .CONF file. */ function newModelFromFile(path, fs) { const m = new Model(); if (path) { m.loadModelFromFile(path, fs); } return m; } exports.newModelFromFile = newModelFromFile; /** * newModelFromString creates a model from a string which contains model text. */ function newModelFromString(text) { const m = new Model(); m.loadModelFromText(text); return m; } exports.newModelFromString = newModelFromString;