Skip to content

feat(local): one-command local manual-test kit for AS, DC and TP (#254) - #259

Merged
dfcoffin merged 11 commits into
mainfrom
feat/254-local-test-kit
Nov 15, 2026
Merged

dfcoffin merged 11 commits into
mainfrom
feat/254-local-test-kit

Conversation

@dfcoffin

Copy link
Copy Markdown
Contributor

Summary

Closes #254. One command starts the AS, DC and TP on the local profile; one command checks them; a runbook lists every URL, login, client and curl recipe.

Merge after #257 and #258. This branch contains their commits (the local profile relies on #257's demo-seeding flag, and giving third_party a client_credentials grant is only safe with #258's per-grant scope rule). Once they merge, this PR's diff shrinks to the #254 commits.

Changes

AS

  • third_party demo client: also client_credentials + ThirdParty_Admin_Access, which is the ESPI client access token for the Authorization (and later Bulk, DC: implement ESPI Batch/Bulk API — Third Party client access token only, scoped to its own Authorizations #256) APIs. fix(authserver): restrict each OAuth grant to its own kind of scope (#251) #258 makes sure customer FB scopes can't be obtained that way.
  • Its redirect URIs are configurable (espi.authserver.seed.third-party.redirect-uris; the default is unchanged). local lists the TP's real Spring OAuth2 callbacks (:8082/ThirdParty/login/oauth2/code/datacustodian-{monthly,daily}).
  • application-local.yml (DEV ONLY), all overridable by environment variables:
    • bootstrap admin admin / admin (bcrypt hash; a passkey is still enrolled at first login);
    • espi_admin and espi_registrar dev secrets.
  • SecurityIntegrationTest: the "client not registered for the grant" case uses the per-test authorization_code-only client instead of third_party.

TP application-local.yml: the registrations match the AS seed.

  • tp-secret everywhere.
  • The monthly/daily scopes are exactly the AS-registered ESPI scopes; openid/profile are dropped.
  • datacustodian-admin becomes client_credentials with ThirdParty_Admin_Access.

Scripts in scripts/local/, Bash and PowerShell versions:

  • start-all builds once, starts all three apps, waits for /actuator/health and prints the URLs; logs go to .local-run/.
  • stop-all stops the apps by port.
  • smoke makes 25 checks: health; four client_credentials tokens; client_credentials refused for an FB scope; introspection; all ten DC feeds; the Authorization feed with the TP client access token; 401 without a token; admin actuator on each app.

Docs and repo

  • docs/LOCAL_TESTING.md is the runbook.
  • .gitattributes keeps *.sh at LF.
  • .local-run/ is git-ignored.

Verification (this branch, Windows 11)

Not in this PR

🤖 Generated with Claude Code

dfcoffin and others added 11 commits November 14, 2026 18:30
The data_custodian_admin, third_party, third_party_admin and datacustodian
clients are development fixtures. They are now seeded only when
espi.authserver.seed.demo-clients=true, which the local, dev-mysql,
dev-postgresql and testcontainers profiles set. Other profiles start with
none of them, matching the production onboarding model: Third Parties
register through RFC 7591.

- datacustodian (introspection) and data_custodian_admin can instead be
  provisioned from ESPI_DATACUSTODIAN_CLIENT_SECRET and
  ESPI_DATACUSTODIAN_ADMIN_CLIENT_SECRET, stored hashed.
- With demo seeding off, an existing demo row that still accepts its
  development secret gets a random secret at startup.
- DEPLOYMENT_GUIDE lists the new settings.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
dev-mysql is the default profile in application.yml, so a deployment
that does not set SPRING_PROFILES_ACTIVE would run with demo seeding on.
Startup now fails when espi.authserver.seed.demo-clients=true and the
issuer host is not localhost, 127.0.0.1 or ::1.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…251)

Spring Authorization Server only checks that a requested scope is
registered for the client, whatever the grant. ESPI keeps customer data
and administrative tokens apart, so:

- client_credentials cannot be granted an ESPI customer data scope
  (any FB=... other than the FB=36_40 registration-token renewal
  scope);
- authorization_code cannot be granted an administrative scope
  (ThirdParty_Admin_Access, DataCustodian_Admin_Access, FB=36_40,
  authserver.admin, client.create). The client receives invalid_scope
  at its redirect URI, as Spring does for its own scope check.

EspiGrantScopePolicy runs after Spring's default validators on the token
and authorization endpoints. Tested with unit tests and a Docker-backed
SecurityIntegrationTest case using a client registered for both grants.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Data Custodian parses scopes tolerantly (any segment order, trimmed,
case-insensitive FB key), so a prefix check on "FB=" could be bypassed
with a different spelling, and Upload_Admin_Access was missing from the
administrative list. Both rules now fail closed:

- client_credentials: only the exact administrative scope strings,
  including Upload_Admin_Access;
- authorization_code: only openid/profile, or a scope that parses like
  EspiScope to Function Blocks other than the reserved 36 and 40.
  Unparseable or unknown scopes are rejected.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…scripts (#254)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…scripts (#254)

- scripts/local/start-all.ps1, stop-all.ps1, smoke.ps1 alongside the
  Bash versions; both smoke checks pass 25/25 against a fresh start.
- docs/LOCAL_TESTING.md: start/check/stop, URLs, logins, the seeded
  OAuth clients, curl recipes (tokens, introspection, feeds, actuator,
  RFC 7591 registration), captured ESPI XML, and the open blockers.
- .gitattributes keeps *.sh at LF so they run on Windows checkouts.
- .local-run/ (script logs) is git-ignored.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… introspection test

The GHSA-mh65 test minted a client_credentials token for the placeholder
scope FB_1, which the per-grant allowlist now rejects. It requests
ThirdParty_Admin_Access instead.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…rant

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@dfcoffin
dfcoffin merged commit d156c24 into main Nov 15, 2026
11 checks passed
@dfcoffin
dfcoffin deleted the feat/254-local-test-kit branch November 15, 2026 05:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Local manual-test kit: aligned AS/DC/TP local profiles, start/stop/smoke scripts, and docs/LOCAL_TESTING.md

1 participant