Skip to content

Commit 19dfaa3

Browse files
committed
Consumer Account Access
1 parent a7edc44 commit 19dfaa3

12 files changed

Lines changed: 823 additions & 0 deletions

‎obp-api/src/main/scala/bootstrap/liftweb/Boot.scala‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1159,6 +1159,7 @@ object ToSchemify extends MdcLoggable {
11591159
code.glossaryitem.DynamicGlossaryItem,
11601160
code.platformapp.PlatformApp,
11611161
code.platformapp.PlatformAppRequiredScope,
1162+
code.consumeraccountaccess.ConsumerAccountAccess,
11621163
code.domainapi.DomainApi,
11631164
code.dynamicentitywebhook.DynamicEntityWebhook,
11641165
code.accountcreation.AccountCreation,

‎obp-api/src/main/scala/code/api/util/APIUtil.scala‎

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3651,6 +3651,7 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{
36513651
case Some(u) if hasAccountFirehoseAccessAtBank(view,u, bankIdAccountId.bankId) => Full(true) //Login User and Firehose access
36523652
case Some(u) if hasAccountFirehoseAccess(view,u) => Full(true)//Login User and Firehose access
36533653
case Some(u) if u.hasAccountAccess(view, bankIdAccountId, callContext)=> Full(true) // Login User and check view access
3654+
case Some(u) if hasConsumerAccountAccess(view, bankIdAccountId, u, callContext) => Full(true) // An application acting as itself, on its own grant
36543655
case Some(u) =>
36553656
// Normal checks failed — try ABAC as fallback
36563657
checkAbacAccountAccess(u, view, bankIdAccountId, callContext)
@@ -3659,6 +3660,28 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{
36593660
}
36603661
}
36613662

3663+
/**
3664+
* Account access granted to a Consumer (code.consumeraccountaccess): true when the caller is an application
3665+
* acting as itself, its Consumer holds a grant for this view of this account, and the endpoint opts in.
3666+
*
3667+
* - Acting as itself means the caller is the Consumer's own client-credentials User (provider obp-oidc#client,
3668+
* named after the Consumer's key). A person calling through the same Consumer never picks up its grants,
3669+
* and neither does a caller identified by a Consumer Key alone: the token proves the client secret.
3670+
* - The endpoint opts in through its auth mode: only ApplicationOnly and UserXORApplication endpoints admit
3671+
* an application acting as itself, so every other endpoint of the view keeps needing a User's access.
3672+
*/
3673+
def hasConsumerAccountAccess(view: View, bankIdAccountId: BankIdAccountId, user: User, callContext: Option[CallContext]): Boolean =
3674+
(for {
3675+
cc <- callContext
3676+
doc <- cc.resourceDocument
3677+
if doc.authMode == ApplicationOnly || doc.authMode == UserXORApplication
3678+
consumer <- cc.consumer.toOption
3679+
if isApplicationsOwnUser(user, consumer)
3680+
} yield code.consumeraccountaccess.ConsumerAccountAccesses.consumerAccountAccessProvider.vend
3681+
.find(consumer.consumerId.get, bankIdAccountId.bankId, bankIdAccountId.accountId, view.viewId)
3682+
.isDefined
3683+
).getOrElse(false)
3684+
36623685
private def checkAbacAccountAccess(
36633686
user: User,
36643687
view: View,

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -651,6 +651,9 @@ object ErrorMessages {
651651
val RetentionHoldAlreadyReleased = "OBP-30621: This retention hold has already been released."
652652
val InvalidRetentionRunApproval = "OBP-30622: Invalid approval. decision must be approve or reject."
653653
val InvalidRetentionRun = "OBP-30623: Invalid retention run. mode must be preview or execute."
654+
val ConsumerAccountAccessAlreadyExists = "OBP-30627: This Consumer already has access to this view of this account."
655+
val ConsumerAccountAccessNotFound = "OBP-30628: Consumer account access not found by CONSUMER_ACCOUNT_ACCESS_ID on this account."
656+
val CannotGrantConsumerAccountAccessToView = "OBP-30629: A Consumer cannot be given access to this view. The owner view and public views are for Users: give the Consumer a view with only the permissions it needs."
654657

655658
// Routing Scheme registry (OBP-30514 .. OBP-30525)
656659
val RoutingSchemeNotFound = "OBP-30514: Routing Scheme not found. Please specify a valid value for SCHEME."

‎obp-api/src/main/scala/code/api/util/Glossary.scala‎

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7428,6 +7428,39 @@ object Glossary extends MdcLoggable {
74287428
|- [Update Current Consumer Platform App](${apiExplorerUrl}/resource-docs/OBPv7.0.0?operationid=OBPv7.0.0-updateCurrentConsumerPlatformApp): `PUT /obp/v7.0.0/consumers/current/platform-app`, for an app to declare the Scopes it needs.
74297429
|- [Get Current Consumer Scopes](${apiExplorerUrl}/resource-docs/OBPv7.0.0?operationid=OBPv7.0.0-getCurrentConsumerScopes): `GET /obp/v7.0.0/consumers/current/scopes`, for an app to read the Scopes its Consumer holds.
74307430
|- [Create Scope for a Consumer](${apiExplorerUrl}/resource-docs/OBPv7.0.0?operationid=OBPv7.0.0-addScope): `POST /obp/v7.0.0/consumers/CONSUMER_ID/scopes`, to grant a missing Scope.
7431+
|
7432+
|A Scope says which endpoints an app may call. An app that also acts on an account through a view, such as initiating payments from it, needs access to that view of the account as well: see ${getGlossaryItemLink("Consumer Account Access")}.
7433+
|""".stripMargin)
7434+
7435+
glossaryItems += GlossaryItem(
7436+
title = "Consumer Account Access",
7437+
description =
7438+
s"""
7439+
|# Consumer Account Access
7440+
|
7441+
|OBP has two layers of permission. **Roles** say which endpoints may be called; they are granted to a User as Entitlements and to a Consumer as Scopes. **Account access** says which accounts may be used, and through which view; it is granted to a User, and with Consumer Account Access also to a Consumer.
7442+
|
7443+
|It is for an application that acts on an account as itself, with its own application token (OAuth2 client credentials), when no person is involved: for example a bank's service that initiates payments from the bank's own account for customers whose payments it relays. Without it, such a service has to log in as a stand-in User who holds the account access.
7444+
|
7445+
|## How it works
7446+
|
7447+
|1. Someone who may grant views of the account (their view lists the target view in `can_grant_access_to_views`, or has `can_grant_access_to_custom_views` for a custom view) gives the Consumer access to a view. The grant records who gave it.
7448+
|2. The application calls an endpoint of that view with its client-credentials token. Its User is then the Consumer's own (provider `obp-oidc#client`, named after the Consumer's key), and OBP finds the Consumer's grant.
7449+
|3. The grant counts only on endpoints that admit an application acting as itself (auth mode ApplicationOnly or UserXORApplication). On every other endpoint the view still needs a User's account access.
7450+
|
7451+
|A person who logs in through the same Consumer does not get the Consumer's account access, and a caller identified by the Consumer Key alone does not either: only the client-credentials token proves the application is calling as itself.
7452+
|
7453+
|## Which view
7454+
|
7455+
|The owner view and public views can't be granted to a Consumer. Holding an account is for people. Give the Consumer a view with only the permissions it needs, for example a custom view with `can_add_transaction_request_to_any_account` for initiating payments: create it with Create Custom View (Management), `POST /obp/v6.0.0/management/banks/BANK_ID/accounts/ACCOUNT_ID/views` (Role CanCreateCustomView), and grant it through the ManageCustomViews system view, which has `can_grant_access_to_custom_views` and which the account holder can give through owner. The Transaction Requests the Consumer creates record it.
7456+
|
7457+
|## Endpoints
7458+
|
7459+
|- [Grant Consumer access to View](${apiExplorerUrl}/resource-docs/OBPv7.0.0?operationid=OBPv7.0.0-grantConsumerAccessToView): `POST /obp/v7.0.0/banks/BANK_ID/accounts/ACCOUNT_ID/views/VIEW_ID/consumer-account-access`
7460+
|- [Get Consumer access to Account](${apiExplorerUrl}/resource-docs/OBPv7.0.0?operationid=OBPv7.0.0-getConsumerAccountAccess): `GET /obp/v7.0.0/banks/BANK_ID/accounts/ACCOUNT_ID/views/VIEW_ID/consumer-account-access`
7461+
|- [Revoke Consumer access to View](${apiExplorerUrl}/resource-docs/OBPv7.0.0?operationid=OBPv7.0.0-revokeConsumerAccessToView): `DELETE /obp/v7.0.0/banks/BANK_ID/accounts/ACCOUNT_ID/views/VIEW_ID/consumer-account-access/CONSUMER_ACCOUNT_ACCESS_ID`
7462+
|
7463+
|See also ${getGlossaryItemLink("Platform Apps")}.
74317464
|""".stripMargin)
74327465

74337466
glossaryItems += GlossaryItem(

‎obp-api/src/main/scala/code/api/v7_0_0/Http4s700.scala‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4764,6 +4764,8 @@ object Http4s700 {
47644764
|
47654765
|Requires `open_corridor_enabled=true` on this instance. Whether it is on shows in GET /banks/BANK_ID/transaction-request-types, which lists OPEN_CORRIDOR_PROMISE exactly when it is.
47664766
|
4767+
|The caller is either a User with account access to VIEW_ID, or an application acting as itself (OAuth2 client credentials) whose Consumer has been given access to VIEW_ID of this account (see Consumer Account Access). The view needs `can_add_transaction_request_to_any_account` or `can_add_transaction_request_to_beneficiary`. The Transaction Request records the Consumer that created it.
4768+
|
47674769
|Authentication is Required.""".stripMargin,
47684770
openCorridorBodyExample,
47694771
JSONFactory700.TransactionRequestWithChargeOpenCorridorJsonV700(
@@ -4800,6 +4802,7 @@ object Http4s700 {
48004802
InsufficientAuthorisationToCreateTransactionRequest, UnknownError),
48014803
apiTagTransactionRequest :: Nil,
48024804
None,
4805+
authMode = UserXORApplication,
48034806
http4sPartialFunction = Some(createTransactionRequestOpenCorridor)
48044807
)
48054808

@@ -7722,6 +7725,9 @@ object Http4s700 {
77227725
// Data retention: retention policies, runs and their approval, the disposal register, legal holds and the archive.
77237726
resourceDocs ++= Http4s700Retention.resourceDocs
77247727

7728+
// Consumer account access: a view of an account granted to a Consumer, for an application acting as itself.
7729+
resourceDocs ++= Http4s700ConsumerAccountAccess.resourceDocs
7730+
77257731
// The order in which the routes are tried. ResourceDocMiddleware selects the doc of the first
77267732
// route that serves a request, so it is given this same list: the doc that validates a request
77277733
// is the doc of the route that runs it.

0 commit comments

Comments
 (0)