Skip to content

Commit cf4d028

Browse files
authored
chore(rbac): migrate RBAC plugin tests from RHDH (#1987)
* chore(rbac): migrate RBAC plugin tests from RHDH Signed-off-by: Patrick Knight <pknight@redhat.com> * chore(rbac): code clean up and test fixes Signed-off-by: Patrick Knight <pknight@redhat.com> * chore(rbac): fix sonarqube issues Signed-off-by: Patrick Knight <pknight@redhat.com> * chore(rbac): fix eol and remove version from RHDH deployement Signed-off-by: Patrick Knight <pknight@redhat.com> * chore(rbac): handle review suggestions Signed-off-by: Patrick Knight <pknight@redhat.com> * chore(rbac): bump test utils and remove auth and rbac apis Signed-off-by: Patrick Knight <pknight@redhat.com> * chore(rbac): remove dynamic-plugins.yaml file Signed-off-by: Patrick Knight <pknight@redhat.com> --------- Signed-off-by: Patrick Knight <pknight@redhat.com>
1 parent 399b94d commit cf4d028

21 files changed

Lines changed: 4538 additions & 0 deletions
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# Keycloak API credentials for catalog-users e2e tests.
2+
# Copy to .env and set values (e.g. from deployment secrets or CI).
3+
# KEYCLOAK_BASE_URL=https://keycloak.example.com
4+
# KEYCLOAK_REALM=your-realm
5+
# KEYCLOAK_CLIENT_ID=admin-cli
6+
# KEYCLOAK_CLIENT_SECRET=<your-secret>
7+
# RBAC_ADMIN_PASSWORD=<your-password>
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
nodeLinker: node-modules
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import { createEslintConfig } from "@red-hat-developer-hub/e2e-test-utils/eslint";
2+
3+
export default createEslintConfig(import.meta.dirname);
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
{
2+
"name": "rbac-e2e-tests",
3+
"version": "1.0.0",
4+
"private": true,
5+
"type": "module",
6+
"engines": {
7+
"node": ">=22",
8+
"yarn": ">=3"
9+
},
10+
"packageManager": "yarn@3.8.7",
11+
"description": "E2E tests for RBAC",
12+
"scripts": {
13+
"test": "playwright test",
14+
"report": "playwright show-report",
15+
"test:ui": "playwright test --ui",
16+
"test:headed": "playwright test --headed",
17+
"lint:check": "eslint .",
18+
"lint:fix": "eslint . --fix",
19+
"prettier:check": "prettier --check .",
20+
"prettier:fix": "prettier --write .",
21+
"tsc:check": "tsc --noEmit",
22+
"check": "tsc --noEmit && yarn lint:check && yarn prettier:check"
23+
},
24+
"devDependencies": {
25+
"@backstage-community/plugin-rbac-common": "1.23.0",
26+
"@eslint/js": "^9.39.2",
27+
"@playwright/test": "1.57.0",
28+
"@red-hat-developer-hub/e2e-test-utils": "1.1.15",
29+
"@types/node": "^24.10.1",
30+
"dotenv": "^16.4.7",
31+
"eslint": "^9.39.2",
32+
"eslint-plugin-check-file": "^3.3.1",
33+
"eslint-plugin-playwright": "^2.4.0",
34+
"prettier": "^3.7.4",
35+
"typescript": "^5.9.3",
36+
"typescript-eslint": "^8.50.0"
37+
}
38+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { defineConfig } from "@red-hat-developer-hub/e2e-test-utils/playwright-config";
2+
import dotenv from "dotenv";
3+
4+
dotenv.config({ path: `${import.meta.dirname}/.env` });
5+
6+
export default defineConfig({
7+
projects: [
8+
{
9+
name: "rbac",
10+
},
11+
],
12+
});
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
export type RbacRef = {
2+
name: string;
3+
ref: string;
4+
};
5+
6+
/**
7+
* All roles referenced by the RBAC e2e test suite.
8+
*
9+
* Note: `rbacAdmin` and `guest` are system-managed roles (sourced from app-config
10+
* and a CSV policy file respectively) and cannot be deleted via the API — they are
11+
* skipped during cleanup in `cleanupRoles`.
12+
*/
13+
export const RBAC_ROLES: Record<string, RbacRef> = {
14+
rbacOwnership: {
15+
name: "rbac-ownership-role",
16+
ref: "role:default/rbac-ownership-role",
17+
},
18+
rbacConditional: {
19+
name: "rbac-conditional-role",
20+
ref: "role:default/rbac-conditional-role",
21+
},
22+
conditionalResource: {
23+
name: "rbac-conditional-resource-role",
24+
ref: "role:default/rbac-conditional-resource-role",
25+
},
26+
overviewListEdit: {
27+
name: "rbac-list-edit-role",
28+
ref: "role:default/rbac-list-edit-role",
29+
},
30+
overviewMembers: {
31+
name: "rbac-overview-members-role",
32+
ref: "role:default/rbac-overview-members-role",
33+
},
34+
overviewPolicies: {
35+
name: "rbac-overview-policies-role",
36+
ref: "role:default/rbac-overview-policies-role",
37+
},
38+
rbacAdmin: {
39+
name: "rbac_admin",
40+
ref: "role:default/rbac_admin",
41+
},
42+
guest: {
43+
name: "guests",
44+
ref: "role:default/guests",
45+
},
46+
};
Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
export type RbacUser = {
2+
username: string;
3+
firstName: string;
4+
lastName: string;
5+
email: string;
6+
password: string;
7+
groups: string[];
8+
};
9+
10+
export type RBACGroup = {
11+
name: string;
12+
keycloak?: boolean;
13+
};
14+
15+
/**
16+
* Users created in Keycloak for RBAC e2e tests.
17+
* Each key describes the scenario the user serves.
18+
*
19+
* - rbacAdmin: RBAC plugin admin; configured as admin in app-config.
20+
* - noAccess: No permissions; verifies RBAC sidebar hidden and direct nav blocked.
21+
* - tara: Fixture member used when constructing roles via the UI.
22+
* - jonathon: Fixture member used when editing role membership via the UI.
23+
* - currentUserOwner: Member of rhdh-qe-2-team. Tests $currentUser: can unregister own
24+
* components but not group-owned ones.
25+
* - conditionalManager: Gets conditional RBAC manage permission via rbac-ownership-role
26+
* Used in the serial IsOwner suite.
27+
* - allowAllowUser: catalog_reader. Both static allow AND conditional IS_ENTITY_OWNER
28+
* allow read — tests policyDecisionPrecedence allow+allow case.
29+
* - conditionalAllowUser: Has static deny (all_resource_denier) but conditional policy allows
30+
* read — tests conditional overrides deny.
31+
* - conditionalDenyUser: Has static allow but conditional deny wins — sees empty catalog.
32+
* - conditionalDenier: all_resource_reader + conditional_denier roles — conditional deny
33+
* overrides static allow.
34+
* - childGroupMember: Member of rhdh-qe-child-team. Tests transitive $ownerRefs: can read
35+
* components owned by the parent group rhdh-qe-parent-team.
36+
* - subChildGroupMember: Member of rhdh-qe-sub-child-team. Tests deep transitive $ownerRefs:
37+
* can read components owned by parent and grandparent groups.
38+
*
39+
* Passwords are generated at module-load time using `crypto.randomUUID()` trimmed
40+
* to 21 characters with hyphens replaced by zeros. This satisfies typical minimum
41+
* length and complexity requirements while staying fully random per test run.
42+
* The rbacAdmin password can be overridden via the `RBAC_ADMIN_PASSWORD` env var
43+
* so that a stable value can be used in CI where needed.
44+
*/
45+
export const RBAC_DESCRIPTIVE_USERS: Record<string, RbacUser> = {
46+
rbacAdmin: {
47+
username: "rbac-admin",
48+
firstName: "RBAC",
49+
lastName: "Admin",
50+
email: "rbac-admin@example.com",
51+
password:
52+
process.env.RBAC_ADMIN_PASSWORD ??
53+
crypto.randomUUID().substring(0, 21).replaceAll("-", "0"),
54+
groups: [],
55+
},
56+
noAccess: {
57+
username: "no-access",
58+
firstName: "No",
59+
lastName: "Access",
60+
email: "no-access@example.com",
61+
password: crypto.randomUUID().substring(0, 21).replaceAll("-", "0"),
62+
groups: [],
63+
},
64+
tara: {
65+
username: "tara",
66+
firstName: "Tara",
67+
lastName: "MacGovern",
68+
email: "tara@example.com",
69+
password: crypto.randomUUID().substring(0, 21).replaceAll("-", "0"),
70+
groups: [],
71+
},
72+
jonathon: {
73+
username: "jonathon",
74+
firstName: "Jonathon",
75+
lastName: "Page",
76+
email: "jonathon@example.com",
77+
password: crypto.randomUUID().substring(0, 21).replaceAll("-", "0"),
78+
groups: [],
79+
},
80+
currentUserOwner: {
81+
username: "current-user-owner",
82+
firstName: "Current",
83+
lastName: "User-Owner",
84+
email: "current-user-owner@example.com",
85+
password: crypto.randomUUID().substring(0, 21).replaceAll("-", "0"),
86+
groups: ["rhdh-qe-2-team"],
87+
},
88+
conditionalManager: {
89+
username: "conditional-manager",
90+
firstName: "Conditional",
91+
lastName: "Manager",
92+
email: "conditional-manager@example.com",
93+
password: crypto.randomUUID().substring(0, 21).replaceAll("-", "0"),
94+
groups: [],
95+
},
96+
allowAllowUser: {
97+
username: "allow-allow-user",
98+
firstName: "Allow",
99+
lastName: "Allow-User",
100+
email: "allow-allow-user@example.com",
101+
password: crypto.randomUUID().substring(0, 21).replaceAll("-", "0"),
102+
groups: [],
103+
},
104+
conditionalAllowUser: {
105+
username: "conditional-allow-user",
106+
firstName: "Conditional",
107+
lastName: "Allow-User",
108+
email: "conditional-allow-user@example.com",
109+
password: crypto.randomUUID().substring(0, 21).replaceAll("-", "0"),
110+
groups: [],
111+
},
112+
conditionalDenyUser: {
113+
username: "conditional-deny-user",
114+
firstName: "Conditional",
115+
lastName: "Deny-User",
116+
email: "conditional-deny-user@example.com",
117+
password: crypto.randomUUID().substring(0, 21).replaceAll("-", "0"),
118+
groups: [],
119+
},
120+
childGroupMember: {
121+
username: "child-group-member",
122+
firstName: "Child",
123+
lastName: "Group-Member",
124+
email: "child-group-member@example.com",
125+
password: crypto.randomUUID().substring(0, 21).replaceAll("-", "0"),
126+
groups: [],
127+
},
128+
subChildGroupMember: {
129+
username: "sub-child-group-member",
130+
firstName: "Sub-Child",
131+
lastName: "Group-Member",
132+
email: "sub-child-group-member@example.com",
133+
password: crypto.randomUUID().substring(0, 21).replaceAll("-", "0"),
134+
groups: [],
135+
},
136+
};
137+
138+
/**
139+
* Groups created in Keycloak for RBAC e2e tests.
140+
* The transitive parent/child/sub-child groups are not created via Keycloak
141+
* (configureForRHDH does not support sub-group membership); they are managed
142+
* separately in the catalog config.
143+
*/
144+
export const RBAC_GROUPS: Record<string, RBACGroup> = {
145+
backstage: { name: "backstage", keycloak: true },
146+
currentUserOwnerTeam: { name: "rhdh-qe-2-team", keycloak: true },
147+
rhdhParentTeam: { name: "rhdh-qe-parent-team" },
148+
rhdhChildTeam: { name: "rhdh-qe-child-team" },
149+
rhdhSubChildTeam: { name: "rhdh-qe-sub-child-team" },
150+
};
151+
152+
/**
153+
* Returns the UI display name for a user in RBAC_DESCRIPTIVE_USERS.
154+
* Used when selecting members from the role creation/edit form dropdowns.
155+
*/
156+
export const displayName = (key: keyof typeof RBAC_DESCRIPTIVE_USERS): string =>
157+
`${RBAC_DESCRIPTIVE_USERS[key].firstName} ${RBAC_DESCRIPTIVE_USERS[key].lastName}`;
158+
159+
/** Returns the Backstage entity reference string for a given user, e.g. `user:default/rbac-admin`. */
160+
export const userEntityRef = (user: RbacUser): string =>
161+
`user:default/${user.username}`;

0 commit comments

Comments
 (0)