Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions workspaces/rbac/e2e-tests/.env.sample
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Keycloak API credentials for catalog-users e2e tests.
# Copy to .env and set values (e.g. from deployment secrets or CI).
# KEYCLOAK_BASE_URL=https://keycloak.example.com
# KEYCLOAK_REALM=your-realm
# KEYCLOAK_CLIENT_ID=admin-cli
# KEYCLOAK_CLIENT_SECRET=<your-secret>
# RBAC_ADMIN_PASSWORD=<your-password>
1 change: 1 addition & 0 deletions workspaces/rbac/e2e-tests/.yarnrc.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
nodeLinker: node-modules
3 changes: 3 additions & 0 deletions workspaces/rbac/e2e-tests/eslint.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { createEslintConfig } from "@red-hat-developer-hub/e2e-test-utils/eslint";

export default createEslintConfig(import.meta.dirname);
38 changes: 38 additions & 0 deletions workspaces/rbac/e2e-tests/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
{
"name": "rbac-e2e-tests",
"version": "1.0.0",
"private": true,
"type": "module",
"engines": {
"node": ">=22",
"yarn": ">=3"
},
"packageManager": "yarn@3.8.7",
"description": "E2E tests for RBAC",
"scripts": {
"test": "playwright test",
"report": "playwright show-report",
"test:ui": "playwright test --ui",
"test:headed": "playwright test --headed",
"lint:check": "eslint .",
"lint:fix": "eslint . --fix",
"prettier:check": "prettier --check .",
"prettier:fix": "prettier --write .",
"tsc:check": "tsc --noEmit",
"check": "tsc --noEmit && yarn lint:check && yarn prettier:check"
},
"devDependencies": {
"@backstage-community/plugin-rbac-common": "1.23.0",
"@eslint/js": "^9.39.2",
"@playwright/test": "1.57.0",
"@red-hat-developer-hub/e2e-test-utils": "1.1.15",
"@types/node": "^24.10.1",
"dotenv": "^16.4.7",
"eslint": "^9.39.2",
"eslint-plugin-check-file": "^3.3.1",
"eslint-plugin-playwright": "^2.4.0",
"prettier": "^3.7.4",
"typescript": "^5.9.3",
"typescript-eslint": "^8.50.0"
}
}
12 changes: 12 additions & 0 deletions workspaces/rbac/e2e-tests/playwright.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { defineConfig } from "@red-hat-developer-hub/e2e-test-utils/playwright-config";
import dotenv from "dotenv";

dotenv.config({ path: `${import.meta.dirname}/.env` });

export default defineConfig({
projects: [
{
name: "rbac",
},
],
});
46 changes: 46 additions & 0 deletions workspaces/rbac/e2e-tests/support/constants/roles.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
export type RbacRef = {
name: string;
ref: string;
};

/**
* All roles referenced by the RBAC e2e test suite.
*
* Note: `rbacAdmin` and `guest` are system-managed roles (sourced from app-config
* and a CSV policy file respectively) and cannot be deleted via the API — they are
* skipped during cleanup in `cleanupRoles`.
*/
export const RBAC_ROLES: Record<string, RbacRef> = {
rbacOwnership: {
name: "rbac-ownership-role",
ref: "role:default/rbac-ownership-role",
},
rbacConditional: {
name: "rbac-conditional-role",
ref: "role:default/rbac-conditional-role",
},
conditionalResource: {
name: "rbac-conditional-resource-role",
ref: "role:default/rbac-conditional-resource-role",
},
overviewListEdit: {
name: "rbac-list-edit-role",
ref: "role:default/rbac-list-edit-role",
},
overviewMembers: {
name: "rbac-overview-members-role",
ref: "role:default/rbac-overview-members-role",
},
overviewPolicies: {
name: "rbac-overview-policies-role",
ref: "role:default/rbac-overview-policies-role",
},
rbacAdmin: {
name: "rbac_admin",
ref: "role:default/rbac_admin",
},
guest: {
name: "guests",
ref: "role:default/guests",
},
};
161 changes: 161 additions & 0 deletions workspaces/rbac/e2e-tests/support/constants/users-and-groups.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
export type RbacUser = {
username: string;
firstName: string;
lastName: string;
email: string;
password: string;
groups: string[];
};

export type RBACGroup = {
name: string;
keycloak?: boolean;
};

