casbin
Version:
An authorization library that supports access control models like ACL, RBAC, ABAC in Node.JS
341 lines (340 loc) • 11.7 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.
Object.defineProperty(exports, "__esModule", { value: true });
exports.DefaultRoleManager = void 0;
const log_1 = require("../log");
// DEFAULT_DOMAIN defines the default domain space.
const DEFAULT_DOMAIN = 'casbin::default';
// loadOrDefault returns the existing value for the key if present.
// Otherwise, it stores and returns the given value.
function loadOrDefault(map, key, value) {
const read = map.get(key);
if (read === undefined) {
map.set(key, value);
return value;
}
return read;
}
/**
* Role represents the data structure for a role in RBAC.
*/
class Role {
constructor(name) {
this.name = name;
this.roles = [];
}
addRole(role) {
if (this.roles.some((n) => n.name === role.name)) {
return;
}
this.roles.push(role);
}
deleteRole(role) {
this.roles = this.roles.filter((n) => n.name !== role.name);
}
hasRole(name, hierarchyLevel) {
if (this.name === name) {
return true;
}
if (hierarchyLevel <= 0) {
return false;
}
for (const role of this.roles) {
if (role.hasRole(name, hierarchyLevel - 1)) {
return true;
}
}
return false;
}
hasDirectRole(name) {
return this.roles.some((n) => n.name === name);
}
toString() {
return this.name + this.roles.join(', ');
}
getRoles() {
return this.roles.map((n) => n.name);
}
}
class Roles extends Map {
constructor() {
super();
}
hasRole(name, matchingFunc) {
let ok = false;
if (matchingFunc) {
this.forEach((value, key) => {
if (matchingFunc(name, key)) {
ok = true;
}
});
}
else {
return this.has(name);
}
return ok;
}
createRole(name, matchingFunc) {
const role = loadOrDefault(this, name, new Role(name));
if (matchingFunc) {
this.forEach((value, key) => {
if (matchingFunc(name, key) && name !== key) {
// Add new role to matching role
const role1 = loadOrDefault(this, key, new Role(key));
role.addRole(role1);
}
});
}
return role;
}
}
// RoleManager provides a default implementation for the RoleManager interface
class DefaultRoleManager {
/**
* DefaultRoleManager is the constructor for creating an instance of the
* default RoleManager implementation.
*
* @param maxHierarchyLevel the maximized allowed RBAC hierarchy level.
*/
constructor(maxHierarchyLevel) {
this.hasPattern = false;
this.hasDomainPattern = false;
this.hasDomainHierarchy = false;
this.allDomains = new Map();
this.allDomains.set(DEFAULT_DOMAIN, new Roles());
this.maxHierarchyLevel = maxHierarchyLevel;
}
/**
* addMatchingFunc support use pattern in g
* @param name name
* @param fn matching function
* @deprecated
*/
async addMatchingFunc(name, fn) {
this.hasPattern = true;
if (typeof name === 'string' && fn) {
this.matchingFunc = fn;
}
else if (typeof name === 'function') {
this.matchingFunc = name;
}
else {
throw new Error('error: domain should be 1 parameter');
}
}
/**
* addDomainMatchingFunc support use domain pattern in g
* @param fn domain matching function
* ```
*/
async addDomainMatchingFunc(fn) {
this.hasDomainPattern = true;
this.domainMatchingFunc = fn;
}
/**
* addDomainHierarchy sets a rolemanager to define role inheritance between domains
* @param rm RoleManager to define domain hierarchy
*/
async addDomainHierarchy(rm) {
if (!(rm === null || rm === void 0 ? void 0 : rm.syncedHasLink))
throw Error('Domain hierarchy must be syncronous.');
this.hasDomainHierarchy = true;
this.domainHierarchyManager = rm;
}
generateTempRoles(domain) {
if (!this.hasPattern && !this.hasDomainPattern && !this.hasDomainHierarchy) {
return loadOrDefault(this.allDomains, domain, new Roles());
}
const extraDomain = new Set([domain]);
if (this.hasDomainPattern || this.hasDomainHierarchy) {
this.allDomains.forEach((value, key) => {
var _a;
if ((this.hasDomainPattern && this.domainMatchingFunc(domain, key)) ||
(((_a = this.domainHierarchyManager) === null || _a === void 0 ? void 0 : _a.syncedHasLink) && this.domainHierarchyManager.syncedHasLink(key, domain))) {
extraDomain.add(key);
}
});
}
const allRoles = new Roles();
extraDomain.forEach((dom) => {
loadOrDefault(this.allDomains, dom, new Roles()).forEach((value, key) => {
const role1 = allRoles.createRole(value.name, this.matchingFunc);
value.getRoles().forEach((n) => {
role1.addRole(allRoles.createRole(n, this.matchingFunc));
});
});
});
return allRoles;
}
/**
* addLink adds the inheritance link between role: name1 and role: name2.
* aka role: name1 inherits role: name2.
* domain is a prefix to the roles.
*/
async addLink(name1, name2, ...domain) {
if (domain.length === 0) {
domain = [DEFAULT_DOMAIN];
}
else if (domain.length > 1) {
throw new Error('error: domain should be 1 parameter');
}
const allRoles = loadOrDefault(this.allDomains, domain[0], new Roles());
const role1 = loadOrDefault(allRoles, name1, new Role(name1));
const role2 = loadOrDefault(allRoles, name2, new Role(name2));
role1.addRole(role2);
}
/**
* clear clears all stored data and resets the role manager to the initial state.
*/
async clear() {
this.allDomains = new Map();
this.allDomains.set(DEFAULT_DOMAIN, new Roles());
}
/**
* deleteLink deletes the inheritance link between role: name1 and role: name2.
* aka role: name1 does not inherit role: name2 any more.
* domain is a prefix to the roles.
*/
async deleteLink(name1, name2, ...domain) {
if (domain.length === 0) {
domain = [DEFAULT_DOMAIN];
}
else if (domain.length > 1) {
throw new Error('error: domain should be 1 parameter');
}
const allRoles = loadOrDefault(this.allDomains, domain[0], new Roles());
if (!allRoles.has(name1) || !allRoles.has(name2)) {
return;
}
const role1 = loadOrDefault(allRoles, name1, new Role(name1));
const role2 = loadOrDefault(allRoles, name2, new Role(name2));
role1.deleteRole(role2);
}
/**
* hasLink determines whether role: name1 inherits role: name2.
* domain is a prefix to the roles.
*/
syncedHasLink(name1, name2, ...domain) {
if (domain.length === 0) {
domain = [DEFAULT_DOMAIN];
}
else if (domain.length > 1) {
throw new Error('error: domain should be 1 parameter');
}
if (name1 === name2) {
return true;
}
const allRoles = this.generateTempRoles(domain[0]);
if (!allRoles.hasRole(name1, this.matchingFunc) || !allRoles.hasRole(name2, this.matchingFunc)) {
return false;
}
const role1 = allRoles.createRole(name1, this.matchingFunc);
return role1.hasRole(name2, this.maxHierarchyLevel);
}
async hasLink(name1, name2, ...domain) {
return new Promise((resolve) => resolve(this.syncedHasLink(name1, name2, ...domain)));
}
/**
* getRoles gets the roles that a subject inherits.
* domain is a prefix to the roles.
*/
async getRoles(name, ...domain) {
if (domain.length === 0) {
domain = [DEFAULT_DOMAIN];
}
else if (domain.length > 1) {
throw new Error('error: domain should be 1 parameter');
}
const allRoles = this.generateTempRoles(domain[0]);
if (!allRoles.hasRole(name, this.matchingFunc)) {
return [];
}
return allRoles.createRole(name, this.matchingFunc).getRoles();
}
/**
* getUsers gets the users that inherits a subject.
* domain is an unreferenced parameter here, may be used in other implementations.
*/
async getUsers(name, ...domain) {
if (domain.length === 0) {
domain = [DEFAULT_DOMAIN];
}
else if (domain.length > 1) {
throw new Error('error: domain should be 1 parameter');
}
const allRoles = this.generateTempRoles(domain[0]);
if (!allRoles.hasRole(name, this.matchingFunc)) {
return [];
}
const users = [];
for (const user of allRoles.values()) {
if (user.hasDirectRole(name))
users.push(user.name);
}
return users;
}
/**
* printRoles prints all the roles to log.
*/
async printRoles() {
if ((0, log_1.getLogger)().isEnable()) {
[...this.allDomains.values()].forEach((n) => {
(0, log_1.logPrint)(n.toString());
});
}
}
/**
* getDomains gets domains that a user has.
*/
async getDomains(name) {
const domains = [];
this.allDomains.forEach((roles, domain) => {
// Skip the default domain if there are other domains
if (domain === DEFAULT_DOMAIN && this.allDomains.size > 1) {
return;
}
const role = roles.get(name);
if (role) {
// Check if role has any roles it inherits OR if any other role inherits from it
const hasRoles = role.getRoles().length > 0;
const hasUsers = this.hasUserForRole(roles, name);
if (hasRoles || hasUsers) {
domains.push(domain);
}
}
});
return domains;
}
/**
* getAllDomains gets all domains.
*/
async getAllDomains() {
const domains = Array.from(this.allDomains.keys());
// Filter out the default domain if there are other domains
if (domains.length > 1) {
return domains.filter((d) => d !== DEFAULT_DOMAIN);
}
return domains;
}
hasUserForRole(roles, name) {
for (const role of roles.values()) {
if (role.hasDirectRole(name)) {
return true;
}
}
return false;
}
}
exports.DefaultRoleManager = DefaultRoleManager;