@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.
Files changed (70) hide show
  1. package/CHANGELOG.md +671 -0
  2. package/README.md +220 -0
  3. package/config.d.ts +68 -0
  4. package/dist/admin-permissions/admin-creation.cjs.js +117 -0
  5. package/dist/admin-permissions/admin-creation.cjs.js.map +1 -0
  6. package/dist/audit-log/audit-logger.cjs.js +108 -0
  7. package/dist/audit-log/audit-logger.cjs.js.map +1 -0
  8. package/dist/audit-log/rest-errors-interceptor.cjs.js +100 -0
  9. package/dist/audit-log/rest-errors-interceptor.cjs.js.map +1 -0
  10. package/dist/conditional-aliases/alias-resolver.cjs.js +76 -0
  11. package/dist/conditional-aliases/alias-resolver.cjs.js.map +1 -0
  12. package/dist/database/casbin-adapter-factory.cjs.js +87 -0
  13. package/dist/database/casbin-adapter-factory.cjs.js.map +1 -0
  14. package/dist/database/conditional-storage.cjs.js +172 -0
  15. package/dist/database/conditional-storage.cjs.js.map +1 -0
  16. package/dist/database/migration.cjs.js +21 -0
  17. package/dist/database/migration.cjs.js.map +1 -0
  18. package/dist/database/role-metadata.cjs.js +89 -0
  19. package/dist/database/role-metadata.cjs.js.map +1 -0
  20. package/dist/file-permissions/csv-file-watcher.cjs.js +407 -0
  21. package/dist/file-permissions/csv-file-watcher.cjs.js.map +1 -0
  22. package/dist/file-permissions/file-watcher.cjs.js +46 -0
  23. package/dist/file-permissions/file-watcher.cjs.js.map +1 -0
  24. package/dist/file-permissions/yaml-conditional-file-watcher.cjs.js +208 -0
  25. package/dist/file-permissions/yaml-conditional-file-watcher.cjs.js.map +1 -0
  26. package/dist/helper.cjs.js +171 -0
  27. package/dist/helper.cjs.js.map +1 -0
  28. package/dist/index.cjs.js +14 -0
  29. package/dist/index.cjs.js.map +1 -0
  30. package/dist/index.d.ts +45 -0
  31. package/dist/plugin.cjs.js +79 -0
  32. package/dist/plugin.cjs.js.map +1 -0
  33. package/dist/policies/allow-all-policy.cjs.js +12 -0
  34. package/dist/policies/allow-all-policy.cjs.js.map +1 -0
  35. package/dist/policies/permission-policy.cjs.js +243 -0
  36. package/dist/policies/permission-policy.cjs.js.map +1 -0
  37. package/dist/providers/connect-providers.cjs.js +211 -0
  38. package/dist/providers/connect-providers.cjs.js.map +1 -0
  39. package/dist/role-manager/ancestor-search-memo.cjs.js +159 -0
  40. package/dist/role-manager/ancestor-search-memo.cjs.js.map +1 -0
  41. package/dist/role-manager/member-list.cjs.js +101 -0
  42. package/dist/role-manager/member-list.cjs.js.map +1 -0
  43. package/dist/role-manager/role-manager.cjs.js +281 -0
  44. package/dist/role-manager/role-manager.cjs.js.map +1 -0
  45. package/dist/service/enforcer-delegate.cjs.js +353 -0
  46. package/dist/service/enforcer-delegate.cjs.js.map +1 -0
  47. package/dist/service/permission-model.cjs.js +21 -0
  48. package/dist/service/permission-model.cjs.js.map +1 -0
  49. package/dist/service/plugin-endpoints.cjs.js +121 -0
  50. package/dist/service/plugin-endpoints.cjs.js.map +1 -0
  51. package/dist/service/policies-rest-api.cjs.js +949 -0
  52. package/dist/service/policies-rest-api.cjs.js.map +1 -0
  53. package/dist/service/policy-builder.cjs.js +134 -0
  54. package/dist/service/policy-builder.cjs.js.map +1 -0
  55. package/dist/service/router.cjs.js +24 -0
  56. package/dist/service/router.cjs.js.map +1 -0
  57. package/dist/validation/condition-validation.cjs.js +107 -0
  58. package/dist/validation/condition-validation.cjs.js.map +1 -0
  59. package/dist/validation/policies-validation.cjs.js +194 -0
  60. package/dist/validation/policies-validation.cjs.js.map +1 -0
  61. package/migrations/20231015161232_migrations.js +41 -0
  62. package/migrations/20231212224526_migrations.js +84 -0
  63. package/migrations/20231221113214_migrations.js +60 -0
  64. package/migrations/20240201144429_migrations.js +37 -0
  65. package/migrations/20240215154456_migrations.js +143 -0
  66. package/migrations/20240308134410_migrations.js +31 -0
  67. package/migrations/20240308134941_migrations.js +43 -0
  68. package/migrations/20240404111242_migrations.js +53 -0
  69. package/migrations/20240611092136_migrations.js +29 -0
  70. package/package.json +98 -0
