Skip to content

Commit 076400e

Browse files
authored
Merge pull request #2877 from simonredfern/develop
Self service bank creation (limited by props)
2 parents 93aca85 + 6179f7c commit 076400e

10 files changed

Lines changed: 531 additions & 37 deletions

File tree

CLAUDE.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ The goal is a full http4s migration — replace Lift Web across all version file
1717

1818
**Key files**: `Http4s700.scala` (v7.0.0 endpoints), `Http4s200.scala` (v2.0.0 endpoints — 37 own + path-rewriting bridge to Http4s140), `Http4s140.scala` (v1.4.0 endpoints — 11 own + path-rewriting bridge to Http4s130), `Http4s130.scala` (v1.3.0 endpoints — 3 own + path-rewriting bridge to Http4s121), `Http4s121.scala` (v1.2.1 endpoints — all 323 API1_2_1Test scenarios), `Http4sSupport.scala` (EndpointHelpers + recordMetric), `ResourceDocMiddleware.scala` (auth, entity resolution, transaction wrapper), `IdempotencyMiddleware.scala` (Redis-backed idempotency, opt-in via `Idempotency-Key` header, nested inside ResourceDocMiddleware), `RequestScopeConnection.scala` (DB transaction propagation to Futures).
1919

20-
**v7.0.0 native endpoints** (46 ResourceDocs): root, corePrivateAccountsAllBanks, deleteEntitlement, addEntitlement, getAccountAccessTrace, getConsentsConfig, getErrorMessages, getUserByUserId, createTradingOffer, getTradingOffer, getTradingOffers, cancelTradingOffer, createMarketOrder, getMarketOrder, cancelMarketOrder, createMarketMatch, getMarketTrade, requestSettlement, notifyDeposit, requestWithdrawal, createPaymentAuth, capturePaymentAuth, releasePaymentAuth, getPaymentAuth, createTestEmail, createValidationEmail, createOrganisation, getOrganisations, getOrganisation, updateOrganisation, deleteOrganisation, createRoutingScheme, getRoutingSchemes, getRoutingScheme, updateRoutingScheme, deleteRoutingScheme, getBankSupportedRoutingSchemes, putBankSupportedRoutingScheme, createPayeeLookup, createTransactionRequestMobileWallet, createTransactionRequestUtility, createTransactionRequestOpenCorridor, createTransactionRequestBulk, factoryResetSystemView. These carry genuinely v7-specific signatures/behaviour. The 20 duplicate "POC" endpoints originally added as migration scaffolding (getBanks, getBank, getCurrentUser, getCoreAccountById, getPrivateAccountByIdFull, getExplicitCounterpartyById, getFeatures, getScannedApiVersions, getConnectors, getProviders, getUsers, getCustomersAtOneBank, getCustomerByCustomerId, getAccountsAtBank, getCacheConfig, getCacheInfo, getDatabasePoolInfo, getStoredProcedureConnectorHealth, getMigrations, getCacheNamespaces) were **removed** — they cascade to their v6 twin via `v700ToV600Bridge` (getExplicitCounterpartyById → v4, no v6/v5 twin), `X-OBP-Version-Served: v6.0.0`. Kept deliberately in v7: `deleteEntitlement` (204), `addEntitlement` (409), `getUserByUserId` (404) — intentional RESTful response-code improvements over the older v6 200/400 convention.
20+
**v7.0.0 native endpoints** (48 ResourceDocs): root, corePrivateAccountsAllBanks, createMyBank, getMyBanks, deleteEntitlement, addEntitlement, getAccountAccessTrace, getConsentsConfig, getErrorMessages, getUserByUserId, createTradingOffer, getTradingOffer, getTradingOffers, cancelTradingOffer, createMarketOrder, getMarketOrder, cancelMarketOrder, createMarketMatch, getMarketTrade, requestSettlement, notifyDeposit, requestWithdrawal, createPaymentAuth, capturePaymentAuth, releasePaymentAuth, getPaymentAuth, createTestEmail, createValidationEmail, createOrganisation, getOrganisations, getOrganisation, updateOrganisation, deleteOrganisation, createRoutingScheme, getRoutingSchemes, getRoutingScheme, updateRoutingScheme, deleteRoutingScheme, getBankSupportedRoutingSchemes, putBankSupportedRoutingScheme, createPayeeLookup, createTransactionRequestMobileWallet, createTransactionRequestUtility, createTransactionRequestOpenCorridor, createTransactionRequestBulk, factoryResetSystemView. These carry genuinely v7-specific signatures/behaviour. The 20 duplicate "POC" endpoints originally added as migration scaffolding (getBanks, getBank, getCurrentUser, getCoreAccountById, getPrivateAccountByIdFull, getExplicitCounterpartyById, getFeatures, getScannedApiVersions, getConnectors, getProviders, getUsers, getCustomersAtOneBank, getCustomerByCustomerId, getAccountsAtBank, getCacheConfig, getCacheInfo, getDatabasePoolInfo, getStoredProcedureConnectorHealth, getMigrations, getCacheNamespaces) were **removed** — they cascade to their v6 twin via `v700ToV600Bridge` (getExplicitCounterpartyById → v4, no v6/v5 twin), `X-OBP-Version-Served: v6.0.0`. Kept deliberately in v7: `deleteEntitlement` (204), `addEntitlement` (409), `getUserByUserId` (404) — intentional RESTful response-code improvements over the older v6 200/400 convention.
2121

