fix(#160): correct AS↔DC token/introspection wire format to ESPI standard + pin contract (#150)#161
Merged
Conversation
…standard + pin it (#150) The C4 token-response/introspection augmentation diverged from the ESPI 4.0 standard (NAESB REQ.21, whose own example shows `resourceURI=.../resource/Batch/Subscription/{id}`, `scope="FB=...;..."`, only the three *URI fields, `customerResourceURI` may be ""). The producer emitted `/resource/Subscription/{id}` (missing `/Batch/`) — which DC's own ResourceValidationFilter already rejected (it expects `/resource/Batch/Subscription`), so a real customer token failed DC's resource validation. The AS also emitted three non-standard `*_id` claims (one mislabeled). Surfaced while scoping the #150 contract. Root-cause fix (not a string patch): a single canonical builder/parser so producer and consumers can't drift again. - openespi-common EspiBatchUri: the one home for the ESPI Batch URL format — builds and parses `Batch/{Subscription|Bulk|RetailCustomer}/{id}` and `Authorization/{id}`. A test asserts the legacy `/Subscription/{id}` form does NOT parse as a Batch subscription. - SubscriptionProvisioningServiceImpl (producer) builds all three canonical URIs via EspiBatchUri: resourceURI -> Batch/Subscription, authorizationURI -> Authorization, and customerResourceURI -> Batch/RetailCustomer/{rc} (built from the retail-customer id DC holds, rather than trusting the round-tripped handoff value). - Dropped the three non-standard `*_id` claims from the AS token customizer + the token-response handler's claim set; consumers parse ids from the canonical URIs instead. (Verified openespi-thirdparty does not read the `*_id` fields.) Contract pin (#150), lightweight — no Spring Cloud (these are standalone EC2 services): - contracts/ holds the two ESPI-standard fixtures (Subscription + Bulk) as the shared single source of truth, with a README explaining the producer/consumer binding. - DC consumer test (IntrospectionWireContractTest) binds DC's parsing (EspiBatchUri + EspiScope) to the fixtures and asserts the `*_id` fields are absent and the scope is FB-grammar only. The producer test asserts all three emitted URIs are the canonical forms, so the contract is verified on both sides. Tests updated: GrantBackchannelTokenCustomizerTest, the #148 orchestration test (asserts the `*_id` fields now do-not-exist), SubscriptionProvisioningServiceImplTest. All green: common 18/18 + DC contract/controller + AS testcontainers-it 20/20. Follow-ups (noted): remove the now-vestigial `customer_resource_uri` from the back-channel request/handoff (structural cleanup); empirically verify `/oauth2/introspect` surfaces the URI claims (DC enforces from its own stored Authorization.resourceURI, now the Batch form). Refs #150. Closes #160. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #160. Refs #150.
Problem (live bug, surfaced while scoping the #150 contract)
The C4 token-response/introspection augmentation diverged from the ESPI 4.0 standard (NAESB REQ.21 — its own example shows
"resourceURI":".../resource/Batch/Subscription/{id}","scope":"FB=...;...", only the three*URIfields,customerResourceURImay be"").The producer emitted
/resource/Subscription/{id}— missing/Batch/— which DC's ownResourceValidationFilteralready rejected (it expects/resource/Batch/Subscription). So a real customer token failed DC's resource validation. The AS also emitted three non-standard*_idclaims (one mislabeledcustomer_subscription_idthat was actually the retail-customer id).Root-cause fix — one canonical builder/parser (no string patch)
The drift existed because producer and consumers each encoded the URL format independently.
EspiBatchUri(openespi-common) — the single home for the ESPI Batch URL format: builds and parsesBatch/{Subscription|Bulk|RetailCustomer}/{id}andAuthorization/{id}. A test asserts the legacy/Subscription/{id}form does not parse as a Batch subscription.SubscriptionProvisioningServiceImpl) builds all three canonical URIs viaEspiBatchUri—resourceURI→Batch/Subscription,authorizationURI→Authorization,customerResourceURI→Batch/RetailCustomer/{rc}(built from the retail-customer id DC holds, not the round-tripped handoff value).*_idclaims dropped from the AS customizer + token-response handler; consumers parse ids from the canonical URIs. (Verifiedopenespi-thirdpartydoesn't read them.)Contract pinned (#150) — lightweight, no Spring Cloud
These are standalone EC2 services, not a microservice mesh — so a shared-fixture consumer-driven contract, not Spring Cloud Contract:
contracts/holds the two ESPI-standard fixtures (Subscription + Bulk) as the single source of truth + a README.EspiBatchUri+EspiScope) to the fixtures (asserts*_idabsent, FB-grammar-only scope). Producer test asserts all three emitted URIs are canonical — so the contract is verified on both sides.Verification
testcontainers-it20/20 (the PR D: Phase 2.0 integration test — auth-code flow + introspection + token-response augmentation end-to-end (closes #122 Step 6) #148 orchestration test confirms*_idgone + the URIs flow).testcontainers-itstep.Follow-ups (noted in the commit)
customer_resource_urifrom the back-channel request/handoff (structural cleanup)./oauth2/introspectsurfaces the URI claims (note: DC enforces from its own storedAuthorization.resourceURI, now the Batch form — the live path is fixed).🤖 Generated with Claude Code