@backstage-community/plugin-rbac-backend
Version:
194 lines (188 loc) • 6.72 kB
JavaScript
'use strict';
var yaml = require('js-yaml');
var lodash = require('lodash');
var fs = require('fs');
var auditor = require('../auditor/auditor.cjs.js');
var helper = require('../helper.cjs.js');
var conditionValidation = require('../validation/condition-validation.cjs.js');
var fileWatcher = require('./file-watcher.cjs.js');
function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e : { default: e }; }
var yaml__default = /*#__PURE__*/_interopDefaultCompat(yaml);
var fs__default = /*#__PURE__*/_interopDefaultCompat(fs);
class YamlConditinalPoliciesFileWatcher extends fileWatcher.AbstractFileWatcher {
constructor(filePath, allowReload, logger, conditionalStorage, auditor, auth, pluginMetadataCollector, roleMetadataStorage, roleEventEmitter) {
super(filePath, allowReload, logger);
this.conditionalStorage = conditionalStorage;
this.auditor = auditor;
this.auth = auth;
this.pluginMetadataCollector = pluginMetadataCollector;
this.roleMetadataStorage = roleMetadataStorage;
this.roleEventEmitter = roleEventEmitter;
this.conditionsDiff = {
addedConditions: [],
removedConditions: []
};
}
conditionsDiff;
async initialize() {
if (!this.filePath) {
return;
}
const fileExists = fs__default.default.existsSync(this.filePath);
if (!fileExists) {
const auditorEvent = await this.auditor.createEvent({
eventId: auditor.ConditionEvents.CONDITIONAL_POLICIES_FILE_NOT_FOUND,
severityLevel: "medium"
});
await auditorEvent.fail({
error: new Error(`File '${this.filePath}' was not found`)
});
return;
}
this.roleEventEmitter.on("roleAdded", this.onChange.bind(this));
await this.onChange();
if (this.allowReload) {
this.watchFile();
}
}
async onChange() {
try {
const newConds = this.parse().filter((c) => c);
const addedConds = [];
const removedConds = [];
const csvFileRoles = await this.roleMetadataStorage.filterRoleMetadata("csv-file");
const existedFileConds = (await this.conditionalStorage.filterConditions(
csvFileRoles.map((role) => role.roleEntityRef)
)).map((condition) => {
return {
...condition,
permissionMapping: condition.permissionMapping.map((pm) => pm.action)
};
});
for (const condition of newConds) {
const roleMetadata = csvFileRoles.find(
(role) => condition.roleEntityRef === role.roleEntityRef
);
if (!roleMetadata) {
this.logger.warn(
`skip to add condition for role '${condition.roleEntityRef}'. The role either does not exist or was not created from a CSV file.`
);
continue;
}
if (roleMetadata.source !== "csv-file") {
this.logger.warn(
`skip to add condition for role '${condition.roleEntityRef}'. Role is not from csv-file`
);
continue;
}
const existingCondition = existedFileConds.find(
(c) => helper.deepSortEqual(lodash.omit(c, ["id"]), lodash.omit(condition, ["id"]))
);
if (!existingCondition) {
addedConds.push(condition);
}
}
for (const condition of existedFileConds) {
if (!newConds.find(
(c) => helper.deepSortEqual(lodash.omit(c, ["id"]), lodash.omit(condition, ["id"]))
)) {
removedConds.push(condition);
}
}
this.conditionsDiff = {
addedConditions: addedConds,
removedConditions: removedConds
};
await this.handleFileChanges();
} catch (error) {
const auditorEvent = await this.auditor.createEvent({
eventId: auditor.ConditionEvents.CONDITIONAL_POLICIES_FILE_CHANGE,
severityLevel: "medium"
});
await auditorEvent.fail({
error
});
}
}
/**
* Reads the current contents of the file and parses it.
* @returns parsed data.
*/
parse() {
const fileContents = this.getCurrentContents();
const data = yaml__default.default.loadAll(
fileContents
);
for (const condition of data) {
conditionValidation.validateRoleCondition(condition);
}
return data;
}
async handleFileChanges() {
await this.removeConditions();
await this.addConditions();
}
async addConditions() {
for (const condition of this.conditionsDiff.addedConditions) {
const auditorEvent = await this.auditor.createEvent({
eventId: auditor.ConditionEvents.CONDITION_WRITE,
severityLevel: "medium",
meta: { actionType: auditor.ActionType.CREATE }
});
try {
const conditionToCreate = await helper.processConditionMapping(
condition,
this.pluginMetadataCollector,
this.auth
);
await this.conditionalStorage.createCondition(conditionToCreate);
await auditorEvent.success({
meta: { condition }
});
} catch (error) {
await auditorEvent.fail({ error, meta: { condition } });
}
}
this.conditionsDiff.addedConditions = [];
}
async removeConditions() {
for (const condition of this.conditionsDiff.removedConditions) {
const auditorEvent = await this.auditor.createEvent({
eventId: auditor.ConditionEvents.CONDITION_WRITE,
severityLevel: "medium",
meta: { actionType: auditor.ActionType.DELETE }
});
try {
const conditionToDelete = (await this.conditionalStorage.filterConditions(
condition.roleEntityRef,
condition.pluginId,
condition.resourceType,
condition.permissionMapping
))[0];
await this.conditionalStorage.deleteCondition(conditionToDelete.id);
await auditorEvent.success({ meta: { condition } });
} catch (error) {
await auditorEvent.fail({
error,
meta: { condition }
});
}
}
this.conditionsDiff.removedConditions = [];
}
async cleanUpConditionalPolicies() {
const csvFileRoles = await this.roleMetadataStorage.filterRoleMetadata("csv-file");
const existedFileConds = (await this.conditionalStorage.filterConditions(
csvFileRoles.map((role) => role.roleEntityRef)
)).map((condition) => {
return {
...condition,
permissionMapping: condition.permissionMapping.map((pm) => pm.action)
};
});
this.conditionsDiff.removedConditions = existedFileConds;
await this.removeConditions();
}
}
exports.YamlConditinalPoliciesFileWatcher = YamlConditinalPoliciesFileWatcher;
//# sourceMappingURL=yaml-conditional-file-watcher.cjs.js.map