UNPKG

@backstage-community/plugin-rbac-backend

Version:
581 lines (575 loc) 19 kB
'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 }); } 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