UNPKG

@backstage-community/plugin-rbac-backend

Version:
279 lines (275 loc) 10.3 kB
'use strict'; var pluginPermissionCommon = require('@backstage/plugin-permission-common'); var pluginRbacCommon = require('@backstage-community/plugin-rbac-common'); var adminCreation = require('../admin-permissions/admin-creation.cjs.js'); var auditor = require('../auditor/auditor.cjs.js'); var aliasResolver = require('../conditional-aliases/alias-resolver.cjs.js'); var csvFileWatcher = require('../file-permissions/csv-file-watcher.cjs.js'); var yamlConditionalFileWatcher = require('../file-permissions/yaml-conditional-file-watcher.cjs.js'); var conditionValidation = require('../validation/condition-validation.cjs.js'); class RBACPermissionPolicy { constructor(enforcer, auditor, userService, auth, conditionStorage, preferPermissionPolicy, superUserList) { this.enforcer = enforcer; this.auditor = auditor; this.userService = userService; this.auth = auth; this.conditionStorage = conditionStorage; this.superUserList = superUserList; this.preferPermissionPolicy = preferPermissionPolicy; } superUserList; preferPermissionPolicy; static async build(logger, auditor, configApi, conditionalStorage, enforcerDelegate, roleMetadataStorage, knex, pluginMetadataCollector, userInfo, auth, conditionValidationLimits) { const superUserList = []; const adminUsers = configApi.getOptionalConfigArray( "permission.rbac.admin.users" ); const superUsers = configApi.getOptionalConfigArray( "permission.rbac.admin.superUsers" ); const policiesFile = configApi.getOptionalString( "permission.rbac.policies-csv-file" ); const allowReload = configApi.getOptionalBoolean("permission.rbac.policyFileReload") || false; const conditionalPoliciesFile = configApi.getOptionalString( "permission.rbac.conditionalPoliciesFile" ); const resolvedConditionValidationLimits = conditionValidationLimits ?? conditionValidation.resolveConditionValidationLimits( conditionValidation.readConditionValidationLimitsFromConfig(configApi) ); const conditionalFileLimits = RBACPermissionPolicy.readConditionalFileLimits(configApi); const preferPermissionPolicy = (configApi.getOptionalString( "permission.rbac.policyDecisionPrecedence" ) ?? "conditional") === "basic"; if (superUsers && superUsers.length > 0) { for (const user of superUsers) { const userName = user.getString("name"); superUserList.push(userName); } } await adminCreation.useAdminsFromConfig( adminUsers || [], enforcerDelegate, auditor, roleMetadataStorage, knex ); await adminCreation.setAdminPermissions(enforcerDelegate, auditor); if ((!adminUsers || adminUsers.length === 0) && (!superUsers || superUsers.length === 0)) { logger.warn( "There are no admins or super admins configured for the RBAC-backend plugin." ); } const csvFile = new csvFileWatcher.CSVFileWatcher( policiesFile, allowReload, logger, enforcerDelegate, roleMetadataStorage, auditor ); await csvFile.initialize(); const conditionalFile = new yamlConditionalFileWatcher.YamlConditionalPoliciesFileWatcher( conditionalPoliciesFile, allowReload, logger, conditionalStorage, auditor, auth, pluginMetadataCollector, roleMetadataStorage, enforcerDelegate, resolvedConditionValidationLimits, conditionalFileLimits ); await conditionalFile.initialize(); if (!conditionalPoliciesFile) { logger.info("conditional policies file feature was disabled"); await conditionalFile.cleanUpConditionalPolicies(); } if (!policiesFile) { logger.info("csv policies file feature was disabled"); await csvFile.cleanUpRolesAndPolicies(); } return new RBACPermissionPolicy( enforcerDelegate, auditor, userInfo, auth, conditionalStorage, preferPermissionPolicy, superUserList ); } async handle(request, user) { let userInfo; if (user?.credentials && this.auth.isPrincipal(user?.credentials, "user")) { userInfo = await this.userService.getUserInfo(user.credentials); } const userEntityRef = userInfo ? userInfo.userEntityRef : `user without entity`; const auditorEvent = await auditor.createPermissionEvaluationAuditorEvent( this.auditor, userEntityRef, request ); try { let status = false; const action = pluginRbacCommon.toPermissionAction(request.permission.attributes); if (!user || !userInfo) { await auditorEvent.success({ meta: { result: pluginPermissionCommon.AuthorizeResult.DENY } }); return { result: pluginPermissionCommon.AuthorizeResult.DENY }; } if (this.superUserList.includes(userEntityRef) || userInfo.ownershipEntityRefs.some( (ref) => this.superUserList.includes(ref) )) { await auditorEvent.success({ meta: { result: pluginPermissionCommon.AuthorizeResult.ALLOW } }); return { result: pluginPermissionCommon.AuthorizeResult.ALLOW }; } const permissionName = request.permission.name; const roles = await this.enforcer.getRolesForUser(userEntityRef); const hasNamedPermission = await this.hasImplicitPermission( permissionName, action, roles ); if (request.permission.name === "policy.entity.create" && !hasNamedPermission) { request.permission = { attributes: { action: "create" }, type: "resource", resourceType: "policy-entity", name: "policy.entity.create" }; } if (pluginPermissionCommon.isResourcePermission(request.permission)) { const resourceType = request.permission.resourceType; const obj = hasNamedPermission ? permissionName : resourceType; const conditionResult = await this.handleConditions( auditorEvent, userEntityRef, request, roles, userInfo ); if (this.preferPermissionPolicy) { const hasResourcedPermission = await this.hasImplicitPermission( resourceType, action, roles ); if (hasNamedPermission || hasResourcedPermission) { status = await this.isAuthorized(userEntityRef, obj, action, roles); } else if (conditionResult) { return conditionResult; } } else { if (conditionResult) return conditionResult; status = await this.isAuthorized(userEntityRef, obj, action, roles); } } else { status = await this.isAuthorized( userEntityRef, permissionName, action, roles ); } const result = status ? pluginPermissionCommon.AuthorizeResult.ALLOW : pluginPermissionCommon.AuthorizeResult.DENY; await auditorEvent.success({ meta: { result } }); return { result }; } catch (error) { await auditorEvent.fail({ error, meta: { result: pluginPermissionCommon.AuthorizeResult.DENY } }); return { result: pluginPermissionCommon.AuthorizeResult.DENY }; } } async hasImplicitPermission(permissionName, action, roles) { for (const role of roles) { const perms = await this.enforcer.getFilteredPolicy( 0, role, permissionName, action ); if (perms.length > 0) { return true; } } return false; } isAuthorized = async (userIdentity, permission, action, roles) => { return await this.enforcer.enforce(userIdentity, permission, action, roles); }; async handleConditions(auditorEvent, userEntityRef, request, roles, userInfo) { const permissionName = request.permission.name; const resourceType = request.permission.resourceType; const action = pluginRbacCommon.toPermissionAction(request.permission.attributes); const conditions = []; let pluginId = ""; for (const role of roles) { const conditionalDecisions = await this.conditionStorage.filterConditions( role, void 0, resourceType, [action], [permissionName] ); if (conditionalDecisions.length === 1) { pluginId = conditionalDecisions[0].pluginId; conditions.push(conditionalDecisions[0].conditions); } if (conditionalDecisions.length > 1) { await auditorEvent.fail({ error: new Error( `Detected ${JSON.stringify( conditionalDecisions )} collisions for conditional policies. Expected to find a stored single condition for permission with name ${permissionName}, resource type ${resourceType}, action ${action} for user ${userEntityRef}` ), meta: { result: pluginPermissionCommon.AuthorizeResult.DENY } }); return { result: pluginPermissionCommon.AuthorizeResult.DENY }; } } if (conditions.length > 0) { const result = { pluginId, result: pluginPermissionCommon.AuthorizeResult.CONDITIONAL, resourceType, conditions: { anyOf: conditions } }; aliasResolver.replaceAliases(result.conditions, userInfo); await auditorEvent.success({ meta: { ...result } }); return result; } return void 0; } static readConditionalFileLimits(configApi) { const maxBytes = configApi.getOptionalNumber( "permission.rbac.validation.conditionalPoliciesFile.maxBytes" ); const maxDocuments = configApi.getOptionalNumber( "permission.rbac.validation.conditionalPoliciesFile.maxDocuments" ); return yamlConditionalFileWatcher.resolveConditionalPoliciesFileLimits( { ...maxBytes !== void 0 ? { maxBytes } : {}, ...maxDocuments !== void 0 ? { maxDocuments } : {} }, { maxBytes: "permission.rbac.validation.conditionalPoliciesFile.maxBytes", maxDocuments: "permission.rbac.validation.conditionalPoliciesFile.maxDocuments" } ); } } exports.RBACPermissionPolicy = RBACPermissionPolicy; //# sourceMappingURL=permission-policy.cjs.js.map