Skip to content

feat: atomic cascades, SCA challenge recipients, exact rate limits and card fixes - #2963

Merged
simonredfern merged 99 commits into
OpenBankProject:developfrom
hongwei1:develop
Nov 6, 2026
Merged

simonredfern merged 99 commits into
OpenBankProject:developfrom
hongwei1:develop

Conversation

@hongwei1

@hongwei1 hongwei1 commented Nov 4, 2026 •

Copy link
Copy Markdown
Contributor

This brings the develop branch of hongwei1/OBP-API into develop. The /management/cascading deletes are registered only when the new prop allow_cascade_delete is true (default false) and run atomically, rolling back to a savepoint when a step fails; the account and bank cascades also remove account holders, the SCA challenges of consents bound to the account and card attributes, and the bank cascade removes the bank's dynamic entities with their records while leaving the system space alone. A consent's OBP_CONSENT_CHALLENGE rows are deleted once they can no longer be used, and a PSU can refuse a consent it has not authorised without SCA. EMAIL and SMS challenge passcodes go to the linked customers' addresses, or else to the user's own confirmed address; a challenge is saved only once its passcode is sent, a transaction request that cannot get enough challenges is marked FAILED (OBP-35059, OBP-35060), and the error does not reveal a phone number.

Account holders get InitiatePaymentsBerlinGroup on refresh, also on accounts they already hold, and scaRedirect is omitted when psu_make_payment_sca_redirect_url is not set. Consumer rate limits apply exactly from from_date to to_date, api_name, api_version and bank_id are refused (OBP-30624), and the v3.1.0/v4.0.0 Set Rate Limits refuses a consumer with several rate limits (OBP-30625) and takes effect at once. Creating or updating an ABAC rule with invalid code answers the error validate reports (OBP-38002, OBP-38005, OBP-38010). Delete Card Attribute (v7.0.0) is new and goes through the connector, Delete Card (v3.1.0) declares CanDeleteCardsForBank, and http4s and BouncyCastle are at 0.23.35 and 1.85. Each change has its own tests and came through hongwei1#108 to hongwei1#127. Not covered yet: two first Set Rate Limits calls racing on a consumer with no rate limit can each create one.

DeleteAccountCascade now removes the MapperAccountHolders rows of the
deleted bank_id/account_id, and DeleteBankCascade removes them for every
account of the bank through it. A holder row that outlived its account was
read back by AuthUser.refreshUser (for example when the former holder
created another account) and the five system views were granted on the
deleted account again.
A consent's OBP_CONSENT_CHALLENGE rows are deleted when the PSU answers
one correctly on POST .../consents/CONSENT_ID/authorise (the answered
challenge and any other one started on the consent), and when the
consent becomes REVOKED, REJECTED or EXPIRED through updateConsentStatus,
revoke or the UK expiry sweep. A wrong answer keeps its row so the
attempt counter still applies. Berlin Group consent challenges and
transaction request challenges are left as they are, since they are read
after answering.
…s bound to it

DELETE /obp/v4.0.0/management/cascading/banks/{BANK_ID}/accounts/{ACCOUNT_ID}
now also removes the SCA challenges of every consent whose JWT views name the
account. The consents are read from the users who hold or can see the account,
before their account access rows are deleted. The bank cascade deletes each
account through the same code.
…llenge table

The account cascade looked for consents through the users who hold or
can see the account, which misses a Berlin Group consent whose first
challenge is still unanswered: no PSU is bound to it yet. The consents
are now read from the challenges that carry a consent id and kept when
their JWT names the account.

The test now uses such a Berlin Group consent instead of an authorised
UK consent, whose challenge is deleted once answered.
…points

The Dynamic Entity cascade deletes back up to a ZZ_BAK_ entity before deleting and are used by API Manager, so they stay available whatever the prop says. The tests check that they are still routed with the prop off.
…hout SCA

PUT /banks/BANK_ID/consents/CONSENT_ID changed nothing while
consents.sca.enabled was on, yet answered 200, so Portal's Deny on a
Berlin Group consent showed the consent as denied while it stayed
received. Setting REJECTED on a consent that is INITIATED, received or
AWAITINGAUTHORISATION now stores the refusal, as rejected for a Berlin
Group consent; other changes keep their behaviour.
CascadeDeleteEndpoints.gated now also reads Implementations4_0_0.routesInOrder, so a route left in the chain without its doc is caught: the middleware never runs such a route, and the 404 requests alone cannot see it. The 404 check also goes through the v5.0.0, v5.1.0 and v6.0.0 prefixes, and the enabled suite says which prop is missing when it fails.
The five ResourceDocs are registered as before and removed again by cascadeDeleteRoutes when allow_cascade_delete is false, the same set that leaves their routes out of routesInOrder, so a cascade endpoint added later needs one entry. The prop is read once per JVM.
The Resource Docs responses are cached in Redis, so after the prop changes the catalog can show the old list until the cache TTL passes or the rd_static and rd_all namespaces are invalidated.
With allow_cascade_delete on, the five CanDelete*Cascade roles are in ApiRole.availableRoles; with it off they are not.
…te is false

