Note: By default we don't serve some older OBP standards. Also v6.0.0 version of /api/versions shows is_active

This commit is contained in:
simonredfern 2025-12-11 20:04:50 +01:00
parent ce4f09d392
commit a93e860fed
5 changed files with 130 additions and 4 deletions

View File

@ -720,9 +720,10 @@ super_admin_user_ids=USER_ID1,USER_ID2,
# For a VERSION (the version in path e.g. /obp/v4.0.0) to be allowed, it must be:
# 1) Absent from here (high priority):
# Note the default is empty, not the example here.
# Black List of Versions
#api_disabled_versions=[OBPv3.0.0,BGv1.3]
# Since December 2025 we are removing older versions of OBP standards by default. Note any endpoints defined in these versions are still
# available via calling v5.1.0 or v6.0.0. We're doing this so API Explorer (II) loads faster etc.
#api_disabled_versions=["OBPv1.2.1,OBPv1.3.0,OBPv1.4.0,OBPv2.0.0,OBPv2.1.0,OBPv2.2.0,OBPv3.0.0,OBPv3.1.0,OBPv4.0.0,OBPv5.0.0"]
# 2) Present here OR this entry must be empty:
# Note the default is empty, not the example here.

View File

@ -2678,7 +2678,14 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{
case JField("ccy", x) => JField("currency", x)
}
def getDisabledVersions() : List[String] = APIUtil.getPropsValue("api_disabled_versions").getOrElse("").replace("[", "").replace("]", "").split(",").toList.filter(_.nonEmpty)
def getDisabledVersions() : List[String] = {
val defaultDisabledVersions = "OBPv1.2.1,OBPv1.3.0,OBPv1.4.0,OBPv2.0.0,OBPv2.1.0,OBPv2.2.0,OBPv3.0.0,OBPv3.1.0,OBPv4.0.0,OBPv5.0.0"
val disabledVersions = APIUtil.getPropsValue("api_disabled_versions").getOrElse(defaultDisabledVersions).replace("[", "").replace("]", "").split(",").toList.filter(_.nonEmpty)
if (disabledVersions.nonEmpty) {
logger.info(s"Disabled API versions: ${disabledVersions.mkString(", ")}")
}
disabledVersions
}
def getDisabledEndpointOperationIds() : List[String] = APIUtil.getPropsValue("api_disabled_endpoints").getOrElse("").replace("[", "").replace("]", "").split(",").toList.filter(_.nonEmpty)

View File