@@ -0,0 +1,76 @@
1
+ 'use strict';
2
+
3
+ var pluginRbacCommon = require('@backstage-community/plugin-rbac-common');
4
+
5
+ function isOwnerRefsAlias(value) {
6
+ const alias = `${pluginRbacCommon.CONDITION_ALIAS_SIGN}${pluginRbacCommon.ConditionalAliases.OWNER_REFS}`;
7
+ return value === alias;
8
+ }
9
+ function isCurrentUserAlias(value) {
10
+ const alias = `${pluginRbacCommon.CONDITION_ALIAS_SIGN}${pluginRbacCommon.ConditionalAliases.CURRENT_USER}`;
11
+ return value === alias;
12
+ }
13
+ function replaceAliasWithValue(params, key, predicate, newValue) {
14
+ if (!params) {
15
+ return params;
16
+ }
17
+ if (Array.isArray(params[key])) {
18
+ const oldValues = params[key];
19
+ const nonAliasValues = [];
20
+ for (const oldValue2 of oldValues) {
21
+ const isAliasMatched2 = predicate(oldValue2);
22
+ if (isAliasMatched2) {
23
+ const newValues = Array.isArray(newValue) ? newValue : [newValue];
24
+ nonAliasValues.push(...newValues);
25
+ } else {
26
+ nonAliasValues.push(oldValue2);
27
+ }
28
+ }
29
+ return { ...params, [key]: nonAliasValues };
30
+ }
31
+ const oldValue = params[key];
32
+ const isAliasMatched = predicate(oldValue);
33
+ if (isAliasMatched && !Array.isArray(newValue)) {
34
+ return { ...params, [key]: newValue };
35
+ }
36
+ return params;
37
+ }
38
+ function replaceAliases(conditions, userInfo) {
39
+ if ("not" in conditions) {
40
+ replaceAliases(conditions.not, userInfo);
41
+ return;
42
+ }
43
+ if ("allOf" in conditions) {
44
+ for (const condition of conditions.allOf) {
45
+ replaceAliases(condition, userInfo);
46
+ }
47
+ return;
48
+ }
49
+ if ("anyOf" in conditions) {
50
+ for (const condition of conditions.anyOf) {
51
+ replaceAliases(condition, userInfo);
52
+ return;
53
+ }
54
+ }
55
+ const params = conditions.params;
56
+ if (params) {
57
+ for (const key of Object.keys(params)) {
58
+ let modifiedParams = replaceAliasWithValue(
59
+ params,
60
+ key,
61
+ isCurrentUserAlias,
62
+ userInfo.userEntityRef
63
+ );
64
+ modifiedParams = replaceAliasWithValue(
65
+ modifiedParams,
66
+ key,
67
+ isOwnerRefsAlias,
68
+ userInfo.ownershipEntityRefs
69
+ );
70
+ conditions.params = modifiedParams;
71
+ }
72
+ }
73
+ }
74
+
75
+ exports.replaceAliases = replaceAliases;
76
+ //# sourceMappingURL=alias-resolver.cjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"alias-resolver.cjs.js","sources":["../../src/conditional-aliases/alias-resolver.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 { BackstageUserInfo } from '@backstage/backend-plugin-api';\nimport type {\n PermissionCondition,\n PermissionCriteria,\n PermissionRuleParam,\n PermissionRuleParams,\n} from '@backstage/plugin-permission-common';\nimport type { JsonPrimitive } from '@backstage/types';\n\nimport {\n CONDITION_ALIAS_SIGN,\n ConditionalAliases,\n} from '@backstage-community/plugin-rbac-common';\n\ninterface Predicate<T> {\n (item: T): boolean;\n}\n\nfunction isOwnerRefsAlias(value: PermissionRuleParam): boolean {\n const alias = `${CONDITION_ALIAS_SIGN}${ConditionalAliases.OWNER_REFS}`;\n return value === alias;\n}\n\nfunction isCurrentUserAlias(value: PermissionRuleParam): boolean {\n const alias = `${CONDITION_ALIAS_SIGN}${ConditionalAliases.CURRENT_USER}`;\n return value === alias;\n}\n\nfunction replaceAliasWithValue<\n K extends string,\n V extends JsonPrimitive | JsonPrimitive[],\n>(\n params: Record<K, PermissionRuleParam> | undefined,\n key: K,\n predicate: Predicate<PermissionRuleParam>,\n newValue: V,\n): Record<K, PermissionRuleParam> | undefined {\n if (!params) {\n return params;\n }\n\n if (Array.isArray(params[key])) {\n const oldValues = params[key] as JsonPrimitive[];\n const nonAliasValues: JsonPrimitive[] = [];\n for (const oldValue of oldValues) {\n const isAliasMatched = predicate(oldValue);\n if (isAliasMatched) {\n const newValues = Array.isArray(newValue) ? newValue : [newValue];\n nonAliasValues.push(...newValues);\n } else {\n nonAliasValues.push(oldValue);\n }\n }\n return { ...params, [key]: nonAliasValues };\n }\n\n const oldValue = params[key] as JsonPrimitive;\n const isAliasMatched = predicate(oldValue);\n if (isAliasMatched && !Array.isArray(newValue)) {\n return { ...params, [key]: newValue };\n }\n\n return params;\n}\n\nexport function replaceAliases(\n conditions: PermissionCriteria<\n PermissionCondition<string, PermissionRuleParams>\n >,\n userInfo: BackstageUserInfo,\n) {\n if ('not' in conditions) {\n replaceAliases(conditions.not, userInfo);\n return;\n }\n if ('allOf' in conditions) {\n for (const condition of conditions.allOf) {\n replaceAliases(condition, userInfo);\n }\n return;\n }\n if ('anyOf' in conditions) {\n for (const condition of conditions.anyOf) {\n replaceAliases(condition, userInfo);\n return;\n }\n }\n\n const params = (\n conditions as PermissionCondition<string, PermissionRuleParams>\n ).params;\n if (params) {\n for (const key of Object.keys(params)) {\n let modifiedParams = replaceAliasWithValue(\n params,\n key,\n isCurrentUserAlias,\n userInfo.userEntityRef,\n );\n\n modifiedParams = replaceAliasWithValue(\n modifiedParams,\n key,\n isOwnerRefsAlias,\n userInfo.ownershipEntityRefs,\n );\n\n (conditions as PermissionCondition<string, PermissionRuleParams>).params =\n modifiedParams;\n }\n }\n}\n"],"names":["CONDITION_ALIAS_SIGN","ConditionalAliases","oldValue","isAliasMatched"],"mappings":"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"}
@@ -0,0 +1,87 @@
1
+ 'use strict';
2
+
3
+ var TypeORMAdapter = require('typeorm-adapter');
4
+ var path = require('path');
5
+ require('@backstage/backend-defaults/database');
6
+
7
+ function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e : { default: e }; }
8
+
9
+ var TypeORMAdapter__default = /*#__PURE__*/_interopDefaultCompat(TypeORMAdapter);
10
+
11
+ const DEFAULT_SQLITE3_STORAGE_FILE_NAME = "rbac.sqlite";
12
+ class CasbinDBAdapterFactory {
13
+ constructor(config, databaseClient) {
14
+ this.config = config;
15
+ this.databaseClient = databaseClient;
16
+ }
17
+ async createAdapter() {
18
+ const databaseConfig = this.config.getOptionalConfig("backend.database");
19
+ const client = databaseConfig?.getOptionalString("client");
20
+ let adapter;
21
+ if (client === "pg") {
22
+ const dbName = await this.databaseClient.client.config.connection.database;
23
+ const schema = await this.databaseClient.client.searchPath?.[0] ?? "public";
24
+ const ssl = this.handlePostgresSSL(databaseConfig);
25
+ adapter = await TypeORMAdapter__default.default.newAdapter({
26
+ type: "postgres",
27
+ host: databaseConfig?.getString("connection.host"),
28
+ port: databaseConfig?.getNumber("connection.port"),
29
+ username: databaseConfig?.getString("connection.user"),
30
+ password: databaseConfig?.getString("connection.password"),
31
+ ssl,
32
+ database: dbName,
33
+ schema
34
+ });
35
+ }
36
+ if (client === "better-sqlite3") {
37
+ let storage;
38
+ if (typeof databaseConfig?.get("connection")?.valueOf() === "string") {
39
+ storage = databaseConfig?.getString("connection");
40
+ } else if (databaseConfig?.has("connection.directory")) {
41
+ const storageDir = databaseConfig?.getString("connection.directory");
42
+ storage = path.resolve(storageDir, DEFAULT_SQLITE3_STORAGE_FILE_NAME);
43
+ }
44
+ adapter = await TypeORMAdapter__default.default.newAdapter({
45
+ type: "better-sqlite3",
46
+ // Storage type or path to the storage.
47
+ database: storage || ":memory:"
48
+ });
49
+ }
50
+ if (!adapter) {
51
+ throw new Error(`Unsupported database client ${client}`);
52
+ }
53
+ return adapter;
54
+ }
55
+ handlePostgresSSL(dbConfig) {
56
+ const connection = dbConfig.getOptional(
57
+ "connection"
58
+ );
59
+ if (!connection) {
60
+ return void 0;
61
+ }
62
+ if (typeof connection === "string" || connection instanceof String) {
63
+ throw new Error(
64
+ `rbac backend plugin doesn't support postgres connection in a string format yet`
65
+ );
66
+ }
67
+ const ssl = connection.ssl;
68
+ if (ssl === void 0) {
69
+ return void 0;
70
+ }
71
+ if (typeof ssl === "boolean") {
72
+ return ssl;
73
+ }
74
+ if (typeof ssl === "object") {
75
+ const { ca, rejectUnauthorized } = ssl;
76
+ const tlsOpts = { ca, rejectUnauthorized };
77
+ if (Object.values(tlsOpts).every((el) => el === void 0)) {
78
+ return true;
79
+ }
80
+ return tlsOpts;
81
+ }
82
+ return void 0;
83
+ }
84
+ }
85
+
86
+ exports.CasbinDBAdapterFactory = CasbinDBAdapterFactory;
87
+ //# sourceMappingURL=casbin-adapter-factory.cjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"casbin-adapter-factory.cjs.js","sources":["../../src/database/casbin-adapter-factory.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 { Config } from '@backstage/config';\nimport type { ConfigApi } from '@backstage/core-plugin-api';\n\nimport { Knex } from 'knex';\nimport TypeORMAdapter from 'typeorm-adapter';\n\nimport { resolve } from 'path';\nimport type { ConnectionOptions, TlsOptions } from 'tls';\n\nimport '@backstage/backend-defaults/database';\n\nconst DEFAULT_SQLITE3_STORAGE_FILE_NAME = 'rbac.sqlite';\n\nexport class CasbinDBAdapterFactory {\n public constructor(\n private readonly config: ConfigApi,\n private readonly databaseClient: Knex,\n ) {}\n\n public async createAdapter(): Promise<TypeORMAdapter> {\n const databaseConfig = this.config.getOptionalConfig('backend.database');\n const client = databaseConfig?.getOptionalString('client');\n\n let adapter;\n if (client === 'pg') {\n const dbName = await this.databaseClient.client.config.connection\n .database;\n const schema =\n (await this.databaseClient.client.searchPath?.[0]) ?? 'public';\n\n const ssl = this.handlePostgresSSL(databaseConfig!);\n\n adapter = await TypeORMAdapter.newAdapter({\n type: 'postgres',\n host: databaseConfig?.getString('connection.host'),\n port: databaseConfig?.getNumber('connection.port'),\n username: databaseConfig?.getString('connection.user'),\n password: databaseConfig?.getString('connection.password'),\n ssl,\n database: dbName,\n schema: schema,\n });\n }\n\n if (client === 'better-sqlite3') {\n let storage;\n if (typeof databaseConfig?.get('connection')?.valueOf() === 'string') {\n storage = databaseConfig?.getString('connection');\n } else if (databaseConfig?.has('connection.directory')) {\n const storageDir = databaseConfig?.getString('connection.directory');\n storage = resolve(storageDir, DEFAULT_SQLITE3_STORAGE_FILE_NAME);\n }\n\n adapter = await TypeORMAdapter.newAdapter({\n type: 'better-sqlite3',\n // Storage type or path to the storage.\n database: storage || ':memory:',\n });\n }\n\n if (!adapter) {\n throw new Error(`Unsupported database client ${client}`);\n }\n\n return adapter;\n }\n\n private handlePostgresSSL(\n dbConfig: Config,\n ): boolean | TlsOptions | undefined {\n const connection = dbConfig.getOptional<Knex.PgConnectionConfig | string>(\n 'connection',\n );\n if (!connection) {\n return undefined;\n }\n\n if (typeof connection === 'string' || connection instanceof String) {\n throw new Error(\n `rbac backend plugin doesn't support postgres connection in a string format yet`,\n );\n }\n\n const ssl: boolean | ConnectionOptions | undefined = connection.ssl;\n\n if (ssl === undefined) {\n return undefined;\n }\n\n if (typeof ssl === 'boolean') {\n return ssl;\n }\n\n if (typeof ssl === 'object') {\n const { ca, rejectUnauthorized } = ssl as ConnectionOptions;\n const tlsOpts = { ca, rejectUnauthorized };\n\n // SSL object was defined with some options that we don't support yet.\n if (Object.values(tlsOpts).every(el => el === undefined)) {\n return true;\n }\n\n return tlsOpts;\n }\n\n return undefined;\n }\n}\n"],"names":["TypeORMAdapter","resolve"],"mappings":"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"}
@@ -0,0 +1,172 @@
1
+ 'use strict';
2
+
3
+ var errors = require('@backstage/errors');
4
+
5
+ const CONDITIONAL_TABLE = "role-condition-policies";
6
+ class DataBaseConditionalStorage {
7
+ constructor(knex) {
8
+ this.knex = knex;
9
+ }
10
+ async filterConditions(roleEntityRef, pluginId, resourceType, actions, permissionNames) {
11
+ const daoRaws = await this.knex.table(CONDITIONAL_TABLE).where((builder) => {
12
+ if (pluginId) {
13
+ builder.where("pluginId", pluginId);
14
+ }
15
+ if (resourceType) {
16
+ builder.where("resourceType", resourceType);
17
+ }
18
+ if (roleEntityRef) {
19
+ if (Array.isArray(roleEntityRef)) {
20
+ builder.whereIn("roleEntityRef", roleEntityRef);
21
+ } else {
22
+ builder.where("roleEntityRef", roleEntityRef);
23
+ }
24
+ }
25
+ });
26
+ let conditions = [];
27
+ if (daoRaws) {
28
+ conditions = daoRaws.map((dao) => this.daoToConditionalDecision(dao));
29
+ }
30
+ if (permissionNames && permissionNames.length > 0) {
31
+ conditions = conditions.filter((condition) => {
32
+ return permissionNames.every(
33
+ (permissionName) => condition.permissionMapping.map((permInfo) => permInfo.name).includes(permissionName)
34
+ );
35
+ });
36
+ }
37
+ if (actions && actions.length > 0) {
38
+ conditions = conditions.filter((condition) => {
39
+ return actions.every(
40
+ (action) => condition.permissionMapping.map((permInfo) => permInfo.action).includes(action)
41
+ );
42
+ });
43
+ }
44
+ return conditions;
45
+ }
46
+ async createCondition(conditionalDecision) {
47
+ await this.checkConflictedConditions(
48
+ conditionalDecision.roleEntityRef,
49
+ conditionalDecision.resourceType,
50
+ conditionalDecision.pluginId,
51
+ conditionalDecision.permissionMapping.map((permInfo) => permInfo.action)
52
+ );
53
+ const conditionRaw = this.toDAO(conditionalDecision);
54
+ const result = await this.knex.table(CONDITIONAL_TABLE).insert(conditionRaw).returning("id");
55
+ if (result && result?.length > 0) {
56
+ return result[0].id;
57
+ }
58
+ throw new Error(`Failed to create the condition.`);
59
+ }
60
+ async checkConflictedConditions(roleEntityRef, resourceType, pluginId, queryConditionActions, idToExclude) {
61
+ let conditionsForTheSameResource = await this.filterConditions(
62
+ roleEntityRef,
63
+ pluginId,
64
+ resourceType
65
+ );
66
+ conditionsForTheSameResource = conditionsForTheSameResource.filter(
67
+ (c) => c.id !== idToExclude
68
+ );
69
+ if (conditionsForTheSameResource) {
70
+ const conflictedCondition = conditionsForTheSameResource.find(
71
+ (condition) => {
72
+ const conditionActions = condition.permissionMapping.map(
73
+ (permInfo) => permInfo.action
74
+ );
75
+ return queryConditionActions.some(
76
+ (action) => conditionActions.includes(action)
77
+ );
78
+ }
79
+ );
80
+ if (conflictedCondition) {
81
+ const conflictedActions = queryConditionActions.filter(
82
+ (action) => conflictedCondition.permissionMapping.some((p) => p.action === action)
83
+ );
84
+ throw new errors.ConflictError(
85
+ `Found condition with conflicted permission action '${JSON.stringify(
86
+ conflictedActions
87
+ )}'. Role could have multiple conditions for the same resource type '${conflictedCondition.resourceType}', but with different permission action sets.`
88
+ );
89
+ }
90
+ }
91
+ }
92
+ async getCondition(id) {
93
+ const daoRaw = await this.knex.table(CONDITIONAL_TABLE).where("id", id).first();
94
+ if (daoRaw) {
95
+ return this.daoToConditionalDecision(daoRaw);
96
+ }
97
+ return void 0;
98
+ }
99
+ async deleteCondition(id) {
100
+ const condition = await this.getCondition(id);
101
+ if (!condition) {
102
+ throw new errors.NotFoundError(`Condition with id ${id} was not found`);
103
+ }
104
+ await this.knex?.table(CONDITIONAL_TABLE).delete().whereIn("id", [id]);
105
+ }
106
+ async updateCondition(id, conditionalDecision) {
107
+ const condition = await this.getCondition(id);
108
+ if (!condition) {
109
+ throw new errors.NotFoundError(`Condition with id ${id} was not found`);
110
+ }
111
+ await this.checkConflictedConditions(
112
+ conditionalDecision.roleEntityRef,
113
+ conditionalDecision.resourceType,
114
+ conditionalDecision.pluginId,
115
+ conditionalDecision.permissionMapping.map((perm) => perm.action),
116
+ id
117
+ );
118
+ const conditionRaw = this.toDAO(conditionalDecision);
119
+ conditionRaw.id = id;
120
+ const result = await this.knex.table(CONDITIONAL_TABLE).where("id", conditionRaw.id).update(conditionRaw).returning("id");
121
+ if (!result || result.length === 0) {
122
+ throw new Error(`Failed to update the condition with id: ${id}.`);
123
+ }
124
+ }
125
+ toDAO(conditionalDecision) {
126
+ const {
127
+ result,
128
+ pluginId,
129
+ resourceType,
130
+ conditions,
131
+ roleEntityRef,
132
+ permissionMapping
133
+ } = conditionalDecision;
134
+ const conditionsJson = JSON.stringify(conditions);
135
+ return {
136
+ result,
137
+ pluginId,
138
+ resourceType,
139
+ conditionsJson,
140
+ roleEntityRef,
141
+ permissions: JSON.stringify(permissionMapping)
142
+ };
143
+ }
144
+ daoToConditionalDecision(dao) {
145
+ if (!dao.id) {
146
+ throw new errors.InputError(`Missed id in the dao object: ${dao}`);
147
+ }
148
+ const {
149
+ id,
150
+ result,
151
+ pluginId,
152
+ resourceType,
153
+ conditionsJson,
154
+ roleEntityRef,
155
+ permissions
156
+ } = dao;
157
+ const conditions = JSON.parse(conditionsJson);
158
+ return {
159
+ id,
160
+ result,
161
+ pluginId,
162
+ resourceType,
163
+ conditions,
164
+ roleEntityRef,
165
+ permissionMapping: JSON.parse(permissions)
166
+ };
167
+ }
168
+ }
169
+
170
+ exports.CONDITIONAL_TABLE = CONDITIONAL_TABLE;
171
+ exports.DataBaseConditionalStorage = DataBaseConditionalStorage;
172
+ //# sourceMappingURL=conditional-storage.cjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"conditional-storage.cjs.js","sources":["../../src/database/conditional-storage.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 { ConflictError, InputError, NotFoundError } from '@backstage/errors';\nimport { AuthorizeResult } from '@backstage/plugin-permission-common';\n\nimport { Knex } from 'knex';\n\nimport type {\n PermissionAction,\n PermissionInfo,\n RoleConditionalPolicyDecision,\n} from '@backstage-community/plugin-rbac-common';\n\nexport const CONDITIONAL_TABLE = 'role-condition-policies';\n\nexport interface ConditionalPolicyDecisionDAO {\n result: AuthorizeResult.CONDITIONAL;\n id?: number;\n roleEntityRef: string;\n permissions: string;\n pluginId: string;\n resourceType: string;\n conditionsJson: string;\n}\n\nexport interface ConditionalStorage {\n filterConditions(\n roleEntityRef?: string | string[],\n pluginId?: string,\n resourceType?: string,\n actions?: PermissionAction[],\n permissionNames?: string[],\n ): Promise<RoleConditionalPolicyDecision<PermissionInfo>[]>;\n createCondition(\n conditionalDecision: RoleConditionalPolicyDecision<PermissionInfo>,\n ): Promise<number>;\n checkConflictedConditions(\n roleEntityRef: string,\n resourceType: string,\n pluginId: string,\n queryPermissionNames: string[],\n idToExclude?: number,\n ): Promise<void>;\n getCondition(\n id: number,\n ): Promise<RoleConditionalPolicyDecision<PermissionInfo> | undefined>;\n deleteCondition(id: number): Promise<void>;\n updateCondition(\n id: number,\n conditionalDecision: RoleConditionalPolicyDecision<PermissionInfo>,\n ): Promise<void>;\n}\n\nexport class DataBaseConditionalStorage implements ConditionalStorage {\n public constructor(private readonly knex: Knex<any, any[]>) {}\n\n async filterConditions(\n roleEntityRef?: string | string[],\n pluginId?: string,\n resourceType?: string,\n actions?: PermissionAction[],\n permissionNames?: string[],\n ): Promise<RoleConditionalPolicyDecision<PermissionInfo>[]> {\n const daoRaws = await this.knex.table(CONDITIONAL_TABLE).where(builder => {\n if (pluginId) {\n builder.where('pluginId', pluginId);\n }\n if (resourceType) {\n builder.where('resourceType', resourceType);\n }\n if (roleEntityRef) {\n if (Array.isArray(roleEntityRef)) {\n builder.whereIn('roleEntityRef', roleEntityRef);\n } else {\n builder.where('roleEntityRef', roleEntityRef);\n }\n }\n });\n\n let conditions: RoleConditionalPolicyDecision<PermissionInfo>[] = [];\n if (daoRaws) {\n conditions = daoRaws.map(dao => this.daoToConditionalDecision(dao));\n }\n\n if (permissionNames && permissionNames.length > 0) {\n conditions = conditions.filter(condition => {\n return permissionNames.every(permissionName =>\n condition.permissionMapping\n .map(permInfo => permInfo.name)\n .includes(permissionName),\n );\n });\n }\n\n if (actions && actions.length > 0) {\n conditions = conditions.filter(condition => {\n return actions.every(action =>\n condition.permissionMapping\n .map(permInfo => permInfo.action)\n .includes(action),\n );\n });\n }\n\n return conditions;\n }\n\n async createCondition(\n conditionalDecision: RoleConditionalPolicyDecision<PermissionInfo>,\n ): Promise<number> {\n await this.checkConflictedConditions(\n conditionalDecision.roleEntityRef,\n conditionalDecision.resourceType,\n conditionalDecision.pluginId,\n conditionalDecision.permissionMapping.map(permInfo => permInfo.action),\n );\n\n const conditionRaw = this.toDAO(conditionalDecision);\n const result = await this.knex\n .table(CONDITIONAL_TABLE)\n .insert<ConditionalPolicyDecisionDAO>(conditionRaw)\n .returning('id');\n if (result && result?.length > 0) {\n return result[0].id;\n }\n\n throw new Error(`Failed to create the condition.`);\n }\n\n async checkConflictedConditions(\n roleEntityRef: string,\n resourceType: string,\n pluginId: string,\n queryConditionActions: PermissionAction[],\n idToExclude?: number,\n ): Promise<void> {\n let conditionsForTheSameResource = await this.filterConditions(\n roleEntityRef,\n pluginId,\n resourceType,\n );\n conditionsForTheSameResource = conditionsForTheSameResource.filter(\n c => c.id !== idToExclude,\n );\n\n if (conditionsForTheSameResource) {\n const conflictedCondition = conditionsForTheSameResource.find(\n condition => {\n const conditionActions = condition.permissionMapping.map(\n permInfo => permInfo.action,\n );\n return queryConditionActions.some(action =>\n conditionActions.includes(action),\n );\n },\n );\n\n if (conflictedCondition) {\n const conflictedActions = queryConditionActions.filter(action =>\n conflictedCondition.permissionMapping.some(p => p.action === action),\n );\n throw new ConflictError(\n `Found condition with conflicted permission action '${JSON.stringify(\n conflictedActions,\n )}'. Role could have multiple ` +\n `conditions for the same resource type '${conflictedCondition.resourceType}', but with different permission action sets.`,\n );\n }\n }\n }\n\n async getCondition(\n id: number,\n ): Promise<RoleConditionalPolicyDecision<PermissionInfo> | undefined> {\n const daoRaw = await this.knex\n .table(CONDITIONAL_TABLE)\n .where('id', id)\n .first();\n\n if (daoRaw) {\n return this.daoToConditionalDecision(daoRaw);\n }\n return undefined;\n }\n\n async deleteCondition(id: number): Promise<void> {\n const condition = await this.getCondition(id);\n if (!condition) {\n throw new NotFoundError(`Condition with id ${id} was not found`);\n }\n await this.knex?.table(CONDITIONAL_TABLE).delete().whereIn('id', [id]);\n }\n\n async updateCondition(\n id: number,\n conditionalDecision: RoleConditionalPolicyDecision<PermissionInfo>,\n ): Promise<void> {\n const condition = await this.getCondition(id);\n if (!condition) {\n throw new NotFoundError(`Condition with id ${id} was not found`);\n }\n\n await this.checkConflictedConditions(\n conditionalDecision.roleEntityRef,\n conditionalDecision.resourceType,\n conditionalDecision.pluginId,\n conditionalDecision.permissionMapping.map(perm => perm.action),\n id,\n );\n\n const conditionRaw = this.toDAO(conditionalDecision);\n conditionRaw.id = id;\n const result = await this.knex\n .table(CONDITIONAL_TABLE)\n .where('id', conditionRaw.id)\n .update<ConditionalPolicyDecisionDAO>(conditionRaw)\n .returning('id');\n\n if (!result || result.length === 0) {\n throw new Error(`Failed to update the condition with id: ${id}.`);\n }\n }\n\n private toDAO(\n conditionalDecision: RoleConditionalPolicyDecision<PermissionInfo>,\n ): ConditionalPolicyDecisionDAO {\n const {\n result,\n pluginId,\n resourceType,\n conditions,\n roleEntityRef,\n permissionMapping,\n } = conditionalDecision;\n const conditionsJson = JSON.stringify(conditions);\n return {\n result,\n pluginId,\n resourceType,\n conditionsJson,\n roleEntityRef,\n permissions: JSON.stringify(permissionMapping),\n };\n }\n\n private daoToConditionalDecision(\n dao: ConditionalPolicyDecisionDAO,\n ): RoleConditionalPolicyDecision<PermissionInfo> {\n if (!dao.id) {\n throw new InputError(`Missed id in the dao object: ${dao}`);\n }\n const {\n id,\n result,\n pluginId,\n resourceType,\n conditionsJson,\n roleEntityRef,\n permissions,\n } = dao;\n\n const conditions = JSON.parse(conditionsJson);\n return {\n id,\n result,\n pluginId,\n resourceType,\n conditions,\n roleEntityRef,\n permissionMapping: JSON.parse(permissions),\n };\n }\n}\n"],"names":["ConflictError","NotFoundError","InputError"],"mappings":"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"}
@@ -0,0 +1,21 @@
1
+ 'use strict';
2
+
3
+ var backendPluginApi = require('@backstage/backend-plugin-api');
4
+
5
+ const migrationsDir = backendPluginApi.resolvePackagePath(
6
+ "@backstage-community/plugin-rbac-backend",
7
+ // Package name
8
+ "migrations"
9
+ // Migrations directory
10
+ );
11
+ async function migrate(databaseManager) {
12
+ const knex = await databaseManager.getClient();
13
+ if (!databaseManager.migrations?.skip) {
14
+ await knex.migrate.latest({
15
+ directory: migrationsDir
16
+ });
17
+ }
18
+ }
19
+
20
+ exports.migrate = migrate;
21
+ //# sourceMappingURL=migration.cjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"migration.cjs.js","sources":["../../src/database/migration.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 {\n DatabaseService,\n resolvePackagePath,\n} from '@backstage/backend-plugin-api';\n\nconst migrationsDir = resolvePackagePath(\n '@backstage-community/plugin-rbac-backend', // Package name\n 'migrations', // Migrations directory\n);\n\nexport async function migrate(databaseManager: DatabaseService) {\n const knex = await databaseManager.getClient();\n\n if (!databaseManager.migrations?.skip) {\n await knex.migrate.latest({\n directory: migrationsDir,\n });\n }\n}\n"],"names":["resolvePackagePath"],"mappings":";;;;AAoBA,MAAM,aAAgB,GAAAA,mCAAA;AAAA,EACpB,0CAAA;AAAA;AAAA,EACA,YAAA;AAAA;AACF,CAAA,CAAA;AAEA,eAAsB,QAAQ,eAAkC,EAAA;AAC9D,EAAM,MAAA,IAAA,GAAO,MAAM,eAAA,CAAgB,SAAU,EAAA,CAAA;AAE7C,EAAI,IAAA,CAAC,eAAgB,CAAA,UAAA,EAAY,IAAM,EAAA;AACrC,IAAM,MAAA,IAAA,CAAK,QAAQ,MAAO,CAAA;AAAA,MACxB,SAAW,EAAA,aAAA;AAAA,KACZ,CAAA,CAAA;AAAA,GACH;AACF;;;;"}
@@ -0,0 +1,89 @@
1
+ 'use strict';
2
+
3
+ var errors = require('@backstage/errors');
4
+ var helper = require('../helper.cjs.js');
5
+
6
+ const ROLE_METADATA_TABLE = "role-metadata";
7
+ class DataBaseRoleMetadataStorage {
8
+ constructor(knex) {
9
+ this.knex = knex;
10
+ }
11
+ async filterRoleMetadata(source) {
12
+ return await this.knex.table(ROLE_METADATA_TABLE).where((builder) => {
13
+ if (source) {
14
+ builder.where("source", source);
15
+ }
16
+ });
17
+ }
18
+ async findRoleMetadata(roleEntityRef, trx) {
19
+ const db = trx || this.knex;
20
+ return await db.table(ROLE_METADATA_TABLE).where("roleEntityRef", roleEntityRef).first();
21
+ }
22
+ async createRoleMetadata(metadata, trx) {
23
+ if (await this.findRoleMetadata(metadata.roleEntityRef, trx)) {
24
+ throw new errors.ConflictError(
25
+ `A metadata for role ${metadata.roleEntityRef} has already been stored`
26
+ );
27
+ }
28
+ const result = await trx(ROLE_METADATA_TABLE).insert(metadata).returning("id");
29
+ if (result && result?.length > 0) {
30
+ return result[0].id;
31
+ }
32
+ throw new Error(
33
+ `Failed to create the role metadata: '${JSON.stringify(metadata)}'.`
34
+ );
35
+ }
36
+ async updateRoleMetadata(newRoleMetadata, oldRoleEntityRef, externalTrx) {
37
+ const trx = externalTrx ?? await this.knex.transaction();
38
+ const currentMetadataDao = await this.findRoleMetadata(
39
+ oldRoleEntityRef,
40
+ trx
41
+ );
42
+ if (!currentMetadataDao) {
43
+ throw new errors.NotFoundError(
44
+ `A metadata for role '${oldRoleEntityRef}' was not found`
45
+ );
46
+ }
47
+ if (currentMetadataDao.source !== "legacy" && currentMetadataDao.source !== newRoleMetadata.source) {
48
+ throw new errors.InputError(`The RoleMetadata.source field is 'read-only'.`);
49
+ }
50
+ if (helper.deepSortedEqual(currentMetadataDao, newRoleMetadata)) {
51
+ return;
52
+ }
53
+ const result = await trx(ROLE_METADATA_TABLE).where("id", currentMetadataDao.id).update(newRoleMetadata).returning("id");
54
+ if (!externalTrx) {
55
+ await trx.commit();
56
+ }
57
+ if (!result || result.length === 0) {
58
+ throw new Error(
59
+ `Failed to update the role metadata '${JSON.stringify(
60
+ currentMetadataDao
61
+ )}' with new value: '${JSON.stringify(newRoleMetadata)}'.`
62
+ );
63
+ }
64
+ }
65
+ async removeRoleMetadata(roleEntityRef, trx) {
66
+ const metadataDao = await this.findRoleMetadata(roleEntityRef, trx);
67
+ if (!metadataDao) {
68
+ throw new errors.NotFoundError(
69
+ `A metadata for role '${roleEntityRef}' was not found`
70
+ );
71
+ }
72
+ await trx(ROLE_METADATA_TABLE).delete().whereIn("id", [metadataDao.id]);
73
+ }
74
+ }
75
+ function daoToMetadata(dao) {
76
+ return {
77
+ source: dao.source,
78
+ description: dao.description,
79
+ author: dao.author,
80
+ modifiedBy: dao.modifiedBy,
81
+ createdAt: dao.createdAt,
82
+ lastModified: dao.lastModified
83
+ };
84
+ }
85
+
86
+ exports.DataBaseRoleMetadataStorage = DataBaseRoleMetadataStorage;
87
+ exports.ROLE_METADATA_TABLE = ROLE_METADATA_TABLE;
88
+ exports.daoToMetadata = daoToMetadata;
89
+ //# sourceMappingURL=role-metadata.cjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"role-metadata.cjs.js","sources":["../../src/database/role-metadata.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 { ConflictError, InputError, NotFoundError } from '@backstage/errors';\n\nimport { Knex } from 'knex';\n\nimport type {\n RoleMetadata,\n Source,\n} from '@backstage-community/plugin-rbac-common';\n\nimport { deepSortedEqual } from '../helper';\n\nexport const ROLE_METADATA_TABLE = 'role-metadata';\n\nexport interface RoleMetadataDao extends RoleMetadata {\n id?: number;\n roleEntityRef: string;\n source: Source;\n modifiedBy: string;\n}\n\nexport interface RoleMetadataStorage {\n filterRoleMetadata(source?: Source): Promise<RoleMetadataDao[]>;\n findRoleMetadata(\n roleEntityRef: string,\n trx?: Knex.Transaction,\n ): Promise<RoleMetadataDao | undefined>;\n createRoleMetadata(\n roleMetadata: RoleMetadataDao,\n trx: Knex.Transaction,\n ): Promise<number>;\n updateRoleMetadata(\n roleMetadata: RoleMetadataDao,\n oldRoleEntityRef: string,\n externalTrx?: Knex.Transaction,\n ): Promise<void>;\n removeRoleMetadata(\n roleEntityRef: string,\n trx: Knex.Transaction,\n ): Promise<void>;\n}\n\nexport class DataBaseRoleMetadataStorage implements RoleMetadataStorage {\n constructor(private readonly knex: Knex<any, any[]>) {}\n\n async filterRoleMetadata(source?: Source): Promise<RoleMetadataDao[]> {\n return await this.knex.table(ROLE_METADATA_TABLE).where(builder => {\n if (source) {\n builder.where('source', source);\n }\n });\n }\n\n async findRoleMetadata(\n roleEntityRef: string,\n trx: Knex.Transaction,\n ): Promise<RoleMetadataDao | undefined> {\n const db = trx || this.knex;\n return await db\n .table(ROLE_METADATA_TABLE)\n .where('roleEntityRef', roleEntityRef)\n // roleEntityRef should be unique.\n .first();\n }\n\n async createRoleMetadata(\n metadata: RoleMetadataDao,\n trx: Knex.Transaction,\n ): Promise<number> {\n if (await this.findRoleMetadata(metadata.roleEntityRef, trx)) {\n throw new ConflictError(\n `A metadata for role ${metadata.roleEntityRef} has already been stored`,\n );\n }\n\n const result = await trx<RoleMetadataDao>(ROLE_METADATA_TABLE)\n .insert(metadata)\n .returning<[{ id: number }]>('id');\n if (result && result?.length > 0) {\n return result[0].id;\n }\n\n throw new Error(\n `Failed to create the role metadata: '${JSON.stringify(metadata)}'.`,\n );\n }\n\n async updateRoleMetadata(\n newRoleMetadata: RoleMetadataDao,\n oldRoleEntityRef: string,\n externalTrx?: Knex.Transaction,\n ): Promise<void> {\n const trx = externalTrx ?? (await this.knex.transaction());\n const currentMetadataDao = await this.findRoleMetadata(\n oldRoleEntityRef,\n trx,\n );\n\n if (!currentMetadataDao) {\n throw new NotFoundError(\n `A metadata for role '${oldRoleEntityRef}' was not found`,\n );\n }\n\n if (\n currentMetadataDao.source !== 'legacy' &&\n currentMetadataDao.source !== newRoleMetadata.source\n ) {\n throw new InputError(`The RoleMetadata.source field is 'read-only'.`);\n }\n\n if (deepSortedEqual(currentMetadataDao, newRoleMetadata)) {\n return;\n }\n\n const result = await trx<RoleMetadataDao>(ROLE_METADATA_TABLE)\n .where('id', currentMetadataDao.id)\n .update(newRoleMetadata)\n .returning('id');\n\n if (!externalTrx) {\n await trx.commit();\n }\n\n if (!result || result.length === 0) {\n throw new Error(\n `Failed to update the role metadata '${JSON.stringify(\n currentMetadataDao,\n )}' with new value: '${JSON.stringify(newRoleMetadata)}'.`,\n );\n }\n }\n\n async removeRoleMetadata(\n roleEntityRef: string,\n trx: Knex.Transaction,\n ): Promise<void> {\n const metadataDao = await this.findRoleMetadata(roleEntityRef, trx);\n if (!metadataDao) {\n throw new NotFoundError(\n `A metadata for role '${roleEntityRef}' was not found`,\n );\n }\n\n await trx<RoleMetadataDao>(ROLE_METADATA_TABLE)\n .delete()\n .whereIn('id', [metadataDao.id!]);\n }\n}\n\nexport function daoToMetadata(dao: RoleMetadataDao): RoleMetadata {\n return {\n source: dao.source,\n description: dao.description,\n author: dao.author,\n modifiedBy: dao.modifiedBy,\n createdAt: dao.createdAt,\n lastModified: dao.lastModified,\n };\n}\n"],"names":["ConflictError","NotFoundError","InputError","deepSortedEqual"],"mappings":"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"}