@backstage-community/plugin-rbac-backend
Version:
336 lines (332 loc) • 11.7 kB
JavaScript
;
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