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