/**
* Users created in Keycloak for RBAC e2e tests.
* Each key describes the scenario the user serves.
*
* - rbacAdmin: RBAC plugin admin; configured as admin in app-config.
* - noAccess: No permissions; verifies RBAC sidebar hidden and direct nav blocked.
* - tara: Fixture member used when constructing roles via the UI.
* - jonathon: Fixture member used when editing role membership via the UI.
* - currentUserOwner: Member of rhdh-qe-2-team. Tests $currentUser: can unregister own
* components but not group-owned ones.
* - conditionalManager: Gets conditional RBAC manage permission via rbac-ownership-role
* Used in the serial IsOwner suite.
* - allowAllowUser: catalog_reader. Both static allow AND conditional IS_ENTITY_OWNER
* allow read — tests policyDecisionPrecedence allow+allow case.
* - conditionalAllowUser: Has static deny (all_resource_denier) but conditional policy allows
* read — tests conditional overrides deny.
* - conditionalDenyUser: Has static allow but conditional deny wins — sees empty catalog.
* - conditionalDenier: all_resource_reader + conditional_denier roles — conditional deny
* overrides static allow.
* - childGroupMember: Member of rhdh-qe-child-team. Tests transitive $ownerRefs: can read
* components owned by the parent group rhdh-qe-parent-team.
* - subChildGroupMember: Member of rhdh-qe-sub-child-team. Tests deep transitive $ownerRefs:
* can read components owned by parent and grandparent groups.
*
* Passwords are generated at module-load time using `crypto.randomUUID()` trimmed
* to 21 characters with hyphens replaced by zeros. This satisfies typical minimum
* length and complexity requirements while staying fully random per test run.
* The rbacAdmin password can be overridden via the `RBAC_ADMIN_PASSWORD` env var
* so that a stable value can be used in CI where needed.
*/
export const RBAC_DESCRIPTIVE_USERS: Record<string, RbacUser> = {
rbacAdmin: {
username: "rbac-admin",
firstName: "RBAC",
lastName: "Admin",
email: "rbac-admin@example.com",
password:
process.env.RBAC_ADMIN_PASSWORD ??
crypto.randomUUID().substring(0, 21).replaceAll("-", "0"),
groups: [],
},
noAccess: {
username: "no-access",
firstName: "No",
lastName: "Access",
email: "no-access@example.com",
password: crypto.randomUUID().substring(0, 21).replaceAll("-", "0"),
groups: [],
},
tara: {
username: "tara",
firstName: "Tara",
lastName: "MacGovern",
email: "tara@example.com",
password: crypto.randomUUID().substring(0, 21).replaceAll("-", "0"),
groups: [],
},
jonathon: {
username: "jonathon",
firstName: "Jonathon",
lastName: "Page",
email: "jonathon@example.com",
password: crypto.randomUUID().substring(0, 21).replaceAll("-", "0"),
groups: [],
},
currentUserOwner: {
username: "current-user-owner",
firstName: "Current",
lastName: "User-Owner",
email: "current-user-owner@example.com",
password: crypto.randomUUID().substring(0, 21).replaceAll("-", "0"),
groups: ["rhdh-qe-2-team"],
},
conditionalManager: {
username: "conditional-manager",
firstName: "Conditional",
lastName: "Manager",
email: "conditional-manager@example.com",
password: crypto.randomUUID().substring(0, 21).replaceAll("-", "0"),
groups: [],
},
allowAllowUser: {
username: "allow-allow-user",
firstName: "Allow",
lastName: "Allow-User",
email: "allow-allow-user@example.com",
password: crypto.randomUUID().substring(0, 21).replaceAll("-", "0"),
groups: [],
},
conditionalAllowUser: {
username: "conditional-allow-user",
firstName: "Conditional",
lastName: "Allow-User",
email: "conditional-allow-user@example.com",
password: crypto.randomUUID().substring(0, 21).replaceAll("-", "0"),
groups: [],
},
conditionalDenyUser: {
username: "conditional-deny-user",
firstName: "Conditional",
lastName: "Deny-User",
email: "conditional-deny-user@example.com",
password: crypto.randomUUID().substring(0, 21).replaceAll("-", "0"),
groups: [],
},
childGroupMember: {
username: "child-group-member",
firstName: "Child",
lastName: "Group-Member",
email: "child-group-member@example.com",
password: crypto.randomUUID().substring(0, 21).replaceAll("-", "0"),
groups: [],
},
subChildGroupMember: {
username: "sub-child-group-member",
firstName: "Sub-Child",
lastName: "Group-Member",
email: "sub-child-group-member@example.com",
password: crypto.randomUUID().substring(0, 21).replaceAll("-", "0"),
groups: [],
},
};

/**
* Groups created in Keycloak for RBAC e2e tests.
* The transitive parent/child/sub-child groups are not created via Keycloak
* (configureForRHDH does not support sub-group membership); they are managed
* separately in the catalog config.
*/
export const RBAC_GROUPS: Record<string, RBACGroup> = {
backstage: { name: "backstage", keycloak: true },
currentUserOwnerTeam: { name: "rhdh-qe-2-team", keycloak: true },
rhdhParentTeam: { name: "rhdh-qe-parent-team" },
rhdhChildTeam: { name: "rhdh-qe-child-team" },
rhdhSubChildTeam: { name: "rhdh-qe-sub-child-team" },
};

/**
* Returns the UI display name for a user in RBAC_DESCRIPTIVE_USERS.
* Used when selecting members from the role creation/edit form dropdowns.
*/
export const displayName = (key: keyof typeof RBAC_DESCRIPTIVE_USERS): string =>
`${RBAC_DESCRIPTIVE_USERS[key].firstName} ${RBAC_DESCRIPTIVE_USERS[key].lastName}`;

/** Returns the Backstage entity reference string for a given user, e.g. `user:default/rbac-admin`. */
export const userEntityRef = (user: RbacUser): string =>
`user:default/${user.username}`;
Loading
Loading