@backstage-community/plugin-rbac-backend
Version:
220 lines (216 loc) • 7.56 kB
JavaScript
;
var errors = require('@backstage/errors');
var pluginRbacCommon = require('@backstage-community/plugin-rbac-common');
var helper = require('../helper.cjs.js');
const DEFAULT_CONDITION_VALIDATION_LIMITS = {
maxConditionDepth: 12,
maxConditionNodeCount: 256,
maxCriteriaItems: 64
};
function readConditionValidationLimitsFromConfig(config) {
const baseKey = "permission.rbac.validation.conditionalPolicies";
const limitKeys = [
"maxConditionDepth",
"maxConditionNodeCount",
"maxCriteriaItems"
];
const limits = {};
for (const key of limitKeys) {
const value = config.getOptionalNumber(`${baseKey}.${key}`);
if (value !== void 0) {
limits[key] = value;
}
}
return limits;
}
function resolveConditionValidationLimits(partial = {}) {
const nextLimits = {
...DEFAULT_CONDITION_VALIDATION_LIMITS,
...partial
};
assertPositiveInteger(nextLimits.maxConditionDepth, "maxConditionDepth");
assertPositiveInteger(
nextLimits.maxConditionNodeCount,
"maxConditionNodeCount"
);
assertPositiveInteger(nextLimits.maxCriteriaItems, "maxCriteriaItems");
return nextLimits;
}
function assertPositiveInteger(value, fieldName) {
if (!Number.isInteger(value) || value <= 0) {
throw new errors.InputError(
`'${fieldName}' must be a positive integer for conditional policy validation`
);
}
}
function validateRoleCondition(condition, limits) {
const resolvedLimits = resolveConditionValidationLimits(limits ?? {});
if (!condition.roleEntityRef) {
throw new errors.InputError(
`'roleEntityRef' must be specified in the role condition`
);
}
if (!condition.result) {
throw new errors.InputError(`'result' must be specified in the role condition`);
}
if (!condition.pluginId) {
throw new errors.InputError(`'pluginId' must be specified in the role condition`);
}
if (!condition.resourceType) {
throw new errors.InputError(
`'resourceType' must be specified in the role condition`
);
}
if (!condition.permissionMapping || condition.permissionMapping.length === 0) {
throw new errors.InputError(
`'permissionMapping' must be non empty array in the role condition`
);
}
const maxDistinctPermissionActions = pluginRbacCommon.PermissionActionValues.length;
if (condition.permissionMapping.length > maxDistinctPermissionActions) {
throw new errors.InputError(
`'permissionMapping' can have at most ${maxDistinctPermissionActions} items (one entry per distinct permission action)`
);
}
const nonActionValue = condition.permissionMapping.find(
(action) => !helper.isPermissionAction(action)
);
if (nonActionValue) {
throw new errors.InputError(
`'permissionMapping' array contains non action value: '${nonActionValue}'`
);
}
const seenActions = /* @__PURE__ */ new Set();
for (const action of condition.permissionMapping) {
if (seenActions.has(action)) {
throw new errors.InputError(
`'permissionMapping' must not contain duplicate permission action '${action}'`
);
}
seenActions.add(action);
}
if (condition.resourceType === "policy-entity" && condition.permissionMapping.includes("create")) {
throw new errors.InputError(
`Conditional policy can not be created for resource type 'policy-entity' with the permission action 'create'`
);
}
if (!condition.conditions) {
throw new errors.InputError(
`'conditions' must be specified in the role condition`
);
}
if (condition.conditions) {
validatePermissionCondition(
condition.conditions,
"roleCondition.conditions",
1,
{ nodeCount: 0 },
resolvedLimits
);
}
}
function validatePermissionCondition(conditionOrCriteria, jsonPathLocator, currentDepth, state, limits) {
if (currentDepth > limits.maxConditionDepth) {
throw new errors.InputError(
`Conditional criteria depth exceeds maximum of ${limits.maxConditionDepth}`
);
}
state.nodeCount += 1;
if (state.nodeCount > limits.maxConditionNodeCount) {
throw new errors.InputError(
`Conditional criteria exceeds maximum of ${limits.maxConditionNodeCount} nodes`
);
}
validateCriteria(conditionOrCriteria, jsonPathLocator);
if ("not" in conditionOrCriteria) {
validatePermissionCondition(
conditionOrCriteria.not,
`${jsonPathLocator}.not`,
currentDepth + 1,
state,
limits
);
return;
}
if ("allOf" in conditionOrCriteria) {
if (!Array.isArray(conditionOrCriteria.allOf) || conditionOrCriteria.allOf.length === 0) {
throw new errors.InputError(
`${jsonPathLocator}.allOf criteria must be non empty array`
);
}
if (conditionOrCriteria.allOf.length > limits.maxCriteriaItems) {
throw new errors.InputError(
`${jsonPathLocator}.allOf criteria supports at most ${limits.maxCriteriaItems} items`
);
}
for (const [index, elem] of conditionOrCriteria.allOf.entries()) {
validatePermissionCondition(
elem,
`${jsonPathLocator}.allOf[${index}]`,
currentDepth + 1,
state,
limits
);
}
return;
}
if ("anyOf" in conditionOrCriteria) {
if (!Array.isArray(conditionOrCriteria.anyOf) || conditionOrCriteria.anyOf.length === 0) {
throw new errors.InputError(
`${jsonPathLocator}.anyOf criteria must be non empty array`
);
}
if (conditionOrCriteria.anyOf.length > limits.maxCriteriaItems) {
throw new errors.InputError(
`${jsonPathLocator}.anyOf criteria supports at most ${limits.maxCriteriaItems} items`
);
}
for (const [index, elem] of conditionOrCriteria.anyOf.entries()) {
validatePermissionCondition(
elem,
`${jsonPathLocator}.anyOf[${index}]`,
currentDepth + 1,
state,
limits
);
}
}
}
function validateRule(conditionOrCriteria, jsonPathLocator) {
if (!("resourceType" in conditionOrCriteria)) {
throw new errors.InputError(
`'resourceType' must be specified in the ${jsonPathLocator}.condition`
);
}
if (!("rule" in conditionOrCriteria)) {
throw new errors.InputError(
`'rule' must be specified in the ${jsonPathLocator}.condition`
);
}
}
function validateCriteria(conditionOrCriteria, jsonPathLocator) {
const criteriaList = ["allOf", "anyOf", "not"];
const found = [];
for (const crit of criteriaList) {
if (crit in conditionOrCriteria) {
found.push(crit);
}
}
if (found.length > 1) {
throw new errors.InputError(
`RBAC plugin does not support parallel conditions, consider reworking request to include nested condition criteria. Conditional criteria causing the error ${found}.`
);
} else if (found.length === 0) {
validateRule(conditionOrCriteria, jsonPathLocator);
}
if (found.length === 1 && "rule" in conditionOrCriteria) {
throw new errors.InputError(
`RBAC plugin does not support parallel conditions alongside rules, consider reworking request to include nested condition criteria. Conditional criteria causing the error ${found}, 'rule: ${conditionOrCriteria.rule}'.`
);
}
}
exports.DEFAULT_CONDITION_VALIDATION_LIMITS = DEFAULT_CONDITION_VALIDATION_LIMITS;
exports.readConditionValidationLimitsFromConfig = readConditionValidationLimitsFromConfig;
exports.resolveConditionValidationLimits = resolveConditionValidationLimits;
exports.validateRoleCondition = validateRoleCondition;
//# sourceMappingURL=condition-validation.cjs.js.map