Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion obp-api/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,7 @@
<dependency>
<groupId>org.apache.pekko</groupId>
<artifactId>pekko-http-core_${scala.version}</artifactId>
<version>1.1.0</version>
<version>${pekko-http.version}</version>
</dependency>
<dependency>
<groupId>org.apache.pekko</groupId>
Expand Down Expand Up @@ -304,6 +304,12 @@
<artifactId>oauth2-oidc-sdk</artifactId>
<version>11.37.1</version>
</dependency>
<!-- json-smart version forced to 2.6.0 via parent pom dependencyManagement
(fixes CVE-2023-1370). -->
<dependency>
<groupId>net.minidev</groupId>
<artifactId>json-smart</artifactId>
</dependency>
<!-- ********** flexmark START ********** -->
<!-- Library flexmark-all v0.40.8 is replaced with used modules -->
<!-- https://mvnrepository.com/artifact/com.vladsch.flexmark/flexmark-profile-pegdown -->
Expand Down
72 changes: 72 additions & 0 deletions obp-api/src/main/scala/code/api/constant/constant.scala
Original file line number Diff line number Diff line change
Expand Up @@ -586,6 +586,78 @@ object Constant extends MdcLoggable {
CAN_ANSWER_TRANSACTION_REQUEST_CHALLENGE
)

// Auditor system view: read-only on the account itself. The auditor can
// see everything but cannot modify account data, counterparties, images,
// locations, aliases, URLs, or initiate / approve payments.
// The only writes allowed are the auditor's own annotations: comments and
// tags (including where_tags). Those are scoped to the auditor's view as
// separate metadata — they do not change the account or its data.
final val SYSTEM_AUDITOR_VIEW_PERMISSION = List(
// See transactions
CAN_SEE_TRANSACTION_THIS_BANK_ACCOUNT,
CAN_SEE_TRANSACTION_OTHER_BANK_ACCOUNT,
CAN_SEE_TRANSACTION_METADATA,
CAN_SEE_TRANSACTION_AMOUNT,
CAN_SEE_TRANSACTION_TYPE,
CAN_SEE_TRANSACTION_CURRENCY,
CAN_SEE_TRANSACTION_START_DATE,
CAN_SEE_TRANSACTION_FINISH_DATE,
CAN_SEE_TRANSACTION_BALANCE,
CAN_SEE_TRANSACTION_STATUS,
// See this account
CAN_SEE_BANK_ACCOUNT_OWNERS,
CAN_SEE_BANK_ACCOUNT_TYPE,
CAN_SEE_BANK_ACCOUNT_BALANCE,
CAN_SEE_BANK_ACCOUNT_CURRENCY,
CAN_SEE_BANK_ACCOUNT_LABEL,
CAN_SEE_BANK_ACCOUNT_NATIONAL_IDENTIFIER,
CAN_SEE_BANK_ACCOUNT_SWIFT_BIC,
CAN_SEE_BANK_ACCOUNT_IBAN,
CAN_SEE_BANK_ACCOUNT_NUMBER,
CAN_SEE_BANK_ACCOUNT_BANK_NAME,
CAN_SEE_BANK_ACCOUNT_BANK_PERMALINK,
CAN_SEE_BANK_ACCOUNT_ROUTING_SCHEME,
CAN_SEE_BANK_ACCOUNT_ROUTING_ADDRESS,
// See bank
CAN_SEE_BANK_ROUTING_SCHEME,
CAN_SEE_BANK_ROUTING_ADDRESS,
// See other accounts
CAN_SEE_OTHER_ACCOUNT_NATIONAL_IDENTIFIER,
CAN_SEE_OTHER_ACCOUNT_SWIFT_BIC,
CAN_SEE_OTHER_ACCOUNT_IBAN,
CAN_SEE_OTHER_ACCOUNT_BANK_NAME,
CAN_SEE_OTHER_ACCOUNT_NUMBER,
CAN_SEE_OTHER_ACCOUNT_METADATA,
CAN_SEE_OTHER_ACCOUNT_KIND,
CAN_SEE_OTHER_ACCOUNT_ROUTING_SCHEME,
CAN_SEE_OTHER_ACCOUNT_ROUTING_ADDRESS,
CAN_SEE_OTHER_BANK_ROUTING_SCHEME,
CAN_SEE_OTHER_BANK_ROUTING_ADDRESS,
// See metadata (everyone's)
CAN_SEE_COMMENTS,
CAN_SEE_OWNER_COMMENT,
CAN_SEE_TAGS,
CAN_SEE_WHERE_TAG,
CAN_SEE_IMAGES,
CAN_SEE_MORE_INFO,
CAN_SEE_URL,
CAN_SEE_IMAGE_URL,
CAN_SEE_OPEN_CORPORATES_URL,
CAN_SEE_CORPORATE_LOCATION,
CAN_SEE_PHYSICAL_LOCATION,
CAN_SEE_PUBLIC_ALIAS,
CAN_SEE_PRIVATE_ALIAS,
// Read counterparties (no modification)
CAN_GET_COUNTERPARTY,
// Auditor's own annotations only
CAN_ADD_COMMENT,
CAN_DELETE_COMMENT,
CAN_ADD_TAG,
CAN_DELETE_TAG,
CAN_ADD_WHERE_TAG,
CAN_DELETE_WHERE_TAG
)

