Migration and vulnerabilities#2780
Merged
Merged
Conversation
100 of 102 functional endpoints (42 GET, 10 DELETE, 19 POST, 25 PUT, 1 GET-shaped revoke, 3 SCA aliases) on native http4s + path-rewriting bridge to Http4s300; all 181 v3.1.0 tests pass. Two endpoints intentionally remain on the Lift bridge and are tracked in LIFT_HTTP4S_MIGRATION.md "Per-version Lift leftovers": - getMessageDocsSwagger — absorbed by future Http4sResourceDocs workstream - getObpConnectorLoopback — deprecated stub that throws NotImplemented Side-fixes uncovered while making v3.1.0 tests pass: - ResourceDocMiddleware joins missing roles with " or " (was ", ") to match NewStyle.function.hasAtLeastOneEntitlement convention. - Http4s200.getPrivateAccountsAtOneBank now returns raw List[BasicAccountJSON] (JArray), matching Lift; the sibling /accounts/private endpoint still wraps in BasicAccountsJSON. Docs: - CLAUDE.md gains 11 new gotchas (empty path segments, RuntimeException → 500, role check before body parse, DELETE returns 200 vs 204, etc.) and the comma-separated -DwildcardSuites recipe for running tests locally. - LIFT_HTTP4S_MIGRATION.md adds a "Per-version Lift leftovers" tracking table and folds getMessageDocsSwagger into the resource-docs workstream steps.
CI shard4 (RefreshUserTest) caught a regression my local run missed: Lift's refreshUser returns 201, my migration used withUser which returns 200. Switched to executeFutureCreated. Both RefreshUserTest scenarios now pass. The local test discovery slipped because the suite class `RefreshUserTest` lives in a file named `RefreshObpDateTest.scala`, and the basename-based -DwildcardSuites generator I documented in CLAUDE.md produced `code.api.v3_1_0.RefreshObpDateTest` (no such class). Replace the basename generator with one that greps each file for its declared `class X extends ServerSetup` name. Now produces the correct FQNs even when class and filename diverge. Documented the failure mode explicitly so the next migration doesn't repeat the trap.
Scaffold Http4s400.scala with staticResourceDocs/resourceDocs split mirroring APIMethods400 (leaves room for dynamic-doc entries), v400→v310 path-rewriting bridge, wired into Http4sApp. Endpoints migrated so far (47): - Dynamic-entity family (11/11): get/create/update/delete System+BankLevel + get/update/delete My. New helper tryOrApiFail converts IllegalArgumentException from DynamicEntityCommons.apply validation into a JSON-encoded APIFailureNewStyle so the response is 400 with the verbatim message (NewStyle.function.tryons would produce a Lift Failure chain that doesn't match what tests assert with startWith). - Dynamic-endpoint family (12/12): create/getList/get/delete System +BankLevel/My + updateHost variants. Shared helpers ported from APIMethods400 (createDynamicEndpointImpl, updateDynamicEndpointHostImpl, getDynamicEndpoint(s)Impl). - Mainstream batch 1 (7): getMapperDatabaseInfo, getLogoutLink, getBanks, getBank, ibanChecker, callsLimit, createBank. - Override audit batch 1 (5 more): root, getAtms, getAtm, getProducts, getProduct, createAtm, createProduct, createProductAttribute, updateProductAttribute. Tests: DynamicEntityTest (21), DynamicEndpointsTest (30), DynamicEndpointHelperTest (20), DynamicResourceDocTest (3), DynamicMessageDocTest (2), DynamicIntegrationTest (1), BankTests (3), BankAttributeTests (11), MapperDatabaseInfoTest (3), RateLimitingTest (9), AtmsTest (9), ProductTest (4) — all pass. Discovered & documented: the "bridge-cascade hijack" gotcha. When a new version overrides an older version's same URL+verb (e.g. v4's POST /banks adds entitlement granting that v2.2.0's POST /banks doesn't), the v4 override must be in Http4s400 own-routes before the bridge cascade runs — otherwise the bridge rewrites the path (v4.0.0 → v3.1.0 → v3.0.0 → v2.2.0) and the request lands on the older version's handler. createBank was the first such case. The collectResourceDocs URL+verb dedup at Lift's level normally keeps the highest-version handler for each route, which is why the test passes without any v4 work (full fallthrough to Lift). Once an HttpXYZ for an in-flight migration is wired in, that "Lift dedup" protection disappears for routes the bridge intercepts. CLAUDE.md updated: - Added the bridge-cascade hijack gotcha with symptom + how to find the override set in advance (intersect v4 ResourceDoc URLs+verbs with older Http4s files; the Lift excludeEndpoints list is *not* the right one — it names removed endpoints, not overrides). 22 v4-over-older overrides remain (user management, customer endpoints, account/balance/counterparty, consumer/scope, consents, complex POSTs). Will continue in follow-up commits.
…/account family Adds v4-native handlers for endpoints that were silently routed to older versions via the v400→v310→...→v200 bridge cascade. Without these, the v4-specific behaviour (different JSON shapes, different status codes, different role checks) was being lost. Migrated in this commit: - root (GET /root) - getAtms (GET /banks/BANK_ID/atms), getAtm (GET .../ATM_ID), createAtm (POST) - getProducts (GET /banks/BANK_ID/products), getProduct (GET .../PRODUCT_CODE) - createProduct (PUT), createProductAttribute (POST), updateProductAttribute (PUT) - getEntitlements (GET /users/USER_ID/entitlements) - getUserByUserId, getUserByUsername, getUsersByEmail, getUsers - getCustomersByAttributes (GET /banks/BANK_ID/customers) - createCustomer (POST /banks/BANK_ID/customers) - getBankAccountsBalancesForCurrentUser (GET /banks/BANK_ID/balances) - getCoreAccountById (GET /my/banks/.../account) - getPrivateAccountByIdFull (GET .../VIEW_ID/account) - getPrivateAccountsAtOneBank (GET /banks/BANK_ID/accounts) - createUserCustomerLinks (POST /banks/BANK_ID/user_customer_links) Progress: 25 of 35 v4-over-older overrides migrated; 10 remain (counterparties GET/POST, /banks/.../my/consents, getProductAttribute, scopes GET/POST, /management/consumers POST, createAccountV400, and answerTransactionRequestChallenge). Tests passing: AtmsTest (9), ProductTest (4), UserTest (14), EntitlementTests (9), AccountTest (13), AccountBalanceTest (2), CustomerTest (12), CustomerAttributesTest (17), UserCustomerLinkTest (9), plus all earlier suites (BankTests, BankAttributeTests, MapperDatabaseInfoTest, RateLimitingTest, DynamicEntityTest, DynamicEndpointsTest, etc). Notes on the migration: - Replaced Lift's SS thread-global DSL (which is populated by Lift's dispatch wrapper and unavailable in http4s) with direct cc.user / cc.bank / cc.bankAccount / cc.view access — middleware already populates these fields before the handler runs. - For getPrivateAccountsAtOneBank, gave explicit types to the tuple destructure (List[View], List[code.views.system.AccountAccess]) because the path-dependent return type of Views.views.vend was inferring Any otherwise. - For createUserCustomerLinks, removed the assert + inline tryons pattern in favour of the standard withUserAndBankAndBodyCreated helper; the inline isValidID(bankId.value) check stays.
…e role check Migrated to Http4s400 own-routes: - getProductAttribute (GET /banks/BANK_ID/products/PRODUCT_CODE/attributes/PRODUCT_ATTRIBUTE_ID) - getScopes (GET /consumers/CONSUMER_ID/scopes) - addScope (POST /consumers/CONSUMER_ID/scopes — 201) - getConsents (GET /banks/BANK_ID/my/consents — returns ConsentsJsonV400 with api_standard/api_version fields, distinct from v3.1.0's ConsentsJsonV310) ResourceDocMiddleware fix: ScopesTest scenarios "require_scopes_for_all_roles=true but without scope" and "require_scopes_for_listed_roles=CanGetAnyUser but without scope" were failing with 200 when 403 was expected. Root cause: authorizeRoles was using APIUtil.hasEntitlement(checkBankId, userId, role) which only checks user entitlements and ignores the require_scopes_for_all_roles / require_scopes_for_listed_roles properties. Switched to APIUtil.handleAccessControlRegardingEntitlementsAndScopes(bankId, userId, consumerId, roles), the same scope-aware check the Lift dispatcher uses. The pre-existing bug only surfaced once getUserByUserId moved from Lift to http4s. Tests passing: ScopesTest (10/10), ConsentTests (5/5), ProductTest (4), AccountTest (13), BankTests (33), UserTest (14), CustomerTest (12), EntitlementTests (9), UserCustomerLinkTest (9), AccountBalanceTest (2), AtmsTest (9), CustomerAttributesTest (17), RateLimitingTest, plus Http4s700RoutesTest (111), Http4s500SystemViewsTest (13), and v3.1.0 ConsentTest/AccountAccessTest — 124 v7/v5/v3.1 tests in total. Override audit: 29/35 done, 6 remain (createCounterpartyForAnyAccount, getExplicitCounterpartiesForAccount, getExplicitCounterpartyById, getFirehoseAccountsAtOneBank, updateAccountLabel, createConsumer, createTransactionRequestCard, answerTransactionRequestChallenge).
… updateAccountLabel
Migrated to Http4s400 own-routes:
- updateAccountLabel (POST /banks/BANK_ID/accounts/ACCOUNT_ID → 200) — v4
takes UpdateAccountJsonV400 {label}, distinct from v1.2.1's
UpdateAccountJSON {id, label, bank_id}; previously hijacked by Http4s121.
- getExplicitCounterpartiesForAccount (GET .../counterparties) — v4
returns counterpartiesJson400 (with currency field) and uses
view.allowed_actions for permission check with 403; previously hijacked
by Http4s220 which returned the v2.2.0 shape (no currency) with 400.
- getExplicitCounterpartyById (GET .../counterparties/COUNTERPARTY_ID) —
v4 must return 400 (not 404) when counterparty is unknown, matching the
delete-then-get test that expects 400 after the counterparty is gone.
Uses EXPLICIT_COUNTERPARTY_ID URL template var (non-standard ALL_CAPS)
to bypass middleware's 404 validation, then calls
NewStyle.function.getCounterpartyByCounterpartyId which fails with 400.
- createExplicitCounterparty (POST .../counterparties → 201) — v4 sets
currency from postJson.currency (v2.2.0 sets ""), validates ISO currency
code, and returns 403 (not 400) when the view lacks can_add_counterparty.
Tests: CounterpartyTest (7/7), API1_2_1Test (323), AccountTest (13),
BankTests (33), ProductTest (4), AtmsTest (9), UserTest (14),
EntitlementTests (9), ScopesTest (10), ConsentTests (5),
UserCustomerLinkTest (9) — 399 total, all pass.
Override audit: 33/37 done, 4 remain (getFirehoseAccountsAtOneBank,
createConsumer, createTransactionRequestCard,
answerTransactionRequestChallenge).
Symptom: POSTs that fell through to the bridge cascade (v400 → v310 → v300 → v220 → v210 …) were producing 500 errors with empty bodies in the downstream handler. TransactionRequestsTest had 21 such failures; v5_1_0.CounterpartyLimitTest and VRPConsentRequestTest had matching ones because their POSTs also bridge down to v2.1.0 createTransactionRequest. Root cause: http4s request bodies are single-shot fs2 streams. Http4sCallContextBuilder.fromRequest is called once per version's ResourceDocMiddleware (because the bridge wraps each version's wrappedRoutes), and the first call drained the stream. Every later fromRequest in the cascade saw an empty stream and stored cc.httpBody = None. By the time v2.1.0 createTransactionRequest's handler read `req.bodyText.compile.string`, the body was gone and JSON parsing of "" failed inside createTransactionRequestImpl — which then bubbled out as an uncaught exception → 500. Fix: 1. Http4sApp.baseServices now pre-reads the body once at the top of the chain, stashes the bytes in a new `cachedBodyKey` attribute, and rebuilds the request body stream via fs2.Stream.emits so that any downstream component that still reads `req.body` (e.g. the Lift fallback bridge) gets the same payload. 2. Http4sCallContextBuilder.fromRequest looks up `cachedBodyKey` first and short-circuits when present, instead of trying to re-drain the stream. 3. ResourceDocMiddleware re-attaches the cached body before calling the inner routes so the request keeps the cache through middleware → handler transitions. 4. Updated direct callers of `req.bodyText.compile.string` (Http4s210, Http4s220, Http4s300) to read from `cc.httpBody` instead, since the stream-reading code path is no longer the canonical source after the cache lands. Also: Http4s210 createTransactionRequest's "unknown transactionRequestType" branch used to throw a plain RuntimeException, which ErrorResponseConverter mapped to 500. It now throws an Exception whose message is the JSON-encoded APIFailureNewStyle with failCode = 400, matching the convention used elsewhere in the file. Still outstanding: a few v4 tests (TransactionRequestsTest 21, v5_1_0 CounterpartyLimitTest 3, VRPConsentRequestTest 3, FirehoseTest 3, etc.) need the corresponding v4 endpoints migrated to Http4s400 own-routes so they get v4-specific behavior (e.g. ACCOUNT/REFUND/SIMPLE/AGENT_CASH_WITHDRAWAL transaction request types, v4 firehose response shape). Those endpoints are on the next-batch list — this commit fixes the underlying bridge cascade so they aren't masked by 500s. Local regression check: AccountTest (13) + BankTests (3 own) + CounterpartyTest (7) + ScopesTest (10) + ConsentTests (5) — 38 total, all pass.
`isTemplateVariable` treated every all-caps segment (uppercase letters + underscore + digits) as a template variable. That collapsed real literals like SANDBOX_TAN, COUNTERPARTY, SEPA, FREE_FORM, ACCOUNT, CARD into wildcards, so the v2.1.0 ResourceDoc /banks/BANK_ID/accounts/ACCOUNT_ID/GRANT_VIEW_ID/transaction-request-types/SANDBOX_TAN/transaction-requests matched *any* transaction-request-type URL, including v4-only ACCOUNT and CARD. The middleware then auth-checked, routed to v2.1.0's handler, which doesn't know those types — the request was either returned with the wrong status or 500'd inside v2.1.0's logic, instead of falling through the bridge cascade to the v4 Lift endpoint that handles it. Fix: keep the original "looks all-caps → wildcard" rule but exclude an explicit set of known literals (the 10 trans-req types listed in v4 plus the 4 SCA-method values EMAIL/SMS/IMPLICIT/NOT_EMAIL_NEITHER_SMS). This preserves the non-standard placeholder convention (NEW_ACCOUNT_ID, GRANT_VIEW_ID, FIREHOSE_BANK_ID, EXPLICIT_COUNTERPARTY_ID, SYS_VIEW_ID, SCA_METHOD, TRANSACTION_REQUEST_TYPE, …) without an allow-list. Also: v2.1.0's createTransactionRequest now guards its route pattern against unsupported types so the bridge-cascade fall-through is robust even if a new literal slips past the matcher. The catch-all ResourceDoc for TRANSACTION_REQUEST_TYPE in v2.1.0 has been removed; the four type-specific docs (SANDBOX_TAN, COUNTERPARTY, SEPA, FREE_FORM) are sufficient and the unknown-type branch in createTransactionRequestImpl now encodes its failure as an APIFailureNewStyle JSON so unreachable errors map to 400 rather than 500. Local results: - TransactionRequestsTest: 21 → 16 failures (5 more pass — the SEPA/ COUNTERPARTY/FREE_FORM/SANDBOX_TAN scenarios that were previously hijacked; the 16 remaining are all CARD or v4-only types that need v4's own-route migration). - v3_1_0.ConsentTest: still 0 failures (SCA_METHOD still works because it ends with _METHOD — actually it stays a wildcard because it's not in the literal allow-list, and EMAIL/SMS/IMPLICIT are listed precisely because they would otherwise fail the matcher). - v4 BankTests/AccountTest/ScopesTest/ConsentTests + v5 + v7 Http4s700RoutesTest: 146 tests, all pass. Outstanding for the remaining 16 TransactionRequestsTest failures plus v5.1 CounterpartyLimitTest/VRPConsentRequestTest/TransactionRequestTest and v4 MakerCheckerTransactionRequestTest/FirehoseTest: these all want v4-specific behaviour the bridge cascade can't deliver because v4 has no own-route yet. Migrating createTransactionRequest + answerTransactionRequestChallenge + getFirehoseAccountsAtOneBank to Http4s400 is the next step.
…nge too Pair with the earlier route-guard fix on v2.1.0 createTransactionRequest. answerTransactionRequestChallenge had the same problem: its single catch-all ResourceDoc used a TRANSACTION_REQUEST_TYPE template variable that still matched v4-only types (ACCOUNT, ACCOUNT_OTP, REFUND, SIMPLE, AGENT_CASH_WITHDRAWAL, CARD). The middleware then auth-checked, discovered the route guard rejected the type, and returned 404 from `getOrElseF(NotFound)` instead of letting the request fall through. Fix: 1. Add the same route guard to answerTransactionRequestChallenge so it only matches the four v2.1.0-supported types. 2. Replace the single TRANSACTION_REQUEST_TYPE catch-all ResourceDoc with four type-specific docs (one per supported type). Unknown types now miss the ResourceDoc lookup entirely, the middleware returns None, and the bridge cascade walks the request down to the Lift fallback where APIMethods400.answerTransactionRequestChallenge handles the v4-specific challenge logic (maker-checker, ChallengeJsonV400 shape, attribute attachment). Local results across the relevant trans-req-related suites: TransactionRequestsTest, MakerCheckerTransactionRequestTest, CounterpartyLimitTest, VRPConsentRequestTest, TransactionRequestTest → 43 passing / 15 failing (was 27 failing pre-fix, 21 before the matcher fix). The remaining 15 are about challenges not appearing in the v4 response body for the test's high-amount scenarios — Lift's v4 createTransactionRequest is reached and returns 201, but `body.challenges` comes back empty. That's a separate issue (presumably a connector/threshold config) not caused by my changes. Regressions: none. AccountTest + BankTests + ScopesTest + ConsentTests + v3_1_0 ConsentTest + AccountAttributeTest + Http4s700RoutesTest: 148 tests, all pass.
GET /banks/BANK_ID/firehose/accounts/views/VIEW_ID was a bridge-hijack override: v4 returns ModeratedFirehoseAccountsJsonV400 (with `accounts`, `product_code`, …) but the bridge cascade routed firehose calls to Http4s300.getFirehoseAccountsAtOneBank which returns ModeratedCoreAccountsJsonV300. FirehoseTest extracts the v4 shape and failed with `MappingException: No usable value for accounts / product_code`. Migration mirrors the v3.0.0 implementation almost line-for-line — same auth + role check, same bank/view lookup, same firehose-account fetching and per-account moderation — only the final response uses `JSONFactory400.createFirehoseCoreBankAccountJSON` which returns the ModeratedFirehoseAccountsJsonV400 shape. ResourceDoc keeps `FIREHOSE_BANK_ID` / `FIREHOSE_VIEW_ID` in the URL template so middleware skips bank/view validation; the in-handler booleanToFuture checks fire in the order tests expect: 1. AccountFirehoseNotAllowedOnThisInstance → 400 2. UserHasMissingRoles (canUseAccountFirehose or *AtAnyBank) → 403 3. BankNotFound → 404 Local results: FirehoseTest 8/8 pass (3 prior failures resolved), AccountTest 13/13, BankTests 3/3 — 24 tests, no regressions. Override audit: 34/37 migrated; 3 remain (createConsumer, createTransactionRequestCard, and the underlying createTransactionRequest which needs a bigger refactor to move the SS thread-globals out of LocalMappedConnectorInternal — for now we rely on the matcher fix + v2.1.0 route guards to let v4 trans-req types fall through to the Lift fallback, which works for ACCOUNT/AGENT_CASH_WITHDRAWAL but leaves the "challenges array empty" test assertions still failing).
Last remaining hijack on the trans-request happy path. Before: v2.1.0 ResourceDocs for SANDBOX_TAN/COUNTERPARTY/SEPA/FREE_FORM were literal matches (after the matcher fix), so bridge-cascaded v4 URLs landed in v2.1.0's handler and returned the v2.1.0 response shape (missing the v4-only `challenges: List[ChallengeJsonV400]` field). v4- only types like ACCOUNT/AGENT_CASH_WITHDRAWAL/CARD already fell through to Lift, but the test still failed for the four overlapping types. The new route in Http4s400 owns ALL trans-req-type POSTs at v4 (no guard — also catches unknown types so the test's "invalidTransactionRequestType" scenario gets 400 from the connector rather than a 404 fall-through to Lift). Implementation: delegate to `LocalMappedConnectorInternal.createTransactionRequest` — the same helper the Lift `lazy val createTransactionRequestAccount` (and 7 sibling lazy vals) call. The helper reads `SS.user` (Lift thread- global) on its first line; we initialize it via `APIUtil.SS.init` so the synchronous read inside the for-comprehension captures the http4s cc.user before any flatMap. SS.init also needs a `View` instance, but the connector itself only consumes ViewId, so a `systemView(viewIdStr)` lookup is enough. ResourceDoc uses `GRANT_VIEW_ID` (non-standard) so middleware skips view-access validation. The connector's `checkAuthorisationToCreateTransactionRequest` returns 400 InsufficientAuthorisationToCreateTransactionRequest if the user has neither the role nor view permission, matching the Lift v4 behaviour the test expects. Local TransactionRequestsTest results: Before any of today's work : 21 / 34 failures After matcher + body-cache : 16 / 34 failures After v210 route guards : 7 / 34 failures After this commit : 7 / 34 failures (27 pass) The remaining 7 are all `400 did not equal 202` on the `answerTransactionRequestChallenge` step of the same flow — v4 challenges for SANDBOX_TAN/COUNTERPARTY/SEPA/FREE_FORM still bridge to v2.1.0's answer-challenge handler which doesn't recognize the v4 ChallengeAnswerJson400 shape and 400s. Fix needs a similar v4 own-route for the challenge endpoint; the body is ~280 lines so it's a separate commit. Override audit: 36/37 migrated; 1 remains (createConsumer).
The v2.1.0 answer-challenge ResourceDocs (one per of the 4 v2.1.0- supported types — SANDBOX_TAN/COUNTERPARTY/SEPA/FREE_FORM, added in 77acfe3) were still hijacking the URL via the bridge cascade and returning v2.1.0's 400 (`InvalidJsonFormat … ChallengeAnswerJSON`) instead of letting the v4 Lift endpoint handle the v4-shaped ChallengeAnswerJson400. The full Lift handler is ~280 lines, so rather than duplicate it we claim the URL at the http4s layer (taking priority over the bridge cascade) and delegate to `Http4sLiftWebBridge.dispatch(req)` — which runs Lift's own dispatcher directly. Lift then picks up v4's `answerTransactionRequestChallenge` because it's registered first in `OBPAPI4_0_0.routes` via `endpointsOf4_0_0`. Local results: code.api.v4_0_0.TransactionRequestsTest — 34/34 pass (was 7 failing for the four shared types' answer-challenge step). Caveat: v5.1.0 VRPConsentRequestTest now fails 4 tests with 500 on trans-request creation. Those tests use `v4_0_0_Request` to POST a trans-request and now hit the v4 own-route added in b673da1, which calls `LocalMappedConnectorInternal.createTransactionRequest` for real — and that triggers `sendCustomerNotification` which tries SMTP and ConnectException's because no mail server is configured in the test env. Before b673da1 the URL was bridge-hijacked into v2.1.0's handler, which doesn't call the notification path, so the SMTP issue was hidden. The underlying SMTP-failure path needs a graceful fallback (swallow or log instead of raise) — separate from this commit, since it's about the notification connector, not routing. Override audit: 37/37 migrated; 1 remains (createConsumer) — and the remaining audit entry is the only v4 override left in the bridge- hijack list.
…ResourceDocMiddleware Three interceptors used to live in `APIUtil.afterAuthenticateInterceptors`, fired by Lift's wrapEndpoint between auth and the endpoint body. Once an endpoint moves to http4s those interceptors stopped running, which made the corresponding test suites fail in odd ways (500 on Force-Error, 201/500 on the body validators). The same applied to v2.2.0+ endpoints that http4s now owns — like createFx — because every v4.0.0 test using those endpoints (via the bridge cascade) sees the migrated handler, not Lift's interceptor chain. Port: three new validation steps in `ResourceDocMiddleware.validateOnly`, each direct ports of the corresponding Lift logic: 1. `processForceError` — opt-in via `enable.force_error` prop. Reads `Force-Error` / `Response-Code` headers, validates name format, checks the error code is in the ResourceDoc's `errorResponseBodies`, then synthesizes the requested error. 2. `validateAuthType` — looks up `AuthenticationTypeValidationProvider` by operation id; rejects with 400 if the current request's authType isn't in the registered allow-list. Skips anonymous requests (they've already passed/failed auth elsewhere). 3. `validateJsonSchema` — looks up the registered JSON schema for this endpoint via `JsonSchemaValidationProvider`, validates `cc.httpBody` against it, returns 400 with `InvalidRequestPayload + errors` joined by `; ` — same prefix the Lift interceptor used so existing assertions on `$.from_currency_code: does not have a value …` still match. Order in the validation chain: after authenticate + authorizeRoles, before bank/account/view validation. That matches Lift's flow (auth first, then interceptors, then endpoint-specific path validation). Local results: - ForceErrorValidationTest: 35 / 35 (was 10 failing) - AuthenticationTypeValidationTest: 27 / 27 (was 1 failing) - JsonSchemaValidationTest: 27 / 27 (was 1 failing) Regression check across 184 tests (Account/Bank/Scopes/Consents/ v3_1 ConsentTest/v7 Http4s700RoutesTest/Firehose/TransactionRequests): 0 failures. Group 2 done. Remaining: group 3 (v5.1 limits, VRP consent) and the v5.1 SMTP-side-effect surfacing from group 1 — both require new own-routes or connector tolerance, not interceptor work.
…e mail.test.mode in CI
Two fixes that together close out the v5.1 VRP / counterparty-limit /
trans-request CI failures.
(1) Http4s400.createTransactionRequest looked up the URL's view via
`Views.views.vend.systemView(viewId)` only — a hard fail for VRP
consent flows whose URLs carry a custom `_vrp-…` view id rather
than a system view name. The lookup threw
`An Empty Box was opened. The justification was OBP-30005: View
not found`, surfacing as 500. Fix: fall back to `customView` (the
account-scoped lookup) when the system view miss. SS.init only
needs *some* View instance; the connector itself works off the
ViewId parameter, so the fallback view is purely for thread-global
plumbing.
(2) The connector's email branch (LocalMappedConnector.
sendCustomerNotification → CommonsEmailWrapper.sendTextEmail)
opens a real SMTP socket unless `mail.test.mode=true`. CI has no
mail server, so it threw ConnectException → 500 on any v5 consent
flow once group 1 connected those flows to the real connector. The
CI workflow already generates `test.default.props` line by line in
a setup step; add `mail.test.mode=true` there.
Local results:
- v5_1_0.CounterpartyLimitTest: 6 / 6 (was 3 failing pre-group-1)
- v5_1_0.VRPConsentRequestTest: 6 / 6 (was 4 failing post-group-1)
- v5_1_0.TransactionRequestTest: 8 / 8 (was 1 failing pre-group-1)
- Full regression across 297 tests (groups 1+2+3 + base v4 + v3.1
ConsentTest + v7 Http4s700RoutesTest): 0 failures.
Group 3 done.
…REE_FORM doc The ResourceDoc matcher now treats a fixed set of ALL_CAPS segments (EMAIL, SMS, IMPLICIT, SANDBOX_TAN, FREE_FORM, ...) as literals rather than wildcards, because real Lift endpoints register them as literal SCA-method or transaction-request-type segments. Two collateral regressions: 1) `GET /users/email/EMAIL/terminator` used EMAIL as a placeholder variable. Once EMAIL became literal, the matcher only matched URLs whose third segment was the literal string "EMAIL" — the "with proper entitlement" scenarios send a real address and missed the doc entirely, so middleware skipped auth/role validation and the handler 500'd on the empty CallContext. Rename the placeholder to USER_EMAIL in both the http4s and Lift declarations so the matcher treats it as a wildcard. 2) The v2.1.0 FREE_FORM `createTransactionRequest` ResourceDoc carried `Some(List(canCreateAnyTransactionRequest))`. In the Lift handler that role is only used to *bypass* view-permission checks inside `checkAuthorisationToCreateTransactionRequest`; it is not a required entitlement. Once the matcher correctly resolved the FREE_FORM doc, the middleware began enforcing the role and tests running as the owner-view user got 403 instead of 201. Set the role to None and let the inline view check govern access. All 42 scenarios in v3.0/v4.0 UserTest and v2.1 TransactionRequestsTest now pass.
Two recurring gotchas surfaced while migrating v3.0/v4.0 getUsersByEmail and v2.1.0 createTransactionRequest FREE_FORM: 1. The Http4sSupport matcher's `literalAllCapsSegments` set treats names like EMAIL, SMS, IMPLICIT, SANDBOX_TAN, FREE_FORM as concrete URL segments (because real SCA-method / transaction-request-type endpoints register them as literals). Re-using one of those names as a placeholder variable in a different endpoint's URL template silently breaks the matcher. 2. Some Lift entitlements are bypass conditions inside authorisation helpers, not required roles. Copying them into the http4s ResourceDoc role list converts them into requirements and locks out view-permission callers — http4s middleware enforces doc roles, Lift did not. Both belong with the existing "Tricky Parts" entries so future migrations don't re-derive the failure mode.
Completes the v5.0.0 Lift→http4s migration. Http4s500 now serves all 39
v5.0.0 own endpoints (9 prior + 33 new) and cascades inherited v1–v4
endpoints through the v500→v400 path-rewriting bridge instead of falling
through to Http4sLiftWebBridge.
Endpoints migrated (33):
- 12 overrides (had to land before the bridge, else cascade hijack):
createBank, updateBank, createAccount, createUserAuthContext,
getUserAuthContexts, createUserAuthContextUpdateRequest,
answerUserAuthContextUpdateChallenge, createCustomer,
getCustomersAtOneBank, createProduct, addCardForBank,
getViewsForBankAccount, getAdapterInfo
- 21 new in v5.0.0:
- 6 consent-request endpoints (3 SCA-method aliases —
EMAIL/SMS/IMPLICIT — share the createConsentByConsentRequestId
handler via a guarded route pattern)
- 5 customer endpoints (getCustomerOverview, getCustomerOverviewFlat,
getMyCustomersAtAnyBank, getMyCustomersAtBank,
getCustomersMinimalAtOneBank)
- 6 customer-account-link endpoints (CRUD)
- headAtms, getMetricsAtBank, getSystemViewsIds
createAccount: ResourceDoc keeps Some(List(canCreateAccount)) and
relies on ResourceDocMiddleware enforcement; the inline
"userIdAccountOwner == loggedInUserId" check is preserved as a
no-op safety net mirroring Lift exactly. (Lift's
OBPRestHelper.registerRoutes wraps every endpoint in
wrappedWithAuthCheck, which DOES enforce doc roles — contrary to
the CLAUDE.md "Conditional role check (403)" note. The note should
be revised: bypass roles vs required roles is the real
distinction.)
createConsentByConsentRequestId: the VRP branch's nested for-comp
hits Scala 2.12 type-inference limits when val bindings inside a
deep monadic context interact with an if/else whose branches
return structurally similar but separately inferred types.
Refactor: extract postJson, postCounterpartyLimitV510, and the
inner for-comp into explicit val bindings (vrpFlow:
Future[(BankId, AccountId, ViewId, CounterpartyId)]) and annotate
the else branch's tuple type. Behaviour unchanged.
Bridge cascade now: v500Routes own-routes → v500ToV400Bridge →
(v4 own → v4ToV310Bridge → … → v1.2.1).
Tests: v5.0.0 (85 pass / 13 ignored), v5.1.0 (239 pass), v6.0.0 +
v7.0.0 + http4sbridge (616 pass) — all green.
Three gotchas asserted "Lift never enforced doc roles" — wrong. The truth:
OBPRestHelper.registerRoutes wraps every endpoint in
ResourceDoc.wrappedWithAuthCheck (APIUtil.scala:1780), which enforces
doc roles whenever rolesForCheck.nonEmpty && _autoValidateRoles. So
Lift and ResourceDocMiddleware enforce doc roles the same way for
practically every endpoint.
Caught when v5 createAccount AccountTest's "user2 without role → 403"
scenario failed against my (mis)application of the "Conditional role
check" gotcha — I'd taken canCreateAccount out of the doc on the
theory the inline conditional was the real gate. It wasn't; both
Lift's wrappedWithAuthCheck AND the inline conditional fire, and
Lift's doc enforcement is what makes the test pass. Restoring
Some(List(canCreateAccount)) fixed it.
Edits:
- Add a new top-level note ("Lift DOES enforce ResourceDoc roles")
citing APIUtil.scala:1780 and explicitly retracting the prior claim,
so future migrators don't repeat the mistake.
- Narrow "Conditional role check (403)" to genuinely-conditional
roles (different role for different paths). Add the corollary: if
the inline check uses the SAME role as the doc, the inline check
is dead-code-but-harmless — keep both, mirror Lift exactly.
- Rewrite "ResourceDoc role and handler role disagreement": Lift
enforces both X (doc) and Y (inline). If a test "passed with only
Y", it's because (a) .disableAutoValidateRoles() was set, (b) the
doc role was actually different than assumed, or (c) the test
granted both. The error-message wording is the more common drift
to watch for.
- Fix "Bypass roles vs required roles": clarify WHY bypass roles
stay out of the doc — adding them would make Lift enforce them as
required (since Lift does enforce doc roles), breaking the OR-chain
intent. The reflex-copy trap is the real warning, not "Lift didn't
enforce".
… dispatch
Lays the foundation for the v5.1.0 Lift→http4s migration. Wires
v510Routes into Http4sApp.baseServices ahead of v500Routes, with
own-routes only (the v510→v500 path-rewriting bridge is deliberately
left disconnected — see below). Required two infrastructure fixes
along the way:
ResourceDocMiddleware authMode dispatch
---------------------------------------
Mirror Lift's wrappedWithAuthCheck (APIUtil.scala:1783-1788) by
dispatching the auth helper on resourceDoc.authMode:
ApplicationOnly | UserOrApplication → APIUtil.applicationAccess
UserOnly | UserAndApplication → APIUtil.anonymousAccess
Without this every endpoint behaved as UserOnly. v5.1.0 createConsumer
and getConsumers (both UserOrApplication) returned
AuthenticatedUserIsRequired for unauth instead of
ApplicationNotIdentified, breaking the ConsumerTest "We test the
authentication errors" body-content assertion. Also bypass the
"empty user + needsAuth" 401 branch when isAppMode is true, so
consumer-only auth (no User) passes through as Lift does.
Bridge cascade is currently DISABLED for v5.1.0
-----------------------------------------------
While the bulk of the 110 v5.1.0 endpoints (96 are new in v5.1.0,
14 are overrides of older versions) still live in Lift, enabling
the v510→v500 path-rewriting bridge would route unmigrated own
endpoints (e.g. updateConsumerRedirectURL) down through the cascade
to a wrong-version handler or a 404. Without the bridge, unmatched
v5.1.0 URLs fall through to Http4sLiftWebBridge with the original
path intact and Lift's OBPAPI5_1_0 dispatch picks them up — same
behaviour as before this commit. The bridge code is still defined
in Implementations5_1_0.v510ToV500Bridge for the eventual flip.
Migrated overrides (12 of 14)
-----------------------------
- root, getMyConsentsByBank, getAggregateMetrics
- createAtm, updateAtm, deleteAtm
- createConsumer, getConsumer, getConsumers
- getTransactionRequests
- getBankAccountsBalances, getAllBankAccountBalances
Deliberately NOT migrated yet (2 of 14)
---------------------------------------
- getAtms, getAtm: ResponseHeadersTest exercises ETag / If-None-Match /
If-Modified-Since on /banks/BANK_ID/atms. Lift's response builder
computes ETag (APIUtil.getRequestHeadersNewStyle:534) and honours
conditional headers (APIUtil.checkConditionalRequest:471).
ResourceDocMiddleware doesn't yet do either, so migrating these
here would regress 4 tests. Leaving them in Lift for now;
APIMethods510 still has the ResourceDocs registered so resource-docs
aggregation is unaffected.
Inline notes from the override batch
------------------------------------
- v5.1 PostAtmJsonV510 requires `id`; updateAtm uses AtmJsonV510 and
takes the id from the URL. The v5.1 atm shape adds atm_type,
license, opening hours, and attributes — wider than v4 — which is
why the bridge cascade hijack to Http4s400.getAtms surfaced
immediately with MappingException: "No usable value for atm_type."
before this commit.
- getMyConsentsByBank pulls a private rowToConsentInfoJsonV510 from
APIMethods510. Copied verbatim; no shared factory exists yet.
- getTransactionRequests v5.1 returns TransactionRequestsJsonV510
(with attributes), not the v4-shape. The "Get Transaction Requests
with Attributes" scenario hits this path.
- createConsumer / getConsumers (UserOrApplication mode) declare
Some(List(canCreateConsumer)) / Some(List(canGetConsumers)) in the
doc and rely on the middleware to enforce them after the new
authMode dispatch.
Tests
-----
- v5.1.0: 239 pass / 0 fail (full suite)
- v5.0.0: AccountTest, BankTests, CustomerTest, ConsentRequestTest,
SystemViewsTests, UserAuthContextTest — all pass
- v6.0.0: ConsumerTest, SystemViewsTest — pass
- v7.0.0: Http4s700RoutesTest — pass
- bridge: Http4sLiftBridgePropertyTest — pass
Total cross-version: 455 tests, 0 failures.
Next session(s): migrate the remaining 96 new v5.1.0 endpoints
(consents family, regulated-entities + attributes, atm-attributes,
agents, customer/user attribute helpers, balance CRUD, view-access,
counterparty-limits, etc.), implement ETag support in the http4s
response wrapper, re-migrate getAtms/getAtm, then enable
v510ToV500Bridge.
Adds the second migration batch on top of the scaffold commit (8cea9b6). Total v5.1.0 own endpoints now in http4s: 27 (was 12). Newly migrated (15): System / UI / metrics - suggestedSessionTimeout (GET /ui/suggested-session-timeout) - getOAuth2ServerWellKnown (GET /well-known) - waitingForGodot (GET /waiting-for-godot) - getAllApiCollections (GET /management/api-collections) - updateMyApiCollection (PUT /my/api-collections/API_COLLECTION_ID) - getApiTags (GET /tags) - getMetrics (GET /management/metrics) - getWebUiProps (GET /webui-props) - mtlsClientCertificateInfo (GET /my/mtls/certificate/current) Regulated entities - regulatedEntities, getRegulatedEntityById, createRegulatedEntity, deleteRegulatedEntity (already in earlier batch — these stay) - createRegulatedEntityAttribute, deleteRegulatedEntityAttribute, getRegulatedEntityAttributeById, getAllRegulatedEntityAttributes, updateRegulatedEntityAttribute (5 new) Log cache (6 — shared logCacheHandler helper for trace/debug/info/warning/error/all) ATM attributes (5) - createAtmAttribute, getAtmAttributes, getAtmAttribute, updateAtmAttribute, deleteAtmAttribute Agents (3 of 4) - createAgent, getAgent, getAgents Deferred to Lift (3 of 14 + previous batch's 2) - getAtms, getAtm: ETag / If-None-Match / If-Modified-Since handling in Lift's response builder (APIUtil.checkConditionalRequest + getRequestHeadersNewStyle). ResourceDocMiddleware doesn't yet emit ETag or honour conditional headers, so migrating these regresses ResponseHeadersTest. - updateAgentStatus: AgentTest "wrong Bankid" expects 404 BankNotFound for unauthorised user1, which means Lift's wrappedWithAuthCheck role check passes here even though user1 lacks canUpdateAgentStatusAtAnyBank/canUpdateAgentStatusAtOneBank. ResourceDocMiddleware applies the same access-control function (with JIT entitlements) and returns 403 — the strict reading of the doc roles. Leaving updateAgentStatus in Lift preserves the established test contract until the discrepancy is root-caused (suspect: per-version Lift environment has additional fixtures). Tests: 239 v5.1.0 tests pass, 0 failures. The v510→v500 bridge is still disabled. Re-enable once all 110 v5.1.0 endpoints are migrated, the role-check parity is resolved, and ETag support lands in the http4s response wrapper.
…rity) Adds the third migration batch on top of 4229b74. Total v5.1.0 own endpoints now in http4s: 46. Newly migrated (19): Non-personal user attributes (3) - createNonPersonalUserAttribute, deleteNonPersonalUserAttribute, getNonPersonalUserAttributes User / lock / sync (8) - syncExternalUser, getEntitlementsAndPermissions, getUserByProviderAndUsername, getUserLockStatus, unlockUserByProviderAndUsername, lockUserByProviderAndUsername, validateUserByUserId, getAccountAccessByUserId Customer helpers (2) - getCustomersForUserIdsOnly, getCustomersByLegalName System integrity (5) - customViewNamesCheck, systemViewNamesCheck, accountAccessUniqueIndexCheck, accountCurrencyCheck, orphanedAccountCheck Currencies (1) - getCurrenciesAtBank — note: scope check uses `failCode = 403`, since `Helper.booleanToFuture` defaults to 400 and CurrenciesTest's "without a proper scope" scenario asserts 403. Deferred (2) - getAccountsHeldByUserAtBank / getAccountsHeldByUser depend on AccountsHelper.getFilteredCoreAccounts which takes a Lift `Req`. Need to port the filter logic before migrating. Tests: 239 v5.1.0 tests pass, 0 failures.
Adds the fourth migration batch on top of 8919213. Total v5.1.0 own endpoints now in http4s: 58. Newly migrated (12): Consumer mgmt (7) - updateConsumerRedirectURL, updateConsumerLogoURL, updateConsumerCertificate, updateConsumerName, getCallsLimit, createMyConsumer, createConsumerDynamicRegistration View access (3) - grantUserAccessToViewById, revokeUserAccessToViewById, createUserWithAccountAccessById Transaction request management (2) - getTransactionRequestById, updateTransactionRequestStatus Tests: 239 v5.1.0 tests pass, 0 failures.
… + perms) Adds the fifth migration batch on top of 6b27fd3. Total v5.1.0 own endpoints now in http4s: 76 of 110. Newly migrated (18): View account/balance reads (3) - getCoreAccountByIdThroughView - getBankAccountBalances - getBankAccountsBalancesThroughView Counterparty limits (4 of 5) - createCounterpartyLimit, updateCounterpartyLimit, getCounterpartyLimit, deleteCounterpartyLimit (getCounterpartyLimitStatus deferred — 200+ line monthly/yearly transaction aggregation, lots of Date arithmetic) Custom view CRUD (4) - createCustomView (uses withViewCreated for 201), updateCustomView (200), getCustomView (200), deleteCustomView (uses executeDelete for 204 — middleware populates cc.view, cc.bankAccount; reads them inline) Bank account balance CRUD (4) - createBankAccountBalance, getBankAccountBalanceById, updateBankAccountBalance, deleteBankAccountBalance System view permissions (2) - addSystemViewPermission, deleteSystemViewPermission Note on createCustomView: original Lift handler returns 201, so used withViewCreated. Initial draft used withView (returns 200), which broke the CustomViewTest "We will call the endpoint" assertion. Tests: 239 v5.1.0 tests pass, 0 failures. Remaining unmigrated (34): consents family (12), getCounterpartyLimitStatus (1), getAtms/getAtm (2 — ETag), updateAgentStatus (1 — role-check parity), getAccountsHeldByUserAtBank/getAccountsHeldByUser (2 — account-type filter port), plus tail of various other endpoints.
Adds the consents family on top of c791499. Total v5.1.0 own endpoints now in http4s: 105 of 111 (95%). Newly migrated (13): Read endpoints (5) - getMyConsents (GET /my/consents) - getConsentsAtBank (GET /management/consents/banks/BANK_ID) - getConsents (GET /management/consents) - getConsentByConsentId (GET /user/current/consents/CONSENT_ID) - getConsentByConsentIdViaConsumer (GET /consumer/current/consents/CONSENT_ID) Update / management (3) - updateConsentStatusByConsent (PUT /management/banks/BANK_ID/consents/CONSENT_ID) - updateConsentAccountAccessByConsentId (PUT /management/banks/BANK_ID/consents/CONSENT_ID/account-access) - updateConsentUserIdByConsentId (PUT /management/banks/BANK_ID/consents/CONSENT_ID/created-by-user) Revoke (3) - revokeConsentAtBank (DELETE /banks/BANK_ID/consents/CONSENT_ID) - selfRevokeConsent (DELETE /my/consent/current — pulls Consent-Id from request header) - revokeMyConsent (DELETE /my/consents/CONSENT_ID — initially missed the audit; added as a follow-up after the post-batch endpoint inventory found 7 unmigrated rather than 6) Create (2) - createConsentImplicit (POST /my/consents/IMPLICIT — also handles SCA = EMAIL/SMS via the same handler, dispatched by the literal URL segment; originally aliased Lift's createConsent) - createVRPConsentRequest (POST /consumer/vrp-consent-requests) createConsentImplicit was the largest port — ~200 lines around challenge/SCA dispatch, JIT entitlement check, JWT stamping, skipConsentScaForConsumerIdPairs handling, etc. Mirrors the Lift shape with one structural simplification: the inner SCA dispatch loses Lift's early `Future` wrapping of `failMsg` strings (no longer needed in the http4s for-comp). Tests: 239 v5.1.0 tests pass, 0 failures (incl. 29 consent-suite tests across ConsentObpTest, ConsentsTest, VRPConsentRequestTest). Remaining unmigrated (6 of 111): getAtms / getAtm (ETag), updateAgentStatus (role-check parity), getAccountsHeldByUserAtBank / getAccountsHeldByUser (Lift Req filter port), getCounterpartyLimitStatus (complex monthly/yearly aggregation). Bridge re-enable still pending on these.
…e checks
Closes the v5.1.0 endpoint migration. Total v5.1.0 own endpoints in
http4s: 111 of 111 (100%). All 239 v5.1.0 tests pass; 447
cross-version tests (v5.0/v6/v7/bridge) pass.
Middleware change: ResourceDocMiddleware bank/roles ordering
-----------------------------------------------------------
Reorder the validation chain to put bank validation BEFORE role
authorization, matching Lift's wrappedWithAuthCheck (APIUtil.scala
line 1934-1969). Lift's own comment explains why:
"A Bank MUST be checked before Roles. In opposite case we get
next paradox: We set non existing bank → We get error message
that we don't have a proper role → We cannot assign the role
to non existing bank."
This unblocks AgentTest's "wrong Bankid" scenario (PUT
/banks/BANK_ID/agents/agentId for unauthorised user1, expects 404
BankNotFound — previously got 403 from the role check firing first).
447 cross-version tests still pass after the reorder.
Newly migrated endpoints (6)
----------------------------
- updateAgentStatus — now passes the role/bank ordering thanks to
the middleware reorder above. Same handler shape as before.
- getAccountsHeldByUserAtBank, getAccountsHeldByUser — port
AccountsHelper.filterWithAccountType inline to operate on http4s
query params instead of Lift Req. Helper lives as
Implementations5_1_0.filteredCoreAccountsByQueryParams.
- getCounterpartyLimitStatus — straight 1-for-1 port of the 200-line
monthly/yearly/total transaction aggregation. Reuses java.time
for date math, falls back to APIUtil.theEpochTime/ToDateInFuture
for the all-time bounds.
- getAtms, getAtm — added a private respondWithETag helper that
inlines the conditional-request flow:
1. Compute body, then ETag = SHA256(URL + body).
2. If-None-Match header matches → 304 with ETag header.
3. If-Modified-Since header → look up MappedETag cache key
(mirror of APIUtil.checkIfModifiedSinceHeader:390 — same
composite cache key shape, same async update/create on
miss) and 304 if cached value is fresh.
4. Otherwise 200 with ETag header.
ResponseHeadersTest's 4 scenarios exercise every branch and all
pass. The helper is inline rather than in Http4sSupport because
ETag/conditional support is currently single-use; lift to a
shared helper when the second endpoint needs it.
Bridge state
------------
v510→v500 path-rewriting bridge is DEFINED in
Implementations5_1_0.v510ToV500Bridge but **not wired** into
wrappedRoutesV510Services. With all 111 endpoints migrated the
bridge could in principle be enabled, but doing so surfaces two
cross-version interaction bugs that need root-causing first:
1. MetricTest's `include_implemented_by_partial_functions=getBanks`
filter returns 0 instead of 12. Setup calls /obp/v5.1.0/banks
should be hijacked to v500.getBanks via the bridge, recording
metrics with partial_function_name=getBanks. Filter doesn't
match — likely the bridged metric records carry a different
URL/version field shape that the filter is keying off.
2. VRPConsentRequestTest's "Create Consent By CONSENT_REQUEST_ID
(EMAIL)" returns 400 instead of 201 (5 scenarios). The v5.1
createVRPConsentRequest stores a payload with `consent_type:
VRP` merged in; the test then calls
/obp/v5.1.0/consumer/consent-requests/X/EMAIL/consents, which
the bridge rewrites to v500.createConsentByConsentRequestId.
That handler tries to parse the payload as
PostVRPConsentRequestJsonInternalV510 and apparently fails —
suspect the merged consent_type field changes the JSON shape
in a way the v500 extractor doesn't tolerate.
Without the bridge, these v5.1.0 URLs fall through Http4sApp's
chain to Http4sLiftWebBridge with the original /obp/v5.1.0/ path,
where Lift's OBPAPI5_1_0 dispatch picks them up via the inherited
APIMethods500 partial functions and serves them correctly. To
re-enable the bridge, append
`.orElse(Implementations5_1_0.v510ToV500Bridge.run(req))` to
wrappedRoutesV510Services and address the two failures above.
Tests: 239 v5.1.0 + 447 cross-version (v5.0/v6/v7/bridge) all green.
… checks CI shard 1 surfaced ForceErrorValidationTest's "We will call the endpoint with user credentials" scenario failing — `errorMessage contains canCreateCustomer.toString() should be (true)` was false. Root cause: the bank/roles reorder I made in commit b274ce7 (matching Lift's "bank before roles" rule) left the three after-authenticate interceptors — Force-Error, AuthType, JsonSchema — running BEFORE the bank/role checks. That made Force-Error fire first, returning the canonical error text "OBP-20006: User is missing one or more roles: " without the role names appended. Lift's wrappedWithAuthCheck (APIUtil.scala:1934-1969) runs the afterAuthenticateInterceptors INSIDE the for-comp's yield block, i.e. only after every checker (auth → bank → roles → account → view → counterparty) has succeeded. When the natural role check fails first, the role-check error message — which formats `UserHasMissingRoles + roles.mkString(" or ")` — short-circuits before the Force-Error interceptor gets a chance. Fix: move processForceError / validateAuthType / validateJsonSchema to the END of the validation chain, after counterparty. Final order now matches Lift exactly: auth → bank → roles → account → view → counterparty → processForceError → validateAuthType → validateJsonSchema ForceErrorValidationTest, MakerCheckerTransactionRequestTest, and BankAttributeTests all pass locally with the new order. 348-test broad regression across v5.0/v5.1/v6/v7/bridge also clean. (MakerCheckerTransactionRequestTest's "Multiple challenges" scenario was the second CI failure but passes locally — likely existing v4 transaction-request flakiness, not middleware-related.)
Single batch covering one Critical, six High, and fourteen Moderate
advisories across the JDBC, crypto, gRPC, HTTP-client, JWT, mail,
log4j, and Elasticsearch stacks. API-compatible bumps throughout;
compile + test-compile clean on both modules; BC, JDBC and http4s
integration smoke suites (24 tests) pass.
- postgresql 42.7.7 -> 42.7.11
- mysql-connector-j 8.1.0 -> 9.4.0
- bcpg-jdk18on, bcpkix-jdk18on 1.78.1 -> 1.81
- com.sun.mail:jakarta.mail:2.0.1 -> org.eclipse.angus:jakarta.mail:2.0.5
(com.sun line abandoned at 2.0.2 still vulnerable; identical
jakarta.mail.* API surface, no source changes needed)
- grpc-{netty-shaded,protobuf,stub,services} 1.48.1 -> 1.71.0
(closes Netty MadeYouReset HTTP/2 DDoS via newer shaded Netty)
- nimbus-jose-jwt 9.37.2 -> 9.48 (latest 9.x; defers 10.x API migration)
- async-http-client 2.10.4 -> 2.15.0 (latest 2.x; 3.x would be a rewrite)
- log4j-api, log4j-core 2.24.3 -> 2.26.0 (pin still required to override
ES's transitive 2.19.0; bumped past the 2.24 Rfc5424/XmlLayout/TLS-host
advisories)
- elasticsearch, elasticsearch-rest-client 8.14.0 -> 8.19.15
- elastic4s-client-esjava 8.5.2 -> 8.11.5
…erts Dependabot rescan after d4c0af7 found that 1.81 / 1.71.0 / 9.48 were not high enough — the actual minimum-patched versions are later. Picks below follow the OSV "fixed" ranges directly. - bcpg-jdk18on, bcpkix-jdk18on 1.81 -> 1.84 (uncontrolled resource consumption + risky-algo fixes only land in 1.84) - grpc-{netty-shaded,protobuf,stub,services} 1.71.0 -> 1.75.0 (shaded Netty's MadeYouReset HTTP/2 DDoS fix lands at grpc 1.75.0) - nimbus-jose-jwt 9.48 -> 10.5 (the 9.x branch past 9.37.4 is unmaintained and remains vulnerable to deeply-nested-JSON DoS; 10.0.2+ has the fix) - oauth2-oidc-sdk 9.27 -> 11.37.1 (paired with nimbus 10 — sdk 9.x was compiled against nimbus-jose-jwt 9.x and would NoSuchMethodError at runtime against nimbus 10; 11.x is the matching major) - commons-beanutils 1.10.1 -> 1.11.0 (Improper Access Control) CertificateUtil.scala: replaced wildcard `import com.nimbusds.jose._` with explicit imports — nimbus 10 added a `com.nimbusds.jose.Option` class which shadowed `scala.Option` in this file. Verified: PasswordResetTest (JWT/Nimbus 10.5), RegulatedEntityTest (BC 1.84), Http4sServerIntegrationTest, Http4s700TransactionTest — 41 tests pass.
The `import com.nimbusds.jose._` at line 265 was functionally dead — every JOSE type referenced in the method (JOSEException, BadJOSEException, JWSAlgorithm, JWSVerificationKeySelector, SecurityContext) is imported explicitly elsewhere in the file. Same hazard the CertificateUtil fix addressed: under Nimbus 10 the wildcard pulls in `com.nimbusds.jose.Option` which shadows `scala.Option`, so any future Option[X] usage in this method would silently break.
|
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.



No description provided.