diff --git a/README.md b/README.md
index 1b1dad292..c9317d84f 100644
--- a/README.md
+++ b/README.md
@@ -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:
diff --git a/obp-api/pom.xml b/obp-api/pom.xml
index 4cae0ddf6..1227da21a 100644
--- a/obp-api/pom.xml
+++ b/obp-api/pom.xml
@@ -503,7 +503,13 @@
jakarta.activation
1.2.2
-
+
+
+
+ com.nulab-inc
+ zxcvbn
+ 1.9.0
+
diff --git a/obp-api/src/main/resources/props/sample.props.template b/obp-api/src/main/resources/props/sample.props.template
index f2baea7a1..64c7733d3 100644
--- a/obp-api/src/main/resources/props/sample.props.template
+++ b/obp-api/src/main/resources/props/sample.props.template
@@ -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
diff --git a/obp-api/src/main/scala/code/api/berlin/group/v1_3/JSONFactory_BERLIN_GROUP_1_3.scala b/obp-api/src/main/scala/code/api/berlin/group/v1_3/JSONFactory_BERLIN_GROUP_1_3.scala
index 13aa39560..f7857a00d 100644
--- a/obp-api/src/main/scala/code/api/berlin/group/v1_3/JSONFactory_BERLIN_GROUP_1_3.scala
+++ b/obp-api/src/main/scala/code/api/berlin/group/v1_3/JSONFactory_BERLIN_GROUP_1_3.scala
@@ -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)
diff --git a/obp-api/src/main/scala/code/api/util/APIUtil.scala b/obp-api/src/main/scala/code/api/util/APIUtil.scala
index 376d3ea68..db5049469 100644
--- a/obp-api/src/main/scala/code/api/util/APIUtil.scala
+++ b/obp-api/src/main/scala/code/api/util/APIUtil.scala
@@ -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 {
diff --git a/obp-api/src/main/scala/code/api/util/PasswordUtil.scala b/obp-api/src/main/scala/code/api/util/PasswordUtil.scala
new file mode 100644
index 000000000..5353a0fd8
--- /dev/null
+++ b/obp-api/src/main/scala/code/api/util/PasswordUtil.scala
@@ -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
+ }
+
+}
+
diff --git a/obp-api/src/test/scala/code/api/berlin/group/v1_3/AccountInformationServiceAISApiTest.scala b/obp-api/src/test/scala/code/api/berlin/group/v1_3/AccountInformationServiceAISApiTest.scala
index 685e0b907..b97039db9 100644
--- a/obp-api/src/test/scala/code/api/berlin/group/v1_3/AccountInformationServiceAISApiTest.scala
+++ b/obp-api/src/test/scala/code/api/berlin/group/v1_3/AccountInformationServiceAISApiTest.scala
@@ -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
diff --git a/obp-api/src/test/scala/code/api/berlin/group/v1_3/JSONFactory_BERLIN_GROUP_1_3Test.scala b/obp-api/src/test/scala/code/api/berlin/group/v1_3/JSONFactory_BERLIN_GROUP_1_3Test.scala
index c08b84132..8e0cda195 100644
--- a/obp-api/src/test/scala/code/api/berlin/group/v1_3/JSONFactory_BERLIN_GROUP_1_3Test.scala
+++ b/obp-api/src/test/scala/code/api/berlin/group/v1_3/JSONFactory_BERLIN_GROUP_1_3Test.scala
@@ -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
diff --git a/obp-api/src/test/scala/code/util/PasswordUtilTest.scala b/obp-api/src/test/scala/code/util/PasswordUtilTest.scala
new file mode 100644
index 000000000..b45e6c6ce
--- /dev/null
+++ b/obp-api/src/test/scala/code/util/PasswordUtilTest.scala
@@ -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 .
+
+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)
+ }
+
+ }
+}
diff --git a/obp-commons/src/main/scala/com/openbankproject/commons/model/enums/Enumerations.scala b/obp-commons/src/main/scala/com/openbankproject/commons/model/enums/Enumerations.scala
index 6de788ee3..a0ae6ca04 100644
--- a/obp-commons/src/main/scala/com/openbankproject/commons/model/enums/Enumerations.scala
+++ b/obp-commons/src/main/scala/com/openbankproject/commons/model/enums/Enumerations.scala
@@ -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