From f688eba19521a8c7e39d8bb800059f9338d5ede6 Mon Sep 17 00:00:00 2001 From: Johnny Huynh <27847622+johnnyhuy@users.noreply.github.com> Date: Sun, 8 Nov 2026 14:12:40 +0900 Subject: [PATCH] feat: define guest demo baseline with readers group and read-only tests --- app-config.yaml | 3 +- docs/demo-baseline.md | 34 +++++++++++++++++++ examples/groups/admins.yaml | 11 ++++++ examples/groups/developers.yaml | 11 ++++++ examples/groups/readers.yaml | 11 ++++++ examples/users/guest.yaml | 10 ++++++ mkdocs.yaml | 1 + .../src/module.test.ts | 32 +++++++++++++++++ 8 files changed, 112 insertions(+), 1 deletion(-) create mode 100644 docs/demo-baseline.md create mode 100644 examples/groups/admins.yaml create mode 100644 examples/groups/developers.yaml create mode 100644 examples/groups/readers.yaml create mode 100644 examples/users/guest.yaml diff --git a/app-config.yaml b/app-config.yaml index 9ce488a..8dbb0d5 100644 --- a/app-config.yaml +++ b/app-config.yaml @@ -55,7 +55,8 @@ auth: # see https://backstage.io/docs/auth/ to learn about auth providers providers: # See https://backstage.io/docs/auth/guest/provider - guest: {} + guest: + userEntityRef: user:default/guest scaffolder: # see https://backstage.io/docs/features/software-templates/configuration for software template options diff --git a/docs/demo-baseline.md b/docs/demo-baseline.md new file mode 100644 index 0000000..9c09aa4 --- /dev/null +++ b/docs/demo-baseline.md @@ -0,0 +1,34 @@ +# Demo baseline + +This page states what a visitor gets in the public demo. It describes the configuration in this repository, not a wishlist. + +## Sign-in options + +The sign-in page offers three providers, wired in `packages/app/src/App.tsx`. + +- Guest. No account needed. This is the demo path. +- GitHub. Needs the production GitHub OAuth and app credentials. +- GitLab. Needs the production GitLab token. + +## What a guest can do + +Guest signs in as `user:default/guest`. The entity is defined in `examples/users/guest.yaml` and belongs to the `readers` group in `examples/groups/readers.yaml`. + +With `permission.enabled: true`, the policy in `plugins/permission-backend-module-default/src/module.ts` gives `readers` read access only. A guest can browse the catalog, read TechDocs, use search, open the API explorer, view the catalog graph and browse the showcase and software templates. + +## What a guest cannot do + +A guest cannot create, update or delete catalog entities, cannot run scaffolder templates, and cannot cancel scaffolder tasks. Those actions need the `developers` group in `examples/groups/developers.yaml`. The `admins` group in `examples/groups/admins.yaml` exists for administrative membership and does not on its own grant extra policy rules today. + +Template cards are safe for a guest to open and read. Submitting one will be denied by the permission policy. + +## Configuration behind this + +- `app-config.yaml` sets the guest `userEntityRef` to `user:default/guest` for local development. +- `app-config.production.yaml` sets the same reference and adds `dangerouslyAllowOutsideDevelopment: true`, which is required for guest access outside development. Treat a public deployment as read-only for visitors on that basis. +- Local development uses an in-memory SQLite database, so catalog changes made locally do not survive a restart. That is fine for trying the portal and is not a shared demo database. + +## Known limits + +- Signed-in GitHub and GitLab users only get write access if their catalog user belongs to `developers`. Membership is managed in the catalog entities, not in the OAuth provider. +- The demo data under `examples/` is sample data. It proves the portal surfaces work and is not real service ownership data. diff --git a/examples/groups/admins.yaml b/examples/groups/admins.yaml new file mode 100644 index 0000000..4575683 --- /dev/null +++ b/examples/groups/admins.yaml @@ -0,0 +1,11 @@ +apiVersion: backstage.io/v1alpha1 +kind: Group +metadata: + name: admins + description: Administrative access group +spec: + type: team + profile: + displayName: Admins + email: admins@echohello.example.com + children: [] diff --git a/examples/groups/developers.yaml b/examples/groups/developers.yaml new file mode 100644 index 0000000..8a7c93d --- /dev/null +++ b/examples/groups/developers.yaml @@ -0,0 +1,11 @@ +apiVersion: backstage.io/v1alpha1 +kind: Group +metadata: + name: developers + description: Create, update and delete access group for signed-in developers +spec: + type: team + profile: + displayName: Developers + email: developers@echohello.example.com + children: [] diff --git a/examples/groups/readers.yaml b/examples/groups/readers.yaml new file mode 100644 index 0000000..2b832a1 --- /dev/null +++ b/examples/groups/readers.yaml @@ -0,0 +1,11 @@ +apiVersion: backstage.io/v1alpha1 +kind: Group +metadata: + name: readers + description: Read-only access group used by the guest demo user +spec: + type: team + profile: + displayName: Readers + email: readers@echohello.example.com + children: [] diff --git a/examples/users/guest.yaml b/examples/users/guest.yaml new file mode 100644 index 0000000..8b7a511 --- /dev/null +++ b/examples/users/guest.yaml @@ -0,0 +1,10 @@ +apiVersion: backstage.io/v1alpha1 +kind: User +metadata: + name: guest + description: Guest demo user with read-only access +spec: + profile: + displayName: Guest + email: guest@echohello.example.com + memberOf: [readers] diff --git a/mkdocs.yaml b/mkdocs.yaml index 3b514e5..ba37c46 100644 --- a/mkdocs.yaml +++ b/mkdocs.yaml @@ -2,6 +2,7 @@ site_name: echoHello Backstage nav: - index.md + - demo-baseline.md - auth.md - customization.md - tracking.md diff --git a/plugins/permission-backend-module-default/src/module.test.ts b/plugins/permission-backend-module-default/src/module.test.ts index 58f9353..4a58d1e 100644 --- a/plugins/permission-backend-module-default/src/module.test.ts +++ b/plugins/permission-backend-module-default/src/module.test.ts @@ -91,6 +91,38 @@ describe('policy', () => { expect(decision.result).toBe(AuthorizeResult.DENY); }); + test('allows the guest demo user to read', async () => { + const request = createRequest('example.permission', { + action: 'read', + }); + const user = createUser('guest', ['readers']); + + const decision = await policy.handle(request, user); + + expect(decision.result).toBe(AuthorizeResult.ALLOW); + }); + + test.each(['create', 'update', 'delete'] as const)( + 'denies the guest demo user the %s action', + async action => { + const request = createRequest('example.permission', { action }); + const user = createUser('guest', ['readers']); + + const decision = await policy.handle(request, user); + + expect(decision.result).toBe(AuthorizeResult.DENY); + }, + ); + + test('denies the guest demo user scaffolder actions', async () => { + const request = createRequest('scaffolder.action.execute'); + const user = createUser('guest', ['readers']); + + const decision = await policy.handle(request, user); + + expect(decision.result).toBe(AuthorizeResult.DENY); + }); + test('denies permission for unknown actions', async () => { const request = createRequest('example.permission', { action: undefined,