casbin
Version:
An authorization library that supports access control models like ACL, RBAC, ABAC in Node.JS
367 lines (366 loc) • 12.9 kB
JavaScript
"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 __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
const expression_eval_1 = require("expression-eval");
const _ = require("lodash");
const effect_1 = require("./effect");
const model_1 = require("./model");
const rbac_1 = require("./rbac");
const util_1 = require("./util");
const casbin_1 = require("./casbin");
const log_1 = require("./log");
/**
* CoreEnforcer defines the core functionality of an enforcer.
*/
class CoreEnforcer {
constructor() {
// @ts-ignore
this.adapter = null;
// @ts-ignore
this.watcher = null;
}
initialize() {
this.rm = new rbac_1.DefaultRoleManager(10);
this.eft = new effect_1.DefaultEffector();
// @ts-ignore
this.watcher = null;
this.enabled = true;
this.autoSave = true;
this.autoBuildRoleLinks = true;
}
/**
* loadModel reloads the model from the model CONF file.
* Because the policy is attached to a model,
* so the policy is invalidated and needs to be reloaded by calling LoadPolicy().
*/
loadModel() {
this.model = casbin_1.newModel();
this.model.loadModel(this.modelPath);
this.model.printModel();
this.fm = model_1.FunctionMap.loadFunctionMap();
}
/**
* getModel gets the current model.
*
* @return the model of the enforcer.
*/
getModel() {
return this.model;
}
/**
* setModel sets the current model.
*
* @param m the model.
*/
setModel(m) {
this.model = m;
this.fm = model_1.FunctionMap.loadFunctionMap();
}
/**
* getAdapter gets the current adapter.
*
* @return the adapter of the enforcer.
*/
getAdapter() {
return this.adapter;
}
/**
* setAdapter sets the current adapter.
*
* @param adapter the adapter.
*/
setAdapter(adapter) {
this.adapter = adapter;
}
/**
* setWatcher sets the current watcher.
*
* @param watcher the watcher.
*/
setWatcher(watcher) {
this.watcher = watcher;
watcher.setUpdateCallback(() => __awaiter(this, void 0, void 0, function* () { return yield this.loadPolicy(); }));
}
/**
* setRoleManager sets the current role manager.
*
* @param rm the role manager.
*/
setRoleManager(rm) {
this.rm = rm;
}
/**
* setEffector sets the current effector.
*
* @param eft the effector.
*/
setEffector(eft) {
this.eft = eft;
}
/**
* clearPolicy clears all policy.
*/
clearPolicy() {
this.model.clearPolicy();
}
/**
* loadPolicy reloads the policy from file/database.
*/
loadPolicy() {
return __awaiter(this, void 0, void 0, function* () {
this.model.clearPolicy();
yield this.adapter.loadPolicy(this.model);
this.model.printPolicy();
if (this.autoBuildRoleLinks) {
this.buildRoleLinks();
}
});
}
/**
* loadFilteredPolicy reloads a filtered policy from file/database.
*
* @param filter the filter used to specify which type of policy should be loaded.
*/
loadFilteredPolicy(filter) {
return __awaiter(this, void 0, void 0, function* () {
this.model.clearPolicy();
if (this.adapter.isFiltered) {
yield this.adapter.loadFilteredPolicy(this.model, filter);
}
else {
throw new Error('filtered policies are not supported by this adapter');
}
this.model.printPolicy();
if (this.autoBuildRoleLinks) {
this.buildRoleLinks();
}
return true;
});
}
/**
* isFiltered returns true if the loaded policy has been filtered.
*
* @return if the loaded policy has been filtered.
*/
isFiltered() {
if (this.adapter.isFiltered) {
return this.adapter.isFiltered();
}
return false;
}
/**
* savePolicy saves the current policy (usually after changed with
* Casbin API) back to file/database.
*/
savePolicy() {
return __awaiter(this, void 0, void 0, function* () {
if (this.isFiltered()) {
throw new Error('cannot save a filtered policy');
}
const flag = yield this.adapter.savePolicy(this.model);
if (!flag) {
return false;
}
if (this.watcher) {
return yield this.watcher.update();
}
return true;
});
}
/**
* enableEnforce changes the enforcing state of Casbin, when Casbin is
* disabled, all access will be allowed by the enforce() function.
*
* @param enable whether to enable the enforcer.
*/
enableEnforce(enable) {
this.enabled = enable;
}
/**
* enableLog changes whether to print Casbin log to the standard output.
*
* @param enable whether to enable Casbin's log.
*/
enableLog(enable) {
log_1.getLogger().enableLog(enable);
}
/**
* enableAutoSave controls whether to save a policy rule automatically to
* the adapter when it is added or removed.
*
* @param autoSave whether to enable the AutoSave feature.
*/
enableAutoSave(autoSave) {
this.autoSave = autoSave;
}
/**
* enableAutoBuildRoleLinks controls whether to save a policy rule
* automatically to the adapter when it is added or removed.
*
* @param autoBuildRoleLinks whether to automatically build the role links.
*/
enableAutoBuildRoleLinks(autoBuildRoleLinks) {
this.autoBuildRoleLinks = autoBuildRoleLinks;
}
/**
* buildRoleLinks manually rebuild the
* role inheritance relations.
*/
buildRoleLinks() {
// error intentionally ignored
this.rm.clear();
this.model.buildRoleLinks(this.rm);
}
/**
* enforce decides whether a "subject" can access a "object" with
* the operation "action", input parameters are usually: (sub, obj, act).
*
* @param rvals the request needs to be mediated, usually an array
* of strings, can be class instances if ABAC is used.
* @return whether to allow the request.
*/
enforce(...rvals) {
if (!this.enabled) {
return true;
}
const functions = {};
this.fm.getFunctions().forEach((value, key) => {
functions[key] = value;
});
const astMap = this.model.model.get('g');
if (astMap) {
astMap.forEach((value, key) => {
const rm = value.rm;
functions[key] = util_1.generateGFunction(rm);
});
}
// @ts-ignore
const expString = this.model.model.get('m').get('m').value;
if (!expString) {
throw new Error('model is undefined');
}
const expression = expression_eval_1.compile(expString);
let policyEffects;
let matcherResults;
// @ts-ignore
const policyLen = this.model.model.get('p').get('p').policy.length;
if (policyLen !== 0) {
policyEffects = new Array(policyLen);
matcherResults = new Array(policyLen);
for (let i = 0; i < policyLen; i++) {
// @ts-ignore
const pvals = this.model.model.get('p').get('p').policy[i];
// logPrint('Policy Rule: ', pvals);
const parameters = {};
// @ts-ignore
this.model.model.get('r').get('r').tokens.forEach((token, j) => {
parameters[token] = rvals[j];
});
// @ts-ignore
this.model.model.get('p').get('p').tokens.forEach((token, j) => {
parameters[token] = pvals[j];
});
const result = expression(Object.assign({}, parameters, functions));
switch (typeof result) {
case 'boolean':
if (!result) {
policyEffects[i] = effect_1.Effect.Indeterminate;
continue;
}
break;
case 'number':
if (result === 0) {
policyEffects[i] = effect_1.Effect.Indeterminate;
continue;
}
else {
matcherResults[i] = result;
}
break;
default:
throw new Error('matcher result should be boolean or number');
}
if (_.has(parameters, 'p_eft')) {
const eft = _.get(parameters, 'p_eft');
if (eft === 'allow') {
policyEffects[i] = effect_1.Effect.Allow;
}
else if (eft === 'deny') {
policyEffects[i] = effect_1.Effect.Deny;
}
else {
policyEffects[i] = effect_1.Effect.Indeterminate;
}
}
else {
policyEffects[i] = effect_1.Effect.Allow;
}
// @ts-ignore
if (this.model.model.get('e').get('e').value === 'priority(p_eft) || deny') {
break;
}
}
}
else {
policyEffects = new Array(1);
matcherResults = new Array(1);
const parameters = {};
// @ts-ignore
this.model.model.get('r').get('r').tokens.forEach((token, j) => {
parameters[token] = rvals[j];
});
// @ts-ignore
this.model.model.get('p').get('p').tokens.forEach((token) => {
parameters[token] = '';
});
const result = expression(Object.assign({}, parameters, functions));
// logPrint(`Result: ${result}`);
if (result) {
policyEffects[0] = effect_1.Effect.Allow;
}
else {
policyEffects[0] = effect_1.Effect.Indeterminate;
}
}
// logPrint(`Rule Results: ${policyEffects}`);
// @ts-ignore
const res = this.eft.mergeEffects(this.model.model.get('e').get('e').value, policyEffects, matcherResults);
// only generate the request --> result string if the message
// is going to be logged.
if (log_1.getLogger().isEnable()) {
let reqStr = 'Request: ';
for (let i = 0; i < rvals.length; i++) {
if (i !== rvals.length - 1) {
reqStr += `${rvals[i]}, `;
}
else {
reqStr += rvals[i];
}
}
reqStr += ` ---> ${res}`;
log_1.logPrint(reqStr);
}
return res;
}
}
exports.CoreEnforcer = CoreEnforcer;