Merge remote-tracking branch 'Simon/develop' into simon-develop

# Conflicts:
#	obp-api/pom.xml
This commit is contained in:
Hongwei 2025-07-31 10:07:15 +02:00
commit 20f5160a2b
10 changed files with 170 additions and 33 deletions

View File

@ -497,8 +497,8 @@ In order to make it work edit your props file in next way:
```
use_consumer_limits=false, In case isn't defined default value is "false"
redis_address=YOUR_REDIS_URL_ADDRESS, In case isn't defined default value is 127.0.0.1
redis_port=YOUR_REDIS_PORT, In case isn't defined default value is 6379
cache.redis.url=YOUR_REDIS_URL_ADDRESS, In case isn't defined default value is 127.0.0.1
cache.redis.port=YOUR_REDIS_PORT, In case isn't defined default value is 6379
```
The next types are supported:

View File

@ -503,7 +503,13 @@
<artifactId>jakarta.activation</artifactId>
<version>1.2.2</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.nulab-inc/zxcvbn -->
<dependency>
<groupId>com.nulab-inc</groupId>
<artifactId>zxcvbn</artifactId>
<version>1.9.0</version>
</dependency>
</dependencies>
<build>

View File

@ -856,8 +856,7 @@ featured_apis=elasticSearchWarehouseV300
# use_consumer_limits=false
# In case isn't defined default value is 60
# user_consumer_limit_anonymous_access=100
# redis_address=127.0.0.1
# redis_port=6379
# For the Rate Limiting feature we use Redis cache instance
# In case isn't defined default value is root
# rate_limiting.exclude_endpoints=root
## Default rate limiting for a new consumer

View File

