Skip to content
Permalink
Signed-off-by: Zxilly <zhouxinyu1001@gmail.com>
4 contributors

Users who have contributed to this file

@nodece @hsluoyz @Zxilly @alexey-yunoshev
// 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.
import { RoleManager } from './roleManager';
import { getLogger, logPrint } from '../log';
export type MatchingFunc = (arg1: string, arg2: string) => boolean;
// 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<K, V>(map: Map<K, V>, key: K, value: V): V {
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 {
public name: string;
private roles: Role[];
constructor(name: string) {
this.name = name;
this.roles = [];
}
public addRole(role: Role): void {
if (this.roles.some((n) => n.name === role.name)) {
return;
}
this.roles.push(role);
}
public deleteRole(role: Role): void {
this.roles = this.roles.filter((n) => n.name !== role.name);
}
public hasRole(name: string, hierarchyLevel: number): boolean {
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;
}
public hasDirectRole(name: string): boolean {
return this.roles.some((n) => n.name === name);
}
public toString(): string {
return this.name + this.roles.join(', ');
}
public getRoles(): string[] {
return this.roles.map((n) => n.name);
}
}
class Roles extends Map<string, Role> {
constructor() {
super();
}
public hasRole(name: string, matchingFunc?: MatchingFunc): boolean {
let ok = false;
if (matchingFunc) {
this.forEach((value, key) => {
if (matchingFunc(name, key)) {
ok = true;
}
});
} else {
return this.has(name);
}
return ok;
}
public createRole(name: string, matchingFunc?: MatchingFunc): Role {
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
export class DefaultRoleManager implements RoleManager {
private allDomains: Map<string, Roles>;
private maxHierarchyLevel: number;
private hasPattern = false;
private hasDomainPattern = false;
private matchingFunc: MatchingFunc;
private domainMatchingFunc: MatchingFunc;
/**
* DefaultRoleManager is the constructor for creating an instance of the
* default RoleManager implementation.
*
* @param maxHierarchyLevel the maximized allowed RBAC hierarchy level.
*/
constructor(maxHierarchyLevel: number) {
this.allDomains = new Map<string, Roles>();
this.allDomains.set(DEFAULT_DOMAIN, new Roles());
this.maxHierarchyLevel = maxHierarchyLevel;
}
/**
* addMatchingFunc support use pattern in g
* @param name name
* @param fn matching function
* @deprecated
*/
public async addMatchingFunc(name: string, fn: MatchingFunc): Promise<void>;
/**
* addMatchingFunc support use pattern in g
* @param fn matching function
*/
public async addMatchingFunc(fn: MatchingFunc): Promise<void>;
/**
* addMatchingFunc support use pattern in g
* @param name name
* @param fn matching function
* @deprecated
*/
public async addMatchingFunc(name: string | MatchingFunc, fn?: MatchingFunc): Promise<void> {
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
* ```
*/
public async addDomainMatchingFunc(fn: MatchingFunc): Promise<void> {
this.hasDomainPattern = true;
this.domainMatchingFunc = fn;
}
private generateTempRoles(domain: string): Roles {
loadOrDefault(this.allDomains, domain, new Roles());
const patternDomain = new Set([domain]);
if (this.hasDomainPattern) {
this.allDomains.forEach((value, key) => {
if (this.domainMatchingFunc(domain, key)) {
patternDomain.add(key);
}
});
}
const allRoles = new Roles();
patternDomain.forEach((domain) => {
loadOrDefault(this.allDomains, domain, 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.
*/
public async addLink(name1: string, name2: string, ...domain: string[]): Promise<void> {
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.
*/
public async clear(): Promise<void> {
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.
*/
public async deleteLink(name1: string, name2: string, ...domain: string[]): Promise<void> {
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.
*/
public async hasLink(name1: string, name2: string, ...domain: string[]): Promise<boolean> {
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;
}
let allRoles: Roles;
if (this.hasPattern || this.hasDomainPattern) {
allRoles = this.generateTempRoles(domain[0]);
} else {
allRoles = loadOrDefault(this.allDomains, domain[0], new Roles());
}
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);
}
/**
* getRoles gets the roles that a subject inherits.
* domain is a prefix to the roles.
*/
public async getRoles(name: string, ...domain: string[]): Promise<string[]> {
if (domain.length === 0) {
domain = [DEFAULT_DOMAIN];
} else if (domain.length > 1) {
throw new Error('error: domain should be 1 parameter');
}
let allRoles: Roles;
if (this.hasPattern || this.hasDomainPattern) {
allRoles = this.generateTempRoles(domain[0]);
} else {
allRoles = loadOrDefault(this.allDomains, domain[0], new Roles());
}
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.
*/
public async getUsers(name: string, ...domain: string[]): Promise<string[]> {
if (domain.length === 0) {
domain = [DEFAULT_DOMAIN];
} else if (domain.length > 1) {
throw new Error('error: domain should be 1 parameter');
}
let allRoles: Roles;
if (this.hasPattern || this.hasDomainPattern) {
allRoles = this.generateTempRoles(domain[0]);
} else {
allRoles = loadOrDefault(this.allDomains, domain[0], new Roles());
}
if (!allRoles.hasRole(name, this.matchingFunc)) {
return [];
}
return [...allRoles.values()].filter((n) => n.hasDirectRole(name)).map((n) => n.name);
}
/**
* printRoles prints all the roles to log.
*/
public async printRoles(): Promise<void> {
if (getLogger().isEnable()) {
[...this.allDomains.values()].forEach((n) => {
logPrint(n.toString());
});
}
}
}