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
22 changes: 22 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,28 @@ ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON SEQUENCES TO obp;

1. Then, restart OBP-API.

### Notes on using MS SQL Server

Set the database connection properties in your props file. You can either embed credentials in the URL or use separate props:

**Option 1: Credentials in the URL**

```
db.driver=com.microsoft.sqlserver.jdbc.SQLServerDriver
db.url=jdbc:sqlserver://YOUR_HOST:1433;databaseName=YOUR_DB;user=YOUR_USER;password=YOUR_PASSWORD;encrypt=true;trustServerCertificate=true
```

**Option 2: Separate props (recommended)**

```
db.driver=com.microsoft.sqlserver.jdbc.SQLServerDriver
db.url=jdbc:sqlserver://YOUR_HOST:1433;databaseName=YOUR_DB;encrypt=true;trustServerCertificate=true
db.user=YOUR_USER
db.password=YOUR_PASSWORD
```

Option 2 is recommended because it keeps credentials out of the URL and avoids URL parsing issues. Note that `db.user` and `db.password` take priority over any credentials in the URL.

### Notes on using Postgres with SSL

Postgres needs to be compiled with SSL support.
Expand Down
13 changes: 7 additions & 6 deletions obp-api/src/main/scala/code/api/util/DBUtil.scala
Original file line number Diff line number Diff line change
Expand Up @@ -82,12 +82,13 @@ object DBUtil {
}

