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
39 changes: 35 additions & 4 deletions obp-api/src/main/resources/props/sample.props.template
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,10 @@ db.url=jdbc:h2:./lift_proto.db;NON_KEYWORDS=VALUE;DB_CLOSE_ON_EXIT=FALSE
## (this needs to be a URL)
hostname=http://127.0.0.1:8080

# Used as the base URL for password reset and email validation links sent via email.
# Set this to your frontend/portal URL so that emails contain the correct link.
portal_external_url=http://localhost:5174

## This is only useful for running the api locally via RunWebApp
## If you use it, make sure this matches your hostname port!
## If you want to change the port when running via the command line, use "mvn -Djetty.port=8080 jetty:run" instead
Expand Down Expand Up @@ -693,22 +697,28 @@ defaultBank.bank_id=OBP

################################################################################
## Super Admin Users are used to boot-strap User Entitlements (access to Roles).
## Super Admins listed below can grant them selves the following entitlements:
## Super Admins listed below automatically have the following virtual roles:
## CanCreateEntitlementAtAnyBank
## and
## CanCreateEntitlementAtOneBank
## CanGetAnyUser
## After they have granted these roles, they can grant further roles and remove their
# user_id from the super_admin_user_ids list because its redundant.
## Once you have the roles above you can grant any other system or bank related roles to yourself.
##
## THUS, probably the first thing a Super Admin will do is to grant themselves or other users a number of Roles
## For instance, a Super Admin defined by their user_id in super_admin_user_ids CANNOT carry out actions unless they first give themselves an actual Entitlment to a Role.
## For instance, a Super Admin defined by their user_id in super_admin_user_ids CANNOT carry out actions unless they first give themselves an actual Entitlement to a Role.

## List the Users here, with their user_id(s), that should be Super Admins
super_admin_user_ids=USER_ID1,USER_ID2,
################################################################################


##################################################################################
# List of Users that should automatically have roles needed to call endpoints used by OBP-OIDC or OBP Keycloak Provider.
# The following users will automatically have: CanGetAnyUser, CanVerifyUserCredentials, CanVerifyOidcClient, CanGetOidcClient
# oidc_operator_user_ids=USER_ID1,USER_ID2,
####################################################################################

######## Enable / Disable Versions and individual endpoints. ########
# In OBP, endpoints are defined in various files but made available under a *version*
# e.g. in v3_0_0 (aka v3.0.0) we have endpoints from various versions.
Expand Down Expand Up @@ -813,7 +823,13 @@ autocomplete_at_login_form_enabled=false
# This involves this OBP-API sending an email to the newly registered email provided by the User and the User clicking on a link in that email
# which results in a field being changed in the database.
# To BYPASS this security features (for local development only), set this property to true to skip the email address validation.
#authUser.skipEmailValidation=false
authUser.skipEmailValidation=false

# Expiry time in minutes for email validation JWT tokens (default: 1440 = 24 hours)
email_validation_token_expiry_minutes=1440

# Expiry time in minutes for password reset JWT tokens (default: 120 = 2 hours)
password_reset_token_expiry_minutes=120

# control the create and access to public views.
# allow_public_views=false
Expand Down Expand Up @@ -1598,6 +1614,14 @@ regulated_entities = []
# super_admin_inital_password=681aeeb9f681aeeb9f681aeeb9
# super_admin_email=tom@tesobe.com

# Bootstrap OIDC Operator User
# Given the following credentials, OBP will create a user if they do not already exist.
# This user will be granted: CanGetAnyUser, CanVerifyUserCredentials, CanVerifyOidcClient, CanGetOidcClient, CanGetConsumers
# If you want to use this feature, please set up all three values properly at the same time.
# oidc_operator_username=...
# oidc_operator_initial_password=...
# oidc_operator_email=...


## Ethereum Connector Configuration
## ================================
Expand Down Expand Up @@ -1697,6 +1721,13 @@ securelogging_mask_credit_card=true
securelogging_mask_email=true


