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