|
| 1 | +# Views Across API Standards (OBP native, Berlin Group, UK Open Banking) |
| 2 | + |
| 3 | +Status: idea / analysis (2026-07-16). Question under discussion: **is it good to have views |
| 4 | +explicitly and exactly named after UK and BG consents?** |
| 5 | + |
| 6 | +Short answer: **no for Berlin Group, accidentally-yes for UK.** A view should be named for the |
| 7 | +*capability it grants* (what data is visible), not for the *standard/channel that asked*. |
| 8 | + |
| 9 | +## 1. Current state — how each standard uses views |
| 10 | + |
| 11 | +The shared spine is identical everywhere: a consent carries a list of |
| 12 | +`(bank_id, account_id, view_id)` triples (`ConsentJWT.views: List[ConsentView]`, |
| 13 | +`ConsentUtil.scala:55`), authorization grants the user real access to those views |
| 14 | +(`grantAccessToViews`, `ConsentUtil.scala:373-399` — revoke-then-regrant per view), and at |
| 15 | +request time the endpoint resolves a `ViewId`, checks access, and uses the returned `View` to |
| 16 | +moderate the response. What differs per standard is *which* view IDs flow through that pipe and |
| 17 | +*who chooses them*. |
| 18 | + |
| 19 | +### OBP native (v1.2.1 → v7.0.0) |
| 20 | +The view is a first-class URL parameter (`VIEW_ID`), resolved by middleware into `cc.view`. |
| 21 | +The caller picks the view; nothing is hardcoded. Arbitrary system and custom views. |
| 22 | + |
| 23 | +### Berlin Group v1.3 |
| 24 | +Fully view-gated, but through a **dedicated BG system-view vocabulary** |
| 25 | +(`constant.scala:151-169`): `ReadAccountsBerlinGroup`, `ReadBalancesBerlinGroup`, |
| 26 | +`ReadTransactionsBerlinGroup`, `InitiatePaymentsBerlinGroup`. |
| 27 | + |
| 28 | +- The BG consent `access` object is translated into `ConsentView`s keyed to those views — |
| 29 | + `createBerlinGroupConsentJWT` / `updateBerlinGroupConsentJWT` |
| 30 | + (`ConsentUtil.scala:823-855, 913-1015`), IBAN → `(bankId, accountId)` resolution via |
| 31 | + `getBankAccountByIban`. |
| 32 | +- Each AIS endpoint hardcodes the matching view: balances → |
| 33 | + `SYSTEM_READ_BALANCES_BERLIN_GROUP_VIEW_ID` (`Http4sBGv13AIS.scala:216, 266`), transactions → |
| 34 | + `SYSTEM_READ_TRANSACTIONS_BERLIN_GROUP_VIEW_ID` (`:287-289, :380-382`), accounts → |
| 35 | + `SYSTEM_READ_ACCOUNTS_BERLIN_GROUP_VIEW_ID` (`:439, :468`). |
| 36 | +- The TPP never names a view; the standard's consent scopes map onto them. |
| 37 | + |
| 38 | +### UK Open Banking v3.1 / v4.0.1 |
| 39 | +Also view-gated, but reusing the **generic** `Read*` system views with a detail-or-basic |
| 40 | +fallback: |
| 41 | + |
| 42 | +- Accounts: `SYSTEM_READ_ACCOUNTS_DETAIL_VIEW_ID` `.or(BASIC)` via `checkViewAccessAndReturnView` |
| 43 | + (`Http4sUKOBv310Accounts.scala:44-58`; `Http4sUKOBv401AccountInfo.scala:324-338`). |
| 44 | +- Balances: `SYSTEM_READ_BALANCES_VIEW_ID` (`Http4sUKOBv310Balances.scala:111-116`). |
| 45 | +- Transactions: detail/basic pair via `checkViewsAccessAndReturnView` |
| 46 | + (`Http4sUKOBv310Transactions.scala:308-315`; `Http4sUKOBv401AccountInfo.scala:2218`). |
| 47 | + UKOB additionally checks the consent itself (`checkUKConsent`) + `passesPsd2Aisp` before the |
| 48 | + view check. |
| 49 | +- Consent side: `createConsentJWT` (UK path, `ConsentUtil.scala:1040-1104`) sets |
| 50 | + **`view_id = permission`** — the OBUK permission strings become the granted view IDs directly |
| 51 | + (`:1063-1088`). If `bankId`/`accountIds` are absent it creates account-unscoped grants with |
| 52 | + `bank_id = null, account_id = null` (`:1078-1085`). |
| 53 | + |
| 54 | +### Known inconsistencies (as of 2026-07-16) |
| 55 | +1. **BG v2 AIS endpoints are pure mocks** — no auth, no consent, no view check |
| 56 | + (`Http4sBGv2AIS.scala:49-135`). If BG v2 goes live, the whole consent→view chain from v1.3 |
| 57 | + needs porting. |
| 58 | +2. **UKOB v2.0.0 hardcodes the `"owner"` view** for all AIS endpoints |
| 59 | + (`Http4sUKOBv200AIS.scala:130, 150`) — a TPP needs full owner access instead of a scoped |
| 60 | + read view. |
| 61 | +3. **Two parallel PSD2 view vocabularies** for the same concept: BG's `*BerlinGroup` views vs |
| 62 | + UK's generic `Read*` views. |
| 63 | +4. **UK `createConsentJWT` trusts the raw permission string as a view ID** and can create |
| 64 | + account-unscoped grants — correctness rests entirely on callers passing valid view IDs. |
| 65 | +5. BG v1.3 uses two different access-check helpers (a local `hasViewAccess`-based |
| 66 | + `checkAccountAccess` for accounts/balances at `Http4sBGv13AIS.scala:57-62` vs |
| 67 | + `ViewNewStyle.checkAccountAccessAndGetView` for transactions). Both enforce, but it's two |
| 68 | + code paths for one job. |
| 69 | +6. Legacy Lift BG v1.3 (`AccountInformationServiceAISApi.scala`) is entirely commented-out dead |
| 70 | + code — the live logic is `Http4sBGv13AIS.scala`. |
| 71 | + |
| 72 | +## 2. What the permission definitions reveal |
| 73 | + |
| 74 | +`MapperViews.applyDefaultsForSystemView` (`MapperViews.scala:757-819`): |
| 75 | + |
| 76 | +- All six generic read views — `ReadAccountsBasic`, `ReadAccountsDetail`, `ReadBalances`, |
| 77 | + `ReadTransactionsBasic`, `ReadTransactionsDebits`, `ReadTransactionsDetail` — get the |
| 78 | + **identical** permission set, `SYSTEM_VIEW_PERMISSION_COMMON` (same as `accountant`). The |
| 79 | + basic/detail distinction the names promise is not yet backed by differing permissions. |
| 80 | +- `ReadAccountsBerlinGroup` and `ReadBalancesBerlinGroup` get **no permissions at all** — a bare |
| 81 | + entity (`MapperViews.scala:785-787`). Only the BG transactions and payments views have tuned |
| 82 | + sets. |
| 83 | + |
| 84 | +So the standard-branded names are not buying differentiated moderation today. The BG views are |
| 85 | +mostly a parallel namespace whose content is empty or duplicative — the naming carries all the |
| 86 | +weight, and naming-by-standard is the wrong axis. |
| 87 | + |
| 88 | +## 3. Why standard-named views are the wrong axis |
| 89 | + |
| 90 | +1. **It scales per-standard, not per-capability.** "Read balances" means the same thing in BG, |
| 91 | + UK, STET, and whatever comes next. Standard-named views mean every new standard mints |
| 92 | + another view set, and "who can see balances on this account?" becomes a query over N views |
| 93 | + that must be kept semantically in sync by hand. |
| 94 | +2. **Views are OBP's primary access-control model and they're user-visible** — they appear in |
| 95 | + account view lists and in grants. Leaking regulatory brand names into that vocabulary couples |
| 96 | + the core ACL model to external specs' branding and lifecycle (a BG rename in a new spec |
| 97 | + version → stale view ID or a migration). |
| 98 | +3. **The real per-standard differences live elsewhere.** BG and UK responses differ in *shape*, |
| 99 | + but shape is handled in each standard's JSONFactory. The view only controls *which underlying |
| 100 | + data is accessible*, and at that layer the standards barely differ. |
| 101 | + |
| 102 | +## 4. Why the UK naming is fine anyway |
| 103 | + |
| 104 | +The UK permission names (`ReadAccountsDetail`, `ReadBalances`, `ReadTransactionsBasic`, …) |
| 105 | +happen to *be* a good generic capability vocabulary — they describe data visibility, not a |
| 106 | +channel. That's why reusing them as shared system views works, and why UKOB v3.1/v4.0.1 feel |
| 107 | +cleaner than BG v1.3. |
| 108 | + |
| 109 | +The wrinkle is the mechanism, not the names: `createConsentJWT` passes the raw permission |
| 110 | +string through as the `view_id`, so the UK spec's vocabulary directly owns part of the view |
| 111 | +namespace. It works because the names are good; the right shape is an explicit |
| 112 | +permission → view mapping table, so a future spec rename doesn't silently mint new views. |
| 113 | + |
| 114 | +## 5. The one honest argument *for* segregation |
| 115 | + |
| 116 | +Grants are per `(user, view, account)`, and `grantAccessToViews` revokes-then-regrants on |
| 117 | +consent authorization. If BG and UK consents shared view IDs, revoking a BG consent could |
| 118 | +clobber access a still-live UK consent granted on the same view+account. The separate BG |
| 119 | +namespace accidentally shields against *cross-standard* interference — though not against two |
| 120 | +consents *within* the same standard, so it's a partial fix for a real bug in the wrong layer. |
| 121 | + |
| 122 | +The proper fix is consent-scoped grant accounting (or checking the consent, not just the view, |
| 123 | +at request time — which the UK path already partly does via `checkUKConsent`). Once that |
| 124 | +exists, the last reason for standard-named views disappears. |
| 125 | + |
| 126 | +## 6. Recommendation |
| 127 | + |
| 128 | +1. **Converge on one capability-named read-view vocabulary** — the existing `Read*` set. |
| 129 | +2. **Give basic vs detail genuinely different permission sets** so the names mean something |
| 130 | + (today all six share `SYSTEM_VIEW_PERMISSION_COMMON`). |
| 131 | +3. **Retarget BG's consent translation at the shared views** — the translation layer in |
| 132 | + `createBerlinGroupConsentJWT` already exists; it just points at the duplicate views. Treat |
| 133 | + the `*BerlinGroup` views as deprecated, with a migration for existing grants |
| 134 | + (`AccountAccess` rows referencing the old view IDs). |
| 135 | +4. **Fix the grant-interference bug at the right layer**: consent-scoped grant accounting or |
| 136 | + request-time consent checks, not view-namespace segregation. |
| 137 | +5. **Make the UK permission → view mapping explicit** instead of `view_id = permission` |
| 138 | + passthrough, and validate/scope the account-unscoped (`null` bank/account) grant path. |
| 139 | +6. Standard-specific code should be exactly two things per standard: its |
| 140 | + consent-scope → view mapping, and its JSON factory. Never the view names themselves. |
0 commit comments