############################################
# Signal Channels (Redis-backed ephemeral channels for AI agent coordination)
############################################
# messaging.channel.ttl.seconds=3600
# messaging.channel.max.messages=1000


############################################
# http4s server configuration
############################################
Expand Down
17 changes: 17 additions & 0 deletions obp-api/src/main/resources/props/test.default.props.template
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,23 @@ write_metrics = false
# --for tests don't set it to 127.0.0.1, for some reason
hostname=http://localhost:8016

# Used as the base URL for password reset and email validation links sent via email.
# Set this to your frontend/portal URL so that emails contain the correct link.
portal_external_url=http://localhost:5174

# Set to true to skip email validation on user signup (default: false)
authUser.skipEmailValidation=false

# Expiry time in minutes for email validation JWT tokens (default: 1440 = 24 hours)
email_validation_token_expiry_minutes=1440

# Expiry time in minutes for password reset JWT tokens (default: 120 = 2 hours)
password_reset_token_expiry_minutes=120

# List of Users that should automatically have roles needed to call endpoints used by OBP-OIDC or OBP Keycloak Provider.
# The following users will automatically have: CanGetAnyUser, CanVerifyUserCredentials, CanVerifyOidcClient, CanGetOidcClient
# oidc_operator_user_ids=USER_ID1,USER_ID2,

#this is only useful for running the api locally via RunWebApp
#if you use it, make sure this matches your hostname port!
#if you want to change the port when running via the command line, use "mvn -Djetty.port=8089 jetty:run" instead
Expand Down
65 changes: 64 additions & 1 deletion obp-api/src/main/scala/bootstrap/liftweb/Boot.scala
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ import code.api.attributedefinition.AttributeDefinition
import code.api.berlin.group.ConstantsBG
import code.api.cache.Redis
import code.api.util.APIUtil.{enableVersionIfAllowed, errorJsonResponse, getPropsValue}
import code.api.util.ApiRole.CanCreateEntitlementAtAnyBank
import code.api.util.ApiRole.{CanCreateEntitlementAtAnyBank, CanGetAnyUser, CanVerifyUserCredentials, CanVerifyOidcClient, CanGetOidcClient, CanGetConsumers}
import code.api.util.ErrorMessages.MandatoryPropertyIsNotSet
import code.api.util._
import code.api.util.migration.Migration
Expand Down Expand Up @@ -334,6 +334,8 @@ class Boot extends MdcLoggable {

warnAboutSuperAdminUsers()

createBootstrapOidcOperatorUser()

//launch the scheduler to clean the database from the expired tokens and nonces, 1 hour
DataBaseCleanerScheduler.start(intervalInSeconds = 60*60)

Expand Down Expand Up @@ -1059,6 +1061,67 @@ class Boot extends MdcLoggable {
}
}