@ -26,7 +26,7 @@ import code.api.v5_0_0.JSONFactory500
import code.api.v5_0_0.{ViewJsonV500, ViewsJsonV500}
import code.api.v5_1_0.{JSONFactory510, PostCustomerLegalNameJsonV510}
import code.api.dynamic.entity.helper.{DynamicEntityHelper, DynamicEntityInfo}
import code.api.v6_0_0.JSONFactory600.{DynamicEntityDiagnosticsJsonV600, DynamicEntityIssueJsonV600, GroupJsonV600, GroupMembershipJsonV600, GroupMembershipsJsonV600, GroupsJsonV600, PostGroupJsonV600, PostGroupMembershipJsonV600, PostResetPasswordUrlJsonV600, PutGroupJsonV600, ReferenceTypeJsonV600, ReferenceTypesJsonV600, ResetPasswordUrlJsonV600, RoleWithEntitlementCountJsonV600, RolesWithEntitlementCountsJsonV600, ValidateUserEmailJsonV600, ValidateUserEmailResponseJsonV600, createActiveCallLimitsJsonV600, createCallLimitJsonV600, createCurrentUsageJson}
import code.api.v6_0_0.JSONFactory600.{DynamicEntityDiagnosticsJsonV600, DynamicEntityIssueJsonV600, GroupJsonV600, GroupMembershipJsonV600, GroupMembershipsJsonV600, GroupsJsonV600, PostGroupJsonV600, PostGroupMembershipJsonV600, PostResetPasswordUrlJsonV600, PutGroupJsonV600, ReferenceTypeJsonV600, ReferenceTypesJsonV600, ResetPasswordUrlJsonV600, RoleWithEntitlementCountJsonV600, RolesWithEntitlementCountsJsonV600, ScannedApiVersionJsonV600, ValidateUserEmailJsonV600, ValidateUserEmailResponseJsonV600, createActiveCallLimitsJsonV600, createCallLimitJsonV600, createCurrentUsageJson}
import code.api.v6_0_0.OBPAPI6_0_0
import code.metrics.APIMetrics
import code.bankconnectors.LocalMappedConnectorInternal
@ -65,6 +65,7 @@ import scala.collection.immutable.{List, Nil}
import scala.collection.mutable.ArrayBuffer
import scala.concurrent.Future
import scala.concurrent.duration._
import scala.jdk.CollectionConverters._
import scala.util.Random
@ -1245,6 +1246,90 @@ trait APIMethods600 {
}
}
staticResourceDocs += ResourceDoc(
getScannedApiVersions,
implementedInApiVersion,
nameOf(getScannedApiVersions),
"GET",
"/api/versions",
"Get Scanned API Versions",
s"""Get all scanned API versions available in this codebase.
|
|This endpoint returns all API versions that have been discovered/scanned, along with their active status.
|
|**Response Fields:**
|
|* `url_prefix`: The URL prefix for the version (e.g., "obp", "berlin-group", "open-banking")
|* `api_standard`: The API standard name (e.g., "OBP", "BG", "UK", "STET")
|* `api_short_version`: The version number (e.g., "v4.0.0", "v1.3")
|* `fully_qualified_version`: The fully qualified version combining standard and version (e.g., "OBPv4.0.0", "BGv1.3")
|* `is_active`: Boolean indicating if the version is currently enabled and accessible
|
|**Active Status:**
|
|* `is_active=true`: Version is enabled and can be accessed via its URL prefix
|* `is_active=false`: Version is scanned but disabled (via `api_disabled_versions` props)
|
|**Use Cases:**
|
|* Discover what API versions are available in the codebase
|* Check which versions are currently enabled
|* Verify that disabled versions configuration is working correctly
|* API documentation and discovery
|
|**Note:** This differs from v4.0.0's `/api/versions` endpoint which shows all scanned versions without is_active status.
|
|""",
EmptyBody,
ListResult(
"scanned_api_versions",
List(
ScannedApiVersionJsonV600(url_prefix = "obp", api_standard = "OBP", api_short_version = "v1.2.1", fully_qualified_version = "OBPv1.2.1", is_active = true),
ScannedApiVersionJsonV600(url_prefix = "obp", api_standard = "OBP", api_short_version = "v1.3.0", fully_qualified_version = "OBPv1.3.0", is_active = true),
ScannedApiVersionJsonV600(url_prefix = "obp", api_standard = "OBP", api_short_version = "v1.4.0", fully_qualified_version = "OBPv1.4.0", is_active = true),
ScannedApiVersionJsonV600(url_prefix = "obp", api_standard = "OBP", api_short_version = "v2.0.0", fully_qualified_version = "OBPv2.0.0", is_active = true),
ScannedApiVersionJsonV600(url_prefix = "obp", api_standard = "OBP", api_short_version = "v2.1.0", fully_qualified_version = "OBPv2.1.0", is_active = true),
ScannedApiVersionJsonV600(url_prefix = "obp", api_standard = "OBP", api_short_version = "v2.2.0", fully_qualified_version = "OBPv2.2.0", is_active = true),
ScannedApiVersionJsonV600(url_prefix = "obp", api_standard = "OBP", api_short_version = "v3.0.0", fully_qualified_version = "OBPv3.0.0", is_active = true),
ScannedApiVersionJsonV600(url_prefix = "obp", api_standard = "OBP", api_short_version = "v3.1.0", fully_qualified_version = "OBPv3.1.0", is_active = true),
ScannedApiVersionJsonV600(url_prefix = "obp", api_standard = "OBP", api_short_version = "v4.0.0", fully_qualified_version = "OBPv4.0.0", is_active = true),
ScannedApiVersionJsonV600(url_prefix = "obp", api_standard = "OBP", api_short_version = "v5.0.0", fully_qualified_version = "OBPv5.0.0", is_active = true),
ScannedApiVersionJsonV600(url_prefix = "obp", api_standard = "OBP", api_short_version = "v5.1.0", fully_qualified_version = "OBPv5.1.0", is_active = true),
ScannedApiVersionJsonV600(url_prefix = "obp", api_standard = "OBP", api_short_version = "v6.0.0", fully_qualified_version = "OBPv6.0.0", is_active = true),
ScannedApiVersionJsonV600(url_prefix = "berlin-group", api_standard = "BG", api_short_version = "v1.3", fully_qualified_version = "BGv1.3", is_active = false)
)
),
List(
UnknownError
),
List(apiTagDocumentation, apiTagApi),
Some(Nil)
)
lazy val getScannedApiVersions: OBPEndpoint = {
case "api" :: "versions" :: Nil JsonGet _ => { cc =>
implicit val ec = EndpointContext(Some(cc))
Future {
val versions: List[ScannedApiVersionJsonV600] =
ApiVersion.allScannedApiVersion.asScala.toList
.filter(version => version.urlPrefix.trim.nonEmpty)
.map { version =>
ScannedApiVersionJsonV600(
url_prefix = version.urlPrefix,
api_standard = version.apiStandard,
api_short_version = version.apiShortVersion,
fully_qualified_version = version.fullyQualifiedVersion,
is_active = versionIsAllowed(version)
)
}
(
ListResult("scanned_api_versions", versions),
HttpCode.`200`(cc.callContext)
)
}
}
}
staticResourceDocs += ResourceDoc(
createCustomer,
implementedInApiVersion,

View File

@ -646,4 +646,12 @@ case class PostResetPasswordUrlJsonV600(username: String, email: String, user_id
case class ResetPasswordUrlJsonV600(reset_password_url: String)
case class ScannedApiVersionJsonV600(
url_prefix: String,
api_standard: String,
api_short_version: String,
fully_qualified_version: String,
is_active: Boolean
)
}

View File

@ -77,11 +77,36 @@ object OBPAPI6_0_0 extends OBPRestHelper
// Exclude v5.1.0 root endpoint since v6.0.0 has its own
lazy val endpointsOf5_1_0_without_root = OBPAPI5_1_0.routes.filterNot(_ == Implementations5_1_0.root)
/*
* IMPORTANT: Endpoint Exclusion Pattern
*
* excludeEndpoints is used to filter out old endpoints when v6.0.0 has a DIFFERENT URL pattern.
*
* WHEN TO EXCLUDE:
* - Old and new endpoints have DIFFERENT URLs (e.g., v4.0.0: /users/:username vs v6.0.0: /providers/:provider/users/:username)
* - The old endpoint should not be accessible via v6.0.0 at all
*
* WHEN NOT TO EXCLUDE:
* - Old and new endpoints have the SAME URL and HTTP method (e.g., GET /api/versions)
* - In this case, collectResourceDocs() automatically deduplicates by (URL, method) and keeps newest version
* - Excluding by function name would remove BOTH versions since they share the same name!
*
* Why? The routing works as follows:
* 1. endpoints list = endpointsOf6_0_0 ++ endpointsOf5_1_0_without_root (contains BOTH old and new)
* 2. allResourceDocs = collectResourceDocs() deduplicates docs by (URL, method), keeps newest
* 3. excludeEndpoints filters ResourceDocs by partialFunctionName (removes by name, not by version)
* 4. getAllowedEndpoints() filters endpoints to only those with matching ResourceDocs
*
* Pattern: Add nameOf(Implementations{version}.endpointName) :: with a comment explaining why
*/
lazy val excludeEndpoints =
nameOf(Implementations3_0_0.getUserByUsername) :: // following 4 endpoints miss Provider parameter in the URL, we introduce new ones in V600.
nameOf(Implementations3_1_0.getBadLoginStatus) ::
nameOf(Implementations3_1_0.unlockUser) ::
nameOf(Implementations4_0_0.lockUser) ::
// NOTE: getScannedApiVersions is NOT excluded here because it has the same URL in both v4.0.0 and v6.0.0
// collectResourceDocs() automatically deduplicates by (URL, HTTP method) and keeps the newest version (v6.0.0)
// Excluding by function name would incorrectly filter out BOTH versions since they share the same function name
nameOf(Implementations4_0_0.createUserWithAccountAccess) :: // following 3 endpoints miss ViewId parameter in the URL, we introduce new ones in V600.
nameOf(Implementations4_0_0.grantUserAccessToView) ::
nameOf(Implementations4_0_0.revokeUserAccessToView) ::