2222
**Tests**: `Http4s700RoutesTest` (91 scenarios, port 8087). `makeHttpRequest` returns `(Int, JValue, Map[String, String])`. `makeHttpRequestWithBody(method, path, body, headers)` for POST/PUT.
2323
## Migrating a Lift Endpoint to http4s

obp-api/src/main/resources/props/sample.props.template

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -459,6 +459,13 @@ allow_sandbox_data_import=true
459459
# Secret key that allows access to the "data import" api. You should change this to your own secret key
460460
sandbox_data_import_secret=change_me
461461

462+
## Self-service bank creation (POST /my/banks): the maximum number of banks each registered
463+
## User may create without holding the role CanCreateBank. The bank identity (bank_id, short
464+
## name, full name) is auto-generated by the server — the caller cannot choose it.
465+
## 0 disables the endpoint (default). Users holding CanCreateBank are unaffected by this
466+
## limit — they use POST /banks as usual.
467+
# self_service_bank_creation.limit=0
468+
462469

463470

464471
### API features

obp-api/src/main/scala/code/api/util/ApiSession.scala

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,37 @@ case class CallContext(
153153

154154
// for endpoint body convenient get userId
155155
def userId: String = user.map(_.userId).openOrThrowException(AuthenticatedUserIsRequired)
156+
157+
/**
158+
* The human User this request is really about.
159+
*
160+
* The authenticated `user` may be the human themselves, or an agent user minted by a
161+
* Consent the human granted (e.g. Opey / MCP acting under a consent). Resolution order:
162+
* 1. `onBehalfOfUser` or `consenter`, when a middleware populated them (free);
163+
* 2. otherwise resolve via the delegation registry: the caller's ResourceUser row's
164+
* CreatedByConsentId names the Consent that minted it, and that Consent's userId
165+
* names the granting human;
166+
* 3. otherwise the caller IS the human.
167+
*
168+
* IMPORTANT: this reads only the authenticated user and server-written columns
169+
* (ResourceUser.CreatedByConsentId, MappedConsent.mUserId). It deliberately takes no
170+
* parameters so nothing caller-asserted (body/header/query values) can ever influence
171+
* the resolution — identity-sensitive queries (e.g. /my/banks) depend on that.
172+
*/
173+
def effectiveHumanUserId: String = {
174+
val delegatedHumanUserId = onBehalfOfUser.or(consenter).map(_.userId).filter(_.nonEmpty)
175+
delegatedHumanUserId.openOr {
176+
val authenticatedUserId = user.map(_.userId).openOr("")
177+
val grantingHumanUserId = for {
178+
callerResourceUser <- code.model.dataAccess.ResourceUser.find(
179+
net.liftweb.mapper.By(code.model.dataAccess.ResourceUser.userId_, authenticatedUserId))
180+
consentId <- net.liftweb.common.Full(callerResourceUser.CreatedByConsentId.get)
181+
.filter(id => id != null && id.nonEmpty)
182+
consent <- code.consent.Consents.consentProvider.vend.getConsentByConsentId(consentId)
183+
} yield consent.userId
184+
grantingHumanUserId.filter(_.nonEmpty).openOr(authenticatedUserId)
185+
}
186+
}
156187
def userPrimaryKey: UserPrimaryKey = user.map(_.userPrimaryKey).openOrThrowException(AuthenticatedUserIsRequired)
157188
def loggedInUser: User = user.openOrThrowException(AuthenticatedUserIsRequired)
158189
// for endpoint body convenient get cc.callContext

obp-api/src/main/scala/code/api/util/ErrorMessages.scala

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -637,6 +637,8 @@ object ErrorMessages {
637637
val UpdateCustomerAccountLinkError = "OBP-30227: Could not update the customer account link."
638638
val DeleteCustomerAccountLinkError = "OBP-30228: Could not delete the customer account link."
639639
val GetConsentImplicitSCAError = "OBP-30229: Could not get the implicit SCA consent."
640+
val SelfServiceBankCreationDisabled = "OBP-30230: Self-service bank creation is disabled on this instance."
641+
val SelfServiceBankLimitReached = "OBP-30231: Self-service bank creation limit reached. To create more banks you require the role CanCreateBank."
640642

641643
val CreateSystemViewError = "OBP-30250: Could not create the system view"
642644
val DeleteSystemViewError = "OBP-30251: Could not delete the system view"

0 commit comments

Comments
 (0)