@backstage-community/plugin-rbac-backend 5.2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +671 -0
- package/README.md +220 -0
- package/config.d.ts +68 -0
- package/dist/admin-permissions/admin-creation.cjs.js +117 -0
- package/dist/admin-permissions/admin-creation.cjs.js.map +1 -0
- package/dist/audit-log/audit-logger.cjs.js +108 -0
- package/dist/audit-log/audit-logger.cjs.js.map +1 -0
- package/dist/audit-log/rest-errors-interceptor.cjs.js +100 -0
- package/dist/audit-log/rest-errors-interceptor.cjs.js.map +1 -0
- package/dist/conditional-aliases/alias-resolver.cjs.js +76 -0
- package/dist/conditional-aliases/alias-resolver.cjs.js.map +1 -0
- package/dist/database/casbin-adapter-factory.cjs.js +87 -0
- package/dist/database/casbin-adapter-factory.cjs.js.map +1 -0
- package/dist/database/conditional-storage.cjs.js +172 -0
- package/dist/database/conditional-storage.cjs.js.map +1 -0
- package/dist/database/migration.cjs.js +21 -0
- package/dist/database/migration.cjs.js.map +1 -0
- package/dist/database/role-metadata.cjs.js +89 -0
- package/dist/database/role-metadata.cjs.js.map +1 -0
- package/dist/file-permissions/csv-file-watcher.cjs.js +407 -0
- package/dist/file-permissions/csv-file-watcher.cjs.js.map +1 -0
- package/dist/file-permissions/file-watcher.cjs.js +46 -0
- package/dist/file-permissions/file-watcher.cjs.js.map +1 -0
- package/dist/file-permissions/yaml-conditional-file-watcher.cjs.js +208 -0
- package/dist/file-permissions/yaml-conditional-file-watcher.cjs.js.map +1 -0
- package/dist/helper.cjs.js +171 -0
- package/dist/helper.cjs.js.map +1 -0
- package/dist/index.cjs.js +14 -0
- package/dist/index.cjs.js.map +1 -0
- package/dist/index.d.ts +45 -0
- package/dist/plugin.cjs.js +79 -0
- package/dist/plugin.cjs.js.map +1 -0
- package/dist/policies/allow-all-policy.cjs.js +12 -0
- package/dist/policies/allow-all-policy.cjs.js.map +1 -0
- package/dist/policies/permission-policy.cjs.js +243 -0
- package/dist/policies/permission-policy.cjs.js.map +1 -0
- package/dist/providers/connect-providers.cjs.js +211 -0
- package/dist/providers/connect-providers.cjs.js.map +1 -0
- package/dist/role-manager/ancestor-search-memo.cjs.js +159 -0
- package/dist/role-manager/ancestor-search-memo.cjs.js.map +1 -0
- package/dist/role-manager/member-list.cjs.js +101 -0
- package/dist/role-manager/member-list.cjs.js.map +1 -0
- package/dist/role-manager/role-manager.cjs.js +281 -0
- package/dist/role-manager/role-manager.cjs.js.map +1 -0
- package/dist/service/enforcer-delegate.cjs.js +353 -0
- package/dist/service/enforcer-delegate.cjs.js.map +1 -0
- package/dist/service/permission-model.cjs.js +21 -0
- package/dist/service/permission-model.cjs.js.map +1 -0
- package/dist/service/plugin-endpoints.cjs.js +121 -0
- package/dist/service/plugin-endpoints.cjs.js.map +1 -0
- package/dist/service/policies-rest-api.cjs.js +949 -0
- package/dist/service/policies-rest-api.cjs.js.map +1 -0
- package/dist/service/policy-builder.cjs.js +134 -0
- package/dist/service/policy-builder.cjs.js.map +1 -0
- package/dist/service/router.cjs.js +24 -0
- package/dist/service/router.cjs.js.map +1 -0
- package/dist/validation/condition-validation.cjs.js +107 -0
- package/dist/validation/condition-validation.cjs.js.map +1 -0
- package/dist/validation/policies-validation.cjs.js +194 -0
- package/dist/validation/policies-validation.cjs.js.map +1 -0
- package/migrations/20231015161232_migrations.js +41 -0
- package/migrations/20231212224526_migrations.js +84 -0
- package/migrations/20231221113214_migrations.js +60 -0
- package/migrations/20240201144429_migrations.js +37 -0
- package/migrations/20240215154456_migrations.js +143 -0
- package/migrations/20240308134410_migrations.js +31 -0
- package/migrations/20240308134941_migrations.js +43 -0
- package/migrations/20240404111242_migrations.js +53 -0
- package/migrations/20240611092136_migrations.js +29 -0
- package/package.json +98 -0
|
@@ -0,0 +1,407 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var casbin = require('casbin');
|
|
4
|
+
var sync = require('csv-parse/sync');
|
|
5
|
+
var lodash = require('lodash');
|
|
6
|
+
var auditLogger = require('../audit-log/audit-logger.cjs.js');
|
|
7
|
+
var helper = require('../helper.cjs.js');
|
|
8
|
+
var permissionModel = require('../service/permission-model.cjs.js');
|
|
9
|
+
var policiesValidation = require('../validation/policies-validation.cjs.js');
|
|
10
|
+
var fileWatcher = require('./file-watcher.cjs.js');
|
|
11
|
+
|
|
12
|
+
const CSV_PERMISSION_POLICY_FILE_AUTHOR = "csv permission policy file";
|
|
13
|
+
class CSVFileWatcher extends fileWatcher.AbstractFileWatcher {
|
|
14
|
+
constructor(filePath, allowReload, logger, enforcer, roleMetadataStorage, auditLogger) {
|
|
15
|
+
super(filePath, allowReload, logger);
|
|
16
|
+
this.enforcer = enforcer;
|
|
17
|
+
this.roleMetadataStorage = roleMetadataStorage;
|
|
18
|
+
this.auditLogger = auditLogger;
|
|
19
|
+
this.currentContent = [];
|
|
20
|
+
this.csvFilePolicies = {
|
|
21
|
+
addedPolicies: [],
|
|
22
|
+
addedGroupPolicies: [],
|
|
23
|
+
removedPolicies: [],
|
|
24
|
+
removedGroupPolicies: []
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
currentContent;
|
|
28
|
+
csvFilePolicies;
|
|
29
|
+
/**
|
|
30
|
+
* parse is used to parse the current contents of the CSV file.
|
|
31
|
+
* @returns The CSV file parsed into a string[][].
|
|
32
|
+
*/
|
|
33
|
+
parse() {
|
|
34
|
+
const content = this.getCurrentContents();
|
|
35
|
+
const parser = sync.parse(content, {
|
|
36
|
+
skip_empty_lines: true,
|
|
37
|
+
relax_column_count: true,
|
|
38
|
+
trim: true
|
|
39
|
+
});
|
|
40
|
+
return parser;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* initialize will initialize the CSV file by loading all of the permission policies and roles into
|
|
44
|
+
* the enforcer.
|
|
45
|
+
* First, we will remove all roles and permission policies if they do not exist in the temporary file enforcer.
|
|
46
|
+
* Next, we will add all roles and permission polices if they are new to the CSV file
|
|
47
|
+
* Finally, we will set the file to be watched if allow reload is set
|
|
48
|
+
* @param csvFileName The name of the csvFile
|
|
49
|
+
* @param allowReload Whether or not we will allow reloads of the CSV file
|
|
50
|
+
*/
|
|
51
|
+
async initialize() {
|
|
52
|
+
if (!this.filePath) {
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
let content = [];
|
|
56
|
+
content = this.parse();
|
|
57
|
+
const tempEnforcer = await casbin.newEnforcer(
|
|
58
|
+
casbin.newModelFromString(permissionModel.MODEL),
|
|
59
|
+
new casbin.FileAdapter(this.filePath)
|
|
60
|
+
);
|
|
61
|
+
const roleMetadatas = await this.roleMetadataStorage.filterRoleMetadata(
|
|
62
|
+
"csv-file"
|
|
63
|
+
);
|
|
64
|
+
const fileRoles = roleMetadatas.map((meta) => meta.roleEntityRef);
|
|
65
|
+
if (fileRoles.length > 0) {
|
|
66
|
+
const groupingPoliciesToRemove = await this.enforcer.getFilteredGroupingPolicy(1, ...fileRoles);
|
|
67
|
+
for (const gPolicy of groupingPoliciesToRemove) {
|
|
68
|
+
if (!await tempEnforcer.hasGroupingPolicy(...gPolicy)) {
|
|
69
|
+
this.csvFilePolicies.removedGroupPolicies.push(gPolicy);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
const policiesToRemove = await this.enforcer.getFilteredPolicy(
|
|
73
|
+
0,
|
|
74
|
+
...fileRoles
|
|
75
|
+
);
|
|
76
|
+
for (const policy of policiesToRemove) {
|
|
77
|
+
if (!await tempEnforcer.hasPolicy(...policy)) {
|
|
78
|
+
this.csvFilePolicies.removedPolicies.push(policy);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
const policiesToAdd = await tempEnforcer.getPolicy();
|
|
83
|
+
const groupPoliciesToAdd = await tempEnforcer.getGroupingPolicy();
|
|
84
|
+
for (const policy of policiesToAdd) {
|
|
85
|
+
if (!await this.enforcer.hasPolicy(...policy)) {
|
|
86
|
+
this.csvFilePolicies.addedPolicies.push(policy);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
for (const groupPolicy of groupPoliciesToAdd) {
|
|
90
|
+
if (!await this.enforcer.hasGroupingPolicy(...groupPolicy)) {
|
|
91
|
+
this.csvFilePolicies.addedGroupPolicies.push(groupPolicy);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
await this.migrateLegacyMetadata(tempEnforcer);
|
|
95
|
+
await this.updatePolicies(content, tempEnforcer);
|
|
96
|
+
if (this.allowReload) {
|
|
97
|
+
this.watchFile();
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
// Check for policies that might need to be updated
|
|
101
|
+
// This will involve update "legacy" source in the role metadata if it exist in both the
|
|
102
|
+
// temp enforcer (csv file) and a role metadata storage.
|
|
103
|
+
// We will update role metadata with the new source "csv-file"
|
|
104
|
+
async migrateLegacyMetadata(tempEnforcer) {
|
|
105
|
+
let legacyRolesMetadata = await this.roleMetadataStorage.filterRoleMetadata(
|
|
106
|
+
"legacy"
|
|
107
|
+
);
|
|
108
|
+
const legacyRoles = legacyRolesMetadata.map((meta) => meta.roleEntityRef);
|
|
109
|
+
if (legacyRoles.length > 0) {
|
|
110
|
+
const legacyGroupPolicies = await tempEnforcer.getFilteredGroupingPolicy(
|
|
111
|
+
1,
|
|
112
|
+
...legacyRoles
|
|
113
|
+
);
|
|
114
|
+
const legacyPolicies = await tempEnforcer.getFilteredPolicy(
|
|
115
|
+
0,
|
|
116
|
+
...legacyRoles
|
|
117
|
+
);
|
|
118
|
+
const legacyRolesFromFile = /* @__PURE__ */ new Set([
|
|
119
|
+
...legacyGroupPolicies.map((gp) => gp[1]),
|
|
120
|
+
...legacyPolicies.map((p) => p[0])
|
|
121
|
+
]);
|
|
122
|
+
legacyRolesMetadata = legacyRolesMetadata.filter(
|
|
123
|
+
(meta) => legacyRolesFromFile.has(meta.roleEntityRef)
|
|
124
|
+
);
|
|
125
|
+
for (const legacyRoleMeta of legacyRolesMetadata) {
|
|
126
|
+
const nonLegacyRole = helper.mergeRoleMetadata(legacyRoleMeta, {
|
|
127
|
+
modifiedBy: CSV_PERMISSION_POLICY_FILE_AUTHOR,
|
|
128
|
+
source: "csv-file",
|
|
129
|
+
roleEntityRef: legacyRoleMeta.roleEntityRef
|
|
130
|
+
});
|
|
131
|
+
await this.roleMetadataStorage.updateRoleMetadata(
|
|
132
|
+
nonLegacyRole,
|
|
133
|
+
legacyRoleMeta.roleEntityRef
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* onChange is called whenever there is a change to the CSV file.
|
|
140
|
+
* It will parse the current and new contents of the CSV file and process the roles and permission policies present.
|
|
141
|
+
* Afterwards, it will find the difference between the current and new contents of the CSV file
|
|
142
|
+
* and sort them into added / removed, permission policies / roles.
|
|
143
|
+
* It will finally call updatePolicies with the new content.
|
|
144
|
+
*/
|
|
145
|
+
async onChange() {
|
|
146
|
+
const newContent = this.parse();
|
|
147
|
+
const tempEnforcer = await casbin.newEnforcer(
|
|
148
|
+
casbin.newModelFromString(permissionModel.MODEL),
|
|
149
|
+
new casbin.FileAdapter(this.filePath)
|
|
150
|
+
);
|
|
151
|
+
const currentFlatContent = this.currentContent.flatMap((data) => {
|
|
152
|
+
return helper.policyToString(data);
|
|
153
|
+
});
|
|
154
|
+
const newFlatContent = newContent.flatMap((data) => {
|
|
155
|
+
return helper.policyToString(data);
|
|
156
|
+
});
|
|
157
|
+
const diffRemoved = lodash.difference(currentFlatContent, newFlatContent);
|
|
158
|
+
const diffAdded = lodash.difference(newFlatContent, currentFlatContent);
|
|
159
|
+
await this.migrateLegacyMetadata(tempEnforcer);
|
|
160
|
+
if (diffRemoved.length === 0 && diffAdded.length === 0) {
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
diffRemoved.forEach((policy) => {
|
|
164
|
+
const convertedPolicy = helper.metadataStringToPolicy(policy);
|
|
165
|
+
if (convertedPolicy[0] === "p") {
|
|
166
|
+
convertedPolicy.splice(0, 1);
|
|
167
|
+
this.csvFilePolicies.removedPolicies.push(convertedPolicy);
|
|
168
|
+
} else if (convertedPolicy[0] === "g") {
|
|
169
|
+
convertedPolicy.splice(0, 1);
|
|
170
|
+
this.csvFilePolicies.removedGroupPolicies.push(convertedPolicy);
|
|
171
|
+
}
|
|
172
|
+
});
|
|
173
|
+
diffAdded.forEach((policy) => {
|
|
174
|
+
const convertedPolicy = helper.metadataStringToPolicy(policy);
|
|
175
|
+
if (convertedPolicy[0] === "p") {
|
|
176
|
+
convertedPolicy.splice(0, 1);
|
|
177
|
+
this.csvFilePolicies.addedPolicies.push(convertedPolicy);
|
|
178
|
+
} else if (convertedPolicy[0] === "g") {
|
|
179
|
+
convertedPolicy.splice(0, 1);
|
|
180
|
+
this.csvFilePolicies.addedGroupPolicies.push(convertedPolicy);
|
|
181
|
+
}
|
|
182
|
+
});
|
|
183
|
+
await this.updatePolicies(newContent, tempEnforcer);
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* updatePolicies is used to update all of the permission policies and roles within a CSV file.
|
|
187
|
+
* It will check the number of added and removed permissions policies and roles and call the appropriate
|
|
188
|
+
* methods for these.
|
|
189
|
+
* It will also update the current contents of the CSV file to the most recent
|
|
190
|
+
* @param newContent The new content present in the CSV file
|
|
191
|
+
* @param tempEnforcer Temporary enforcer for checking for duplicates when adding policies
|
|
192
|
+
*/
|
|
193
|
+
async updatePolicies(newContent, tempEnforcer) {
|
|
194
|
+
this.currentContent = newContent;
|
|
195
|
+
if (this.csvFilePolicies.addedPolicies.length > 0)
|
|
196
|
+
await this.addPermissionPolicies(tempEnforcer);
|
|
197
|
+
if (this.csvFilePolicies.removedPolicies.length > 0)
|
|
198
|
+
await this.removePermissionPolicies();
|
|
199
|
+
if (this.csvFilePolicies.addedGroupPolicies.length > 0)
|
|
200
|
+
await this.addRoles(tempEnforcer);
|
|
201
|
+
if (this.csvFilePolicies.removedGroupPolicies.length > 0)
|
|
202
|
+
await this.removeRoles();
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* addPermissionPolicies will add the new permission policies that are present in the CSV file.
|
|
206
|
+
* We will attempt to validate the permission policy and log any warnings that are encountered.
|
|
207
|
+
* If a warning is encountered, we will skip adding the permission policy to the enforcer.
|
|
208
|
+
* @param tempEnforcer Temporary enforcer for checking for duplicates when adding policies
|
|
209
|
+
*/
|
|
210
|
+
async addPermissionPolicies(tempEnforcer) {
|
|
211
|
+
for (const policy of this.csvFilePolicies.addedPolicies) {
|
|
212
|
+
const transformedPolicy = helper.transformArrayToPolicy(policy);
|
|
213
|
+
const metadata = await this.roleMetadataStorage.findRoleMetadata(
|
|
214
|
+
policy[0]
|
|
215
|
+
);
|
|
216
|
+
let err = policiesValidation.validatePolicy(transformedPolicy);
|
|
217
|
+
if (err) {
|
|
218
|
+
this.logger.warn(
|
|
219
|
+
`Failed to validate policy from file ${this.filePath}. Cause: ${err.message}`
|
|
220
|
+
);
|
|
221
|
+
continue;
|
|
222
|
+
}
|
|
223
|
+
err = await policiesValidation.validateSource("csv-file", metadata);
|
|
224
|
+
if (err) {
|
|
225
|
+
this.logger.warn(
|
|
226
|
+
`Unable to add policy ${policy} from file ${this.filePath}. Cause: ${err.message}`
|
|
227
|
+
);
|
|
228
|
+
continue;
|
|
229
|
+
}
|
|
230
|
+
err = await policiesValidation.checkForDuplicatePolicies(
|
|
231
|
+
tempEnforcer,
|
|
232
|
+
policy,
|
|
233
|
+
this.filePath
|
|
234
|
+
);
|
|
235
|
+
if (err) {
|
|
236
|
+
this.logger.warn(err.message);
|
|
237
|
+
continue;
|
|
238
|
+
}
|
|
239
|
+
try {
|
|
240
|
+
await this.enforcer.addPolicy(policy);
|
|
241
|
+
await this.auditLogger.auditLog({
|
|
242
|
+
actorId: auditLogger.RBAC_BACKEND,
|
|
243
|
+
message: `Created policy`,
|
|
244
|
+
eventName: auditLogger.PermissionEvents.CREATE_POLICY,
|
|
245
|
+
metadata: { policies: [policy], source: "csv-file" },
|
|
246
|
+
stage: auditLogger.HANDLE_RBAC_DATA_STAGE,
|
|
247
|
+
status: "succeeded"
|
|
248
|
+
});
|
|
249
|
+
} catch (e) {
|
|
250
|
+
this.logger.warn(
|
|
251
|
+
`Failed to add or update policy ${policy} after modification ${this.filePath}. Cause: ${e}`
|
|
252
|
+
);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
this.csvFilePolicies.addedPolicies = [];
|
|
256
|
+
}
|
|
257
|
+
/**
|
|
258
|
+
* removePermissionPolicies will remove the permission policies that are no longer present in the CSV file.
|
|
259
|
+
*/
|
|
260
|
+
async removePermissionPolicies() {
|
|
261
|
+
try {
|
|
262
|
+
await this.enforcer.removePolicies(this.csvFilePolicies.removedPolicies);
|
|
263
|
+
await this.auditLogger.auditLog({
|
|
264
|
+
actorId: auditLogger.RBAC_BACKEND,
|
|
265
|
+
message: `Deleted policies`,
|
|
266
|
+
eventName: auditLogger.PermissionEvents.DELETE_POLICY,
|
|
267
|
+
metadata: {
|
|
268
|
+
policies: this.csvFilePolicies.removedPolicies,
|
|
269
|
+
source: "csv-file"
|
|
270
|
+
},
|
|
271
|
+
stage: auditLogger.HANDLE_RBAC_DATA_STAGE,
|
|
272
|
+
status: "succeeded"
|
|
273
|
+
});
|
|
274
|
+
} catch (e) {
|
|
275
|
+
this.logger.warn(
|
|
276
|
+
`Failed to remove policies ${JSON.stringify(
|
|
277
|
+
this.csvFilePolicies.removedPolicies
|
|
278
|
+
)} after modification ${this.filePath}. Cause: ${e}`
|
|
279
|
+
);
|
|
280
|
+
}
|
|
281
|
+
this.csvFilePolicies.removedPolicies = [];
|
|
282
|
+
}
|
|
283
|
+
/**
|
|
284
|
+
* addRoles will add the new roles that are present in the CSV file.
|
|
285
|
+
* We will attempt to validate the role and log any warnings that are encountered.
|
|
286
|
+
* If a warning is encountered, we will skip adding the role to the enforcer.
|
|
287
|
+
* @param tempEnforcer Temporary enforcer for checking for duplicates when adding policies
|
|
288
|
+
*/
|
|
289
|
+
async addRoles(tempEnforcer) {
|
|
290
|
+
for (const groupPolicy of this.csvFilePolicies.addedGroupPolicies) {
|
|
291
|
+
let err = await policiesValidation.validateGroupingPolicy(
|
|
292
|
+
groupPolicy,
|
|
293
|
+
this.roleMetadataStorage,
|
|
294
|
+
"csv-file"
|
|
295
|
+
);
|
|
296
|
+
if (err) {
|
|
297
|
+
this.logger.warn(
|
|
298
|
+
`${err.message}, error originates from file ${this.filePath}`
|
|
299
|
+
);
|
|
300
|
+
continue;
|
|
301
|
+
}
|
|
302
|
+
err = await policiesValidation.checkForDuplicateGroupPolicies(
|
|
303
|
+
tempEnforcer,
|
|
304
|
+
groupPolicy,
|
|
305
|
+
this.filePath
|
|
306
|
+
);
|
|
307
|
+
if (err) {
|
|
308
|
+
this.logger.warn(err.message);
|
|
309
|
+
continue;
|
|
310
|
+
}
|
|
311
|
+
try {
|
|
312
|
+
const roleMetadata = {
|
|
313
|
+
source: "csv-file",
|
|
314
|
+
roleEntityRef: groupPolicy[1],
|
|
315
|
+
author: CSV_PERMISSION_POLICY_FILE_AUTHOR,
|
|
316
|
+
modifiedBy: CSV_PERMISSION_POLICY_FILE_AUTHOR
|
|
317
|
+
};
|
|
318
|
+
const currentMetadata = await this.roleMetadataStorage.findRoleMetadata(
|
|
319
|
+
roleMetadata.roleEntityRef
|
|
320
|
+
);
|
|
321
|
+
await this.enforcer.addGroupingPolicy(groupPolicy, roleMetadata);
|
|
322
|
+
const eventName = currentMetadata ? auditLogger.RoleEvents.UPDATE_ROLE : auditLogger.RoleEvents.CREATE_ROLE;
|
|
323
|
+
const message = currentMetadata ? "Updated role" : "Created role";
|
|
324
|
+
await this.auditLogger.auditLog({
|
|
325
|
+
actorId: auditLogger.RBAC_BACKEND,
|
|
326
|
+
message,
|
|
327
|
+
eventName,
|
|
328
|
+
metadata: { ...roleMetadata, members: [groupPolicy[0]] },
|
|
329
|
+
stage: auditLogger.HANDLE_RBAC_DATA_STAGE,
|
|
330
|
+
status: "succeeded"
|
|
331
|
+
});
|
|
332
|
+
} catch (e) {
|
|
333
|
+
this.logger.warn(
|
|
334
|
+
`Failed to add or update group policy ${groupPolicy} after modification ${this.filePath}. Cause: ${e}`
|
|
335
|
+
);
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
this.csvFilePolicies.addedGroupPolicies = [];
|
|
339
|
+
}
|
|
340
|
+
/**
|
|
341
|
+
* removeRoles will remove the roles that are no longer present in the CSV file.
|
|
342
|
+
* If the role exists with multiple groups and or users, we will update it role information.
|
|
343
|
+
* Otherwise, we will remove the role completely.
|
|
344
|
+
*/
|
|
345
|
+
async removeRoles() {
|
|
346
|
+
for (const groupPolicy of this.csvFilePolicies.removedGroupPolicies) {
|
|
347
|
+
const roleEntityRef = groupPolicy[1];
|
|
348
|
+
const isUpdate = await this.enforcer.getFilteredGroupingPolicy(
|
|
349
|
+
1,
|
|
350
|
+
roleEntityRef
|
|
351
|
+
);
|
|
352
|
+
try {
|
|
353
|
+
const metadata = {
|
|
354
|
+
source: "csv-file",
|
|
355
|
+
roleEntityRef,
|
|
356
|
+
author: CSV_PERMISSION_POLICY_FILE_AUTHOR,
|
|
357
|
+
modifiedBy: CSV_PERMISSION_POLICY_FILE_AUTHOR
|
|
358
|
+
};
|
|
359
|
+
await this.enforcer.removeGroupingPolicy(
|
|
360
|
+
groupPolicy,
|
|
361
|
+
metadata,
|
|
362
|
+
isUpdate.length > 1
|
|
363
|
+
);
|
|
364
|
+
const isRolePresent = await this.roleMetadataStorage.findRoleMetadata(
|
|
365
|
+
roleEntityRef
|
|
366
|
+
);
|
|
367
|
+
const eventName = isRolePresent ? auditLogger.RoleEvents.UPDATE_ROLE : auditLogger.RoleEvents.DELETE_ROLE;
|
|
368
|
+
const message = isRolePresent ? "Updated role: deleted members" : "Deleted role";
|
|
369
|
+
await this.auditLogger.auditLog({
|
|
370
|
+
actorId: auditLogger.RBAC_BACKEND,
|
|
371
|
+
message,
|
|
372
|
+
eventName,
|
|
373
|
+
metadata: { ...metadata, members: [groupPolicy[0]] },
|
|
374
|
+
stage: auditLogger.HANDLE_RBAC_DATA_STAGE,
|
|
375
|
+
status: "succeeded"
|
|
376
|
+
});
|
|
377
|
+
} catch (e) {
|
|
378
|
+
this.logger.warn(
|
|
379
|
+
`Failed to remove group policy ${groupPolicy} after modification ${this.filePath}. Cause: ${e}`
|
|
380
|
+
);
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
this.csvFilePolicies.removedGroupPolicies = [];
|
|
384
|
+
}
|
|
385
|
+
async cleanUpRolesAndPolicies() {
|
|
386
|
+
const roleMetadatas = await this.roleMetadataStorage.filterRoleMetadata(
|
|
387
|
+
"csv-file"
|
|
388
|
+
);
|
|
389
|
+
const fileRoles = roleMetadatas.map((meta) => meta.roleEntityRef);
|
|
390
|
+
if (fileRoles.length > 0) {
|
|
391
|
+
for (const fileRole of fileRoles) {
|
|
392
|
+
this.csvFilePolicies.removedGroupPolicies.push(
|
|
393
|
+
...await this.enforcer.getFilteredGroupingPolicy(1, fileRole)
|
|
394
|
+
);
|
|
395
|
+
this.csvFilePolicies.removedPolicies.push(
|
|
396
|
+
...await this.enforcer.getFilteredPolicy(0, fileRole)
|
|
397
|
+
);
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
await this.removePermissionPolicies();
|
|
401
|
+
await this.removeRoles();
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
exports.CSVFileWatcher = CSVFileWatcher;
|
|
406
|
+
exports.CSV_PERMISSION_POLICY_FILE_AUTHOR = CSV_PERMISSION_POLICY_FILE_AUTHOR;
|
|
407
|
+
//# sourceMappingURL=csv-file-watcher.cjs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"csv-file-watcher.cjs.js","sources":["../../src/file-permissions/csv-file-watcher.ts"],"sourcesContent":["/*\n * Copyright 2024 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport type { LoggerService } from '@backstage/backend-plugin-api';\n\nimport type { AuditLogger } from '@janus-idp/backstage-plugin-audit-log-node';\nimport { Enforcer, FileAdapter, newEnforcer, newModelFromString } from 'casbin';\nimport { parse } from 'csv-parse/sync';\nimport { difference } from 'lodash';\n\nimport {\n HANDLE_RBAC_DATA_STAGE,\n PermissionAuditInfo,\n PermissionEvents,\n RBAC_BACKEND,\n RoleAuditInfo,\n RoleEvents,\n} from '../audit-log/audit-logger';\nimport {\n RoleMetadataDao,\n RoleMetadataStorage,\n} from '../database/role-metadata';\nimport {\n mergeRoleMetadata,\n metadataStringToPolicy,\n policyToString,\n transformArrayToPolicy,\n} from '../helper';\nimport { EnforcerDelegate } from '../service/enforcer-delegate';\nimport { MODEL } from '../service/permission-model';\nimport {\n checkForDuplicateGroupPolicies,\n checkForDuplicatePolicies,\n validateGroupingPolicy,\n validatePolicy,\n validateSource,\n} from '../validation/policies-validation';\nimport { AbstractFileWatcher } from './file-watcher';\n\nexport const CSV_PERMISSION_POLICY_FILE_AUTHOR = 'csv permission policy file';\n\ntype CSVFilePolicies = {\n addedPolicies: string[][];\n addedGroupPolicies: string[][];\n removedPolicies: string[][];\n removedGroupPolicies: string[][];\n};\n\nexport class CSVFileWatcher extends AbstractFileWatcher<string[][]> {\n private currentContent: string[][];\n private csvFilePolicies: CSVFilePolicies;\n\n constructor(\n filePath: string | undefined,\n allowReload: boolean,\n logger: LoggerService,\n private readonly enforcer: EnforcerDelegate,\n private readonly roleMetadataStorage: RoleMetadataStorage,\n private readonly auditLogger: AuditLogger,\n ) {\n super(filePath, allowReload, logger);\n this.currentContent = [];\n this.csvFilePolicies = {\n addedPolicies: [],\n addedGroupPolicies: [],\n removedPolicies: [],\n removedGroupPolicies: [],\n };\n }\n\n /**\n * parse is used to parse the current contents of the CSV file.\n * @returns The CSV file parsed into a string[][].\n */\n parse(): string[][] {\n const content = this.getCurrentContents();\n const parser = parse(content, {\n skip_empty_lines: true,\n relax_column_count: true,\n trim: true,\n });\n\n return parser;\n }\n\n /**\n * initialize will initialize the CSV file by loading all of the permission policies and roles into\n * the enforcer.\n * First, we will remove all roles and permission policies if they do not exist in the temporary file enforcer.\n * Next, we will add all roles and permission polices if they are new to the CSV file\n * Finally, we will set the file to be watched if allow reload is set\n * @param csvFileName The name of the csvFile\n * @param allowReload Whether or not we will allow reloads of the CSV file\n */\n async initialize(): Promise<void> {\n if (!this.filePath) {\n return;\n }\n let content: string[][] = [];\n // If the file is set load the file contents\n content = this.parse();\n\n const tempEnforcer = await newEnforcer(\n newModelFromString(MODEL),\n new FileAdapter(this.filePath),\n );\n\n // Check for any old policies that will need to be removed by checking if\n // the policy no longer exists in the temp enforcer (csv file)\n const roleMetadatas = await this.roleMetadataStorage.filterRoleMetadata(\n 'csv-file',\n );\n const fileRoles = roleMetadatas.map(meta => meta.roleEntityRef);\n\n if (fileRoles.length > 0) {\n const groupingPoliciesToRemove =\n await this.enforcer.getFilteredGroupingPolicy(1, ...fileRoles);\n for (const gPolicy of groupingPoliciesToRemove) {\n if (!(await tempEnforcer.hasGroupingPolicy(...gPolicy))) {\n this.csvFilePolicies.removedGroupPolicies.push(gPolicy);\n }\n }\n const policiesToRemove = await this.enforcer.getFilteredPolicy(\n 0,\n ...fileRoles,\n );\n for (const policy of policiesToRemove) {\n if (!(await tempEnforcer.hasPolicy(...policy))) {\n this.csvFilePolicies.removedPolicies.push(policy);\n }\n }\n }\n\n // Check for any new policies that need to be added by checking if\n // the policy does not currently exist in the enforcer\n const policiesToAdd = await tempEnforcer.getPolicy();\n const groupPoliciesToAdd = await tempEnforcer.getGroupingPolicy();\n\n for (const policy of policiesToAdd) {\n if (!(await this.enforcer.hasPolicy(...policy))) {\n this.csvFilePolicies.addedPolicies.push(policy);\n }\n }\n\n for (const groupPolicy of groupPoliciesToAdd) {\n if (!(await this.enforcer.hasGroupingPolicy(...groupPolicy))) {\n this.csvFilePolicies.addedGroupPolicies.push(groupPolicy);\n }\n }\n\n await this.migrateLegacyMetadata(tempEnforcer);\n\n // We pass current here because this is during initialization and it has not changed yet\n await this.updatePolicies(content, tempEnforcer);\n\n if (this.allowReload) {\n this.watchFile();\n }\n }\n\n // Check for policies that might need to be updated\n // This will involve update \"legacy\" source in the role metadata if it exist in both the\n // temp enforcer (csv file) and a role metadata storage.\n // We will update role metadata with the new source \"csv-file\"\n private async migrateLegacyMetadata(tempEnforcer: Enforcer) {\n let legacyRolesMetadata = await this.roleMetadataStorage.filterRoleMetadata(\n 'legacy',\n );\n const legacyRoles = legacyRolesMetadata.map(meta => meta.roleEntityRef);\n if (legacyRoles.length > 0) {\n const legacyGroupPolicies = await tempEnforcer.getFilteredGroupingPolicy(\n 1,\n ...legacyRoles,\n );\n const legacyPolicies = await tempEnforcer.getFilteredPolicy(\n 0,\n ...legacyRoles,\n );\n const legacyRolesFromFile = new Set([\n ...legacyGroupPolicies.map(gp => gp[1]),\n ...legacyPolicies.map(p => p[0]),\n ]);\n legacyRolesMetadata = legacyRolesMetadata.filter(meta =>\n legacyRolesFromFile.has(meta.roleEntityRef),\n );\n for (const legacyRoleMeta of legacyRolesMetadata) {\n const nonLegacyRole = mergeRoleMetadata(legacyRoleMeta, {\n modifiedBy: CSV_PERMISSION_POLICY_FILE_AUTHOR,\n source: 'csv-file',\n roleEntityRef: legacyRoleMeta.roleEntityRef,\n });\n await this.roleMetadataStorage.updateRoleMetadata(\n nonLegacyRole,\n legacyRoleMeta.roleEntityRef,\n );\n }\n }\n }\n\n /**\n * onChange is called whenever there is a change to the CSV file.\n * It will parse the current and new contents of the CSV file and process the roles and permission policies present.\n * Afterwards, it will find the difference between the current and new contents of the CSV file\n * and sort them into added / removed, permission policies / roles.\n * It will finally call updatePolicies with the new content.\n */\n async onChange(): Promise<void> {\n const newContent = this.parse();\n\n const tempEnforcer = await newEnforcer(\n newModelFromString(MODEL),\n new FileAdapter(this.filePath!),\n );\n\n const currentFlatContent = this.currentContent.flatMap(data => {\n return policyToString(data);\n });\n const newFlatContent = newContent.flatMap(data => {\n return policyToString(data);\n });\n\n const diffRemoved = difference(currentFlatContent, newFlatContent); // policy was removed\n const diffAdded = difference(newFlatContent, currentFlatContent); // policy was added\n\n await this.migrateLegacyMetadata(tempEnforcer);\n\n if (diffRemoved.length === 0 && diffAdded.length === 0) {\n return;\n }\n\n diffRemoved.forEach(policy => {\n const convertedPolicy = metadataStringToPolicy(policy);\n if (convertedPolicy[0] === 'p') {\n convertedPolicy.splice(0, 1);\n this.csvFilePolicies.removedPolicies.push(convertedPolicy);\n } else if (convertedPolicy[0] === 'g') {\n convertedPolicy.splice(0, 1);\n this.csvFilePolicies.removedGroupPolicies.push(convertedPolicy);\n }\n });\n\n diffAdded.forEach(policy => {\n const convertedPolicy = metadataStringToPolicy(policy);\n if (convertedPolicy[0] === 'p') {\n convertedPolicy.splice(0, 1);\n this.csvFilePolicies.addedPolicies.push(convertedPolicy);\n } else if (convertedPolicy[0] === 'g') {\n convertedPolicy.splice(0, 1);\n this.csvFilePolicies.addedGroupPolicies.push(convertedPolicy);\n }\n });\n\n await this.updatePolicies(newContent, tempEnforcer);\n }\n\n /**\n * updatePolicies is used to update all of the permission policies and roles within a CSV file.\n * It will check the number of added and removed permissions policies and roles and call the appropriate\n * methods for these.\n * It will also update the current contents of the CSV file to the most recent\n * @param newContent The new content present in the CSV file\n * @param tempEnforcer Temporary enforcer for checking for duplicates when adding policies\n */\n private async updatePolicies(\n newContent: string[][],\n tempEnforcer: Enforcer,\n ): Promise<void> {\n this.currentContent = newContent;\n\n if (this.csvFilePolicies.addedPolicies.length > 0)\n await this.addPermissionPolicies(tempEnforcer);\n if (this.csvFilePolicies.removedPolicies.length > 0)\n await this.removePermissionPolicies();\n if (this.csvFilePolicies.addedGroupPolicies.length > 0)\n await this.addRoles(tempEnforcer);\n if (this.csvFilePolicies.removedGroupPolicies.length > 0)\n await this.removeRoles();\n }\n\n /**\n * addPermissionPolicies will add the new permission policies that are present in the CSV file.\n * We will attempt to validate the permission policy and log any warnings that are encountered.\n * If a warning is encountered, we will skip adding the permission policy to the enforcer.\n * @param tempEnforcer Temporary enforcer for checking for duplicates when adding policies\n */\n private async addPermissionPolicies(tempEnforcer: Enforcer): Promise<void> {\n for (const policy of this.csvFilePolicies.addedPolicies) {\n const transformedPolicy = transformArrayToPolicy(policy);\n const metadata = await this.roleMetadataStorage.findRoleMetadata(\n policy[0],\n );\n\n let err = validatePolicy(transformedPolicy);\n if (err) {\n this.logger.warn(\n `Failed to validate policy from file ${this.filePath}. Cause: ${err.message}`,\n );\n continue;\n }\n\n err = await validateSource('csv-file', metadata);\n if (err) {\n this.logger.warn(\n `Unable to add policy ${policy} from file ${this.filePath}. Cause: ${err.message}`,\n );\n continue;\n }\n\n err = await checkForDuplicatePolicies(\n tempEnforcer,\n policy,\n this.filePath!,\n );\n if (err) {\n this.logger.warn(err.message);\n continue;\n }\n try {\n await this.enforcer.addPolicy(policy);\n\n await this.auditLogger.auditLog<PermissionAuditInfo>({\n actorId: RBAC_BACKEND,\n message: `Created policy`,\n eventName: PermissionEvents.CREATE_POLICY,\n metadata: { policies: [policy], source: 'csv-file' },\n stage: HANDLE_RBAC_DATA_STAGE,\n status: 'succeeded',\n });\n } catch (e) {\n this.logger.warn(\n `Failed to add or update policy ${policy} after modification ${this.filePath}. Cause: ${e}`,\n );\n }\n }\n\n this.csvFilePolicies.addedPolicies = [];\n }\n\n /**\n * removePermissionPolicies will remove the permission policies that are no longer present in the CSV file.\n */\n private async removePermissionPolicies(): Promise<void> {\n try {\n await this.enforcer.removePolicies(this.csvFilePolicies.removedPolicies);\n\n await this.auditLogger.auditLog<PermissionAuditInfo>({\n actorId: RBAC_BACKEND,\n message: `Deleted policies`,\n eventName: PermissionEvents.DELETE_POLICY,\n metadata: {\n policies: this.csvFilePolicies.removedPolicies,\n source: 'csv-file',\n },\n stage: HANDLE_RBAC_DATA_STAGE,\n status: 'succeeded',\n });\n } catch (e) {\n this.logger.warn(\n `Failed to remove policies ${JSON.stringify(\n this.csvFilePolicies.removedPolicies,\n )} after modification ${this.filePath}. Cause: ${e}`,\n );\n }\n this.csvFilePolicies.removedPolicies = [];\n }\n\n /**\n * addRoles will add the new roles that are present in the CSV file.\n * We will attempt to validate the role and log any warnings that are encountered.\n * If a warning is encountered, we will skip adding the role to the enforcer.\n * @param tempEnforcer Temporary enforcer for checking for duplicates when adding policies\n */\n private async addRoles(tempEnforcer: Enforcer): Promise<void> {\n for (const groupPolicy of this.csvFilePolicies.addedGroupPolicies) {\n let err = await validateGroupingPolicy(\n groupPolicy,\n this.roleMetadataStorage,\n 'csv-file',\n );\n if (err) {\n this.logger.warn(\n `${err.message}, error originates from file ${this.filePath}`,\n );\n continue;\n }\n\n err = await checkForDuplicateGroupPolicies(\n tempEnforcer,\n groupPolicy,\n this.filePath!,\n );\n if (err) {\n this.logger.warn(err.message);\n continue;\n }\n\n try {\n const roleMetadata: RoleMetadataDao = {\n source: 'csv-file',\n roleEntityRef: groupPolicy[1],\n author: CSV_PERMISSION_POLICY_FILE_AUTHOR,\n modifiedBy: CSV_PERMISSION_POLICY_FILE_AUTHOR,\n };\n\n const currentMetadata = await this.roleMetadataStorage.findRoleMetadata(\n roleMetadata.roleEntityRef,\n );\n\n await this.enforcer.addGroupingPolicy(groupPolicy, roleMetadata);\n\n const eventName = currentMetadata\n ? RoleEvents.UPDATE_ROLE\n : RoleEvents.CREATE_ROLE;\n const message = currentMetadata ? 'Updated role' : 'Created role';\n await this.auditLogger.auditLog<RoleAuditInfo>({\n actorId: RBAC_BACKEND,\n message,\n eventName,\n metadata: { ...roleMetadata, members: [groupPolicy[0]] },\n stage: HANDLE_RBAC_DATA_STAGE,\n status: 'succeeded',\n });\n } catch (e) {\n this.logger.warn(\n `Failed to add or update group policy ${groupPolicy} after modification ${this.filePath}. Cause: ${e}`,\n );\n }\n }\n this.csvFilePolicies.addedGroupPolicies = [];\n }\n\n /**\n * removeRoles will remove the roles that are no longer present in the CSV file.\n * If the role exists with multiple groups and or users, we will update it role information.\n * Otherwise, we will remove the role completely.\n */\n private async removeRoles(): Promise<void> {\n for (const groupPolicy of this.csvFilePolicies.removedGroupPolicies) {\n const roleEntityRef = groupPolicy[1];\n // this requires knowledge of whether or not it is an update\n const isUpdate = await this.enforcer.getFilteredGroupingPolicy(\n 1,\n roleEntityRef,\n );\n\n // Need to update the time\n try {\n const metadata: RoleMetadataDao = {\n source: 'csv-file',\n roleEntityRef,\n author: CSV_PERMISSION_POLICY_FILE_AUTHOR,\n modifiedBy: CSV_PERMISSION_POLICY_FILE_AUTHOR,\n };\n\n await this.enforcer.removeGroupingPolicy(\n groupPolicy,\n metadata,\n isUpdate.length > 1,\n );\n\n const isRolePresent = await this.roleMetadataStorage.findRoleMetadata(\n roleEntityRef,\n );\n const eventName = isRolePresent\n ? RoleEvents.UPDATE_ROLE\n : RoleEvents.DELETE_ROLE;\n const message = isRolePresent\n ? 'Updated role: deleted members'\n : 'Deleted role';\n await this.auditLogger.auditLog<RoleAuditInfo>({\n actorId: RBAC_BACKEND,\n message,\n eventName,\n metadata: { ...metadata, members: [groupPolicy[0]] },\n stage: HANDLE_RBAC_DATA_STAGE,\n status: 'succeeded',\n });\n } catch (e) {\n this.logger.warn(\n `Failed to remove group policy ${groupPolicy} after modification ${this.filePath}. Cause: ${e}`,\n );\n }\n }\n this.csvFilePolicies.removedGroupPolicies = [];\n }\n\n async cleanUpRolesAndPolicies(): Promise<void> {\n const roleMetadatas = await this.roleMetadataStorage.filterRoleMetadata(\n 'csv-file',\n );\n const fileRoles = roleMetadatas.map(meta => meta.roleEntityRef);\n\n if (fileRoles.length > 0) {\n for (const fileRole of fileRoles) {\n this.csvFilePolicies.removedGroupPolicies.push(\n ...(await this.enforcer.getFilteredGroupingPolicy(1, fileRole)),\n );\n this.csvFilePolicies.removedPolicies.push(\n ...(await this.enforcer.getFilteredPolicy(0, fileRole)),\n );\n }\n }\n await this.removePermissionPolicies();\n await this.removeRoles();\n }\n}\n"],"names":["AbstractFileWatcher","parse","newEnforcer","newModelFromString","MODEL","FileAdapter","mergeRoleMetadata","policyToString","difference","metadataStringToPolicy","transformArrayToPolicy","validatePolicy","validateSource","checkForDuplicatePolicies","RBAC_BACKEND","PermissionEvents","HANDLE_RBAC_DATA_STAGE","validateGroupingPolicy","checkForDuplicateGroupPolicies","RoleEvents"],"mappings":"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"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var chokidar = require('chokidar');
|
|
4
|
+
var fs = require('fs');
|
|
5
|
+
|
|
6
|
+
function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e : { default: e }; }
|
|
7
|
+
|
|
8
|
+
var chokidar__default = /*#__PURE__*/_interopDefaultCompat(chokidar);
|
|
9
|
+
var fs__default = /*#__PURE__*/_interopDefaultCompat(fs);
|
|
10
|
+
|
|
11
|
+
class AbstractFileWatcher {
|
|
12
|
+
constructor(filePath, allowReload, logger) {
|
|
13
|
+
this.filePath = filePath;
|
|
14
|
+
this.allowReload = allowReload;
|
|
15
|
+
this.logger = logger;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* watchFile initializes the file watcher and sets it to begin watching for changes.
|
|
19
|
+
*/
|
|
20
|
+
watchFile() {
|
|
21
|
+
if (!this.filePath) {
|
|
22
|
+
throw new Error("File path is not specified");
|
|
23
|
+
}
|
|
24
|
+
const watcher = chokidar__default.default.watch(this.filePath);
|
|
25
|
+
watcher.on("change", async (path) => {
|
|
26
|
+
this.logger.info(`file ${path} has changed`);
|
|
27
|
+
await this.onChange();
|
|
28
|
+
});
|
|
29
|
+
watcher.on("error", (error) => {
|
|
30
|
+
this.logger.error(`error watching file ${this.filePath}: ${error}`);
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* getCurrentContents reads the current contents of the CSV file.
|
|
35
|
+
* @returns The current contents of the file.
|
|
36
|
+
*/
|
|
37
|
+
getCurrentContents() {
|
|
38
|
+
if (!this.filePath) {
|
|
39
|
+
throw new Error("File path is not specified");
|
|
40
|
+
}
|
|
41
|
+
return fs__default.default.readFileSync(this.filePath, "utf-8");
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
exports.AbstractFileWatcher = AbstractFileWatcher;
|
|
46
|
+
//# sourceMappingURL=file-watcher.cjs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"file-watcher.cjs.js","sources":["../../src/file-permissions/file-watcher.ts"],"sourcesContent":["/*\n * Copyright 2024 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport type { LoggerService } from '@backstage/backend-plugin-api';\n\nimport chokidar from 'chokidar';\n\nimport fs from 'fs';\n\n/**\n * Represents a file watcher that can be used to monitor changes in a file.\n */\nexport abstract class AbstractFileWatcher<T> {\n constructor(\n protected readonly filePath: string | undefined,\n protected readonly allowReload: boolean,\n protected readonly logger: LoggerService,\n ) {}\n\n /**\n * Initializes the file watcher and starts watching the specified file.\n */\n abstract initialize(): Promise<void>;\n\n /**\n * watchFile initializes the file watcher and sets it to begin watching for changes.\n */\n watchFile(): void {\n if (!this.filePath) {\n throw new Error('File path is not specified');\n }\n const watcher = chokidar.watch(this.filePath);\n watcher.on('change', async path => {\n this.logger.info(`file ${path} has changed`);\n await this.onChange();\n });\n watcher.on('error', error => {\n this.logger.error(`error watching file ${this.filePath}: ${error}`);\n });\n }\n\n /**\n * Handles the change event when the watched file is modified.\n * @returns A promise that resolves when the change event is handled.\n */\n abstract onChange(): Promise<void>;\n\n /**\n * getCurrentContents reads the current contents of the CSV file.\n * @returns The current contents of the file.\n */\n getCurrentContents(): string {\n if (!this.filePath) {\n throw new Error('File path is not specified');\n }\n return fs.readFileSync(this.filePath, 'utf-8');\n }\n\n /**\n * parse is used to parse the current contents of the file.\n * @returns The file parsed into a type <T>.\n */\n abstract parse(): T;\n}\n"],"names":["chokidar","fs"],"mappings":";;;;;;;;;;AAwBO,MAAe,mBAAuB,CAAA;AAAA,EAC3C,WAAA,CACqB,QACA,EAAA,WAAA,EACA,MACnB,EAAA;AAHmB,IAAA,IAAA,CAAA,QAAA,GAAA,QAAA,CAAA;AACA,IAAA,IAAA,CAAA,WAAA,GAAA,WAAA,CAAA;AACA,IAAA,IAAA,CAAA,MAAA,GAAA,MAAA,CAAA;AAAA,GAClB;AAAA;AAAA;AAAA;AAAA,EAUH,SAAkB,GAAA;AAChB,IAAI,IAAA,CAAC,KAAK,QAAU,EAAA;AAClB,MAAM,MAAA,IAAI,MAAM,4BAA4B,CAAA,CAAA;AAAA,KAC9C;AACA,IAAA,MAAM,OAAU,GAAAA,yBAAA,CAAS,KAAM,CAAA,IAAA,CAAK,QAAQ,CAAA,CAAA;AAC5C,IAAQ,OAAA,CAAA,EAAA,CAAG,QAAU,EAAA,OAAM,IAAQ,KAAA;AACjC,MAAA,IAAA,CAAK,MAAO,CAAA,IAAA,CAAK,CAAQ,KAAA,EAAA,IAAI,CAAc,YAAA,CAAA,CAAA,CAAA;AAC3C,MAAA,MAAM,KAAK,QAAS,EAAA,CAAA;AAAA,KACrB,CAAA,CAAA;AACD,IAAQ,OAAA,CAAA,EAAA,CAAG,SAAS,CAAS,KAAA,KAAA;AAC3B,MAAA,IAAA,CAAK,OAAO,KAAM,CAAA,CAAA,oBAAA,EAAuB,KAAK,QAAQ,CAAA,EAAA,EAAK,KAAK,CAAE,CAAA,CAAA,CAAA;AAAA,KACnE,CAAA,CAAA;AAAA,GACH;AAAA;AAAA;AAAA;AAAA;AAAA,EAYA,kBAA6B,GAAA;AAC3B,IAAI,IAAA,CAAC,KAAK,QAAU,EAAA;AAClB,MAAM,MAAA,IAAI,MAAM,4BAA4B,CAAA,CAAA;AAAA,KAC9C;AACA,IAAA,OAAOC,mBAAG,CAAA,YAAA,CAAa,IAAK,CAAA,QAAA,EAAU,OAAO,CAAA,CAAA;AAAA,GAC/C;AAOF;;;;"}
|