diff --git a/workspaces/rbac/.changeset/tall-lizards-pay.md b/workspaces/rbac/.changeset/tall-lizards-pay.md new file mode 100644 index 00000000000..013b688df4a --- /dev/null +++ b/workspaces/rbac/.changeset/tall-lizards-pay.md @@ -0,0 +1,28 @@ +--- +'@backstage-community/plugin-rbac-backend': patch +'@backstage-community/plugin-rbac-common': patch +'@backstage-community/plugin-rbac': patch +--- + +Add CI bump-trust coverage and contributor dev harness documentation for the RBAC plugin family. + +**`@backstage-community/plugin-rbac-backend`** + +- Add backend `dev/` harness config (`app-config.yaml`) that no longer depends on the workspace root `app-config.yaml` +- Add `startTestBackend` smoke test for `GET /api/permission/roles` +- Add policy contract test locking the documented `superUsers` direct-membership rule +- Add manual backend-only test harness under `plugins/rbac-backend/manual-tests/` (Keycloak, CSV policies, login/permission scripts) +- Add `CONTRIBUTING.md` with harness, test, and REST smoke guidance + +**`@backstage-community/plugin-rbac-common`** + +- Add minimal public API contract tests +- Add `CONTRIBUTING.md` + +**`@backstage-community/plugin-rbac`** + +- Add `CONTRIBUTING.md` and link it from the plugin README + +**Workspace** + +- Remove non-functional root `yarn start` and `yarn start:alpha` scripts that paired separate plugin dev servers diff --git a/workspaces/rbac/.gitignore b/workspaces/rbac/.gitignore index 3f4b6f058eb..296911f6618 100644 --- a/workspaces/rbac/.gitignore +++ b/workspaces/rbac/.gitignore @@ -57,3 +57,4 @@ e2e-test-report*/ # Local CSV Policy Files *.local.csv +plugins/rbac-backend/manual-tests/userinfo.csv \ No newline at end of file diff --git a/workspaces/rbac/.vscode/launch.json b/workspaces/rbac/.vscode/launch.json index a7f559be483..537fd5af889 100644 --- a/workspaces/rbac/.vscode/launch.json +++ b/workspaces/rbac/.vscode/launch.json @@ -15,9 +15,9 @@ "request": "launch", "name": "Debug backend", "program": "${workspaceRoot}/node_modules/.bin/backstage-cli", - "args": ["repo", "start"], + "args": ["package", "start", "--config", "app-config.yaml"], "console": "integratedTerminal", - "cwd": "${workspaceFolder}" + "cwd": "${workspaceFolder}/plugins/rbac-backend" }, ] } diff --git a/workspaces/rbac/README.md b/workspaces/rbac/README.md index 9f1ec5a938f..10958bd0ab1 100644 --- a/workspaces/rbac/README.md +++ b/workspaces/rbac/README.md @@ -1,18 +1,31 @@ -# [Backstage](https://backstage.io) +# RBAC — Backstage community-plugins workspace -This is your newly scaffolded Backstage App, Good Luck! +Publishable packages live under `plugins/` (`rbac`, `rbac-backend`, `rbac-common`, `rbac-node`). -To start the app, run: +## Setup ```sh yarn install -yarn start ``` -To generate knip reports for this app, run: +## Development + +There is no `packages/app` in this workspace. Use per-plugin dev harnesses: ```sh -yarn backstage-repo-tools knip-reports +# Backend (policy, REST API) +yarn workspace @backstage-community/plugin-rbac-backend start + +# Frontend UI (mocked APIs — default for UI work) +yarn workspace @backstage-community/plugin-rbac start:mock ``` -> Notice: The guest user has admin permissions in this application for quick setup. For better control, specify more users and groups in app-config.local.yaml and define a separate admin/admins permission instead of using the guest user. Using the guest user as an admin is not recommended for permission management. +See [plugins/rbac-backend/CONTRIBUTING.md](./plugins/rbac-backend/CONTRIBUTING.md) and [plugins/rbac/CONTRIBUTING.md](./plugins/rbac/CONTRIBUTING.md). + +## Other commands + +```sh +yarn test +yarn lint +yarn backstage-repo-tools knip-reports +``` diff --git a/workspaces/rbac/package.json b/workspaces/rbac/package.json index ec43622250e..97081eecf6b 100644 --- a/workspaces/rbac/package.json +++ b/workspaces/rbac/package.json @@ -6,8 +6,6 @@ "node": "22 || 24" }, "scripts": { - "start": "backstage-cli repo start", - "start:alpha": "yarn workspaces foreach -A --include @backstage-community/plugin-rbac --include @backstage-community/plugin-rbac-backend --parallel -v -i run start:alpha", "tsc": "tsc", "tsc:full": "tsc --skipLibCheck true --incremental false", "build:all": "backstage-cli repo build --all", diff --git a/workspaces/rbac/plugins/rbac-backend/CONTRIBUTING.md b/workspaces/rbac/plugins/rbac-backend/CONTRIBUTING.md new file mode 100644 index 00000000000..a9b11972348 --- /dev/null +++ b/workspaces/rbac/plugins/rbac-backend/CONTRIBUTING.md @@ -0,0 +1,90 @@ +# Contributing — RBAC backend plugin + +Developer guide for `@backstage-community/plugin-rbac-backend`. For operator install and configuration, see [README.md](./README.md). + +## Prerequisites + +- Node.js **22+** (see workspace `engines` in the workspace root `package.json`) +- Yarn (community-plugins monorepo lockfile) + +## Development harness + +Start the backend plugin in isolation (auth, catalog, permission, and RBAC — no frontend): + +```bash +# From workspaces/rbac +yarn workspace @backstage-community/plugin-rbac-backend start +``` + +Sample non-secret config keys live in [`app-config.yaml`](./app-config.yaml) beside this package. Optional overrides: untracked `app-config.local.yaml` in the same directory. + +For OIDC-backed manual permission checks (Keycloak + superhero CSV policies): + +```bash +yarn workspace @backstage-community/plugin-rbac-backend start:manual-tests +``` + +See [manual-tests/README.md](./manual-tests/README.md) for that flow. + +Only one backend `dev/` harness should listen on port **7007** at a time. + +### Recommended workflows + +| Goal | Command | +| ---------------------------- | --------------------------------------------------------------------------------------------------------------------------------- | +| Backend / REST / policy work | `yarn workspace @backstage-community/plugin-rbac-backend start` | +| UI work (mocked APIs) | `yarn workspace @backstage-community/plugin-rbac start:mock` | +| Multi-user permission smoke | `yarn workspace @backstage-community/plugin-rbac-backend start:manual-tests` + [manual-tests/README.md](./manual-tests/README.md) | + +### Full workspace evaluation + +There is no in-repo full Backstage app (`packages/app`, `packages/backend`). Day-to-day development uses plugin `dev/` harnesses plus automated tests. Legacy NFS-related CI in this workspace was evaluated and remains unchanged — backend `dev/` plus unit/integration tests cover module init and permission route registration. + +## Validation commands + +From `workspaces/rbac`: + +```bash +yarn workspace @backstage-community/plugin-rbac-backend test +yarn workspace @backstage-community/plugin-rbac-backend lint:check +yarn tsc +``` + +## What automated tests cover + +CI exercises: + +- **`startTestBackend`** — RBAC module init and `GET /api/permission/roles` route registration (`src/plugin.test.ts`) +- **Policy contracts** — Casbin/precedence and documented `superUsers` direct-membership rule (`src/policies/permission-policy.test.ts`) +- **REST handlers** — role and policy CRUD with mocked dependencies (`src/service/policies-rest-api*.test.ts`) + +Shared types and permission constants are covered in [`@backstage-community/plugin-rbac-common`](../rbac-common/CONTRIBUTING.md). + +## Optional manual smoke checklist + +After changing route registration or bumping `@backstage/*` dependencies: + +1. Start the backend harness (`start` or `start:manual-tests`). +2. Readiness: + + ```bash + curl -sf http://localhost:7007/.backstage/health/v1/readiness + ``` + +3. List roles (guest dev token): + + ```bash + TOKEN=$(curl -s http://localhost:7007/api/auth/guest/refresh -X POST \ + -H 'Content-Type: application/json' -d '{}' \ + | python3 -c "import sys,json; print(json.load(sys.stdin)['backstageIdentity']['token'])") + curl -H "Authorization: Bearer ${TOKEN}" http://localhost:7007/api/permission/roles + ``` + +REST endpoint reference: [docs/apis.md](./docs/apis.md). + +Multi-user, credential-backed, or production-like end-to-end testing belongs in a consumer Backstage deployment — not a newly scaffolded in-repo full app. + +## Related packages + +- [RBAC frontend plugin](../rbac/CONTRIBUTING.md) +- [RBAC common library](../rbac-common/CONTRIBUTING.md) diff --git a/workspaces/rbac/plugins/rbac-backend/README.md b/workspaces/rbac/plugins/rbac-backend/README.md index c7df1942dc1..1665994cea7 100644 --- a/workspaces/rbac/plugins/rbac-backend/README.md +++ b/workspaces/rbac/plugins/rbac-backend/README.md @@ -6,6 +6,8 @@ The Backstage permission framework is a core component of the Backstage project, With the RBAC plugin, you'll have the means to efficiently administer permissions within your Backstage instance by assigning them to users and groups. +For local development and CI commands, see [CONTRIBUTING.md](./CONTRIBUTING.md). + ## Prerequisites Before you dive into utilizing the RBAC plugin for Backstage, there are a few essential prerequisites to ensure a seamless experience. Please review the following requirements to make sure your environment is properly set up @@ -348,6 +350,11 @@ The maxDepth must be greater than 0 to ensure that the graphs are built correctl More information about group hierarchy can be found in the doc: [Group hierarchy](./docs/group-hierarchy.md). +## Related documentation + +- [Contributor guide](./CONTRIBUTING.md) +- [REST API reference](./docs/apis.md) + ### Optional RBAC provider module support We also include the ability to create and load in RBAC backend plugin modules that can be used to make connections to third part access management tools. For more information, consult the [RBAC Providers documentation](./docs/providers.md). diff --git a/workspaces/rbac/plugins/rbac-backend/app-config.manual-tests.yaml b/workspaces/rbac/plugins/rbac-backend/app-config.manual-tests.yaml new file mode 100644 index 00000000000..a98e2a677f4 --- /dev/null +++ b/workspaces/rbac/plugins/rbac-backend/app-config.manual-tests.yaml @@ -0,0 +1,55 @@ +# Overlay for backend-only manual RBAC tests (no frontend required). +# Usage: yarn workspace @backstage-community/plugin-rbac-backend start:manual-tests +# +# Merges on top of app-config.yaml in this directory. +# See manual-tests/README.md for Keycloak + OIDC login scripts. + +discovery: + endpoints: + - target: + internal: http://127.0.0.1:7007/api/{{pluginId}} + plugins: ['*'] + +backend: + auth: + keys: + - secret: manual-tests-backend-secret + +auth: + session: + secret: manual-tests-session-secret + environment: development + providers: + guest: {} + oidc: + development: + metadataUrl: http://localhost:8080/realms/backstage + clientId: backstage + clientSecret: 5Mpv83xSyJkBVnP1Oi28u9RrbGAMRps3 + prompt: auto + signIn: + resolvers: + - resolver: emailMatchingUserEntityProfileEmail + +catalog: + rules: + - allow: [Component, System, Group, User, Resource, Location, Template, API] + locations: + - type: file + target: manual-tests/rbac/all.yaml + rules: + - allow: [User, Group] + +permission: + enabled: true + rbac: + pluginsWithPermission: + - catalog + - permission + - scaffolder + policies-csv-file: manual-tests/rbac/rbac-policy.csv + admin: + users: + - name: user:default/admin + superUsers: + - name: user:default/super_user diff --git a/workspaces/rbac/plugins/rbac-backend/app-config.yaml b/workspaces/rbac/plugins/rbac-backend/app-config.yaml new file mode 100644 index 00000000000..b22250a8607 --- /dev/null +++ b/workspaces/rbac/plugins/rbac-backend/app-config.yaml @@ -0,0 +1,53 @@ +# Self-contained config for the RBAC backend dev harness. +# Usage: yarn workspace @backstage-community/plugin-rbac-backend start +# +# Optional local overrides: app-config.local.yaml beside this file (gitignored). + +app: + baseUrl: http://localhost:3000 + +organization: + name: RBAC Dev + +backend: + baseUrl: http://localhost:7007 + listen: + port: 7007 + cors: + origin: http://localhost:3000 + methods: [GET, HEAD, PATCH, POST, PUT, DELETE] + credentials: true + database: + client: better-sqlite3 + connection: ':memory:' + +auth: + environment: development + providers: + guest: {} + +catalog: + rules: + - allow: [Component, System, API, Resource, Location, Template, User, Group] + locations: + - type: file + target: ../../examples/entities.yaml + - type: file + target: ../../examples/template/template.yaml + rules: + - allow: [Template] + - type: file + target: ../../examples/org.yaml + rules: + - allow: [User, Group] + +permission: + enabled: true + rbac: + pluginsWithPermission: + - catalog + - permission + - scaffolder + admin: + users: + - name: user:development/guest diff --git a/workspaces/rbac/plugins/rbac-backend/dev/index.ts b/workspaces/rbac/plugins/rbac-backend/dev/index.ts index 2be0a79edb7..17379003069 100644 --- a/workspaces/rbac/plugins/rbac-backend/dev/index.ts +++ b/workspaces/rbac/plugins/rbac-backend/dev/index.ts @@ -20,6 +20,7 @@ const backend = createBackend(); backend.add(import('@backstage/plugin-auth-backend')); backend.add(import('@backstage/plugin-auth-backend-module-guest-provider')); +backend.add(import('@backstage/plugin-auth-backend-module-oidc-provider')); backend.add(import('@backstage/plugin-catalog-backend')); backend.add( diff --git a/workspaces/rbac/plugins/rbac-backend/manual-tests/README.md b/workspaces/rbac/plugins/rbac-backend/manual-tests/README.md new file mode 100644 index 00000000000..651e988f0b4 --- /dev/null +++ b/workspaces/rbac/plugins/rbac-backend/manual-tests/README.md @@ -0,0 +1,132 @@ +# Manual RBAC tests (backend-only) + +Run RBAC permission checks against all **42** superhero catalog users without a Backstage frontend. The flow uses: + +- **Keycloak** (`backstage` realm) for OIDC sign-in +- **Backend dev server** with CSV policies from `plugins/rbac-backend/manual-tests/rbac/rbac-policy.csv` +- **HTTP scripts** to obtain bearer tokens and call `/api/permission/authorize` + +## Prerequisites + +- Node.js 22+ and Yarn +- Python 3 with `requests` (`python3 -m pip install requests`) +- Docker or Podman (for Keycloak) +- Optional: PostgreSQL via `plugins/rbac-backend/app-config.local.yaml` (default harness uses in-memory SQLite from `plugins/rbac-backend/app-config.yaml`) + +## Quick start + +Run all commands from the **RBAC workspace root** (`workspaces/rbac`): + +```bash +cd workspaces/rbac +yarn install +``` + +### 1. Start Keycloak + +Start Keycloak **before** the Backstage backend so OIDC metadata is available on first login. + +```bash +chmod +x plugins/rbac-backend/manual-tests/scripts/* +./plugins/rbac-backend/manual-tests/scripts/start-keycloak.sh +``` + +Uses Docker when it is running, otherwise Podman. Keycloak admin: http://localhost:8080/admin (`admin` / `admin`) + +All test users use password `test` (for example `ant_man@example.com`). + +### 2. Start the RBAC backend (no frontend) + +In a second terminal, still from `workspaces/rbac`: + +```bash +yarn workspace @backstage-community/plugin-rbac-backend start:manual-tests +``` + +This loads `plugins/rbac-backend/app-config.yaml` + `app-config.manual-tests.yaml` (OIDC, catalog entities, CSV policies, admin/superUser config). + +If OIDC login returns `500 ECONNREFUSED`, restart the backend after Keycloak is healthy. + +Health check: + +```bash +curl -sf http://localhost:7007/.backstage/health/v1/readiness +``` + +### 3. Log in and obtain tokens (all 42 users) + +```bash +python3 plugins/rbac-backend/manual-tests/scripts/generate-userinfo.py +python3 plugins/rbac-backend/manual-tests/scripts/login.py \ + --csv plugins/rbac-backend/manual-tests/userinfo.csv +``` + +Or a single user: + +```bash +python3 plugins/rbac-backend/manual-tests/scripts/login.py --user ant_man +``` + +The login script performs the OIDC redirect flow against `/api/auth/oidc/start` — no browser or frontend required. `FRONTEND_URL` defaults to `http://localhost:3000` and is only used as the auth `origin` parameter. + +**Important:** bearer tokens in `userinfo.csv` are tied to the running backend instance. After every backend restart (in-memory SQLite resets auth keys), re-run `login.py` before `test-permissions.sh`. A `401 Failed user token verification` error means the tokens are stale. + +Regenerate `userinfo.csv` after changing expected results: + +```bash +python3 plugins/rbac-backend/manual-tests/scripts/generate-userinfo.py +``` + +### 4. Verify ALLOW / DENY (all 42 users) + +```bash +./plugins/rbac-backend/manual-tests/scripts/test-permissions.sh +``` + +## Files + +| Path | Purpose | +| ------------------------------------------------------------------ | ------------------------------------------------ | +| `plugins/rbac-backend/manual-tests/rbac/all.yaml` | Catalog location for superhero users/groups | +| `plugins/rbac-backend/manual-tests/rbac/rbac-policy.csv` | Casbin CSV policies (42 hierarchy test cases) | +| `plugins/rbac-backend/manual-tests/rbac/users.yaml`, `groups.yaml` | Catalog entities | +| `plugins/rbac-backend/manual-tests/keycloak/backstage-realm.json` | Keycloak realm with matching users | +| `plugins/rbac-backend/manual-tests/userinfo.csv` | Login + test matrix for all 42 users | +| `plugins/rbac-backend/manual-tests/scripts/start-keycloak.sh` | Run Keycloak with realm import | +| `plugins/rbac-backend/manual-tests/scripts/login.py` | OIDC login → Backstage bearer token | +| `plugins/rbac-backend/manual-tests/scripts/generate-userinfo.py` | Regenerate `userinfo.csv` from test expectations | +| `plugins/rbac-backend/manual-tests/scripts/test-permissions.sh` | POST `/api/permission/authorize` for every user | +| `plugins/rbac-backend/app-config.manual-tests.yaml` | Backend overlay config | + +## Environment variables + +| Variable | Default | Description | +| ----------------------- | ----------------------- | -------------------------------------------------------- | +| `BASE_URL` | `http://localhost:7007` | Backstage backend URL | +| `FRONTEND_URL` | `http://localhost:3000` | Auth flow origin (no UI needed) | +| `KEYCLOAK_HOST_REWRITE` | _(empty)_ | e.g. `keycloak:8080=localhost:8080` in Docker Compose | +| `CSV_FILE` | `../userinfo.csv` | Path for `test-permissions.sh` (relative to scripts dir) | +| `KEYCLOAK_PORT` | `8080` | Keycloak host port | + +## Guest auth (smoke test) + +The dev backend also registers the guest provider. Without Keycloak: + +```bash +TOKEN=$(curl -s http://localhost:7007/api/auth/guest/refresh \ + -X POST -H 'Content-Type: application/json' -d '{}' \ + | python3 -c "import sys,json; print(json.load(sys.stdin)['backstageIdentity']['token'])") + +curl -s http://localhost:7007/api/permission/authorize \ + -H "Authorization: Bearer $TOKEN" \ + -H 'Content-Type: application/json' \ + -d '{"items":[{"id":"1","permission":{"name":"catalog.entity.read","type":"resource","resourceType":"catalog-entity","attributes":{"action":"read"}},"resourceRef":"component:default/x"}]}' +``` + +Guest users are not in the CSV policy file, so catalog read is typically **DENY** unless configured as admin. + +## API reference + +RBAC REST endpoints: [`plugins/rbac-backend/docs/apis.md`](../docs/apis.md) + +Hierarchy test cases mirror [`plugins/rbac-backend/src/policies/permission-policy.hierarchy.test.ts`](../src/policies/permission-policy.hierarchy.test.ts). diff --git a/workspaces/rbac/plugins/rbac-backend/manual-tests/keycloak/backstage-realm.json b/workspaces/rbac/plugins/rbac-backend/manual-tests/keycloak/backstage-realm.json new file mode 100644 index 00000000000..2ceca63781c --- /dev/null +++ b/workspaces/rbac/plugins/rbac-backend/manual-tests/keycloak/backstage-realm.json @@ -0,0 +1,2673 @@ +{ + "realm": "backstage", + "notBefore": 0, + "defaultSignatureAlgorithm": "RS256", + "revokeRefreshToken": false, + "refreshTokenMaxReuse": 0, + "accessTokenLifespan": 3600, + "accessTokenLifespanForImplicitFlow": 900, + "ssoSessionIdleTimeout": 1800, + "ssoSessionMaxLifespan": 36000, + "ssoSessionIdleTimeoutRememberMe": 0, + "ssoSessionMaxLifespanRememberMe": 0, + "offlineSessionIdleTimeout": 2592000, + "offlineSessionMaxLifespanEnabled": false, + "offlineSessionMaxLifespan": 5184000, + "clientSessionIdleTimeout": 0, + "clientSessionMaxLifespan": 0, + "clientOfflineSessionIdleTimeout": 0, + "clientOfflineSessionMaxLifespan": 0, + "accessCodeLifespan": 60, + "accessCodeLifespanUserAction": 300, + "accessCodeLifespanLogin": 1800, + "actionTokenGeneratedByAdminLifespan": 43200, + "actionTokenGeneratedByUserLifespan": 300, + "oauth2DeviceCodeLifespan": 600, + "oauth2DevicePollingInterval": 5, + "enabled": true, + "sslRequired": "none", + "registrationAllowed": false, + "registrationEmailAsUsername": false, + "rememberMe": false, + "verifyEmail": false, + "loginWithEmailAllowed": true, + "duplicateEmailsAllowed": false, + "resetPasswordAllowed": false, + "editUsernameAllowed": false, + "bruteForceProtected": false, + "permanentLockout": false, + "maxFailureWaitSeconds": 900, + "minimumQuickLoginWaitSeconds": 60, + "waitIncrementSeconds": 60, + "quickLoginCheckMilliSeconds": 1000, + "maxDeltaTimeSeconds": 43200, + "failureFactor": 30, + "roles": { + "realm": [ + { + "name": "offline_access", + "description": "${role_offline-access}", + "composite": false, + "clientRole": false, + "containerId": "f892c4a8-7256-496d-806f-392dd1ca743f", + "attributes": {} + }, + { + "name": "default-roles-backstage", + "description": "${role_default-roles}", + "composite": true, + "composites": { + "realm": ["offline_access", "uma_authorization"], + "client": { + "account": ["view-profile", "manage-account"] + } + }, + "clientRole": false, + "containerId": "f892c4a8-7256-496d-806f-392dd1ca743f", + "attributes": {} + }, + { + "name": "uma_authorization", + "description": "${role_uma_authorization}", + "composite": false, + "clientRole": false, + "containerId": "f892c4a8-7256-496d-806f-392dd1ca743f", + "attributes": {} + } + ], + "client": { + "realm-management": [ + { + "name": "query-realms", + "description": "${role_query-realms}", + "composite": false, + "clientRole": true, + "containerId": "f96e0fcc-eb97-4ef9-b1b1-a6c619e72d32", + "attributes": {} + }, + { + "name": "create-client", + "description": "${role_create-client}", + "composite": false, + "clientRole": true, + "containerId": "f96e0fcc-eb97-4ef9-b1b1-a6c619e72d32", + "attributes": {} + }, + { + "name": "query-clients", + "description": "${role_query-clients}", + "composite": false, + "clientRole": true, + "containerId": "f96e0fcc-eb97-4ef9-b1b1-a6c619e72d32", + "attributes": {} + }, + { + "name": "view-events", + "description": "${role_view-events}", + "composite": false, + "clientRole": true, + "containerId": "f96e0fcc-eb97-4ef9-b1b1-a6c619e72d32", + "attributes": {} + }, + { + "name": "impersonation", + "description": "${role_impersonation}", + "composite": false, + "clientRole": true, + "containerId": "f96e0fcc-eb97-4ef9-b1b1-a6c619e72d32", + "attributes": {} + }, + { + "name": "view-clients", + "description": "${role_view-clients}", + "composite": true, + "composites": { + "client": { + "realm-management": ["query-clients"] + } + }, + "clientRole": true, + "containerId": "f96e0fcc-eb97-4ef9-b1b1-a6c619e72d32", + "attributes": {} + }, + { + "name": "manage-authorization", + "description": "${role_manage-authorization}", + "composite": false, + "clientRole": true, + "containerId": "f96e0fcc-eb97-4ef9-b1b1-a6c619e72d32", + "attributes": {} + }, + { + "name": "realm-admin", + "description": "${role_realm-admin}", + "composite": true, + "composites": { + "client": { + "realm-management": [ + "query-realms", + "create-client", + "query-clients", + "view-events", + "view-clients", + "impersonation", + "manage-authorization", + "view-authorization", + "manage-clients", + "view-identity-providers", + "view-realm", + "manage-identity-providers", + "manage-events", + "manage-realm", + "query-groups", + "view-users", + "manage-users", + "query-users" + ] + } + }, + "clientRole": true, + "containerId": "f96e0fcc-eb97-4ef9-b1b1-a6c619e72d32", + "attributes": {} + }, + { + "name": "view-authorization", + "description": "${role_view-authorization}", + "composite": false, + "clientRole": true, + "containerId": "f96e0fcc-eb97-4ef9-b1b1-a6c619e72d32", + "attributes": {} + }, + { + "name": "manage-clients", + "description": "${role_manage-clients}", + "composite": false, + "clientRole": true, + "containerId": "f96e0fcc-eb97-4ef9-b1b1-a6c619e72d32", + "attributes": {} + }, + { + "name": "view-identity-providers", + "description": "${role_view-identity-providers}", + "composite": false, + "clientRole": true, + "containerId": "f96e0fcc-eb97-4ef9-b1b1-a6c619e72d32", + "attributes": {} + }, + { + "name": "view-realm", + "description": "${role_view-realm}", + "composite": false, + "clientRole": true, + "containerId": "f96e0fcc-eb97-4ef9-b1b1-a6c619e72d32", + "attributes": {} + }, + { + "name": "manage-events", + "description": "${role_manage-events}", + "composite": false, + "clientRole": true, + "containerId": "f96e0fcc-eb97-4ef9-b1b1-a6c619e72d32", + "attributes": {} + }, + { + "name": "manage-identity-providers", + "description": "${role_manage-identity-providers}", + "composite": false, + "clientRole": true, + "containerId": "f96e0fcc-eb97-4ef9-b1b1-a6c619e72d32", + "attributes": {} + }, + { + "name": "manage-realm", + "description": "${role_manage-realm}", + "composite": false, + "clientRole": true, + "containerId": "f96e0fcc-eb97-4ef9-b1b1-a6c619e72d32", + "attributes": {} + }, + { + "name": "query-groups", + "description": "${role_query-groups}", + "composite": false, + "clientRole": true, + "containerId": "f96e0fcc-eb97-4ef9-b1b1-a6c619e72d32", + "attributes": {} + }, + { + "name": "view-users", + "description": "${role_view-users}", + "composite": true, + "composites": { + "client": { + "realm-management": ["query-groups", "query-users"] + } + }, + "clientRole": true, + "containerId": "f96e0fcc-eb97-4ef9-b1b1-a6c619e72d32", + "attributes": {} + }, + { + "name": "manage-users", + "description": "${role_manage-users}", + "composite": false, + "clientRole": true, + "containerId": "f96e0fcc-eb97-4ef9-b1b1-a6c619e72d32", + "attributes": {} + }, + { + "name": "query-users", + "description": "${role_query-users}", + "composite": false, + "clientRole": true, + "containerId": "f96e0fcc-eb97-4ef9-b1b1-a6c619e72d32", + "attributes": {} + } + ], + "security-admin-console": [], + "admin-cli": [], + "backstage": [], + "account-console": [], + "broker": [ + { + "name": "read-token", + "description": "${role_read-token}", + "composite": false, + "clientRole": true, + "containerId": "c8f8eee7-fee3-4ee1-9880-15ecaeb9936f", + "attributes": {} + } + ], + "account": [ + { + "name": "view-consent", + "description": "${role_view-consent}", + "composite": false, + "clientRole": true, + "containerId": "dfa14f3d-e608-4244-a655-809ec398c76d", + "attributes": {} + }, + { + "name": "manage-account-links", + "description": "${role_manage-account-links}", + "composite": false, + "clientRole": true, + "containerId": "dfa14f3d-e608-4244-a655-809ec398c76d", + "attributes": {} + }, + { + "name": "view-applications", + "description": "${role_view-applications}", + "composite": false, + "clientRole": true, + "containerId": "dfa14f3d-e608-4244-a655-809ec398c76d", + "attributes": {} + }, + { + "name": "view-groups", + "description": "${role_view-groups}", + "composite": false, + "clientRole": true, + "containerId": "dfa14f3d-e608-4244-a655-809ec398c76d", + "attributes": {} + }, + { + "name": "manage-consent", + "description": "${role_manage-consent}", + "composite": true, + "composites": { + "client": { + "account": ["view-consent"] + } + }, + "clientRole": true, + "containerId": "dfa14f3d-e608-4244-a655-809ec398c76d", + "attributes": {} + }, + { + "name": "delete-account", + "description": "${role_delete-account}", + "composite": false, + "clientRole": true, + "containerId": "dfa14f3d-e608-4244-a655-809ec398c76d", + "attributes": {} + }, + { + "name": "view-profile", + "description": "${role_view-profile}", + "composite": false, + "clientRole": true, + "containerId": "dfa14f3d-e608-4244-a655-809ec398c76d", + "attributes": {} + }, + { + "name": "manage-account", + "description": "${role_manage-account}", + "composite": true, + "composites": { + "client": { + "account": ["manage-account-links"] + } + }, + "clientRole": true, + "containerId": "dfa14f3d-e608-4244-a655-809ec398c76d", + "attributes": {} + } + ] + } + }, + "groups": [], + "defaultRole": { + "id": "b72f3980-b5bf-48d1-8459-835ec20c98a0", + "name": "default-roles-backstage", + "description": "${role_default-roles}", + "composite": true, + "clientRole": false, + "containerId": "f892c4a8-7256-496d-806f-392dd1ca743f" + }, + "requiredCredentials": ["password"], + "otpPolicyType": "totp", + "otpPolicyAlgorithm": "HmacSHA1", + "otpPolicyInitialCounter": 0, + "otpPolicyDigits": 6, + "otpPolicyLookAheadWindow": 1, + "otpPolicyPeriod": 30, + "otpPolicyCodeReusable": false, + "otpSupportedApplications": [ + "totpAppFreeOTPName", + "totpAppMicrosoftAuthenticatorName", + "totpAppGoogleName" + ], + "webAuthnPolicyRpEntityName": "keycloak", + "webAuthnPolicySignatureAlgorithms": ["ES256"], + "webAuthnPolicyRpId": "", + "webAuthnPolicyAttestationConveyancePreference": "not specified", + "webAuthnPolicyAuthenticatorAttachment": "not specified", + "webAuthnPolicyRequireResidentKey": "not specified", + "webAuthnPolicyUserVerificationRequirement": "not specified", + "webAuthnPolicyCreateTimeout": 0, + "webAuthnPolicyAvoidSameAuthenticatorRegister": false, + "webAuthnPolicyAcceptableAaguids": [], + "webAuthnPolicyPasswordlessRpEntityName": "keycloak", + "webAuthnPolicyPasswordlessSignatureAlgorithms": ["ES256"], + "webAuthnPolicyPasswordlessRpId": "", + "webAuthnPolicyPasswordlessAttestationConveyancePreference": "not specified", + "webAuthnPolicyPasswordlessAuthenticatorAttachment": "not specified", + "webAuthnPolicyPasswordlessRequireResidentKey": "not specified", + "webAuthnPolicyPasswordlessUserVerificationRequirement": "not specified", + "webAuthnPolicyPasswordlessCreateTimeout": 0, + "webAuthnPolicyPasswordlessAvoidSameAuthenticatorRegister": false, + "webAuthnPolicyPasswordlessAcceptableAaguids": [], + "scopeMappings": [ + { + "clientScope": "offline_access", + "roles": ["offline_access"] + } + ], + "clientScopeMappings": { + "account": [ + { + "client": "account-console", + "roles": ["manage-account", "view-groups"] + } + ] + }, + "clients": [ + { + "clientId": "account", + "name": "${client_account}", + "rootUrl": "${authBaseUrl}", + "baseUrl": "/realms/backstage/account/", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "redirectUris": ["/realms/backstage/account/*"], + "webOrigins": [], + "notBefore": 0, + "bearerOnly": false, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": false, + "serviceAccountsEnabled": false, + "publicClient": true, + "frontchannelLogout": false, + "protocol": "openid-connect", + "attributes": { + "post.logout.redirect.uris": "+" + }, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": false, + "nodeReRegistrationTimeout": 0, + "defaultClientScopes": [ + "web-origins", + "acr", + "roles", + "profile", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] + }, + { + "clientId": "account-console", + "name": "${client_account-console}", + "rootUrl": "${authBaseUrl}", + "baseUrl": "/realms/backstage/account/", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "redirectUris": ["/realms/backstage/account/*"], + "webOrigins": [], + "notBefore": 0, + "bearerOnly": false, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": false, + "serviceAccountsEnabled": false, + "publicClient": true, + "frontchannelLogout": false, + "protocol": "openid-connect", + "attributes": { + "post.logout.redirect.uris": "+", + "pkce.code.challenge.method": "S256" + }, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": false, + "nodeReRegistrationTimeout": 0, + "protocolMappers": [ + { + "id": "ce1a136a-cf7a-4ce2-a2e2-a3239e5a3b38", + "name": "audience resolve", + "protocol": "openid-connect", + "protocolMapper": "oidc-audience-resolve-mapper", + "consentRequired": false, + "config": {} + } + ], + "defaultClientScopes": [ + "web-origins", + "acr", + "roles", + "profile", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] + }, + { + "clientId": "admin-cli", + "name": "${client_admin-cli}", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "redirectUris": [], + "webOrigins": [], + "notBefore": 0, + "bearerOnly": false, + "consentRequired": false, + "standardFlowEnabled": false, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": true, + "serviceAccountsEnabled": false, + "publicClient": true, + "frontchannelLogout": false, + "protocol": "openid-connect", + "attributes": {}, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": false, + "nodeReRegistrationTimeout": 0, + "defaultClientScopes": [ + "web-origins", + "acr", + "roles", + "profile", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] + }, + { + "clientId": "backstage", + "name": "Backstage", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "secret": "5Mpv83xSyJkBVnP1Oi28u9RrbGAMRps3", + "redirectUris": [ + "http://localhost:3000/*", + "http://localhost:7007/*", + "http://backstage:7007/*", + "http://backstage:3000/*" + ], + "webOrigins": [ + "http://localhost:7007", + "http://localhost:3000", + "http://backstage:7007", + "http://backstage:3000" + ], + "notBefore": 0, + "bearerOnly": false, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": true, + "serviceAccountsEnabled": false, + "publicClient": false, + "frontchannelLogout": false, + "protocol": "openid-connect", + "attributes": { + "post.logout.redirect.uris": "+" + }, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": true, + "nodeReRegistrationTimeout": -1, + "defaultClientScopes": [ + "web-origins", + "acr", + "roles", + "profile", + "microprofile-jwt", + "email" + ], + "optionalClientScopes": [] + }, + { + "clientId": "broker", + "name": "${client_broker}", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "redirectUris": [], + "webOrigins": [], + "notBefore": 0, + "bearerOnly": true, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": false, + "serviceAccountsEnabled": false, + "publicClient": false, + "frontchannelLogout": false, + "protocol": "openid-connect", + "attributes": {}, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": false, + "nodeReRegistrationTimeout": 0, + "defaultClientScopes": [ + "web-origins", + "acr", + "roles", + "profile", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] + }, + { + "clientId": "realm-management", + "name": "${client_realm-management}", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "redirectUris": [], + "webOrigins": [], + "notBefore": 0, + "bearerOnly": true, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": false, + "serviceAccountsEnabled": false, + "publicClient": false, + "frontchannelLogout": false, + "protocol": "openid-connect", + "attributes": {}, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": false, + "nodeReRegistrationTimeout": 0, + "defaultClientScopes": [ + "web-origins", + "acr", + "roles", + "profile", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] + }, + { + "clientId": "security-admin-console", + "name": "${client_security-admin-console}", + "rootUrl": "${authAdminUrl}", + "baseUrl": "/admin/backstage/console/", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "redirectUris": ["/admin/backstage/console/*"], + "webOrigins": ["+"], + "notBefore": 0, + "bearerOnly": false, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": false, + "serviceAccountsEnabled": false, + "publicClient": true, + "frontchannelLogout": false, + "protocol": "openid-connect", + "attributes": { + "post.logout.redirect.uris": "+", + "pkce.code.challenge.method": "S256" + }, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": false, + "nodeReRegistrationTimeout": 0, + "protocolMappers": [ + { + "id": "fa97e0de-3c9e-4216-8168-af6046055610", + "name": "locale", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "locale", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "locale", + "jsonType.label": "String" + } + } + ], + "defaultClientScopes": [ + "web-origins", + "acr", + "roles", + "profile", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] + } + ], + "clientScopes": [ + { + "name": "roles", + "description": "OpenID Connect scope for add user roles to the access token", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "false", + "display.on.consent.screen": "true", + "consent.screen.text": "${rolesScopeConsentText}" + }, + "protocolMappers": [ + { + "id": "3c004204-52ca-4683-9cad-7de8a5931255", + "name": "client roles", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-client-role-mapper", + "consentRequired": false, + "config": { + "user.attribute": "foo", + "access.token.claim": "true", + "claim.name": "resource_access.${client_id}.roles", + "jsonType.label": "String", + "multivalued": "true" + } + }, + { + "id": "2824e8a5-2e8c-4d50-9195-6cd155d382f2", + "name": "realm roles", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-realm-role-mapper", + "consentRequired": false, + "config": { + "user.attribute": "foo", + "access.token.claim": "true", + "claim.name": "realm_access.roles", + "jsonType.label": "String", + "multivalued": "true" + } + }, + { + "id": "4a8a545d-a9d6-41c9-a1de-71f98f0736be", + "name": "audience resolve", + "protocol": "openid-connect", + "protocolMapper": "oidc-audience-resolve-mapper", + "consentRequired": false, + "config": {} + } + ] + }, + { + "name": "microprofile-jwt", + "description": "Microprofile - JWT built-in scope", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "display.on.consent.screen": "false" + }, + "protocolMappers": [ + { + "id": "2f8ce880-5e12-4527-a578-0ae59e759d92", + "name": "groups", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-realm-role-mapper", + "consentRequired": false, + "config": { + "multivalued": "true", + "user.attribute": "foo", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "groups", + "jsonType.label": "String" + } + }, + { + "id": "648108f6-f0db-4b22-bab0-70e179dcd44d", + "name": "upn", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "username", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "upn", + "jsonType.label": "String" + } + } + ] + }, + { + "name": "email", + "description": "OpenID Connect built-in scope: email", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "display.on.consent.screen": "true", + "consent.screen.text": "${emailScopeConsentText}" + }, + "protocolMappers": [ + { + "id": "930a0927-bd83-46a6-b402-d8170e21921a", + "name": "email verified", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-property-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "emailVerified", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "email_verified", + "jsonType.label": "boolean" + } + }, + { + "id": "9b3d4919-edfe-4e03-b948-399740660002", + "name": "email", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "email", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "email", + "jsonType.label": "String" + } + } + ] + }, + { + "name": "profile", + "description": "OpenID Connect built-in scope: profile", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "display.on.consent.screen": "true", + "consent.screen.text": "${profileScopeConsentText}" + }, + "protocolMappers": [ + { + "id": "7ec2b258-a3be-419c-8f21-b0ec529c32d6", + "name": "family name", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "lastName", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "family_name", + "jsonType.label": "String" + } + }, + { + "id": "1e0eda82-ab93-4638-ad96-c89a6543a3c8", + "name": "website", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "website", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "website", + "jsonType.label": "String" + } + }, + { + "id": "1cd46887-3c81-4953-a013-531ccbdcf3eb", + "name": "picture", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "picture", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "picture", + "jsonType.label": "String" + } + }, + { + "id": "9d76fd2f-aa97-46fd-9104-1682fe5491fa", + "name": "gender", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "gender", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "gender", + "jsonType.label": "String" + } + }, + { + "id": "3bcfa352-e87e-4b4b-b7fd-41546fe2469e", + "name": "zoneinfo", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "zoneinfo", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "zoneinfo", + "jsonType.label": "String" + } + }, + { + "id": "b3d92a82-e518-400c-a959-0923b74a6b59", + "name": "given name", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "firstName", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "given_name", + "jsonType.label": "String" + } + }, + { + "id": "580c0f30-5f42-4014-b3aa-9cfc3382b019", + "name": "nickname", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "nickname", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "nickname", + "jsonType.label": "String" + } + }, + { + "id": "874b2964-c827-40a9-86c0-4d730ae3bbdc", + "name": "locale", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "locale", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "locale", + "jsonType.label": "String" + } + }, + { + "id": "c42dc69e-464c-4845-9a2c-5c26dd816feb", + "name": "middle name", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "middleName", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "middle_name", + "jsonType.label": "String" + } + }, + { + "id": "3128bec9-9bf7-4807-ba6e-422c5e00ac16", + "name": "profile", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "profile", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "profile", + "jsonType.label": "String" + } + }, + { + "id": "19c1c795-c9f1-483e-a5fb-06f96f673b60", + "name": "username", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "username", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "preferred_username", + "jsonType.label": "String" + } + }, + { + "id": "29ba7b71-ee62-45fe-a988-095d5d5aa8d8", + "name": "full name", + "protocol": "openid-connect", + "protocolMapper": "oidc-full-name-mapper", + "consentRequired": false, + "config": { + "id.token.claim": "true", + "access.token.claim": "true", + "userinfo.token.claim": "true" + } + }, + { + "id": "483910fe-1226-459f-af7b-2cda1ea795bd", + "name": "birthdate", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "birthdate", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "birthdate", + "jsonType.label": "String" + } + }, + { + "id": "02673266-e549-442a-ab09-af41d9f0b5c3", + "name": "updated at", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "updatedAt", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "updated_at", + "jsonType.label": "long" + } + } + ] + }, + { + "name": "acr", + "description": "OpenID Connect scope for add acr (authentication context class reference) to the token", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "false", + "display.on.consent.screen": "false" + }, + "protocolMappers": [ + { + "id": "eeceabe8-04c7-4bb4-9b9e-677844630bc1", + "name": "acr loa level", + "protocol": "openid-connect", + "protocolMapper": "oidc-acr-mapper", + "consentRequired": false, + "config": { + "id.token.claim": "true", + "access.token.claim": "true" + } + } + ] + }, + { + "name": "offline_access", + "description": "OpenID Connect built-in scope: offline_access", + "protocol": "openid-connect", + "attributes": { + "consent.screen.text": "${offlineAccessScopeConsentText}", + "display.on.consent.screen": "true" + } + }, + { + "name": "role_list", + "description": "SAML role list", + "protocol": "saml", + "attributes": { + "consent.screen.text": "${samlRoleListScopeConsentText}", + "display.on.consent.screen": "true" + }, + "protocolMappers": [ + { + "id": "6fcf7b83-7ee8-400e-a2d5-dd7b97b336c3", + "name": "role list", + "protocol": "saml", + "protocolMapper": "saml-role-list-mapper", + "consentRequired": false, + "config": { + "single": "false", + "attribute.nameformat": "Basic", + "attribute.name": "Role" + } + } + ] + }, + { + "name": "address", + "description": "OpenID Connect built-in scope: address", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "display.on.consent.screen": "true", + "consent.screen.text": "${addressScopeConsentText}" + }, + "protocolMappers": [ + { + "id": "30b700cf-ad85-455e-9918-8b7ea22a2ba6", + "name": "address", + "protocol": "openid-connect", + "protocolMapper": "oidc-address-mapper", + "consentRequired": false, + "config": { + "user.attribute.formatted": "formatted", + "user.attribute.country": "country", + "user.attribute.postal_code": "postal_code", + "userinfo.token.claim": "true", + "user.attribute.street": "street", + "id.token.claim": "true", + "user.attribute.region": "region", + "access.token.claim": "true", + "user.attribute.locality": "locality" + } + } + ] + }, + { + "name": "phone", + "description": "OpenID Connect built-in scope: phone", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "display.on.consent.screen": "true", + "consent.screen.text": "${phoneScopeConsentText}" + }, + "protocolMappers": [ + { + "id": "3d0978b9-261c-43fb-97db-34a3d85c8817", + "name": "phone number", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "phoneNumber", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "phone_number", + "jsonType.label": "String" + } + }, + { + "id": "97f53db5-2f3d-4eaa-9eb2-e549a9058dc8", + "name": "phone number verified", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "phoneNumberVerified", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "phone_number_verified", + "jsonType.label": "boolean" + } + } + ] + }, + { + "name": "web-origins", + "description": "OpenID Connect scope for add allowed web origins to the access token", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "false", + "display.on.consent.screen": "false", + "consent.screen.text": "" + }, + "protocolMappers": [ + { + "id": "f0f93efa-2a19-4747-8adb-0b63ba4f454a", + "name": "allowed web origins", + "protocol": "openid-connect", + "protocolMapper": "oidc-allowed-origins-mapper", + "consentRequired": false, + "config": {} + } + ] + } + ], + "defaultDefaultClientScopes": [ + "role_list", + "profile", + "email", + "roles", + "web-origins", + "acr" + ], + "defaultOptionalClientScopes": [ + "offline_access", + "address", + "phone", + "microprofile-jwt" + ], + "browserSecurityHeaders": { + "contentSecurityPolicyReportOnly": "", + "xContentTypeOptions": "nosniff", + "referrerPolicy": "no-referrer", + "xRobotsTag": "none", + "xFrameOptions": "SAMEORIGIN", + "contentSecurityPolicy": "frame-src 'self'; frame-ancestors 'self'; object-src 'none';", + "xXSSProtection": "1; mode=block", + "strictTransportSecurity": "max-age=31536000; includeSubDomains" + }, + "smtpServer": {}, + "eventsEnabled": false, + "eventsListeners": ["jboss-logging"], + "enabledEventTypes": [], + "adminEventsEnabled": false, + "adminEventsDetailsEnabled": false, + "identityProviders": [], + "identityProviderMappers": [], + "components": { + "org.keycloak.services.clientregistration.policy.ClientRegistrationPolicy": [ + { + "id": "7bc682ad-008b-41e3-aca3-672c725ae94c", + "name": "Max Clients Limit", + "providerId": "max-clients", + "subType": "anonymous", + "subComponents": {}, + "config": { + "max-clients": ["200"] + } + }, + { + "id": "dadf7066-9020-4eca-bd1f-ed70e20e8d42", + "name": "Allowed Protocol Mapper Types", + "providerId": "allowed-protocol-mappers", + "subType": "authenticated", + "subComponents": {}, + "config": { + "allowed-protocol-mapper-types": [ + "oidc-full-name-mapper", + "saml-user-property-mapper", + "oidc-sha256-pairwise-sub-mapper", + "saml-user-attribute-mapper", + "oidc-usermodel-property-mapper", + "oidc-usermodel-attribute-mapper", + "oidc-address-mapper", + "saml-role-list-mapper" + ] + } + }, + { + "id": "2e410ef2-3c5e-4d96-9718-efd3f1f521fa", + "name": "Full Scope Disabled", + "providerId": "scope", + "subType": "anonymous", + "subComponents": {}, + "config": {} + }, + { + "id": "60e7a38c-5cea-4bc2-88ea-a73917f5150a", + "name": "Allowed Client Scopes", + "providerId": "allowed-client-templates", + "subType": "anonymous", + "subComponents": {}, + "config": { + "allow-default-scopes": ["true"] + } + }, + { + "id": "3828602a-e101-4d0e-857d-dd09d481f98d", + "name": "Trusted Hosts", + "providerId": "trusted-hosts", + "subType": "anonymous", + "subComponents": {}, + "config": { + "host-sending-registration-request-must-match": ["true"], + "client-uris-must-match": ["true"] + } + }, + { + "id": "e969fbb0-5265-414c-ba0c-afcd35375fce", + "name": "Consent Required", + "providerId": "consent-required", + "subType": "anonymous", + "subComponents": {}, + "config": {} + }, + { + "id": "83d2d0b6-93ec-4d85-84c5-9a526a780ba8", + "name": "Allowed Client Scopes", + "providerId": "allowed-client-templates", + "subType": "authenticated", + "subComponents": {}, + "config": { + "allow-default-scopes": ["true"] + } + }, + { + "id": "28f248b8-5ca7-4aa7-8295-d4ed80771226", + "name": "Allowed Protocol Mapper Types", + "providerId": "allowed-protocol-mappers", + "subType": "anonymous", + "subComponents": {}, + "config": { + "allowed-protocol-mapper-types": [ + "oidc-sha256-pairwise-sub-mapper", + "oidc-address-mapper", + "oidc-usermodel-property-mapper", + "saml-user-property-mapper", + "oidc-usermodel-attribute-mapper", + "oidc-full-name-mapper", + "saml-role-list-mapper", + "saml-user-attribute-mapper" + ] + } + } + ], + "org.keycloak.keys.KeyProvider": [ + { + "id": "39a0acb8-ca3a-438b-a374-b4e265086aa3", + "name": "rsa-generated", + "providerId": "rsa-generated", + "subComponents": {}, + "config": { + "priority": ["100"] + } + }, + { + "id": "6e6183e9-749d-44b3-8f4e-f3f9d88e4b40", + "name": "aes-generated", + "providerId": "aes-generated", + "subComponents": {}, + "config": { + "priority": ["100"] + } + }, + { + "id": "03a05ebe-a3bb-4263-a4c8-b2bc9bb915b7", + "name": "hmac-generated", + "providerId": "hmac-generated", + "subComponents": {}, + "config": { + "priority": ["100"], + "algorithm": ["HS256"] + } + }, + { + "id": "1f81528b-c789-4bc3-9c57-6d1ee4537bfa", + "name": "rsa-enc-generated", + "providerId": "rsa-enc-generated", + "subComponents": {}, + "config": { + "priority": ["100"], + "algorithm": ["RSA-OAEP"] + } + } + ] + }, + "internationalizationEnabled": false, + "supportedLocales": [], + "authenticationFlows": [ + { + "id": "fac33691-6625-487f-a5d8-9d92ad97135a", + "alias": "Account verification options", + "description": "Method with which to verity the existing account", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "idp-email-verification", + "authenticatorFlow": false, + "requirement": "ALTERNATIVE", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticatorFlow": true, + "requirement": "ALTERNATIVE", + "priority": 20, + "autheticatorFlow": true, + "flowAlias": "Verify Existing Account by Re-authentication", + "userSetupAllowed": false + } + ] + }, + { + "id": "bad292c5-f187-42d4-8fd2-a9cddf83cbd6", + "alias": "Browser - Conditional OTP", + "description": "Flow to determine if the OTP is required for the authentication", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "conditional-user-configured", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "auth-otp-form", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 20, + "autheticatorFlow": false, + "userSetupAllowed": false + } + ] + }, + { + "id": "d3ef44a0-5002-4535-b147-7aec910884e7", + "alias": "Direct Grant - Conditional OTP", + "description": "Flow to determine if the OTP is required for the authentication", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "conditional-user-configured", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "direct-grant-validate-otp", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 20, + "autheticatorFlow": false, + "userSetupAllowed": false + } + ] + }, + { + "id": "487ab26e-7002-429a-9b88-844daeaa7b26", + "alias": "First broker login - Conditional OTP", + "description": "Flow to determine if the OTP is required for the authentication", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "conditional-user-configured", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "auth-otp-form", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 20, + "autheticatorFlow": false, + "userSetupAllowed": false + } + ] + }, + { + "id": "54a59734-b775-4d22-be54-ac33c052fe04", + "alias": "Handle Existing Account", + "description": "Handle what to do if there is existing account with same email/username like authenticated identity provider", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "idp-confirm-link", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticatorFlow": true, + "requirement": "REQUIRED", + "priority": 20, + "autheticatorFlow": true, + "flowAlias": "Account verification options", + "userSetupAllowed": false + } + ] + }, + { + "id": "ea8fb840-73c0-4875-ba15-25976e32d2ee", + "alias": "Reset - Conditional OTP", + "description": "Flow to determine if the OTP should be reset or not. Set to REQUIRED to force.", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "conditional-user-configured", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "reset-otp", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 20, + "autheticatorFlow": false, + "userSetupAllowed": false + } + ] + }, + { + "id": "cbfc525b-beea-4731-bf1b-89e95229ed43", + "alias": "User creation or linking", + "description": "Flow for the existing/non-existing user alternatives", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticatorConfig": "create unique user config", + "authenticator": "idp-create-user-if-unique", + "authenticatorFlow": false, + "requirement": "ALTERNATIVE", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticatorFlow": true, + "requirement": "ALTERNATIVE", + "priority": 20, + "autheticatorFlow": true, + "flowAlias": "Handle Existing Account", + "userSetupAllowed": false + } + ] + }, + { + "id": "4694743f-9503-452e-b79e-956eea5ae97e", + "alias": "Verify Existing Account by Re-authentication", + "description": "Reauthentication of existing account", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "idp-username-password-form", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticatorFlow": true, + "requirement": "CONDITIONAL", + "priority": 20, + "autheticatorFlow": true, + "flowAlias": "First broker login - Conditional OTP", + "userSetupAllowed": false + } + ] + }, + { + "id": "df5149f8-1800-4728-90f0-f71b6a72dd10", + "alias": "browser", + "description": "browser based authentication", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "auth-cookie", + "authenticatorFlow": false, + "requirement": "ALTERNATIVE", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "auth-spnego", + "authenticatorFlow": false, + "requirement": "DISABLED", + "priority": 20, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "identity-provider-redirector", + "authenticatorFlow": false, + "requirement": "ALTERNATIVE", + "priority": 25, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticatorFlow": true, + "requirement": "ALTERNATIVE", + "priority": 30, + "autheticatorFlow": true, + "flowAlias": "forms", + "userSetupAllowed": false + } + ] + }, + { + "id": "ac3a909a-216c-4289-b06f-99b10624da52", + "alias": "clients", + "description": "Base authentication for clients", + "providerId": "client-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "client-secret", + "authenticatorFlow": false, + "requirement": "ALTERNATIVE", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "client-jwt", + "authenticatorFlow": false, + "requirement": "ALTERNATIVE", + "priority": 20, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "client-secret-jwt", + "authenticatorFlow": false, + "requirement": "ALTERNATIVE", + "priority": 30, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "client-x509", + "authenticatorFlow": false, + "requirement": "ALTERNATIVE", + "priority": 40, + "autheticatorFlow": false, + "userSetupAllowed": false + } + ] + }, + { + "id": "7d537f56-1fd1-4e2f-9f59-4909cfe6e95e", + "alias": "direct grant", + "description": "OpenID Connect Resource Owner Grant", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "direct-grant-validate-username", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "direct-grant-validate-password", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 20, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticatorFlow": true, + "requirement": "CONDITIONAL", + "priority": 30, + "autheticatorFlow": true, + "flowAlias": "Direct Grant - Conditional OTP", + "userSetupAllowed": false + } + ] + }, + { + "id": "bd9f5045-c5fb-4bac-829f-3b91ed8dff4a", + "alias": "docker auth", + "description": "Used by Docker clients to authenticate against the IDP", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "docker-http-basic-authenticator", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + } + ] + }, + { + "id": "2aae6b83-c235-4fac-93d6-5f6597b1bc83", + "alias": "first broker login", + "description": "Actions taken after first broker login with identity provider account, which is not yet linked to any Keycloak account", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticatorConfig": "review profile config", + "authenticator": "idp-review-profile", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticatorFlow": true, + "requirement": "REQUIRED", + "priority": 20, + "autheticatorFlow": true, + "flowAlias": "User creation or linking", + "userSetupAllowed": false + } + ] + }, + { + "id": "cee0e243-d921-41a5-bf67-f39783a1216a", + "alias": "forms", + "description": "Username, password, otp and other auth forms.", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "auth-username-password-form", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticatorFlow": true, + "requirement": "CONDITIONAL", + "priority": 20, + "autheticatorFlow": true, + "flowAlias": "Browser - Conditional OTP", + "userSetupAllowed": false + } + ] + }, + { + "id": "1dceaa18-8d2f-4407-a997-72eed1514b2b", + "alias": "registration", + "description": "registration flow", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "registration-page-form", + "authenticatorFlow": true, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": true, + "flowAlias": "registration form", + "userSetupAllowed": false + } + ] + }, + { + "id": "eab5e71c-f1e8-42a3-a25f-360cedf41902", + "alias": "registration form", + "description": "registration form", + "providerId": "form-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "registration-user-creation", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 20, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "registration-profile-action", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 40, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "registration-password-action", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 50, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "registration-recaptcha-action", + "authenticatorFlow": false, + "requirement": "DISABLED", + "priority": 60, + "autheticatorFlow": false, + "userSetupAllowed": false + } + ] + }, + { + "id": "e7b95bc6-e228-4c84-8e38-077bebfea518", + "alias": "reset credentials", + "description": "Reset credentials for a user if they forgot their password or something", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "reset-credentials-choose-user", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "reset-credential-email", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 20, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "reset-password", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 30, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticatorFlow": true, + "requirement": "CONDITIONAL", + "priority": 40, + "autheticatorFlow": true, + "flowAlias": "Reset - Conditional OTP", + "userSetupAllowed": false + } + ] + }, + { + "id": "b7574cac-ff70-4b6b-b3e0-c81be5dc66e9", + "alias": "saml ecp", + "description": "SAML ECP Profile Authentication Flow", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "http-basic-authenticator", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + } + ] + } + ], + "authenticatorConfig": [ + { + "id": "baa63eca-054e-4d69-b2b4-926feb7e910b", + "alias": "create unique user config", + "config": { + "require.password.update.after.registration": "false" + } + }, + { + "id": "2b7dd735-8e94-4926-92e1-443d152d7076", + "alias": "review profile config", + "config": { + "update.profile.on.first.login": "missing" + } + } + ], + "requiredActions": [ + { + "alias": "CONFIGURE_TOTP", + "name": "Configure OTP", + "providerId": "CONFIGURE_TOTP", + "enabled": true, + "defaultAction": false, + "priority": 10, + "config": {} + }, + { + "alias": "TERMS_AND_CONDITIONS", + "name": "Terms and Conditions", + "providerId": "TERMS_AND_CONDITIONS", + "enabled": false, + "defaultAction": false, + "priority": 20, + "config": {} + }, + { + "alias": "UPDATE_PASSWORD", + "name": "Update Password", + "providerId": "UPDATE_PASSWORD", + "enabled": true, + "defaultAction": false, + "priority": 30, + "config": {} + }, + { + "alias": "UPDATE_PROFILE", + "name": "Update Profile", + "providerId": "UPDATE_PROFILE", + "enabled": true, + "defaultAction": false, + "priority": 40, + "config": {} + }, + { + "alias": "VERIFY_EMAIL", + "name": "Verify Email", + "providerId": "VERIFY_EMAIL", + "enabled": true, + "defaultAction": false, + "priority": 50, + "config": {} + }, + { + "alias": "delete_account", + "name": "Delete Account", + "providerId": "delete_account", + "enabled": false, + "defaultAction": false, + "priority": 60, + "config": {} + }, + { + "alias": "webauthn-register", + "name": "Webauthn Register", + "providerId": "webauthn-register", + "enabled": true, + "defaultAction": false, + "priority": 70, + "config": {} + }, + { + "alias": "webauthn-register-passwordless", + "name": "Webauthn Register Passwordless", + "providerId": "webauthn-register-passwordless", + "enabled": true, + "defaultAction": false, + "priority": 80, + "config": {} + }, + { + "alias": "update_user_locale", + "name": "Update User Locale", + "providerId": "update_user_locale", + "enabled": true, + "defaultAction": false, + "priority": 1000, + "config": {} + } + ], + "browserFlow": "browser", + "registrationFlow": "registration", + "directGrantFlow": "direct grant", + "resetCredentialsFlow": "reset credentials", + "clientAuthenticationFlow": "clients", + "dockerAuthenticationFlow": "docker auth", + "attributes": { + "cibaBackchannelTokenDeliveryMode": "poll", + "cibaExpiresIn": "120", + "cibaAuthRequestedUserHint": "login_hint", + "oauth2DeviceCodeLifespan": "600", + "oauth2DevicePollingInterval": "5", + "parRequestUriLifespan": "60", + "cibaInterval": "5", + "realmReusableOtpCode": "false" + }, + "keycloakVersion": "22.0.1", + "userManagedAccessAllowed": false, + "clientProfiles": { + "profiles": [] + }, + "clientPolicies": { + "policies": [] + }, + "users": [ + { + "username": "ant_man", + "email": "ant_man@example.com", + "firstName": "Ant", + "lastName": "Man", + "enabled": true, + "emailVerified": true, + "credentials": [ + { + "type": "password", + "value": "test", + "temporary": false + } + ] + }, + { + "username": "hulk", + "email": "hulk@example.com", + "firstName": "Hulk", + "lastName": "Hulk", + "enabled": true, + "emailVerified": true, + "credentials": [ + { + "type": "password", + "value": "test", + "temporary": false + } + ] + }, + { + "username": "thor", + "email": "thor@example.com", + "firstName": "Thor", + "lastName": "Thor", + "enabled": true, + "emailVerified": true, + "credentials": [ + { + "type": "password", + "value": "test", + "temporary": false + } + ] + }, + { + "username": "wasp", + "email": "wasp@example.com", + "firstName": "Wasp", + "lastName": "Wasp", + "enabled": true, + "emailVerified": true, + "credentials": [ + { + "type": "password", + "value": "test", + "temporary": false + } + ] + }, + { + "username": "captain_america", + "email": "captain_america@example.com", + "firstName": "Captain", + "lastName": "America", + "enabled": true, + "emailVerified": true, + "credentials": [ + { + "type": "password", + "value": "test", + "temporary": false + } + ] + }, + { + "username": "hawkeye", + "email": "hawkeye@example.com", + "firstName": "Hawkeye", + "lastName": "Hawkeye", + "enabled": true, + "emailVerified": true, + "credentials": [ + { + "type": "password", + "value": "test", + "temporary": false + } + ] + }, + { + "username": "quicksilver", + "email": "quicksilver@example.com", + "firstName": "Quicksilver", + "lastName": "Quicksilver", + "enabled": true, + "emailVerified": true, + "credentials": [ + { + "type": "password", + "value": "test", + "temporary": false + } + ] + }, + { + "username": "scarlet_witch", + "email": "scarlet_witch@example.com", + "firstName": "Scarlet", + "lastName": "Witch", + "enabled": true, + "emailVerified": true, + "credentials": [ + { + "type": "password", + "value": "test", + "temporary": false + } + ] + }, + { + "username": "swordsman", + "email": "swordsman@example.com", + "firstName": "Swordsman", + "lastName": "Swordsman", + "enabled": true, + "emailVerified": true, + "credentials": [ + { + "type": "password", + "value": "test", + "temporary": false + } + ] + }, + { + "username": "hercules", + "email": "hercules@example.com", + "firstName": "Hercules", + "lastName": "Hercules", + "enabled": true, + "emailVerified": true, + "credentials": [ + { + "type": "password", + "value": "test", + "temporary": false + } + ] + }, + { + "username": "black_panther", + "email": "black_panther@example.com", + "firstName": "Black", + "lastName": "Panther", + "enabled": true, + "emailVerified": true, + "credentials": [ + { + "type": "password", + "value": "test", + "temporary": false + } + ] + }, + { + "username": "vision", + "email": "vision@example.com", + "firstName": "Vision", + "lastName": "Vision", + "enabled": true, + "emailVerified": true, + "credentials": [ + { + "type": "password", + "value": "test", + "temporary": false + } + ] + }, + { + "username": "black_knight", + "email": "black_knight@example.com", + "firstName": "Black", + "lastName": "Knight", + "enabled": true, + "emailVerified": true, + "credentials": [ + { + "type": "password", + "value": "test", + "temporary": false + } + ] + }, + { + "username": "black_widow", + "email": "black_widow@example.com", + "firstName": "Black", + "lastName": "Widow", + "enabled": true, + "emailVerified": true, + "credentials": [ + { + "type": "password", + "value": "test", + "temporary": false + } + ] + }, + { + "username": "mantis", + "email": "mantis@example.com", + "firstName": "Mantis", + "lastName": "Mantis", + "enabled": true, + "emailVerified": true, + "credentials": [ + { + "type": "password", + "value": "test", + "temporary": false + } + ] + }, + { + "username": "beast", + "email": "beast@example.com", + "firstName": "Beast", + "lastName": "Beast", + "enabled": true, + "emailVerified": true, + "credentials": [ + { + "type": "password", + "value": "test", + "temporary": false + } + ] + }, + { + "username": "moondragon", + "email": "moondragon@example.com", + "firstName": "Moondragon", + "lastName": "Moondragon", + "enabled": true, + "emailVerified": true, + "credentials": [ + { + "type": "password", + "value": "test", + "temporary": false + } + ] + }, + { + "username": "hellcat", + "email": "hellcat@example.com", + "firstName": "Hellcat", + "lastName": "Hellcat", + "enabled": true, + "emailVerified": true, + "credentials": [ + { + "type": "password", + "value": "test", + "temporary": false + } + ] + }, + { + "username": "captain_marvel", + "email": "captain_marvel@example.com", + "firstName": "Captain", + "lastName": "Marvel", + "enabled": true, + "emailVerified": true, + "credentials": [ + { + "type": "password", + "value": "test", + "temporary": false + } + ] + }, + { + "username": "falcon", + "email": "falcon@example.com", + "firstName": "Falcon", + "lastName": "Falcon", + "enabled": true, + "emailVerified": true, + "credentials": [ + { + "type": "password", + "value": "test", + "temporary": false + } + ] + }, + { + "username": "wonder_man", + "email": "wonder_man@example.com", + "firstName": "Wonder", + "lastName": "Man", + "enabled": true, + "emailVerified": true, + "credentials": [ + { + "type": "password", + "value": "test", + "temporary": false + } + ] + }, + { + "username": "tigra", + "email": "tigra@example.com", + "firstName": "Tigra", + "lastName": "Tigra", + "enabled": true, + "emailVerified": true, + "credentials": [ + { + "type": "password", + "value": "test", + "temporary": false + } + ] + }, + { + "username": "she_hulk", + "email": "she_hulk@example.com", + "firstName": "She", + "lastName": "Hulk", + "enabled": true, + "emailVerified": true, + "credentials": [ + { + "type": "password", + "value": "test", + "temporary": false + } + ] + }, + { + "username": "starfox", + "email": "starfox@example.com", + "firstName": "Starfox", + "lastName": "Starfox", + "enabled": true, + "emailVerified": true, + "credentials": [ + { + "type": "password", + "value": "test", + "temporary": false + } + ] + }, + { + "username": "mockingbird", + "email": "mockingbird@example.com", + "firstName": "Mockingbird", + "lastName": "Mockingbird", + "enabled": true, + "emailVerified": true, + "credentials": [ + { + "type": "password", + "value": "test", + "temporary": false + } + ] + }, + { + "username": "war_machine", + "email": "war_machine@example.com", + "firstName": "War", + "lastName": "Machine", + "enabled": true, + "emailVerified": true, + "credentials": [ + { + "type": "password", + "value": "test", + "temporary": false + } + ] + }, + { + "username": "namor", + "email": "namor@example.com", + "firstName": "Namor", + "lastName": "Namor", + "enabled": true, + "emailVerified": true, + "credentials": [ + { + "type": "password", + "value": "test", + "temporary": false + } + ] + }, + { + "username": "thing", + "email": "thing@example.com", + "firstName": "Thing", + "lastName": "Thing", + "enabled": true, + "emailVerified": true, + "credentials": [ + { + "type": "password", + "value": "test", + "temporary": false + } + ] + }, + { + "username": "doctor_druid", + "email": "doctor_druid@example.com", + "firstName": "Doctor", + "lastName": "Druid", + "enabled": true, + "emailVerified": true, + "credentials": [ + { + "type": "password", + "value": "test", + "temporary": false + } + ] + }, + { + "username": "firebird", + "email": "firebird@example.com", + "firstName": "Firebird", + "lastName": "Firebird", + "enabled": true, + "emailVerified": true, + "credentials": [ + { + "type": "password", + "value": "test", + "temporary": false + } + ] + }, + { + "username": "moon_knight", + "email": "moon_knight@example.com", + "firstName": "Moon", + "lastName": "Knight", + "enabled": true, + "emailVerified": true, + "credentials": [ + { + "type": "password", + "value": "test", + "temporary": false + } + ] + }, + { + "username": "spiderman", + "email": "spiderman@example.com", + "firstName": "Spiderman", + "lastName": "Spiderman", + "enabled": true, + "emailVerified": true, + "credentials": [ + { + "type": "password", + "value": "test", + "temporary": false + } + ] + }, + { + "username": "valkyrie", + "email": "valkyrie@example.com", + "firstName": "Valkyrie", + "lastName": "Valkyrie", + "enabled": true, + "emailVerified": true, + "credentials": [ + { + "type": "password", + "value": "test", + "temporary": false + } + ] + }, + { + "username": "nova", + "email": "nova@example.com", + "firstName": "Nova", + "lastName": "Nova", + "enabled": true, + "emailVerified": true, + "credentials": [ + { + "type": "password", + "value": "test", + "temporary": false + } + ] + }, + { + "username": "storm", + "email": "storm@example.com", + "firstName": "Storm", + "lastName": "Storm", + "enabled": true, + "emailVerified": true, + "credentials": [ + { + "type": "password", + "value": "test", + "temporary": false + } + ] + }, + { + "username": "daredevil", + "email": "daredevil@example.com", + "firstName": "Daredevil", + "lastName": "Daredevil", + "enabled": true, + "emailVerified": true, + "credentials": [ + { + "type": "password", + "value": "test", + "temporary": false + } + ] + }, + { + "username": "psylocke", + "email": "psylocke@example.com", + "firstName": "Psylocke", + "lastName": "Psylocke", + "enabled": true, + "emailVerified": true, + "credentials": [ + { + "type": "password", + "value": "test", + "temporary": false + } + ] + }, + { + "username": "penance", + "email": "penance@example.com", + "firstName": "Penance", + "lastName": "Penance", + "enabled": true, + "emailVerified": true, + "credentials": [ + { + "type": "password", + "value": "test", + "temporary": false + } + ] + }, + { + "username": "cable", + "email": "cable@example.com", + "firstName": "Cable", + "lastName": "Cable", + "enabled": true, + "emailVerified": true, + "credentials": [ + { + "type": "password", + "value": "test", + "temporary": false + } + ] + }, + { + "username": "ghost_rider", + "email": "ghost_rider@example.com", + "firstName": "Ghost", + "lastName": "Rider", + "enabled": true, + "emailVerified": true, + "credentials": [ + { + "type": "password", + "value": "test", + "temporary": false + } + ] + }, + { + "username": "admin", + "email": "admin@example.com", + "firstName": "Admin", + "lastName": "Admin", + "enabled": true, + "emailVerified": true, + "credentials": [ + { + "type": "password", + "value": "test", + "temporary": false + } + ] + }, + { + "username": "super_user", + "email": "super_user@example.com", + "firstName": "Super", + "lastName": "User", + "enabled": true, + "emailVerified": true, + "credentials": [ + { + "type": "password", + "value": "test", + "temporary": false + } + ] + } + ] +} diff --git a/workspaces/rbac/examples/manual-tests/rbac/all.yaml b/workspaces/rbac/plugins/rbac-backend/manual-tests/rbac/all.yaml similarity index 100% rename from workspaces/rbac/examples/manual-tests/rbac/all.yaml rename to workspaces/rbac/plugins/rbac-backend/manual-tests/rbac/all.yaml diff --git a/workspaces/rbac/examples/manual-tests/rbac/groups.yaml b/workspaces/rbac/plugins/rbac-backend/manual-tests/rbac/groups.yaml similarity index 100% rename from workspaces/rbac/examples/manual-tests/rbac/groups.yaml rename to workspaces/rbac/plugins/rbac-backend/manual-tests/rbac/groups.yaml diff --git a/workspaces/rbac/examples/manual-tests/rbac/rbac-group-charts.txt b/workspaces/rbac/plugins/rbac-backend/manual-tests/rbac/rbac-group-charts.txt similarity index 100% rename from workspaces/rbac/examples/manual-tests/rbac/rbac-group-charts.txt rename to workspaces/rbac/plugins/rbac-backend/manual-tests/rbac/rbac-group-charts.txt diff --git a/workspaces/rbac/examples/manual-tests/rbac/rbac-policy.csv b/workspaces/rbac/plugins/rbac-backend/manual-tests/rbac/rbac-policy.csv similarity index 100% rename from workspaces/rbac/examples/manual-tests/rbac/rbac-policy.csv rename to workspaces/rbac/plugins/rbac-backend/manual-tests/rbac/rbac-policy.csv diff --git a/workspaces/rbac/examples/manual-tests/rbac/users.yaml b/workspaces/rbac/plugins/rbac-backend/manual-tests/rbac/users.yaml similarity index 100% rename from workspaces/rbac/examples/manual-tests/rbac/users.yaml rename to workspaces/rbac/plugins/rbac-backend/manual-tests/rbac/users.yaml diff --git a/workspaces/rbac/plugins/rbac-backend/manual-tests/scripts/generate-userinfo.py b/workspaces/rbac/plugins/rbac-backend/manual-tests/scripts/generate-userinfo.py new file mode 100755 index 00000000000..9a314e50b43 --- /dev/null +++ b/workspaces/rbac/plugins/rbac-backend/manual-tests/scripts/generate-userinfo.py @@ -0,0 +1,77 @@ +#!/usr/bin/env python3 +"""Generate userinfo.csv for all 42 manual-test users from hierarchy test expectations.""" + +from __future__ import annotations + +import csv +from pathlib import Path + +# Expected catalog.entity.read results — mirrors permission-policy.hierarchy.test.ts +EXPECTED: list[tuple[str, str]] = [ + ("ant_man", "ALLOW"), + ("hulk", "DENY"), + ("thor", "ALLOW"), + ("wasp", "DENY"), + ("moon_knight", "ALLOW"), + ("spiderman", "DENY"), + ("captain_america", "ALLOW"), + ("hawkeye", "DENY"), + ("quicksilver", "DENY"), + ("scarlet_witch", "DENY"), + ("swordsman", "ALLOW"), + ("hercules", "DENY"), + ("black_panther", "DENY"), + ("vision", "DENY"), + ("black_knight", "ALLOW"), + ("black_widow", "DENY"), + ("mantis", "DENY"), + ("beast", "DENY"), + ("moondragon", "ALLOW"), + ("hellcat", "DENY"), + ("captain_marvel", "DENY"), + ("falcon", "DENY"), + ("wonder_man", "DENY"), + ("tigra", "DENY"), + ("she_hulk", "DENY"), + ("starfox", "DENY"), + ("mockingbird", "DENY"), + ("war_machine", "DENY"), + ("namor", "DENY"), + ("thing", "DENY"), + ("doctor_druid", "DENY"), + ("firebird", "DENY"), + ("valkyrie", "DENY"), + ("nova", "DENY"), + ("storm", "DENY"), + ("daredevil", "DENY"), + ("psylocke", "ALLOW"), + ("penance", "DENY"), + ("cable", "ALLOW"), + ("ghost_rider", "DENY"), + ("admin", "ALLOW"), + ("super_user", "ALLOW"), +] + +PASSWORD = "test" +OUTPUT = Path(__file__).resolve().parent.parent / "userinfo.csv" + + +def main() -> None: + if len(EXPECTED) != 42: + raise SystemExit(f"Expected 42 users, got {len(EXPECTED)}") + + rows = [ + [f"{username}@example.com", PASSWORD, expected, "token"] + for username, expected in EXPECTED + ] + + with OUTPUT.open("w", newline="", encoding="utf-8") as handle: + writer = csv.writer(handle) + writer.writerow(["email", "password", "expected", "token"]) + writer.writerows(rows) + + print(f"Wrote {len(rows)} users to {OUTPUT}") + + +if __name__ == "__main__": + main() diff --git a/workspaces/rbac/plugins/rbac-backend/manual-tests/scripts/login.py b/workspaces/rbac/plugins/rbac-backend/manual-tests/scripts/login.py new file mode 100755 index 00000000000..52ea95bd07b --- /dev/null +++ b/workspaces/rbac/plugins/rbac-backend/manual-tests/scripts/login.py @@ -0,0 +1,170 @@ +#!/usr/bin/env python3 +"""Obtain Backstage bearer tokens via the OIDC auth flow (no frontend required).""" + +from __future__ import annotations + +import argparse +import csv +import json +import os +import re +import sys +import time +import urllib.parse + +try: + import requests +except ImportError: + print("Install requests: python3 -m pip install requests", file=sys.stderr) + sys.exit(1) + +BACKSTAGE_BASE_URL = os.getenv("BASE_URL", "http://localhost:7007") +FRONTEND_URL = os.getenv("FRONTEND_URL", "http://localhost:3000") + +_HOST_REWRITES: list[tuple[str, str]] = [] +for _rule in os.getenv("KEYCLOAK_HOST_REWRITE", "").split(","): + _rule = _rule.strip() + if "=" in _rule: + _old, _new = _rule.split("=", 1) + _HOST_REWRITES.append((_old.strip(), _new.strip())) + + +def _rewrite_url(url: str) -> str: + for old, new in _HOST_REWRITES: + url = url.replace(old, new) + return url + + +def _use_manual_redirects() -> bool: + return bool(_HOST_REWRITES) + + +def _follow_redirects(session: requests.Session, resp: requests.Response) -> requests.Response: + while resp.is_redirect or resp.status_code in (301, 302, 303, 307, 308): + location = resp.headers.get("Location", "") + if not location: + break + location = _rewrite_url(location) + if resp.status_code in (301, 302, 303): + resp = session.get(location, allow_redirects=False) + else: + resp = session.request(resp.request.method, location, allow_redirects=False) + return resp + + +def get_backstage_token(username: str, password: str) -> str: + """Run the OIDC browserless login flow and return a Backstage bearer token.""" + session = requests.Session() + + start_url = ( + f"{BACKSTAGE_BASE_URL}/api/auth/oidc/start" + f"?env=development&scope=openid+profile+email" + f"&origin={urllib.parse.quote(FRONTEND_URL)}&flow=popup" + ) + + if _use_manual_redirects(): + resp = session.get(start_url, allow_redirects=False) + resp = _follow_redirects(session, resp) + else: + resp = session.get(start_url, allow_redirects=True) + resp.raise_for_status() + + action_match = re.search(r'action="([^"]+)"', resp.text) + if not action_match: + raise RuntimeError(f"No Keycloak login form found at {resp.url}") + login_url = _rewrite_url(action_match.group(1).replace("&", "&")) + + login_data = {"username": username, "password": password, "credentialId": ""} + if _use_manual_redirects(): + login_resp = session.post(login_url, data=login_data, allow_redirects=False) + login_resp = _follow_redirects(session, login_resp) + else: + login_resp = session.post(login_url, data=login_data, allow_redirects=True) + login_resp.raise_for_status() + + auth_match = re.search(r"decodeURIComponent\('([^']+)'\)", login_resp.text) + if not auth_match: + raise RuntimeError(f"No auth response in handler/frame for {username}") + + auth_data = json.loads(urllib.parse.unquote(auth_match.group(1))) + if "error" in auth_data: + err = auth_data["error"] + message = err.get("message", err) if isinstance(err, dict) else err + raise RuntimeError(f"Auth error for {username}: {message}") + + token = auth_data.get("response", {}).get("backstageIdentity", {}).get("token", "") + if not token: + raise RuntimeError(f"No backstageIdentity token for {username}") + + return token + + +def login_users_from_csv(csv_path: str, max_retries: int = 4) -> list[tuple[str, str, str, str]]: + """Read user CSV and append tokens. Returns rows with token in column 4.""" + rows: list[list[str]] = [] + with open(csv_path, newline="", encoding="utf-8") as handle: + reader = csv.reader(handle) + for row in reader: + if not row or row[0].startswith("#") or row[0].lower() == "email": + continue + while len(row) < 4: + row.append("") + rows.append(row) + + updated: list[tuple[str, str, str, str]] = [] + for row in rows: + email, password, expected = row[0], row[1], row[2] + username = email.split("@")[0] + token = "" + last_error: Exception | None = None + + for attempt in range(1, max_retries + 1): + try: + print(f"Logging in {email} ...") + token = get_backstage_token(username, password) + break + except Exception as exc: + last_error = exc + print(f" attempt {attempt} failed: {exc}") + if attempt < max_retries: + time.sleep(1) + + if not token: + raise RuntimeError( + f"Login failed for {email} after {max_retries} attempts" + ) from last_error + + updated.append((email, password, expected, token)) + + with open(csv_path, "w", newline="", encoding="utf-8") as handle: + writer = csv.writer(handle) + writer.writerows(updated) + + return updated + + +def main() -> None: + parser = argparse.ArgumentParser(description="Obtain Backstage OIDC bearer tokens") + parser.add_argument( + "--csv", + default=os.path.join(os.path.dirname(__file__), "..", "userinfo.csv"), + help="CSV file: email,password,expected[,token]", + ) + parser.add_argument( + "--user", + help="Single Keycloak username (instead of CSV)", + ) + parser.add_argument("--password", default="test", help="Password for --user") + args = parser.parse_args() + + if args.user: + token = get_backstage_token(args.user, args.password) + print(token) + return + + login_users_from_csv(os.path.abspath(args.csv)) + print(f"Updated tokens in {args.csv}") + + +if __name__ == "__main__": + main() diff --git a/workspaces/rbac/plugins/rbac-backend/manual-tests/scripts/start-keycloak.sh b/workspaces/rbac/plugins/rbac-backend/manual-tests/scripts/start-keycloak.sh new file mode 100755 index 00000000000..7e5ef74bcde --- /dev/null +++ b/workspaces/rbac/plugins/rbac-backend/manual-tests/scripts/start-keycloak.sh @@ -0,0 +1,39 @@ +#!/usr/bin/env bash +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +REALM_FILE="${REALM_FILE:-$SCRIPT_DIR/../keycloak/backstage-realm.json}" +KEYCLOAK_IMAGE="${KEYCLOAK_IMAGE:-quay.io/keycloak/keycloak:22.0.1}" +KEYCLOAK_PORT="${KEYCLOAK_PORT:-8080}" + +if [[ ! -f "$REALM_FILE" ]]; then + echo "ERROR: Keycloak realm file not found: $REALM_FILE" >&2 + exit 1 +fi + +if command -v docker >/dev/null 2>&1 && docker info >/dev/null 2>&1; then + RUNTIME=docker +elif command -v podman >/dev/null 2>&1 && podman info >/dev/null 2>&1; then + RUNTIME=podman +else + echo "ERROR: Start Docker or Podman, then rerun this script." >&2 + exit 1 +fi + +SELINUX_SUFFIX="" +if [[ "$(uname -s)" = "Linux" ]] && command -v getenforce >/dev/null 2>&1; then + if [[ "$(getenforce)" != "Disabled" ]]; then + SELINUX_SUFFIX=":z" + fi +fi + +echo "Starting Keycloak on http://localhost:${KEYCLOAK_PORT} (realm: backstage)" +echo "Admin console: http://localhost:${KEYCLOAK_PORT}/admin (admin / admin)" +echo "Realm file: $REALM_FILE" + +exec "$RUNTIME" run --rm -p "${KEYCLOAK_PORT}:8080" \ + -e KEYCLOAK_ADMIN=admin \ + -e KEYCLOAK_ADMIN_PASSWORD=admin \ + -v "${REALM_FILE}:/opt/keycloak/data/import/backstage-realm.json${SELINUX_SUFFIX}" \ + "$KEYCLOAK_IMAGE" \ + start-dev --import-realm diff --git a/workspaces/rbac/plugins/rbac-backend/manual-tests/scripts/test-permissions.sh b/workspaces/rbac/plugins/rbac-backend/manual-tests/scripts/test-permissions.sh new file mode 100755 index 00000000000..4d40eb189fb --- /dev/null +++ b/workspaces/rbac/plugins/rbac-backend/manual-tests/scripts/test-permissions.sh @@ -0,0 +1,102 @@ +#!/usr/bin/env bash +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +CSV_FILE="${CSV_FILE:-$SCRIPT_DIR/../userinfo.csv}" +BASE_URL="${BASE_URL:-http://localhost:7007}" + +if [[ ! -f "$CSV_FILE" ]]; then + echo "ERROR: CSV not found: $CSV_FILE" >&2 + echo "Run: python3 $SCRIPT_DIR/login.py --csv $CSV_FILE" >&2 + exit 1 +fi + +python3 - "$CSV_FILE" "$BASE_URL" <<'PY' +import csv +import json +import sys +import urllib.error +import urllib.request + +csv_path, base_url = sys.argv[1:3] +payload = json.dumps( + { + "items": [ + { + "id": "manual-test", + "resourceRef": "component:default/artist-lookup", + "permission": { + "attributes": {"action": "read"}, + "name": "catalog.entity.read", + "type": "resource", + "resourceType": "catalog-entity", + }, + } + ] + } +).encode() + +failures = 0 +checked = 0 +auth_failures = 0 +auth_hint_shown = False +with open(csv_path, newline="", encoding="utf-8") as handle: + for row in csv.reader(handle): + if not row or row[0].startswith("#"): + continue + if row[0].lower() == "email": + continue + email = row[0] + expected = row[2] if len(row) > 2 else "" + token = row[3] if len(row) > 3 else "" + if not token or token == "token": + print(f"SKIP {email} (no token — run login.py first)") + continue + + request = urllib.request.Request( + f"{base_url}/api/permission/authorize", + data=payload, + headers={ + "Authorization": f"Bearer {token}", + "Content-Type": "application/json", + }, + method="POST", + ) + try: + with urllib.request.urlopen(request, timeout=15) as response: + body = json.load(response) + except urllib.error.HTTPError as exc: + if exc.code == 401: + auth_failures += 1 + if not auth_hint_shown: + auth_hint_shown = True + print( + "FAIL auth: tokens rejected (backend restarted or expired). " + "Re-run login.py against the running backend (from workspaces/rbac):\n" + " python3 plugins/rbac-backend/manual-tests/scripts/login.py " + "--csv plugins/rbac-backend/manual-tests/userinfo.csv\n" + ) + print(f"FAIL {email} HTTP 401: token verification failed") + else: + print(f"FAIL {email} HTTP {exc.code}: {exc.read().decode()}") + failures += 1 + continue + + result = body["items"][0]["result"] + checked += 1 + if result == expected: + print(f"PASS {email} expected={expected} got={result}") + else: + print(f"FAIL {email} expected={expected} got={result}") + failures += 1 + +if auth_failures: + print(f"{auth_failures} token(s) rejected — refresh with login.py before re-testing") + sys.exit(1) + +if failures: + print(f"{failures} of {checked} permission check(s) failed") + sys.exit(1) + +print(f"All {checked} permission checks passed.") +PY diff --git a/workspaces/rbac/plugins/rbac-backend/package.json b/workspaces/rbac/plugins/rbac-backend/package.json index 5d0d825d526..aff388ee333 100644 --- a/workspaces/rbac/plugins/rbac-backend/package.json +++ b/workspaces/rbac/plugins/rbac-backend/package.json @@ -20,8 +20,8 @@ ] }, "scripts": { - "start": "backstage-cli package start", - "start:alpha": "backstage-cli package start", + "start": "backstage-cli package start --config app-config.yaml", + "start:manual-tests": "backstage-cli package start --config app-config.yaml --config app-config.manual-tests.yaml", "build": "backstage-cli package build", "tsc": "tsc", "prettier:check": "prettier --ignore-unknown --check .", @@ -62,6 +62,7 @@ "@backstage/core-plugin-api": "^1.12.6", "@backstage/plugin-auth-backend": "^0.29.0", "@backstage/plugin-auth-backend-module-guest-provider": "^0.2.19", + "@backstage/plugin-auth-backend-module-oidc-provider": "^0.4.16", "@backstage/plugin-catalog-backend": "^3.7.0", "@backstage/plugin-catalog-backend-module-scaffolder-entity-model": "^0.2.20", "@backstage/plugin-catalog-node": "^2.2.1", diff --git a/workspaces/rbac/plugins/rbac-backend/src/plugin.test.ts b/workspaces/rbac/plugins/rbac-backend/src/plugin.test.ts new file mode 100644 index 00000000000..5a14cb230ed --- /dev/null +++ b/workspaces/rbac/plugins/rbac-backend/src/plugin.test.ts @@ -0,0 +1,73 @@ +/* + * Copyright 2026 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + mockCredentials, + mockServices, + startTestBackend, + type TestBackend, +} from '@backstage/backend-test-utils'; +import permissionBackend from '@backstage/plugin-permission-backend'; +import request from 'supertest'; + +import { rbacPlugin } from './plugin'; + +jest.setTimeout(120_000); + +const TEST_CONFIG = { + permission: { + enabled: true, + rbac: { + admin: { + users: [{ name: 'user:default/admin' }], + }, + }, + }, + backend: { + database: { + client: 'better-sqlite3', + connection: ':memory:', + }, + }, +}; + +describe('rbacPlugin', () => { + let backend: TestBackend; + + beforeEach(async () => { + backend = await startTestBackend({ + features: [ + permissionBackend, + rbacPlugin, + mockServices.rootConfig.factory({ data: TEST_CONFIG }), + mockServices.httpAuth.factory({ + defaultCredentials: mockCredentials.service(), + }), + ], + }); + }); + + afterEach(async () => { + await backend?.stop(); + }); + + it('registers RBAC routes on the permission plugin', async () => { + const response = await request(backend.server).get('/api/permission/roles'); + + expect(response.status).toBe(200); + expect(Array.isArray(response.body)).toBe(true); + }); +}); diff --git a/workspaces/rbac/plugins/rbac-backend/src/policies/permission-policy.hierarchy.test.ts b/workspaces/rbac/plugins/rbac-backend/src/policies/permission-policy.hierarchy.test.ts index db5f3d16834..c401780ba84 100644 --- a/workspaces/rbac/plugins/rbac-backend/src/policies/permission-policy.hierarchy.test.ts +++ b/workspaces/rbac/plugins/rbac-backend/src/policies/permission-policy.hierarchy.test.ts @@ -65,7 +65,7 @@ type PermissionAction = 'create' | 'read' | 'update' | 'delete'; /** * Group, user, role, and permission information can be found under `__fixtures__/data/hierarchy/` - * More information can be found at `examples/manual-tests/rbac` at the root of the workspace + * More information can be found at `manual-tests/rbac` in the rbac-backend plugin * Included is a txt file with charts for the hierarchy levels for visualization */ describe('Policy checks for users and groups', () => { diff --git a/workspaces/rbac/plugins/rbac-backend/src/policies/permission-policy.test.ts b/workspaces/rbac/plugins/rbac-backend/src/policies/permission-policy.test.ts index f684e48f720..e054bc67f56 100644 --- a/workspaces/rbac/plugins/rbac-backend/src/policies/permission-policy.test.ts +++ b/workspaces/rbac/plugins/rbac-backend/src/policies/permission-policy.test.ts @@ -1123,6 +1123,46 @@ describe('RBACPermissionPolicy Tests', () => { ); }); + it('should deny super user access when the user belongs only to a sub-group of a configured super user group', async () => { + // Catalog hierarchy (see __fixtures__/data/hierarchy/groups.ts): + // data_read_admin.parent = data_parent_admin + // user:default/mike is a direct member of data_read_admin + // + // superUsers checks ownershipEntityRefs for an exact match only — it does + // not walk the catalog group tree. A resolver therefore returns the child + // group ref, not the parent configured as super user. + const superUsersConfig = new Array<{ name: string }>(); + superUsersConfig.push({ name: 'group:default/data_parent_admin' }); + + const config = newConfig(csvPermFile, admins, superUsersConfig); + const adapter = await newAdapter(config); + const enfDelegateForTest = await newEnforcerDelegate(adapter, config); + const policyForTest = await newPermissionPolicy( + config, + enfDelegateForTest, + roleMetadataStorageTest, + ); + + const decision = await policyForTest.handle( + newPolicyQueryWithResourcePermission( + 'catalog.entity.delete', + 'catalog-entity', + 'delete', + ), + newPolicyQueryUser('user:default/mike', [ + 'group:default/data_read_admin', + ]), + ); + expect(decision.result).toBe(AuthorizeResult.DENY); + expectAuditorLogForPermission( + 'user:default/mike', + 'catalog.entity.delete', + 'catalog-entity', + 'delete', + AuthorizeResult.DENY, + ); + }); + it('should remove users that are no longer in the config file', async () => { const enfRole = await enfDelegate.getFilteredGroupingPolicy(1, adminRole); const enfPermission = await enfDelegate.getFilteredPolicy(0, adminRole); diff --git a/workspaces/rbac/plugins/rbac-common/CONTRIBUTING.md b/workspaces/rbac/plugins/rbac-common/CONTRIBUTING.md new file mode 100644 index 00000000000..cbb8b7de39f --- /dev/null +++ b/workspaces/rbac/plugins/rbac-common/CONTRIBUTING.md @@ -0,0 +1,36 @@ +# Contributing — RBAC common library + +Developer guide for `@backstage-community/plugin-rbac-common`. For package overview, see [README.md](./README.md). + +## Prerequisites + +- Node.js **22+** +- Yarn (community-plugins monorepo lockfile) + +## Development + +This package is a shared types and permission-constants library. There is no standalone backend or frontend `dev/` harness. + +Work on it from `workspaces/rbac`: + +```bash +yarn workspace @backstage-community/plugin-rbac-common test +yarn workspace @backstage-community/plugin-rbac-common lint:check +yarn tsc +``` + +## Contract tests + +`src/contract.test.ts` locks the public API surface used by `@backstage-community/plugin-rbac-backend` and `@backstage-community/plugin-rbac`: + +- Policy-entity permission names and `policyEntityPermissions` ordering +- `RESOURCE_TYPE_POLICY_ENTITY` +- `isResourcedPolicy`, `isValidPermissionAction`, `toPermissionAction` +- `ConditionalAliases` and `UnauthorizedError` + +Run contract tests after bumps to `@backstage/plugin-permission-common` or when changing exported symbols. + +## Related packages + +- [RBAC backend plugin](../rbac-backend/CONTRIBUTING.md) +- [RBAC frontend plugin](../rbac/CONTRIBUTING.md) diff --git a/workspaces/rbac/plugins/rbac-common/src/contract.test.ts b/workspaces/rbac/plugins/rbac-common/src/contract.test.ts new file mode 100644 index 00000000000..814739c5bb4 --- /dev/null +++ b/workspaces/rbac/plugins/rbac-common/src/contract.test.ts @@ -0,0 +1,88 @@ +/* + * Copyright 2026 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + ConditionalAliases, + isResourcedPolicy, + isValidPermissionAction, + PermissionActionValues, + policyEntityCreatePermission, + policyEntityDeletePermission, + policyEntityPermissions, + policyEntityReadPermission, + policyEntityUpdatePermission, + RESOURCE_TYPE_POLICY_ENTITY, + toPermissionAction, + UnauthorizedError, +} from './index'; + +describe('rbac-common public contract', () => { + it('exports stable policy-entity permission names', () => { + expect(policyEntityReadPermission.name).toBe('policy.entity.read'); + expect(policyEntityCreatePermission.name).toBe('policy.entity.create'); + expect(policyEntityUpdatePermission.name).toBe('policy.entity.update'); + expect(policyEntityDeletePermission.name).toBe('policy.entity.delete'); + expect(policyEntityPermissions).toEqual([ + policyEntityReadPermission, + policyEntityCreatePermission, + policyEntityDeletePermission, + policyEntityUpdatePermission, + ]); + }); + + it('keeps the policy-entity resource type stable', () => { + expect(RESOURCE_TYPE_POLICY_ENTITY).toBe('policy-entity'); + expect(policyEntityReadPermission.resourceType).toBe( + RESOURCE_TYPE_POLICY_ENTITY, + ); + }); + + it('discriminates resourced policies', () => { + expect( + isResourcedPolicy({ + name: 'catalog.entity.read', + policy: 'read', + resourceType: 'catalog-entity', + }), + ).toBe(true); + expect( + isResourcedPolicy({ + name: 'policy.entity.read', + policy: 'read', + }), + ).toBe(false); + }); + + it('validates permission actions and maps undefined to use', () => { + expect(PermissionActionValues).toEqual([ + 'create', + 'read', + 'update', + 'delete', + 'use', + ]); + expect(isValidPermissionAction('read')).toBe(true); + expect(isValidPermissionAction('invalid')).toBe(false); + expect(toPermissionAction({})).toBe('use'); + expect(toPermissionAction({ action: 'delete' })).toBe('delete'); + }); + + it('exports conditional alias constants and UnauthorizedError', () => { + expect(ConditionalAliases.CURRENT_USER).toBe('currentUser'); + expect(ConditionalAliases.OWNER_REFS).toBe('ownerRefs'); + expect(new UnauthorizedError().message).toBe('Unauthorized'); + }); +}); diff --git a/workspaces/rbac/plugins/rbac/CONTRIBUTING.md b/workspaces/rbac/plugins/rbac/CONTRIBUTING.md new file mode 100644 index 00000000000..c65ff238f09 --- /dev/null +++ b/workspaces/rbac/plugins/rbac/CONTRIBUTING.md @@ -0,0 +1,49 @@ +# Contributing — RBAC frontend plugin + +Developer guide for `@backstage-community/plugin-rbac`. For administrator install steps, see [README.md](./README.md). + +## Prerequisites + +- Node.js **22+** +- Yarn (community-plugins monorepo lockfile) + +## Development harness + +Start the frontend plugin in isolation (mock backend): + +```bash +yarn workspace @backstage-community/plugin-rbac start:mock +``` + +Alpha API entrypoint: + +```bash +yarn workspace @backstage-community/plugin-rbac start:alpha:mock +``` + +This is the default frontend workflow. Playwright UI tests also use `start:mock` (no live backend). + +For backend/API work, use the [RBAC backend dev harness](../rbac-backend/CONTRIBUTING.md) and `curl`/scripts — not a paired frontend+backend repo start. + +## Validation commands + +From `workspaces/rbac`: + +```bash +yarn workspace @backstage-community/plugin-rbac test +yarn workspace @backstage-community/plugin-rbac lint:check +yarn workspace @backstage-community/plugin-rbac ui-test +yarn tsc +``` + +## Optional manual smoke checklist + +1. `yarn workspace @backstage-community/plugin-rbac start:mock` — confirm RBAC page renders with mock data. +2. For real API behavior, use the [backend manual-tests flow](../rbac-backend/manual-tests/README.md) or backend harness + `curl` (see [backend CONTRIBUTING](../rbac-backend/CONTRIBUTING.md)). + +REST and policy administration APIs: [rbac-backend docs/apis.md](../rbac-backend/docs/apis.md). + +## Related packages + +- [RBAC backend plugin](../rbac-backend/CONTRIBUTING.md) +- [RBAC common library](../rbac-common/CONTRIBUTING.md) diff --git a/workspaces/rbac/plugins/rbac/README.md b/workspaces/rbac/plugins/rbac/README.md index 1adb9e2d612..36c5e68bcb5 100644 --- a/workspaces/rbac/plugins/rbac/README.md +++ b/workspaces/rbac/plugins/rbac/README.md @@ -2,6 +2,8 @@ The RBAC UI plugin offers a streamlined user interface for effectively managing permissions in your Backstage instance. It allows you to assign permissions to users and groups, empowering them to view, create, modify and delete Roles, provided they have the necessary permissions. +For local development and test commands, see [CONTRIBUTING.md](./CONTRIBUTING.md). + ## For administrators ### Installation diff --git a/workspaces/rbac/yarn.lock b/workspaces/rbac/yarn.lock index b8fecdf51f7..d7521224f67 100644 --- a/workspaces/rbac/yarn.lock +++ b/workspaces/rbac/yarn.lock @@ -1789,6 +1789,7 @@ __metadata: "@backstage/errors": "npm:^1.3.1" "@backstage/plugin-auth-backend": "npm:^0.29.0" "@backstage/plugin-auth-backend-module-guest-provider": "npm:^0.2.19" + "@backstage/plugin-auth-backend-module-oidc-provider": "npm:^0.4.16" "@backstage/plugin-catalog-backend": "npm:^3.7.0" "@backstage/plugin-catalog-backend-module-scaffolder-entity-model": "npm:^0.2.20" "@backstage/plugin-catalog-node": "npm:^2.2.1" @@ -2050,25 +2051,24 @@ __metadata: languageName: node linkType: hard -"@backstage/backend-plugin-api@npm:^1.9.1": - version: 1.9.1 - resolution: "@backstage/backend-plugin-api@npm:1.9.1" +"@backstage/backend-plugin-api@npm:^1.9.1, @backstage/backend-plugin-api@npm:^1.9.2": + version: 1.9.2 + resolution: "@backstage/backend-plugin-api@npm:1.9.2" dependencies: "@backstage/cli-common": "npm:^0.2.2" "@backstage/config": "npm:^1.3.8" "@backstage/errors": "npm:^1.3.1" - "@backstage/plugin-auth-node": "npm:^0.7.1" + "@backstage/plugin-auth-node": "npm:^0.7.2" "@backstage/plugin-permission-common": "npm:^0.9.9" - "@backstage/plugin-permission-node": "npm:^0.11.0" + "@backstage/plugin-permission-node": "npm:^0.11.1" "@backstage/types": "npm:^1.2.2" "@types/express": "npm:^4.17.6" "@types/json-schema": "npm:^7.0.6" "@types/luxon": "npm:^3.0.0" - json-schema: "npm:^0.4.0" knex: "npm:^3.0.0" luxon: "npm:^3.0.0" zod: "npm:^3.25.76 || ^4.0.0" - checksum: 10/deb919cbb1a8dbcd57c1ae8f37531db433422bdefd4f46d4bce21f0198cca33267223d296b8b6052d8cf9cb1e77f4b5ba75c1eecde159f4ca261e41d044b48dc + checksum: 10/6592ae8fd1c4f23708b64506a90995a561347751fae4240bd6f1d2607ee8175163d3b03182297a647563875b7e3165466c043e930c9818805f7aa291a4b8c1b8 languageName: node linkType: hard @@ -2116,17 +2116,18 @@ __metadata: languageName: node linkType: hard -"@backstage/catalog-client@npm:^1.15.1": - version: 1.15.1 - resolution: "@backstage/catalog-client@npm:1.15.1" +"@backstage/catalog-client@npm:^1.15.1, @backstage/catalog-client@npm:^1.16.0": + version: 1.16.0 + resolution: "@backstage/catalog-client@npm:1.16.0" dependencies: "@backstage/catalog-model": "npm:^1.9.0" "@backstage/errors": "npm:^1.3.1" "@backstage/filter-predicates": "npm:^0.1.3" + "@backstage/plugin-catalog-common": "npm:^1.1.10" cross-fetch: "npm:^4.0.0" lodash: "npm:^4.17.21" uri-template: "npm:^2.0.0" - checksum: 10/6d319a45ed21463cc2ebd9ac26ab3e2635389d30d32c6b13a5ebbdf0ad06dc8aa9100071013f866e008426ee430152dc6918eb0a3ebb10b5380e324ab5a62c82 + checksum: 10/2348646d6cdad5ff4c3df9dd909aa0e0f886fb652d35b4f78987c551a0c95a882b2641c3b823c5635c66a03d53690ba16675495eba67f7ec3cdbb0a9cc64133c languageName: node linkType: hard @@ -3034,16 +3035,33 @@ __metadata: languageName: node linkType: hard -"@backstage/plugin-auth-backend@npm:^0.29.0": - version: 0.29.0 - resolution: "@backstage/plugin-auth-backend@npm:0.29.0" +"@backstage/plugin-auth-backend-module-oidc-provider@npm:^0.4.16": + version: 0.4.17 + resolution: "@backstage/plugin-auth-backend-module-oidc-provider@npm:0.4.17" dependencies: - "@backstage/backend-plugin-api": "npm:^1.9.1" + "@backstage/backend-plugin-api": "npm:^1.9.2" + "@backstage/config": "npm:^1.3.8" + "@backstage/plugin-auth-backend": "npm:^0.29.1" + "@backstage/plugin-auth-node": "npm:^0.7.2" + "@backstage/types": "npm:^1.2.2" + express: "npm:^4.22.0" + openid-client: "npm:^5.5.0" + passport: "npm:^0.7.0" + zod: "npm:^3.25.76 || ^4.0.0" + checksum: 10/3d14a74d1cd493463751817e687366069314de4bb809d7d3e332eb771a1cad68f655dcfdc87b872f9f3066b3ddab71736624cb079156cc66aaf54ebd56935448 + languageName: node + linkType: hard + +"@backstage/plugin-auth-backend@npm:^0.29.0, @backstage/plugin-auth-backend@npm:^0.29.1": + version: 0.29.1 + resolution: "@backstage/plugin-auth-backend@npm:0.29.1" + dependencies: + "@backstage/backend-plugin-api": "npm:^1.9.2" "@backstage/catalog-model": "npm:^1.9.0" "@backstage/config": "npm:^1.3.8" "@backstage/errors": "npm:^1.3.1" - "@backstage/plugin-auth-node": "npm:^0.7.1" - "@backstage/plugin-catalog-node": "npm:^2.2.1" + "@backstage/plugin-auth-node": "npm:^0.7.2" + "@backstage/plugin-catalog-node": "npm:^2.2.2" "@backstage/types": "npm:^1.2.2" "@google-cloud/firestore": "npm:^7.0.0" connect-session-knex: "npm:^4.0.0" @@ -3061,16 +3079,16 @@ __metadata: passport: "npm:^0.7.0" zod: "npm:^3.25.76 || ^4.0.0" zod-validation-error: "npm:^5.0.0" - checksum: 10/77e973baa29c48cc1b3e6b62ceb027d64db91db7995e55da5882159de5142e864603d813e45ed2d2d4ddc4f1687f61dbdf72056e0d549f1039a6bec2cea9cfcd + checksum: 10/498348539b08a1154073530ab9d5cb1e1face01c0f19c63e92ba6ee4654aca24c9c1d3c714ef727d0207a7a45e67b419483ddee8e67367601b54d3ed11a1136c languageName: node linkType: hard -"@backstage/plugin-auth-node@npm:^0.7.1": - version: 0.7.1 - resolution: "@backstage/plugin-auth-node@npm:0.7.1" +"@backstage/plugin-auth-node@npm:^0.7.1, @backstage/plugin-auth-node@npm:^0.7.2": + version: 0.7.2 + resolution: "@backstage/plugin-auth-node@npm:0.7.2" dependencies: - "@backstage/backend-plugin-api": "npm:^1.9.1" - "@backstage/catalog-client": "npm:^1.15.1" + "@backstage/backend-plugin-api": "npm:^1.9.2" + "@backstage/catalog-client": "npm:^1.16.0" "@backstage/catalog-model": "npm:^1.9.0" "@backstage/config": "npm:^1.3.8" "@backstage/errors": "npm:^1.3.1" @@ -3084,7 +3102,7 @@ __metadata: zod: "npm:^3.25.76 || ^4.0.0" zod-to-json-schema: "npm:^3.25.1" zod-validation-error: "npm:^4.0.2" - checksum: 10/e799d61a7b213d76a69d1214f26cfa36e7cbb528a65fbdc25b638a09cf411a9a2bee97ea7bd9665a2ca2ff124d7edec71780a5b1be8f0b8e6a4de492a5f88584 + checksum: 10/063c1b934a4f91c17b30f148a77d3b11f19d83273843b083cc8a8c272533fd33f6c6964a61d254ea3cba29e0200b505398d9595f3fb6ff5eb1e54dcdb4b0fdc0 languageName: node linkType: hard @@ -3154,27 +3172,27 @@ __metadata: languageName: node linkType: hard -"@backstage/plugin-catalog-node@npm:^2.2.1": - version: 2.2.1 - resolution: "@backstage/plugin-catalog-node@npm:2.2.1" +"@backstage/plugin-catalog-node@npm:^2.2.1, @backstage/plugin-catalog-node@npm:^2.2.2": + version: 2.2.2 + resolution: "@backstage/plugin-catalog-node@npm:2.2.2" dependencies: - "@backstage/backend-plugin-api": "npm:^1.9.1" - "@backstage/catalog-client": "npm:^1.15.1" + "@backstage/backend-plugin-api": "npm:^1.9.2" + "@backstage/catalog-client": "npm:^1.16.0" "@backstage/catalog-model": "npm:^1.9.0" "@backstage/errors": "npm:^1.3.1" "@backstage/plugin-catalog-common": "npm:^1.1.10" "@backstage/plugin-permission-common": "npm:^0.9.9" - "@backstage/plugin-permission-node": "npm:^0.11.0" + "@backstage/plugin-permission-node": "npm:^0.11.1" "@backstage/types": "npm:^1.2.2" "@opentelemetry/api": "npm:^1.9.0" lodash: "npm:^4.17.21" yaml: "npm:^2.0.0" peerDependencies: - "@backstage/backend-test-utils": ^1.11.3 + "@backstage/backend-test-utils": ^1.11.4 peerDependenciesMeta: "@backstage/backend-test-utils": optional: true - checksum: 10/71faa299520540260a58a5b582fd7eeb56109a8522e411b1341112f20cf6cb9d220826d597e223d77121697949cfad6cbaef7be4e126dd35b5e7cd400bdb35fb + checksum: 10/80db02b656a9e099e75ea6d437a4bf72e7e0f109b8fc44c33fd05448b9aa0b788e96a7d8b6dc4979b2c38e08dec266ea86f4ea84e9674dbf4e15db33133d697d languageName: node linkType: hard @@ -3321,11 +3339,11 @@ __metadata: languageName: node linkType: hard -"@backstage/plugin-permission-node@npm:^0.11.0": - version: 0.11.0 - resolution: "@backstage/plugin-permission-node@npm:0.11.0" +"@backstage/plugin-permission-node@npm:^0.11.0, @backstage/plugin-permission-node@npm:^0.11.1": + version: 0.11.1 + resolution: "@backstage/plugin-permission-node@npm:0.11.1" dependencies: - "@backstage/backend-plugin-api": "npm:^1.9.1" + "@backstage/backend-plugin-api": "npm:^1.9.2" "@backstage/config": "npm:^1.3.8" "@backstage/errors": "npm:^1.3.1" "@backstage/plugin-permission-common": "npm:^0.9.9" @@ -3334,7 +3352,7 @@ __metadata: express-promise-router: "npm:^4.1.0" zod: "npm:^3.25.76 || ^4.0.0" zod-to-json-schema: "npm:^3.25.1" - checksum: 10/4376f5f41c31739e85ff4285fee6c361f04649a68eadadaa8e556a698909e48ad7f8c8eb6606a6ba4d721114bb217762c8dcc6ee95071021998f668a5bb30224 + checksum: 10/2b8c3e280aef147e0df147c7cd5508983462501f8fd9472a7ca82afe6b7dc6e2432e7e48e64e2446f190368a07e13d05000414836cf5aeb68e3aa813ecb1c715 languageName: node linkType: hard @@ -18646,6 +18664,13 @@ __metadata: languageName: node linkType: hard +"jose@npm:^4.15.9": + version: 4.15.9 + resolution: "jose@npm:4.15.9" + checksum: 10/256234b6f85cdc080b1331f2d475bd58c8ccf459cb20f70ac5e4200b271bce10002b1c2f8e5b96dd975d83065ae5a586d52cdf89d28471d56de5d297992f9905 + languageName: node + linkType: hard + "jose@npm:^5.0.0": version: 5.9.6 resolution: "jose@npm:5.9.6" @@ -21525,6 +21550,13 @@ __metadata: languageName: node linkType: hard +"object-hash@npm:^2.2.0": + version: 2.2.0 + resolution: "object-hash@npm:2.2.0" + checksum: 10/dee06b6271bf5769ae5f1a7386fdd52c1f18aae9fcb0b8d4bb1232f2d743d06cb5b662be42378b60a1c11829f96f3f86834a16bbaa57a085763295fff8b93e27 + languageName: node + linkType: hard + "object-hash@npm:^3.0.0": version: 3.0.0 resolution: "object-hash@npm:3.0.0" @@ -21623,6 +21655,13 @@ __metadata: languageName: node linkType: hard +"oidc-token-hash@npm:^5.0.3": + version: 5.2.0 + resolution: "oidc-token-hash@npm:5.2.0" + checksum: 10/4eb991b454722d051ac79c98ab08c5ec9e40ae98835797690f1419dd396d680dbbdb17dfb700c4a1b6be472b795fb9b494c15c6af9b026d576f3c9482b43c03f + languageName: node + linkType: hard + "on-finished@npm:^2.4.1, on-finished@npm:~2.4.1": version: 2.4.1 resolution: "on-finished@npm:2.4.1" @@ -21741,6 +21780,18 @@ __metadata: languageName: node linkType: hard +"openid-client@npm:^5.5.0": + version: 5.7.1 + resolution: "openid-client@npm:5.7.1" + dependencies: + jose: "npm:^4.15.9" + lru-cache: "npm:^6.0.0" + object-hash: "npm:^2.2.0" + oidc-token-hash: "npm:^5.0.3" + checksum: 10/188a875ab1824010bde85b6755f31401d4b0bcf6edffe5f149b1e67fc886c692658121c0c3cc04db84be33138c0e9e2e7d829e6997adf489f23a32ea7e745151 + languageName: node + linkType: hard + "oppa@npm:^0.4.0": version: 0.4.0 resolution: "oppa@npm:0.4.0"