The five CanDelete*Cascade roles are checked only by the /management/cascading endpoints, so while those are left out ApiRole.availableRoles leaves the roles out too: GET /roles, the glossary, the role check of v3.0.0 entitlement creation and of consents. ApiRole.valueOf still resolves them, so entitlements granted earlier keep working.
feat: hide the /management/cascading endpoints unless allow_cascade_delete is true
Trivy reports a CRITICAL vulnerability with a fix available in each: CVE-2026-69204 in
http4s-ember-core 0.23.30 (fixed in 0.23.35) and CVE-2026-8763 in bcprov-jdk18on 1.84,
which bcpg and bcpkix pull in (fixed in 1.85).
…link

The new scenarios fail on the current code: the holder gets no
InitiatePaymentsBerlinGroup access on refresh, and scaRedirect repeats the
payment id and is built from the missing-prop error text.
…caRedirect once

The mapped connector now generates the payment initiation view with the other
Berlin Group views, so a holder can initiate a payment after a refresh. The
scaRedirect link carries the payment id once and is left out when
psu_make_payment_sca_redirect_url is not set.

Closes #110
… link behaviour

A blank psu_make_payment_sca_redirect_url no longer yields a relative link.
The Boot comment and sample props now say that the mapped connector creates
the payment initiation view on refresh and that the link is optional. Tests
cover a second refresh and a user who holds no account.
…de and a new endpoint

The scenarios fail on the current code: a deleted card and a cascaded account
leave their MappedCardAttribute rows, and there is no endpoint that deletes
one attribute.
Within a request the connection ignores rollback and is committed when the request ends, whatever the response, so a cascade that failed part way kept what it had deleted. DeletionUtil.databaseAtomicTask now takes a savepoint and rolls back to it when the cascade throws, the request connection passes rollback(Savepoint) through to the real connection, and the DB.rollback calls the cascades made, which did nothing there, are gone.
The savepoint handling of databaseAtomicTask moves into withSavepoint, which takes the connection, so it can be tested on its own; behaviour is unchanged.
…connection

A savepoint is released on success and rolled back to on failure, a database that cannot release a savepoint does not fail a cascade that succeeded, a failed rollback keeps the cascade's exception, and rollback(Savepoint) on the request connection reaches the real one.
The Oracle and SQL Server drivers do not support releaseSavepoint; releasing it is now best effort, and the savepoint ends with the transaction.
… limits

With several rate limits the legacy PUT must change none of them, its change must apply at once, the consumer's only rate limit is still changed or created, and v4.0.0 refuses a scope field and takes an empty one as not set.
…te limits, and apply its change at once

PUT /management/consumers/CONSUMER_ID/consumer/call-limits (v3.1.0 and v4.0.0) changes the consumer's rate limit or creates it; with more than one rate limit, which v6.0.0 allows and sums, it now answers 400 OBP-30583 instead of overwriting the most recent one. Its change clears the rate limit cache as the other writes do, so it applies at once, and v4.0.0 refuses api_name, api_version and bank_id like v6.0.0. The existing rate limit tests expected the old cached behaviour and an endpoint scope nothing enforced; they now set the limit from another consumer and expect 429 on the call that goes over it.
A row an older v4.0.0 stored with "" for api_name, api_version and bank_id is the consumer's only rate limit and must be the one the PUT changes.
The legacy PUT finds and counts the consumer's rate limits through the getters, which take an empty bank_id, api_version or api_name as not set, so a row an older version stored with "" is changed instead of getting a second row next to it. The scaladoc of rateLimitScopeFields is no longer stacked on another function's.
… to tell

Redis rounds the counter's TTL to whole seconds, so a one-second window reads as expired about half a second after the first call; a run slower than that cannot show the limit applied.
… doc

The middleware enforces a resource doc's roles before the handler runs, so
listing CanCreateCardsForBank made deleting a card need both roles.
fix: hold a rate limit to its exact dates and refuse unenforced scope fields
fix: delete a bank's dynamic entities and records in the bank cascade
fix: make the cascading deletes atomic
Create and update passed the failed validation to unboxFullOrFail with a
message lacking an OBP code, so every invalid rule answered OBP-50005.
…imits-put

# Conflicts:
#	scripts/resource_doc_baseline/parity_allowlist.json
fix: send challenge passcodes to the user when no customer is linked and report a failed send
…ctor

fix: delete a card attribute through the connector
fix: grant the payment initiation view on accounts already held and keep existing opt-in views current
fix(cards): declare CanDeleteCardsForBank on the delete card resource doc
fix: keep the legacy Set Rate Limits from overwriting one of several rate limits
The create and update scenarios now check that validate reports OBP-38005 for a field reference, OBP-38002 for an incomplete expression (the compiler reports it as a failed compilation) and OBP-38010 for a tautology, before checking that create and update answer the same; the update scenarios carry the updateAbacRule tag.
fix(abac): answer rejected rule code with the error validate reports
Upstream now gives OBP-30582 and OBP-30583 to assets, so the rate limit
errors RateLimitScopeNotSupported and RateLimitNotUnique move to
OBP-30624 and OBP-30625.
@sonarqubecloud

sonarqubecloud Bot commented Nov 6, 2026

Copy link
Copy Markdown

@simonredfern
simonredfern merged commit f6c1bf5 into OpenBankProject:develop Nov 6, 2026
14 checks passed
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.

2 participants