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
Original file line number Diff line number Diff line change
Expand Up @@ -1071,6 +1071,15 @@ object SwaggerDefinitionsJSON {
bank_routings = Some(List(bankRoutingJsonV121))
)

lazy val postBankJson600 = PostBankJson600(
bank_id = bankIdExample.value,
bank_code = bankCodeExample.value,
full_name = Some(fullNameExample.value),
logo = Some(logoExample.value),
website = Some(websiteExample.value),
bank_routings = Some(List(bankRoutingJsonV121))
)

lazy val banksJSON400 = BanksJson400(
banks = List(bankJson400)
)
Expand Down
2 changes: 1 addition & 1 deletion obp-api/src/main/scala/code/api/directlogin.scala
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,7 @@ object DirectLogin extends RestHelper with MdcLoggable {
}
}
//return a Map containing the directLogin parameters : prameter -> value
private def getAllParameters: Map[String, String] = {
def getAllParameters: Map[String, String] = {
def toMap(parametersList: String) = {
//transform the string "directLogin_prameter="value""
//to a tuple (directLogin_parameter,Decoded(value))
Expand Down
8 changes: 6 additions & 2 deletions obp-api/src/main/scala/code/api/util/APIUtil.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3114,11 +3114,15 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{
(user, callContext)
}
} // Direct Login i.e DirectLogin: token=eyJhbGciOiJIUzI1NiJ9.eyIiOiIifQ.Y0jk1EQGB4XgdqmYZUHT6potmH3mKj5mEaA9qrIXXWQ
else if (getPropsAsBoolValue("allow_direct_login", true) && has2021DirectLoginHeader(cc.requestHeaders)) {
else if (getPropsAsBoolValue("allow_direct_login", true) && has2021DirectLoginHeader(cc.requestHeaders) && !url.contains("/my/logins/direct")) {
DirectLogin.getUserFromDirectLoginHeaderFuture(cc)
} // Direct Login Deprecated i.e Authorization: DirectLogin token=eyJhbGciOiJIUzI1NiJ9.eyIiOiIifQ.Y0jk1EQGB4XgdqmYZUHT6potmH3mKj5mEaA9qrIXXWQ
else if (getPropsAsBoolValue("allow_direct_login", true) && hasDirectLoginHeader(cc.authReqHeaderField)) {
else if (getPropsAsBoolValue("allow_direct_login", true) && hasDirectLoginHeader(cc.authReqHeaderField) && !url.contains("/my/logins/direct")) {
DirectLogin.getUserFromDirectLoginHeaderFuture(cc)
} else if (getPropsAsBoolValue("allow_direct_login", true) &&
(has2021DirectLoginHeader(cc.requestHeaders) || hasDirectLoginHeader(cc.authReqHeaderField)) &&
url.contains("/my/logins/direct")) {
Future{(Empty, Some(cc))}
} // Gateway Login
else if (getPropsAsBoolValue("allow_gateway_login", false) && hasGatewayHeader(cc.authReqHeaderField)) {
APIUtil.getPropsValue("gateway.host") match {
Expand Down
3 changes: 2 additions & 1 deletion obp-api/src/main/scala/code/api/util/RSAUtil.scala
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,8 @@ object RSAUtil extends MdcLoggable {
}

def main(args: Array[String]): Unit = {
val randomString = """G!y"k9GHD$D"""
// Use props configuration or generate random password to avoid hard-coded credentials
val randomString = APIUtil.getPropsValue("DEMO_DB_PASSWORD", net.liftweb.util.Helpers.randomString(16) + "!A1")
val db = "jdbc:postgresql://localhost:5432/obp_mapped?user=obp&password=%s".format(randomString)
val res = encrypt(db)
println("db.url: " + db)
Expand Down
160 changes: 157 additions & 3 deletions obp-api/src/main/scala/code/api/v6_0_0/APIMethods600.scala
Original file line number Diff line number Diff line change
@@ -1,19 +1,23 @@
package code.api.v6_0_0

import code.api.{APIFailureNewStyle, ObpApiFailure}
import code.api.{APIFailureNewStyle, DirectLogin, ObpApiFailure}
import code.api.v6_0_0.JSONFactory600
import code.api.ResourceDocs1_4_0.SwaggerDefinitionsJSON._
import code.api.util.APIUtil._
import code.api.util.ApiRole.{canDeleteRateLimiting, canReadCallLimits, canSetCallLimits}
import code.api.util.ApiRole.{CanCreateEntitlementAtOneBank, CanReadDynamicResourceDocsAtOneBank, canCreateBank, canDeleteRateLimiting, canReadCallLimits, canSetCallLimits}
import code.api.util.ApiTag._
import code.api.util.ErrorMessages.{$UserNotLoggedIn, InvalidDateFormat, InvalidJsonFormat, UnknownError, _}
import code.api.util.FutureUtil.EndpointContext
import code.api.util.{NewStyle, RateLimitingUtil}
import code.api.util.{APIUtil, ErrorMessages, NewStyle, RateLimitingUtil}
import code.api.util.NewStyle.HttpCode
import code.api.v5_0_0.{JSONFactory500, PostBankJson500}
import code.api.v6_0_0.JSONFactory600.{createActiveCallLimitsJsonV600, createCallLimitJsonV600, createCurrentUsageJson}
import code.bankconnectors.LocalMappedConnectorInternal
import code.bankconnectors.LocalMappedConnectorInternal._
import code.entitlement.Entitlement
import code.ratelimiting.RateLimitingDI
import code.util.Helper
import code.util.Helper.SILENCE_IS_GOLDEN
import code.views.Views
import com.github.dwickern.macros.NameOf.nameOf
import com.openbankproject.commons.ExecutionContext.Implicits.global
Expand Down Expand Up @@ -405,6 +409,156 @@ trait APIMethods600 {
val transactionRequestType = TransactionRequestType("ETH_SEND_RAW_TRANSACTION")
LocalMappedConnectorInternal.createTransactionRequest(bankId, accountId, viewId , transactionRequestType, json)
}


staticResourceDocs += ResourceDoc(
createBank,
implementedInApiVersion,
"createBank",
"POST",
"/banks",
"Create Bank",
s"""Create a new bank (Authenticated access).
|
|The user creating this will be automatically assigned the Role CanCreateEntitlementAtOneBank.
|Thus the User can manage the bank they create and assign Roles to other Users.
|
|Only SANDBOX mode
|The settlement accounts are created specified by the bank in the POST body.
|Name and account id are created in accordance to the next rules:
| - Incoming account (name: Default incoming settlement account, Account ID: OBP_DEFAULT_INCOMING_ACCOUNT_ID, currency: EUR)
| - Outgoing account (name: Default outgoing settlement account, Account ID: OBP_DEFAULT_OUTGOING_ACCOUNT_ID, currency: EUR)
|
|""",
postBankJson600,
bankJson500,
List(
InvalidJsonFormat,
$UserNotLoggedIn,
InsufficientAuthorisationToCreateBank,
UnknownError
),
List(apiTagBank),
Some(List(canCreateBank))
)

lazy val createBank: OBPEndpoint = {
case "banks" :: Nil JsonPost json -> _ => {
cc =>
implicit val ec = EndpointContext(Some(cc))
val failMsg = s"$InvalidJsonFormat The Json body should be the $PostBankJson600 "
for {
postJson <- NewStyle.function.tryons(failMsg, 400, cc.callContext) {
json.extract[PostBankJson600]
}

checkShortStringValue = APIUtil.checkOptionalShortString(postJson.bank_id)
_ <- Helper.booleanToFuture(failMsg = s"$checkShortStringValue.", cc = cc.callContext) {
checkShortStringValue == SILENCE_IS_GOLDEN
}

_ <- Helper.booleanToFuture(failMsg = ErrorMessages.InvalidConsumerCredentials, cc = cc.callContext) {
cc.callContext.map(_.consumer.isDefined == true).isDefined
}
_ <- Helper.booleanToFuture(failMsg = s"$InvalidJsonFormat Min length of BANK_ID should be greater than 3 characters.", cc = cc.callContext) {
postJson.bank_id.length > 3
}
_ <- Helper.booleanToFuture(failMsg = s"$InvalidJsonFormat BANK_ID can not contain space characters", cc = cc.callContext) {
!postJson.bank_id.contains(" ")
}
_ <- Helper.booleanToFuture(failMsg = s"$InvalidJsonFormat BANK_ID can not contain `::::` characters", cc = cc.callContext) {
!`checkIfContains::::`(postJson.bank_id)
}
(banks, callContext) <- NewStyle.function.getBanks(cc.callContext)
_ <- Helper.booleanToFuture(failMsg = ErrorMessages.bankIdAlreadyExists, cc = cc.callContext) {
!banks.exists { b => postJson.bank_id.contains(b.bankId.value) }
}
(success, callContext) <- NewStyle.function.createOrUpdateBank(
postJson.bank_id,
postJson.full_name.getOrElse(""),
postJson.bank_code,
postJson.logo.getOrElse(""),
postJson.website.getOrElse(""),
postJson.bank_routings.getOrElse(Nil).find(_.scheme == "BIC").map(_.address).getOrElse(""),
"",
postJson.bank_routings.getOrElse(Nil).filterNot(_.scheme == "BIC").headOption.map(_.scheme).getOrElse(""),
postJson.bank_routings.getOrElse(Nil).filterNot(_.scheme == "BIC").headOption.map(_.address).getOrElse(""),
callContext
)
entitlements <- NewStyle.function.getEntitlementsByUserId(cc.userId, callContext)
entitlementsByBank = entitlements.filter(_.bankId == postJson.bank_id)
_ <- entitlementsByBank.exists(_.roleName == CanCreateEntitlementAtOneBank.toString()) match {
case true =>
// Already has entitlement
Future()
case false =>
Future(Entitlement.entitlement.vend.addEntitlement(postJson.bank_id, cc.userId, CanCreateEntitlementAtOneBank.toString()))
}
_ <- entitlementsByBank.exists(_.roleName == CanReadDynamicResourceDocsAtOneBank.toString()) match {
case true =>
// Already has entitlement
Future()
case false =>
Future(Entitlement.entitlement.vend.addEntitlement(postJson.bank_id, cc.userId, CanReadDynamicResourceDocsAtOneBank.toString()))
}
} yield {
(JSONFactory500.createBankJSON500(success), HttpCode.`201`(callContext))
}
}
}


staticResourceDocs += ResourceDoc(
directLoginEndpoint,
implementedInApiVersion,
nameOf(directLoginEndpoint),
"POST",
"/my/logins/direct",
"Direct Login",
s"""This endpoint allows users to create a DirectLogin token to access the API.
|
|DirectLogin is a simple authentication flow. You POST your credentials (username, password, and consumer key)
|to the DirectLogin endpoint and receive a token in return.
|
|This is an alias to the legacy DirectLogin endpoint that includes the standard API versioning prefix.
|
|This endpoint requires the following headers:
|- DirectLogin: username=YOUR_USERNAME, password=YOUR_PASSWORD, consumer_key=YOUR_CONSUMER_KEY
|OR
|- Authorization: DirectLogin username=YOUR_USERNAME, password=YOUR_PASSWORD, consumer_key=YOUR_CONSUMER_KEY
|
|Example header:
|DirectLogin: username=YOUR_USERNAME, password=YOUR_PASSWORD, consumer_key=GET-YOUR-OWN-API-KEY-FROM-THE-OBP
|
|The token returned can be used as a bearer token in subsequent API calls.
|
|""".stripMargin,
EmptyBody,
JSONFactory600.createTokenJSON("DirectLoginToken{eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwczpcL1wvd3d3Lm9wZW5iYW5rcHJvamVjdC5jb20iLCJpYXQiOjE0NTU4OTQyNzYsImV4cCI6MTQ1NTg5Nzg3NiwiYXVkIjoib2JwLWFwaSIsInN1YiI6IjA2Zjc0YjUwLTA5OGYtNDYwNi1hOGNjLTBjNDc5MjAyNmI5ZCIsImNvbnN1bWVyX2tleSI6IjYwNGY3ZTAyNGQ5MWU2MzMwNGMzOGM0YzRmZjc0MjMwZGU5NDk4NTEwNjgxZWNjM2Q5MzViNWQ5MGEwOTI3ODciLCJyb2xlIjoiY2FuX2FjY2Vzc19hcGkifQ.f8xHvXP5fDxo5-LlfTj1OQS9oqHNZfFd7N-WkV2o4Cc}"),
List(
InvalidDirectLoginParameters,
InvalidLoginCredentials,
InvalidConsumerCredentials,
UnknownError
),
List(apiTagUser),
Some(List()))


lazy val directLoginEndpoint: OBPEndpoint = {
case "my" :: "logins" :: "direct" :: Nil JsonPost _ =>
cc => implicit val ec = EndpointContext(Some(cc))
for {
(httpCode: Int, message: String, userId: Long) <- DirectLogin.createTokenFuture(DirectLogin.getAllParameters)
_ <- Future { DirectLogin.grantEntitlementsToUseDynamicEndpointsInSpacesInDirectLogin(userId) }
} yield {
if (httpCode == 200) {
(JSONFactory600.createTokenJSON(message), HttpCode.`201`(cc.callContext))
} else {
unboxFullOrFail(Empty, None, message, httpCode)
}
}
}

}
}
Expand Down
19 changes: 19 additions & 0 deletions obp-api/src/main/scala/code/api/v6_0_0/JSONFactory6.0.0.scala
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,14 @@ package code.api.v6_0_0
import code.api.util.APIUtil.stringOrNull
import code.api.util.RateLimitingPeriod.LimitCallPeriod
import code.api.util._
import code.api.v1_2_1.BankRoutingJsonV121
import code.api.v2_0_0.{EntitlementJSONs, JSONFactory200}
import code.api.v3_0_0.{UserJsonV300, ViewJSON300, ViewsJSON300}
import code.api.v3_1_0.{RateLimit, RedisCallLimitJson}
import code.entitlement.Entitlement
import code.util.Helper.MdcLoggable
import com.openbankproject.commons.model._

import java.util.Date

case class CardanoPaymentJsonV600(
Expand All @@ -58,6 +60,10 @@ case class CardanoMetadataStringJsonV600(
string: String
)

case class TokenJSON(
token: String
)

case class CallLimitPostJsonV600(
from_date: java.util.Date,
to_date: java.util.Date,
Expand Down Expand Up @@ -136,6 +142,15 @@ case class UserJsonV600(
case class UserV600(user: User, entitlements: List[Entitlement], views: Option[Permission])
case class UsersJsonV600(current_user: UserV600, on_behalf_of_user: UserV600)

case class PostBankJson600(
bank_id: String,
bank_code: String,
full_name: Option[String],
logo: Option[String],
website: Option[String],
bank_routings: Option[List[BankRoutingJsonV121]]
)

object JSONFactory600 extends CustomJsonFormats with MdcLoggable{

def createCurrentUsageJson(rateLimits: List[((Option[Long], Option[Long]), LimitCallPeriod)]): Option[RedisCallLimitJson] = {
Expand Down Expand Up @@ -219,4 +234,8 @@ object JSONFactory600 extends CustomJsonFormats with MdcLoggable{
total_per_month_call_limit = rateLimitings.map(_.perMonthCallLimit).sum
)
}

def createTokenJSON(token: String): TokenJSON = {
TokenJSON(token)
}
}
7 changes: 3 additions & 4 deletions obp-api/src/test/scala/code/api/DirectLoginTest.scala
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import code.api.v3_0_0.UserJsonV300
import code.consumer.Consumers
import code.loginattempts.LoginAttempt
import code.model.dataAccess.AuthUser
import code.setup.{APIResponse, ServerSetup}
import code.setup.{APIResponse, ServerSetup, TestPasswordConfig}
import code.userlocks.UserLocksProvider
import com.github.dwickern.macros.NameOf.nameOf
import com.openbankproject.commons.model.ErrorMessage
Expand Down Expand Up @@ -39,9 +39,8 @@ class DirectLoginTest extends ServerSetup with BeforeAndAfter {
val SECRET = randomString(40).toLowerCase
val EMAIL = randomString(10).toLowerCase + "@example.com"
val USERNAME = "username with spaces"
//these are password, but sonarcloud: "password" detected here, make sure this is not a hard-coded credential.
val NO_EXISTING_PW = "notExistingPassword"
val VALID_PW = """G!y"k9GHD$D"""
val NO_EXISTING_PW = TestPasswordConfig.INVALID_PASSWORD
val VALID_PW = TestPasswordConfig.VALID_PASSWORD

val KEY_DISABLED = randomString(40).toLowerCase
val SECRET_DISABLED = randomString(40).toLowerCase
Expand Down
59 changes: 59 additions & 0 deletions obp-api/src/test/scala/code/api/v6_0_0/BankTests.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package code.api.v6_0_0

import code.api.ResourceDocs1_4_0.SwaggerDefinitionsJSON.postBankJson500
import code.api.util.APIUtil.OAuth._
import code.api.util.ApiRole.CanCreateBank
import code.api.util.ErrorMessages
import code.api.util.ErrorMessages.UserHasMissingRoles
import code.api.v6_0_0.APIMethods600.Implementations6_0_0
import code.setup.DefaultUsers
import com.github.dwickern.macros.NameOf.nameOf
import com.openbankproject.commons.model.ErrorMessage
import com.openbankproject.commons.util.ApiVersion
import net.liftweb.json.Serialization.write
import org.scalatest.Tag

class BankTests extends V600ServerSetup with DefaultUsers {

override def beforeAll() {
super.beforeAll()
}

override def afterAll() {
super.afterAll()
}

/**
* Test tags
* Example: To run tests with tag "getPermissions":
* mvn test -D tagsToInclude
*
* This is made possible by the scalatest maven plugin
*/
object VersionOfApi extends Tag(ApiVersion.v6_0_0.toString)
object ApiEndpoint1 extends Tag(nameOf(Implementations6_0_0.createBank))

feature(s"Assuring that endpoint createBank works as expected - $VersionOfApi") {

scenario("We try to consume endpoint createBank - Anonymous access", ApiEndpoint1, VersionOfApi) {
When("We make the request")
val request = (v6_0_0_Request / "banks").POST
val response = makePostRequest(request, write(postBankJson500))
Then("We should get a 401")
And("We should get a message: " + ErrorMessages.UserNotLoggedIn)
response.code should equal(401)
response.body.extract[ErrorMessage].message should equal(ErrorMessages.UserNotLoggedIn)
}

scenario("We try to consume endpoint createBank without proper role - Authorized access", ApiEndpoint1, VersionOfApi) {
When("We make the request")
val request = (v6_0_0_Request / "banks").POST <@ (user1)
val response = makePostRequest(request, write(postBankJson500))
Then("We should get a 403")
And("We should get a message: " + s"$CanCreateBank entitlement required")
response.code should equal(403)
response.body.extract[ErrorMessage].message should equal(UserHasMissingRoles + CanCreateBank)
}
}

}
Loading