@backstage-community/plugin-rbac-backend 6.2.6 → 6.3.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.
Files changed (31) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/README.md +41 -3
  3. package/dist/auditor/auditor.cjs.js +5 -0
  4. package/dist/auditor/auditor.cjs.js.map +1 -1
  5. package/dist/auditor/rest-interceptor.cjs.js +5 -0
  6. package/dist/auditor/rest-interceptor.cjs.js.map +1 -1
  7. package/dist/database/extra-permission-enabled-plugins-storage.cjs.js +21 -0
  8. package/dist/database/extra-permission-enabled-plugins-storage.cjs.js.map +1 -0
  9. package/dist/index.d.ts +5 -1
  10. package/dist/permissions/conditions.cjs.js +6 -5
  11. package/dist/permissions/conditions.cjs.js.map +1 -1
  12. package/dist/permissions/resource.cjs.js +12 -0
  13. package/dist/permissions/resource.cjs.js.map +1 -0
  14. package/dist/permissions/rules.cjs.js +3 -4
  15. package/dist/permissions/rules.cjs.js.map +1 -1
  16. package/dist/plugin.cjs.js +6 -3
  17. package/dist/plugin.cjs.js.map +1 -1
  18. package/dist/service/extendable-id-provider.cjs.js +32 -0
  19. package/dist/service/extendable-id-provider.cjs.js.map +1 -0
  20. package/dist/service/permission-definition-routes.cjs.js +104 -0
  21. package/dist/service/permission-definition-routes.cjs.js.map +1 -0
  22. package/dist/service/plugin-endpoints.cjs.js +5 -4
  23. package/dist/service/plugin-endpoints.cjs.js.map +1 -1
  24. package/dist/service/policies-rest-api.cjs.js +131 -125
  25. package/dist/service/policies-rest-api.cjs.js.map +1 -1
  26. package/dist/service/policy-builder.cjs.js +31 -15
  27. package/dist/service/policy-builder.cjs.js.map +1 -1
  28. package/dist/validation/plugin-validation.cjs.js +15 -0
  29. package/dist/validation/plugin-validation.cjs.js.map +1 -0
  30. package/migrations/20250509110032_migrations.js +29 -0
  31. package/package.json +4 -4
package/CHANGELOG.md CHANGED
@@ -1,5 +1,19 @@
1
1
  ### Dependencies
2
2
 
3
+ ## 6.3.0
4
+
5
+ ### Minor Changes
6
+
7
+ - a42945e: Introduce API to store additional plugin ID list
8
+ - 3e3f346: Migrate rbac-backend to use permission registry service.
9
+
10
+ ### Patch Changes
11
+
12
+ - 098b200: Updated dependency `@types/express` to `4.17.22`.
13
+ - e958f2f: Updated dependency `@types/node` to `22.15.29`.
14
+ - Updated dependencies [a42945e]
15
+ - @backstage-community/plugin-rbac-common@1.17.0
16
+
3
17
  ## 6.2.6
4
18
 
5
19
  ### Patch Changes
package/README.md CHANGED
@@ -87,10 +87,14 @@ For more information on the available API endpoints accessible to the policy adm
87
87
 
88
88
  ### Configure plugins with permission
89
89
 
90
- In order for the RBAC UI to display available permissions provided by installed plugins, add the corresponding
91
- plugin IDs to the `app-config.yaml`.
90
+ In order for the RBAC UI to display the available permissions provided by installed plugins, you must supply the corresponding list of plugin IDs. There are two ways to achieve this:
92
91
 
93
- You can specify the plugins with permission in your application configuration as follows:
92
+ - Application configuration(`app-config.yaml`)
93
+ - REST API
94
+
95
+ #### Configure plugins with Application configuration
96
+
97
+ You can specify the plugins with permissions in your application configuration as follows:
94
98
 
95
99
  ```YAML
96
100
  permission:
@@ -106,6 +110,40 @@ permission:
106
110
  - name: group:default/admins
107
111
  ```
108
112
 
