UNPKG

@backstage-community/plugin-rbac-backend

Version:
220 lines (216 loc) 7.56 kB
'use strict'; 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