private def getOtherDbConnectionParameters: (String, String, String) = {
val usernameAndPassword = dbUrl.split("\\?").filter(_.contains("user")).mkString
val username = usernameAndPassword.split("&").filter(_.contains("user")).mkString.split("=")(1)
val password = usernameAndPassword.split("&").filter(_.contains("password")).mkString.split("=")(1)
val dbUser = APIUtil.getPropsValue("db.user").getOrElse(username)
val dbPassword = APIUtil.getPropsValue("db.password").getOrElse(password)
(dbUrl, dbUser, dbPassword)
// Split URL parameters by both ? / & (PostgreSQL, MySQL) and ; (SQL Server)
val params = dbUrl.split("[?&;]")
val username = params.find(_.startsWith("user=")).map(_.split("=", 2)(1))
val password = params.find(_.startsWith("password=")).map(_.split("=", 2)(1))
val dbUser = APIUtil.getPropsValue("db.user").orElse(username)
val dbPassword = APIUtil.getPropsValue("db.password").orElse(password)
(dbUrl, dbUser.getOrElse(""), dbPassword.getOrElse(""))
}
// H2 database has specific bd url string which is different compared to other databases
private def getH2DbConnectionParameters: (String, String, String) = {
Expand Down
2 changes: 1 addition & 1 deletion obp-api/src/main/scala/code/api/util/ErrorMessages.scala
Original file line number Diff line number Diff line change
Expand Up @@ -292,7 +292,7 @@ object ErrorMessages {
val NotValidRfc7231Date = "OBP-20257: Request header Date is not in accordance with RFC 7231 "

val DuplicateUsername = "OBP-20258: Duplicate Username. Cannot create Username because it already exists. "

val ExternalUserCheckFailed = "OBP-20259: Could not check username uniqueness against the external provider. The Connector or Adapter may not be running. "


// X.509
Expand Down
237 changes: 237 additions & 0 deletions obp-api/src/main/scala/code/api/util/Glossary.scala

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion obp-api/src/main/scala/code/api/v2_0_0/APIMethods200.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1319,7 +1319,7 @@ trait APIMethods200 {
|""",
createUserJson,
userJsonV200,
List(AuthenticatedUserIsRequired, InvalidJsonFormat, InvalidStrongPasswordFormat, DuplicateUsername, "Error occurred during user creation.", UnknownError),
List(AuthenticatedUserIsRequired, InvalidJsonFormat, InvalidStrongPasswordFormat, DuplicateUsername, ExternalUserCheckFailed, "Error occurred during user creation.", UnknownError),
List(apiTagUser, apiTagOnboarding))

lazy val createUser: OBPEndpoint = {
Expand Down
10 changes: 5 additions & 5 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 @@ -4268,7 +4268,7 @@ trait APIMethods600 {
|""",
createUserJsonV600,
userJsonV200,
List(InvalidJsonFormat, InvalidStrongPasswordFormat, DuplicateUsername, "Error occurred during user creation.", UnknownError),
List(InvalidJsonFormat, InvalidStrongPasswordFormat, DuplicateUsername, ExternalUserCheckFailed, "Error occurred during user creation.", UnknownError),
List(apiTagUser, apiTagOnboarding))

lazy val createUser: OBPEndpoint = {
Expand Down Expand Up @@ -5388,7 +5388,7 @@ trait APIMethods600 {
"74a8ebcc-10e4-4036-bef3-9835922246bf"
),
ResetPasswordUrlJsonV600(
"https://api.example.com/user_mgt/reset_password/QOL1CPNJPCZ4BRMPX3Z01DPOX1HMGU3L"
"https://api.example.com/reset-password/QOL1CPNJPCZ4BRMPX3Z01DPOX1HMGU3L"
),
List(
$AuthenticatedUserIsRequired,
Expand Down Expand Up @@ -5452,9 +5452,9 @@ trait APIMethods600 {
.build()
val jwtToken = CertificateUtil.jwtWithHmacProtection(claimsSet)

// Construct reset URL using portal_hostname
// Construct reset URL using portal_external_url
val resetPasswordLink = APIUtil.getPropsValue("portal_external_url", Constant.HostName) +
"/user_mgt/reset_password/" +
"/reset-password/" +
java.net.URLEncoder.encode(jwtToken, "UTF-8")

// Send email using CommonsEmailWrapper (like createUser does)
Expand Down Expand Up @@ -5562,7 +5562,7 @@ trait APIMethods600 {

// Construct reset URL
val resetPasswordLink = APIUtil.getPropsValue("portal_external_url", Constant.HostName) +
"/user_mgt/reset_password/" +
"/reset-password/" +
java.net.URLEncoder.encode(jwtToken, "UTF-8")

// Send email
Expand Down
6 changes: 3 additions & 3 deletions obp-api/src/main/scala/code/model/dataAccess/AuthUser.scala
Original file line number Diff line number Diff line change
Expand Up @@ -225,13 +225,13 @@ class AuthUser extends MegaProtoUser[AuthUser] with CreatedUpdated with MdcLogga
Nil // All good. Allow username creation
case Failure(failureMsg, exception, chain) =>
logger.warn(s"valUniqueExternally: checkExternalUserExists failed for username: $uniqueUsername, message: $failureMsg, exception: ${exception.map(_.getMessage)}, chain: $chain")
List(FieldError(this, Text(msg)))
List(FieldError(this, Text(ErrorMessages.ExternalUserCheckFailed)))
case Empty =>
logger.warn(s"valUniqueExternally: checkExternalUserExists returned Empty for username: $uniqueUsername")
List(FieldError(this, Text(msg)))
List(FieldError(this, Text(ErrorMessages.ExternalUserCheckFailed)))
case _ => // Any other case we provide error message
logger.warn(s"valUniqueExternally: checkExternalUserExists returned unexpected result for username: $uniqueUsername")
List(FieldError(this, Text(msg)))
List(FieldError(this, Text(ErrorMessages.ExternalUserCheckFailed)))
}
} else {
Nil // All good. Allow username creation
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -132,8 +132,8 @@ class PasswordResetTest extends V600ServerSetup {
response600.body.extractOpt[JSONFactory600.ResetPasswordUrlJsonV600].isDefined should equal(true)
And("The response should contain a valid reset URL")
val resetUrl = (response600.body \ "reset_password_url").extract[String]
resetUrl should include("/user_mgt/reset_password/")
resetUrl.split("/user_mgt/reset_password/").last.length should be > 0
resetUrl should include("/reset-password/")
resetUrl.split("/reset-password/").last.length should be > 0
}

scenario("We will call the endpoint with unvalidated user", ApiEndpoint1, VersionOfApi) {
Expand Down Expand Up @@ -386,10 +386,10 @@ class PasswordResetTest extends V600ServerSetup {
Then("We should get a 201 with a reset URL")
resetUrlResponse.code should equal(201)
val resetUrl = (resetUrlResponse.body \ "reset_password_url").extract[String]
resetUrl should include("/user_mgt/reset_password/")
resetUrl should include("/reset-password/")

And("We extract the JWT token from the URL (URL-decoded)")
val encodedToken = resetUrl.split("/user_mgt/reset_password/").last
val encodedToken = resetUrl.split("/reset-password/").last
val token = java.net.URLDecoder.decode(encodedToken, "UTF-8")
token.length should be > 0

Expand Down