UNPKG

@backstage-community/plugin-rbac-backend

Version:
213 lines (209 loc) 7.74 kB
'use strict'; var catalogModel = require('@backstage/catalog-model'); var errors = require('@backstage/errors'); var pluginPermissionCommon = require('@backstage/plugin-permission-common'); var pluginRbacCommon = require('@backstage-community/plugin-rbac-common'); function permissionContainsDoubleQuote(permission) { return permission.includes('"'); } const validateSource = async (source, roleMetadata) => { if (!roleMetadata) { return void 0; } if (roleMetadata.source !== source && roleMetadata.source !== "legacy") { return new Error( `source does not match originating role ${roleMetadata.roleEntityRef}, consider making changes to the '${roleMetadata.source.toLocaleUpperCase()}'` ); } return void 0; }; function validatePolicy(policy) { const err = validateEntityReference(policy.entityReference); if (err) { return err; } if (!policy.permission) { return new Error(`'permission' field must not be empty`); } if (permissionContainsDoubleQuote(policy.permission)) { return new errors.InputError( `'permission' contains double quotes (") which are not supported by the RBAC policy persistence format.` ); } if (!policy.policy) { return new Error(`'policy' field must not be empty`); } else if (!pluginRbacCommon.isValidPermissionAction(policy.policy)) { return new Error( `'policy' has invalid value: '${policy.policy}'. It should be one of: ${pluginRbacCommon.PermissionActionValues.join(", ")}` ); } if (!policy.effect) { return new Error(`'effect' field must not be empty`); } else if (!isValidEffectValue(policy.effect)) { return new Error( `'effect' has invalid value: '${policy.effect}'. It should be: '${pluginPermissionCommon.AuthorizeResult.ALLOW.toLocaleLowerCase()}' or '${pluginPermissionCommon.AuthorizeResult.DENY.toLocaleLowerCase()}'` ); } return void 0; } function validateRole(role) { if (!role.name) { return new Error(`'name' field must not be empty`); } let err = validateEntityReference(role.name, true); if (err) { return err; } if (!role.memberReferences || role.memberReferences.length === 0) { return new Error(`'memberReferences' field must not be empty`); } for (const member of role.memberReferences) { err = validateEntityReference(member); if (err) { return err; } } if (role.metadata && role.metadata.owner) { err = validateEntityReference(role.metadata.owner, false, true); if (err) { return err; } } return void 0; } function isValidEffectValue(effect) { return effect === pluginPermissionCommon.AuthorizeResult.ALLOW.toLocaleLowerCase() || effect === pluginPermissionCommon.AuthorizeResult.DENY.toLocaleLowerCase(); } function isValidEntityName(name) { const validNamePattern = /^[a-zA-Z0-9]+([._-][a-zA-Z0-9]+)*$/; return validNamePattern.test(name) && name.length <= 63; } function isValidEntityNamespace(namespace) { const validNamespacePattern = /^[a-z0-9]+(-[a-z0-9]+)*$/; return validNamespacePattern.test(namespace) && namespace.length <= 63; } function validateEntityReference(entityRef, role, owner) { if (!entityRef) { return new Error(`'entityReference' must not be empty`); } let entityRefCompound; try { entityRefCompound = catalogModel.parseEntityRef(entityRef); } catch (err) { return err; } const entityRefFull = `${entityRefCompound.kind}:${entityRefCompound.namespace}/${entityRefCompound.name}`; if (entityRefFull !== entityRef) { return new Error( `entity reference '${entityRef}' does not match the required format [<kind>:][<namespace>/]<name>. Provide, please, full entity reference.` ); } if (role && entityRefCompound.kind !== "role") { return new Error( `Unsupported kind ${entityRefCompound.kind}. Supported value should be "role"` ); } if (owner && entityRefCompound.kind !== "user" && entityRefCompound.kind !== "group") { return new Error( `Unsupported kind ${entityRefCompound.kind}. List supported values ["user", "group"]` ); } if (entityRefCompound.kind !== "user" && entityRefCompound.kind !== "group" && entityRefCompound.kind !== "role") { return new Error( `Unsupported kind ${entityRefCompound.kind}. List supported values ["user", "group", "role"]` ); } if (!isValidEntityName(entityRefCompound.name)) { return new Error( `The name '${entityRefCompound.name}' in the entity reference must be a string that is sequences of [a-zA-Z0-9] separated by any of [-_.], at most 63 characters in total` ); } if (!isValidEntityNamespace(entityRefCompound.namespace)) { return new Error( `The namespace '${entityRefCompound.namespace}' in the entity reference must be a string that is sequences of [a-z0-9] separated by [-], at most 63 characters in total` ); } return void 0; } async function validateGroupingPolicy(groupPolicy, metadata, source) { if (groupPolicy.length !== 2) { return new Error(`Group policy should have length 2`); } const member = groupPolicy[0]; let err = validateEntityReference(member); if (err) { return new Error( `Failed to validate group policy ${groupPolicy}. Cause: ${err.message}` ); } const parent = groupPolicy[1]; err = validateEntityReference(parent); if (err) { return new Error( `Failed to validate group policy ${groupPolicy}. Cause: ${err.message}` ); } if (member.startsWith(`role:`)) { return new Error( `Group policy is invalid: ${groupPolicy}. rbac-backend plugin doesn't support role inheritance.` ); } if (member.startsWith(`group:`) && parent.startsWith(`group:`)) { return new Error( `Group policy is invalid: ${groupPolicy}. Group inheritance information could be provided only with help of Catalog API.` ); } if (member.startsWith(`user:`) && parent.startsWith(`group:`)) { return new Error( `Group policy is invalid: ${groupPolicy}. User membership information could be provided only with help of Catalog API.` ); } err = await validateSource(source, metadata); if (metadata && err) { return new errors.NotAllowedError( `Unable to validate role ${groupPolicy}. Cause: ${err.message}` ); } return void 0; } const checkForDuplicatePolicies = async (fileEnf, policy, policyFile) => { const duplicates = await fileEnf.getFilteredPolicy(0, ...policy); if (duplicates.length > 1) { return new Error( `Duplicate policy: ${policy} found in the file ${policyFile}` ); } const flipPolicyEffect = [ policy[0], policy[1], policy[2], policy[3] === "deny" ? "allow" : "deny" ]; const dupWithDifferentEffect = await fileEnf.getFilteredPolicy( 0, ...flipPolicyEffect ); if (dupWithDifferentEffect.length > 0) { return new Error( `Duplicate policy: ${policy[0]}, ${policy[1]}, ${policy[2]} with different effect found in the file ${policyFile}` ); } return void 0; }; const checkForDuplicateGroupPolicies = async (fileEnf, policy, policyFile) => { const duplicates = await fileEnf.getFilteredGroupingPolicy(0, ...policy); if (duplicates.length > 1) { return new Error( `Duplicate role: ${policy} found in the file ${policyFile}` ); } return void 0; }; exports.checkForDuplicateGroupPolicies = checkForDuplicateGroupPolicies; exports.checkForDuplicatePolicies = checkForDuplicatePolicies; exports.validateEntityReference = validateEntityReference; exports.validateGroupingPolicy = validateGroupingPolicy; exports.validatePolicy = validatePolicy; exports.validateRole = validateRole; exports.validateSource = validateSource; //# sourceMappingURL=policies-validation.cjs.js.map