113
+ #### Configure plugins with REST API
114
+
115
+ You can specify the plugins with permissions using the corresponding [REST API](./docs/apis.md#plugin-ids-that-support-the-backstage-permission-framework).
116
+
117
+ Curl Examples:
118
+
119
+ Get the object containing the list of plugin IDs:
120
+
121
+ ```
122
+ curl -X GET "http://localhost:7007/api/permission/plugins/id" \
123
+ -H "Content-Type: application/json" \
124
+ -H "Authorization: Bearer $token" -v
125
+ ```
126
+
127
+ Add more plugin IDs:
128
+
129
+ ```
130
+ curl -X POST "http://localhost:7007/api/permission/plugins/id" \
131
+ -d '{ "ids": [ "permission", "scaffolder" ] }' \
132
+ -H "Content-Type: application/json" \
133
+ -H "Authorization: Bearer $token" -v
134
+ ```
135
+
136
+ Remove plugin IDs:
137
+
138
+ ```
139
+ curl -X DELETE "http://localhost:7007/api/permission/plugins/id" \
140
+ -d '{ "ids": [ "permission", "scaffolder" ] }' \
141
+ -H "Content-Type: application/json" \
142
+ -H "Authorization: Bearer $token" -v
143
+ ```
144
+
145
+ Notice: The REST API does not allow deletion of plugin IDs that were provided via application configuration, in order to prevent an inconsistent state after a deployment restart. These ID values can only be removed through the configuration file.
146
+
109
147
  For more information on the available permissions, refer to the [RBAC permissions documentation](./docs/permissions.md).
110
148
 
111
149
  ### Configuring policies via file
@@ -25,6 +25,10 @@ const ListPluginPoliciesEvents = {
25
25
  const ListConditionEvents = {
26
26
  CONDITION_RULES_READ: "condition-rules-read"
27
27
  };
28
+ const ListPluginIDsEvents = {
29
+ PLUGIN_IDS_READ: "plugin-ids-read",
30
+ PLUGIN_IDS_WRITE: "plugin-ids-write"
31
+ };
28
32
  const PoliciesData = {
29
33
  PERMISSIONS_READ: "permissions-read"
30
34
  };
@@ -57,6 +61,7 @@ exports.ActionType = ActionType;
57
61
  exports.ConditionEvents = ConditionEvents;
58
62
  exports.EvaluationEvents = EvaluationEvents;
59
63
  exports.ListConditionEvents = ListConditionEvents;
64
+ exports.ListPluginIDsEvents = ListPluginIDsEvents;
60
65
  exports.ListPluginPoliciesEvents = ListPluginPoliciesEvents;
61
66
  exports.PermissionEvents = PermissionEvents;
62
67
  exports.PoliciesData = PoliciesData;
@@ -1 +1 @@
1
- {"version":3,"file":"auditor.cjs.js","sources":["../../src/auditor/auditor.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 PolicyDecision,\n ResourcePermission,\n} from '@backstage/plugin-permission-common';\nimport type { PolicyQuery } from '@backstage/plugin-permission-node';\n\nimport {\n PermissionAction,\n toPermissionAction,\n} from '@backstage-community/plugin-rbac-common';\nimport {\n AuditorService,\n AuditorServiceEvent,\n} from '@backstage/backend-plugin-api';\n\nexport const ActionType = {\n CREATE: 'create',\n CREATE_OR_UPDATE: 'create_or_update',\n UPDATE: 'update',\n DELETE: 'delete',\n};\n\nexport const RoleEvents = {\n ROLE_WRITE: 'role-write',\n ROLE_READ: 'role-read',\n} as const;\n\nexport const PermissionEvents = {\n POLICY_WRITE: 'policy-write',\n POLICY_READ: 'policy-read',\n} as const;\n\nexport const EvaluationEvents = {\n PERMISSION_EVALUATION: 'permission-evaluation',\n} as const;\n\nexport const ListPluginPoliciesEvents = {\n PLUGIN_POLICIES_READ: 'plugin-policies-read',\n};\n\nexport const ListConditionEvents = {\n CONDITION_RULES_READ: 'condition-rules-read',\n};\n\nexport type EvaluationAuditInfo = {\n userEntityRef: string;\n permissionName: string;\n action: PermissionAction;\n resourceType?: string;\n decision?: PolicyDecision;\n};\n\nexport const PoliciesData = {\n PERMISSIONS_READ: 'permissions-read',\n};\n\nexport const ConditionEvents = {\n CONDITION_WRITE: 'condition-write',\n CONDITION_READ: 'condition-read',\n CONDITIONAL_POLICIES_FILE_NOT_FOUND: 'conditional-policies-file-not-found',\n CONDITIONAL_POLICIES_FILE_CHANGE: 'conditional-policies-file-change',\n};\n\nexport async function createPermissionEvaluationAuditorEvent(\n auditor: AuditorService,\n userEntityRef: string,\n request: PolicyQuery,\n policyDecision?: PolicyDecision,\n): Promise<AuditorServiceEvent> {\n const auditInfo: EvaluationAuditInfo = {\n userEntityRef,\n permissionName: request.permission.name,\n action: toPermissionAction(request.permission.attributes),\n };\n\n const resourceType = (request.permission as ResourcePermission).resourceType;\n if (resourceType) {\n auditInfo.resourceType = resourceType;\n }\n if (policyDecision) {\n auditInfo.decision = policyDecision;\n }\n\n return await auditor.createEvent({\n eventId: EvaluationEvents.PERMISSION_EVALUATION,\n severityLevel: 'medium',\n meta: {\n ...auditInfo,\n },\n });\n}\n"],"names":["toPermissionAction"],"mappings":";;;;AA8BO,MAAM,UAAa,GAAA;AAAA,EACxB,MAAQ,EAAA,QAAA;AAAA,EACR,gBAAkB,EAAA,kBAAA;AAAA,EAClB,MAAQ,EAAA,QAAA;AAAA,EACR,MAAQ,EAAA;AACV;AAEO,MAAM,UAAa,GAAA;AAAA,EACxB,UAAY,EAAA,YAAA;AAAA,EACZ,SAAW,EAAA;AACb;AAEO,MAAM,gBAAmB,GAAA;AAAA,EAC9B,YAAc,EAAA,cAAA;AAAA,EACd,WAAa,EAAA;AACf;AAEO,MAAM,gBAAmB,GAAA;AAAA,EAC9B,qBAAuB,EAAA;AACzB;AAEO,MAAM,wBAA2B,GAAA;AAAA,EACtC,oBAAsB,EAAA;AACxB;AAEO,MAAM,mBAAsB,GAAA;AAAA,EACjC,oBAAsB,EAAA;AACxB;AAUO,MAAM,YAAe,GAAA;AAAA,EAC1B,gBAAkB,EAAA;AACpB;AAEO,MAAM,eAAkB,GAAA;AAAA,EAC7B,eAAiB,EAAA,iBAAA;AAAA,EACjB,cAAgB,EAAA,gBAAA;AAAA,EAChB,mCAAqC,EAAA,qCAAA;AAAA,EACrC,gCAAkC,EAAA;AACpC;AAEA,eAAsB,sCACpB,CAAA,OAAA,EACA,aACA,EAAA,OAAA,EACA,cAC8B,EAAA;AAC9B,EAAA,MAAM,SAAiC,GAAA;AAAA,IACrC,aAAA;AAAA,IACA,cAAA,EAAgB,QAAQ,UAAW,CAAA,IAAA;AAAA,IACnC,MAAQ,EAAAA,mCAAA,CAAmB,OAAQ,CAAA,UAAA,CAAW,UAAU;AAAA,GAC1D;AAEA,EAAM,MAAA,YAAA,GAAgB,QAAQ,UAAkC,CAAA,YAAA;AAChE,EAAA,IAAI,YAAc,EAAA;AAChB,IAAA,SAAA,CAAU,YAAe,GAAA,YAAA;AAAA;AAM3B,EAAO,OAAA,MAAM,QAAQ,WAAY,CAAA;AAAA,IAC/B,SAAS,gBAAiB,CAAA,qBAAA;AAAA,IAC1B,aAAe,EAAA,QAAA;AAAA,IACf,IAAM,EAAA;AAAA,MACJ,GAAG;AAAA;AACL,GACD,CAAA;AACH;;;;;;;;;;;;"}
1
+ {"version":3,"file":"auditor.cjs.js","sources":["../../src/auditor/auditor.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 PolicyDecision,\n ResourcePermission,\n} from '@backstage/plugin-permission-common';\nimport type { PolicyQuery } from '@backstage/plugin-permission-node';\n\nimport {\n PermissionAction,\n toPermissionAction,\n} from '@backstage-community/plugin-rbac-common';\nimport {\n AuditorService,\n AuditorServiceEvent,\n} from '@backstage/backend-plugin-api';\n\nexport const ActionType = {\n CREATE: 'create',\n CREATE_OR_UPDATE: 'create_or_update',\n UPDATE: 'update',\n DELETE: 'delete',\n};\n\nexport const RoleEvents = {\n ROLE_WRITE: 'role-write',\n ROLE_READ: 'role-read',\n} as const;\n\nexport const PermissionEvents = {\n POLICY_WRITE: 'policy-write',\n POLICY_READ: 'policy-read',\n} as const;\n\nexport const EvaluationEvents = {\n PERMISSION_EVALUATION: 'permission-evaluation',\n} as const;\n\nexport const ListPluginPoliciesEvents = {\n PLUGIN_POLICIES_READ: 'plugin-policies-read',\n};\n\nexport const ListConditionEvents = {\n CONDITION_RULES_READ: 'condition-rules-read',\n};\n\nexport const ListPluginIDsEvents = {\n PLUGIN_IDS_READ: 'plugin-ids-read',\n PLUGIN_IDS_WRITE: 'plugin-ids-write',\n};\n\nexport type EvaluationAuditInfo = {\n userEntityRef: string;\n permissionName: string;\n action: PermissionAction;\n resourceType?: string;\n decision?: PolicyDecision;\n};\n\nexport const PoliciesData = {\n PERMISSIONS_READ: 'permissions-read',\n};\n\nexport const ConditionEvents = {\n CONDITION_WRITE: 'condition-write',\n CONDITION_READ: 'condition-read',\n CONDITIONAL_POLICIES_FILE_NOT_FOUND: 'conditional-policies-file-not-found',\n CONDITIONAL_POLICIES_FILE_CHANGE: 'conditional-policies-file-change',\n};\n\nexport async function createPermissionEvaluationAuditorEvent(\n auditor: AuditorService,\n userEntityRef: string,\n request: PolicyQuery,\n policyDecision?: PolicyDecision,\n): Promise<AuditorServiceEvent> {\n const auditInfo: EvaluationAuditInfo = {\n userEntityRef,\n permissionName: request.permission.name,\n action: toPermissionAction(request.permission.attributes),\n };\n\n const resourceType = (request.permission as ResourcePermission).resourceType;\n if (resourceType) {\n auditInfo.resourceType = resourceType;\n }\n if (policyDecision) {\n auditInfo.decision = policyDecision;\n }\n\n return await auditor.createEvent({\n eventId: EvaluationEvents.PERMISSION_EVALUATION,\n severityLevel: 'medium',\n meta: {\n ...auditInfo,\n },\n });\n}\n"],"names":["toPermissionAction"],"mappings":";;;;AA8BO,MAAM,UAAa,GAAA;AAAA,EACxB,MAAQ,EAAA,QAAA;AAAA,EACR,gBAAkB,EAAA,kBAAA;AAAA,EAClB,MAAQ,EAAA,QAAA;AAAA,EACR,MAAQ,EAAA;AACV;AAEO,MAAM,UAAa,GAAA;AAAA,EACxB,UAAY,EAAA,YAAA;AAAA,EACZ,SAAW,EAAA;AACb;AAEO,MAAM,gBAAmB,GAAA;AAAA,EAC9B,YAAc,EAAA,cAAA;AAAA,EACd,WAAa,EAAA;AACf;AAEO,MAAM,gBAAmB,GAAA;AAAA,EAC9B,qBAAuB,EAAA;AACzB;AAEO,MAAM,wBAA2B,GAAA;AAAA,EACtC,oBAAsB,EAAA;AACxB;AAEO,MAAM,mBAAsB,GAAA;AAAA,EACjC,oBAAsB,EAAA;AACxB;AAEO,MAAM,mBAAsB,GAAA;AAAA,EACjC,eAAiB,EAAA,iBAAA;AAAA,EACjB,gBAAkB,EAAA;AACpB;AAUO,MAAM,YAAe,GAAA;AAAA,EAC1B,gBAAkB,EAAA;AACpB;AAEO,MAAM,eAAkB,GAAA;AAAA,EAC7B,eAAiB,EAAA,iBAAA;AAAA,EACjB,cAAgB,EAAA,gBAAA;AAAA,EAChB,mCAAqC,EAAA,qCAAA;AAAA,EACrC,gCAAkC,EAAA;AACpC;AAEA,eAAsB,sCACpB,CAAA,OAAA,EACA,aACA,EAAA,OAAA,EACA,cAC8B,EAAA;AAC9B,EAAA,MAAM,SAAiC,GAAA;AAAA,IACrC,aAAA;AAAA,IACA,cAAA,EAAgB,QAAQ,UAAW,CAAA,IAAA;AAAA,IACnC,MAAQ,EAAAA,mCAAA,CAAmB,OAAQ,CAAA,UAAA,CAAW,UAAU;AAAA,GAC1D;AAEA,EAAM,MAAA,YAAA,GAAgB,QAAQ,UAAkC,CAAA,YAAA;AAChE,EAAA,IAAI,YAAc,EAAA;AAChB,IAAA,SAAA,CAAU,YAAe,GAAA,YAAA;AAAA;AAM3B,EAAO,OAAA,MAAM,QAAQ,WAAY,CAAA;AAAA,IAC/B,SAAS,gBAAiB,CAAA,qBAAA;AAAA,IAC1B,aAAe,EAAA,QAAA;AAAA,IACf,IAAM,EAAA;AAAA,MACJ,GAAG;AAAA;AACL,GACD,CAAA;AACH;;;;;;;;;;;;;"}
@@ -26,6 +26,11 @@ const eventMap = {
26
26
  },
27
27
  "/plugins/condition-rules": {
28
28
  GET: auditor.ListConditionEvents.CONDITION_RULES_READ
29
+ },
30
+ "/plugins/id": {
31
+ GET: auditor.ListPluginIDsEvents.PLUGIN_IDS_READ,
32
+ POST: auditor.ListPluginIDsEvents.PLUGIN_IDS_WRITE,
33
+ DELETE: auditor.ListPluginIDsEvents.PLUGIN_IDS_WRITE
29
34
  }
30
35
  };
31
36
  const eventToActionMap = {
@@ -1 +1 @@
1
- {"version":3,"file":"rest-interceptor.cjs.js","sources":["../../src/auditor/rest-interceptor.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 type RequestHandler,\n type NextFunction,\n type Request,\n type Response,\n type ErrorRequestHandler,\n} from 'express';\n\nimport {\n ActionType,\n ConditionEvents,\n ListConditionEvents,\n ListPluginPoliciesEvents,\n PermissionEvents,\n RoleEvents,\n} from './auditor';\nimport {\n AuditorService,\n AuditorServiceEvent,\n} from '@backstage/backend-plugin-api';\nimport type { JsonObject } from '@backstage/types';\n\n// Mapping paths and methods to corresponding events and messages\nconst eventMap: {\n [key: string]: { [key: string]: string };\n} = {\n '/policies': {\n POST: PermissionEvents.POLICY_WRITE,\n PUT: PermissionEvents.POLICY_WRITE,\n DELETE: PermissionEvents.POLICY_WRITE,\n GET: PermissionEvents.POLICY_READ,\n },\n '/roles/conditions': {\n POST: ConditionEvents.CONDITION_WRITE,\n PUT: ConditionEvents.CONDITION_WRITE,\n DELETE: ConditionEvents.CONDITION_WRITE,\n GET: ConditionEvents.CONDITION_READ,\n },\n '/roles': {\n POST: RoleEvents.ROLE_WRITE,\n PUT: RoleEvents.ROLE_WRITE,\n DELETE: RoleEvents.ROLE_WRITE,\n GET: RoleEvents.ROLE_READ,\n },\n '/plugins/policies': {\n GET: ListPluginPoliciesEvents.PLUGIN_POLICIES_READ,\n },\n '/plugins/condition-rules': {\n GET: ListConditionEvents.CONDITION_RULES_READ,\n },\n};\n\nconst eventToActionMap: {\n [key: string]: string;\n} = {\n POST: ActionType.CREATE,\n PUT: ActionType.UPDATE,\n DELETE: ActionType.DELETE,\n};\n\nfunction getRequestAuditorMeta(req: Request, eventId: string): JsonObject {\n const meta = {\n ...(req.method in eventToActionMap\n ? { actionType: eventToActionMap[req.method] }\n : {}),\n source: 'rest',\n };\n\n if (req.method !== 'GET') {\n return meta;\n }\n\n let extraMeta = {};\n const hasQuery = Object.keys(req.query).length > 0;\n const hasParams = Object.keys(req.params).length > 0;\n switch (eventId) {\n case PermissionEvents.POLICY_READ:\n if (hasParams) {\n extraMeta = {\n queryType: 'by-role',\n entityRef: `${req.params.kind}:${req.params.namespace}/${req.params.name}`,\n };\n break;\n }\n extraMeta = {\n queryType: hasQuery ? 'by-query' : 'all',\n ...(hasQuery ? { query: req.query } : {}),\n };\n break;\n case RoleEvents.ROLE_READ:\n extraMeta = {\n queryType: hasParams ? 'by-role' : 'all',\n ...(hasParams\n ? {\n entityRef: `${req.params.kind}:${req.params.namespace}/${req.params.name}`,\n }\n : {}),\n };\n break;\n case ConditionEvents.CONDITION_READ:\n if (hasParams) {\n extraMeta = {\n queryType: 'by-id',\n id: req.params.id,\n };\n break;\n }\n extraMeta = {\n queryType: hasQuery ? 'by-query' : 'all',\n ...(hasQuery ? { query: req.query } : {}),\n };\n break;\n default:\n break;\n }\n return { ...meta, ...extraMeta };\n}\n\nexport function logAuditorEvent(auditor: AuditorService): RequestHandler {\n return async (req: Request, resp: Response, next: NextFunction) => {\n let auditorEvent: AuditorServiceEvent | undefined;\n const matchedPath = Object.keys(eventMap).find(path =>\n req.path.startsWith(path),\n );\n if (matchedPath) {\n const methodEvent = eventMap[matchedPath][req.method];\n if (methodEvent) {\n const meta = getRequestAuditorMeta(req, methodEvent);\n auditorEvent = await auditor.createEvent({\n eventId: methodEvent,\n severityLevel: 'medium',\n request: req,\n meta,\n });\n }\n }\n\n resp.on('finish', async () => {\n const meta = {\n response: { status: resp.statusCode },\n ...(resp.locals.meta ?? {}),\n };\n if (resp.statusCode < 400) {\n await auditorEvent?.success({ meta });\n } else {\n const error = resp.locals.error ?? new Error(resp.statusMessage);\n await auditorEvent?.fail({\n error,\n meta,\n });\n }\n });\n\n next();\n };\n}\n\nexport function setAuditorError(): ErrorRequestHandler {\n return async (\n err: Error,\n _req: Request,\n resp: Response,\n next: NextFunction,\n ) => {\n resp.locals.error = err;\n next(err);\n };\n}\n"],"names":["PermissionEvents","ConditionEvents","RoleEvents","ListPluginPoliciesEvents","ListConditionEvents","ActionType"],"mappings":"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"}
1
+ {"version":3,"file":"rest-interceptor.cjs.js","sources":["../../src/auditor/rest-interceptor.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 type RequestHandler,\n type NextFunction,\n type Request,\n type Response,\n type ErrorRequestHandler,\n} from 'express';\n\nimport {\n ActionType,\n ConditionEvents,\n ListConditionEvents,\n ListPluginIDsEvents,\n ListPluginPoliciesEvents,\n PermissionEvents,\n RoleEvents,\n} from './auditor';\nimport {\n AuditorService,\n AuditorServiceEvent,\n} from '@backstage/backend-plugin-api';\nimport type { JsonObject } from '@backstage/types';\n\n// Mapping paths and methods to corresponding events and messages\nconst eventMap: {\n [key: string]: { [key: string]: string };\n} = {\n '/policies': {\n POST: PermissionEvents.POLICY_WRITE,\n PUT: PermissionEvents.POLICY_WRITE,\n DELETE: PermissionEvents.POLICY_WRITE,\n GET: PermissionEvents.POLICY_READ,\n },\n '/roles/conditions': {\n POST: ConditionEvents.CONDITION_WRITE,\n PUT: ConditionEvents.CONDITION_WRITE,\n DELETE: ConditionEvents.CONDITION_WRITE,\n GET: ConditionEvents.CONDITION_READ,\n },\n '/roles': {\n POST: RoleEvents.ROLE_WRITE,\n PUT: RoleEvents.ROLE_WRITE,\n DELETE: RoleEvents.ROLE_WRITE,\n GET: RoleEvents.ROLE_READ,\n },\n '/plugins/policies': {\n GET: ListPluginPoliciesEvents.PLUGIN_POLICIES_READ,\n },\n '/plugins/condition-rules': {\n GET: ListConditionEvents.CONDITION_RULES_READ,\n },\n '/plugins/id': {\n GET: ListPluginIDsEvents.PLUGIN_IDS_READ,\n POST: ListPluginIDsEvents.PLUGIN_IDS_WRITE,\n DELETE: ListPluginIDsEvents.PLUGIN_IDS_WRITE,\n },\n};\n\nconst eventToActionMap: {\n [key: string]: string;\n} = {\n POST: ActionType.CREATE,\n PUT: ActionType.UPDATE,\n DELETE: ActionType.DELETE,\n};\n\nfunction getRequestAuditorMeta(req: Request, eventId: string): JsonObject {\n const meta = {\n ...(req.method in eventToActionMap\n ? { actionType: eventToActionMap[req.method] }\n : {}),\n source: 'rest',\n };\n\n if (req.method !== 'GET') {\n return meta;\n }\n\n let extraMeta = {};\n const hasQuery = Object.keys(req.query).length > 0;\n const hasParams = Object.keys(req.params).length > 0;\n switch (eventId) {\n case PermissionEvents.POLICY_READ:\n if (hasParams) {\n extraMeta = {\n queryType: 'by-role',\n entityRef: `${req.params.kind}:${req.params.namespace}/${req.params.name}`,\n };\n break;\n }\n extraMeta = {\n queryType: hasQuery ? 'by-query' : 'all',\n ...(hasQuery ? { query: req.query } : {}),\n };\n break;\n case RoleEvents.ROLE_READ:\n extraMeta = {\n queryType: hasParams ? 'by-role' : 'all',\n ...(hasParams\n ? {\n entityRef: `${req.params.kind}:${req.params.namespace}/${req.params.name}`,\n }\n : {}),\n };\n break;\n case ConditionEvents.CONDITION_READ:\n if (hasParams) {\n extraMeta = {\n queryType: 'by-id',\n id: req.params.id,\n };\n break;\n }\n extraMeta = {\n queryType: hasQuery ? 'by-query' : 'all',\n ...(hasQuery ? { query: req.query } : {}),\n };\n break;\n default:\n break;\n }\n return { ...meta, ...extraMeta };\n}\n\nexport function logAuditorEvent(auditor: AuditorService): RequestHandler {\n return async (req: Request, resp: Response, next: NextFunction) => {\n let auditorEvent: AuditorServiceEvent | undefined;\n const matchedPath = Object.keys(eventMap).find(path =>\n req.path.startsWith(path),\n );\n if (matchedPath) {\n const methodEvent = eventMap[matchedPath][req.method];\n if (methodEvent) {\n const meta = getRequestAuditorMeta(req, methodEvent);\n auditorEvent = await auditor.createEvent({\n eventId: methodEvent,\n severityLevel: 'medium',\n request: req,\n meta,\n });\n }\n }\n\n resp.on('finish', async () => {\n const meta = {\n response: { status: resp.statusCode },\n ...(resp.locals.meta ?? {}),\n };\n if (resp.statusCode < 400) {\n await auditorEvent?.success({ meta });\n } else {\n const error = resp.locals.error ?? new Error(resp.statusMessage);\n await auditorEvent?.fail({\n error,\n meta,\n });\n }\n });\n\n next();\n };\n}\n\nexport function setAuditorError(): ErrorRequestHandler {\n return async (\n err: Error,\n _req: Request,\n resp: Response,\n next: NextFunction,\n ) => {\n resp.locals.error = err;\n next(err);\n };\n}\n"],"names":["PermissionEvents","ConditionEvents","RoleEvents","ListPluginPoliciesEvents","ListConditionEvents","ListPluginIDsEvents","ActionType"],"mappings":"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"}
@@ -0,0 +1,21 @@
1
+ 'use strict';
2
+
3
+ const PLUGINS_TABLE = "extra_permission_enabled_plugins";
4
+ class PermissionDependentPluginDatabaseStore {
5
+ constructor(knex) {
6
+ this.knex = knex;
7
+ }
8
+ async getPlugins() {
9
+ return await this.knex.table(PLUGINS_TABLE).select("pluginId");
10
+ }
11
+ async addPlugins(plugins) {
12
+ await this.knex.table(PLUGINS_TABLE).insert(plugins);
13
+ }
14
+ async deletePlugins(pluginIds) {
15
+ await this.knex.table(PLUGINS_TABLE).whereIn("pluginId", pluginIds).delete();
16
+ }
17
+ }
18
+
19
+ exports.PLUGINS_TABLE = PLUGINS_TABLE;
20
+ exports.PermissionDependentPluginDatabaseStore = PermissionDependentPluginDatabaseStore;
21
+ //# sourceMappingURL=extra-permission-enabled-plugins-storage.cjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"extra-permission-enabled-plugins-storage.cjs.js","sources":["../../src/database/extra-permission-enabled-plugins-storage.ts"],"sourcesContent":["/*\n * Copyright 2025 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 { Knex } from 'knex';\n\nexport const PLUGINS_TABLE = 'extra_permission_enabled_plugins';\n\nexport interface PermissionDependentPluginDTO {\n pluginId: string;\n}\n\n/**\n * This interface defines the methods for managing the extra permission-enabled plugins in the database.\n */\nexport interface PermissionDependentPluginStore {\n // Fetches the extra plugin list from database.\n // This list contains information about extra plugins that supports Backstage permissions framework.\n getPlugins(): Promise<PermissionDependentPluginDTO[]>;\n\n // Adds the plugins to the database.\n addPlugins(plugins: PermissionDependentPluginDTO[]): Promise<void>;\n\n // Removes plugins from the database by pluginIds.\n deletePlugins(pluginIds: string[]): Promise<void>;\n}\n\nexport class PermissionDependentPluginDatabaseStore\n implements PermissionDependentPluginStore\n{\n public constructor(private readonly knex: Knex<any, any[]>) {}\n\n async getPlugins(): Promise<PermissionDependentPluginDTO[]> {\n return await this.knex\n .table(PLUGINS_TABLE)\n .select<PermissionDependentPluginDTO[]>('pluginId');\n }\n\n async addPlugins(plugins: PermissionDependentPluginDTO[]): Promise<void> {\n await this.knex.table(PLUGINS_TABLE).insert(plugins);\n }\n\n async deletePlugins(pluginIds: string[]): Promise<void> {\n await this.knex\n .table(PLUGINS_TABLE)\n .whereIn('pluginId', pluginIds)\n .delete();\n }\n}\n"],"names":[],"mappings":";;AAiBO,MAAM,aAAgB,GAAA;AAqBtB,MAAM,sCAEb,CAAA;AAAA,EACS,YAA6B,IAAwB,EAAA;AAAxB,IAAA,IAAA,CAAA,IAAA,GAAA,IAAA;AAAA;AAAyB,EAE7D,MAAM,UAAsD,GAAA;AAC1D,IAAA,OAAO,MAAM,IAAK,CAAA,IAAA,CACf,MAAM,aAAa,CAAA,CACnB,OAAuC,UAAU,CAAA;AAAA;AACtD,EAEA,MAAM,WAAW,OAAwD,EAAA;AACvE,IAAA,MAAM,KAAK,IAAK,CAAA,KAAA,CAAM,aAAa,CAAA,CAAE,OAAO,OAAO,CAAA;AAAA;AACrD,EAEA,MAAM,cAAc,SAAoC,EAAA;AACtD,IAAM,MAAA,IAAA,CAAK,KACR,KAAM,CAAA,aAAa,EACnB,OAAQ,CAAA,UAAA,EAAY,SAAS,CAAA,CAC7B,MAAO,EAAA;AAAA;AAEd;;;;;"}
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import * as _backstage_backend_plugin_api from '@backstage/backend-plugin-api';
2
- import { LoggerService, DiscoveryService, AuthService, HttpAuthService, AuditorService, UserInfoService, LifecycleService } from '@backstage/backend-plugin-api';
2
+ import { LoggerService, DiscoveryService, AuthService, HttpAuthService, AuditorService, UserInfoService, LifecycleService, PermissionsRegistryService, PermissionsService } from '@backstage/backend-plugin-api';
3
3
  import { Config } from '@backstage/config';
4
4
  import express, { Router } from 'express';
5
5
  import { PermissionEvaluator } from '@backstage/plugin-permission-common';
@@ -32,6 +32,7 @@ type EnvOptions = {
32
32
  auditor: AuditorService;
33
33
  userInfo: UserInfoService;
34
34
  lifecycle: LifecycleService;
35
+ permissionsRegistry: PermissionsRegistryService;
35
36
  };
36
37
  /**
37
38
  * @public
@@ -44,6 +45,9 @@ type RBACRouterOptions = {
44
45
  auth: AuthService;
45
46
  httpAuth: HttpAuthService;
46
47
  userInfo: UserInfoService;
48
+ permissions: PermissionsService;
49
+ permissionsRegistry: PermissionsRegistryService;
50
+ auditor: AuditorService;
47
51
  };
48
52
  /**
49
53
  * @public
@@ -1,15 +1,16 @@
1
1
  'use strict';
2
2
 
3
- var pluginRbacCommon = require('@backstage-community/plugin-rbac-common');
4
3
  var pluginPermissionNode = require('@backstage/plugin-permission-node');
5
4
  var rules = require('./rules.cjs.js');
5
+ var resource = require('./resource.cjs.js');
6
6
 
7
7
  pluginPermissionNode.createConditionExports({
8
- pluginId: "permission",
9
- resourceType: pluginRbacCommon.RESOURCE_TYPE_POLICY_ENTITY,
8
+ resourceRef: resource.permissionMetadataResourceRef,
10
9
  rules: rules.rules
11
10
  });
12
- const transformConditions = pluginPermissionNode.createConditionTransformer(Object.values(rules.rules));
11
+ const conditionTransformerFunc = (permissionRegistry) => pluginPermissionNode.createConditionTransformer(
12
+ permissionRegistry.getPermissionRuleset(resource.permissionMetadataResourceRef)
13
+ );
13
14
 
14
- exports.transformConditions = transformConditions;
15
+ exports.conditionTransformerFunc = conditionTransformerFunc;
15
16
  //# sourceMappingURL=conditions.cjs.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"conditions.cjs.js","sources":["../../src/permissions/conditions.ts"],"sourcesContent":["/*\n * Copyright 2025 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 { RESOURCE_TYPE_POLICY_ENTITY } from '@backstage-community/plugin-rbac-common';\nimport {\n ConditionTransformer,\n createConditionExports,\n createConditionTransformer,\n} from '@backstage/plugin-permission-node';\n\nimport { rules, RBACFilter } from './rules';\n\nconst { conditions, createConditionalDecision } = createConditionExports({\n pluginId: 'permission',\n resourceType: RESOURCE_TYPE_POLICY_ENTITY,\n rules,\n});\n\nexport const rbacConditions = conditions;\n\nexport const createRBACConditionalDecision = createConditionalDecision;\n\nexport const transformConditions: ConditionTransformer<RBACFilter> =\n createConditionTransformer(Object.values(rules));\n"],"names":["createConditionExports","RESOURCE_TYPE_POLICY_ENTITY","rules","createConditionTransformer"],"mappings":";;;;;;AAwBkDA,2CAAuB,CAAA;AAAA,EACvE,QAAU,EAAA,YAAA;AAAA,EACV,YAAc,EAAAC,4CAAA;AAAA,SACdC;AACF,CAAC;AAMM,MAAM,mBACX,GAAAC,+CAAA,CAA2B,MAAO,CAAA,MAAA,CAAOD,WAAK,CAAC;;;;"}
1
+ {"version":3,"file":"conditions.cjs.js","sources":["../../src/permissions/conditions.ts"],"sourcesContent":["/*\n * Copyright 2025 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 ConditionTransformer,\n createConditionExports,\n createConditionTransformer,\n} from '@backstage/plugin-permission-node';\n\nimport { rules, RBACFilter } from './rules';\nimport { PermissionsRegistryService } from '@backstage/backend-plugin-api';\nimport { permissionMetadataResourceRef } from './resource';\n\nconst { conditions, createConditionalDecision } = createConditionExports({\n resourceRef: permissionMetadataResourceRef,\n rules,\n});\n\nexport const rbacConditions = conditions;\n\nexport const createRBACConditionalDecision = createConditionalDecision;\n\nexport const conditionTransformerFunc: (\n permissionRegistry: PermissionsRegistryService,\n) => ConditionTransformer<RBACFilter> = (\n permissionRegistry: PermissionsRegistryService,\n) =>\n createConditionTransformer(\n permissionRegistry.getPermissionRuleset(permissionMetadataResourceRef),\n );\n"],"names":["createConditionExports","permissionMetadataResourceRef","rules","createConditionTransformer"],"mappings":";;;;;;AAyBkDA,2CAAuB,CAAA;AAAA,EACvE,WAAa,EAAAC,sCAAA;AAAA,SACbC;AACF,CAAC;AAMY,MAAA,wBAAA,GAE2B,CACtC,kBAEA,KAAAC,+CAAA;AAAA,EACE,kBAAA,CAAmB,qBAAqBF,sCAA6B;AACvE;;;;"}
@@ -0,0 +1,12 @@
1
+ 'use strict';
2
+
3
+ var pluginRbacCommon = require('@backstage-community/plugin-rbac-common');
4
+ var pluginPermissionNode = require('@backstage/plugin-permission-node');
5
+
6
+ const permissionMetadataResourceRef = pluginPermissionNode.createPermissionResourceRef().with({
7
+ pluginId: "permission",
8
+ resourceType: pluginRbacCommon.RESOURCE_TYPE_POLICY_ENTITY
9
+ });
10
+
11
+ exports.permissionMetadataResourceRef = permissionMetadataResourceRef;
12
+ //# sourceMappingURL=resource.cjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resource.cjs.js","sources":["../../src/permissions/resource.ts"],"sourcesContent":["/*\n * Copyright 2025 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 RESOURCE_TYPE_POLICY_ENTITY,\n type RoleMetadata,\n} from '@backstage-community/plugin-rbac-common';\nimport { createPermissionResourceRef } from '@backstage/plugin-permission-node';\nimport { RBACFilter } from './rules';\n\n/**\n * Reference to the RBAC permission metadata resource.\n * This is used to create RBAC permissions and conditions.\n *\n */\nexport const permissionMetadataResourceRef = createPermissionResourceRef<\n RoleMetadata,\n RBACFilter\n>().with({\n pluginId: 'permission',\n resourceType: RESOURCE_TYPE_POLICY_ENTITY,\n});\n"],"names":["createPermissionResourceRef","RESOURCE_TYPE_POLICY_ENTITY"],"mappings":";;;;;AA2Ba,MAAA,6BAAA,GAAgCA,gDAG3C,EAAA,CAAE,IAAK,CAAA;AAAA,EACP,QAAU,EAAA,YAAA;AAAA,EACV,YAAc,EAAAC;AAChB,CAAC;;;;"}
@@ -1,19 +1,18 @@
1
1
  'use strict';
2
2
 
3
3
  var pluginPermissionNode = require('@backstage/plugin-permission-node');
4
- var pluginRbacCommon = require('@backstage-community/plugin-rbac-common');
5
4
  var zod = require('zod');
6
5
  var zodToJsonSchema = require('zod-to-json-schema');
6
+ var resource = require('./resource.cjs.js');
7
7
 
8
8
  function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e : { default: e }; }
9
9
 
10
10
  var zodToJsonSchema__default = /*#__PURE__*/_interopDefaultCompat(zodToJsonSchema);
11
11
 
12
- const createRBACPermissionRule = pluginPermissionNode.makeCreatePermissionRule();
13
- const isOwner = createRBACPermissionRule({
12
+ const isOwner = pluginPermissionNode.createPermissionRule({
14
13
  name: "IS_OWNER",
15
14
  description: "Should allow access to RBAC roles and Permissions through ownership",
16
- resourceType: pluginRbacCommon.RESOURCE_TYPE_POLICY_ENTITY,
15
+ resourceRef: resource.permissionMetadataResourceRef,
17
16
  paramsSchema: zod.z.object({
18
17
  owners: zod.z.string().array().describe("List of entity refs to match against")
19
18
  }),
@@ -1 +1 @@
1
- {"version":3,"file":"rules.cjs.js","sources":["../../src/permissions/rules.ts"],"sourcesContent":["/*\n * Copyright 2025 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 { makeCreatePermissionRule } from '@backstage/plugin-permission-node';\nimport {\n RESOURCE_TYPE_POLICY_ENTITY,\n RoleMetadata,\n} from '@backstage-community/plugin-rbac-common';\nimport { z } from 'zod';\nimport zodToJsonSchema from 'zod-to-json-schema';\n\nexport type RBACFilter = {\n key: string;\n values: any[];\n};\n\nexport type RBACFilters =\n | { anyOf: RBACFilters[] }\n | { allOf: RBACFilters[] }\n | { not: RBACFilters }\n | RBACFilter;\n\nconst createRBACPermissionRule = makeCreatePermissionRule<\n RoleMetadata,\n RBACFilter,\n typeof RESOURCE_TYPE_POLICY_ENTITY\n>();\n\nconst isOwner = createRBACPermissionRule({\n name: 'IS_OWNER',\n description:\n 'Should allow access to RBAC roles and Permissions through ownership',\n resourceType: RESOURCE_TYPE_POLICY_ENTITY,\n paramsSchema: z.object({\n owners: z.string().array().describe('List of entity refs to match against'),\n }),\n apply: (roleMeta: RoleMetadata, { owners }) => {\n if (!roleMeta.owner) {\n return false;\n }\n return owners.includes(roleMeta.owner);\n },\n toQuery: ({ owners }) => ({\n key: 'owners',\n values: owners,\n }),\n});\n\nexport const rbacRules = {\n name: isOwner.name,\n description: isOwner.description,\n resourceType: isOwner.resourceType,\n paramsSchema: zodToJsonSchema(isOwner.paramsSchema ?? z.object({})),\n};\n\nexport const rules = { isOwner };\n"],"names":["makeCreatePermissionRule","RESOURCE_TYPE_POLICY_ENTITY","z","zodToJsonSchema"],"mappings":";;;;;;;;;;;AAkCA,MAAM,2BAA2BA,6CAI/B,EAAA;AAEF,MAAM,UAAU,wBAAyB,CAAA;AAAA,EACvC,IAAM,EAAA,UAAA;AAAA,EACN,WACE,EAAA,qEAAA;AAAA,EACF,YAAc,EAAAC,4CAAA;AAAA,EACd,YAAA,EAAcC,MAAE,MAAO,CAAA;AAAA,IACrB,QAAQA,KAAE,CAAA,MAAA,GAAS,KAAM,EAAA,CAAE,SAAS,sCAAsC;AAAA,GAC3E,CAAA;AAAA,EACD,KAAO,EAAA,CAAC,QAAwB,EAAA,EAAE,QAAa,KAAA;AAC7C,IAAI,IAAA,CAAC,SAAS,KAAO,EAAA;AACnB,MAAO,OAAA,KAAA;AAAA;AAET,IAAO,OAAA,MAAA,CAAO,QAAS,CAAA,QAAA,CAAS,KAAK,CAAA;AAAA,GACvC;AAAA,EACA,OAAS,EAAA,CAAC,EAAE,MAAA,EAAc,MAAA;AAAA,IACxB,GAAK,EAAA,QAAA;AAAA,IACL,MAAQ,EAAA;AAAA,GACV;AACF,CAAC,CAAA;AAEM,MAAM,SAAY,GAAA;AAAA,EACvB,MAAM,OAAQ,CAAA,IAAA;AAAA,EACd,aAAa,OAAQ,CAAA,WAAA;AAAA,EACrB,cAAc,OAAQ,CAAA,YAAA;AAAA,EACtB,YAAA,EAAcC,iCAAgB,OAAQ,CAAA,YAAA,IAAgBD,MAAE,MAAO,CAAA,EAAE,CAAC;AACpE;AAEa,MAAA,KAAA,GAAQ,EAAE,OAAQ;;;;;"}
1
+ {"version":3,"file":"rules.cjs.js","sources":["../../src/permissions/rules.ts"],"sourcesContent":["/*\n * Copyright 2025 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 { createPermissionRule } from '@backstage/plugin-permission-node';\nimport type { RoleMetadata } from '@backstage-community/plugin-rbac-common';\nimport { z } from 'zod';\nimport zodToJsonSchema from 'zod-to-json-schema';\nimport { permissionMetadataResourceRef } from './resource';\n\n/**\n * The RBACFilter is a simple filter without any conditional criteria.\n *\n */\nexport type RBACFilter = {\n key: string;\n values: any[];\n};\n\n/**\n * The RBACFilters type is a recursive type that can be used to create complex filter structures.\n * It can be used to create filters that are a combination of other filters, or a negation of a filter.\n *\n */\nexport type RBACFilters =\n | { anyOf: RBACFilters[] }\n | { allOf: RBACFilters[] }\n | { not: RBACFilters }\n | RBACFilter;\n\nconst isOwner = createPermissionRule({\n name: 'IS_OWNER',\n description:\n 'Should allow access to RBAC roles and Permissions through ownership',\n resourceRef: permissionMetadataResourceRef,\n paramsSchema: z.object({\n owners: z.string().array().describe('List of entity refs to match against'),\n }),\n apply: (roleMeta: RoleMetadata, { owners }) => {\n if (!roleMeta.owner) {\n return false;\n }\n return owners.includes(roleMeta.owner);\n },\n toQuery: ({ owners }) => ({\n key: 'owners',\n values: owners,\n }),\n});\n\nexport const rbacRules = {\n name: isOwner.name,\n description: isOwner.description,\n resourceType: isOwner.resourceType,\n paramsSchema: zodToJsonSchema(isOwner.paramsSchema ?? z.object({})),\n};\n\nexport const rules = { isOwner };\n"],"names":["createPermissionRule","permissionMetadataResourceRef","z","zodToJsonSchema"],"mappings":";;;;;;;;;;;AAyCA,MAAM,UAAUA,yCAAqB,CAAA;AAAA,EACnC,IAAM,EAAA,UAAA;AAAA,EACN,WACE,EAAA,qEAAA;AAAA,EACF,WAAa,EAAAC,sCAAA;AAAA,EACb,YAAA,EAAcC,MAAE,MAAO,CAAA;AAAA,IACrB,QAAQA,KAAE,CAAA,MAAA,GAAS,KAAM,EAAA,CAAE,SAAS,sCAAsC;AAAA,GAC3E,CAAA;AAAA,EACD,KAAO,EAAA,CAAC,QAAwB,EAAA,EAAE,QAAa,KAAA;AAC7C,IAAI,IAAA,CAAC,SAAS,KAAO,EAAA;AACnB,MAAO,OAAA,KAAA;AAAA;AAET,IAAO,OAAA,MAAA,CAAO,QAAS,CAAA,QAAA,CAAS,KAAK,CAAA;AAAA,GACvC;AAAA,EACA,OAAS,EAAA,CAAC,EAAE,MAAA,EAAc,MAAA;AAAA,IACxB,GAAK,EAAA,QAAA;AAAA,IACL,MAAQ,EAAA;AAAA,GACV;AACF,CAAC,CAAA;AAEM,MAAM,SAAY,GAAA;AAAA,EACvB,MAAM,OAAQ,CAAA,IAAA;AAAA,EACd,aAAa,OAAQ,CAAA,WAAA;AAAA,EACrB,cAAc,OAAQ,CAAA,YAAA;AAAA,EACtB,YAAA,EAAcC,iCAAgB,OAAQ,CAAA,YAAA,IAAgBD,MAAE,MAAO,CAAA,EAAE,CAAC;AACpE;AAEa,MAAA,KAAA,GAAQ,EAAE,OAAQ;;;;;"}
@@ -34,7 +34,8 @@ const rbacPlugin = backendPluginApi.createBackendPlugin({
34
34
  httpAuth: backendPluginApi.coreServices.httpAuth,
35
35
  auditor: backendPluginApi.coreServices.auditor,
36
36
  userInfo: backendPluginApi.coreServices.userInfo,
37
- lifecycle: backendPluginApi.coreServices.lifecycle
37
+ lifecycle: backendPluginApi.coreServices.lifecycle,
38
+ permissionsRegistry: backendPluginApi.coreServices.permissionsRegistry
38
39
  },
39
40
  async init({
40
41
  http,
@@ -46,7 +47,8 @@ const rbacPlugin = backendPluginApi.createBackendPlugin({
46
47
  httpAuth,
47
48
  auditor,
48
49
  userInfo,
49
- lifecycle
50
+ lifecycle,
51
+ permissionsRegistry
50
52
  }) {
51
53
  http.use(
52
54
  await pluginRbacBackend.PolicyBuilder.build(
@@ -59,7 +61,8 @@ const rbacPlugin = backendPluginApi.createBackendPlugin({
59
61
  httpAuth,
60
62
  auditor,
61
63
  userInfo,
62
- lifecycle
64
+ lifecycle,
65
+ permissionsRegistry
63
66
  },
64
67
  {
65
68
  getPluginIds: () => Array.from(
@@ -1 +1 @@
1
- {"version":3,"file":"plugin.cjs.js","sources":["../src/plugin.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 coreServices,\n createBackendPlugin,\n} from '@backstage/backend-plugin-api';\n\nimport { PolicyBuilder } from '@backstage-community/plugin-rbac-backend';\nimport {\n PluginIdProvider,\n PluginIdProviderExtensionPoint,\n pluginIdProviderExtensionPoint,\n RBACProvider,\n rbacProviderExtensionPoint,\n} from '@backstage-community/plugin-rbac-node';\n\n/**\n * @public\n * RBAC plugin\n *\n */\nexport const rbacPlugin = createBackendPlugin({\n pluginId: 'permission',\n register(env) {\n const pluginIdProviderExtensionPointImpl = new (class PluginIdProviderImpl\n implements PluginIdProviderExtensionPoint\n {\n pluginIdProviders: PluginIdProvider[] = [];\n\n addPluginIdProvider(pluginIdProvider: PluginIdProvider): void {\n this.pluginIdProviders.push(pluginIdProvider);\n }\n })();\n\n env.registerExtensionPoint(\n pluginIdProviderExtensionPoint,\n pluginIdProviderExtensionPointImpl,\n );\n\n const rbacProviders = new Array<RBACProvider>();\n\n env.registerExtensionPoint(rbacProviderExtensionPoint, {\n addRBACProvider(\n ...providers: Array<RBACProvider | Array<RBACProvider>>\n ): void {\n rbacProviders.push(...providers.flat());\n },\n });\n\n env.registerInit({\n deps: {\n http: coreServices.httpRouter,\n config: coreServices.rootConfig,\n logger: coreServices.logger,\n discovery: coreServices.discovery,\n permissions: coreServices.permissions,\n auth: coreServices.auth,\n httpAuth: coreServices.httpAuth,\n auditor: coreServices.auditor,\n userInfo: coreServices.userInfo,\n lifecycle: coreServices.lifecycle,\n },\n async init({\n http,\n config,\n logger,\n discovery,\n permissions,\n auth,\n httpAuth,\n auditor,\n userInfo,\n lifecycle,\n }) {\n http.use(\n await PolicyBuilder.build(\n {\n config,\n logger,\n discovery,\n permissions,\n auth,\n httpAuth,\n auditor,\n userInfo,\n lifecycle,\n },\n {\n getPluginIds: () =>\n Array.from(\n new Set(\n pluginIdProviderExtensionPointImpl.pluginIdProviders.flatMap(\n p => p.getPluginIds(),\n ),\n ),\n ),\n },\n rbacProviders,\n ),\n );\n },\n });\n },\n});\n"],"names":["createBackendPlugin","pluginIdProviderExtensionPoint","rbacProviderExtensionPoint","coreServices","PolicyBuilder"],"mappings":";;;;;;AAkCO,MAAM,aAAaA,oCAAoB,CAAA;AAAA,EAC5C,QAAU,EAAA,YAAA;AAAA,EACV,SAAS,GAAK,EAAA;AACZ,IAAM,MAAA,kCAAA,GAAqC,IAAK,MAAM,oBAEtD,CAAA;AAAA,MACE,oBAAwC,EAAC;AAAA,MAEzC,oBAAoB,gBAA0C,EAAA;AAC5D,QAAK,IAAA,CAAA,iBAAA,CAAkB,KAAK,gBAAgB,CAAA;AAAA;AAC9C,KACC,EAAA;AAEH,IAAI,GAAA,CAAA,sBAAA;AAAA,MACFC,6CAAA;AAAA,MACA;AAAA,KACF;AAEA,IAAM,MAAA,aAAA,GAAgB,IAAI,KAAoB,EAAA;AAE9C,IAAA,GAAA,CAAI,uBAAuBC,yCAA4B,EAAA;AAAA,MACrD,mBACK,SACG,EAAA;AACN,QAAA,aAAA,CAAc,IAAK,CAAA,GAAG,SAAU,CAAA,IAAA,EAAM,CAAA;AAAA;AACxC,KACD,CAAA;AAED,IAAA,GAAA,CAAI,YAAa,CAAA;AAAA,MACf,IAAM,EAAA;AAAA,QACJ,MAAMC,6BAAa,CAAA,UAAA;AAAA,QACnB,QAAQA,6BAAa,CAAA,UAAA;AAAA,QACrB,QAAQA,6BAAa,CAAA,MAAA;AAAA,QACrB,WAAWA,6BAAa,CAAA,SAAA;AAAA,QACxB,aAAaA,6BAAa,CAAA,WAAA;AAAA,QAC1B,MAAMA,6BAAa,CAAA,IAAA;AAAA,QACnB,UAAUA,6BAAa,CAAA,QAAA;AAAA,QACvB,SAASA,6BAAa,CAAA,OAAA;AAAA,QACtB,UAAUA,6BAAa,CAAA,QAAA;AAAA,QACvB,WAAWA,6BAAa,CAAA;AAAA,OAC1B;AAAA,MACA,MAAM,IAAK,CAAA;AAAA,QACT,IAAA;AAAA,QACA,MAAA;AAAA,QACA,MAAA;AAAA,QACA,SAAA;AAAA,QACA,WAAA;AAAA,QACA,IAAA;AAAA,QACA,QAAA;AAAA,QACA,OAAA;AAAA,QACA,QAAA;AAAA,QACA;AAAA,OACC,EAAA;AACD,QAAK,IAAA,CAAA,GAAA;AAAA,UACH,MAAMC,+BAAc,CAAA,KAAA;AAAA,YAClB;AAAA,cACE,MAAA;AAAA,cACA,MAAA;AAAA,cACA,SAAA;AAAA,cACA,WAAA;AAAA,cACA,IAAA;AAAA,cACA,QAAA;AAAA,cACA,OAAA;AAAA,cACA,QAAA;AAAA,cACA;AAAA,aACF;AAAA,YACA;AAAA,cACE,YAAA,EAAc,MACZ,KAAM,CAAA,IAAA;AAAA,gBACJ,IAAI,GAAA;AAAA,kBACF,mCAAmC,iBAAkB,CAAA,OAAA;AAAA,oBACnD,CAAA,CAAA,KAAK,EAAE,YAAa;AAAA;AACtB;AACF;AACF,aACJ;AAAA,YACA;AAAA;AACF,SACF;AAAA;AACF,KACD,CAAA;AAAA;AAEL,CAAC;;;;"}
1
+ {"version":3,"file":"plugin.cjs.js","sources":["../src/plugin.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 coreServices,\n createBackendPlugin,\n} from '@backstage/backend-plugin-api';\n\nimport { PolicyBuilder } from '@backstage-community/plugin-rbac-backend';\nimport {\n PluginIdProvider,\n PluginIdProviderExtensionPoint,\n pluginIdProviderExtensionPoint,\n RBACProvider,\n rbacProviderExtensionPoint,\n} from '@backstage-community/plugin-rbac-node';\n\n/**\n * @public\n * RBAC plugin\n *\n */\nexport const rbacPlugin = createBackendPlugin({\n pluginId: 'permission',\n register(env) {\n const pluginIdProviderExtensionPointImpl = new (class PluginIdProviderImpl\n implements PluginIdProviderExtensionPoint\n {\n pluginIdProviders: PluginIdProvider[] = [];\n\n addPluginIdProvider(pluginIdProvider: PluginIdProvider): void {\n this.pluginIdProviders.push(pluginIdProvider);\n }\n })();\n\n env.registerExtensionPoint(\n pluginIdProviderExtensionPoint,\n pluginIdProviderExtensionPointImpl,\n );\n\n const rbacProviders = new Array<RBACProvider>();\n\n env.registerExtensionPoint(rbacProviderExtensionPoint, {\n addRBACProvider(\n ...providers: Array<RBACProvider | Array<RBACProvider>>\n ): void {\n rbacProviders.push(...providers.flat());\n },\n });\n\n env.registerInit({\n deps: {\n http: coreServices.httpRouter,\n config: coreServices.rootConfig,\n logger: coreServices.logger,\n discovery: coreServices.discovery,\n permissions: coreServices.permissions,\n auth: coreServices.auth,\n httpAuth: coreServices.httpAuth,\n auditor: coreServices.auditor,\n userInfo: coreServices.userInfo,\n lifecycle: coreServices.lifecycle,\n permissionsRegistry: coreServices.permissionsRegistry,\n },\n async init({\n http,\n config,\n logger,\n discovery,\n permissions,\n auth,\n httpAuth,\n auditor,\n userInfo,\n lifecycle,\n permissionsRegistry: permissionsRegistry,\n }) {\n http.use(\n await PolicyBuilder.build(\n {\n config,\n logger,\n discovery,\n permissions,\n auth,\n httpAuth,\n auditor,\n userInfo,\n lifecycle,\n permissionsRegistry: permissionsRegistry,\n },\n {\n getPluginIds: () =>\n Array.from(\n new Set(\n pluginIdProviderExtensionPointImpl.pluginIdProviders.flatMap(\n p => p.getPluginIds(),\n ),\n ),\n ),\n },\n rbacProviders,\n ),\n );\n },\n });\n },\n});\n"],"names":["createBackendPlugin","pluginIdProviderExtensionPoint","rbacProviderExtensionPoint","coreServices","PolicyBuilder"],"mappings":";;;;;;AAkCO,MAAM,aAAaA,oCAAoB,CAAA;AAAA,EAC5C,QAAU,EAAA,YAAA;AAAA,EACV,SAAS,GAAK,EAAA;AACZ,IAAM,MAAA,kCAAA,GAAqC,IAAK,MAAM,oBAEtD,CAAA;AAAA,MACE,oBAAwC,EAAC;AAAA,MAEzC,oBAAoB,gBAA0C,EAAA;AAC5D,QAAK,IAAA,CAAA,iBAAA,CAAkB,KAAK,gBAAgB,CAAA;AAAA;AAC9C,KACC,EAAA;AAEH,IAAI,GAAA,CAAA,sBAAA;AAAA,MACFC,6CAAA;AAAA,MACA;AAAA,KACF;AAEA,IAAM,MAAA,aAAA,GAAgB,IAAI,KAAoB,EAAA;AAE9C,IAAA,GAAA,CAAI,uBAAuBC,yCAA4B,EAAA;AAAA,MACrD,mBACK,SACG,EAAA;AACN,QAAA,aAAA,CAAc,IAAK,CAAA,GAAG,SAAU,CAAA,IAAA,EAAM,CAAA;AAAA;AACxC,KACD,CAAA;AAED,IAAA,GAAA,CAAI,YAAa,CAAA;AAAA,MACf,IAAM,EAAA;AAAA,QACJ,MAAMC,6BAAa,CAAA,UAAA;AAAA,QACnB,QAAQA,6BAAa,CAAA,UAAA;AAAA,QACrB,QAAQA,6BAAa,CAAA,MAAA;AAAA,QACrB,WAAWA,6BAAa,CAAA,SAAA;AAAA,QACxB,aAAaA,6BAAa,CAAA,WAAA;AAAA,QAC1B,MAAMA,6BAAa,CAAA,IAAA;AAAA,QACnB,UAAUA,6BAAa,CAAA,QAAA;AAAA,QACvB,SAASA,6BAAa,CAAA,OAAA;AAAA,QACtB,UAAUA,6BAAa,CAAA,QAAA;AAAA,QACvB,WAAWA,6BAAa,CAAA,SAAA;AAAA,QACxB,qBAAqBA,6BAAa,CAAA;AAAA,OACpC;AAAA,MACA,MAAM,IAAK,CAAA;AAAA,QACT,IAAA;AAAA,QACA,MAAA;AAAA,QACA,MAAA;AAAA,QACA,SAAA;AAAA,QACA,WAAA;AAAA,QACA,IAAA;AAAA,QACA,QAAA;AAAA,QACA,OAAA;AAAA,QACA,QAAA;AAAA,QACA,SAAA;AAAA,QACA;AAAA,OACC,EAAA;AACD,QAAK,IAAA,CAAA,GAAA;AAAA,UACH,MAAMC,+BAAc,CAAA,KAAA;AAAA,YAClB;AAAA,cACE,MAAA;AAAA,cACA,MAAA;AAAA,cACA,SAAA;AAAA,cACA,WAAA;AAAA,cACA,IAAA;AAAA,cACA,QAAA;AAAA,cACA,OAAA;AAAA,cACA,QAAA;AAAA,cACA,SAAA;AAAA,cACA;AAAA,aACF;AAAA,YACA;AAAA,cACE,YAAA,EAAc,MACZ,KAAM,CAAA,IAAA;AAAA,gBACJ,IAAI,GAAA;AAAA,kBACF,mCAAmC,iBAAkB,CAAA,OAAA;AAAA,oBACnD,CAAA,CAAA,KAAK,EAAE,YAAa;AAAA;AACtB;AACF;AACF,aACJ;AAAA,YACA;AAAA;AACF,SACF;AAAA;AACF,KACD,CAAA;AAAA;AAEL,CAAC;;;;"}
@@ -0,0 +1,32 @@
1
+ 'use strict';
2
+
3
+ var lodash = require('lodash');
4
+
5
+ class ExtendablePluginIdProvider {
6
+ constructor(pluginStore, pluginIdProvider, config) {
7
+ this.pluginStore = pluginStore;
8
+ const pluginIdsConfig = config.getOptionalStringArray(
9
+ "permission.rbac.pluginsWithPermission"
10
+ );
11
+ this.configurationPluginIds = pluginIdsConfig ? lodash.union(pluginIdsConfig, pluginIdProvider.getPluginIds()) : pluginIdProvider.getPluginIds();
12
+ }
13
+ // plugin ids which came from application config and PluginIdProvider
14
+ configurationPluginIds;
15
+ isConfiguredPluginId(pluginId) {
16
+ return this.configurationPluginIds.includes(pluginId);
17
+ }
18
+ async handleConflictedPluginIds() {
19
+ const conflictedIds = await (await this.pluginStore.getPlugins()).filter((pId) => this.configurationPluginIds.includes(pId.pluginId));
20
+ await this.pluginStore.deletePlugins(conflictedIds.map((p) => p.pluginId));
21
+ }
22
+ async getPluginIds() {
23
+ const extraPlugins = await this.pluginStore.getPlugins();
24
+ return lodash.union(
25
+ this.configurationPluginIds,
26
+ extraPlugins.map((plugin) => plugin.pluginId)
27
+ );
28
+ }
29
+ }
30
+
31
+ exports.ExtendablePluginIdProvider = ExtendablePluginIdProvider;
32
+ //# sourceMappingURL=extendable-id-provider.cjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"extendable-id-provider.cjs.js","sources":["../../src/service/extendable-id-provider.ts"],"sourcesContent":["/*\n * Copyright 2025 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 { PluginIdProvider } from '@backstage-community/plugin-rbac-node';\nimport { PermissionDependentPluginStore } from '../database/extra-permission-enabled-plugins-storage';\nimport type { Config } from '@backstage/config';\nimport { union } from 'lodash';\n\nexport class ExtendablePluginIdProvider {\n // plugin ids which came from application config and PluginIdProvider\n private readonly configurationPluginIds: string[];\n\n constructor(\n private readonly pluginStore: PermissionDependentPluginStore,\n pluginIdProvider: PluginIdProvider,\n config: Config,\n ) {\n const pluginIdsConfig = config.getOptionalStringArray(\n 'permission.rbac.pluginsWithPermission',\n );\n this.configurationPluginIds = pluginIdsConfig\n ? union(pluginIdsConfig, pluginIdProvider.getPluginIds())\n : pluginIdProvider.getPluginIds();\n }\n\n isConfiguredPluginId(pluginId: string): boolean {\n return this.configurationPluginIds.includes(pluginId);\n }\n\n async handleConflictedPluginIds(): Promise<void> {\n const conflictedIds = await (\n await this.pluginStore.getPlugins()\n ).filter(pId => this.configurationPluginIds.includes(pId.pluginId));\n await this.pluginStore.deletePlugins(conflictedIds.map(p => p.pluginId));\n }\n\n async getPluginIds(): Promise<string[]> {\n const extraPlugins = await this.pluginStore.getPlugins();\n return union(\n this.configurationPluginIds,\n extraPlugins.map(plugin => plugin.pluginId),\n );\n }\n}\n"],"names":["union"],"mappings":";;;;AAoBO,MAAM,0BAA2B,CAAA;AAAA,EAItC,WAAA,CACmB,WACjB,EAAA,gBAAA,EACA,MACA,EAAA;AAHiB,IAAA,IAAA,CAAA,WAAA,GAAA,WAAA;AAIjB,IAAA,MAAM,kBAAkB,MAAO,CAAA,sBAAA;AAAA,MAC7B;AAAA,KACF;AACA,IAAK,IAAA,CAAA,sBAAA,GAAyB,kBAC1BA,YAAM,CAAA,eAAA,EAAiB,iBAAiB,YAAa,EAAC,CACtD,GAAA,gBAAA,CAAiB,YAAa,EAAA;AAAA;AACpC;AAAA,EAbiB,sBAAA;AAAA,EAejB,qBAAqB,QAA2B,EAAA;AAC9C,IAAO,OAAA,IAAA,CAAK,sBAAuB,CAAA,QAAA,CAAS,QAAQ,CAAA;AAAA;AACtD,EAEA,MAAM,yBAA2C,GAAA;AAC/C,IAAA,MAAM,aAAgB,GAAA,MAAA,CACpB,MAAM,IAAA,CAAK,YAAY,UAAW,EAAA,EAClC,MAAO,CAAA,CAAA,GAAA,KAAO,IAAK,CAAA,sBAAA,CAAuB,QAAS,CAAA,GAAA,CAAI,QAAQ,CAAC,CAAA;AAClE,IAAM,MAAA,IAAA,CAAK,YAAY,aAAc,CAAA,aAAA,CAAc,IAAI,CAAK,CAAA,KAAA,CAAA,CAAE,QAAQ,CAAC,CAAA;AAAA;AACzE,EAEA,MAAM,YAAkC,GAAA;AACtC,IAAA,MAAM,YAAe,GAAA,MAAM,IAAK,CAAA,WAAA,CAAY,UAAW,EAAA;AACvD,IAAO,OAAAA,YAAA;AAAA,MACL,IAAK,CAAA,sBAAA;AAAA,MACL,YAAa,CAAA,GAAA,CAAI,CAAU,MAAA,KAAA,MAAA,CAAO,QAAQ;AAAA,KAC5C;AAAA;AAEJ;;;;"}
@@ -0,0 +1,104 @@
1
+ 'use strict';
2
+
3
+ var pluginRbacCommon = require('@backstage-community/plugin-rbac-common');
4
+ var restInterceptor = require('../auditor/rest-interceptor.cjs.js');
5
+ var policiesRestApi = require('./policies-rest-api.cjs.js');
6
+ var errors = require('@backstage/errors');
7
+ var pluginValidation = require('../validation/plugin-validation.cjs.js');
8
+
9
+ function registerPermissionDefinitionRoutes(router, pluginPermMetaData, pluginIdProvider, extraPluginsIdStorage, deps) {
10
+ const { auth, auditor } = deps;
11
+ router.get(
12
+ "/plugins/policies",
13
+ restInterceptor.logAuditorEvent(auditor),
14
+ async (request, response) => {
15
+ await policiesRestApi.authorizeConditional(request, pluginRbacCommon.policyEntityReadPermission, deps);
16
+ const body = await pluginPermMetaData.getPluginPolicies(auth);
17
+ response.json(body);
18
+ }
19
+ );
20
+ router.get(
21
+ "/plugins/condition-rules",
22
+ restInterceptor.logAuditorEvent(auditor),
23
+ async (request, response) => {
24
+ await policiesRestApi.authorizeConditional(request, pluginRbacCommon.policyEntityReadPermission, deps);
25
+ const body = await pluginPermMetaData.getPluginConditionRules(auth);
26
+ response.json(body);
27
+ }
28
+ );
29
+ router.get(
30
+ "/plugins/id",
31
+ restInterceptor.logAuditorEvent(auditor),
32
+ async (request, response) => {
33
+ await policiesRestApi.authorizeConditional(request, pluginRbacCommon.policyEntityReadPermission, deps);
34
+ const actualPluginIds = await pluginIdProvider.getPluginIds();
35
+ response.status(200).json(pluginIdsToResponse(actualPluginIds));
36
+ }
37
+ );
38
+ router.post(
39
+ "/plugins/id",
40
+ restInterceptor.logAuditorEvent(auditor),
41
+ async (request, response) => {
42
+ await policiesRestApi.authorizeConditional(request, pluginRbacCommon.policyEntityCreatePermission, deps);
43
+ const pluginIds = request.body;
44
+ pluginValidation.validatePermissionDependentPlugin(pluginIds);
45
+ const pluginDtos = permissionDependentPluginListToDTO(pluginIds);
46
+ let actualPluginIds = await pluginIdProvider.getPluginIds();
47
+ const conflictedIds = pluginIds.ids.filter(
48
+ (id) => actualPluginIds.includes(id)
49
+ );
50
+ if (conflictedIds.length > 0) {
51
+ throw new errors.ConflictError(
52
+ `Plugin IDs ${JSON.stringify(conflictedIds)} already exist in the system. Please use a different set of plugin ids.`
53
+ );
54
+ }
55
+ await extraPluginsIdStorage.addPlugins(pluginDtos);
56
+ response.locals.meta = pluginIds;
57
+ actualPluginIds = await pluginIdProvider.getPluginIds();
58
+ response.status(200).json(pluginIdsToResponse(actualPluginIds));
59
+ }
60
+ );
61
+ router.delete(
62
+ "/plugins/id",
63
+ restInterceptor.logAuditorEvent(auditor),
64
+ async (request, response) => {
65
+ await policiesRestApi.authorizeConditional(request, pluginRbacCommon.policyEntityDeletePermission, deps);
66
+ const pluginIds = request.body;
67
+ pluginValidation.validatePermissionDependentPlugin(pluginIds);
68
+ const configuredPluginIds = pluginIds.ids.filter(
69
+ (pluginId) => pluginIdProvider.isConfiguredPluginId(pluginId)
70
+ );
71
+ if (configuredPluginIds.length > 0) {
72
+ throw new errors.NotAllowedError(
73
+ `Plugin IDs ${JSON.stringify(pluginIds.ids)} can be removed only with help of configuration.`
74
+ );
75
+ }
76
+ let actualPluginIds = await pluginIdProvider.getPluginIds();
77
+ const notFoundPlugins = pluginIds.ids.filter(
78
+ (pluginToDel) => !actualPluginIds.includes(pluginToDel)
79
+ );
80
+ if (notFoundPlugins.length > 0) {
81
+ throw new errors.NotFoundError(
82
+ `Plugin IDs ${JSON.stringify(notFoundPlugins)} were not found.`
83
+ );
84
+ }
85
+ await extraPluginsIdStorage.deletePlugins(pluginIds.ids);
86
+ response.locals.meta = pluginIds;
87
+ actualPluginIds = await pluginIdProvider.getPluginIds();
88
+ response.status(200).json(pluginIdsToResponse(actualPluginIds));
89
+ }
90
+ );
91
+ }
92
+ function pluginIdsToResponse(pluginIds) {
93
+ return { ids: pluginIds };
94
+ }
95
+ function permissionDependentPluginListToDTO(pluginList) {
96
+ return pluginList.ids.map((pluginId) => {
97
+ return { pluginId };
98
+ });
99
+ }
100
+
101
+ exports.permissionDependentPluginListToDTO = permissionDependentPluginListToDTO;
102
+ exports.pluginIdsToResponse = pluginIdsToResponse;
103
+ exports.registerPermissionDefinitionRoutes = registerPermissionDefinitionRoutes;
104
+ //# sourceMappingURL=permission-definition-routes.cjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"permission-definition-routes.cjs.js","sources":["../../src/service/permission-definition-routes.ts"],"sourcesContent":["/*\n * Copyright 2025 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 PermissionDependentPluginList,\n policyEntityCreatePermission,\n policyEntityDeletePermission,\n policyEntityReadPermission,\n} from '@backstage-community/plugin-rbac-common';\nimport { logAuditorEvent } from '../auditor/rest-interceptor';\nimport { PluginPermissionMetadataCollector } from './plugin-endpoints';\nimport {\n PermissionDependentPluginDTO,\n PermissionDependentPluginStore,\n} from '../database/extra-permission-enabled-plugins-storage';\nimport { authorizeConditional } from './policies-rest-api';\nimport {\n AuditorService,\n AuthService,\n HttpAuthService,\n PermissionsService,\n} from '@backstage/backend-plugin-api';\nimport { ExtendablePluginIdProvider } from './extendable-id-provider';\nimport {\n ConflictError,\n NotAllowedError,\n NotFoundError,\n} from '@backstage/errors';\nimport { validatePermissionDependentPlugin } from '../validation/plugin-validation';\nimport express from 'express';\n\nexport function registerPermissionDefinitionRoutes(\n router: express.Router,\n pluginPermMetaData: PluginPermissionMetadataCollector,\n pluginIdProvider: ExtendablePluginIdProvider,\n extraPluginsIdStorage: PermissionDependentPluginStore,\n deps: {\n auth: AuthService;\n httpAuth: HttpAuthService;\n auditor: AuditorService;\n permissions: PermissionsService;\n },\n) {\n const { auth, auditor } = deps;\n\n router.get(\n '/plugins/policies',\n logAuditorEvent(auditor),\n async (request, response) => {\n await authorizeConditional(request, policyEntityReadPermission, deps);\n\n const body = await pluginPermMetaData.getPluginPolicies(auth);\n\n response.json(body);\n },\n );\n\n router.get(\n '/plugins/condition-rules',\n logAuditorEvent(auditor),\n async (request, response) => {\n await authorizeConditional(request, policyEntityReadPermission, deps);\n\n const body = await pluginPermMetaData.getPluginConditionRules(auth);\n\n response.json(body);\n },\n );\n\n router.get(\n '/plugins/id',\n logAuditorEvent(auditor),\n async (request, response) => {\n await authorizeConditional(request, policyEntityReadPermission, deps);\n\n const actualPluginIds = await pluginIdProvider.getPluginIds();\n response.status(200).json(pluginIdsToResponse(actualPluginIds));\n },\n );\n\n router.post(\n '/plugins/id',\n logAuditorEvent(auditor),\n async (request, response) => {\n await authorizeConditional(request, policyEntityCreatePermission, deps);\n const pluginIds: PermissionDependentPluginList = request.body;\n validatePermissionDependentPlugin(pluginIds);\n const pluginDtos = permissionDependentPluginListToDTO(pluginIds);\n\n let actualPluginIds = await pluginIdProvider.getPluginIds();\n const conflictedIds = pluginIds.ids.filter(id =>\n actualPluginIds.includes(id),\n );\n if (conflictedIds.length > 0) {\n throw new ConflictError(\n `Plugin IDs ${JSON.stringify(conflictedIds)} already exist in the system. Please use a different set of plugin ids.`,\n );\n }\n await extraPluginsIdStorage.addPlugins(pluginDtos);\n response.locals.meta = pluginIds;\n\n actualPluginIds = await pluginIdProvider.getPluginIds();\n response.status(200).json(pluginIdsToResponse(actualPluginIds));\n },\n );\n\n router.delete(\n '/plugins/id',\n logAuditorEvent(auditor),\n async (request, response) => {\n await authorizeConditional(request, policyEntityDeletePermission, deps);\n const pluginIds: PermissionDependentPluginList = request.body;\n validatePermissionDependentPlugin(pluginIds);\n const configuredPluginIds = pluginIds.ids.filter(pluginId =>\n pluginIdProvider.isConfiguredPluginId(pluginId),\n );\n if (configuredPluginIds.length > 0) {\n throw new NotAllowedError(\n `Plugin IDs ${JSON.stringify(pluginIds.ids)} can be removed only with help of configuration.`,\n );\n }\n\n let actualPluginIds = await pluginIdProvider.getPluginIds();\n const notFoundPlugins = pluginIds.ids.filter(\n pluginToDel => !actualPluginIds.includes(pluginToDel),\n );\n if (notFoundPlugins.length > 0) {\n throw new NotFoundError(\n `Plugin IDs ${JSON.stringify(notFoundPlugins)} were not found.`,\n );\n }\n\n await extraPluginsIdStorage.deletePlugins(pluginIds.ids);\n response.locals.meta = pluginIds;\n\n actualPluginIds = await pluginIdProvider.getPluginIds();\n response.status(200).json(pluginIdsToResponse(actualPluginIds));\n },\n );\n}\n\nexport function pluginIdsToResponse(\n pluginIds: string[],\n): PermissionDependentPluginList {\n return { ids: pluginIds };\n}\n\nexport function permissionDependentPluginListToDTO(\n pluginList: PermissionDependentPluginList,\n): PermissionDependentPluginDTO[] {\n return pluginList.ids.map(pluginId => {\n return { pluginId };\n });\n}\n"],"names":["logAuditorEvent","authorizeConditional","policyEntityReadPermission","policyEntityCreatePermission","validatePermissionDependentPlugin","ConflictError","policyEntityDeletePermission","NotAllowedError","NotFoundError"],"mappings":";;;;;;;;AA2CO,SAAS,kCACd,CAAA,MAAA,EACA,kBACA,EAAA,gBAAA,EACA,uBACA,IAMA,EAAA;AACA,EAAM,MAAA,EAAE,IAAM,EAAA,OAAA,EAAY,GAAA,IAAA;AAE1B,EAAO,MAAA,CAAA,GAAA;AAAA,IACL,mBAAA;AAAA,IACAA,gCAAgB,OAAO,CAAA;AAAA,IACvB,OAAO,SAAS,QAAa,KAAA;AAC3B,MAAM,MAAAC,oCAAA,CAAqB,OAAS,EAAAC,2CAAA,EAA4B,IAAI,CAAA;AAEpE,MAAA,MAAM,IAAO,GAAA,MAAM,kBAAmB,CAAA,iBAAA,CAAkB,IAAI,CAAA;AAE5D,MAAA,QAAA,CAAS,KAAK,IAAI,CAAA;AAAA;AACpB,GACF;AAEA,EAAO,MAAA,CAAA,GAAA;AAAA,IACL,0BAAA;AAAA,IACAF,gCAAgB,OAAO,CAAA;AAAA,IACvB,OAAO,SAAS,QAAa,KAAA;AAC3B,MAAM,MAAAC,oCAAA,CAAqB,OAAS,EAAAC,2CAAA,EAA4B,IAAI,CAAA;AAEpE,MAAA,MAAM,IAAO,GAAA,MAAM,kBAAmB,CAAA,uBAAA,CAAwB,IAAI,CAAA;AAElE,MAAA,QAAA,CAAS,KAAK,IAAI,CAAA;AAAA;AACpB,GACF;AAEA,EAAO,MAAA,CAAA,GAAA;AAAA,IACL,aAAA;AAAA,IACAF,gCAAgB,OAAO,CAAA;AAAA,IACvB,OAAO,SAAS,QAAa,KAAA;AAC3B,MAAM,MAAAC,oCAAA,CAAqB,OAAS,EAAAC,2CAAA,EAA4B,IAAI,CAAA;AAEpE,MAAM,MAAA,eAAA,GAAkB,MAAM,gBAAA,CAAiB,YAAa,EAAA;AAC5D,MAAA,QAAA,CAAS,OAAO,GAAG,CAAA,CAAE,IAAK,CAAA,mBAAA,CAAoB,eAAe,CAAC,CAAA;AAAA;AAChE,GACF;AAEA,EAAO,MAAA,CAAA,IAAA;AAAA,IACL,aAAA;AAAA,IACAF,gCAAgB,OAAO,CAAA;AAAA,IACvB,OAAO,SAAS,QAAa,KAAA;AAC3B,MAAM,MAAAC,oCAAA,CAAqB,OAAS,EAAAE,6CAAA,EAA8B,IAAI,CAAA;AACtE,MAAA,MAAM,YAA2C,OAAQ,CAAA,IAAA;AACzD,MAAAC,kDAAA,CAAkC,SAAS,CAAA;AAC3C,MAAM,MAAA,UAAA,GAAa,mCAAmC,SAAS,CAAA;AAE/D,MAAI,IAAA,eAAA,GAAkB,MAAM,gBAAA,CAAiB,YAAa,EAAA;AAC1D,MAAM,MAAA,aAAA,GAAgB,UAAU,GAAI,CAAA,MAAA;AAAA,QAAO,CAAA,EAAA,KACzC,eAAgB,CAAA,QAAA,CAAS,EAAE;AAAA,OAC7B;AACA,MAAI,IAAA,aAAA,CAAc,SAAS,CAAG,EAAA;AAC5B,QAAA,MAAM,IAAIC,oBAAA;AAAA,UACR,CAAc,WAAA,EAAA,IAAA,CAAK,SAAU,CAAA,aAAa,CAAC,CAAA,uEAAA;AAAA,SAC7C;AAAA;AAEF,MAAM,MAAA,qBAAA,CAAsB,WAAW,UAAU,CAAA;AACjD,MAAA,QAAA,CAAS,OAAO,IAAO,GAAA,SAAA;AAEvB,MAAkB,eAAA,GAAA,MAAM,iBAAiB,YAAa,EAAA;AACtD,MAAA,QAAA,CAAS,OAAO,GAAG,CAAA,CAAE,IAAK,CAAA,mBAAA,CAAoB,eAAe,CAAC,CAAA;AAAA;AAChE,GACF;AAEA,EAAO,MAAA,CAAA,MAAA;AAAA,IACL,aAAA;AAAA,IACAL,gCAAgB,OAAO,CAAA;AAAA,IACvB,OAAO,SAAS,QAAa,KAAA;AAC3B,MAAM,MAAAC,oCAAA,CAAqB,OAAS,EAAAK,6CAAA,EAA8B,IAAI,CAAA;AACtE,MAAA,MAAM,YAA2C,OAAQ,CAAA,IAAA;AACzD,MAAAF,kDAAA,CAAkC,SAAS,CAAA;AAC3C,MAAM,MAAA,mBAAA,GAAsB,UAAU,GAAI,CAAA,MAAA;AAAA,QAAO,CAAA,QAAA,KAC/C,gBAAiB,CAAA,oBAAA,CAAqB,QAAQ;AAAA,OAChD;AACA,MAAI,IAAA,mBAAA,CAAoB,SAAS,CAAG,EAAA;AAClC,QAAA,MAAM,IAAIG,sBAAA;AAAA,UACR,CAAc,WAAA,EAAA,IAAA,CAAK,SAAU,CAAA,SAAA,CAAU,GAAG,CAAC,CAAA,gDAAA;AAAA,SAC7C;AAAA;AAGF,MAAI,IAAA,eAAA,GAAkB,MAAM,gBAAA,CAAiB,YAAa,EAAA;AAC1D,MAAM,MAAA,eAAA,GAAkB,UAAU,GAAI,CAAA,MAAA;AAAA,QACpC,CAAe,WAAA,KAAA,CAAC,eAAgB,CAAA,QAAA,CAAS,WAAW;AAAA,OACtD;AACA,MAAI,IAAA,eAAA,CAAgB,SAAS,CAAG,EAAA;AAC9B,QAAA,MAAM,IAAIC,oBAAA;AAAA,UACR,CAAc,WAAA,EAAA,IAAA,CAAK,SAAU,CAAA,eAAe,CAAC,CAAA,gBAAA;AAAA,SAC/C;AAAA;AAGF,MAAM,MAAA,qBAAA,CAAsB,aAAc,CAAA,SAAA,CAAU,GAAG,CAAA;AACvD,MAAA,QAAA,CAAS,OAAO,IAAO,GAAA,SAAA;AAEvB,MAAkB,eAAA,GAAA,MAAM,iBAAiB,YAAa,EAAA;AACtD,MAAA,QAAA,CAAS,OAAO,GAAG,CAAA,CAAE,IAAK,CAAA,mBAAA,CAAoB,eAAe,CAAC,CAAA;AAAA;AAChE,GACF;AACF;AAEO,SAAS,oBACd,SAC+B,EAAA;AAC/B,EAAO,OAAA,EAAE,KAAK,SAAU,EAAA;AAC1B;AAEO,SAAS,mCACd,UACgC,EAAA;AAChC,EAAO,OAAA,UAAA,CAAW,GAAI,CAAA,GAAA,CAAI,CAAY,QAAA,KAAA;AACpC,IAAA,OAAO,EAAE,QAAS,EAAA;AAAA,GACnB,CAAA;AACH;;;;;;"}
@@ -12,7 +12,7 @@ const rbacPermissionMetadata = {
12
12
  rules: [rules.rbacRules]
13
13
  };
14
14
  class PluginPermissionMetadataCollector {
15
- pluginIds;
15
+ pluginIdProvider;
16
16
  discovery;
17
17
  logger;
18
18
  urlReader;
@@ -20,9 +20,9 @@ class PluginPermissionMetadataCollector {
20
20
  deps,
21
21
  optional
22
22
  }) {
23
- const { discovery, pluginIdProvider, logger, config } = deps;
24
- this.pluginIds = pluginIdProvider.getPluginIds();
23
+ const { discovery, logger, config, pluginIdProvider } = deps;
25
24
  this.discovery = discovery;
25
+ this.pluginIdProvider = pluginIdProvider;
26
26
  this.logger = logger;
27
27
  this.urlReader = optional?.urlReader ?? urlReader.UrlReaders.default({
28
28
  config,
@@ -55,7 +55,8 @@ class PluginPermissionMetadataCollector {
55
55
  };
56
56
  async getPluginMetaData(auth) {
57
57
  let pluginResponses = [];
58
- for (const pluginId of this.pluginIds) {
58
+ const pluginIds = await this.pluginIdProvider.getPluginIds();
59
+ for (const pluginId of pluginIds) {
59
60
  try {
60
61
  const { token } = await auth.getPluginRequestToken({
61
62
  onBehalfOf: await auth.getOwnServiceCredentials(),