Skip to content

Commit 66da4b0

Browse files
refactor(homepage): simplify permission checks by moving logic into buildUserContext
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> Signed-off-by: Christoph Jerolimov <jerolimov+git@redhat.com>
1 parent 5147ac1 commit 66da4b0

10 files changed

Lines changed: 119 additions & 397 deletions

File tree

workspaces/homepage/plugins/homepage-backend/README.md

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -126,17 +126,6 @@ homepage:
126126
xl: { w: 12, h: 4 }
127127
```
128128
129-
### Three filtering layers
130-
131-
```
132-
Config (if/unless) --> Permission check (ALLOW/DENY/CONDITIONAL) --> Conditional rules (HAS_TAG/HAS_WIDGET_ID)
133-
Layer 1 Layer 2 Layer 3
134-
```
135-
136-
- **Layer 1** always runs—identity and group based (`if`/`unless`).
137-
- **Layer 2**—RBAC returns ALLOW (pass all), DENY (block all), or CONDITIONAL (apply Layer 3).
138-
- **Layer 3**—rule-based filtering on survivors from Layer 1 using `HAS_TAG` and/or `HAS_WIDGET_ID`.
139-
140129
### Permission rules
141130
142131
The plugin registers two permission rules for the `homepage-default-widget` resource type:

workspaces/homepage/plugins/homepage-backend/src/defaultWidgets/buildUserContext.ts

Lines changed: 49 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,17 +18,29 @@ import {
1818
BackstageCredentials,
1919
BackstageUserPrincipal,
2020
LoggerService,
21+
PermissionsService,
2122
} from '@backstage/backend-plugin-api';
2223
import { RELATION_MEMBER_OF } from '@backstage/catalog-model';
2324
import { CatalogService } from '@backstage/plugin-catalog-node';
25+
import {
26+
createPermission,
27+
PolicyDecision,
28+
QueryPermissionRequest,
29+
} from '@backstage/plugin-permission-common';
30+
import { homepageDefaultWidgetsReadPermission } from '@red-hat-developer-hub/backstage-plugin-homepage-common';
31+
import { UserContext } from './types';
2432

2533
export async function buildUserContext(opts: {
2634
credentials: BackstageCredentials<BackstageUserPrincipal>;
2735
catalog: CatalogService;
36+
permissions: PermissionsService;
37+
referencedPermissions: Set<string>;
2838
logger: LoggerService;
29-
}): Promise<{ userEntityRef: string; groupEntityRefs: Set<string> }> {
30-
const { credentials, catalog, logger } = opts;
39+
}): Promise<UserContext> {
40+
const { credentials, catalog, permissions, referencedPermissions, logger } =
41+
opts;
3142

43+
// user ref
3244
const userEntityRef = credentials.principal.userEntityRef;
3345
const userEntity = await catalog.getEntityByRef(userEntityRef, {
3446
credentials,
@@ -39,11 +51,45 @@ export async function buildUserContext(opts: {
3951
`User entity '${userEntityRef}' not found in catalog; group-based visibility will fail closed`,
4052
);
4153
}
54+
55+
// group refs
4256
const groupEntityRefs = new Set<string>(
4357
(userEntity?.relations ?? [])
4458
.filter(relation => relation.type === RELATION_MEMBER_OF)
4559
.map(relation => relation.targetRef),
4660
);
4761

48-
return { userEntityRef, groupEntityRefs };
62+
// permissions
63+
const names = [...referencedPermissions];
64+
const conditionalPermissionRequests = [
65+
// This default permission will be "removed" below and added as a dedicated
66+
// `defaultWidgetsReadDecision` to the user context.
67+
{
68+
permission: homepageDefaultWidgetsReadPermission,
69+
},
70+
...names.map<QueryPermissionRequest>(name => ({
71+
permission: createPermission({
72+
name,
73+
attributes: { action: 'read' },
74+
resourceType: 'homepage-default-widget',
75+
}),
76+
})),
77+
];
78+
79+
const [defaultWidgetsReadDecision, ...otherConditionalDecisions] =
80+
await permissions.authorizeConditional(conditionalPermissionRequests, {
81+
credentials,
82+
});
83+
84+
const otherPolicyDecisions = new Map<string, PolicyDecision>();
85+
otherConditionalDecisions.forEach((decision, index) => {
86+
otherPolicyDecisions.set(names[index], decision);
87+
});
88+
89+
return {
90+
userEntityRef,
91+
groupEntityRefs,
92+
defaultWidgetsReadDecision,
93+
otherPolicyDecisions,
94+
};
4995
}

0 commit comments

Comments
 (0)