@ -473,27 +473,30 @@ object JSONFactory_BERLIN_GROUP_1_3 extends CustomJsonFormats with MdcLoggable{
val bookingDate = transaction.startDate.orNull
val valueDate = if(transaction.finishDate.isDefined) Some(BgSpecValidation.formatToISODate(transaction.finishDate.orNull)) else None
val creditorName = transaction.otherBankAccount.map(_.label.display).getOrElse("")
val creditorAccountIban = stringOrNone(transaction.otherBankAccount.map(_.iban.getOrElse("")).getOrElse(""))
val debtorName = stringOrNone(transaction.bankAccount.map(_.label.getOrElse("")).getOrElse(""))
val debtorIban = transaction.bankAccount.map(_.accountRoutingAddress.getOrElse("")).getOrElse("")
val debtorAccountIdIban = stringOrNone(debtorIban)
val out: Boolean = transaction.amount.get.toString().startsWith("-")
val in: Boolean = !out
val isIban = transaction.bankAccount.flatMap(_.accountRoutingScheme.map(_.toUpperCase == "IBAN")).getOrElse(false)
// Creditor
val creditorName = if(in) transaction.otherBankAccount.map(_.label.display) else None
val creditorAccountIban = if(in) {
val creditorIban = if(isIban) transaction.otherBankAccount.map(_.iban.getOrElse("")) else Some("")
Some(BgTransactionAccountJson(iban = creditorIban))
} else None
// Debtor
val debtorName = if(out) transaction.bankAccount.map(_.label.getOrElse("")) else None
val debtorAccountIban = if(out) {
val debtorIban = if(isIban) transaction.bankAccount.map(_.accountRoutingAddress.getOrElse("")) else Some("")
Some(BgTransactionAccountJson(iban = debtorIban))
} else None
TransactionJsonV13(
transactionId = transaction.id.value,
creditorName = stringOrNone(creditorName),
creditorAccount =
if(creditorAccountIban.isEmpty)
None
else
Some(BgTransactionAccountJson(iban=creditorAccountIban)),
creditorName = creditorName,
creditorAccount = creditorAccountIban,
debtorName = debtorName,
debtorAccount =
if(debtorAccountIdIban.isEmpty)
None
else
Some(BgTransactionAccountJson(iban = debtorAccountIdIban)),
debtorAccount = debtorAccountIban,
transactionAmount = AmountOfMoneyV13(
transaction.currency.getOrElse(""),
if(bgRemoveSignOfAmounts)

View File

@ -3974,7 +3974,8 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{
tpp <- BerlinGroupSigning.getTppByCertificate(certificate, cc)
} yield {
if (tpp.nonEmpty) {
val hasRole = tpp.exists(_.services.contains(serviceProvider))
val berlinGroupRole = PemCertificateRole.toBerlinGroup(serviceProvider)
val hasRole = tpp.exists(_.services.contains(berlinGroupRole))
if (hasRole) {
Full(true)
} else {

View File

@ -0,0 +1,21 @@
package code.api.util
import com.nulabinc.zxcvbn.Zxcvbn
import com.nulabinc.zxcvbn.Strength
object PasswordUtil {
private val zxcvbn = new Zxcvbn()
/** Check password strength score: 0 (very weak) to 4 (very strong) */
def getStrength(password: String): Strength = {
zxcvbn.measure(password)
}
/** Recommend minimum score of 3 (strong) */
def isAcceptable(password: String, minScore: Int = 3): Boolean = {
getStrength(password).getScore >= minScore
}
}

View File

@ -185,14 +185,32 @@ class AccountInformationServiceAISApiTest extends BerlinGroupServerSetupV1_3 wit
user1,
PostViewJsonV400(view_id = Constant.SYSTEM_READ_TRANSACTIONS_BERLIN_GROUP_VIEW_ID, is_system = true)
)
val requestGet = (V1_3_BG / "accounts" /testAccountId1.value/ "transactions").GET <@ (user1)
val requestGet = (V1_3_BG / "accounts" /testAccountId1.value/ "transactions").GET <@ (user1) <<? List(("bookingStatus", "both"))
val response: APIResponse = makeGetRequest(requestGet)
Then("We should get a 200 ")
response.code should equal(200)
response.body.extract[TransactionsJsonV13].account.iban should not be ("")
// response.body.extract[TransactionsJsonV13].transactions.booked.head.length >0 should be (true)
response.body.extract[TransactionsJsonV13].transactions.pending.head.length >0 should be (true)
response.body.extract[TransactionsJsonV13].transactions.pending.head.nonEmpty should be (true)
response.body.extract[TransactionsJsonV13].transactions.booked.nonEmpty should be (true)
val requestGet2 = (V1_3_BG / "accounts" / testAccountId1.value / "transactions").GET <@ (user1) <<? List(("bookingStatus", "booked"))
val response2: APIResponse = makeGetRequest(requestGet2)
Then("We should get a 200 ")
response2.code should equal(200)
response2.body.extract[TransactionsJsonV13].account.iban should not be ("")
response2.body.extract[TransactionsJsonV13].transactions.pending.isEmpty should be(true)
response2.body.extract[TransactionsJsonV13].transactions.booked.nonEmpty should be(true)
val requestGet3 = (V1_3_BG / "accounts" / testAccountId1.value / "transactions").GET <@ (user1) <<? List(("bookingStatus", "pending"))
val response3: APIResponse = makeGetRequest(requestGet3)
Then("We should get a 200 ")
response3.code should equal(200)
response3.body.extract[TransactionsJsonV13].account.iban should not be ("")
response3.body.extract[TransactionsJsonV13].transactions.pending.nonEmpty should be(true)
response3.body.extract[TransactionsJsonV13].transactions.booked.isEmpty should be(true)
}
}
@ -214,13 +232,13 @@ class AccountInformationServiceAISApiTest extends BerlinGroupServerSetupV1_3 wit
user1,
PostViewJsonV400(view_id = Constant.SYSTEM_READ_TRANSACTIONS_BERLIN_GROUP_VIEW_ID, is_system = true)
)
val requestGet = (V1_3_BG / "accounts" / testAccountId.value / "transactions").GET <@ (user1)
val requestGet = (V1_3_BG / "accounts" / testAccountId.value / "transactions").GET <@ (user1) <<? List(("bookingStatus", "both"))
val response: APIResponse = makeGetRequest(requestGet)
Then("We should get a 200 ")
response.code should equal(200)
response.body.extract[TransactionsJsonV13].account.iban should not be ("")
response.body.extract[TransactionsJsonV13].transactions.pending.head.length > 0 should be (true)
response.body.extract[TransactionsJsonV13].transactions.pending.head.nonEmpty should be (true)
// response.body.extract[TransactionsJsonV13].transactions.pending.length > 0 should be (true)
val transactionId = response.body.extract[TransactionsJsonV13].transactions.pending.head.head.transactionId

View File

@ -100,10 +100,6 @@ class JSONFactory_BERLIN_GROUP_1_3Test extends FeatureSpec with Matchers with Gi
val result = JSONFactory_BERLIN_GROUP_1_3.createTransactionJSON(transaction)
result.transactionId shouldBe transaction.id.value
result.creditorName shouldBe None //Some("Creditor Name")
result.creditorAccount shouldBe None
result.debtorName shouldBe None//Some(bankAccount.name)
result.debtorAccount shouldBe None
result.transactionAmount.currency shouldBe transaction.currency.get
result.bookingDate should not be empty
@ -112,8 +108,8 @@ class JSONFactory_BERLIN_GROUP_1_3Test extends FeatureSpec with Matchers with Gi
val jsonString: String = compactRender(Extraction.decompose(result))
jsonString.contains("creditorName") shouldBe false
jsonString.contains("creditorAccount") shouldBe false
jsonString.contains("creditorName") shouldBe true
jsonString.contains("creditorAccount") shouldBe true
jsonString.contains("debtorName") shouldBe false
jsonString.contains("debtorAccount") shouldBe false

View File

@ -0,0 +1,85 @@
/**
Open Bank Project - API
Copyright (C) 2011-2019, TESOBE GmbH.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Email: contact@tesobe.com
TESOBE GmbH.
Osloer Strasse 16/17
Berlin 13359, Germany
This product includes software developed at
TESOBE (http://www.tesobe.com/)
*/
package code.api.util
import code.util.Helper.MdcLoggable
import org.scalatest.{FeatureSpec, GivenWhenThen, Matchers}
class PasswordUtilTest extends FeatureSpec with Matchers with GivenWhenThen with MdcLoggable {
feature("Evaluate password strength using Zxcvbn") {
scenario("Very weak password should return low score and be unacceptable") {
Given("a common password '12345678'")
val password = "12345678"
When("measured with zxcvbn")
val strength = PasswordUtil.getStrength(password)
Then("the score should be 0 and it should be unacceptable")
strength.getScore should be <= 1
PasswordUtil.isAcceptable(password) should be (false)
}
scenario("Moderate password should be acceptable") {
Given("a moderately strong password 'OpenBank2025$'")
val password = "OpenBank2025$"
When("measured with zxcvbn")
val strength = PasswordUtil.getStrength(password)
Then("the score should be >= 3 and it should be acceptable")
strength.getScore should be >= 3
PasswordUtil.isAcceptable(password) should be (true)
}
scenario("Strong password with emoji and unicode should be acceptable") {
Given("a complex password '🔥MySecurę密码2025!'")
val password = "🔥MySecurę密码2025!"
When("measured with zxcvbn")
val strength = PasswordUtil.getStrength(password)
Then("the score should be >= 3 and it should be acceptable")
strength.getScore should be >= 3
PasswordUtil.isAcceptable(password) should be (true)
}
scenario("Very strong password should be clearly acceptable") {
Given("a very strong password 'G@lacticSafe#AlphaZebra99!!'")
val password = "G@lacticSafe#AlphaZebra99!!"
When("measured with zxcvbn")
val strength = PasswordUtil.getStrength(password)
Then("the score should be 4 and it should be acceptable")
strength.getScore should be (4)
PasswordUtil.isAcceptable(password) should be (true)
}
}
}

View File

@ -173,6 +173,14 @@ object PemCertificateRole extends OBPEnumeration[PemCertificateRole] {
object PSP_IC extends Value
object PSP_AI extends Value
object PSP_PI extends Value
def toBerlinGroup(role: String): String = {
role match {
case item if PSP_AI.toString == item => "AISP"
case item if PSP_PI.toString == item => "PISP"
case _ => ""
}
}
}
sealed trait UserInvitationPurpose extends EnumValue