Skip to content

Commit 5315b91

Browse files
authored
Merge pull request #2584 from constantine2nd/develop
Berlin Group
2 parents 6a04b89 + e9041d5 commit 5315b91

10 files changed

Lines changed: 168 additions & 30 deletions

File tree

‎README.md‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -497,8 +497,8 @@ In order to make it work edit your props file in next way:
497497
498498
```
499499
use_consumer_limits=false, In case isn't defined default value is "false"
500-
redis_address=YOUR_REDIS_URL_ADDRESS, In case isn't defined default value is 127.0.0.1
501-
redis_port=YOUR_REDIS_PORT, In case isn't defined default value is 6379
500+
cache.redis.url=YOUR_REDIS_URL_ADDRESS, In case isn't defined default value is 127.0.0.1
501+
cache.redis.port=YOUR_REDIS_PORT, In case isn't defined default value is 6379
502502
```
503503
504504
The next types are supported:

‎obp-api/pom.xml‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -497,6 +497,13 @@
497497
<version>1.20.3</version>
498498
<scope>test</scope>
499499
</dependency>
500+
501+
<!-- https://mvnrepository.com/artifact/com.nulab-inc/zxcvbn -->
502+
<dependency>
503+
<groupId>com.nulab-inc</groupId>
504+
<artifactId>zxcvbn</artifactId>
505+
<version>1.9.0</version>
506+
</dependency>
500507
</dependencies>
501508

502509
<build>

‎obp-api/src/main/resources/props/sample.props.template‎

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -856,8 +856,7 @@ featured_apis=elasticSearchWarehouseV300
856856
# use_consumer_limits=false
857857
# In case isn't defined default value is 60
858858
# user_consumer_limit_anonymous_access=100
859-
# redis_address=127.0.0.1
860-
# redis_port=6379
859+
# For the Rate Limiting feature we use Redis cache instance
861860
# In case isn't defined default value is root
862861
# rate_limiting.exclude_endpoints=root
863862
## Default rate limiting for a new consumer

‎obp-api/src/main/scala/code/api/berlin/group/v1_3/JSONFactory_BERLIN_GROUP_1_3.scala‎

