Skip to content

Commit 55c00b2

Browse files
fix(rbac): rework condition policies to bound them to RBAC roles (janus-idp#1330)
* fix(rbac)!: rework condition policies to bound them to RBAC roles Signed-off-by: Oleksandr Andriienko <[email protected]>
1 parent c8c2b13 commit 55c00b2

17 files changed

+2533
-457
lines changed

plugins/rbac-backend/docs/apis.md

Lines changed: 32 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -454,11 +454,11 @@ Returns:
454454

455455
## Conditions
456456

457-
The Backstage permission framework provides conditions, and the RBAC backend plugin supports this feature. Conditions work like content filters for Backstage resources (provided by plugins). The RBAC backend plugin checks if a user has access to a particular resource. If the user has access to the resource, the RBAC backend API delegates the condition for this resource to the corresponding plugin by plugin ID.
457+
The Backstage permission framework provides conditions, and the RBAC backend plugin supports this feature. Conditions work like content filters for Backstage resources (provided by plugins). The RBAC backend API stores conditions assigned to the role in the database. When a user requests access to the frontend resources, the RBAC backend API searches for corresponding conditions and delegates the condition for this resource to the corresponding plugin by its plugin ID. If a user was assigned to multiple roles, and each of these roles contains its own condition, the RBAC backend merges conditions using the anyOf criteria.
458458

459-
The corresponding plugin analyzes conditional parameters and makes a decision about which part of the content the user should see. Consequently, the user can view not all resource content but only some allowed parts. The RBAC backend plugin supports conditions on a generic level - conditions are bound to all roles but none to specific roles.
459+
The corresponding plugin analyzes conditional parameters and makes a decision about which part of the content the user should see. Consequently, the user can view not all resource content but only some allowed parts. The RBAC backend plugin supports conditions bounded to the RBAC role.
460460

461-
A Backstage condition consists of a parameter or an array of parameters joined by criteria. The list of supported conditional criteria includes:
461+
A Backstage condition can be a simple condition with a rule and parameters. But also a Backstage condition could consists of a parameter or an array of parameters joined by criteria. The list of supported conditional criteria includes:
462462

463463
- allOf
464464
- anyOf
@@ -468,12 +468,14 @@ The plugin defines the supported condition parameters. API users can retrieve th
468468

469469
The structure of the condition JSON object is as follows:
470470

471-
| Json field | Description | Type |
472-
| ------------ | --------------------------------------------------------------------- | ------ |
473-
| result | Always has the value "CONDITIONAL" | String |
474-
| pluginId | Corresponding plugin ID (e.g., "catalog") | String |
475-
| resourceType | Resource type provided by the plugin (e.g., "catalog-entity") | String |
476-
| conditions | Condition JSON with parameters or array parameters joined by criteria | JSON |
471+
| Json field | Description | Type |
472+
| ----------------- | --------------------------------------------------------------------- | ------------ |
473+
| result | Always has the value "CONDITIONAL" | String |
474+
| roleEntityRef | String entity reference to the RBAC role ('role:default/dev') | String |
475+
| pluginId | Corresponding plugin ID (e.g., "catalog") | String |
476+
| permissionMapping | Array permission actions (['read', 'update', 'delete']) | String array |
477+
| resourceType | Resource type provided by the plugin (e.g., "catalog-entity") | String |
478+
| conditions | Condition JSON with parameters or array parameters joined by criteria | JSON |
477479

478480
### GET </plugins/condition-rules>
479481

@@ -633,7 +635,7 @@ For example, consider a condition without criteria: displaying catalogs only if
633635
- criteria: in this example, criteria are not used since we need to use only one conditional parameter
634636
- params: from the schema, it is evident that it should be an object named "claims" with a string array. This string array constitutes a list of user or group string entity references.
635637

636-
Based on the above schema:
638+
Based on the above schema condition is:
637639

638640
```json
639641
{
@@ -645,13 +647,15 @@ Based on the above schema:
645647
}
646648
```
647649

648-
To utilize this condition to the RBAC REST api you need to wrap it with more info:
650+
To utilize this condition to the RBAC REST api you need to wrap it with more info
649651

650652
```json
651653
{
652654
"result": "CONDITIONAL",
655+
"roleEntityRef": "role:default/test",
653656
"pluginId": "catalog",
654657
"resourceType": "catalog-entity",
658+
"permissionMapping": ["read"],
655659
"conditions": {
656660
"rule": "IS_ENTITY_OWNER",
657661
"resourceType": "catalog-entity",
@@ -699,8 +703,10 @@ To utilize this condition to the RBAC REST api you need to wrap it with more inf
699703
```json
700704
{
701705
"result": "CONDITIONAL",
706+
"roleEntityRef": "role:default/test",
702707
"pluginId": "catalog",
703708
"resourceType": "catalog-entity",
709+
"permissionMapping": ["read"],
704710
"conditions": {
705711
"anyOf": [
706712
{
@@ -726,7 +732,7 @@ To utilize this condition to the RBAC REST api you need to wrap it with more inf
726732

727733
### POST condition
728734

729-
POST </api/permission/conditions>
735+
POST </api/permission/roles/conditions>
730736

731737
Creates a new condition.
732738

@@ -737,8 +743,10 @@ body:
737743
```json
738744
{
739745
"result": "CONDITIONAL",
746+
"roleEntityRef": "role:default/test",
740747
"pluginId": "catalog",
741748
"resourceType": "catalog-entity",
749+
"permissionMapping": ["read"],
742750
"conditions": {
743751
"rule": "IS_ENTITY_OWNER",
744752
"resourceType": "catalog-entity",
@@ -761,7 +769,7 @@ Returns a status code of 201 and json with id upon success:
761769

762770
### PUT condition
763771

764-
PUT </permission/conditions/:id>
772+
PUT </permission/roles/conditions/:id>
765773

766774
Update conditions by id.
767775

@@ -772,8 +780,10 @@ body:
772780
```json
773781
{
774782
"result": "CONDITIONAL",
783+
"roleEntityRef": "role:default/test",
775784
"pluginId": "catalog",
776785
"resourceType": "catalog-entity",
786+
"permissionMapping": ["read"],
777787
"conditions": {
778788
"anyOf": [
779789
{
@@ -801,15 +811,18 @@ Returns a status code of 200 upon success.
801811

802812
### Get condition by id
803813

804-
GET </api/permission/conditions/:id>
814+
GET </api/permission/roles/conditions/:id>
805815

806816
Returns condition by id:
807817

808818
```json
809819
{
820+
"id": 1,
810821
"result": "CONDITIONAL",
822+
"roleEntityRef": "role:default/test",
811823
"pluginId": "catalog",
812824
"resourceType": "catalog-entity",
825+
"permissionMapping": ["read"],
813826
"conditions": {
814827
"anyOf": [
815828
{
@@ -837,16 +850,19 @@ Returns a status code of 200 upon success.
837850

838851
### GET conditions
839852

840-
GET </api/permission/conditions>
853+
GET </api/permission/roles/conditions>
841854

842855
Returns lists all conditions:
843856

844857
```json
845858
[
846859
{
860+
"id": 1,
847861
"result": "CONDITIONAL",
862+
"roleEntityRef": "role:default/test",
848863
"pluginId": "catalog",
849864
"resourceType": "catalog-entity",
865+
"permissionMapping": ["read"],
850866
"conditions": {
851867
"anyOf": [
852868
{
@@ -875,7 +891,7 @@ Returns a status code of 200 upon success.
875891

876892
### DELETE condition by id
877893

878-
DELETE </api/permission/conditions/:id>
894+
DELETE </api/permission/roles/conditions/:id>
879895

880896
Deletes condition by id.
881897

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
/**
2+
* @param { import("knex").Knex } knex
3+
* @returns { Promise<void> }
4+
*/
5+
exports.up = async function up(knex) {
6+
const policyConditionsExist = await knex.schema.hasTable('policy-conditions');
7+
8+
if (policyConditionsExist) {
9+
// We drop policy condition table, because we decided to rework this feature
10+
// and bound policy condition to the role
11+
await knex.schema.dropTable('policy-conditions');
12+
}
13+
};
14+
15+
/**
16+
* @param { import("knex").Knex } knex
17+
* @returns { Promise<void> }
18+
*/
19+
exports.down = async function down(_knex) {};
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
/**
2+
* up - runs migration.
3+
*
4+
* @param { import("knex").Knex } knex
5+
* @returns { Promise<void> }
6+
*/
7+
exports.up = async function up(knex) {
8+
await knex.schema.createTable('role-condition-policies', table => {
9+
table.increments('id').primary();
10+
table.string('roleEntityRef');
11+
table.string('result');
12+
table.string('pluginId');
13+
table.string('resourceType');
14+
table.string('permissions');
15+
// Conditions is potentially long json.
16+
// In the future maybe we can use `json` or `jsonb` type instead of `text`:
17+
// table.json('conditions') or table.jsonb('conditions').
18+
// But let's start with text type.
19+
// Data type "text" can be unlimited by size for Postgres.
20+
// Also postgres has a lot of build in features for this data type.
21+
table.text('conditionsJson');
22+
});
23+
};
24+
25+
/**
26+
* down - reverts(undo) migration.
27+
*
28+
* @param { import("knex").Knex } knex
29+
* @returns { Promise<void> }
30+
*/
31+
exports.down = async function down(knex) {
32+
await knex.schema.dropTable('policy-conditions');
33+
};

0 commit comments

Comments
 (0)