final val ALL_VIEW_PERMISSION_NAMES = List(
CAN_SEE_TRANSACTION_OTHER_BANK_ACCOUNT,
CAN_SEE_TRANSACTION_METADATA,
Expand Down
15 changes: 15 additions & 0 deletions obp-api/src/main/scala/code/api/util/Glossary.scala
Original file line number Diff line number Diff line change
Expand Up @@ -939,6 +939,21 @@ object Glossary extends MdcLoggable {
|
|
|Example value: ${bankIdExample.value}
|
|## Version history
|
|The JSON field name for this identifier changed across OBP-API versions:
|
|- **v6.0.0+** (current): `bank_id` — the canonical field name in both request and response bodies (e.g. `PostBankJson600`, `BankJson600`).
|- **v5.0.0**: `id` (Option[String]) — see `PostBankJson500` / `BankJson500`.
|- **v4.0.0**: `id` (String), plus a now-removed `short_name` field — see `PostBankJson400` / `BankJson400`.
|
|The v6 createBank request body shape is exactly:
|`bank_id`, `bank_code`, `full_name`, `logo`, `website`, `bank_routings`.
|
|If you're regenerating client code from older docs, samples, or LLM training data, double-check
|the field name — sending `id` to v6 endpoints will silently produce an empty `bank_id` and
|fail validation with a confusing length error.
""")

glossaryItems += GlossaryItem(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,12 @@ object ViewNewStyle {
}
}

def factoryResetSystemView(viewId: ViewId, callContext: Option[CallContext]): Future[View] = {
Future(Views.views.vend.factoryResetSystemView(viewId)) map {
unboxFullOrFail(_, callContext, s"$SystemViewNotFound Current ViewId is ${viewId.value}", 404)
}
}

def checkOwnerViewAccessAndReturnOwnerView(user: User, bankAccountId: BankIdAccountId, callContext: Option[CallContext]): Future[View] = {
Future {
user.checkOwnerViewAccessAndReturnOwnerView(bankAccountId, callContext)
Expand Down
7 changes: 7 additions & 0 deletions obp-api/src/main/scala/code/api/v1_4_0/JSONFactory1_4_0.scala
Original file line number Diff line number Diff line change
Expand Up @@ -394,8 +394,15 @@ object JSONFactory1_4_0 extends MdcLoggable{
.find(_.title.toLowerCase.contains(s"${parameter.toLowerCase}"))
.map(_.title).getOrElse("").replaceAll(" ","-")
case _ =>
// First try exact match (e.g. body field "address" → glossary item "address").
// If that fails, fall back to a dotted-suffix match so body fields like
// "bank_id" / "account_id" / "customer_id" resolve to "Bank.bank_id" /
// "Account.account_id" / "Customer.customer_id". Without this fallback
// body-field glossary links render as [field](/glossary#) with an empty
// anchor — see the v5 → v6 createBank rename for a real-world example.
glossaryItems
.find(_.title.toLowerCase.equals(s"${parameter.toLowerCase}"))
.orElse(glossaryItems.find(_.title.toLowerCase.endsWith(s".${parameter.toLowerCase}")))
.map(_.title).getOrElse("").replaceAll(" ","-")
}
}
Expand Down
18 changes: 18 additions & 0 deletions obp-api/src/main/scala/code/api/v6_0_0/APIMethods600.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2221,7 +2221,25 @@ trait APIMethods600 {
cc =>
implicit val ec = EndpointContext(Some(cc))
val failMsg = s"$InvalidJsonFormat The Json body should be the $PostBankJson600 "
// Catch the common v4/v5 → v6 field-rename mistakes before extraction.
// Without this, sending {"id": "..."} silently produces an empty bank_id
// and fails downstream with a confusing BANK_ID length-validation error.
val deprecatedFieldHints: List[String] = json match {
case JObject(fields) => fields.collect {
case JField("id", _) =>
"'id' was renamed to 'bank_id' in v6.0.0 (it was the field name in v4.0.0 and v5.0.0)"
case JField("short_name", _) =>
"'short_name' was removed in v5.0.0 (it only existed in v4.0.0)"
}
case _ => Nil
}
for {
_ <- Helper.booleanToFuture(
failMsg = s"$InvalidJsonFormat Deprecated request-body field(s): ${deprecatedFieldHints.mkString("; ")}. The v6.0.0 createBank body shape is: bank_id, bank_code, full_name, logo, website, bank_routings.",
cc = cc.callContext
) {
deprecatedFieldHints.isEmpty
}
postJson <- NewStyle.function.tryons(failMsg, 400, cc.callContext) {
json.extract[PostBankJson600]
}
Expand Down
86 changes: 84 additions & 2 deletions obp-api/src/main/scala/code/api/v7_0_0/Http4s700.scala
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import code.api.ResourceDocs1_4_0.SwaggerDefinitionsJSON._
import code.api.ResourceDocs1_4_0.{ResourceDocs140, ResourceDocsAPIMethodsUtil}
import code.api.util.APIUtil.{EmptyBody, _}
import code.api.util.{APIUtil, ApiRole, ApiVersionUtils, CallContext, CustomJsonFormats, Glossary, NewStyle}
import code.api.util.ApiRole.{canCreateEntitlementAtAnyBank, canCreateEntitlementAtOneBank, canCreateOrganisation, canCreateRoutingScheme, canDeleteEntitlementAtAnyBank, canDeleteOrganisation, canDeleteRoutingScheme, canGetAccountAccessTrace, canGetAnyOrganisation, canGetAnyUser, canGetCacheConfig, canGetCacheInfo, canGetCacheNamespaces, canGetConnectorHealth, canGetCustomersAtOneBank, canGetDatabasePoolInfo, canGetMigrations, canUpdateBankSupportedRoutingScheme, canUpdateOrganisation, canUpdateRoutingScheme}
import code.api.util.ApiRole.{canCreateEntitlementAtAnyBank, canCreateEntitlementAtOneBank, canCreateOrganisation, canCreateRoutingScheme, canDeleteEntitlementAtAnyBank, canDeleteOrganisation, canDeleteRoutingScheme, canGetAccountAccessTrace, canGetAnyOrganisation, canGetAnyUser, canGetCacheConfig, canGetCacheInfo, canGetCacheNamespaces, canGetConnectorHealth, canGetCustomersAtOneBank, canGetDatabasePoolInfo, canGetMigrations, canUpdateBankSupportedRoutingScheme, canUpdateOrganisation, canUpdateRoutingScheme, canUpdateSystemView}
import code.api.util.ApiTag._
import code.api.util.ErrorMessages._
import code.api.util.http4s.{ErrorResponseConverter, Http4sRequestAttributes, IdempotencyMiddleware, RequestScopeConnection, ResourceDocMiddleware}
Expand All @@ -18,6 +18,7 @@ import code.api.v1_4_0.JSONFactory1_4_0
import code.api.v2_0_0.{BasicViewJson, CreateEntitlementJSON, JSONFactory200}
import code.api.v4_0_0.JSONFactory400
import code.api.v6_0_0.{BasicAccountJsonV600, BasicAccountsJsonV600, BankJsonV600, CacheConfigJsonV600, CacheInfoJsonV600, CacheNamespaceInfoJsonV600, CacheNamespaceJsonV600, CacheNamespacesJsonV600, ConnectorInfoJsonV600, ConnectorsJsonV600, DatabasePoolInfoJsonV600, FeaturesJsonV600, InMemoryCacheStatusJsonV600, JSONFactory600, RedisCacheStatusJsonV600, StoredProcedureConnectorHealthJsonV600, UserV600}
import code.api.v6_0_0.JSONFactory600.ViewJsonV600
import code.api.cache.Redis
import code.bankconnectors.storedprocedure.StoredProcedureUtils
import code.migration.MigrationScriptLogProvider
Expand Down Expand Up @@ -150,7 +151,7 @@ object Http4s700 {
}
}

object Implementations7_0_0 {
object Implementations7_0_0 extends code.util.Helper.MdcLoggable {

// Common prefix: /obp/v7.0.0
val prefixPath = Root / ApiPathZero.toString / implementedInApiVersion.toString
Expand Down Expand Up @@ -3437,6 +3438,87 @@ object Http4s700 {
// ── End BULK ──────────────────────────────────────────────────────────────

// ── Test-only rollback endpoint ───────────────────────────────────────────
// Route: POST /obp/v7.0.0/management/system-views/VIEW_ID/factory-reset
//
// Reset an existing system view's permissions and view-level flags to the
// code-defined defaults. The ViewDefinition row is preserved so any
// AccountAccess records that reference this view remain valid — only the
// contents of the view are wiped and rewritten.
//
// Each successful invocation is audit-logged at INFO level with the
// calling user_id and the reset view_id; this is a high-impact admin
// action and we want a trace of who reset what.
val factoryResetSystemView: HttpRoutes[IO] = HttpRoutes.of[IO] {
case req @ POST -> `prefixPath` / "management" / "system-views" / viewIdStr / "factory-reset" =>
EndpointHelpers.withUser(req) { (user, cc) =>
val viewId = ViewId(viewIdStr)
for {
view <- ViewNewStyle.factoryResetSystemView(viewId, Some(cc))
} yield {
logger.info(
s"AUDIT factoryResetSystemView: user_id=${user.userId} provider=${user.provider} " +
s"view_id=${viewId.value} permissions_count=${view.allowed_actions.size}"
)
JSONFactory600.createViewJsonV600(view)
}
}
}

resourceDocs += ResourceDoc(
null,
implementedInApiVersion,
nameOf(factoryResetSystemView),
"POST",
"/management/system-views/VIEW_ID/factory-reset",
"Factory Reset a System View",
s"""Reset the system view identified by VIEW_ID to the code-defined defaults.
|
|This wipes the view's existing permissions and re-applies whatever the
|running OBP-API code currently defines as the default permission set
|for that system view id. View-level flags (name, description, is_firehose,
|alias settings, is_public) are also restored to defaults.
|
|The underlying view row is preserved, so any AccountAccess records that
|grant users this view on specific accounts remain in place — only the
|contents of the view itself are reset.
|
|Each successful invocation is audit-logged with the calling user_id and
|the reset view_id.
|
|${userAuthenticationMessage(true)}""".stripMargin,
EmptyBody,
ViewJsonV600(
bank_id = "",
account_id = "",
view_id = "auditor",
view_name = "Auditor",
description = "auditor",
metadata_view = "",
is_public = false,
is_system = true,
is_firehose = Some(false),
alias = "",
hide_metadata_if_alias_used = false,
can_grant_access_to_views = Nil,
can_revoke_access_to_views = Nil,
allowed_actions = List(
"can_see_bank_account_balance",
"can_see_transaction_amount",
"can_add_comment",
"can_add_tag"
)
),
List(
$AuthenticatedUserIsRequired,
UserHasMissingRoles,
SystemViewNotFound,
UnknownError
),
apiTagSystemView :: Nil,
Some(List(canUpdateSystemView)),
http4sPartialFunction = Some(factoryResetSystemView)
)

// Enabled only in Lift test mode (Props.testMode == true, i.e. -Drun.mode=test).
// Props.testMode is set from the JVM system property before any props file loads,
// so it is reliably available at object-initialization time unlike file-based props.
Expand Down
Loading
Loading