Lines changed: 20 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -473,27 +473,30 @@ object JSONFactory_BERLIN_GROUP_1_3 extends CustomJsonFormats with MdcLoggable{
473473
val bookingDate = transaction.startDate.orNull
474474
val valueDate = if(transaction.finishDate.isDefined) Some(BgSpecValidation.formatToISODate(transaction.finishDate.orNull)) else None
475475

476-
val creditorName = transaction.otherBankAccount.map(_.label.display).getOrElse("")
477-
val creditorAccountIban = stringOrNone(transaction.otherBankAccount.map(_.iban.getOrElse("")).getOrElse(""))
478-
479-
val debtorName = stringOrNone(transaction.bankAccount.map(_.label.getOrElse("")).getOrElse(""))
480-
val debtorIban = transaction.bankAccount.map(_.accountRoutingAddress.getOrElse("")).getOrElse("")
481-
val debtorAccountIdIban = stringOrNone(debtorIban)
476+
val out: Boolean = transaction.amount.get.toString().startsWith("-")
477+
val in: Boolean = !out
478+
479+
val isIban = transaction.bankAccount.flatMap(_.accountRoutingScheme.map(_.toUpperCase == "IBAN")).getOrElse(false)
480+
// Creditor
481+
val creditorName = if(in) transaction.otherBankAccount.map(_.label.display) else None
482+
val creditorAccountIban = if(in) {
483+
val creditorIban = if(isIban) transaction.otherBankAccount.map(_.iban.getOrElse("")) else Some("")
484+
Some(BgTransactionAccountJson(iban = creditorIban))
485+
} else None
486+
487+
// Debtor
488+
val debtorName = if(out) transaction.bankAccount.map(_.label.getOrElse("")) else None
489+
val debtorAccountIban = if(out) {
490+
val debtorIban = if(isIban) transaction.bankAccount.map(_.accountRoutingAddress.getOrElse("")) else Some("")
491+
Some(BgTransactionAccountJson(iban = debtorIban))
492+
} else None
482493

483494
TransactionJsonV13(
484495
transactionId = transaction.id.value,
485-
creditorName = stringOrNone(creditorName),
486-
creditorAccount =
487-
if(creditorAccountIban.isEmpty)
488-
None
489-
else
490-
Some(BgTransactionAccountJson(iban=creditorAccountIban)),
496+
creditorName = creditorName,
497+
creditorAccount = creditorAccountIban,
491498
debtorName = debtorName,
492-
debtorAccount =
493-
if(debtorAccountIdIban.isEmpty)
494-
None
495-
else
496-
Some(BgTransactionAccountJson(iban = debtorAccountIdIban)),
499+
debtorAccount = debtorAccountIban,
497500
transactionAmount = AmountOfMoneyV13(
498501
transaction.currency.getOrElse(""),
499502
if(bgRemoveSignOfAmounts)

‎obp-api/src/main/scala/code/api/util/APIUtil.scala‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3974,7 +3974,8 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{
39743974
tpp <- BerlinGroupSigning.getTppByCertificate(certificate, cc)
39753975
} yield {
39763976
if (tpp.nonEmpty) {
3977-
val hasRole = tpp.exists(_.services.contains(serviceProvider))
3977+
val berlinGroupRole = PemCertificateRole.toBerlinGroup(serviceProvider)
3978+
val hasRole = tpp.exists(_.services.contains(berlinGroupRole))
39783979
if (hasRole) {
39793980
Full(true)
39803981
} else {
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package code.api.util
2+
3+
import com.nulabinc.zxcvbn.Zxcvbn
4+
import com.nulabinc.zxcvbn.Strength
5+
6+
object PasswordUtil {
7+
8+
private val zxcvbn = new Zxcvbn()
9+
10+
/** Check password strength score: 0 (very weak) to 4 (very strong) */
11+
def getStrength(password: String): Strength = {
12+
zxcvbn.measure(password)
13+
}
14+
15+
/** Recommend minimum score of 3 (strong) */
16+
def isAcceptable(password: String, minScore: Int = 3): Boolean = {
17+
getStrength(password).getScore >= minScore
18+
}
19+
20+
}
21+

‎obp-api/src/test/scala/code/api/berlin/group/v1_3/AccountInformationServiceAISApiTest.scala‎

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,25 @@ class AccountInformationServiceAISApiTest extends BerlinGroupServerSetupV1_3 wit
192192
response.code should equal(200)
193193
response.body.extract[TransactionsJsonV13].account.iban should not be ("")
194194
// response.body.extract[TransactionsJsonV13].transactions.booked.head.length >0 should be (true)
195-
response.body.extract[TransactionsJsonV13].transactions.pending.head.length >0 should be (true)
195+
response.body.extract[TransactionsJsonV13].transactions.pending.head.nonEmpty should be (true)
196+
response.body.extract[TransactionsJsonV13].transactions.booked.nonEmpty should be (true)
197+
198+
199+
val requestGet2 = (V1_3_BG / "accounts" / testAccountId1.value / "transactions").GET <@ (user1) <<? List(("bookingStatus", "booked"))
200+
val response2: APIResponse = makeGetRequest(requestGet2)
201+
Then("We should get a 200 ")
202+
response2.code should equal(200)
203+
response2.body.extract[TransactionsJsonV13].account.iban should not be ("")
204+
response2.body.extract[TransactionsJsonV13].transactions.pending.isEmpty should be(true)
205+
response2.body.extract[TransactionsJsonV13].transactions.booked.nonEmpty should be(true)
206+
207+
val requestGet3 = (V1_3_BG / "accounts" / testAccountId1.value / "transactions").GET <@ (user1) <<? List(("bookingStatus", "pending"))
208+
val response3: APIResponse = makeGetRequest(requestGet3)
209+
Then("We should get a 200 ")
210+
response3.code should equal(200)
211+
response3.body.extract[TransactionsJsonV13].account.iban should not be ("")
212+
response3.body.extract[TransactionsJsonV13].transactions.pending.nonEmpty should be(true)
213+
response3.body.extract[TransactionsJsonV13].transactions.booked.isEmpty should be(true)
196214
}
197215
}
198216

@@ -220,7 +238,7 @@ class AccountInformationServiceAISApiTest extends BerlinGroupServerSetupV1_3 wit
220238
Then("We should get a 200 ")
221239
response.code should equal(200)
222240
response.body.extract[TransactionsJsonV13].account.iban should not be ("")
223-
response.body.extract[TransactionsJsonV13].transactions.pending.head.length > 0 should be (true)
241+
response.body.extract[TransactionsJsonV13].transactions.pending.head.nonEmpty should be (true)
224242
// response.body.extract[TransactionsJsonV13].transactions.pending.length > 0 should be (true)
225243
val transactionId = response.body.extract[TransactionsJsonV13].transactions.pending.head.head.transactionId
226244

‎obp-api/src/test/scala/code/api/berlin/group/v1_3/JSONFactory_BERLIN_GROUP_1_3Test.scala‎

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -100,10 +100,6 @@ class JSONFactory_BERLIN_GROUP_1_3Test extends FeatureSpec with Matchers with Gi
100100
val result = JSONFactory_BERLIN_GROUP_1_3.createTransactionJSON(transaction)
101101

102102
result.transactionId shouldBe transaction.id.value
103-
result.creditorName shouldBe None //Some("Creditor Name")
104-
result.creditorAccount shouldBe None
105-
result.debtorName shouldBe None//Some(bankAccount.name)
106-
result.debtorAccount shouldBe None
107103

108104
result.transactionAmount.currency shouldBe transaction.currency.get
109105
result.bookingDate should not be empty
@@ -112,8 +108,8 @@ class JSONFactory_BERLIN_GROUP_1_3Test extends FeatureSpec with Matchers with Gi
112108

113109
val jsonString: String = compactRender(Extraction.decompose(result))
114110

115-
jsonString.contains("creditorName") shouldBe false
116-
jsonString.contains("creditorAccount") shouldBe false
111+
jsonString.contains("creditorName") shouldBe true
112+
jsonString.contains("creditorAccount") shouldBe true
117113
jsonString.contains("debtorName") shouldBe false
118114
jsonString.contains("debtorAccount") shouldBe false
119115

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
/**
2+
Open Bank Project - API
3+
Copyright (C) 2011-2019, TESOBE GmbH.
4+
5+
This program is free software: you can redistribute it and/or modify
6+
it under the terms of the GNU Affero General Public License as published by
7+
the Free Software Foundation, either version 3 of the License, or
8+
(at your option) any later version.
9+
10+
This program is distributed in the hope that it will be useful,
11+
but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
GNU Affero General Public License for more details.
14+
15+
You should have received a copy of the GNU Affero General Public License
16+
along with this program. If not, see <http://www.gnu.org/licenses/>.
17+
18+
Email: contact@tesobe.com
19+
TESOBE GmbH.
20+
Osloer Strasse 16/17
21+
Berlin 13359, Germany
22+
23+
This product includes software developed at
24+
TESOBE (http://www.tesobe.com/)
25+
*/
26+
27+
package code.api.util
28+
29+
import code.util.Helper.MdcLoggable
30+
import org.scalatest.{FeatureSpec, GivenWhenThen, Matchers}
31+
32+
class PasswordUtilTest extends FeatureSpec with Matchers with GivenWhenThen with MdcLoggable {
33+
34+
feature("Evaluate password strength using Zxcvbn") {
35+
36+
scenario("Very weak password should return low score and be unacceptable") {
37+
Given("a common password '12345678'")
38+
val password = "12345678"
39+
40+
When("measured with zxcvbn")
41+
val strength = PasswordUtil.getStrength(password)
42+
43+
Then("the score should be 0 and it should be unacceptable")
44+
strength.getScore should be <= 1
45+
PasswordUtil.isAcceptable(password) should be (false)
46+
}
47+
48+
scenario("Moderate password should be acceptable") {
49+
Given("a moderately strong password 'OpenBank2025$'")
50+
val password = "OpenBank2025$"
51+
52+
When("measured with zxcvbn")
53+
val strength = PasswordUtil.getStrength(password)
54+
55+
Then("the score should be >= 3 and it should be acceptable")
56+
strength.getScore should be >= 3
57+
PasswordUtil.isAcceptable(password) should be (true)
58+
}
59+
60+
scenario("Strong password with emoji and unicode should be acceptable") {
61+
Given("a complex password '🔥MySecurę密码2025!'")
62+
val password = "🔥MySecurę密码2025!"
63+
64+
When("measured with zxcvbn")
65+
val strength = PasswordUtil.getStrength(password)
66+
67+
Then("the score should be >= 3 and it should be acceptable")
68+
strength.getScore should be >= 3
69+
PasswordUtil.isAcceptable(password) should be (true)
70+
}
71+
72+
scenario("Very strong password should be clearly acceptable") {
73+
Given("a very strong password 'G@lacticSafe#AlphaZebra99!!'")
74+
val password = "G@lacticSafe#AlphaZebra99!!"
75+
76+
When("measured with zxcvbn")
77+
val strength = PasswordUtil.getStrength(password)
78+
79+
Then("the score should be 4 and it should be acceptable")
80+
strength.getScore should be (4)
81+
PasswordUtil.isAcceptable(password) should be (true)
82+
}
83+
84+
}
85+
}

‎obp-commons/src/main/scala/com/openbankproject/commons/model/enums/Enumerations.scala‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,14 @@ object PemCertificateRole extends OBPEnumeration[PemCertificateRole] {
173173
object PSP_IC extends Value
174174
object PSP_AI extends Value
175175
object PSP_PI extends Value
176+
177+
def toBerlinGroup(role: String): String = {
178+
role match {
179+
case item if PSP_AI.toString == item => "AISP"
180+
case item if PSP_PI.toString == item => "PISP"
181+
case _ => ""
182+
}
183+
}
176184
}
177185

178186
sealed trait UserInvitationPurpose extends EnumValue

0 commit comments

Comments
 (0)