UNPKG

@backstage-community/plugin-rbac-backend

Version:
336 lines (332 loc) 11.7 kB
'use strict'; var lodash = require('lodash'); var auditor = require('./auditor/auditor.cjs.js'); function policyToString(policy) { return `[${policy.join(", ")}]`; } function typedPolicyToString(policy, type) { return `${type}, ${policy.join(", ")}`; } function policiesToString(policies) { const policiesString = policies.map((policy) => policyToString(policy)).join(","); return `[${policiesString}]`; } function typedPoliciesToString(policies, type) { const policiesString = policies.map((policy) => { return policy.length !== 0 ? typedPolicyToString(policy, type) : ""; }).join("\n"); return ` ${policiesString} `; } function metadataStringToPolicy(policy) { return policy.replace("[", "").replace("]", "").split(", "); } function policyArraysEqual(a, b) { return a.length === b.length && a.every((v, i) => v === b[i]); } function serializeErrorForLog(error) { return { name: error.name, message: error.message, stack: error.stack }; } function toError(value) { return value instanceof Error ? value : new Error(String(value)); } function diffConditionalPolicies(stored, desired, equals) { const toAdd = desired.filter( (desiredItem) => !stored.some((storedItem) => equals(storedItem, desiredItem)) ); const toRemove = stored.filter( (storedItem) => !desired.some((desiredItem) => equals(storedItem, desiredItem)) ); return { toAdd, toRemove }; } function permissionMappingToActions(mapping) { return mapping.map( (entry) => typeof entry === "string" ? entry : entry.action ); } function sameConditionalResource(a, b) { return a.roleEntityRef === b.roleEntityRef && a.pluginId === b.pluginId && a.resourceType === b.resourceType; } function conditionalActionsOverlap(actionsA, actionsB) { return actionsA.some((action) => actionsB.includes(action)); } function planConditionalReconcile(toAdd, toRemove, getActions) { const matchedRemoveIds = /* @__PURE__ */ new Set(); const updates = []; const creates = []; for (const desired of toAdd) { const stored = toRemove.find( (item) => item.id !== void 0 && !matchedRemoveIds.has(item.id) && sameConditionalResource(item, desired) && conditionalActionsOverlap(getActions(item), getActions(desired)) ); if (stored?.id !== void 0) { matchedRemoveIds.add(stored.id); updates.push({ stored, desired }); } else { creates.push(desired); } } const deletes = toRemove.filter( (item) => item.id === void 0 || !matchedRemoveIds.has(item.id) ); return { updates, creates, deletes }; } function pendingDeleteIdsFromPlan(plan) { const ids = plan.deletes.map((item) => item.id).filter((id) => id !== void 0); return new Set(ids); } async function abortConditionalPolicyReconcile(context) { const { logger, auditor: auditor$1, source, abortEventId = auditor.ConditionEvents.CONDITIONAL_POLICIES_FILE_CHANGE, pendingAdds, pendingRemoves, pluginIds, error } = context; const abortMeta = { source, pendingAdds, pendingRemoves, pluginIds, ...abortEventId === auditor.ConditionEvents.CONDITION_WRITE ? { actionType: auditor.ActionType.RECONCILE_ABORT } : {} }; logger.error( "Conditional policy reconcile aborted; stored conditions preserved. Retry reconciliation once the underlying staging or persistence failure is resolved.", { event: "conditional_reconcile_aborted", ...abortMeta, error: serializeErrorForLog(error) } ); const auditorEvent = await auditor$1.createEvent({ eventId: abortEventId, severityLevel: "medium", meta: abortMeta }); await auditorEvent.fail({ error, meta: abortMeta }); } async function syncRolePolicies(enforcerDelegate, roleEntityRef, desiredPolicies) { const current = await enforcerDelegate.getFilteredPolicy(0, roleEntityRef); const toAdd = desiredPolicies.filter( (d) => !current.some((c) => policyArraysEqual(c, d)) ); const toRemove = current.filter( (c) => !desiredPolicies.some((d) => policyArraysEqual(c, d)) ); if (toAdd.length > 0) { await enforcerDelegate.addPolicies(toAdd); } if (toRemove.length > 0) { await enforcerDelegate.removePolicies(toRemove); } } async function removeTheDifference(originalGroup, addedGroup, source, roleEntityRef, enf, auditor$1, modifiedBy) { originalGroup.sort((a, b) => a.localeCompare(b)); addedGroup.sort((a, b) => a.localeCompare(b)); const missing = lodash.difference(originalGroup, addedGroup); const groupPolicies = []; for (const missingRole of missing) { groupPolicies.push([missingRole, roleEntityRef]); } if (groupPolicies.length === 0) { return; } const roleMetadata = { source, modifiedBy, roleEntityRef }; const existingMembers = await enf.getFilteredGroupingPolicy(1, roleEntityRef); const actionType = existingMembers.length === missing.length ? auditor.ActionType.DELETE : auditor.ActionType.UPDATE; const auditorMeta = { ...roleMetadata, members: groupPolicies.map((gp) => gp[0]) }; const auditorEvent = await auditor$1.createEvent({ eventId: auditor.RoleEvents.ROLE_WRITE, severityLevel: "medium", meta: { actionType, source: auditorMeta.source } }); try { await enf.removeGroupingPolicies(groupPolicies, roleMetadata, false); await auditorEvent.success({ meta: auditorMeta }); } catch (error) { await auditorEvent.fail({ error, meta: auditorMeta }); throw error; } } function transformArrayToPolicy(policyArray) { const [entityReference, permission, policy, effect] = policyArray; return { entityReference, permission, policy, effect }; } function transformPolicyGroupToLowercase(policyArray) { if (policyArray.length > 1 && policyArray[0].startsWith("g") && (policyArray[1].startsWith("user") || policyArray[1].startsWith("group"))) { policyArray[1] = policyArray[1].toLocaleLowerCase("en-US"); } } function transformRolesGroupToLowercase(roles) { return roles.map( (role) => role.length >= 1 ? [role[0].toLocaleLowerCase("en-US"), ...role.slice(1)] : role ); } function deepSortedEqual(obj1, obj2, excludeFields) { let copyObj1; let copyObj2; if (excludeFields) { const excludeFieldsPredicate = (_value, key) => { for (const field of excludeFields) { if (key === field) { return true; } } return false; }; copyObj1 = lodash.omitBy(obj1, excludeFieldsPredicate); copyObj2 = lodash.omitBy(obj2, excludeFieldsPredicate); } const sortedObj1 = lodash.sortBy(lodash.toPairs(copyObj1 || obj1), ([key]) => key); const sortedObj2 = lodash.sortBy(lodash.toPairs(copyObj2 || obj2), ([key]) => key); return lodash.isEqual(sortedObj1, sortedObj2); } function isPermissionAction(action) { return ["create", "read", "update", "delete", "use"].includes( action ); } async function buildRoleSourceMap(policies, roleMetadata) { return await policies.reduce( async (acc, policy) => { const roleEntityRef = policy[0]; const acummulator = await acc; if (!acummulator.has(roleEntityRef)) { const metadata = await roleMetadata.findRoleMetadata(roleEntityRef); acummulator.set(roleEntityRef, metadata?.source); } return acummulator; }, Promise.resolve(/* @__PURE__ */ new Map()) ); } function mergeRoleMetadata(currentMetadata, newMetadata) { const mergedMetaData = { ...currentMetadata }; mergedMetaData.lastModified = newMetadata.lastModified ?? (/* @__PURE__ */ new Date()).toUTCString(); mergedMetaData.modifiedBy = newMetadata.modifiedBy; mergedMetaData.description = newMetadata.description ?? currentMetadata.description; mergedMetaData.roleEntityRef = newMetadata.roleEntityRef; mergedMetaData.source = newMetadata.source; mergedMetaData.owner = newMetadata.owner ?? currentMetadata.owner; return mergedMetaData; } async function processConditionMapping(roleConditionPolicy, pluginPermMetaData, auth) { const { token } = await auth.getPluginRequestToken({ onBehalfOf: await auth.getOwnServiceCredentials(), targetPluginId: roleConditionPolicy.pluginId }); const rule = await pluginPermMetaData.getMetadataByPluginId( roleConditionPolicy.pluginId, token ); if (!rule?.permissions) { throw new Error( `Unable to get permission list for plugin ${roleConditionPolicy.pluginId}` ); } const permInfo = []; for (const action of roleConditionPolicy.permissionMapping) { const perm = rule.permissions.find((permission) => { if (permission.type === "resource") { const isCorrectResourceType = permission.resourceType === roleConditionPolicy.resourceType; const isCorrectAction = action === permission.attributes.action; const undefinedAction = action === "use" && permission.attributes.action === void 0; return isCorrectResourceType && (isCorrectAction || undefinedAction); } return false; }); if (!perm) { throw new Error( `Unable to find permission to get permission name for resource type '${roleConditionPolicy.resourceType}' and action ${JSON.stringify(action)}` ); } permInfo.push({ name: perm.name, action }); } return { ...roleConditionPolicy, permissionMapping: permInfo }; } function deepSort(value) { if (lodash.isArray(value)) { return lodash.sortBy(value.map(deepSort)); } else if (lodash.isPlainObject(value)) { return lodash.fromPairs( lodash.sortBy( lodash.toPairs(value).map(([k, v]) => [k, deepSort(v)]), 0 ) ); } return value; } function deepSortEqual(obj1, obj2) { return lodash.isEqual(deepSort(obj1), deepSort(obj2)); } const matches = (role, filters) => { if (!filters) { return true; } if (!role) { return false; } if ("allOf" in filters) { return filters.allOf.every((filter) => matches(role, filter)); } if ("anyOf" in filters) { return filters.anyOf.some((filter) => matches(role, filter)); } if ("not" in filters) { return !matches(role, filters.not); } if (role.isDefault) { return true; } if (!role.owner) { return false; } if (filters.key === "owners" || filters.key === "owner") { return filters.values.includes(role.owner); } return false; }; exports.abortConditionalPolicyReconcile = abortConditionalPolicyReconcile; exports.buildRoleSourceMap = buildRoleSourceMap; exports.conditionalActionsOverlap = conditionalActionsOverlap; exports.deepSort = deepSort; exports.deepSortEqual = deepSortEqual; exports.deepSortedEqual = deepSortedEqual; exports.diffConditionalPolicies = diffConditionalPolicies; exports.isPermissionAction = isPermissionAction; exports.matches = matches; exports.mergeRoleMetadata = mergeRoleMetadata; exports.metadataStringToPolicy = metadataStringToPolicy; exports.pendingDeleteIdsFromPlan = pendingDeleteIdsFromPlan; exports.permissionMappingToActions = permissionMappingToActions; exports.planConditionalReconcile = planConditionalReconcile; exports.policiesToString = policiesToString; exports.policyToString = policyToString; exports.processConditionMapping = processConditionMapping; exports.removeTheDifference = removeTheDifference; exports.sameConditionalResource = sameConditionalResource; exports.syncRolePolicies = syncRolePolicies; exports.toError = toError; exports.transformArrayToPolicy = transformArrayToPolicy; exports.transformPolicyGroupToLowercase = transformPolicyGroupToLowercase; exports.transformRolesGroupToLowercase = transformRolesGroupToLowercase; exports.typedPoliciesToString = typedPoliciesToString; exports.typedPolicyToString = typedPolicyToString; //# sourceMappingURL=helper.cjs.js.map