@backstage-community/plugin-rbac-backend 7.9.1 → 7.11.0

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.
@@ -5,12 +5,14 @@ var auditor = require('../auditor/auditor.cjs.js');
5
5
  var helper = require('../helper.cjs.js');
6
6
  var permissionModel = require('../service/permission-model.cjs.js');
7
7
  var policiesValidation = require('../validation/policies-validation.cjs.js');
8
+ var lodash = require('lodash');
8
9
 
9
10
  class Connection {
10
- constructor(id, enforcer, roleMetadataStorage, logger, auditor) {
11
+ constructor(id, enforcer, roleMetadataStorage, conditionStorage, logger, auditor) {
11
12
  this.id = id;
12
13
  this.enforcer = enforcer;
13
14
  this.roleMetadataStorage = roleMetadataStorage;
15
+ this.conditionStorage = conditionStorage;
14
16
  this.logger = logger;
15
17
  this.auditor = auditor;
16
18
  }
@@ -49,6 +51,27 @@ class Connection {
49
51
  await this.removePermissions(providerPermissions, tempEnforcer);
50
52
  await this.addPermissions(permissions);
51
53
  }
54
+ async applyConditionalPermissions(conditionalPermissions) {
55
+ const storedConditionalPermissions = await this.conditionStorage.filterConditions();
56
+ const conditionsToBeAdded = conditionalPermissions.filter(
57
+ (conditionalPermission) => !storedConditionalPermissions.some(
58
+ (stored) => conditionalPermission.roleEntityRef === stored.roleEntityRef && conditionalPermission.pluginId === stored.pluginId && conditionalPermission.resourceType === stored.resourceType && lodash.isEqual(
59
+ conditionalPermission.permissionMapping,
60
+ stored.permissionMapping
61
+ ) && lodash.isEqual(conditionalPermission.conditions, stored.conditions)
62
+ )
63
+ );
64
+ const conditionsToBeRemoved = storedConditionalPermissions.filter(
65
+ (stored) => !conditionalPermissions.some(
66
+ (conditionalPermission) => stored.roleEntityRef === conditionalPermission.roleEntityRef && stored.pluginId === conditionalPermission.pluginId && stored.resourceType === conditionalPermission.resourceType && lodash.isEqual(
67
+ stored.permissionMapping,
68
+ conditionalPermission.permissionMapping
69
+ ) && lodash.isEqual(stored.conditions, conditionalPermission.conditions)
70
+ )
71
+ );
72
+ await this.removeConditionalPermissions(conditionsToBeRemoved);
73
+ await this.addConditionalPermissions(conditionsToBeAdded);
74
+ }
52
75
  async addRoles(roles) {
53
76
  for (const role of roles) {
54
77
  if (!await this.enforcer.hasGroupingPolicy(...role)) {
@@ -193,6 +216,62 @@ class Connection {
193
216
  }
194
217
  }
195
218
  }
219
+ async addConditionalPermissions(conditionalPermissions) {
220
+ for (const condition of conditionalPermissions) {
221
+ const auditorMeta = {
222
+ policies: [condition]
223
+ };
224
+ const auditorEvent = await this.auditor.createEvent({
225
+ eventId: auditor.ConditionEvents.CONDITION_WRITE,
226
+ severityLevel: "medium",
227
+ meta: {
228
+ actionType: auditor.ActionType.CREATE,
229
+ source: this.id
230
+ }
231
+ });
232
+ try {
233
+ const metadata = await this.roleMetadataStorage.findRoleMetadata(
234
+ condition.roleEntityRef
235
+ );
236
+ const err = await policiesValidation.validateSource(this.id, metadata);
237
+ if (err) {
238
+ throw err;
239
+ }
240
+ await this.conditionStorage.createCondition(condition);
241
+ await auditorEvent.success({ meta: auditorMeta });
242
+ } catch (error) {
243
+ await auditorEvent.fail({ error, meta: auditorMeta });
244
+ }
245
+ }
246
+ }
247
+ async removeConditionalPermissions(conditionalPermissions) {
248
+ for (const conditionalPermission of conditionalPermissions) {
249
+ const auditorMeta = {
250
+ policies: [conditionalPermission]
251
+ };
252
+ const auditorEvent = await this.auditor.createEvent({
253
+ eventId: auditor.ConditionEvents.CONDITION_WRITE,
254
+ severityLevel: "medium",
255
+ meta: { actionType: auditor.ActionType.DELETE, source: this.id }
256
+ });
257
+ try {
258
+ const metadata = await this.roleMetadataStorage.findRoleMetadata(
259
+ conditionalPermission.roleEntityRef
260
+ );
261
+ const err = await policiesValidation.validateSource(this.id, metadata);
262
+ if (err) {
263
+ throw err;
264
+ }
265
+ await this.conditionStorage.deleteCondition(conditionalPermission.id);
266
+ await auditorEvent.success({ meta: auditorMeta });
267
+ } catch (error) {
268
+ await auditorEvent.fail({
269
+ error,
270
+ meta: auditorMeta
271
+ });
272
+ }
273
+ }
274
+ }
196
275
  async getProviderRoles() {
197
276
  const currentRoles = await this.roleMetadataStorage.filterRoleMetadata(
198
277
  this.id
@@ -200,7 +279,7 @@ class Connection {
200
279
  return currentRoles.map((meta) => meta.roleEntityRef);
201
280
  }
202
281
  }
203
- async function connectRBACProviders(providers, enforcer, roleMetadataStorage, logger, auditor) {
282
+ async function connectRBACProviders(providers, enforcer, roleMetadataStorage, conditionStorage, logger, auditor) {
204
283
  await Promise.all(
205
284
  providers.map(async (provider) => {
206
285
  try {
@@ -208,6 +287,7 @@ async function connectRBACProviders(providers, enforcer, roleMetadataStorage, lo
208
287
  provider.getProviderName(),
209
288
  enforcer,
210
289
  roleMetadataStorage,
290
+ conditionStorage,
211
291
  logger,
212
292
  auditor
213
293
  );
@@ -1 +1 @@
1
- {"version":3,"file":"connect-providers.cjs.js","sources":["../../src/providers/connect-providers.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 {\n AuditorService,\n LoggerService,\n} from '@backstage/backend-plugin-api';\n\nimport {\n Enforcer,\n newEnforcer,\n newModelFromString,\n StringAdapter,\n} from 'casbin';\n\nimport type {\n RBACProvider,\n RBACProviderConnection,\n} from '@backstage-community/plugin-rbac-node';\n\nimport { ActionType, PermissionEvents, RoleEvents } from '../auditor/auditor';\nimport { RoleMetadataStorage } from '../database/role-metadata';\nimport {\n transformArrayToPolicy,\n transformRolesGroupToLowercase,\n typedPoliciesToString,\n} from '../helper';\nimport { EnforcerDelegate } from '../service/enforcer-delegate';\nimport { MODEL } from '../service/permission-model';\nimport {\n validateGroupingPolicy,\n validatePolicy,\n validateSource,\n} from '../validation/policies-validation';\n\nexport class Connection implements RBACProviderConnection {\n constructor(\n private readonly id: string,\n private readonly enforcer: EnforcerDelegate,\n private readonly roleMetadataStorage: RoleMetadataStorage,\n private readonly logger: LoggerService,\n private readonly auditor: AuditorService,\n ) {}\n\n async applyRoles(roles: string[][]): Promise<void> {\n const lowercasedRoles = transformRolesGroupToLowercase(roles);\n const stringPolicy = typedPoliciesToString(lowercasedRoles, 'g');\n const providerRolesforRemoval: string[][] = [];\n\n const tempEnforcer = await newEnforcer(\n newModelFromString(MODEL),\n new StringAdapter(stringPolicy),\n );\n\n const providerRoles = await this.getProviderRoles();\n\n await this.enforcer.loadPolicy();\n // Get the roles for this provider coming from rbac plugin\n for (const providerRole of providerRoles) {\n providerRolesforRemoval.push(\n ...(await this.enforcer.getFilteredGroupingPolicy(1, providerRole)),\n );\n }\n\n // Remove role\n // role exists in rbac but does not exist in provider\n await this.removeRoles(providerRolesforRemoval, tempEnforcer);\n\n // Add the role\n // role exists in provider but does not exist in rbac\n await this.addRoles(lowercasedRoles);\n }\n\n async applyPermissions(permissions: string[][]): Promise<void> {\n const stringPolicy = typedPoliciesToString(permissions, 'p');\n\n const providerPermissions: string[][] = [];\n\n const tempEnforcer = await newEnforcer(\n newModelFromString(MODEL),\n new StringAdapter(stringPolicy),\n );\n\n const providerRoles = await this.getProviderRoles();\n\n await this.enforcer.loadPolicy();\n // Get the roles for this provider coming from rbac plugin\n for (const providerRole of providerRoles) {\n providerPermissions.push(\n ...(await this.enforcer.getFilteredPolicy(0, providerRole)),\n );\n }\n\n await this.removePermissions(providerPermissions, tempEnforcer);\n\n await this.addPermissions(permissions);\n }\n\n private async addRoles(roles: string[][]): Promise<void> {\n for (const role of roles) {\n if (!(await this.enforcer.hasGroupingPolicy(...role))) {\n const metadata = await this.roleMetadataStorage.findRoleMetadata(\n role[1],\n );\n const err = await validateGroupingPolicy(role, metadata, this.id);\n\n if (err) {\n this.logger.warn(err.message);\n continue; // Skip adding this role as there was an error\n }\n\n let roleMeta = await this.roleMetadataStorage.findRoleMetadata(role[1]);\n // role does not exist in rbac, create the metadata for it\n if (!roleMeta) {\n roleMeta = {\n modifiedBy: this.id,\n source: this.id,\n roleEntityRef: role[1],\n };\n }\n\n const auditorMeta = {\n ...roleMeta,\n members: [role[0]],\n };\n const auditorEvent = await this.auditor.createEvent({\n eventId: RoleEvents.ROLE_WRITE,\n severityLevel: 'medium',\n meta: {\n actionType: roleMeta ? ActionType.UPDATE : ActionType.CREATE,\n source: auditorMeta.source,\n },\n });\n\n try {\n await this.enforcer.addGroupingPolicy(role, roleMeta);\n await auditorEvent.success({ meta: auditorMeta });\n } catch (error) {\n await auditorEvent.fail({\n error,\n meta: auditorMeta,\n });\n }\n }\n }\n }\n\n private async removeRoles(\n providerRoles: string[][],\n tempEnforcer: Enforcer,\n ): Promise<void> {\n // Remove role\n // role exists in rbac but does not exist in provider\n const lowercasedProviderRoles =\n transformRolesGroupToLowercase(providerRoles);\n for (const role of lowercasedProviderRoles) {\n if (!(await tempEnforcer.hasGroupingPolicy(...role))) {\n const roleMeta = await this.roleMetadataStorage.findRoleMetadata(\n role[1],\n );\n\n const currentRole = await this.enforcer.getFilteredGroupingPolicy(\n 1,\n role[1],\n );\n\n if (!roleMeta) {\n this.logger.warn('role does not exist');\n continue;\n }\n\n const singleRole = roleMeta && currentRole.length === 1;\n const actionType = singleRole ? ActionType.DELETE : ActionType.UPDATE;\n\n const auditorMeta = { ...roleMeta, members: [role[0]] };\n const auditorEvent = await this.auditor.createEvent({\n eventId: RoleEvents.ROLE_WRITE,\n severityLevel: 'medium',\n meta: { actionType, source: roleMeta.source },\n });\n\n try {\n await this.enforcer.removeGroupingPolicy(\n role,\n roleMeta,\n actionType === ActionType.UPDATE,\n );\n await auditorEvent.success({ meta: auditorMeta });\n } catch (error) {\n await auditorEvent.fail({\n error,\n meta: auditorMeta,\n });\n }\n }\n }\n }\n\n private async addPermissions(permissions: string[][]): Promise<void> {\n for (const permission of permissions) {\n // TODO: Temporary workaround to prevent breakages after the removal of the resource type `policy-entity` from the permission `policy.entity.create`\n if (permission[1] === 'policy-entity' && permission[2] === 'create') {\n this.logger.warn(\n `Permission policy with resource type 'policy-entity' and action 'create' has been removed. Please consider updating policy ${permission} to use 'policy.entity.create' instead of 'policy-entity' from source ${this.id}`,\n );\n }\n\n if (!(await this.enforcer.hasPolicy(...permission))) {\n const transformedPolicy = transformArrayToPolicy(permission);\n const metadata = await this.roleMetadataStorage.findRoleMetadata(\n permission[0],\n );\n\n const auditorMeta = {\n policies: [permission],\n };\n const auditorEvent = await this.auditor.createEvent({\n eventId: PermissionEvents.POLICY_WRITE,\n severityLevel: 'medium',\n meta: { actionType: ActionType.CREATE, source: this.id },\n });\n\n let err = validatePolicy(transformedPolicy);\n if (err) {\n auditorEvent.fail({ error: err, meta: auditorMeta });\n continue; // Skip this invalid permission policy\n }\n\n err = await validateSource(this.id, metadata);\n if (err) {\n auditorEvent.fail({ error: err, meta: auditorMeta });\n continue;\n }\n\n try {\n await this.enforcer.addPolicy(permission);\n await auditorEvent.success({ meta: auditorMeta });\n } catch (error) {\n await auditorEvent.fail({ error, meta: auditorMeta });\n }\n }\n }\n }\n\n private async removePermissions(\n providerPermissions: string[][],\n tempEnforcer: Enforcer,\n ): Promise<void> {\n for (const permission of providerPermissions) {\n if (!(await tempEnforcer.hasPolicy(...permission))) {\n const auditorMeta = {\n policies: [permission],\n };\n const auditorEvent = await this.auditor?.createEvent({\n eventId: PermissionEvents.POLICY_WRITE,\n severityLevel: 'medium',\n meta: { actionType: ActionType.DELETE, source: this.id },\n });\n\n try {\n await this.enforcer.removePolicy(permission);\n await auditorEvent.success({ meta: auditorMeta });\n } catch (error) {\n await auditorEvent.fail({\n error,\n meta: auditorMeta,\n });\n }\n }\n }\n }\n\n private async getProviderRoles(): Promise<string[]> {\n const currentRoles = await this.roleMetadataStorage.filterRoleMetadata(\n this.id,\n );\n return currentRoles.map(meta => meta.roleEntityRef);\n }\n}\n\nexport async function connectRBACProviders(\n providers: RBACProvider[],\n enforcer: EnforcerDelegate,\n roleMetadataStorage: RoleMetadataStorage,\n logger: LoggerService,\n auditor: AuditorService,\n) {\n await Promise.all(\n providers.map(async provider => {\n try {\n const connection = new Connection(\n provider.getProviderName(),\n enforcer,\n roleMetadataStorage,\n logger,\n auditor,\n );\n return provider.connect(connection);\n } catch (error) {\n throw new Error(\n `Unable to connect provider ${provider.getProviderName()}, ${error}`,\n );\n }\n }),\n );\n}\n"],"names":["transformRolesGroupToLowercase","typedPoliciesToString","newEnforcer","newModelFromString","MODEL","StringAdapter","validateGroupingPolicy","RoleEvents","ActionType","transformArrayToPolicy","PermissionEvents","validatePolicy","validateSource"],"mappings":"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"}
1
+ {"version":3,"file":"connect-providers.cjs.js","sources":["../../src/providers/connect-providers.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 {\n AuditorService,\n LoggerService,\n} from '@backstage/backend-plugin-api';\n\nimport {\n Enforcer,\n newEnforcer,\n newModelFromString,\n StringAdapter,\n} from 'casbin';\n\nimport type {\n RBACProvider,\n RBACProviderConnection,\n} from '@backstage-community/plugin-rbac-node';\n\nimport {\n ActionType,\n ConditionEvents,\n PermissionEvents,\n RoleEvents,\n} from '../auditor/auditor';\nimport { RoleMetadataStorage } from '../database/role-metadata';\nimport {\n transformArrayToPolicy,\n transformRolesGroupToLowercase,\n typedPoliciesToString,\n} from '../helper';\nimport { EnforcerDelegate } from '../service/enforcer-delegate';\nimport { MODEL } from '../service/permission-model';\nimport {\n validateGroupingPolicy,\n validatePolicy,\n validateSource,\n} from '../validation/policies-validation';\nimport { ConditionalStorage } from '../database/conditional-storage';\nimport {\n PermissionInfo,\n RoleConditionalPolicyDecision,\n} from '@backstage-community/plugin-rbac-common';\nimport { isEqual } from 'lodash';\n\nexport class Connection implements RBACProviderConnection {\n constructor(\n private readonly id: string,\n private readonly enforcer: EnforcerDelegate,\n private readonly roleMetadataStorage: RoleMetadataStorage,\n private readonly conditionStorage: ConditionalStorage,\n private readonly logger: LoggerService,\n private readonly auditor: AuditorService,\n ) {}\n\n async applyRoles(roles: string[][]): Promise<void> {\n const lowercasedRoles = transformRolesGroupToLowercase(roles);\n const stringPolicy = typedPoliciesToString(lowercasedRoles, 'g');\n const providerRolesforRemoval: string[][] = [];\n\n const tempEnforcer = await newEnforcer(\n newModelFromString(MODEL),\n new StringAdapter(stringPolicy),\n );\n\n const providerRoles = await this.getProviderRoles();\n\n await this.enforcer.loadPolicy();\n // Get the roles for this provider coming from rbac plugin\n for (const providerRole of providerRoles) {\n providerRolesforRemoval.push(\n ...(await this.enforcer.getFilteredGroupingPolicy(1, providerRole)),\n );\n }\n\n // Remove role\n // role exists in rbac but does not exist in provider\n await this.removeRoles(providerRolesforRemoval, tempEnforcer);\n\n // Add the role\n // role exists in provider but does not exist in rbac\n await this.addRoles(lowercasedRoles);\n }\n\n async applyPermissions(permissions: string[][]): Promise<void> {\n const stringPolicy = typedPoliciesToString(permissions, 'p');\n\n const providerPermissions: string[][] = [];\n\n const tempEnforcer = await newEnforcer(\n newModelFromString(MODEL),\n new StringAdapter(stringPolicy),\n );\n\n const providerRoles = await this.getProviderRoles();\n\n await this.enforcer.loadPolicy();\n // Get the roles for this provider coming from rbac plugin\n for (const providerRole of providerRoles) {\n providerPermissions.push(\n ...(await this.enforcer.getFilteredPolicy(0, providerRole)),\n );\n }\n\n await this.removePermissions(providerPermissions, tempEnforcer);\n\n await this.addPermissions(permissions);\n }\n\n async applyConditionalPermissions(\n conditionalPermissions: RoleConditionalPolicyDecision<PermissionInfo>[],\n ): Promise<void> {\n const storedConditionalPermissions =\n await this.conditionStorage.filterConditions();\n\n const conditionsToBeAdded: RoleConditionalPolicyDecision<PermissionInfo>[] =\n conditionalPermissions.filter(\n conditionalPermission =>\n !storedConditionalPermissions.some(\n stored =>\n conditionalPermission.roleEntityRef === stored.roleEntityRef &&\n conditionalPermission.pluginId === stored.pluginId &&\n conditionalPermission.resourceType === stored.resourceType &&\n isEqual(\n conditionalPermission.permissionMapping,\n stored.permissionMapping,\n ) &&\n isEqual(conditionalPermission.conditions, stored.conditions),\n ),\n );\n\n // Updated policies fails the 'some' check due to permissionMapping differences\n const conditionsToBeRemoved: RoleConditionalPolicyDecision<PermissionInfo>[] =\n storedConditionalPermissions.filter(\n stored =>\n !conditionalPermissions.some(\n conditionalPermission =>\n stored.roleEntityRef === conditionalPermission.roleEntityRef &&\n stored.pluginId === conditionalPermission.pluginId &&\n stored.resourceType === conditionalPermission.resourceType &&\n isEqual(\n stored.permissionMapping,\n conditionalPermission.permissionMapping,\n ) &&\n isEqual(stored.conditions, conditionalPermission.conditions),\n ),\n );\n\n await this.removeConditionalPermissions(conditionsToBeRemoved);\n\n await this.addConditionalPermissions(conditionsToBeAdded);\n }\n\n private async addRoles(roles: string[][]): Promise<void> {\n for (const role of roles) {\n if (!(await this.enforcer.hasGroupingPolicy(...role))) {\n const metadata = await this.roleMetadataStorage.findRoleMetadata(\n role[1],\n );\n const err = await validateGroupingPolicy(role, metadata, this.id);\n\n if (err) {\n this.logger.warn(err.message);\n continue; // Skip adding this role as there was an error\n }\n\n let roleMeta = await this.roleMetadataStorage.findRoleMetadata(role[1]);\n // role does not exist in rbac, create the metadata for it\n if (!roleMeta) {\n roleMeta = {\n modifiedBy: this.id,\n source: this.id,\n roleEntityRef: role[1],\n };\n }\n\n const auditorMeta = {\n ...roleMeta,\n members: [role[0]],\n };\n const auditorEvent = await this.auditor.createEvent({\n eventId: RoleEvents.ROLE_WRITE,\n severityLevel: 'medium',\n meta: {\n actionType: roleMeta ? ActionType.UPDATE : ActionType.CREATE,\n source: auditorMeta.source,\n },\n });\n\n try {\n await this.enforcer.addGroupingPolicy(role, roleMeta);\n await auditorEvent.success({ meta: auditorMeta });\n } catch (error) {\n await auditorEvent.fail({\n error,\n meta: auditorMeta,\n });\n }\n }\n }\n }\n\n private async removeRoles(\n providerRoles: string[][],\n tempEnforcer: Enforcer,\n ): Promise<void> {\n // Remove role\n // role exists in rbac but does not exist in provider\n const lowercasedProviderRoles =\n transformRolesGroupToLowercase(providerRoles);\n for (const role of lowercasedProviderRoles) {\n if (!(await tempEnforcer.hasGroupingPolicy(...role))) {\n const roleMeta = await this.roleMetadataStorage.findRoleMetadata(\n role[1],\n );\n\n const currentRole = await this.enforcer.getFilteredGroupingPolicy(\n 1,\n role[1],\n );\n\n if (!roleMeta) {\n this.logger.warn('role does not exist');\n continue;\n }\n\n const singleRole = roleMeta && currentRole.length === 1;\n const actionType = singleRole ? ActionType.DELETE : ActionType.UPDATE;\n\n const auditorMeta = { ...roleMeta, members: [role[0]] };\n const auditorEvent = await this.auditor.createEvent({\n eventId: RoleEvents.ROLE_WRITE,\n severityLevel: 'medium',\n meta: { actionType, source: roleMeta.source },\n });\n\n try {\n await this.enforcer.removeGroupingPolicy(\n role,\n roleMeta,\n actionType === ActionType.UPDATE,\n );\n await auditorEvent.success({ meta: auditorMeta });\n } catch (error) {\n await auditorEvent.fail({\n error,\n meta: auditorMeta,\n });\n }\n }\n }\n }\n\n private async addPermissions(permissions: string[][]): Promise<void> {\n for (const permission of permissions) {\n // TODO: Temporary workaround to prevent breakages after the removal of the resource type `policy-entity` from the permission `policy.entity.create`\n if (permission[1] === 'policy-entity' && permission[2] === 'create') {\n this.logger.warn(\n `Permission policy with resource type 'policy-entity' and action 'create' has been removed. Please consider updating policy ${permission} to use 'policy.entity.create' instead of 'policy-entity' from source ${this.id}`,\n );\n }\n\n if (!(await this.enforcer.hasPolicy(...permission))) {\n const transformedPolicy = transformArrayToPolicy(permission);\n const metadata = await this.roleMetadataStorage.findRoleMetadata(\n permission[0],\n );\n\n const auditorMeta = {\n policies: [permission],\n };\n const auditorEvent = await this.auditor.createEvent({\n eventId: PermissionEvents.POLICY_WRITE,\n severityLevel: 'medium',\n meta: { actionType: ActionType.CREATE, source: this.id },\n });\n\n let err = validatePolicy(transformedPolicy);\n if (err) {\n auditorEvent.fail({ error: err, meta: auditorMeta });\n continue; // Skip this invalid permission policy\n }\n\n err = await validateSource(this.id, metadata);\n if (err) {\n auditorEvent.fail({ error: err, meta: auditorMeta });\n continue;\n }\n\n try {\n await this.enforcer.addPolicy(permission);\n await auditorEvent.success({ meta: auditorMeta });\n } catch (error) {\n await auditorEvent.fail({ error, meta: auditorMeta });\n }\n }\n }\n }\n\n private async removePermissions(\n providerPermissions: string[][],\n tempEnforcer: Enforcer,\n ): Promise<void> {\n for (const permission of providerPermissions) {\n if (!(await tempEnforcer.hasPolicy(...permission))) {\n const auditorMeta = {\n policies: [permission],\n };\n const auditorEvent = await this.auditor?.createEvent({\n eventId: PermissionEvents.POLICY_WRITE,\n severityLevel: 'medium',\n meta: { actionType: ActionType.DELETE, source: this.id },\n });\n\n try {\n await this.enforcer.removePolicy(permission);\n await auditorEvent.success({ meta: auditorMeta });\n } catch (error) {\n await auditorEvent.fail({\n error,\n meta: auditorMeta,\n });\n }\n }\n }\n }\n\n private async addConditionalPermissions(\n conditionalPermissions: RoleConditionalPolicyDecision<PermissionInfo>[],\n ): Promise<void> {\n for (const condition of conditionalPermissions) {\n const auditorMeta = {\n policies: [condition],\n };\n const auditorEvent = await this.auditor.createEvent({\n eventId: ConditionEvents.CONDITION_WRITE,\n severityLevel: 'medium',\n meta: {\n actionType: ActionType.CREATE,\n source: this.id,\n },\n });\n try {\n const metadata = await this.roleMetadataStorage.findRoleMetadata(\n condition.roleEntityRef,\n );\n const err = await validateSource(this.id, metadata);\n if (err) {\n throw err;\n }\n await this.conditionStorage.createCondition(condition);\n await auditorEvent.success({ meta: auditorMeta });\n } catch (error) {\n await auditorEvent.fail({ error, meta: auditorMeta });\n }\n }\n }\n\n private async removeConditionalPermissions(\n conditionalPermissions: RoleConditionalPolicyDecision<PermissionInfo>[],\n ): Promise<void> {\n for (const conditionalPermission of conditionalPermissions) {\n const auditorMeta = {\n policies: [conditionalPermission],\n };\n const auditorEvent = await this.auditor.createEvent({\n eventId: ConditionEvents.CONDITION_WRITE,\n severityLevel: 'medium',\n meta: { actionType: ActionType.DELETE, source: this.id },\n });\n try {\n const metadata = await this.roleMetadataStorage.findRoleMetadata(\n conditionalPermission.roleEntityRef,\n );\n const err = await validateSource(this.id, metadata);\n if (err) {\n throw err;\n }\n await this.conditionStorage.deleteCondition(conditionalPermission.id);\n await auditorEvent.success({ meta: auditorMeta });\n } catch (error) {\n await auditorEvent.fail({\n error,\n meta: auditorMeta,\n });\n }\n }\n }\n\n private async getProviderRoles(): Promise<string[]> {\n const currentRoles = await this.roleMetadataStorage.filterRoleMetadata(\n this.id,\n );\n return currentRoles.map(meta => meta.roleEntityRef);\n }\n}\n\nexport async function connectRBACProviders(\n providers: RBACProvider[],\n enforcer: EnforcerDelegate,\n roleMetadataStorage: RoleMetadataStorage,\n conditionStorage: ConditionalStorage,\n logger: LoggerService,\n auditor: AuditorService,\n) {\n await Promise.all(\n providers.map(async provider => {\n try {\n const connection = new Connection(\n provider.getProviderName(),\n enforcer,\n roleMetadataStorage,\n conditionStorage,\n logger,\n auditor,\n );\n return provider.connect(connection);\n } catch (error) {\n throw new Error(\n `Unable to connect provider ${provider.getProviderName()}, ${error}`,\n );\n }\n }),\n );\n}\n"],"names":["transformRolesGroupToLowercase","typedPoliciesToString","newEnforcer","newModelFromString","MODEL","StringAdapter","isEqual","validateGroupingPolicy","RoleEvents","ActionType","transformArrayToPolicy","PermissionEvents","validatePolicy","validateSource","ConditionEvents"],"mappings":"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"}
@@ -5,7 +5,7 @@ var memberList = require('./member-list.cjs.js');
5
5
  var ancestorSearchFactory = require('./ancestor-search-factory.cjs.js');
6
6
 
7
7
  class BackstageRoleManager {
8
- constructor(catalogApi, logger, catalogDBClient, rbacDBClient, config, auth) {
8
+ constructor(catalogApi, logger, catalogDBClient, rbacDBClient, config, auth, defaultPermissionReader) {
9
9
  this.catalogApi = catalogApi;
10
10
  this.logger = logger;
11
11
  this.catalogDBClient = catalogDBClient;
@@ -15,6 +15,7 @@ class BackstageRoleManager {
15
15
  this.allRoles = /* @__PURE__ */ new Map();
16
16
  const rbacConfig = this.config.getOptionalConfig("permission.rbac");
17
17
  this.maxDepth = rbacConfig?.getOptionalNumber("maxDepth");
18
+ this.defaultRoleRef = defaultPermissionReader.readRole();
18
19
  if (this.maxDepth !== undefined && this.maxDepth < 0) {
19
20
  throw new Error(
20
21
  "Max Depth for RBAC group hierarchy must be greater than or equal to zero"
@@ -23,6 +24,7 @@ class BackstageRoleManager {
23
24
  }
24
25
  allRoles;
25
26
  maxDepth;
27
+ defaultRoleRef;
26
28
  /**
27
29
  * clear clears all stored data and resets the role manager to the initial state.
28
30
  */
@@ -187,7 +189,9 @@ class BackstageRoleManager {
187
189
  memo.getNodes(),
188
190
  this.rbacDBClient
189
191
  );
190
- return Promise.resolve(currentRole.getRoles());
192
+ const roles = currentRole.getRoles();
193
+ if (this.defaultRoleRef) roles.push(this.defaultRoleRef);
194
+ return Promise.resolve(roles);
191
195
  }
192
196
  const allRoles = [];
193
197
  for (const value of this.allRoles.values()) {
@@ -195,6 +199,7 @@ class BackstageRoleManager {
195
199
  allRoles.push(value.name);
196
200
  }
197
201
  }
202
+ if (this.defaultRoleRef) allRoles.push(this.defaultRoleRef);
198
203
  return Promise.resolve(allRoles);
199
204
  }
200
205
  return [];
@@ -1 +1 @@
1
- {"version":3,"file":"role-manager.cjs.js","sources":["../../src/role-manager/role-manager.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 { AuthService, LoggerService } from '@backstage/backend-plugin-api';\nimport type { CatalogApi } from '@backstage/catalog-client';\nimport { parseEntityRef } from '@backstage/catalog-model';\nimport type { Config } from '@backstage/config';\n\nimport { RoleManager } from 'casbin';\nimport { Knex } from 'knex';\n\nimport { AncestorSearchMemo, ASMGroup } from './ancestor-search-memo';\nimport { RoleMemberList } from './member-list';\nimport { AncestorSearchFactory } from './ancestor-search-factory';\n\nexport class BackstageRoleManager implements RoleManager {\n private allRoles: Map<string, RoleMemberList>;\n private maxDepth?: number;\n constructor(\n private readonly catalogApi: CatalogApi,\n private readonly logger: LoggerService,\n private readonly catalogDBClient: Knex,\n private readonly rbacDBClient: Knex,\n private readonly config: Config,\n private readonly auth: AuthService,\n ) {\n this.allRoles = new Map<string, RoleMemberList>();\n const rbacConfig = this.config.getOptionalConfig('permission.rbac');\n this.maxDepth = rbacConfig?.getOptionalNumber('maxDepth');\n if (this.maxDepth !== undefined && this.maxDepth! < 0) {\n throw new Error(\n 'Max Depth for RBAC group hierarchy must be greater than or equal to zero',\n );\n }\n }\n\n /**\n * clear clears all stored data and resets the role manager to the initial state.\n */\n async clear(): Promise<void> {\n // do nothing\n }\n\n /**\n * addLink adds the inheritance link between name1 and role: name2.\n * aka name1 inherits role: name2.\n * The link that is established is based on the defined grouping policies that are added by the enforcer.\n *\n * ex. `g, name1, name2`.\n * @param name1 User or group that will be assigned to a role.\n * @param name2 The role that will be created or updated.\n * @param _domain Unimplemented prefix to the role.\n */\n async addLink(\n name1: string,\n name2: string,\n ..._domain: string[]\n ): Promise<void> {\n if (!this.isPGClient()) {\n const role1 = this.getOrCreateRole(name2);\n role1.addMember(name1);\n }\n }\n\n /**\n * deleteLink deletes the inheritance link between name1 and role: name2.\n * aka name1 does not inherit role: name2 any more.\n * The link that is deleted is based on the defined grouping policies that are removed by the enforcer.\n *\n * ex. `g, name1, name2`.\n * @param name1 User or group that will be removed from assignment of a role.\n * @param name2 The role that will be deleted or updated.\n * @param _domain Unimplemented.\n */\n async deleteLink(\n name1: string,\n name2: string,\n ..._domain: string[]\n ): Promise<void> {\n if (!this.isPGClient()) {\n const role1 = this.getOrCreateRole(name2);\n role1.deleteMember(name1);\n\n // Clean up in the event that there are no more members in the role\n if (role1.getMembers().length === 0) {\n this.allRoles.delete(name2);\n }\n }\n }\n\n /**\n * hasLink determines whether name1 inherits role: name2.\n * Before this check is called in the background by the enforcer,\n * we filter out all roles that the user is not connected to\n * directly or indirectly through the use of retrieving roles through\n * enforcer.getRolesForUser and apply those roles to a tempEnforcer.\n *\n * This means that hasLink will almost always be true in the event that a user\n * is assigned to a role (either directly or indirectly)\n *\n * In the event that a user or group is not assigned to a role and instead\n * are assigned directly to permissions, then name2 will become either that\n * user or group through the filtering. In this case we will build the graph\n * if necessary for name2 group presence or evaulate based on the names matching.\n * @param name1 The user that we are authorizing.\n * @param name2 The name of the role that we are checking against.\n * @param domain Unimplemented.\n * @returns True if the user is directly or indirectly attached to the role.\n */\n async hasLink(\n name1: string,\n name2: string,\n ...domain: string[]\n ): Promise<boolean> {\n if (domain.length > 0) {\n throw new Error('domain argument is not supported.');\n }\n\n // Name2 can be an empty string in the event that there is not a role associated with the user\n // This happens because of the filtering of the roles reduces the number of roles that we iterate through.\n if (name2.length === 0) {\n return false;\n }\n\n if (name1 === name2) {\n return true;\n }\n\n // name1 is always user in our case.\n // name2 is user or group.\n // user(name1) couldn't inherit user(name2).\n // We can use this fact for optimization.\n const { kind } = parseEntityRef(name2);\n if (kind.toLocaleLowerCase() === 'user') {\n return false;\n }\n\n // if it is a group, then we will have to build the graph,\n if (kind.toLocaleLowerCase() === 'group') {\n const memo = await AncestorSearchFactory.createAncestorSearchMemo(\n name1,\n this.config,\n this.catalogApi,\n this.catalogDBClient,\n this.auth,\n this.maxDepth,\n );\n\n await memo.buildUserGraph();\n memo.debugNodesAndEdges(this.logger, name1);\n\n if (!memo.isAcyclic()) {\n const cycles = memo.findCycles();\n\n this.logger.warn(\n `Detected cycle dependencies in the Group graph: ${JSON.stringify(\n cycles,\n )}. Admin/(catalog owner) have to fix it to make RBAC permission evaluation correct for groups: ${JSON.stringify(\n cycles,\n )}`,\n );\n return false;\n }\n\n return memo.hasEntityRef(name2);\n }\n\n return true;\n }\n\n /**\n * syncedHasLink determines whether role: name1 inherits role: name2.\n * domain is a prefix to the roles.\n */\n syncedHasLink?(\n _name1: string,\n _name2: string,\n ..._domain: string[]\n ): boolean {\n throw new Error('Method \"syncedHasLink\" not implemented.');\n }\n\n /**\n * getRoles gets the roles that a subject inherits.\n *\n * name - is a string entity reference, for example: user:default/tom, role:default/dev,\n * so format is <kind>:<namespace>/<entity-name>.\n * GetRoles method supports only two kind values: 'user' and 'role'.\n *\n * domain - is a prefix to the roles, unused parameter.\n *\n * If name's kind === 'user' we return all inherited roles from groups and roles directly assigned to the user.\n * if name's kind === 'role' we return empty array, because we don't support role inheritance.\n * Case kind === 'group' - should not happen, because:\n * 1) Method getRoles returns only role entity references, so casbin engine doesn't call this\n * method again to ask about name with kind \"group\".\n * 2) We implemented getRoles method only to use:\n * 'await enforcer.getImplicitPermissionsForUser(userEntityRef)',\n * so name argument can be only with kind 'user' or 'role'.\n *\n * Info: when we call 'await enforcer.getImplicitPermissionsForUser(userEntityRef)',\n * then casbin engine executes 'getRoles' method few times.\n * Firstly casbin asks about roles for 'userEntityRef'.\n * Let's imagine, that 'getRoles' returned two roles for userEntityRef.\n * Then casbin calls 'getRoles' two more times to\n * find parent roles. But we return empty array for each such call,\n * because we don't support role inheritance and we notify casbin about end of the role sub-tree.\n */\n async getRoles(name: string, ..._domain: string[]): Promise<string[]> {\n const { kind } = parseEntityRef(name);\n if (kind === 'user') {\n const memo = await AncestorSearchFactory.createAncestorSearchMemo(\n name,\n this.config,\n this.catalogApi,\n this.catalogDBClient,\n this.auth,\n this.maxDepth,\n );\n await memo.buildUserGraph();\n memo.debugNodesAndEdges(this.logger, name);\n\n // Account for the user not being in the graph (this can happen during direct assignment to roles)\n memo.setNode(name);\n\n if (!memo.isAcyclic()) {\n const cycles = memo.findCycles();\n\n this.logger.warn(\n `Detected cycle dependencies in the Group graph: ${JSON.stringify(\n cycles,\n )}. Admin/(catalog owner) have to fix it to make RBAC permission evaluation correct for groups: ${JSON.stringify(\n cycles,\n )}`,\n );\n return Promise.resolve([]);\n }\n\n if (this.isPGClient()) {\n const currentRole = new RoleMemberList(name);\n await currentRole.buildRoles(\n currentRole,\n memo.getNodes(),\n this.rbacDBClient,\n );\n return Promise.resolve(currentRole.getRoles());\n }\n\n const allRoles: string[] = [];\n for (const value of this.allRoles.values()) {\n if (this.hasMember(value, memo)) {\n allRoles.push(value.name);\n }\n }\n\n return Promise.resolve(allRoles);\n }\n\n return [];\n }\n\n /**\n * getUsers gets the users that inherits a subject.\n * domain is an unreferenced parameter here, may be used in other implementations.\n */\n async getUsers(_name: string, ..._domain: string[]): Promise<string[]> {\n throw new Error('Method \"getUsers\" not implemented.');\n }\n\n /**\n * printRoles prints all the roles to log.\n */\n async printRoles(): Promise<void> {\n // do nothing\n }\n\n /**\n * getOrCreateRole will get a role if it has already been cached\n * or it will create a new role to be cached.\n * This cache is a simple tree that is used to quickly compare\n * users and groups to roles.\n * @param name The user or group whose cache we will be getting / creating.\n * @returns The cached role as a RoleList.\n */\n private getOrCreateRole(name: string): RoleMemberList {\n const role = this.allRoles.get(name);\n if (role) {\n return role;\n }\n const newRole = new RoleMemberList(name);\n this.allRoles.set(name, newRole);\n\n return newRole;\n }\n\n /**\n * isPGClient checks what the current database client is at them time.\n * This is to ensure that we are querying the database in the event of postgres\n * or using in memory cache for better sqlite3.\n * @returns True if the database client is pg.\n */\n isPGClient(): boolean {\n const client = this.rbacDBClient.client.config.client;\n return client === 'pg';\n }\n\n /**\n * hasMember checks if the members from a particular role is associated with the user\n * that the AncestorSearchMemo graph is built for.\n * @param role The role that we are getting the members from.\n * @param memo The user graph that we are comparing members with.\n * @returns True if a member from the role is also associated with the user.\n */\n private hasMember(\n role: RoleMemberList | undefined,\n memo: AncestorSearchMemo<ASMGroup>,\n ): boolean {\n if (role === undefined) {\n return false;\n }\n\n for (const member of role.getMembers()) {\n if (memo.hasEntityRef(member)) {\n return true;\n }\n }\n return false;\n }\n}\n"],"names":["parseEntityRef","AncestorSearchFactory","RoleMemberList"],"mappings":";;;;;;AA2BO,MAAM,oBAA4C,CAAA;AAAA,EAGvD,YACmB,UACA,EAAA,MAAA,EACA,eACA,EAAA,YAAA,EACA,QACA,IACjB,EAAA;AANiB,IAAA,IAAA,CAAA,UAAA,GAAA,UAAA;AACA,IAAA,IAAA,CAAA,MAAA,GAAA,MAAA;AACA,IAAA,IAAA,CAAA,eAAA,GAAA,eAAA;AACA,IAAA,IAAA,CAAA,YAAA,GAAA,YAAA;AACA,IAAA,IAAA,CAAA,MAAA,GAAA,MAAA;AACA,IAAA,IAAA,CAAA,IAAA,GAAA,IAAA;AAEjB,IAAK,IAAA,CAAA,QAAA,uBAAe,GAA4B,EAAA;AAChD,IAAA,MAAM,UAAa,GAAA,IAAA,CAAK,MAAO,CAAA,iBAAA,CAAkB,iBAAiB,CAAA;AAClE,IAAK,IAAA,CAAA,QAAA,GAAW,UAAY,EAAA,iBAAA,CAAkB,UAAU,CAAA;AACxD,IAAA,IAAI,IAAK,CAAA,QAAA,KAAa,SAAa,IAAA,IAAA,CAAK,WAAY,CAAG,EAAA;AACrD,MAAA,MAAM,IAAI,KAAA;AAAA,QACR;AAAA,OACF;AAAA;AACF;AACF,EAlBQ,QAAA;AAAA,EACA,QAAA;AAAA;AAAA;AAAA;AAAA,EAsBR,MAAM,KAAuB,GAAA;AAAA;AAE7B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAYA,MAAM,OAAA,CACJ,KACA,EAAA,KAAA,EAAA,GACG,OACY,EAAA;AACf,IAAI,IAAA,CAAC,IAAK,CAAA,UAAA,EAAc,EAAA;AACtB,MAAM,MAAA,KAAA,GAAQ,IAAK,CAAA,eAAA,CAAgB,KAAK,CAAA;AACxC,MAAA,KAAA,CAAM,UAAU,KAAK,CAAA;AAAA;AACvB;AACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAYA,MAAM,UAAA,CACJ,KACA,EAAA,KAAA,EAAA,GACG,OACY,EAAA;AACf,IAAI,IAAA,CAAC,IAAK,CAAA,UAAA,EAAc,EAAA;AACtB,MAAM,MAAA,KAAA,GAAQ,IAAK,CAAA,eAAA,CAAgB,KAAK,CAAA;AACxC,MAAA,KAAA,CAAM,aAAa,KAAK,CAAA;AAGxB,MAAA,IAAI,KAAM,CAAA,UAAA,EAAa,CAAA,MAAA,KAAW,CAAG,EAAA;AACnC,QAAK,IAAA,CAAA,QAAA,CAAS,OAAO,KAAK,CAAA;AAAA;AAC5B;AACF;AACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAqBA,MAAM,OAAA,CACJ,KACA,EAAA,KAAA,EAAA,GACG,MACe,EAAA;AAClB,IAAI,IAAA,MAAA,CAAO,SAAS,CAAG,EAAA;AACrB,MAAM,MAAA,IAAI,MAAM,mCAAmC,CAAA;AAAA;AAKrD,IAAI,IAAA,KAAA,CAAM,WAAW,CAAG,EAAA;AACtB,MAAO,OAAA,KAAA;AAAA;AAGT,IAAA,IAAI,UAAU,KAAO,EAAA;AACnB,MAAO,OAAA,IAAA;AAAA;AAOT,IAAA,MAAM,EAAE,IAAA,EAAS,GAAAA,2BAAA,CAAe,KAAK,CAAA;AACrC,IAAI,IAAA,IAAA,CAAK,iBAAkB,EAAA,KAAM,MAAQ,EAAA;AACvC,MAAO,OAAA,KAAA;AAAA;AAIT,IAAI,IAAA,IAAA,CAAK,iBAAkB,EAAA,KAAM,OAAS,EAAA;AACxC,MAAM,MAAA,IAAA,GAAO,MAAMC,2CAAsB,CAAA,wBAAA;AAAA,QACvC,KAAA;AAAA,QACA,IAAK,CAAA,MAAA;AAAA,QACL,IAAK,CAAA,UAAA;AAAA,QACL,IAAK,CAAA,eAAA;AAAA,QACL,IAAK,CAAA,IAAA;AAAA,QACL,IAAK,CAAA;AAAA,OACP;AAEA,MAAA,MAAM,KAAK,cAAe,EAAA;AAC1B,MAAK,IAAA,CAAA,kBAAA,CAAmB,IAAK,CAAA,MAAA,EAAQ,KAAK,CAAA;AAE1C,MAAI,IAAA,CAAC,IAAK,CAAA,SAAA,EAAa,EAAA;AACrB,QAAM,MAAA,MAAA,GAAS,KAAK,UAAW,EAAA;AAE/B,QAAA,IAAA,CAAK,MAAO,CAAA,IAAA;AAAA,UACV,mDAAmD,IAAK,CAAA,SAAA;AAAA,YACtD;AAAA,WACD,iGAAiG,IAAK,CAAA,SAAA;AAAA,YACrG;AAAA,WACD,CAAA;AAAA,SACH;AACA,QAAO,OAAA,KAAA;AAAA;AAGT,MAAO,OAAA,IAAA,CAAK,aAAa,KAAK,CAAA;AAAA;AAGhC,IAAO,OAAA,IAAA;AAAA;AACT;AAAA;AAAA;AAAA;AAAA,EAMA,aAAA,CACE,MACA,EAAA,MAAA,EAAA,GACG,OACM,EAAA;AACT,IAAM,MAAA,IAAI,MAAM,yCAAyC,CAAA;AAAA;AAC3D;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EA4BA,MAAM,QAAS,CAAA,IAAA,EAAA,GAAiB,OAAsC,EAAA;AACpE,IAAA,MAAM,EAAE,IAAA,EAAS,GAAAD,2BAAA,CAAe,IAAI,CAAA;AACpC,IAAA,IAAI,SAAS,MAAQ,EAAA;AACnB,MAAM,MAAA,IAAA,GAAO,MAAMC,2CAAsB,CAAA,wBAAA;AAAA,QACvC,IAAA;AAAA,QACA,IAAK,CAAA,MAAA;AAAA,QACL,IAAK,CAAA,UAAA;AAAA,QACL,IAAK,CAAA,eAAA;AAAA,QACL,IAAK,CAAA,IAAA;AAAA,QACL,IAAK,CAAA;AAAA,OACP;AACA,MAAA,MAAM,KAAK,cAAe,EAAA;AAC1B,MAAK,IAAA,CAAA,kBAAA,CAAmB,IAAK,CAAA,MAAA,EAAQ,IAAI,CAAA;AAGzC,MAAA,IAAA,CAAK,QAAQ,IAAI,CAAA;AAEjB,MAAI,IAAA,CAAC,IAAK,CAAA,SAAA,EAAa,EAAA;AACrB,QAAM,MAAA,MAAA,GAAS,KAAK,UAAW,EAAA;AAE/B,QAAA,IAAA,CAAK,MAAO,CAAA,IAAA;AAAA,UACV,mDAAmD,IAAK,CAAA,SAAA;AAAA,YACtD;AAAA,WACD,iGAAiG,IAAK,CAAA,SAAA;AAAA,YACrG;AAAA,WACD,CAAA;AAAA,SACH;AACA,QAAO,OAAA,OAAA,CAAQ,OAAQ,CAAA,EAAE,CAAA;AAAA;AAG3B,MAAI,IAAA,IAAA,CAAK,YAAc,EAAA;AACrB,QAAM,MAAA,WAAA,GAAc,IAAIC,yBAAA,CAAe,IAAI,CAAA;AAC3C,QAAA,MAAM,WAAY,CAAA,UAAA;AAAA,UAChB,WAAA;AAAA,UACA,KAAK,QAAS,EAAA;AAAA,UACd,IAAK,CAAA;AAAA,SACP;AACA,QAAA,OAAO,OAAQ,CAAA,OAAA,CAAQ,WAAY,CAAA,QAAA,EAAU,CAAA;AAAA;AAG/C,MAAA,MAAM,WAAqB,EAAC;AAC5B,MAAA,KAAA,MAAW,KAAS,IAAA,IAAA,CAAK,QAAS,CAAA,MAAA,EAAU,EAAA;AAC1C,QAAA,IAAI,IAAK,CAAA,SAAA,CAAU,KAAO,EAAA,IAAI,CAAG,EAAA;AAC/B,UAAS,QAAA,CAAA,IAAA,CAAK,MAAM,IAAI,CAAA;AAAA;AAC1B;AAGF,MAAO,OAAA,OAAA,CAAQ,QAAQ,QAAQ,CAAA;AAAA;AAGjC,IAAA,OAAO,EAAC;AAAA;AACV;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,QAAS,CAAA,KAAA,EAAA,GAAkB,OAAsC,EAAA;AACrE,IAAM,MAAA,IAAI,MAAM,oCAAoC,CAAA;AAAA;AACtD;AAAA;AAAA;AAAA,EAKA,MAAM,UAA4B,GAAA;AAAA;AAElC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUQ,gBAAgB,IAA8B,EAAA;AACpD,IAAA,MAAM,IAAO,GAAA,IAAA,CAAK,QAAS,CAAA,GAAA,CAAI,IAAI,CAAA;AACnC,IAAA,IAAI,IAAM,EAAA;AACR,MAAO,OAAA,IAAA;AAAA;AAET,IAAM,MAAA,OAAA,GAAU,IAAIA,yBAAA,CAAe,IAAI,CAAA;AACvC,IAAK,IAAA,CAAA,QAAA,CAAS,GAAI,CAAA,IAAA,EAAM,OAAO,CAAA;AAE/B,IAAO,OAAA,OAAA;AAAA;AACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,UAAsB,GAAA;AACpB,IAAA,MAAM,MAAS,GAAA,IAAA,CAAK,YAAa,CAAA,MAAA,CAAO,MAAO,CAAA,MAAA;AAC/C,IAAA,OAAO,MAAW,KAAA,IAAA;AAAA;AACpB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASQ,SAAA,CACN,MACA,IACS,EAAA;AACT,IAAA,IAAI,SAAS,SAAW,EAAA;AACtB,MAAO,OAAA,KAAA;AAAA;AAGT,IAAW,KAAA,MAAA,MAAA,IAAU,IAAK,CAAA,UAAA,EAAc,EAAA;AACtC,MAAI,IAAA,IAAA,CAAK,YAAa,CAAA,MAAM,CAAG,EAAA;AAC7B,QAAO,OAAA,IAAA;AAAA;AACT;AAEF,IAAO,OAAA,KAAA;AAAA;AAEX;;;;"}
1
+ {"version":3,"file":"role-manager.cjs.js","sources":["../../src/role-manager/role-manager.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 { AuthService, LoggerService } from '@backstage/backend-plugin-api';\nimport type { CatalogApi } from '@backstage/catalog-client';\nimport { parseEntityRef } from '@backstage/catalog-model';\nimport type { Config } from '@backstage/config';\n\nimport { RoleManager } from 'casbin';\nimport { Knex } from 'knex';\n\nimport { AncestorSearchMemo, ASMGroup } from './ancestor-search-memo';\nimport { RoleMemberList } from './member-list';\nimport { AncestorSearchFactory } from './ancestor-search-factory';\nimport { DefaultPermissionsReader } from '../default-permissions/default-permissions';\n\nexport class BackstageRoleManager implements RoleManager {\n private allRoles: Map<string, RoleMemberList>;\n private maxDepth?: number;\n private defaultRoleRef?: string;\n constructor(\n private readonly catalogApi: CatalogApi,\n private readonly logger: LoggerService,\n private readonly catalogDBClient: Knex,\n private readonly rbacDBClient: Knex,\n private readonly config: Config,\n private readonly auth: AuthService,\n defaultPermissionReader: DefaultPermissionsReader,\n ) {\n this.allRoles = new Map<string, RoleMemberList>();\n const rbacConfig = this.config.getOptionalConfig('permission.rbac');\n this.maxDepth = rbacConfig?.getOptionalNumber('maxDepth');\n this.defaultRoleRef = defaultPermissionReader.readRole();\n if (this.maxDepth !== undefined && this.maxDepth! < 0) {\n throw new Error(\n 'Max Depth for RBAC group hierarchy must be greater than or equal to zero',\n );\n }\n }\n\n /**\n * clear clears all stored data and resets the role manager to the initial state.\n */\n async clear(): Promise<void> {\n // do nothing\n }\n\n /**\n * addLink adds the inheritance link between name1 and role: name2.\n * aka name1 inherits role: name2.\n * The link that is established is based on the defined grouping policies that are added by the enforcer.\n *\n * ex. `g, name1, name2`.\n * @param name1 User or group that will be assigned to a role.\n * @param name2 The role that will be created or updated.\n * @param _domain Unimplemented prefix to the role.\n */\n async addLink(\n name1: string,\n name2: string,\n ..._domain: string[]\n ): Promise<void> {\n if (!this.isPGClient()) {\n const role1 = this.getOrCreateRole(name2);\n role1.addMember(name1);\n }\n }\n\n /**\n * deleteLink deletes the inheritance link between name1 and role: name2.\n * aka name1 does not inherit role: name2 any more.\n * The link that is deleted is based on the defined grouping policies that are removed by the enforcer.\n *\n * ex. `g, name1, name2`.\n * @param name1 User or group that will be removed from assignment of a role.\n * @param name2 The role that will be deleted or updated.\n * @param _domain Unimplemented.\n */\n async deleteLink(\n name1: string,\n name2: string,\n ..._domain: string[]\n ): Promise<void> {\n if (!this.isPGClient()) {\n const role1 = this.getOrCreateRole(name2);\n role1.deleteMember(name1);\n\n // Clean up in the event that there are no more members in the role\n if (role1.getMembers().length === 0) {\n this.allRoles.delete(name2);\n }\n }\n }\n\n /**\n * hasLink determines whether name1 inherits role: name2.\n * Before this check is called in the background by the enforcer,\n * we filter out all roles that the user is not connected to\n * directly or indirectly through the use of retrieving roles through\n * enforcer.getRolesForUser and apply those roles to a tempEnforcer.\n *\n * This means that hasLink will almost always be true in the event that a user\n * is assigned to a role (either directly or indirectly)\n *\n * In the event that a user or group is not assigned to a role and instead\n * are assigned directly to permissions, then name2 will become either that\n * user or group through the filtering. In this case we will build the graph\n * if necessary for name2 group presence or evaulate based on the names matching.\n * @param name1 The user that we are authorizing.\n * @param name2 The name of the role that we are checking against.\n * @param domain Unimplemented.\n * @returns True if the user is directly or indirectly attached to the role.\n */\n async hasLink(\n name1: string,\n name2: string,\n ...domain: string[]\n ): Promise<boolean> {\n if (domain.length > 0) {\n throw new Error('domain argument is not supported.');\n }\n\n // Name2 can be an empty string in the event that there is not a role associated with the user\n // This happens because of the filtering of the roles reduces the number of roles that we iterate through.\n if (name2.length === 0) {\n return false;\n }\n\n if (name1 === name2) {\n return true;\n }\n\n // name1 is always user in our case.\n // name2 is user or group.\n // user(name1) couldn't inherit user(name2).\n // We can use this fact for optimization.\n const { kind } = parseEntityRef(name2);\n if (kind.toLocaleLowerCase() === 'user') {\n return false;\n }\n\n // if it is a group, then we will have to build the graph,\n if (kind.toLocaleLowerCase() === 'group') {\n const memo = await AncestorSearchFactory.createAncestorSearchMemo(\n name1,\n this.config,\n this.catalogApi,\n this.catalogDBClient,\n this.auth,\n this.maxDepth,\n );\n\n await memo.buildUserGraph();\n memo.debugNodesAndEdges(this.logger, name1);\n\n if (!memo.isAcyclic()) {\n const cycles = memo.findCycles();\n\n this.logger.warn(\n `Detected cycle dependencies in the Group graph: ${JSON.stringify(\n cycles,\n )}. Admin/(catalog owner) have to fix it to make RBAC permission evaluation correct for groups: ${JSON.stringify(\n cycles,\n )}`,\n );\n return false;\n }\n\n return memo.hasEntityRef(name2);\n }\n\n return true;\n }\n\n /**\n * syncedHasLink determines whether role: name1 inherits role: name2.\n * domain is a prefix to the roles.\n */\n syncedHasLink?(\n _name1: string,\n _name2: string,\n ..._domain: string[]\n ): boolean {\n throw new Error('Method \"syncedHasLink\" not implemented.');\n }\n\n /**\n * getRoles gets the roles that a subject inherits.\n *\n * name - is a string entity reference, for example: user:default/tom, role:default/dev,\n * so format is <kind>:<namespace>/<entity-name>.\n * GetRoles method supports only two kind values: 'user' and 'role'.\n *\n * domain - is a prefix to the roles, unused parameter.\n *\n * If name's kind === 'user' we return all inherited roles from groups and roles directly assigned to the user.\n * if name's kind === 'role' we return empty array, because we don't support role inheritance.\n * Case kind === 'group' - should not happen, because:\n * 1) Method getRoles returns only role entity references, so casbin engine doesn't call this\n * method again to ask about name with kind \"group\".\n * 2) We implemented getRoles method only to use:\n * 'await enforcer.getImplicitPermissionsForUser(userEntityRef)',\n * so name argument can be only with kind 'user' or 'role'.\n *\n * Info: when we call 'await enforcer.getImplicitPermissionsForUser(userEntityRef)',\n * then casbin engine executes 'getRoles' method few times.\n * Firstly casbin asks about roles for 'userEntityRef'.\n * Let's imagine, that 'getRoles' returned two roles for userEntityRef.\n * Then casbin calls 'getRoles' two more times to\n * find parent roles. But we return empty array for each such call,\n * because we don't support role inheritance and we notify casbin about end of the role sub-tree.\n */\n async getRoles(name: string, ..._domain: string[]): Promise<string[]> {\n const { kind } = parseEntityRef(name);\n if (kind === 'user') {\n const memo = await AncestorSearchFactory.createAncestorSearchMemo(\n name,\n this.config,\n this.catalogApi,\n this.catalogDBClient,\n this.auth,\n this.maxDepth,\n );\n await memo.buildUserGraph();\n memo.debugNodesAndEdges(this.logger, name);\n\n // Account for the user not being in the graph (this can happen during direct assignment to roles)\n memo.setNode(name);\n\n if (!memo.isAcyclic()) {\n const cycles = memo.findCycles();\n\n this.logger.warn(\n `Detected cycle dependencies in the Group graph: ${JSON.stringify(\n cycles,\n )}. Admin/(catalog owner) have to fix it to make RBAC permission evaluation correct for groups: ${JSON.stringify(\n cycles,\n )}`,\n );\n return Promise.resolve([]);\n }\n\n if (this.isPGClient()) {\n const currentRole = new RoleMemberList(name);\n await currentRole.buildRoles(\n currentRole,\n memo.getNodes(),\n this.rbacDBClient,\n );\n const roles = currentRole.getRoles();\n if (this.defaultRoleRef) roles.push(this.defaultRoleRef);\n return Promise.resolve(roles);\n }\n\n const allRoles: string[] = [];\n for (const value of this.allRoles.values()) {\n if (this.hasMember(value, memo)) {\n allRoles.push(value.name);\n }\n }\n\n if (this.defaultRoleRef) allRoles.push(this.defaultRoleRef);\n return Promise.resolve(allRoles);\n }\n\n return [];\n }\n\n /**\n * getUsers gets the users that inherits a subject.\n * domain is an unreferenced parameter here, may be used in other implementations.\n */\n async getUsers(_name: string, ..._domain: string[]): Promise<string[]> {\n throw new Error('Method \"getUsers\" not implemented.');\n }\n\n /**\n * printRoles prints all the roles to log.\n */\n async printRoles(): Promise<void> {\n // do nothing\n }\n\n /**\n * getOrCreateRole will get a role if it has already been cached\n * or it will create a new role to be cached.\n * This cache is a simple tree that is used to quickly compare\n * users and groups to roles.\n * @param name The user or group whose cache we will be getting / creating.\n * @returns The cached role as a RoleList.\n */\n private getOrCreateRole(name: string): RoleMemberList {\n const role = this.allRoles.get(name);\n if (role) {\n return role;\n }\n const newRole = new RoleMemberList(name);\n this.allRoles.set(name, newRole);\n\n return newRole;\n }\n\n /**\n * isPGClient checks what the current database client is at them time.\n * This is to ensure that we are querying the database in the event of postgres\n * or using in memory cache for better sqlite3.\n * @returns True if the database client is pg.\n */\n isPGClient(): boolean {\n const client = this.rbacDBClient.client.config.client;\n return client === 'pg';\n }\n\n /**\n * hasMember checks if the members from a particular role is associated with the user\n * that the AncestorSearchMemo graph is built for.\n * @param role The role that we are getting the members from.\n * @param memo The user graph that we are comparing members with.\n * @returns True if a member from the role is also associated with the user.\n */\n private hasMember(\n role: RoleMemberList | undefined,\n memo: AncestorSearchMemo<ASMGroup>,\n ): boolean {\n if (role === undefined) {\n return false;\n }\n\n for (const member of role.getMembers()) {\n if (memo.hasEntityRef(member)) {\n return true;\n }\n }\n return false;\n }\n}\n"],"names":["parseEntityRef","AncestorSearchFactory","RoleMemberList"],"mappings":"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"}
@@ -53,6 +53,15 @@ class PoliciesServer {
53
53
  async serve() {
54
54
  const router$1 = await router.createRouter(this.options);
55
55
  const { logger, auditor, auth, permissionsRegistry } = this.options;
56
+ const defRoleMeta = this.roleMetadata.getCachedDefaultRoleMetadata();
57
+ let defRole;
58
+ if (defRoleMeta) {
59
+ defRole = {
60
+ name: defRoleMeta.roleEntityRef,
61
+ memberReferences: [],
62
+ metadata: roleMetadata.daoToMetadata(defRoleMeta)
63
+ };
64
+ }
56
65
  const isPluginEnabled = this.options.config.getOptionalBoolean("permission.enabled");
57
66
  if (!isPluginEnabled) {
58
67
  return router$1;
@@ -285,6 +294,9 @@ class PoliciesServer {
285
294
  }
286
295
  const roles = await this.enforcer.getGroupingPolicy();
287
296
  const body = await this.transformRoleArray(conditionsFilter, ...roles);
297
+ if (defRole) {
298
+ body.push(defRole);
299
+ }
288
300
  response.json(body);
289
301
  }
290
302
  );
@@ -302,11 +314,16 @@ class PoliciesServer {
302
314
  conditionsFilter = transformConditions(decision.conditions);
303
315
  }
304
316
  const roleEntityRef = this.getEntityReference(request, true);
305
- const role = await this.enforcer.getFilteredGroupingPolicy(
306
- 1,
307
- roleEntityRef
308
- );
309
- const body = await this.transformRoleArray(conditionsFilter, ...role);
317
+ let body;
318
+ if (defRole && roleEntityRef === defRole.name) {
319
+ body = [defRole];
320
+ } else {
321
+ const role = await this.enforcer.getFilteredGroupingPolicy(
322
+ 1,
323
+ roleEntityRef
324
+ );
325
+ body = await this.transformRoleArray(conditionsFilter, ...role);
326
+ }
310
327
  if (body.length !== 0) {
311
328
  response.json(body);
312
329
  } else {
@@ -430,7 +447,7 @@ class PoliciesServer {
430
447
  if (err) {
431
448
  throw new errors.NotAllowedError(`Unable to edit role: ${err.message}`);
432
449
  }
433
- if (!helper.matches(oldMetadata, conditionsFilter)) {
450
+ if (!helper.matches(roleMetadata.daoToMetadata(oldMetadata), conditionsFilter)) {
434
451
  throw new errors.NotAllowedError();
435
452
  }
436
453
  if (lodash.isEqual(oldRole, newRole) && helper.deepSortedEqual(oldMetadata, newMetadata, [
@@ -512,7 +529,7 @@ class PoliciesServer {
512
529
  }
513
530
  const roleEntityRef = this.getEntityReference(request, true);
514
531
  const currentMetadata = await this.roleMetadata.findRoleMetadata(roleEntityRef);
515
- if (!helper.matches(currentMetadata, conditionsFilter)) {
532
+ if (!currentMetadata || !helper.matches(roleMetadata.daoToMetadata(currentMetadata), conditionsFilter)) {
516
533
  throw new errors.NotAllowedError();
517
534
  }
518
535
  const err = await policiesValidation.validateSource("rest", currentMetadata);
@@ -682,10 +699,10 @@ class PoliciesServer {
682
699
  ...condition,
683
700
  permissionMapping: condition.permissionMapping.map((pm) => pm.action)
684
701
  };
685
- const roleMetadata = await this.roleMetadata.findRoleMetadata(
702
+ const roleMetadata$1 = await this.roleMetadata.findRoleMetadata(
686
703
  conditionToDelete.roleEntityRef
687
704
  );
688
- if (!helper.matches(roleMetadata, conditionsFilter)) {
705
+ if (!roleMetadata$1 || !helper.matches(roleMetadata.daoToMetadata(roleMetadata$1), conditionsFilter)) {
689
706
  throw new errors.NotAllowedError();
690
707
  }
691
708
  await this.conditionalStorage.deleteCondition(id);
@@ -714,10 +731,10 @@ class PoliciesServer {
714
731
  if (!condition) {
715
732
  throw new errors.NotFoundError(`Condition with id ${id} was not found`);
716
733
  }
717
- const roleMetadata = await this.roleMetadata.findRoleMetadata(
734
+ const roleMetadata$1 = await this.roleMetadata.findRoleMetadata(
718
735
  condition.roleEntityRef
719
736
  );
720
- if (!helper.matches(roleMetadata, conditionsFilter)) {
737
+ if (!roleMetadata$1 || !helper.matches(roleMetadata.daoToMetadata(roleMetadata$1), conditionsFilter)) {
721
738
  throw new errors.NotAllowedError();
722
739
  }
723
740
  const roleConditionPolicy = request.body;
@@ -896,7 +913,7 @@ class PoliciesServer {
896
913
  const metadata = await this.roleMetadata.findRoleMetadata(
897
914
  policy.entityReference
898
915
  );
899
- if (!helper.matches(metadata, filter)) {
916
+ if (!metadata || !helper.matches(roleMetadata.daoToMetadata(metadata), filter)) {
900
917
  throw new errors.NotAllowedError();
901
918
  }
902
919
  let action = errorMessage ? "edit" : "delete";