Skip to content

Commit adff5c1

Browse files
authored
Merge pull request #2733 from simonredfern/develop
consumer_id in consent 250
2 parents f5e63c9 + e13e300 commit adff5c1

File tree

11 files changed

+161
-12
lines changed

11 files changed

+161
-12
lines changed

obp-api/src/main/resources/docs/introductory_system_documentation.md

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -900,7 +900,7 @@ obp.base_url=https://api.example.com
900900

901901
# Client credentials (from OBP consumer registration)
902902
oauth2.client_id=your-client-id
903-
oauth2.redirect_uri=http://localhost:8087/callback
903+
oauth2.redirect_uri=http://localhost:48123/callback
904904
oauth2.client_scope=ReadAccountsDetail ReadBalances ReadTransactionsDetail
905905

906906
# mTLS (if required)
@@ -917,14 +917,14 @@ mvn clean package
917917
# Run locally
918918
java -jar target/obp-hola-app-0.0.29-SNAPSHOT.jar
919919

920-
# Access at http://localhost:8087
920+
# Access at http://localhost:48123
921921
```
922922

923923
**Docker Deployment:**
924924

925925
```bash
926926
docker build -t obp-hola .
927-
docker run -p 8087:8087 \
927+
docker run -p 48123:48123 \
928928
-e OAUTH2_PUBLIC_URL=https://oauth2.example.com \
929929
-e OBP_BASE_URL=https://api.example.com \
930930
obp-hola
@@ -2866,6 +2866,23 @@ super_admin_user_ids=uuid-1,uuid-2
28662866
# Then remove super_admin_user_ids from props
28672867
```
28682868

2869+
**Bootstrap OIDC Operator Consumer:**
2870+
2871+
OBP can bootstrap a Consumer (Application) for OBP-OIDC at startup. This allows OBP-OIDC to authenticate as an application (without a User) and manage consumers via the API, eliminating the need for direct database access.
2872+
2873+
The bootstrap consumer is granted the following Scopes: `CanGetConsumers`, `CanCreateConsumer`, `CanVerifyOidcClient`, `CanGetOidcClient`.
2874+
2875+
These endpoints use `authMode = UserOrApplication`, meaning they can be accessed either by a logged-in User with Entitlements, or by an Application using a Consumer Key with Scopes.
2876+
2877+
```properties
2878+
# Bootstrap OIDC Operator Consumer
2879+
# Both values must be between 10 and 250 characters.
2880+
oidc_operator_consumer_key=your-consumer-key-here
2881+
oidc_operator_consumer_secret=your-consumer-secret-here
2882+
```
2883+
2884+
Note: If you use the Bootstrap OIDC Operator Consumer, you may not need the Bootstrap OIDC Operator User, depending on how OBP-OIDC implements its authentication.
2885+
28692886
**Checking User Entitlements:**
28702887

28712888
```bash

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1151,7 +1151,7 @@ database_messages_scheduler_interval=3600
11511151
# consumer_validation_method_for_consent=CONSUMER_CERTIFICATE
11521152
#
11531153
# consents.max_time_to_live=3600
1154-
# In case isn't defined default value is "false"
1154+
# In case isn't defined default value is "true"
11551155
# consents.sca.enabled=true
11561156
# ---------------------------------------------------------
11571157