/**
* Bootstrap OIDC Operator User
* Given the following credentials, OBP will create a user *if it does not exist already*.
* This user will be granted: CanGetAnyUser, CanVerifyUserCredentials, CanVerifyOidcClient, CanGetOidcClient, CanGetConsumers
*/
private def createBootstrapOidcOperatorUser() = {

val oidcOperatorUsername = APIUtil.getPropsValue("oidc_operator_username", "")
val oidcOperatorInitialPassword = APIUtil.getPropsValue("oidc_operator_initial_password", "")
val oidcOperatorEmail = APIUtil.getPropsValue("oidc_operator_email", "")

val isPropsNotSetProperly = oidcOperatorUsername == "" || oidcOperatorInitialPassword == "" || oidcOperatorEmail == ""

val existingAuthUser = AuthUser.find(By(AuthUser.username, oidcOperatorUsername))

if (isPropsNotSetProperly) {
//Nothing happens, props is not set
} else if (existingAuthUser.isDefined) {
logger.error(s"createBootstrapOidcOperatorUser- Errors: Existing AuthUser with username ${oidcOperatorUsername} detected in data import where no ResourceUser was found")
} else {
val authUser = AuthUser.create
.email(oidcOperatorEmail)
.firstName(oidcOperatorUsername)
.lastName(oidcOperatorUsername)
.username(oidcOperatorUsername)
.password(oidcOperatorInitialPassword)
.passwordShouldBeChanged(false)
.validated(true)

val validationErrors = authUser.validate

if (!validationErrors.isEmpty)
logger.error(s"createBootstrapOidcOperatorUser- Errors: ${validationErrors.map(_.msg)}")
else {
Full(authUser.save())

val userBox = Users.users.vend.getUserByProviderAndUsername(authUser.getProvider(), authUser.username.get)

val oidcOperatorRoles = List(
CanGetAnyUser,
CanVerifyUserCredentials,
CanVerifyOidcClient,
CanGetOidcClient,
CanGetConsumers
)

userBox match {
case Full(user) =>
oidcOperatorRoles.foreach { role =>
val resultBox = Entitlement.entitlement.vend.addEntitlement("", user.userId, role.toString)
if (resultBox.isEmpty) {
logger.error(s"createBootstrapOidcOperatorUser- Error granting ${role}: ${resultBox}")
}
}
case _ =>
logger.error(s"createBootstrapOidcOperatorUser- Error: Could not find user after creation")
}
}
}
}

LiftRules.statelessDispatch.append(aliveCheck)

}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2135,8 +2135,7 @@ object SwaggerDefinitionsJSON {
username = usernameExample.value,
password = "String",
first_name = "Simon",
last_name = "Redfern",
validating_application = Some("OBP-Portal")
last_name = "Redfern"
)


Expand Down Expand Up @@ -6189,6 +6188,59 @@ object SwaggerDefinitionsJSON {
account_access_requests = List(accountAccessRequestJsonV600)
)

// Signal Channels swagger examples
lazy val postSignalMessageJsonV600 = PostSignalMessageJsonV600(
payload = net.liftweb.json.parse("""{"agent_name": "my-agent", "capabilities": ["summarize", "search"]}"""),
message_type = Some("announce"),
to_user_id = None
)

lazy val signalMessageJsonV600 = SignalMessageJsonV600(
message_id = "d8839721-2e41-4c60-9bba-42c5a7164027",
channel_name = "discovery",
sender_consumer_id = "7uy8a7e4-6d02-40e3-a129-0b2bf89de8uh",
sender_user_id = "9ca9a7e4-6d02-40e3-a129-0b2bf89de9b1",
to_user_id = None,
timestamp = "2026-02-20T10:30:00Z",
message_type = "announce",
payload = net.liftweb.json.parse("""{"agent_name": "my-agent", "capabilities": ["summarize", "search"]}""")
)

lazy val signalMessagesJsonV600 = SignalMessagesJsonV600(
channel_name = "discovery",
messages = List(signalMessageJsonV600),
total_count = 1,
has_more = false
)

lazy val signalMessagePublishedJsonV600 = SignalMessagePublishedJsonV600(
message_id = "d8839721-2e41-4c60-9bba-42c5a7164027",
channel_name = "discovery",
timestamp = "2026-02-20T10:30:00Z",
channel_message_count = 1
)

lazy val signalChannelInfoJsonV600 = SignalChannelInfoJsonV600(
channel_name = "discovery",
message_count = 5,
ttl_seconds = 3500
)

lazy val signalChannelsJsonV600 = SignalChannelsJsonV600(
channels = List(signalChannelInfoJsonV600)
)

lazy val signalStatsJsonV600 = SignalStatsJsonV600(
total_channels = 3,
total_messages = 12,
channels = List(signalChannelInfoJsonV600)
)

lazy val signalChannelDeletedJsonV600 = SignalChannelDeletedJsonV600(
channel_name = "discovery",
deleted = true
)

//The common error or success format.
//Just some helper format to use in Json
case class NotSupportedYet()
Expand Down
Loading