@backstage-community/plugin-rbac-backend
Version:
582 lines (576 loc) • 19 kB
JavaScript
'use strict';
var casbin = require('casbin');
var EventEmitter = require('events');
var adminCreation = require('../admin-permissions/admin-creation.cjs.js');
var helper = require('../helper.cjs.js');
var permissionModel = require('./permission-model.cjs.js');
var auditor = require('../auditor/auditor.cjs.js');
function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e : { default: e }; }
var EventEmitter__default = /*#__PURE__*/_interopDefaultCompat(EventEmitter);
class EnforcerDelegate {
// Queue to track edit operations
constructor(enforcer, auditor, conditionalStorage, roleMetadataStorage, knex) {
this.enforcer = enforcer;
this.auditor = auditor;
this.conditionalStorage = conditionalStorage;
this.roleMetadataStorage = roleMetadataStorage;
this.knex = knex;
}
roleEventEmitter = new EventEmitter__default.default();
loadPolicyPromise = null;
editOperationsQueue = [];
async loadPolicy() {
if (this.loadPolicyPromise) {
return this.loadPolicyPromise;
}
this.loadPolicyPromise = (async () => {
try {
await this.waitForEditOperationsToFinish();
await this.enforcer.loadPolicy();
} catch (error) {
const auditorEvent = await this.auditor.createEvent({
eventId: auditor.PoliciesData.PERMISSIONS_READ,
severityLevel: "medium"
});
await auditorEvent.fail({ error });
throw error;
} finally {
this.loadPolicyPromise = null;
}
})();
return this.loadPolicyPromise;
}
async waitForEditOperationsToFinish() {
await Promise.all(this.editOperationsQueue);
}
async execOperation(operation) {
this.editOperationsQueue.push(operation);
let result;
try {
result = await operation;
} catch (err) {
throw err;
} finally {
const index = this.editOperationsQueue.indexOf(operation);
if (index !== -1) {
this.editOperationsQueue.splice(index, 1);
}
}
return result;
}
on(event, listener) {
this.roleEventEmitter.on(event, listener);
return this;
}
async hasPolicy(...policy) {
const tempModel = casbin.newModelFromString(permissionModel.MODEL);
await this.enforcer.getAdapter().loadFilteredPolicy(
tempModel,
[
{
ptype: "p",
v0: policy[0],
v1: policy[1],
v2: policy[2],
v3: policy[3]
}
]
);
return tempModel.hasPolicy("p", "p", policy);
}
async hasGroupingPolicy(...policy) {
const tempModel = casbin.newModelFromString(permissionModel.MODEL);
await this.enforcer.getAdapter().loadFilteredPolicy(
tempModel,
[
{
ptype: "g",
v0: policy[0],
v1: policy[1]
}
]
);
return tempModel.hasPolicy("g", "g", policy);
}
async getPolicy() {
const tempModel = casbin.newModelFromString(permissionModel.MODEL);
await this.enforcer.getAdapter().loadFilteredPolicy(
tempModel,
[{ ptype: "p" }]
);
return await tempModel.getPolicy("p", "p");
}
async getGroupingPolicy() {
const tempModel = casbin.newModelFromString(permissionModel.MODEL);
await this.enforcer.getAdapter().loadFilteredPolicy(
tempModel,
[{ ptype: "g" }]
);
return await tempModel.getPolicy("g", "g");
}
async getRolesForUser(userEntityRef) {
return await this.enforcer.getRolesForUser(userEntityRef);
}
async getFilteredPolicy(fieldIndex, ...filter) {
const tempModel = casbin.newModelFromString(permissionModel.MODEL);
const filterObj = { ptype: "p" };
for (let i = 0; i < filter.length; i++) {
if (filter[i]) {
filterObj[`v${i + fieldIndex}`] = filter[i];
}
}
await this.enforcer.getAdapter().loadFilteredPolicy(
tempModel,
[filterObj]
);
return await tempModel.getPolicy("p", "p");
}
async getFilteredGroupingPolicy(fieldIndex, ...filter) {
const tempModel = casbin.newModelFromString(permissionModel.MODEL);
const filterObj = { ptype: "g" };
for (let i = 0; i < filter.length; i++) {
if (filter[i]) {
filterObj[`v${i + fieldIndex}`] = filter[i];
}
}
await this.enforcer.getAdapter().loadFilteredPolicy(
tempModel,
[filterObj]
);
return await tempModel.getPolicy("g", "g");
}
async addPolicy(policy, externalTrx) {
const trx = externalTrx ?? await this.knex.transaction();
if (await this.hasPolicy(...policy)) {
return;
}
try {
const ok = await this.enforcer.addPolicy(...policy);
if (!ok) {
throw new Error(`failed to create policy ${helper.policyToString(policy)}`);
}
if (!externalTrx) {
await trx.commit();
}
} catch (err) {
if (!externalTrx) {
await trx.rollback(err);
}
throw err;
}
}
async addPolicies(policies, externalTrx) {
if (this.loadPolicyPromise) {
await this.loadPolicyPromise;
} else {
await this.loadPolicy();
}
const addPoliciesOperation = (async () => {
if (policies.length === 0) {
return;
}
const trx = externalTrx || await this.knex.transaction();
try {
const ok = await this.enforcer.addPolicies(policies);
if (!ok) {
throw new Error(
`Failed to store policies ${helper.policiesToString(policies)}`
);
}
if (!externalTrx) {
await trx.commit();
}
} catch (err) {
if (!externalTrx) {
await trx.rollback(err);
}
throw err;
}
})();
await this.execOperation(addPoliciesOperation);
}
async addGroupingPolicy(policy, roleMetadata, externalTrx) {
if (this.loadPolicyPromise) {
await this.loadPolicyPromise;
} else {
await this.loadPolicy();
}
const addGroupingPolicyOperation = (async () => {
const trx = externalTrx ?? await this.knex.transaction();
const entityRef = roleMetadata.roleEntityRef;
if (await this.hasGroupingPolicy(...policy)) {
return;
}
try {
let currentMetadata;
if (entityRef.startsWith(`role:`)) {
currentMetadata = await this.roleMetadataStorage.findRoleMetadata(
entityRef,
trx
);
}
if (currentMetadata) {
await this.roleMetadataStorage.updateRoleMetadata(
helper.mergeRoleMetadata(currentMetadata, roleMetadata),
entityRef,
trx
);
} else {
const currentDate = /* @__PURE__ */ new Date();
roleMetadata.createdAt = currentDate.toUTCString();
roleMetadata.lastModified = currentDate.toUTCString();
await this.roleMetadataStorage.createRoleMetadata(roleMetadata, trx);
}
const ok = await this.enforcer.addGroupingPolicy(...policy);
if (!ok) {
throw new Error(`failed to create policy ${helper.policyToString(policy)}`);
}
if (!externalTrx) {
await trx.commit();
}
if (!currentMetadata) {
this.roleEventEmitter.emit("roleAdded", roleMetadata.roleEntityRef);
}
} catch (err) {
if (!externalTrx) {
await trx.rollback(err);
}
throw err;
}
})();
await this.execOperation(addGroupingPolicyOperation);
}
async addGroupingPolicies(policies, roleMetadata, oldRoleEntityRef, externalTrx) {
if (this.loadPolicyPromise) {
await this.loadPolicyPromise;
} else {
await this.loadPolicy();
}
const addGroupingPoliciesOperation = (async () => {
if (policies.length === 0) {
return;
}
const trx = externalTrx ?? await this.knex.transaction();
try {
const currentRoleMetadata = await this.roleMetadataStorage.findRoleMetadata(
oldRoleEntityRef ?? roleMetadata.roleEntityRef,
trx
);
if (currentRoleMetadata) {
await this.roleMetadataStorage.updateRoleMetadata(
helper.mergeRoleMetadata(currentRoleMetadata, roleMetadata),
oldRoleEntityRef ?? roleMetadata.roleEntityRef,
trx
);
} else {
const currentDate = /* @__PURE__ */ new Date();
roleMetadata.createdAt = currentDate.toUTCString();
roleMetadata.lastModified = currentDate.toUTCString();
await this.roleMetadataStorage.createRoleMetadata(roleMetadata, trx);
}
const ok = await this.enforcer.addGroupingPolicies(policies);
if (!ok) {
throw new Error(
`Failed to store policies ${helper.policiesToString(policies)}`
);
}
if (!externalTrx) {
await trx.commit();
}
if (!currentRoleMetadata) {
this.roleEventEmitter.emit("roleAdded", roleMetadata.roleEntityRef);
}
} catch (err) {
if (!externalTrx) {
await trx.rollback(err);
}
throw err;
}
})();
await this.execOperation(addGroupingPoliciesOperation);
}
async updateGroupingPolicies(oldRole, newRole, newRoleMetadata) {
const oldRoleName = oldRole.at(0)?.at(1);
const trx = await this.knex.transaction();
try {
const currentMetadata = await this.roleMetadataStorage.findRoleMetadata(
oldRoleName,
trx
);
if (!currentMetadata) {
throw new Error(`Role metadata ${oldRoleName} was not found`);
}
await this.removeGroupingPolicies(oldRole, currentMetadata, true, trx);
await this.addGroupingPolicies(
newRole,
newRoleMetadata,
currentMetadata.roleEntityRef,
trx
);
if (newRoleMetadata.roleEntityRef !== currentMetadata.roleEntityRef) {
const oldPolicies = await this.enforcer.getFilteredPolicy(
0,
currentMetadata.roleEntityRef
);
const updatedPolicies = oldPolicies.map((oldPolicy) => [
newRoleMetadata.roleEntityRef,
...oldPolicy.slice(1)
]);
await this.updatePolicies(oldPolicies, updatedPolicies, trx);
const oldConditions = await this.conditionalStorage.filterConditions(
currentMetadata.roleEntityRef,
void 0,
void 0,
void 0,
void 0,
trx
);
for (const condition of oldConditions) {
await this.conditionalStorage.updateCondition(
condition.id,
{
...condition,
roleEntityRef: newRoleMetadata.roleEntityRef
},
trx
);
}
}
await trx.commit();
} catch (err) {
await trx.rollback(err);
throw err;
}
}
async updatePolicies(oldPolicies, newPolicies, externalTrx) {
const trx = externalTrx ?? await this.knex.transaction();
try {
await this.removePolicies(oldPolicies, trx);
await this.addPolicies(newPolicies, trx);
if (!externalTrx) {
await trx.commit();
}
} catch (err) {
if (!externalTrx) {
await trx.rollback(err);
}
throw err;
}
}
async removePolicy(policy, externalTrx) {
if (this.loadPolicyPromise) {
await this.loadPolicyPromise;
} else {
await this.loadPolicy();
}
const removePolicyOperation = (async () => {
const trx = externalTrx ?? await this.knex.transaction();
try {
const ok = await this.enforcer.removePolicy(...policy);
if (!ok) {
throw new Error(`fail to delete policy ${policy}`);
}
if (!externalTrx) {
await trx.commit();
}
} catch (err) {
if (!externalTrx) {
await trx.rollback(err);
}
throw err;
}
})();
await this.execOperation(removePolicyOperation);
}
async removePolicies(policies, externalTrx) {
if (this.loadPolicyPromise) {
await this.loadPolicyPromise;
} else {
await this.loadPolicy();
}
const removePoliciesOperation = (async () => {
const trx = externalTrx ?? await this.knex.transaction();
try {
const ok = await this.enforcer.removePolicies(policies);
if (!ok) {
throw new Error(
`Failed to delete policies ${helper.policiesToString(policies)}`
);
}
if (!externalTrx) {
await trx.commit();
}
} catch (err) {
if (!externalTrx) {
await trx.rollback(err);
}
throw err;
}
})();
await this.execOperation(removePoliciesOperation);
}
async removeGroupingPolicy(policy, roleMetadata, isUpdate, externalTrx) {
if (this.loadPolicyPromise) {
await this.loadPolicyPromise;
} else {
await this.loadPolicy();
}
const removeGroupingPolicyOperation = (async () => {
const trx = externalTrx ?? await this.knex.transaction();
const roleEntity = policy[1];
try {
const ok = await this.enforcer.removeGroupingPolicy(...policy);
if (!ok) {
throw new Error(`Failed to delete policy ${helper.policyToString(policy)}`);
}
if (!isUpdate) {
const currentRoleMetadata = await this.roleMetadataStorage.findRoleMetadata(roleEntity, trx);
const remainingGroupPolicies = await this.getFilteredGroupingPolicy(
1,
roleEntity
);
if (currentRoleMetadata && remainingGroupPolicies.length === 0 && roleEntity !== adminCreation.ADMIN_ROLE_NAME) {
await this.roleMetadataStorage.removeRoleMetadata(roleEntity, trx);
} else if (currentRoleMetadata) {
await this.roleMetadataStorage.updateRoleMetadata(
helper.mergeRoleMetadata(currentRoleMetadata, roleMetadata),
roleEntity,
trx
);
}
}
if (!externalTrx) {
await trx.commit();
}
} catch (err) {
if (!externalTrx) {
await trx.rollback(err);
}
throw err;
}
})();
await this.execOperation(removeGroupingPolicyOperation);
}
async removeGroupingPolicies(policies, roleMetadata, isUpdate, externalTrx) {
if (this.loadPolicyPromise) {
await this.loadPolicyPromise;
} else {
await this.loadPolicy();
}
const removeGroupingPolicyOperation = (async () => {
const trx = externalTrx ?? await this.knex.transaction();
const roleEntity = roleMetadata.roleEntityRef;
try {
const ok = await this.enforcer.removeGroupingPolicies(policies);
if (!ok) {
throw new Error(
`Failed to delete grouping policies: ${helper.policiesToString(policies)}`
);
}
if (!isUpdate) {
const currentRoleMetadata = await this.roleMetadataStorage.findRoleMetadata(roleEntity, trx);
const remainingGroupPolicies = await this.getFilteredGroupingPolicy(
1,
roleEntity
);
if (currentRoleMetadata && remainingGroupPolicies.length === 0 && roleEntity !== adminCreation.ADMIN_ROLE_NAME) {
await this.roleMetadataStorage.removeRoleMetadata(roleEntity, trx);
} else if (currentRoleMetadata) {
await this.roleMetadataStorage.updateRoleMetadata(
helper.mergeRoleMetadata(currentRoleMetadata, roleMetadata),
roleEntity,
trx
);
}
}
if (!externalTrx) {
await trx.commit();
}
} catch (err) {
if (!externalTrx) {
await trx.rollback(err);
}
throw err;
}
})();
await this.execOperation(removeGroupingPolicyOperation);
}
/**
* enforce aims to enforce a particular permission policy based on the user that it receives.
* Under the hood, enforce uses the `enforce` method from the enforcer`.
*
* Before enforcement, a filter is set up to reduce the number of permission policies that will
* be loaded in.
* This will reduce the amount of checks that need to be made to determine if a user is authorize
* to perform an action
*
* A temporary enforcer will also be used while enforcing.
* This is to ensure that the filter does not interact with the base enforcer.
* The temporary enforcer has lazy loading of the permission policies enabled to reduce the amount
* of time it takes to initialize the temporary enforcer.
* The justification for lazy loading is because permission policies are already present in the
* role manager / database and it will be filtered and loaded whenever `getFilteredPolicy` is called
* and permissions / roles are applied to the temp enforcer
* @param entityRef The user to enforce
* @param resourceType The resource type / name of the permission policy
* @param action The action of the permission policy
* @param roles Any roles that the user is directly or indirectly attached to.
* Used for filtering permission policies.
* @returns True if the user is allowed based on the particular permission
*/
async enforce(entityRef, resourceType, action, roles) {
const model = casbin.newModelFromString(permissionModel.MODEL);
let policies = [];
if (roles.length > 0) {
for (const role of roles) {
const filteredPolicy = await this.getFilteredPolicy(
0,
role,
resourceType,
action
);
policies.push(...filteredPolicy);
}
} else {
const enforcePolicies = await this.getFilteredPolicy(
1,
resourceType,
action
);
policies = enforcePolicies.filter(
(policy) => policy[0].startsWith("user:") || policy[0].startsWith("group:")
);
}
const roleManager = this.enforcer.getRoleManager();
const tempEnforcer = new casbin.Enforcer();
model.addPolicies("p", "p", policies);
await tempEnforcer.initWithModelAndAdapter(model);
tempEnforcer.setRoleManager(roleManager);
await tempEnforcer.buildRoleLinks();
return await tempEnforcer.enforce(entityRef, resourceType, action);
}
async getImplicitPermissionsForUser(user) {
if (this.loadPolicyPromise) {
await this.loadPolicyPromise;
} else {
await this.loadPolicy();
}
const getPermissionsForUserOperation = (async () => {
return this.enforcer.getImplicitPermissionsForUser(user);
})();
return await this.execOperation(getPermissionsForUserOperation);
}
async getAllRoles() {
if (this.loadPolicyPromise) {
await this.loadPolicyPromise;
} else {
await this.loadPolicy();
}
const getRolesOperation = (async () => {
return this.enforcer.getAllRoles();
})();
return await this.execOperation(getRolesOperation);
}
}
exports.EnforcerDelegate = EnforcerDelegate;
//# sourceMappingURL=enforcer-delegate.cjs.js.map