casbin
Version:
An authorization library that supports access control models like ACL, RBAC, ABAC in Node.JS
588 lines (587 loc) • 20.9 kB
JavaScript
;
// 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;