obp-api/src/main/scala/code/api/OAuth2.scala

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -501,7 +501,11 @@ object OAuth2Login extends RestHelper with MdcLoggable {
501501
val sub = getClaim(name = "sub", jwtToken = jwtToken)
502502
val email = getClaim(name = "email", jwtToken = jwtToken)
503503
val name = getClaim(name = "name", jwtToken = jwtToken).orElse(description)
504-
val consumerId = if(APIUtil.checkIfStringIsUUID(azp.getOrElse(""))) azp else Some(s"{$azp}_${APIUtil.generateUUID()}")
504+
val consumerId = azp match {
505+
case Some(value) if APIUtil.checkIfStringIsUUID(value) => azp
506+
case Some(value) => Some(s"${value}_${APIUtil.generateUUID()}")
507+
case None => Some(APIUtil.generateUUID())
508+
}
505509
Consumers.consumers.vend.getOrCreateConsumer(
506510
consumerId = consumerId, // Use azp as consumer id if it is uuid value
507511
key = Some(Helpers.randomString(40).toLowerCase),

obp-api/src/main/scala/code/api/ResourceDocs1_4_0/SwaggerDefinitionsJSON.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6208,7 +6208,7 @@ object SwaggerDefinitionsJSON {
62086208
ConfigPropJsonV600("public_obp_sandbox_populator_url", "http://localhost:5178"),
62096209
ConfigPropJsonV600("public_obp_oidc_url", "http://localhost:9000"),
62106210
ConfigPropJsonV600("public_keycloak_url", "http://localhost:7787"),
6211-
ConfigPropJsonV600("public_obp_hola_url", "http://localhost:8087"),
6211+
ConfigPropJsonV600("public_obp_hola_url", "http://localhost:48123"),
62126212
ConfigPropJsonV600("public_obp_mcp_url", "http://localhost:9100"),
62136213
ConfigPropJsonV600("public_obp_opey_url", "http://localhost:5000")
62146214
)

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4093,7 +4093,7 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{
40934093
"public_obp_sandbox_populator_url" -> getPropsValue("public_obp_sandbox_populator_url").openOr("http://localhost:5178"),
40944094
"public_obp_oidc_url" -> getPropsValue("public_obp_oidc_url").openOr("http://localhost:9000"),
40954095
"public_keycloak_url" -> getPropsValue("public_keycloak_url").openOr("http://localhost:7787"),
4096-
"public_obp_hola_url" -> getPropsValue("public_obp_hola_url").openOr("http://localhost:8087"),
4096+
"public_obp_hola_url" -> getPropsValue("public_obp_hola_url").openOr("http://localhost:48123"),
40974097
"public_obp_mcp_url" -> getPropsValue("public_obp_mcp_url").openOr("http://localhost:9100"),
40984098
"public_obp_opey_url" -> getPropsValue("public_obp_opey_url").openOr("http://localhost:5000"),
40994099
"public_rabbit_cats_adapter_url" -> getPropsValue("public_rabbit_cats_adapter_url").openOr("http://localhost:8089")

obp-api/src/main/scala/code/api/util/migration/Migration.scala

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,8 @@ object Migration extends MdcLoggable {
107107
addUniqueIndexOnResourceUserUserId()
108108
addIndexOnMappedMetricUserId()
109109
alterRoleNameLength()
110+
alterConsentRequestColumnConsumerIdLength()
111+
alterMappedConsentColumnConsumerIdLength()
110112
}
111113

112114
private def dummyScript(): Boolean = {
@@ -553,6 +555,20 @@ object Migration extends MdcLoggable {
553555
MigrationOfRoleNameFieldLength.alterRoleNameLength(name)
554556
}
555557
}
558+
559+
private def alterConsentRequestColumnConsumerIdLength(): Boolean = {
560+
val name = nameOf(alterConsentRequestColumnConsumerIdLength)
561+
runOnce(name) {
562+
MigrationOfConsentRequestConsumerIdFieldLength.alterColumnConsumerIdLength(name)
563+
}
564+
}
565+
566+
private def alterMappedConsentColumnConsumerIdLength(): Boolean = {
567+
val name = nameOf(alterMappedConsentColumnConsumerIdLength)
568+
runOnce(name) {
569+
MigrationOfMappedConsent.alterColumnConsumerIdLength(name)
570+
}
571+
}
556572
}
557573

558574
/**
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
package code.api.util.migration
2+
3+
import code.api.util.APIUtil
4+
import code.api.util.migration.Migration.{DbFunction, saveLog}
5+
import code.consent.ConsentRequest
6+
import net.liftweb.common.Full
7+
import net.liftweb.mapper.Schemifier
8+
9+
object MigrationOfConsentRequestConsumerIdFieldLength {
10+
11+
def alterColumnConsumerIdLength(name: String): Boolean = {
12+
DbFunction.tableExists(ConsentRequest) match {
13+
case true =>
14+
val startDate = System.currentTimeMillis()
15+
val commitId: String = APIUtil.gitCommit
16+
var isSuccessful = false
17+
18+
val executedSql =
19+
DbFunction.maybeWrite(true, Schemifier.infoF _) {
20+
APIUtil.getPropsValue("db.driver") match {
21+
case Full(dbDriver) if dbDriver.contains("com.microsoft.sqlserver.jdbc.SQLServerDriver") =>
22+
() =>
23+
"""
24+
|ALTER TABLE consentrequest ALTER COLUMN consumerid varchar(250);
25+
|""".stripMargin
26+
case _ =>
27+
() =>
28+
"""
29+
|ALTER TABLE consentrequest ALTER COLUMN consumerid TYPE character varying(250);
30+
|""".stripMargin
31+
}
32+
}
33+
34+
val endDate = System.currentTimeMillis()
35+
val comment: String =
36+
s"""Executed SQL:
37+
|$executedSql
38+
|""".stripMargin
39+
isSuccessful = true
40+
saveLog(name, commitId, isSuccessful, startDate, endDate, comment)
41+
isSuccessful
42+
43+
case false =>
44+
val startDate = System.currentTimeMillis()
45+
val commitId: String = APIUtil.gitCommit
46+
val isSuccessful = false
47+
val endDate = System.currentTimeMillis()
48+
val comment: String =
49+
s"""${ConsentRequest._dbTableNameLC} table does not exist""".stripMargin
50+
saveLog(name, commitId, isSuccessful, startDate, endDate, comment)
51+
isSuccessful
52+
}
53+
}
54+
}

obp-api/src/main/scala/code/api/util/migration/MigrationOfMappedConsent.scala

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,56 @@ object MigrationOfMappedConsent {
102102
isSuccessful
103103
}
104104
}
105+
// The mConsumerId column was originally MappedUUID (varchar(36)), but Consumer.consumerId
106+
// is MappedString(250) and can hold composite IDs like "{azp_value}_UUID" generated
107+
// by OAuth2.getOrCreateConsumer when the azp claim is not a UUID.
108+
// This migration widens mConsumerId to match the Consumer model.
109+
def alterColumnConsumerIdLength(name: String): Boolean = {
110+
DbFunction.tableExists(MappedConsent) match {
111+
case true =>
112+
val startDate = System.currentTimeMillis()
113+
val commitId: String = APIUtil.gitCommit
114+
var isSuccessful = false
115+
116+
val executedSql =
117+
DbFunction.maybeWrite(true, Schemifier.infoF _) {
118+
APIUtil.getPropsValue("db.driver") match {
119+
case Full(dbDriver) if dbDriver.contains("com.microsoft.sqlserver.jdbc.SQLServerDriver") =>
120+
() =>
121+
"""ALTER TABLE mappedconsent ALTER COLUMN mconsumerid varchar(250);
122+
|""".stripMargin
123+
case Full(dbDriver) if dbDriver.contains("com.mysql.cj.jdbc.Driver") => // MySQL
124+
() =>
125+
"""ALTER TABLE mappedconsent MODIFY COLUMN mconsumerid varchar(250);
126+
|""".stripMargin
127+
case _ =>
128+
() =>
129+
"""ALTER TABLE mappedconsent ALTER COLUMN mconsumerid TYPE character varying(250);
130+
|""".stripMargin
131+
}
132+
}
133+
134+
val endDate = System.currentTimeMillis()
135+
val comment: String =
136+
s"""Executed SQL:
137+
|$executedSql
138+
|""".stripMargin
139+
isSuccessful = true
140+
saveLog(name, commitId, isSuccessful, startDate, endDate, comment)
141+
isSuccessful
142+
143+
case false =>
144+
val startDate = System.currentTimeMillis()
145+
val commitId: String = APIUtil.gitCommit
146+
val isSuccessful = false
147+
val endDate = System.currentTimeMillis()
148+
val comment: String =
149+
s"""${MappedConsent._dbTableNameLC} table does not exist""".stripMargin
150+
saveLog(name, commitId, isSuccessful, startDate, endDate, comment)
151+
isSuccessful
152+
}
153+
}
154+
105155
def alterColumnStatus(name: String): Boolean = {
106156
DbFunction.tableExists(MappedConsent) match {
107157
case true =>

obp-api/src/main/scala/code/consent/ConsentRequest.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ class ConsentRequest extends ConsentRequestTrait with LongKeyedMapper[ConsentReq
2929
//the following are the obp consent.
3030
object ConsentRequestId extends MappedUUID(this)
3131
object Payload extends MappedText(this)
32-
object ConsumerId extends MappedUUID(this) {
32+
object ConsumerId extends MappedString(this, 250) {
3333
override def defaultValue = null
3434
}
3535

obp-api/src/main/scala/code/consent/MappedConsent.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -400,7 +400,7 @@ class MappedConsent extends ConsentTrait with LongKeyedMapper[MappedConsent] wit
400400
override def defaultValue = BCrypt.gensalt()
401401
}
402402
object mJsonWebToken extends MappedText(this)
403-
object mConsumerId extends MappedUUID(this) {
403+
object mConsumerId extends MappedString(this, 250) {
404404
override def defaultValue = null
405405
}
406406
object mConsentRequestId extends MappedUUID(this) {

0 commit comments

Comments
 (0)