From 3a537adf08577ac317fbc9d4ecb26fcf00d61fe5 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Marko=20Mili=C4=87?=
Date: Tue, 14 Sep 2021 08:59:11 +0200
Subject: [PATCH 008/185] feature/Add migration script to extend coulmns code
and parent product code
---
.../code/api/util/migration/Migration.scala | 12 ++++
.../util/migration/MigrationOfProduct.scala | 66 +++++++++++++++++++
.../products/MappedProductsProvider.scala | 4 +-
3 files changed, 80 insertions(+), 2 deletions(-)
create mode 100644 obp-api/src/main/scala/code/api/util/migration/MigrationOfProduct.scala
diff --git a/obp-api/src/main/scala/code/api/util/migration/Migration.scala b/obp-api/src/main/scala/code/api/util/migration/Migration.scala
index 660a93130..e2d307817 100644
--- a/obp-api/src/main/scala/code/api/util/migration/Migration.scala
+++ b/obp-api/src/main/scala/code/api/util/migration/Migration.scala
@@ -84,6 +84,7 @@ object Migration extends MdcLoggable {
populateTheFieldIsActiveAtProductAttribute(startedBeforeSchemifier)
alterColumnUsernameProviderFirstnameAndLastnameAtAuthUser(startedBeforeSchemifier)
alterColumnEmailAtResourceUser(startedBeforeSchemifier)
+ alterColumnCdeAndParentProductCodeAtMappedProduct(startedBeforeSchemifier)
}
private def dummyScript(): Boolean = {
@@ -283,6 +284,17 @@ object Migration extends MdcLoggable {
}
}
}
+ private def alterColumnCdeAndParentProductCodeAtMappedProduct(startedBeforeSchemifier: Boolean): Boolean = {
+ if(startedBeforeSchemifier == true) {
+ logger.warn(s"Migration.database.alterColumnCdeAndParentProductCodeAtMappedProduct(true) cannot be run before Schemifier.")
+ true
+ } else {
+ val name = nameOf(alterColumnCdeAndParentProductCodeAtMappedProduct(startedBeforeSchemifier))
+ runOnce(name) {
+ MigrationOfProduct.alterColumnsCodeAndPrentProductCode(name)
+ }
+ }
+ }
}
diff --git a/obp-api/src/main/scala/code/api/util/migration/MigrationOfProduct.scala b/obp-api/src/main/scala/code/api/util/migration/MigrationOfProduct.scala
new file mode 100644
index 000000000..f52816949
--- /dev/null
+++ b/obp-api/src/main/scala/code/api/util/migration/MigrationOfProduct.scala
@@ -0,0 +1,66 @@
+package code.api.util.migration
+
+import java.time.format.DateTimeFormatter
+import java.time.{ZoneId, ZonedDateTime}
+
+import code.api.util.APIUtil
+import code.api.util.migration.Migration.{DbFunction, saveLog}
+import code.products.MappedProduct
+import net.liftweb.common.Full
+import net.liftweb.mapper.{DB, Schemifier}
+import net.liftweb.util.DefaultConnectionIdentifier
+
+object MigrationOfProduct {
+
+ val oneDayAgo = ZonedDateTime.now(ZoneId.of("UTC")).minusDays(1)
+ val oneYearInFuture = ZonedDateTime.now(ZoneId.of("UTC")).plusYears(1)
+ val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm'Z'")
+
+ def alterColumnsCodeAndPrentProductCode(name: String): Boolean = {
+ DbFunction.tableExists(MappedProduct, (DB.use(DefaultConnectionIdentifier){ conn => conn})) match {
+ case true =>
+ val startDate = System.currentTimeMillis()
+ val commitId: String = APIUtil.gitCommit
+ var isSuccessful = false
+
+ val executedSql =
+ DbFunction.maybeWrite(true, Schemifier.infoF _, DB.use(DefaultConnectionIdentifier){ conn => conn}) {
+ APIUtil.getPropsValue("db.driver") match {
+ case Full(value) if value.contains("com.microsoft.sqlserver.jdbc.SQLServerDriver") =>
+ () =>
+ """
+ |ALTER TABLE mappedproduct ALTER COLUMN mcode varchar(100);
+ |ALTER TABLE mappedproduct ALTER COLUMN mparentproductcode varchar(100);
+ |""".stripMargin
+ case _ =>
+ () =>
+ """
+ |ALTER TABLE mappedproduct ALTER COLUMN mcode type varchar(100);
+ |ALTER TABLE mappedproduct ALTER COLUMN mparentproductcode type varchar(100);
+ |""".stripMargin
+ }
+
+ }
+
+ val endDate = System.currentTimeMillis()
+ val comment: String =
+ s"""Executed SQL:
+ |$executedSql
+ |""".stripMargin
+ isSuccessful = true
+ saveLog(name, commitId, isSuccessful, startDate, endDate, comment)
+ isSuccessful
+
+ case false =>
+ val startDate = System.currentTimeMillis()
+ val commitId: String = APIUtil.gitCommit
+ val isSuccessful = false
+ val endDate = System.currentTimeMillis()
+ val comment: String =
+ s"""${MappedProduct._dbTableNameLC} table does not exist""".stripMargin
+ saveLog(name, commitId, isSuccessful, startDate, endDate, comment)
+ isSuccessful
+ }
+ }
+
+}
diff --git a/obp-api/src/main/scala/code/products/MappedProductsProvider.scala b/obp-api/src/main/scala/code/products/MappedProductsProvider.scala
index 4c3fa601d..e8d85d7d7 100644
--- a/obp-api/src/main/scala/code/products/MappedProductsProvider.scala
+++ b/obp-api/src/main/scala/code/products/MappedProductsProvider.scala
@@ -27,8 +27,8 @@ class MappedProduct extends Product with LongKeyedMapper[MappedProduct] with IdP
override def getSingleton = MappedProduct
object mBankId extends UUIDString(this) // combination of this
- object mCode extends MappedString(this, 50) // and this is unique
- object mParentProductCode extends MappedString(this, 50) // and this is unique
+ object mCode extends MappedString(this, 100) // and this is unique
+ object mParentProductCode extends MappedString(this, 100) // and this is unique
object mName extends MappedString(this, 125)
From a9257bd3e3d68cb30a126af10fb9ba6f1969cce7 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Marko=20Mili=C4=87?=
Date: Tue, 14 Sep 2021 10:51:18 +0200
Subject: [PATCH 009/185] Revert "feature/Add migration script to extend
coulmns code and parent product code"
This reverts commit 3a537adf
---
.../code/api/util/migration/Migration.scala | 12 ----
.../util/migration/MigrationOfProduct.scala | 66 -------------------
.../products/MappedProductsProvider.scala | 4 +-
3 files changed, 2 insertions(+), 80 deletions(-)
delete mode 100644 obp-api/src/main/scala/code/api/util/migration/MigrationOfProduct.scala
diff --git a/obp-api/src/main/scala/code/api/util/migration/Migration.scala b/obp-api/src/main/scala/code/api/util/migration/Migration.scala
index e2d307817..660a93130 100644
--- a/obp-api/src/main/scala/code/api/util/migration/Migration.scala
+++ b/obp-api/src/main/scala/code/api/util/migration/Migration.scala
@@ -84,7 +84,6 @@ object Migration extends MdcLoggable {
populateTheFieldIsActiveAtProductAttribute(startedBeforeSchemifier)
alterColumnUsernameProviderFirstnameAndLastnameAtAuthUser(startedBeforeSchemifier)
alterColumnEmailAtResourceUser(startedBeforeSchemifier)
- alterColumnCdeAndParentProductCodeAtMappedProduct(startedBeforeSchemifier)
}
private def dummyScript(): Boolean = {
@@ -284,17 +283,6 @@ object Migration extends MdcLoggable {
}
}
}
- private def alterColumnCdeAndParentProductCodeAtMappedProduct(startedBeforeSchemifier: Boolean): Boolean = {
- if(startedBeforeSchemifier == true) {
- logger.warn(s"Migration.database.alterColumnCdeAndParentProductCodeAtMappedProduct(true) cannot be run before Schemifier.")
- true
- } else {
- val name = nameOf(alterColumnCdeAndParentProductCodeAtMappedProduct(startedBeforeSchemifier))
- runOnce(name) {
- MigrationOfProduct.alterColumnsCodeAndPrentProductCode(name)
- }
- }
- }
}
diff --git a/obp-api/src/main/scala/code/api/util/migration/MigrationOfProduct.scala b/obp-api/src/main/scala/code/api/util/migration/MigrationOfProduct.scala
deleted file mode 100644
index f52816949..000000000
--- a/obp-api/src/main/scala/code/api/util/migration/MigrationOfProduct.scala
+++ /dev/null
@@ -1,66 +0,0 @@
-package code.api.util.migration
-
-import java.time.format.DateTimeFormatter
-import java.time.{ZoneId, ZonedDateTime}
-
-import code.api.util.APIUtil
-import code.api.util.migration.Migration.{DbFunction, saveLog}
-import code.products.MappedProduct
-import net.liftweb.common.Full
-import net.liftweb.mapper.{DB, Schemifier}
-import net.liftweb.util.DefaultConnectionIdentifier
-
-object MigrationOfProduct {
-
- val oneDayAgo = ZonedDateTime.now(ZoneId.of("UTC")).minusDays(1)
- val oneYearInFuture = ZonedDateTime.now(ZoneId.of("UTC")).plusYears(1)
- val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm'Z'")
-
- def alterColumnsCodeAndPrentProductCode(name: String): Boolean = {
- DbFunction.tableExists(MappedProduct, (DB.use(DefaultConnectionIdentifier){ conn => conn})) match {
- case true =>
- val startDate = System.currentTimeMillis()
- val commitId: String = APIUtil.gitCommit
- var isSuccessful = false
-
- val executedSql =
- DbFunction.maybeWrite(true, Schemifier.infoF _, DB.use(DefaultConnectionIdentifier){ conn => conn}) {
- APIUtil.getPropsValue("db.driver") match {
- case Full(value) if value.contains("com.microsoft.sqlserver.jdbc.SQLServerDriver") =>
- () =>
- """
- |ALTER TABLE mappedproduct ALTER COLUMN mcode varchar(100);
- |ALTER TABLE mappedproduct ALTER COLUMN mparentproductcode varchar(100);
- |""".stripMargin
- case _ =>
- () =>
- """
- |ALTER TABLE mappedproduct ALTER COLUMN mcode type varchar(100);
- |ALTER TABLE mappedproduct ALTER COLUMN mparentproductcode type varchar(100);
- |""".stripMargin
- }
-
- }
-
- val endDate = System.currentTimeMillis()
- val comment: String =
- s"""Executed SQL:
- |$executedSql
- |""".stripMargin
- isSuccessful = true
- saveLog(name, commitId, isSuccessful, startDate, endDate, comment)
- isSuccessful
-
- case false =>
- val startDate = System.currentTimeMillis()
- val commitId: String = APIUtil.gitCommit
- val isSuccessful = false
- val endDate = System.currentTimeMillis()
- val comment: String =
- s"""${MappedProduct._dbTableNameLC} table does not exist""".stripMargin
- saveLog(name, commitId, isSuccessful, startDate, endDate, comment)
- isSuccessful
- }
- }
-
-}
diff --git a/obp-api/src/main/scala/code/products/MappedProductsProvider.scala b/obp-api/src/main/scala/code/products/MappedProductsProvider.scala
index e8d85d7d7..4c3fa601d 100644
--- a/obp-api/src/main/scala/code/products/MappedProductsProvider.scala
+++ b/obp-api/src/main/scala/code/products/MappedProductsProvider.scala
@@ -27,8 +27,8 @@ class MappedProduct extends Product with LongKeyedMapper[MappedProduct] with IdP
override def getSingleton = MappedProduct
object mBankId extends UUIDString(this) // combination of this
- object mCode extends MappedString(this, 100) // and this is unique
- object mParentProductCode extends MappedString(this, 100) // and this is unique
+ object mCode extends MappedString(this, 50) // and this is unique
+ object mParentProductCode extends MappedString(this, 50) // and this is unique
object mName extends MappedString(this, 125)
From a86f8815d4c79209fbc3ce441ff88a6d901c1346 Mon Sep 17 00:00:00 2001
From: hongwei
Date: Wed, 15 Sep 2021 10:49:25 +0200
Subject: [PATCH 010/185] feature/Add migration script to extend column name to
100
---
.../code/api/util/migration/Migration.scala | 12 ++++
.../migration/MigrationOfProductFee.scala | 63 +++++++++++++++++++
.../productfee/MappedProductFeeProvider.scala | 2 +-
3 files changed, 76 insertions(+), 1 deletion(-)
create mode 100644 obp-api/src/main/scala/code/api/util/migration/MigrationOfProductFee.scala
diff --git a/obp-api/src/main/scala/code/api/util/migration/Migration.scala b/obp-api/src/main/scala/code/api/util/migration/Migration.scala
index 660a93130..c627f88b8 100644
--- a/obp-api/src/main/scala/code/api/util/migration/Migration.scala
+++ b/obp-api/src/main/scala/code/api/util/migration/Migration.scala
@@ -84,6 +84,7 @@ object Migration extends MdcLoggable {
populateTheFieldIsActiveAtProductAttribute(startedBeforeSchemifier)
alterColumnUsernameProviderFirstnameAndLastnameAtAuthUser(startedBeforeSchemifier)
alterColumnEmailAtResourceUser(startedBeforeSchemifier)
+ alterColumnNameAtProductFee(startedBeforeSchemifier)
}
private def dummyScript(): Boolean = {
@@ -283,6 +284,17 @@ object Migration extends MdcLoggable {
}
}
}
+ private def alterColumnNameAtProductFee(startedBeforeSchemifier: Boolean): Boolean = {
+ if(startedBeforeSchemifier == true) {
+ logger.warn(s"Migration.database.alterColumnNameAtProductFee(true) cannot be run before Schemifier.")
+ true
+ } else {
+ val name = nameOf(alterColumnNameAtProductFee(startedBeforeSchemifier))
+ runOnce(name) {
+ MigrationOfProductFee.alterColumnProductFeeName(name)
+ }
+ }
+ }
}
diff --git a/obp-api/src/main/scala/code/api/util/migration/MigrationOfProductFee.scala b/obp-api/src/main/scala/code/api/util/migration/MigrationOfProductFee.scala
new file mode 100644
index 000000000..bbd589050
--- /dev/null
+++ b/obp-api/src/main/scala/code/api/util/migration/MigrationOfProductFee.scala
@@ -0,0 +1,63 @@
+package code.api.util.migration
+
+import java.time.format.DateTimeFormatter
+import java.time.{ZoneId, ZonedDateTime}
+import code.api.util.APIUtil
+import code.api.util.migration.Migration.{DbFunction, saveLog}
+import code.productfee.ProductFee
+import net.liftweb.common.Full
+import net.liftweb.mapper.{DB, Schemifier}
+import net.liftweb.util.DefaultConnectionIdentifier
+
+object MigrationOfProductFee {
+
+ val oneDayAgo = ZonedDateTime.now(ZoneId.of("UTC")).minusDays(1)
+ val oneYearInFuture = ZonedDateTime.now(ZoneId.of("UTC")).plusYears(1)
+ val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm'Z'")
+
+ def alterColumnProductFeeName(name: String): Boolean = {
+ DbFunction.tableExists(ProductFee, (DB.use(DefaultConnectionIdentifier){ conn => conn})) match {
+ case true =>
+ val startDate = System.currentTimeMillis()
+ val commitId: String = APIUtil.gitCommit
+ var isSuccessful = false
+
+ val executedSql =
+ DbFunction.maybeWrite(true, Schemifier.infoF _, DB.use(DefaultConnectionIdentifier){ conn => conn}) {
+ APIUtil.getPropsValue("db.driver") match {
+ case Full(value) if value.contains("com.microsoft.sqlserver.jdbc.SQLServerDriver") =>
+ () =>
+ """
+ |ALTER TABLE ProductFee ALTER COLUMN name varchar(100);
+ |""".stripMargin
+ case _ =>
+ () =>
+ """
+ |ALTER TABLE ProductFee ALTER COLUMN name type varchar(100);
+ |""".stripMargin
+ }
+
+ }
+
+ val endDate = System.currentTimeMillis()
+ val comment: String =
+ s"""Executed SQL:
+ |$executedSql
+ |""".stripMargin
+ isSuccessful = true
+ saveLog(name, commitId, isSuccessful, startDate, endDate, comment)
+ isSuccessful
+
+ case false =>
+ val startDate = System.currentTimeMillis()
+ val commitId: String = APIUtil.gitCommit
+ val isSuccessful = false
+ val endDate = System.currentTimeMillis()
+ val comment: String =
+ s"""${ProductFee._dbTableNameLC} table does not exist""".stripMargin
+ saveLog(name, commitId, isSuccessful, startDate, endDate, comment)
+ isSuccessful
+ }
+ }
+
+}
diff --git a/obp-api/src/main/scala/code/productfee/MappedProductFeeProvider.scala b/obp-api/src/main/scala/code/productfee/MappedProductFeeProvider.scala
index 00b5f20e1..0c292eb68 100644
--- a/obp-api/src/main/scala/code/productfee/MappedProductFeeProvider.scala
+++ b/obp-api/src/main/scala/code/productfee/MappedProductFeeProvider.scala
@@ -95,7 +95,7 @@ class ProductFee extends ProductFeeTrait with LongKeyedMapper[ProductFee] with I
object ProductFeeId extends UUIDString(this)
- object Name extends MappedString(this, 50)
+ object Name extends MappedString(this, 100)
object IsActive extends MappedBoolean(this) {
override def defaultValue = true
From ec6beb1788b24bed6cee733f3a5bb2315fa2367d Mon Sep 17 00:00:00 2001
From: hongwei
Date: Wed, 15 Sep 2021 12:15:49 +0200
Subject: [PATCH 011/185] feature/tweaked the error messages for create/update
product fee
---
obp-api/src/main/scala/code/api/util/ErrorMessages.scala | 2 ++
.../scala/code/productfee/MappedProductFeeProvider.scala | 7 ++++---
2 files changed, 6 insertions(+), 3 deletions(-)
diff --git a/obp-api/src/main/scala/code/api/util/ErrorMessages.scala b/obp-api/src/main/scala/code/api/util/ErrorMessages.scala
index 3224f8f7e..474afa523 100644
--- a/obp-api/src/main/scala/code/api/util/ErrorMessages.scala
+++ b/obp-api/src/main/scala/code/api/util/ErrorMessages.scala
@@ -372,6 +372,8 @@ object ErrorMessages {
val InvalidPaymentSystemName = "OBP-30116: Invalid payment system name. The payment system name should only contain 0-9/a-z/A-Z/'-'/'.'/'_', the length should be smaller than 200."
val ProductFeeNotFoundById = "OBP-30117: Product Fee not found. Please specify a valid value for PRODUCT_FEE_ID."
+ val CreateProductFeeError = "OBP-30118: Could not insert the Product Fee."
+ val UpdateProductFeeError = "OBP-30119: Could not update the Product Fee."
val EntitlementIsBankRole = "OBP-30205: This entitlement is a Bank Role. Please set bank_id to a valid bank id."
diff --git a/obp-api/src/main/scala/code/productfee/MappedProductFeeProvider.scala b/obp-api/src/main/scala/code/productfee/MappedProductFeeProvider.scala
index 0c292eb68..8c5e01438 100644
--- a/obp-api/src/main/scala/code/productfee/MappedProductFeeProvider.scala
+++ b/obp-api/src/main/scala/code/productfee/MappedProductFeeProvider.scala
@@ -1,6 +1,7 @@
package code.productfee
import code.api.util.APIUtil
+import code.api.util.ErrorMessages.{CreateProductFeeError, UpdateProductFeeError}
import code.util.UUIDString
import com.openbankproject.commons.model.{BankId, ProductCode, ProductFeeTrait}
import net.liftweb.common.{Box, Empty, Full}
@@ -54,12 +55,12 @@ object MappedProductFeeProvider extends ProductFeeProvider {
.Frequency(frequency)
.Type(`type`)
.saveMe()
- }
+ } ?~! s"$UpdateProductFeeError"
case _ => Empty
}
}
case None => Future {
- Full {
+ tryo {
ProductFee
.create
.ProductFeeId(APIUtil.generateUUID)
@@ -73,7 +74,7 @@ object MappedProductFeeProvider extends ProductFeeProvider {
.Frequency(frequency)
.Type(`type`)
.saveMe()
- }
+ } ?~! s"$CreateProductFeeError"
}
}
}
From e0b384303d48473a182c4b05021a8eb06d4e3292 Mon Sep 17 00:00:00 2001
From: hongwei
Date: Wed, 15 Sep 2021 17:13:54 +0200
Subject: [PATCH 012/185] docfix/added the Dynamic Endpoint and Endpoint
Mapping to Glossary
---
.../main/scala/code/api/util/Glossary.scala | 49 +++++++++++++++++++
1 file changed, 49 insertions(+)
diff --git a/obp-api/src/main/scala/code/api/util/Glossary.scala b/obp-api/src/main/scala/code/api/util/Glossary.scala
index d1d947480..d84c42439 100644
--- a/obp-api/src/main/scala/code/api/util/Glossary.scala
+++ b/obp-api/src/main/scala/code/api/util/Glossary.scala
@@ -2216,6 +2216,55 @@ object Glossary extends MdcLoggable {
|
""".stripMargin)
+ glossaryItems += GlossaryItem(
+ title = "Dynamic Endpoint",
+ description =
+ s"""
+|Dynamic Endpoint, you can create dynamic endpoints by the swagger files.
+|All the endpoints defined in the swagger file, will be created in OBP sandbox.
+|There will be two different modes of these created endpoints.
+|
+|If the host of swagger is dynamic_entity, then you need link the swagger fields to the dynamic entity fields,
+|please check *Endpoint Mapping* endpoints.
+|
+|If the host of swagger is obp_mock, every dynamic endpoint will return example response of swagger.
+|If you need to link the response to external resource, please check * Method Routing* endpoints.
+|
+|
+|Dynamic Endpoint can be created at the System level (bank_id is null) - or Bank / Space level (bank_id is not null).
+|You might want to create Bank level Dynamic Entities in order to grant automated roles based on user email domain.
+|
+|Upon successful creation of a Dynamic Endpoint, OBP automatically:
+|
+|*Creates Roles to guard the above endpoints.
+|*Granted yourself the entitlements to get the access to these endpoints.
+|
+|The following videos are available:
+|
+| * [Introduction to Dynamic Endpoints](https://vimeo.com/426235612)
+| * [Features of Dynamic Endpoints](https://vimeo.com/444133309)
+|
+""".stripMargin)
+
+ glossaryItems += GlossaryItem(
+ title = "Endpoint Mapping",
+ description =
+ s"""
+ |This can be used to map the dynamic entity fields and dynamic endpoint fields.
+ |
+ |When you create the dynamic endpoint, and set `host` of swagger to dynamic_entity.
+ |
+ |Then you can use these endpoints to map dynamic endpoint response to dynamic entity model.
+ |
+ |Check the [Create Endpoint Mapping](/index#OBPv4.0.0-createEndpointMapping) json body, you need to first know the operation_id
+ |
+ |and you can prepare the request_mapping and response_mapping objects.
+ |
+ |Details better to see the video:
+ |
+ | * [Endpoint Mapping -step1-getOne:GetAll](https://vimeo.com/553369108)
+ |""".stripMargin)
+
///////////////////////////////////////////////////////////////////
// NOTE! Some glossary items are generated in ExampleValue.scala
From 3c7c5ffcf70fa4187718d1ebf97749ea63f3bdba Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Marko=20Mili=C4=87?=
Date: Wed, 15 Sep 2021 18:11:19 +0200
Subject: [PATCH 013/185] feature/Add User Agreements at User's JSON Response
v4.0.0
---
.../SwaggerDefinitionsJSON.scala | 15 +++
.../main/scala/code/api/util/APIUtil.scala | 2 +-
.../main/scala/code/api/util/NewStyle.scala | 5 +-
.../scala/code/api/v4_0_0/APIMethods400.scala | 101 ++++++++++++++++--
.../code/api/v4_0_0/JSONFactory4.0.0.scala | 24 ++++-
.../remotedata/RemotedataUserAgreement.scala | 3 +
.../RemotedataUserAgreementActor.scala | 4 +
.../code/remotedata/RemotedataUsers.scala | 7 +-
.../remotedata/RemotedataUsersActor.scala | 4 +
.../src/main/scala/code/users/LiftUsers.scala | 34 ++++--
.../main/scala/code/users/UserAgreement.scala | 7 ++
.../code/users/UserAgreementProvider.scala | 5 +
obp-api/src/main/scala/code/users/Users.scala | 3 +
.../test/scala/code/api/v4_0_0/UserTest.scala | 74 ++++++++++++-
14 files changed, 266 insertions(+), 22 deletions(-)
diff --git a/obp-api/src/main/scala/code/api/ResourceDocs1_4_0/SwaggerDefinitionsJSON.scala b/obp-api/src/main/scala/code/api/ResourceDocs1_4_0/SwaggerDefinitionsJSON.scala
index 5a40b3321..bd7240257 100644
--- a/obp-api/src/main/scala/code/api/ResourceDocs1_4_0/SwaggerDefinitionsJSON.scala
+++ b/obp-api/src/main/scala/code/api/ResourceDocs1_4_0/SwaggerDefinitionsJSON.scala
@@ -1701,6 +1701,18 @@ object SwaggerDefinitionsJSON {
username = usernameExample.value,
entitlements = entitlementJSONs
)
+
+ val userJsonV400 = UserJsonV400(
+ user_id = ExampleValue.userIdExample.value,
+ email = ExampleValue.emailExample.value,
+ provider_id = providerIdValueExample.value,
+ provider = providerValueExample.value,
+ username = usernameExample.value,
+ entitlements = entitlementJSONs,
+ views = None,
+ agreements = Some(Nil),
+ is_deleted = false
+ )
val userIdJsonV400 = UserIdJsonV400(
user_id = ExampleValue.userIdExample.value
)
@@ -1965,6 +1977,9 @@ object SwaggerDefinitionsJSON {
val usersJsonV200 = UsersJsonV200(
users = List(userJsonV200)
)
+ val usersJsonV400 = UsersJsonV400(
+ users = List(userJsonV400)
+ )
val counterpartiesJSON = CounterpartiesJSON(
counterparties = List(coreCounterpartyJSON)
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 97487d270..ee9975643 100644
--- a/obp-api/src/main/scala/code/api/util/APIUtil.scala
+++ b/obp-api/src/main/scala/code/api/util/APIUtil.scala
@@ -2989,7 +2989,7 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{
throw new Exception(UnknownError)
}
}
-
+
def unboxFullAndWrapIntoFuture[T](box: Box[T])(implicit m: Manifest[T]) : Future[T] = {
Future {
unboxFull(fullBoxOrException(box))
diff --git a/obp-api/src/main/scala/code/api/util/NewStyle.scala b/obp-api/src/main/scala/code/api/util/NewStyle.scala
index f2a844196..bc5838b3a 100644
--- a/obp-api/src/main/scala/code/api/util/NewStyle.scala
+++ b/obp-api/src/main/scala/code/api/util/NewStyle.scala
@@ -36,7 +36,7 @@ import code.apicollection.{ApiCollectionTrait, MappedApiCollectionsProvider}
import code.model.dataAccess.{AuthUser, BankAccountRouting}
import code.standingorders.StandingOrderTrait
import code.usercustomerlinks.UserCustomerLink
-import code.users.{UserInvitation, UserInvitationProvider, Users}
+import code.users.{UserAgreement, UserAgreementProvider, UserInvitation, UserInvitationProvider, Users}
import code.util.Helper
import com.openbankproject.commons.util.{ApiVersion, JsonUtils}
import code.views.Views
@@ -824,6 +824,9 @@ object NewStyle {
connectorEmptyResponse(_, callContext)
}
}
+ def getAgreementByUserId(userId: String, agreementType: String, callContext: Option[CallContext]): Future[Box[UserAgreement]] = {
+ Future(UserAgreementProvider.userAgreementProvider.vend.getUserAgreement(userId, agreementType))
+ }
def getEntitlementsByBankId(bankId: String, callContext: Option[CallContext]): Future[List[Entitlement]] = {
Entitlement.entitlement.vend.getEntitlementsByBankId(bankId) map {
diff --git a/obp-api/src/main/scala/code/api/v4_0_0/APIMethods400.scala b/obp-api/src/main/scala/code/api/v4_0_0/APIMethods400.scala
index 8283d6434..c7096c497 100644
--- a/obp-api/src/main/scala/code/api/v4_0_0/APIMethods400.scala
+++ b/obp-api/src/main/scala/code/api/v4_0_0/APIMethods400.scala
@@ -41,7 +41,7 @@ import code.endpointMapping.EndpointMappingCommons
import code.entitlement.Entitlement
import code.metadata.counterparties.{Counterparties, MappedCounterparty}
import code.metadata.tags.Tags
-import code.model.dataAccess.{AuthUser, BankAccountCreation}
+import code.model.dataAccess.{AuthUser, BankAccountCreation, ResourceUser}
import code.model.{toUserExtended, _}
import code.ratelimiting.RateLimitingDI
import code.snippet.{WebUIPlaceholder, WebUITemplate}
@@ -51,7 +51,7 @@ import code.transactionrequests.TransactionRequests.TransactionChallengeTypes._
import code.transactionrequests.TransactionRequests.TransactionRequestTypes
import code.transactionrequests.TransactionRequests.TransactionRequestTypes.{apply => _, _}
import code.userlocks.UserLocksProvider
-import code.users.Users
+import code.users.{UserAgreement, Users}
import code.util.Helper.booleanToFuture
import code.util.{Helper, JsonSchemaUtil}
import code.validation.JsonValidation
@@ -78,9 +78,9 @@ import net.liftweb.util.Mailer.{From, PlainMailBodyType, Subject, To, XHTMLMailB
import net.liftweb.util.{Helpers, Mailer, StringHelpers}
import org.apache.commons.collections4.CollectionUtils
import org.apache.commons.lang3.StringUtils
-
import java.net.URLEncoder
import java.util.{Calendar, Date}
+
import scala.collection.immutable.{List, Nil}
import scala.collection.mutable.ArrayBuffer
import scala.concurrent.Future
@@ -3384,7 +3384,7 @@ trait APIMethods400 {
|
""".stripMargin,
EmptyBody,
- usersJsonV200,
+ userJsonV400,
List(UserNotLoggedIn, UserHasMissingRoles, UserNotFoundById, UnknownError),
List(apiTagUser, apiTagNewStyle),
Some(List(canGetAnyUser)))
@@ -3394,14 +3394,97 @@ trait APIMethods400 {
case "users" :: "user_id" :: userId :: Nil JsonGet _ => {
cc =>
for {
- (Full(u), callContext) <- authenticatedAccess(cc)
- _ <- NewStyle.function.hasEntitlement("", u.userId, ApiRole.canGetAnyUser, callContext)
user <- Users.users.vend.getUserByUserIdFuture(userId) map {
- x => unboxFullOrFail(x, callContext, s"$UserNotFoundByUserId Current UserId($userId)")
+ x => unboxFullOrFail(x, cc.callContext, s"$UserNotFoundByUserId Current UserId($userId)")
}
- entitlements <- NewStyle.function.getEntitlementsByUserId(user.userId, callContext)
+ entitlements <- NewStyle.function.getEntitlementsByUserId(user.userId, cc.callContext)
+ acceptMarketingInfo <- NewStyle.function.getAgreementByUserId(user.userId, "accept_marketing_info", cc.callContext)
+ termsAndConditions <- NewStyle.function.getAgreementByUserId(user.userId, "terms_and_conditions", cc.callContext)
+ privacyConditions <- NewStyle.function.getAgreementByUserId(user.userId, "privacy_conditions", cc.callContext)
} yield {
- (JSONFactory400.createUserInfoJSON(user, entitlements), HttpCode.`200`(callContext))
+ val agreements = acceptMarketingInfo.toList ::: termsAndConditions.toList ::: privacyConditions.toList
+ (JSONFactory400.createUserInfoJSON(user, entitlements, Some(agreements)), HttpCode.`200`(cc.callContext))
+ }
+ }
+ }
+
+ staticResourceDocs += ResourceDoc(
+ getUserByUsername,
+ implementedInApiVersion,
+ nameOf(getUserByUsername),
+ "GET",
+ "/users/username/USERNAME",
+ "Get User by USERNAME",
+ s"""Get user by USERNAME
+ |
+ |${authenticationRequiredMessage(true)}
+ |
+ |CanGetAnyUser entitlement is required,
+ |
+ """.stripMargin,
+ emptyObjectJson,
+ userJsonV400,
+ List($UserNotLoggedIn, UserHasMissingRoles, UserNotFoundByUsername, UnknownError),
+ List(apiTagUser, apiTagNewStyle),
+ Some(List(canGetAnyUser)))
+
+
+ lazy val getUserByUsername: OBPEndpoint = {
+ case "users" :: "username" :: username :: Nil JsonGet _ => {
+ cc =>
+ for {
+ user <- Users.users.vend.getUserByUserNameFuture(username) map {
+ x => unboxFullOrFail(x, cc.callContext, UserNotFoundByUsername, 404)
+ }
+ entitlements <- NewStyle.function.getEntitlementsByUserId(user.userId, cc.callContext)
+ acceptMarketingInfo <- NewStyle.function.getAgreementByUserId(user.userId, "accept_marketing_info", cc.callContext)
+ termsAndConditions <- NewStyle.function.getAgreementByUserId(user.userId, "terms_and_conditions", cc.callContext)
+ privacyConditions <- NewStyle.function.getAgreementByUserId(user.userId, "privacy_conditions", cc.callContext)
+ } yield {
+ val agreements = acceptMarketingInfo.toList ::: termsAndConditions.toList ::: privacyConditions.toList
+ (JSONFactory400.createUserInfoJSON(user, entitlements, Some(agreements)), HttpCode.`200`(cc.callContext))
+ }
+ }
+ }
+
+ staticResourceDocs += ResourceDoc(
+ getUsers,
+ implementedInApiVersion,
+ nameOf(getUsers),
+ "GET",
+ "/users",
+ "Get all Users",
+ s"""Get all users
+ |
+ |${authenticationRequiredMessage(true)}
+ |
+ |CanGetAnyUser entitlement is required,
+ |
+ |${urlParametersDocument(false, false)}
+ |* locked_status (if null ignore)
+ |
+ """.stripMargin,
+ emptyObjectJson,
+ usersJsonV400,
+ List(
+ $UserNotLoggedIn,
+ UserHasMissingRoles,
+ UnknownError
+ ),
+ List(apiTagUser, apiTagNewStyle),
+ Some(List(canGetAnyUser)))
+
+ lazy val getUsers: OBPEndpoint = {
+ case "users" :: Nil JsonGet _ => {
+ cc =>
+ for {
+ httpParams <- NewStyle.function.extractHttpParamsFromUrl(cc.url)
+ obpQueryParams <- createQueriesByHttpParamsFuture(httpParams) map {
+ x => unboxFullOrFail(x, cc.callContext, InvalidFilterParameterFormat)
+ }
+ users <- Users.users.vend.getUsers(obpQueryParams)
+ } yield {
+ (JSONFactory400.createUsersJson(users), HttpCode.`200`(cc.callContext))
}
}
}
diff --git a/obp-api/src/main/scala/code/api/v4_0_0/JSONFactory4.0.0.scala b/obp-api/src/main/scala/code/api/v4_0_0/JSONFactory4.0.0.scala
index a3ad2eace..05713e556 100644
--- a/obp-api/src/main/scala/code/api/v4_0_0/JSONFactory4.0.0.scala
+++ b/obp-api/src/main/scala/code/api/v4_0_0/JSONFactory4.0.0.scala
@@ -35,6 +35,7 @@ import code.api.util.APIUtil.{DateWithDay, DateWithSeconds, stringOptionOrNull,
import code.api.v1_2_1.JSONFactory.{createAmountOfMoneyJSON, createOwnersJSON}
import code.api.v1_2_1.{BankRoutingJsonV121, JSONFactory, UserJSONV121, ViewJSONV121}
import code.api.v1_4_0.JSONFactory1_4_0.{LocationJsonV140, MetaJsonV140, TransactionRequestAccountJsonV140, transformToLocationFromV140, transformToMetaFromV140}
+import code.api.v2_0_0.JSONFactory200.UserJsonV200
import code.api.v2_0_0.{EntitlementJSONs, JSONFactory200, TransactionRequestChargeJsonV200}
import code.api.v2_1_0.{IbanJson, JSONFactory210, PostCounterpartyBespokeJson, ResourceUserJSON}
import code.api.v2_2_0.CounterpartyMetadataJson
@@ -48,12 +49,13 @@ import code.atms.Atms.Atm
import code.bankattribute.BankAttribute
import code.consent.MappedConsent
import code.entitlement.Entitlement
+import code.model.dataAccess.ResourceUser
import code.model.{Consumer, ModeratedBankAccount, ModeratedBankAccountCore}
import code.ratelimiting.RateLimiting
import code.standingorders.StandingOrderTrait
import code.transactionrequests.TransactionRequests.TransactionChallengeTypes
import code.userlocks.UserLocks
-import code.users.UserInvitation
+import code.users.{UserAgreement, UserInvitation}
import com.openbankproject.commons.model.{DirectDebitTrait, ProductFeeTrait, _}
import net.liftweb.common.{Box, Full}
import net.liftweb.json.JValue
@@ -887,6 +889,7 @@ case class AtmJsonV400 (
case class AtmsJsonV400(atms : List[AtmJsonV400])
+case class UserAgreementJson(`type`: String, text: String)
case class UserJsonV400(
user_id: String,
email : String,
@@ -895,12 +898,14 @@ case class UserJsonV400(
username : String,
entitlements : EntitlementJSONs,
views: Option[ViewsJSON300],
+ agreements: Option[List[UserAgreementJson]],
is_deleted: Boolean
)
+case class UsersJsonV400(users: List[UserJsonV400])
object JSONFactory400 {
- def createUserInfoJSON(user : User, entitlements: List[Entitlement]) : UserJsonV400 = {
+ def createUserInfoJSON(user : User, entitlements: List[Entitlement], agreements: Option[List[UserAgreement]]) : UserJsonV400 = {
UserJsonV400(
user_id = user.userId,
email = user.emailAddress,
@@ -909,10 +914,25 @@ object JSONFactory400 {
provider = stringOrNull(user.provider),
entitlements = JSONFactory200.createEntitlementJSONs(entitlements),
views = None,
+ agreements = agreements.map(_.map( i =>
+ UserAgreementJson(`type` = i.agreementType, text = i.agreementText))
+ ),
is_deleted = user.isDeleted.getOrElse(false)
)
}
+ def createUsersJson(users : List[(ResourceUser, Box[List[Entitlement]], Option[List[UserAgreement]])]) : UsersJsonV400 = {
+ UsersJsonV400(
+ users.map(t =>
+ createUserInfoJSON(
+ t._1,
+ t._2.getOrElse(Nil),
+ t._3
+ )
+ )
+ )
+ }
+
def createCallsLimitJson(rateLimiting: RateLimiting) : CallLimitJsonV400 = {
CallLimitJsonV400(
rateLimiting.fromDate,
diff --git a/obp-api/src/main/scala/code/remotedata/RemotedataUserAgreement.scala b/obp-api/src/main/scala/code/remotedata/RemotedataUserAgreement.scala
index 9960f49a7..bc872e22c 100644
--- a/obp-api/src/main/scala/code/remotedata/RemotedataUserAgreement.scala
+++ b/obp-api/src/main/scala/code/remotedata/RemotedataUserAgreement.scala
@@ -16,4 +16,7 @@ object RemotedataUserAgreement extends ObpActorInit with UserAgreementProvider {
def createUserAgreement(userId: String, agreementType: String, agreementText: String): Box[UserAgreement] = getValueFromFuture(
(actor ? cc.createOrUpdateUserAgreement(userId, agreementType, agreementText)).mapTo[Box[UserAgreement]]
)
+ def getUserAgreement(userId: String, agreementType: String): Box[UserAgreement] = getValueFromFuture(
+ (actor ? cc.getUserAgreement(userId, agreementType)).mapTo[Box[UserAgreement]]
+ )
}
diff --git a/obp-api/src/main/scala/code/remotedata/RemotedataUserAgreementActor.scala b/obp-api/src/main/scala/code/remotedata/RemotedataUserAgreementActor.scala
index cac3ddcb7..0967f7670 100644
--- a/obp-api/src/main/scala/code/remotedata/RemotedataUserAgreementActor.scala
+++ b/obp-api/src/main/scala/code/remotedata/RemotedataUserAgreementActor.scala
@@ -20,6 +20,10 @@ class RemotedataUserAgreementActor extends Actor with ObpActorHelper with MdcLog
logger.debug(s"createUserAgreement($userId, $agreementType, $agreementText)")
sender ! (mapper.createUserAgreement(userId, agreementType, agreementText))
+ case cc.getUserAgreement(userId: String, agreementType: String) =>
+ logger.debug(s"getUserAgreement($userId, $agreementType)")
+ sender ! (mapper.getUserAgreement(userId, agreementType))
+
case message => logger.warn("[AKKA ACTOR ERROR - REQUEST NOT RECOGNIZED] " + message)
}
diff --git a/obp-api/src/main/scala/code/remotedata/RemotedataUsers.scala b/obp-api/src/main/scala/code/remotedata/RemotedataUsers.scala
index faa3b2a6c..596de4772 100644
--- a/obp-api/src/main/scala/code/remotedata/RemotedataUsers.scala
+++ b/obp-api/src/main/scala/code/remotedata/RemotedataUsers.scala
@@ -5,7 +5,7 @@ import code.actorsystem.ObpActorInit
import code.api.util.OBPQueryParam
import code.entitlement.Entitlement
import code.model.dataAccess.ResourceUser
-import code.users.{RemotedataUsersCaseClasses, Users}
+import code.users.{RemotedataUsersCaseClasses, UserAgreement, Users}
import com.openbankproject.commons.model.{User, UserPrimaryKey}
import net.liftweb.common.Box
@@ -70,6 +70,11 @@ object RemotedataUsers extends ObpActorInit with Users {
val res = (actor ? cc.getAllUsersF(queryParams))
res.mapTo[List[(ResourceUser, Box[List[Entitlement]])]]
}
+
+ def getUsers(queryParams: List[OBPQueryParam]): Future[List[(ResourceUser, Box[List[Entitlement]], Option[List[UserAgreement]])]] = {
+ val res = (actor ? cc.getUsers(queryParams))
+ res.mapTo[List[(ResourceUser, Box[List[Entitlement]], Option[List[UserAgreement]])]]
+ }
def createResourceUser(provider: String, providerId: Option[String], createdByConsentId: Option[String], name: Option[String], email: Option[String], userId: Option[String], createdByUserInvitationId: Option[String], company: Option[String]) : Box[ResourceUser] = getValueFromFuture(
(actor ? cc.createResourceUser(provider, providerId, createdByConsentId, name, email, userId, createdByUserInvitationId, company)).mapTo[Box[ResourceUser]]
diff --git a/obp-api/src/main/scala/code/remotedata/RemotedataUsersActor.scala b/obp-api/src/main/scala/code/remotedata/RemotedataUsersActor.scala
index a088959ed..eab4b368c 100644
--- a/obp-api/src/main/scala/code/remotedata/RemotedataUsersActor.scala
+++ b/obp-api/src/main/scala/code/remotedata/RemotedataUsersActor.scala
@@ -78,6 +78,10 @@ class RemotedataUsersActor extends Actor with ObpActorHelper with MdcLoggable {
case cc.getAllUsersF(queryParams: List[OBPQueryParam]) =>
logger.debug(s"getAllUsersF(queryParams: ($queryParams))")
mapper.getAllUsersF(queryParams) pipeTo sender
+
+ case cc.getUsers(queryParams: List[OBPQueryParam]) =>
+ logger.debug(s"getUsers(queryParams: ($queryParams))")
+ mapper.getUsers(queryParams) pipeTo sender
case cc.createResourceUser(provider: String, providerId: Option[String], createdByConsentId: Option[String], name: Option[String], email: Option[String], userId: Option[String], createdByUserInvitationId: Option[String], company: Option[String]) =>
logger.debug("createResourceUser(" + provider + ", " + providerId.getOrElse("None") + ", " + name.getOrElse("None") + ", " + email.getOrElse("None") + ", " + userId.getOrElse("None") + ", " + createdByUserInvitationId.getOrElse("None") + ", " + company.getOrElse("None") + ")")
diff --git a/obp-api/src/main/scala/code/users/LiftUsers.scala b/obp-api/src/main/scala/code/users/LiftUsers.scala
index 99c1168e0..4071da280 100644
--- a/obp-api/src/main/scala/code/users/LiftUsers.scala
+++ b/obp-api/src/main/scala/code/users/LiftUsers.scala
@@ -123,11 +123,21 @@ object LiftUsers extends Users with MdcLoggable{
}
override def getAllUsersF(queryParams: List[OBPQueryParam]): Future[List[(ResourceUser, Box[List[Entitlement]])]] = {
-
+ Future {
+ for {
+ user <- getUsersCommon(queryParams)
+ } yield {
+ (user, Entitlement.entitlement.vend.getEntitlementsByUserId(user.userId).map(_.sortWith(_.roleName < _.roleName)))
+ }
+ }
+ }
+
+
+ private def getUsersCommon(queryParams: List[OBPQueryParam]) = {
val limit = queryParams.collect { case OBPLimit(value) => MaxRows[ResourceUser](value) }.headOption
val offset: Option[StartAt[ResourceUser]] = queryParams.collect { case OBPOffset(value) => StartAt[ResourceUser](value) }.headOption
val locked: Option[String] = queryParams.collect { case OBPLockedStatus(value) => value }.headOption
- val deleted = queryParams.collect {
+ val deleted = queryParams.collect {
case OBPIsDeleted(value) if value == true => // ?is_deleted=true
By(ResourceUser.IsDeleted, true)
case OBPIsDeleted(value) if value == false => // ?is_deleted=false
@@ -135,13 +145,14 @@ object LiftUsers extends Users with MdcLoggable{
}.headOption.orElse(
Some(By(ResourceUser.IsDeleted, false)) // There is no query parameter "is_deleted"
)
-
+
val optionalParams: Seq[QueryParam[ResourceUser]] = Seq(limit.toSeq, offset.toSeq, deleted.toSeq).flatten
-
+
def getAllResourceUsers(): List[ResourceUser] = ResourceUser.findAll(optionalParams: _*)
+
val showUsers: List[ResourceUser] = locked.map(_.toLowerCase()) match {
case Some("active") =>
- val lockedUsers: immutable.Seq[MappedBadLoginAttempt] =
+ val lockedUsers: immutable.Seq[MappedBadLoginAttempt] =
MappedBadLoginAttempt.findAll(
By_>(MappedBadLoginAttempt.mBadAttemptsSinceLastSuccessOrReset, maxBadLoginAttempts.toInt)
)
@@ -157,11 +168,20 @@ object LiftUsers extends Users with MdcLoggable{
case _ =>
getAllResourceUsers()
}
+ showUsers
+ }
+
+ override def getUsers(queryParams: List[OBPQueryParam]): Future[List[(ResourceUser, Box[List[Entitlement]], Option[List[UserAgreement]])]] = {
Future {
for {
- user <- showUsers
+ user <- getUsersCommon(queryParams)
} yield {
- (user, Entitlement.entitlement.vend.getEntitlementsByUserId(user.userId).map(_.sortWith(_.roleName < _.roleName)))
+ val entitlements = Entitlement.entitlement.vend.getEntitlementsByUserId(user.userId).map(_.sortWith(_.roleName < _.roleName))
+ val acceptMarketingInfo = UserAgreementProvider.userAgreementProvider.vend.getUserAgreement(user.userId, "accept_marketing_info")
+ val termsAndConditions = UserAgreementProvider.userAgreementProvider.vend.getUserAgreement(user.userId, "terms_and_conditions")
+ val privacyConditions = UserAgreementProvider.userAgreementProvider.vend.getUserAgreement(user.userId, "privacy_conditions")
+ val agreements = acceptMarketingInfo.toList ::: termsAndConditions.toList ::: privacyConditions.toList
+ (user, entitlements, Some(agreements))
}
}
}
diff --git a/obp-api/src/main/scala/code/users/UserAgreement.scala b/obp-api/src/main/scala/code/users/UserAgreement.scala
index 9dc3439bd..19a643279 100644
--- a/obp-api/src/main/scala/code/users/UserAgreement.scala
+++ b/obp-api/src/main/scala/code/users/UserAgreement.scala
@@ -43,6 +43,12 @@ object MappedUserAgreementProvider extends UserAgreementProvider {
.saveMe()
)
}
+ override def getUserAgreement(userId: String, agreementType: String): Box[UserAgreement] = {
+ UserAgreement.findAll(
+ By(UserAgreement.UserId, userId),
+ By(UserAgreement.AgreementType, agreementType)
+ ).sortBy(_.Date.get)(Ordering[Date].reverse).headOption
+ }
}
class UserAgreement extends UserAgreementTrait with LongKeyedMapper[UserAgreement] with IdPK with CreatedUpdated {
@@ -64,6 +70,7 @@ class UserAgreement extends UserAgreementTrait with LongKeyedMapper[UserAgreemen
override def agreementType: String = AgreementType.get
override def agreementText: String = AgreementText.get
override def agreementHash: String = AgreementHash.get
+ override def date: Date = Date.get
}
object UserAgreement extends UserAgreement with LongKeyedMetaMapper[UserAgreement] {
diff --git a/obp-api/src/main/scala/code/users/UserAgreementProvider.scala b/obp-api/src/main/scala/code/users/UserAgreementProvider.scala
index 85d57dbb2..30c9a049e 100644
--- a/obp-api/src/main/scala/code/users/UserAgreementProvider.scala
+++ b/obp-api/src/main/scala/code/users/UserAgreementProvider.scala
@@ -1,5 +1,7 @@
package code.users
+import java.util.Date
+
import code.api.util.APIUtil
import code.remotedata.RemotedataUserAgreement
import net.liftweb.common.Box
@@ -21,11 +23,13 @@ object UserAgreementProvider extends SimpleInjector {
trait UserAgreementProvider {
def createOrUpdateUserAgreement(userId: String, agreementType: String, agreementText: String): Box[UserAgreement]
def createUserAgreement(userId: String, agreementType: String, agreementText: String): Box[UserAgreement]
+ def getUserAgreement(userId: String, agreementType: String): Box[UserAgreement]
}
class RemotedataUserAgreementProviderCaseClass {
case class createOrUpdateUserAgreement(userId: String, agreementType: String, agreementText: String)
case class createUserAgreement(userId: String, agreementType: String, agreementText: String)
+ case class getUserAgreement(userId: String, agreementType: String)
}
object RemotedataUserAgreementProviderCaseClass extends RemotedataUserAgreementProviderCaseClass
@@ -36,4 +40,5 @@ trait UserAgreementTrait {
def agreementType: String
def agreementText: String
def agreementHash: String
+ def date: Date
}
\ No newline at end of file
diff --git a/obp-api/src/main/scala/code/users/Users.scala b/obp-api/src/main/scala/code/users/Users.scala
index 771d37a78..a84512374 100644
--- a/obp-api/src/main/scala/code/users/Users.scala
+++ b/obp-api/src/main/scala/code/users/Users.scala
@@ -51,6 +51,8 @@ trait Users {
def getAllUsersF(queryParams: List[OBPQueryParam]) : Future[List[(ResourceUser, Box[List[Entitlement]])]]
+ def getUsers(queryParams: List[OBPQueryParam]): Future[List[(ResourceUser, Box[List[Entitlement]], Option[List[UserAgreement]])]]
+
def createResourceUser(provider: String, providerId: Option[String], createdByConsentId: Option[String], name: Option[String], email: Option[String], userId: Option[String], createdByUserInvitationId: Option[String], company: Option[String]) : Box[ResourceUser]
def createUnsavedResourceUser(provider: String, providerId: Option[String], name: Option[String], email: Option[String], userId: Option[String]) : Box[ResourceUser]
@@ -80,6 +82,7 @@ class RemotedataUsersCaseClasses {
case class getUserByEmailFuture(email : String)
case class getAllUsers()
case class getAllUsersF(queryParams: List[OBPQueryParam])
+ case class getUsers(queryParams: List[OBPQueryParam])
case class createResourceUser(provider: String, providerId: Option[String],createdByConsentId: Option[String], name: Option[String], email: Option[String], userId: Option[String], createdByUserInvitationId: Option[String], company: Option[String])
case class createUnsavedResourceUser(provider: String, providerId: Option[String], name: Option[String], email: Option[String], userId: Option[String])
case class saveResourceUser(resourceUser: ResourceUser)
diff --git a/obp-api/src/test/scala/code/api/v4_0_0/UserTest.scala b/obp-api/src/test/scala/code/api/v4_0_0/UserTest.scala
index adbc5274d..10734a17b 100644
--- a/obp-api/src/test/scala/code/api/v4_0_0/UserTest.scala
+++ b/obp-api/src/test/scala/code/api/v4_0_0/UserTest.scala
@@ -1,10 +1,14 @@
package code.api.v4_0_0
+import java.util.UUID
+
import code.api.util.APIUtil.OAuth._
import code.api.util.ApiRole.CanGetAnyUser
-import code.api.util.ErrorMessages.{UserHasMissingRoles, UserNotLoggedIn}
+import code.api.util.ErrorMessages.{UserHasMissingRoles, UserNotLoggedIn, attemptedToOpenAnEmptyBox}
import code.api.v4_0_0.OBPAPI4_0_0.Implementations4_0_0
import code.entitlement.Entitlement
+import code.model.UserX
+import code.users.Users
import com.github.dwickern.macros.NameOf.nameOf
import com.openbankproject.commons.model.ErrorMessage
import com.openbankproject.commons.util.ApiVersion
@@ -21,6 +25,8 @@ class UserTest extends V400ServerSetup {
object VersionOfApi extends Tag(ApiVersion.v4_0_0.toString)
object ApiEndpoint1 extends Tag(nameOf(Implementations4_0_0.getCurrentUserId))
object ApiEndpoint2 extends Tag(nameOf(Implementations4_0_0.getUserByUserId))
+ object ApiEndpoint3 extends Tag(nameOf(Implementations4_0_0.getUsers))
+ object ApiEndpoint4 extends Tag(nameOf(Implementations4_0_0.getUserByUsername))
feature(s"test $ApiEndpoint1 version $VersionOfApi - Unauthorized access") {
@@ -76,6 +82,72 @@ class UserTest extends V400ServerSetup {
response400.body.extract[UserJsonV400].user_id should equal(resourceUser3.userId)
}
}
+
+ feature(s"test $ApiEndpoint3 version $VersionOfApi - Unauthorized access") {
+ scenario("We will call the endpoint without user credentials", ApiEndpoint3, VersionOfApi) {
+ When("We make a request v4.0.0")
+ val request400 = (v4_0_0_Request / "users").GET
+ val response400 = makeGetRequest(request400)
+ Then("We should get a 401")
+ response400.code should equal(401)
+ response400.body.extract[ErrorMessage].message should equal(UserNotLoggedIn)
+ }
+ }
+ feature(s"test $ApiEndpoint3 version $VersionOfApi - Authorized access") {
+ scenario("We will call the endpoint with user credentials but without a proper entitlement", ApiEndpoint3, VersionOfApi) {
+ When("We make a request v4.0.0")
+ val request400 = (v4_0_0_Request / "users").GET <@(user1)
+ val response400 = makeGetRequest(request400)
+ Then("error should be " + UserHasMissingRoles + CanGetAnyUser)
+ response400.code should equal(403)
+ response400.body.extract[ErrorMessage].message should be (UserHasMissingRoles + CanGetAnyUser)
+ }
+ }
+ feature(s"test $ApiEndpoint3 version $VersionOfApi - Authorized access") {
+ scenario("We will call the endpoint with user credentials and a proper entitlement", ApiEndpoint3, VersionOfApi) {
+ Entitlement.entitlement.vend.addEntitlement("", resourceUser1.userId, CanGetAnyUser.toString)
+ When("We make a request v4.0.0")
+ val request400 = (v4_0_0_Request / "users").GET <@(user1)
+ val response400 = makeGetRequest(request400)
+ Then("We get successful response")
+ response400.code should equal(200)
+ response400.body.extract[UsersJsonV400]
+ }
+ }
+
+ feature(s"test $ApiEndpoint4 version $VersionOfApi - Unauthorized access") {
+ scenario("We will call the endpoint without user credentials", ApiEndpoint4, VersionOfApi) {
+ When("We make a request v4.0.0")
+ val request400 = (v4_0_0_Request / "users" / "username" / "USERNAME").GET
+ val response400 = makeGetRequest(request400)
+ Then("We should get a 401")
+ response400.code should equal(401)
+ response400.body.extract[ErrorMessage].message should equal(UserNotLoggedIn)
+ }
+ }
+ feature(s"test $ApiEndpoint4 version $VersionOfApi - Authorized access") {
+ scenario("We will call the endpoint with user credentials but without a proper entitlement", ApiEndpoint4, VersionOfApi) {
+ When("We make a request v4.0.0")
+ val request400 = (v4_0_0_Request / "users" / "username" / "USERNAME").GET <@(user1)
+ val response400 = makeGetRequest(request400)
+ Then("error should be " + UserHasMissingRoles + CanGetAnyUser)
+ response400.code should equal(403)
+ response400.body.extract[ErrorMessage].message should be (UserHasMissingRoles + CanGetAnyUser)
+ }
+ }
+ feature(s"test $ApiEndpoint4 version $VersionOfApi - Authorized access") {
+ scenario("We will call the endpoint with user credentials and a proper entitlement", ApiEndpoint4, VersionOfApi) {
+ Entitlement.entitlement.vend.addEntitlement("", resourceUser1.userId, CanGetAnyUser.toString)
+ val user = UserX.createResourceUser(defaultProvider, Some("user.name.1"), None, Some("user.name.1"), None, Some(UUID.randomUUID.toString), None).openOrThrowException(attemptedToOpenAnEmptyBox)
+ When("We make a request v4.0.0")
+ val request400 = (v4_0_0_Request / "users" / "username" / user.idGivenByProvider).GET <@(user1)
+ val response400 = makeGetRequest(request400)
+ Then("We get successful response")
+ response400.code should equal(200)
+ response400.body.extract[UserJsonV400]
+ Users.users.vend.deleteResourceUser(user.id.get)
+ }
+ }
}
From 536ea172966c1a939266f362ef4fc18e99395a37 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Marko=20Mili=C4=87?=
Date: Wed, 15 Sep 2021 18:58:34 +0200
Subject: [PATCH 014/185] feature/Add endpoint getUsersByEmail v4.0.0
---
.../scala/code/api/v4_0_0/APIMethods400.scala | 32 +++++++++++++++++
.../code/remotedata/RemotedataUsers.scala | 3 ++
.../remotedata/RemotedataUsersActor.scala | 4 +++
.../src/main/scala/code/users/LiftUsers.scala | 14 ++++++++
obp-api/src/main/scala/code/users/Users.scala | 2 ++
.../test/scala/code/api/v4_0_0/UserTest.scala | 35 +++++++++++++++++++
6 files changed, 90 insertions(+)
diff --git a/obp-api/src/main/scala/code/api/v4_0_0/APIMethods400.scala b/obp-api/src/main/scala/code/api/v4_0_0/APIMethods400.scala
index c7096c497..3e1136a53 100644
--- a/obp-api/src/main/scala/code/api/v4_0_0/APIMethods400.scala
+++ b/obp-api/src/main/scala/code/api/v4_0_0/APIMethods400.scala
@@ -3447,6 +3447,38 @@ trait APIMethods400 {
}
}
+
+ staticResourceDocs += ResourceDoc(
+ getUsersByEmail,
+ implementedInApiVersion,
+ nameOf(getUsersByEmail),
+ "GET",
+ "/users/email/EMAIL/terminator",
+ "Get Users by Email Address",
+ s"""Get users by email address
+ |
+ |${authenticationRequiredMessage(true)}
+ |CanGetAnyUser entitlement is required,
+ |
+ """.stripMargin,
+ emptyObjectJson,
+ usersJsonV400,
+ List($UserNotLoggedIn, UserHasMissingRoles, UserNotFoundByEmail, UnknownError),
+ List(apiTagUser, apiTagNewStyle),
+ Some(List(canGetAnyUser)))
+
+
+ lazy val getUsersByEmail: OBPEndpoint = {
+ case "users" :: "email" :: email :: "terminator" :: Nil JsonGet _ => {
+ cc =>
+ for {
+ users <- Users.users.vend.getUsersByEmail(email)
+ } yield {
+ (JSONFactory400.createUsersJson(users), HttpCode.`200`(cc.callContext))
+ }
+ }
+ }
+
staticResourceDocs += ResourceDoc(
getUsers,
implementedInApiVersion,
diff --git a/obp-api/src/main/scala/code/remotedata/RemotedataUsers.scala b/obp-api/src/main/scala/code/remotedata/RemotedataUsers.scala
index 596de4772..50c9eef34 100644
--- a/obp-api/src/main/scala/code/remotedata/RemotedataUsers.scala
+++ b/obp-api/src/main/scala/code/remotedata/RemotedataUsers.scala
@@ -61,6 +61,9 @@ object RemotedataUsers extends ObpActorInit with Users {
def getUserByEmailFuture(email : String) : Future[List[(ResourceUser, Box[List[Entitlement]])]] =
(actor ? cc.getUserByEmailFuture(email)).mapTo[List[(ResourceUser, Box[List[Entitlement]])]]
+
+ def getUsersByEmail(email : String) : Future[List[(ResourceUser, Box[List[Entitlement]], Option[List[UserAgreement]])]] =
+ (actor ? cc.getUsersByEmail(email)).mapTo[List[(ResourceUser, Box[List[Entitlement]], Option[List[UserAgreement]])]]
def getAllUsers() : Box[List[ResourceUser]] = getValueFromFuture(
(actor ? cc.getAllUsers()).mapTo[Box[List[ResourceUser]]]
diff --git a/obp-api/src/main/scala/code/remotedata/RemotedataUsersActor.scala b/obp-api/src/main/scala/code/remotedata/RemotedataUsersActor.scala
index eab4b368c..696999375 100644
--- a/obp-api/src/main/scala/code/remotedata/RemotedataUsersActor.scala
+++ b/obp-api/src/main/scala/code/remotedata/RemotedataUsersActor.scala
@@ -70,6 +70,10 @@ class RemotedataUsersActor extends Actor with ObpActorHelper with MdcLoggable {
case cc.getUserByEmailFuture(email: String) =>
logger.debug("getUserByEmailFuture(" + email +")")
sender ! (mapper.getUserByEmailF(email))
+
+ case cc.getUsersByEmail(email: String) =>
+ logger.debug("getUsersByEmail(" + email +")")
+ mapper.getUsersByEmail(email) pipeTo sender
case cc.getAllUsers() =>
logger.debug("getAllUsers()")
diff --git a/obp-api/src/main/scala/code/users/LiftUsers.scala b/obp-api/src/main/scala/code/users/LiftUsers.scala
index 4071da280..f9e610510 100644
--- a/obp-api/src/main/scala/code/users/LiftUsers.scala
+++ b/obp-api/src/main/scala/code/users/LiftUsers.scala
@@ -111,6 +111,20 @@ object LiftUsers extends Users with MdcLoggable{
(user, Entitlement.entitlement.vend.getEntitlementsByUserId(user.userId).map(_.sortWith(_.roleName < _.roleName)))
}
}
+
+ override def getUsersByEmail(email: String): Future[List[(ResourceUser, Box[List[Entitlement]], Option[List[UserAgreement]])]] = Future {
+ val users = ResourceUser.findAll(By(ResourceUser.email, email))
+ for {
+ user <- users
+ } yield {
+ val entitlements = Entitlement.entitlement.vend.getEntitlementsByUserId(user.userId).map(_.sortWith(_.roleName < _.roleName))
+ val acceptMarketingInfo = UserAgreementProvider.userAgreementProvider.vend.getUserAgreement(user.userId, "accept_marketing_info")
+ val termsAndConditions = UserAgreementProvider.userAgreementProvider.vend.getUserAgreement(user.userId, "terms_and_conditions")
+ val privacyConditions = UserAgreementProvider.userAgreementProvider.vend.getUserAgreement(user.userId, "privacy_conditions")
+ val agreements = acceptMarketingInfo.toList ::: termsAndConditions.toList ::: privacyConditions.toList
+ (user, entitlements, Some(agreements))
+ }
+ }
override def getUserByEmailFuture(email: String): Future[List[(ResourceUser, Box[List[Entitlement]])]] = {
Future {
diff --git a/obp-api/src/main/scala/code/users/Users.scala b/obp-api/src/main/scala/code/users/Users.scala
index a84512374..ef6344eb5 100644
--- a/obp-api/src/main/scala/code/users/Users.scala
+++ b/obp-api/src/main/scala/code/users/Users.scala
@@ -46,6 +46,7 @@ trait Users {
def getUserByEmail(email: String) : Box[List[ResourceUser]]
def getUserByEmailFuture(email: String) : Future[List[(ResourceUser, Box[List[Entitlement]])]]
+ def getUsersByEmail(email: String) : Future[List[(ResourceUser, Box[List[Entitlement]], Option[List[UserAgreement]])]]
def getAllUsers() : Box[List[ResourceUser]]
@@ -80,6 +81,7 @@ class RemotedataUsersCaseClasses {
case class getUserByUserNameFuture(userName : String)
case class getUserByEmail(email : String)
case class getUserByEmailFuture(email : String)
+ case class getUsersByEmail(email : String)
case class getAllUsers()
case class getAllUsersF(queryParams: List[OBPQueryParam])
case class getUsers(queryParams: List[OBPQueryParam])
diff --git a/obp-api/src/test/scala/code/api/v4_0_0/UserTest.scala b/obp-api/src/test/scala/code/api/v4_0_0/UserTest.scala
index 10734a17b..614563610 100644
--- a/obp-api/src/test/scala/code/api/v4_0_0/UserTest.scala
+++ b/obp-api/src/test/scala/code/api/v4_0_0/UserTest.scala
@@ -27,6 +27,7 @@ class UserTest extends V400ServerSetup {
object ApiEndpoint2 extends Tag(nameOf(Implementations4_0_0.getUserByUserId))
object ApiEndpoint3 extends Tag(nameOf(Implementations4_0_0.getUsers))
object ApiEndpoint4 extends Tag(nameOf(Implementations4_0_0.getUserByUsername))
+ object ApiEndpoint5 extends Tag(nameOf(Implementations4_0_0.getUsersByEmail))
feature(s"test $ApiEndpoint1 version $VersionOfApi - Unauthorized access") {
@@ -149,5 +150,39 @@ class UserTest extends V400ServerSetup {
}
}
+ feature(s"test $ApiEndpoint5 version $VersionOfApi - Unauthorized access") {
+ scenario("We will call the endpoint without user credentials", ApiEndpoint5, VersionOfApi) {
+ When("We make a request v4.0.0")
+ val request400 = (v4_0_0_Request / "users" / "email" / "EMAIL" / "terminator").GET
+ val response400 = makeGetRequest(request400)
+ Then("We should get a 401")
+ response400.code should equal(401)
+ response400.body.extract[ErrorMessage].message should equal(UserNotLoggedIn)
+ }
+ }
+ feature(s"test $ApiEndpoint5 version $VersionOfApi - Authorized access") {
+ scenario("We will call the endpoint with user credentials but without a proper entitlement", ApiEndpoint5, VersionOfApi) {
+ When("We make a request v4.0.0")
+ val request400 = (v4_0_0_Request / "users" / "email" / "EMAIL" / "terminator").GET <@(user1)
+ val response400 = makeGetRequest(request400)
+ Then("error should be " + UserHasMissingRoles + CanGetAnyUser)
+ response400.code should equal(403)
+ response400.body.extract[ErrorMessage].message should be (UserHasMissingRoles + CanGetAnyUser)
+ }
+ }
+ feature(s"test $ApiEndpoint5 version $VersionOfApi - Authorized access") {
+ scenario("We will call the endpoint with user credentials and a proper entitlement", ApiEndpoint5, VersionOfApi) {
+ Entitlement.entitlement.vend.addEntitlement("", resourceUser1.userId, CanGetAnyUser.toString)
+ val user = UserX.createResourceUser(defaultProvider, Some("user.name.1"), None, Some("user.name.1"), Some("test@tesobe.com"), Some(UUID.randomUUID.toString), None).openOrThrowException(attemptedToOpenAnEmptyBox)
+ When("We make a request v4.0.0")
+ val request400 = (v4_0_0_Request / "users" / "email" / user.emailAddress / "terminator").GET <@(user1)
+ val response400 = makeGetRequest(request400)
+ Then("We get successful response")
+ response400.code should equal(200)
+ response400.body.extract[UsersJsonV400]
+ Users.users.vend.deleteResourceUser(user.id.get)
+ }
+ }
+
}
From 93937bac0a246dc3e34d9ddf94519c41224b6208 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Marko=20Mili=C4=87?=
Date: Thu, 16 Sep 2021 09:32:12 +0200
Subject: [PATCH 015/185] refactor/Add User Agreements at User's JSON Response
v4.0.0
---
.../src/main/scala/code/users/LiftUsers.scala | 18 ++++++++++--------
1 file changed, 10 insertions(+), 8 deletions(-)
diff --git a/obp-api/src/main/scala/code/users/LiftUsers.scala b/obp-api/src/main/scala/code/users/LiftUsers.scala
index f9e610510..56a68a496 100644
--- a/obp-api/src/main/scala/code/users/LiftUsers.scala
+++ b/obp-api/src/main/scala/code/users/LiftUsers.scala
@@ -118,14 +118,19 @@ object LiftUsers extends Users with MdcLoggable{
user <- users
} yield {
val entitlements = Entitlement.entitlement.vend.getEntitlementsByUserId(user.userId).map(_.sortWith(_.roleName < _.roleName))
- val acceptMarketingInfo = UserAgreementProvider.userAgreementProvider.vend.getUserAgreement(user.userId, "accept_marketing_info")
- val termsAndConditions = UserAgreementProvider.userAgreementProvider.vend.getUserAgreement(user.userId, "terms_and_conditions")
- val privacyConditions = UserAgreementProvider.userAgreementProvider.vend.getUserAgreement(user.userId, "privacy_conditions")
- val agreements = acceptMarketingInfo.toList ::: termsAndConditions.toList ::: privacyConditions.toList
+ val agreements = getUserAgreements(user)
(user, entitlements, Some(agreements))
}
}
+ private def getUserAgreements(user: ResourceUser) = {
+ val acceptMarketingInfo = UserAgreementProvider.userAgreementProvider.vend.getUserAgreement(user.userId, "accept_marketing_info")
+ val termsAndConditions = UserAgreementProvider.userAgreementProvider.vend.getUserAgreement(user.userId, "terms_and_conditions")
+ val privacyConditions = UserAgreementProvider.userAgreementProvider.vend.getUserAgreement(user.userId, "privacy_conditions")
+ val agreements = acceptMarketingInfo.toList ::: termsAndConditions.toList ::: privacyConditions.toList
+ agreements
+ }
+
override def getUserByEmailFuture(email: String): Future[List[(ResourceUser, Box[List[Entitlement]])]] = {
Future {
getUserByEmailF(email)
@@ -191,10 +196,7 @@ object LiftUsers extends Users with MdcLoggable{
user <- getUsersCommon(queryParams)
} yield {
val entitlements = Entitlement.entitlement.vend.getEntitlementsByUserId(user.userId).map(_.sortWith(_.roleName < _.roleName))
- val acceptMarketingInfo = UserAgreementProvider.userAgreementProvider.vend.getUserAgreement(user.userId, "accept_marketing_info")
- val termsAndConditions = UserAgreementProvider.userAgreementProvider.vend.getUserAgreement(user.userId, "terms_and_conditions")
- val privacyConditions = UserAgreementProvider.userAgreementProvider.vend.getUserAgreement(user.userId, "privacy_conditions")
- val agreements = acceptMarketingInfo.toList ::: termsAndConditions.toList ::: privacyConditions.toList
+ val agreements = getUserAgreements(user)
(user, entitlements, Some(agreements))
}
}
From 138af5eb85231fc2f75e1c8a180bc3a51df30f2c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Marko=20Mili=C4=87?=
Date: Thu, 16 Sep 2021 12:39:49 +0200
Subject: [PATCH 016/185] bugfix/Enforce Auth User validations at user
invitation flow
---
.../scala/code/snippet/UserInvitation.scala | 49 ++++++++++++-------
1 file changed, 30 insertions(+), 19 deletions(-)
diff --git a/obp-api/src/main/scala/code/snippet/UserInvitation.scala b/obp-api/src/main/scala/code/snippet/UserInvitation.scala
index 94b18e002..c4c38f61f 100644
--- a/obp-api/src/main/scala/code/snippet/UserInvitation.scala
+++ b/obp-api/src/main/scala/code/snippet/UserInvitation.scala
@@ -36,7 +36,7 @@ import code.util.Helper
import code.util.Helper.MdcLoggable
import code.webuiprops.MappedWebUiPropsProvider.getWebUiPropsValue
import com.openbankproject.commons.model.User
-import net.liftweb.common.{Box, Full}
+import net.liftweb.common.{Box, Empty, Failure, Full}
import net.liftweb.http.{RequestVar, S, SHtml}
import net.liftweb.util.CssSel
import net.liftweb.util.Helpers._
@@ -96,25 +96,30 @@ class UserInvitation extends MdcLoggable {
createResourceUser(
provider = "OBP-User-Invitation",
providerId = Some(usernameVar.is),
- name = Some(firstNameVar.is + " " + lastNameVar.is),
+ name = Some(usernameVar.is),
email = Some(email),
userInvitationId = userInvitation.map(_.userInvitationId).toOption,
company = userInvitation.map(_.company).toOption
).map{ u =>
// AuthUser table
- createAuthUser(user = u, firstName = firstNameVar.is, lastName = lastNameVar.is, password = "")
- // Use Agreement table
- UserAgreementProvider.userAgreementProvider.vend.createOrUpdateUserAgreement(
- u.userId, "privacy_conditions", privacyConditionsValue)
- UserAgreementProvider.userAgreementProvider.vend.createOrUpdateUserAgreement(
- u.userId, "terms_and_conditions", termsAndConditionsValue)
- UserAgreementProvider.userAgreementProvider.vend.createOrUpdateUserAgreement(
- u.userId, "accept_marketing_info", marketingInfoCheckboxVar.is.toString)
- // Set the status of the user invitation to "FINISHED"
- UserInvitationProvider.userInvitationProvider.vend.updateStatusOfUserInvitation(userInvitation.map(_.userInvitationId).getOrElse(""), "FINISHED")
- // Set a new password
- val resetLink = AuthUser.passwordResetUrl(u.idGivenByProvider, u.emailAddress, u.userId) + "?action=set"
- S.redirectTo(resetLink)
+ createAuthUser(user = u, firstName = firstNameVar.is, lastName = lastNameVar.is, password = "") match {
+ case Failure(msg,_,_) =>
+ Users.users.vend.deleteResourceUser(u.id.get)
+ showError(msg)
+ case _ =>
+ // User Agreement table
+ UserAgreementProvider.userAgreementProvider.vend.createOrUpdateUserAgreement(
+ u.userId, "privacy_conditions", privacyConditionsValue)
+ UserAgreementProvider.userAgreementProvider.vend.createOrUpdateUserAgreement(
+ u.userId, "terms_and_conditions", termsAndConditionsValue)
+ UserAgreementProvider.userAgreementProvider.vend.createOrUpdateUserAgreement(
+ u.userId, "accept_marketing_info", marketingInfoCheckboxVar.is.toString)
+ // Set the status of the user invitation to "FINISHED"
+ UserInvitationProvider.userInvitationProvider.vend.updateStatusOfUserInvitation(userInvitation.map(_.userInvitationId).getOrElse(""), "FINISHED")
+ // Set a new password
+ val resetLink = AuthUser.passwordResetUrl(u.idGivenByProvider, u.emailAddress, u.userId) + "?action=set"
+ S.redirectTo(resetLink)
+ }
}
}
@@ -181,18 +186,24 @@ class UserInvitation extends MdcLoggable {
register
}
- private def createAuthUser(user: User, firstName: String, lastName: String, password: String): Box[AuthUser] = tryo {
+ private def createAuthUser(user: User, firstName: String, lastName: String, password: String): Box[AuthUser] = {
val newUser = AuthUser.create
.firstName(firstName)
.lastName(lastName)
.email(user.emailAddress)
.user(user.userPrimaryKey.value)
- .username(user.idGivenByProvider)
+ .username(user.name)
.provider(user.provider)
.password(password)
.validated(true)
- // Save the user
- newUser.saveMe()
+ newUser.validate match {
+ case Nil =>
+ // Save the user
+ Full(newUser.saveMe())
+ case xs => S.error(xs)
+ Failure(xs.map(i => i.msg).mkString(";"))
+ }
+
}
private def createResourceUser(provider: String,
From 39b83a8e7a06616a6076ce7407641e0585a17f58 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Marko=20Mili=C4=87?=
Date: Thu, 16 Sep 2021 13:31:56 +0200
Subject: [PATCH 017/185] feature/Remove User Agreements at User's JSON
Response v4.0.0 except getUserByUserId endpoint
---
.../ResourceDocs1_4_0/SwaggerDefinitionsJSON.scala | 11 +++++++++++
.../main/scala/code/api/v4_0_0/APIMethods400.scala | 8 ++------
obp-api/src/main/scala/code/users/LiftUsers.scala | 8 ++++----
3 files changed, 17 insertions(+), 10 deletions(-)
diff --git a/obp-api/src/main/scala/code/api/ResourceDocs1_4_0/SwaggerDefinitionsJSON.scala b/obp-api/src/main/scala/code/api/ResourceDocs1_4_0/SwaggerDefinitionsJSON.scala
index bd7240257..60e866dad 100644
--- a/obp-api/src/main/scala/code/api/ResourceDocs1_4_0/SwaggerDefinitionsJSON.scala
+++ b/obp-api/src/main/scala/code/api/ResourceDocs1_4_0/SwaggerDefinitionsJSON.scala
@@ -1703,6 +1703,17 @@ object SwaggerDefinitionsJSON {
)
val userJsonV400 = UserJsonV400(
+ user_id = ExampleValue.userIdExample.value,
+ email = ExampleValue.emailExample.value,
+ provider_id = providerIdValueExample.value,
+ provider = providerValueExample.value,
+ username = usernameExample.value,
+ entitlements = entitlementJSONs,
+ views = None,
+ agreements = None,
+ is_deleted = false
+ )
+ val userJsonWithAgreementsV400 = UserJsonV400(
user_id = ExampleValue.userIdExample.value,
email = ExampleValue.emailExample.value,
provider_id = providerIdValueExample.value,
diff --git a/obp-api/src/main/scala/code/api/v4_0_0/APIMethods400.scala b/obp-api/src/main/scala/code/api/v4_0_0/APIMethods400.scala
index 3e1136a53..42e71be4b 100644
--- a/obp-api/src/main/scala/code/api/v4_0_0/APIMethods400.scala
+++ b/obp-api/src/main/scala/code/api/v4_0_0/APIMethods400.scala
@@ -3384,7 +3384,7 @@ trait APIMethods400 {
|
""".stripMargin,
EmptyBody,
- userJsonV400,
+ userJsonWithAgreementsV400,
List(UserNotLoggedIn, UserHasMissingRoles, UserNotFoundById, UnknownError),
List(apiTagUser, apiTagNewStyle),
Some(List(canGetAnyUser)))
@@ -3437,12 +3437,8 @@ trait APIMethods400 {
x => unboxFullOrFail(x, cc.callContext, UserNotFoundByUsername, 404)
}
entitlements <- NewStyle.function.getEntitlementsByUserId(user.userId, cc.callContext)
- acceptMarketingInfo <- NewStyle.function.getAgreementByUserId(user.userId, "accept_marketing_info", cc.callContext)
- termsAndConditions <- NewStyle.function.getAgreementByUserId(user.userId, "terms_and_conditions", cc.callContext)
- privacyConditions <- NewStyle.function.getAgreementByUserId(user.userId, "privacy_conditions", cc.callContext)
} yield {
- val agreements = acceptMarketingInfo.toList ::: termsAndConditions.toList ::: privacyConditions.toList
- (JSONFactory400.createUserInfoJSON(user, entitlements, Some(agreements)), HttpCode.`200`(cc.callContext))
+ (JSONFactory400.createUserInfoJSON(user, entitlements, None), HttpCode.`200`(cc.callContext))
}
}
}
diff --git a/obp-api/src/main/scala/code/users/LiftUsers.scala b/obp-api/src/main/scala/code/users/LiftUsers.scala
index 56a68a496..e8dcf9f45 100644
--- a/obp-api/src/main/scala/code/users/LiftUsers.scala
+++ b/obp-api/src/main/scala/code/users/LiftUsers.scala
@@ -118,8 +118,8 @@ object LiftUsers extends Users with MdcLoggable{
user <- users
} yield {
val entitlements = Entitlement.entitlement.vend.getEntitlementsByUserId(user.userId).map(_.sortWith(_.roleName < _.roleName))
- val agreements = getUserAgreements(user)
- (user, entitlements, Some(agreements))
+ // val agreements = getUserAgreements(user)
+ (user, entitlements, None)
}
}
@@ -196,8 +196,8 @@ object LiftUsers extends Users with MdcLoggable{
user <- getUsersCommon(queryParams)
} yield {
val entitlements = Entitlement.entitlement.vend.getEntitlementsByUserId(user.userId).map(_.sortWith(_.roleName < _.roleName))
- val agreements = getUserAgreements(user)
- (user, entitlements, Some(agreements))
+ // val agreements = getUserAgreements(user)
+ (user, entitlements, None)
}
}
}
From 1156efa4d97bbf94765fb6b8fa9421464a03b1e7 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Marko=20Mili=C4=87?=
Date: Thu, 16 Sep 2021 16:35:38 +0200
Subject: [PATCH 018/185] bugfix/Enforce Auth User validations at user
invitation flow 2
---
.../main/scala/code/api/util/SecureRandomUtil.scala | 5 +++++
.../src/main/scala/code/snippet/UserInvitation.scala | 10 +++++-----
2 files changed, 10 insertions(+), 5 deletions(-)
diff --git a/obp-api/src/main/scala/code/api/util/SecureRandomUtil.scala b/obp-api/src/main/scala/code/api/util/SecureRandomUtil.scala
index 095cc9c93..a11b8c2e1 100644
--- a/obp-api/src/main/scala/code/api/util/SecureRandomUtil.scala
+++ b/obp-api/src/main/scala/code/api/util/SecureRandomUtil.scala
@@ -1,5 +1,6 @@
package code.api.util
+import java.math.BigInteger
import java.security.SecureRandom
/**
@@ -15,4 +16,8 @@ object SecureRandomUtil {
// Obtains random numbers from the underlying native OS.
// No assertions are made as to the blocking nature of generating these numbers.
val csprng = SecureRandom.getInstance("NativePRNG")
+
+ def alphanumeric(nrChars: Int = 24): String = {
+ new BigInteger(nrChars * 5, csprng).toString(32)
+ }
}
diff --git a/obp-api/src/main/scala/code/snippet/UserInvitation.scala b/obp-api/src/main/scala/code/snippet/UserInvitation.scala
index c4c38f61f..9785fb22a 100644
--- a/obp-api/src/main/scala/code/snippet/UserInvitation.scala
+++ b/obp-api/src/main/scala/code/snippet/UserInvitation.scala
@@ -28,7 +28,7 @@ package code.snippet
import java.time.{Duration, ZoneId, ZoneOffset, ZonedDateTime}
-import code.api.util.APIUtil
+import code.api.util.{APIUtil, SecureRandomUtil}
import code.model.dataAccess.{AuthUser, ResourceUser}
import code.users
import code.users.{UserAgreementProvider, UserInvitationProvider, Users}
@@ -38,7 +38,7 @@ import code.webuiprops.MappedWebUiPropsProvider.getWebUiPropsValue
import com.openbankproject.commons.model.User
import net.liftweb.common.{Box, Empty, Failure, Full}
import net.liftweb.http.{RequestVar, S, SHtml}
-import net.liftweb.util.CssSel
+import net.liftweb.util.{CssSel, Helpers}
import net.liftweb.util.Helpers._
import scala.collection.immutable.List
@@ -102,7 +102,7 @@ class UserInvitation extends MdcLoggable {
company = userInvitation.map(_.company).toOption
).map{ u =>
// AuthUser table
- createAuthUser(user = u, firstName = firstNameVar.is, lastName = lastNameVar.is, password = "") match {
+ createAuthUser(user = u, firstName = firstNameVar.is, lastName = lastNameVar.is) match {
case Failure(msg,_,_) =>
Users.users.vend.deleteResourceUser(u.id.get)
showError(msg)
@@ -186,7 +186,7 @@ class UserInvitation extends MdcLoggable {
register
}
- private def createAuthUser(user: User, firstName: String, lastName: String, password: String): Box[AuthUser] = {
+ private def createAuthUser(user: User, firstName: String, lastName: String): Box[AuthUser] = {
val newUser = AuthUser.create
.firstName(firstName)
.lastName(lastName)
@@ -194,7 +194,7 @@ class UserInvitation extends MdcLoggable {
.user(user.userPrimaryKey.value)
.username(user.name)
.provider(user.provider)
- .password(password)
+ .password(SecureRandomUtil.alphanumeric(10))
.validated(true)
newUser.validate match {
case Nil =>
From a265d5152859e96f2e385888f3f0020cefc52bde Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Marko=20Mili=C4=87?=
Date: Thu, 16 Sep 2021 17:05:27 +0200
Subject: [PATCH 019/185] refactor/Add field
last_marketing_agreement_signed_date to resourceuser table
---
obp-api/src/main/scala/code/api/GatewayLogin.scala | 3 ++-
obp-api/src/main/scala/code/api/OAuth2.scala | 3 ++-
.../src/main/scala/code/api/openidconnect.scala | 3 ++-
.../src/main/scala/code/api/util/ConsentUtil.scala | 3 ++-
.../src/main/scala/code/model/BankingData.scala | 1 +
obp-api/src/main/scala/code/model/User.scala | 2 +-
.../scala/code/model/dataAccess/ResourceUser.scala | 4 ++++
.../scala/code/remotedata/RemotedataUsers.scala | 6 ++++--
.../code/remotedata/RemotedataUsersActor.scala | 8 +++++---
.../main/scala/code/snippet/UserInvitation.scala | 10 +++++++---
obp-api/src/main/scala/code/users/LiftUsers.scala | 12 ++++++++++--
obp-api/src/main/scala/code/users/Users.scala | 14 ++++++++++++--
.../BankAccountCreationListenerTest.scala | 2 +-
.../commons/model/CommonModel.scala | 11 ++++++++++-
.../openbankproject/commons/model/UserModel.scala | 3 +++
15 files changed, 66 insertions(+), 19 deletions(-)
diff --git a/obp-api/src/main/scala/code/api/GatewayLogin.scala b/obp-api/src/main/scala/code/api/GatewayLogin.scala
index aaf83020e..9d2128a18 100755
--- a/obp-api/src/main/scala/code/api/GatewayLogin.scala
+++ b/obp-api/src/main/scala/code/api/GatewayLogin.scala
@@ -262,7 +262,8 @@ object GatewayLogin extends RestHelper with MdcLoggable {
email = None,
userId = None,
createdByUserInvitationId = None,
- company = None
+ company = None,
+ lastMarketingAgreementSignedDate = None
)
} match {
case Full(u) =>
diff --git a/obp-api/src/main/scala/code/api/OAuth2.scala b/obp-api/src/main/scala/code/api/OAuth2.scala
index f138f5949..e77309bac 100644
--- a/obp-api/src/main/scala/code/api/OAuth2.scala
+++ b/obp-api/src/main/scala/code/api/OAuth2.scala
@@ -287,7 +287,8 @@ object OAuth2Login extends RestHelper with MdcLoggable {
email = getClaim(name = "email", idToken = idToken),
userId = None,
createdByUserInvitationId = None,
- company = None
+ company = None,
+ lastMarketingAgreementSignedDate = None
)
}
}
diff --git a/obp-api/src/main/scala/code/api/openidconnect.scala b/obp-api/src/main/scala/code/api/openidconnect.scala
index 2ed85dde2..6df50fdc2 100644
--- a/obp-api/src/main/scala/code/api/openidconnect.scala
+++ b/obp-api/src/main/scala/code/api/openidconnect.scala
@@ -186,7 +186,8 @@ object OpenIdConnect extends OBPRestHelper with MdcLoggable {
email = getClaim(name = "email", idToken = idToken),
userId = None,
createdByUserInvitationId = None,
- company = None
+ company = None,
+ lastMarketingAgreementSignedDate = None
)
}
}
diff --git a/obp-api/src/main/scala/code/api/util/ConsentUtil.scala b/obp-api/src/main/scala/code/api/util/ConsentUtil.scala
index 9cec32129..49ef8d0fb 100644
--- a/obp-api/src/main/scala/code/api/util/ConsentUtil.scala
+++ b/obp-api/src/main/scala/code/api/util/ConsentUtil.scala
@@ -198,7 +198,8 @@ object Consent {
email = email,
userId = None,
createdByUserInvitationId = None,
- company = None
+ company = None,
+ lastMarketingAgreementSignedDate = None
)
}
}
diff --git a/obp-api/src/main/scala/code/model/BankingData.scala b/obp-api/src/main/scala/code/model/BankingData.scala
index 07374a734..f6d0a66b2 100644
--- a/obp-api/src/main/scala/code/model/BankingData.scala
+++ b/obp-api/src/main/scala/code/model/BankingData.scala
@@ -199,6 +199,7 @@ case class BankAccountExtended(val bankAccount: BankAccount) extends MdcLoggable
val createdByConsentId = None
val createdByUserInvitationId = None
val isDeleted = None
+ val lastMarketingAgreementSignedDate = None
})
} else {
accountHolders
diff --git a/obp-api/src/main/scala/code/model/User.scala b/obp-api/src/main/scala/code/model/User.scala
index 316cc2106..aff34e40a 100644
--- a/obp-api/src/main/scala/code/model/User.scala
+++ b/obp-api/src/main/scala/code/model/User.scala
@@ -131,7 +131,7 @@ object UserX {
}
def createResourceUser(provider: String, providerId: Option[String], createdByConsentId: Option[String], name: Option[String], email: Option[String], userId: Option[String], company: Option[String]) = {
- Users.users.vend.createResourceUser(provider, providerId, createdByConsentId, name, email, userId, None, company)
+ Users.users.vend.createResourceUser(provider, providerId, createdByConsentId, name, email, userId, None, company, None)
}
def createUnsavedResourceUser(provider: String, providerId: Option[String], name: Option[String], email: Option[String], userId: Option[String]) = {
diff --git a/obp-api/src/main/scala/code/model/dataAccess/ResourceUser.scala b/obp-api/src/main/scala/code/model/dataAccess/ResourceUser.scala
index 6c8d72159..30d5d8a20 100644
--- a/obp-api/src/main/scala/code/model/dataAccess/ResourceUser.scala
+++ b/obp-api/src/main/scala/code/model/dataAccess/ResourceUser.scala
@@ -26,6 +26,8 @@ TESOBE (http://www.tesobe.com/)
*/
package code.model.dataAccess
+import java.util.Date
+
import code.api.util.APIUtil
import code.util.MappedUUID
import com.openbankproject.commons.model.{User, UserPrimaryKey}
@@ -81,6 +83,7 @@ class ResourceUser extends LongKeyedMapper[ResourceUser] with User with ManyToMa
object IsDeleted extends MappedBoolean(this) {
override def defaultValue = false
}
+ object LastMarketingAgreementSignedDate extends MappedDate(this)
def emailAddress = {
val e = email.get
@@ -109,6 +112,7 @@ class ResourceUser extends LongKeyedMapper[ResourceUser] with User with ManyToMa
override def createdByConsentId = if(CreatedByConsentId.get == null) None else if (CreatedByConsentId.get.isEmpty) None else Some(CreatedByConsentId.get) //null --> None
override def createdByUserInvitationId = if(CreatedByUserInvitationId.get == null) None else if (CreatedByUserInvitationId.get.isEmpty) None else Some(CreatedByUserInvitationId.get) //null --> None
override def isDeleted: Option[Boolean] = if(IsDeleted.jdbcFriendly(IsDeleted.calcFieldName) == null) None else Some(IsDeleted.get) // null --> None
+ override def lastMarketingAgreementSignedDate: Option[Date] = if(IsDeleted.jdbcFriendly(LastMarketingAgreementSignedDate.calcFieldName) == null) None else Some(LastMarketingAgreementSignedDate.get) // null --> None
}
object ResourceUser extends ResourceUser with LongKeyedMetaMapper[ResourceUser]{
diff --git a/obp-api/src/main/scala/code/remotedata/RemotedataUsers.scala b/obp-api/src/main/scala/code/remotedata/RemotedataUsers.scala
index 50c9eef34..3b28059fb 100644
--- a/obp-api/src/main/scala/code/remotedata/RemotedataUsers.scala
+++ b/obp-api/src/main/scala/code/remotedata/RemotedataUsers.scala
@@ -1,5 +1,7 @@
package code.remotedata
+import java.util.Date
+
import akka.pattern.ask
import code.actorsystem.ObpActorInit
import code.api.util.OBPQueryParam
@@ -79,8 +81,8 @@ object RemotedataUsers extends ObpActorInit with Users {
res.mapTo[List[(ResourceUser, Box[List[Entitlement]], Option[List[UserAgreement]])]]
}
- def createResourceUser(provider: String, providerId: Option[String], createdByConsentId: Option[String], name: Option[String], email: Option[String], userId: Option[String], createdByUserInvitationId: Option[String], company: Option[String]) : Box[ResourceUser] = getValueFromFuture(
- (actor ? cc.createResourceUser(provider, providerId, createdByConsentId, name, email, userId, createdByUserInvitationId, company)).mapTo[Box[ResourceUser]]
+ def createResourceUser(provider: String, providerId: Option[String], createdByConsentId: Option[String], name: Option[String], email: Option[String], userId: Option[String], createdByUserInvitationId: Option[String], company: Option[String], lastMarketingAgreementSignedDate: Option[Date]) : Box[ResourceUser] = getValueFromFuture(
+ (actor ? cc.createResourceUser(provider, providerId, createdByConsentId, name, email, userId, createdByUserInvitationId, company, lastMarketingAgreementSignedDate)).mapTo[Box[ResourceUser]]
)
def createUnsavedResourceUser(provider: String, providerId: Option[String], name: Option[String], email: Option[String], userId: Option[String]) : Box[ResourceUser] = getValueFromFuture(
diff --git a/obp-api/src/main/scala/code/remotedata/RemotedataUsersActor.scala b/obp-api/src/main/scala/code/remotedata/RemotedataUsersActor.scala
index 696999375..5ed768852 100644
--- a/obp-api/src/main/scala/code/remotedata/RemotedataUsersActor.scala
+++ b/obp-api/src/main/scala/code/remotedata/RemotedataUsersActor.scala
@@ -1,5 +1,7 @@
package code.remotedata
+import java.util.Date
+
import akka.actor.Actor
import akka.pattern.pipe
import code.actorsystem.ObpActorHelper
@@ -87,9 +89,9 @@ class RemotedataUsersActor extends Actor with ObpActorHelper with MdcLoggable {
logger.debug(s"getUsers(queryParams: ($queryParams))")
mapper.getUsers(queryParams) pipeTo sender
- case cc.createResourceUser(provider: String, providerId: Option[String], createdByConsentId: Option[String], name: Option[String], email: Option[String], userId: Option[String], createdByUserInvitationId: Option[String], company: Option[String]) =>
- logger.debug("createResourceUser(" + provider + ", " + providerId.getOrElse("None") + ", " + name.getOrElse("None") + ", " + email.getOrElse("None") + ", " + userId.getOrElse("None") + ", " + createdByUserInvitationId.getOrElse("None") + ", " + company.getOrElse("None") + ")")
- sender ! (mapper.createResourceUser(provider, providerId, createdByConsentId, name, email, userId, createdByUserInvitationId, company))
+ case cc.createResourceUser(provider: String, providerId: Option[String], createdByConsentId: Option[String], name: Option[String], email: Option[String], userId: Option[String], createdByUserInvitationId: Option[String], company: Option[String], lastMarketingAgreementSignedDate: Option[Date]) =>
+ logger.debug("createResourceUser(" + provider + ", " + providerId.getOrElse("None") + ", " + name.getOrElse("None") + ", " + email.getOrElse("None") + ", " + userId.getOrElse("None") + ", " + createdByUserInvitationId.getOrElse("None") + ", " + company.getOrElse("None") + ", " + lastMarketingAgreementSignedDate.getOrElse("None") + ")")
+ sender ! (mapper.createResourceUser(provider, providerId, createdByConsentId, name, email, userId, createdByUserInvitationId, company, lastMarketingAgreementSignedDate))
case cc.createUnsavedResourceUser(provider: String, providerId: Option[String], name: Option[String], email: Option[String], userId: Option[String]) =>
logger.debug("createUnsavedResourceUser(" + provider + ", " + providerId.getOrElse("None") + ", " + name.getOrElse("None") + ", " + email.getOrElse("None") + ", " + userId.getOrElse("None") + ")")
diff --git a/obp-api/src/main/scala/code/snippet/UserInvitation.scala b/obp-api/src/main/scala/code/snippet/UserInvitation.scala
index 9785fb22a..5cd61f183 100644
--- a/obp-api/src/main/scala/code/snippet/UserInvitation.scala
+++ b/obp-api/src/main/scala/code/snippet/UserInvitation.scala
@@ -27,6 +27,7 @@ TESOBE (http://www.tesobe.com/)
package code.snippet
import java.time.{Duration, ZoneId, ZoneOffset, ZonedDateTime}
+import java.util.Date
import code.api.util.{APIUtil, SecureRandomUtil}
import code.model.dataAccess.{AuthUser, ResourceUser}
@@ -99,7 +100,8 @@ class UserInvitation extends MdcLoggable {
name = Some(usernameVar.is),
email = Some(email),
userInvitationId = userInvitation.map(_.userInvitationId).toOption,
- company = userInvitation.map(_.company).toOption
+ company = userInvitation.map(_.company).toOption,
+ lastMarketingAgreementSignedDate = if(marketingInfoCheckboxVar.is) Some(new Date()) else None
).map{ u =>
// AuthUser table
createAuthUser(user = u, firstName = firstNameVar.is, lastName = lastNameVar.is) match {
@@ -211,7 +213,8 @@ class UserInvitation extends MdcLoggable {
name: Option[String],
email: Option[String],
userInvitationId: Option[String],
- company: Option[String]
+ company: Option[String],
+ lastMarketingAgreementSignedDate: Option[Date],
): Box[ResourceUser] = {
Users.users.vend.createResourceUser(
provider = provider,
@@ -221,7 +224,8 @@ class UserInvitation extends MdcLoggable {
email = email,
userId = None,
createdByUserInvitationId = userInvitationId,
- company = company
+ company = company,
+ lastMarketingAgreementSignedDate = lastMarketingAgreementSignedDate
)
}
diff --git a/obp-api/src/main/scala/code/users/LiftUsers.scala b/obp-api/src/main/scala/code/users/LiftUsers.scala
index e8dcf9f45..73e759889 100644
--- a/obp-api/src/main/scala/code/users/LiftUsers.scala
+++ b/obp-api/src/main/scala/code/users/LiftUsers.scala
@@ -1,5 +1,7 @@
package code.users
+import java.util.Date
+
import code.api.util._
import code.entitlement.Entitlement
import code.loginattempts.LoginAttempt.maxBadLoginAttempts
@@ -60,7 +62,8 @@ object LiftUsers extends Users with MdcLoggable{
email = email,
userId = None,
createdByUserInvitationId = None,
- company = None
+ company = None,
+ lastMarketingAgreementSignedDate = None
)
(newUser, true)
}
@@ -210,7 +213,8 @@ object LiftUsers extends Users with MdcLoggable{
email: Option[String],
userId: Option[String],
createdByUserInvitationId: Option[String],
- company: Option[String]): Box[ResourceUser] = {
+ company: Option[String],
+ lastMarketingAgreementSignedDate: Option[Date]): Box[ResourceUser] = {
val ru = ResourceUser.create
ru.provider_(provider)
providerId match {
@@ -241,6 +245,10 @@ object LiftUsers extends Users with MdcLoggable{
case Some(v) => ru.Company(v)
case None =>
}
+ lastMarketingAgreementSignedDate match {
+ case Some(v) => ru.LastMarketingAgreementSignedDate(v)
+ case None =>
+ }
Full(ru.saveMe())
}
diff --git a/obp-api/src/main/scala/code/users/Users.scala b/obp-api/src/main/scala/code/users/Users.scala
index ef6344eb5..d6b446199 100644
--- a/obp-api/src/main/scala/code/users/Users.scala
+++ b/obp-api/src/main/scala/code/users/Users.scala
@@ -1,5 +1,7 @@
package code.users
+import java.util.Date
+
import code.api.util.{APIUtil, OBPQueryParam}
import code.entitlement.Entitlement
import code.model.dataAccess.ResourceUser
@@ -54,7 +56,15 @@ trait Users {
def getUsers(queryParams: List[OBPQueryParam]): Future[List[(ResourceUser, Box[List[Entitlement]], Option[List[UserAgreement]])]]
- def createResourceUser(provider: String, providerId: Option[String], createdByConsentId: Option[String], name: Option[String], email: Option[String], userId: Option[String], createdByUserInvitationId: Option[String], company: Option[String]) : Box[ResourceUser]
+ def createResourceUser(provider: String,
+ providerId: Option[String],
+ createdByConsentId: Option[String],
+ name: Option[String],
+ email: Option[String],
+ userId: Option[String],
+ createdByUserInvitationId: Option[String],
+ company: Option[String],
+ lastMarketingAgreementSignedDate: Option[Date]) : Box[ResourceUser]
def createUnsavedResourceUser(provider: String, providerId: Option[String], name: Option[String], email: Option[String], userId: Option[String]) : Box[ResourceUser]
@@ -85,7 +95,7 @@ class RemotedataUsersCaseClasses {
case class getAllUsers()
case class getAllUsersF(queryParams: List[OBPQueryParam])
case class getUsers(queryParams: List[OBPQueryParam])
- case class createResourceUser(provider: String, providerId: Option[String],createdByConsentId: Option[String], name: Option[String], email: Option[String], userId: Option[String], createdByUserInvitationId: Option[String], company: Option[String])
+ case class createResourceUser(provider: String, providerId: Option[String],createdByConsentId: Option[String], name: Option[String], email: Option[String], userId: Option[String], createdByUserInvitationId: Option[String], company: Option[String], lastMarketingAgreementSignedDate: Option[Date])
case class createUnsavedResourceUser(provider: String, providerId: Option[String], name: Option[String], email: Option[String], userId: Option[String])
case class saveResourceUser(resourceUser: ResourceUser)
case class deleteResourceUser(userId: Long)
diff --git a/obp-api/src/test/scala/code/bankaccountcreation/BankAccountCreationListenerTest.scala b/obp-api/src/test/scala/code/bankaccountcreation/BankAccountCreationListenerTest.scala
index 79aac35ef..580a02a46 100644
--- a/obp-api/src/test/scala/code/bankaccountcreation/BankAccountCreationListenerTest.scala
+++ b/obp-api/src/test/scala/code/bankaccountcreation/BankAccountCreationListenerTest.scala
@@ -38,7 +38,7 @@ class BankAccountCreationListenerTest extends ServerSetup with DefaultConnectorT
//need to create the user for the bank accout creation process to work
def getTestUser() =
Users.users.vend.getUserByProviderId(userProvider, userId).getOrElse {
- Users.users.vend.createResourceUser(userProvider, Some(userId), None, None, None, None, None, None).openOrThrowException(attemptedToOpenAnEmptyBox)
+ Users.users.vend.createResourceUser(userProvider, Some(userId), None, None, None, None, None, None, None).openOrThrowException(attemptedToOpenAnEmptyBox)
}
val expectedBankId = "quxbank"
diff --git a/obp-commons/src/main/scala/com/openbankproject/commons/model/CommonModel.scala b/obp-commons/src/main/scala/com/openbankproject/commons/model/CommonModel.scala
index 8dceecd5e..048be111e 100644
--- a/obp-commons/src/main/scala/com/openbankproject/commons/model/CommonModel.scala
+++ b/obp-commons/src/main/scala/com/openbankproject/commons/model/CommonModel.scala
@@ -802,7 +802,16 @@ case class Transaction(
val accountId = thisAccount.accountId
}
-case class UserCommons(userPrimaryKey : UserPrimaryKey, userId: String,idGivenByProvider: String, provider : String, emailAddress : String, name : String, createdByConsentId: Option[String] = None, createdByUserInvitationId: Option[String] = None, isDeleted: Option[Boolean] = None) extends User
+case class UserCommons(userPrimaryKey : UserPrimaryKey,
+ userId: String,
+ idGivenByProvider: String,
+ provider : String,
+ emailAddress : String,
+ name : String,
+ createdByConsentId: Option[String] = None,
+ createdByUserInvitationId: Option[String] = None,
+ isDeleted: Option[Boolean] = None,
+ lastMarketingAgreementSignedDate: Option[Date] = None) extends User
case class InternalBasicUser(
userId:String,
diff --git a/obp-commons/src/main/scala/com/openbankproject/commons/model/UserModel.scala b/obp-commons/src/main/scala/com/openbankproject/commons/model/UserModel.scala
index bf6a26021..4452e3b3d 100644
--- a/obp-commons/src/main/scala/com/openbankproject/commons/model/UserModel.scala
+++ b/obp-commons/src/main/scala/com/openbankproject/commons/model/UserModel.scala
@@ -26,6 +26,8 @@ TESOBE (http://www.tesobe.com/)
package com.openbankproject.commons.model
+import java.util.Date
+
/**
* An O-R mapped "User" class that includes first name, last name, password
*
@@ -66,6 +68,7 @@ trait User {
def isOriginalUser = createdByConsentId.isEmpty
def isConsentUser = createdByConsentId.nonEmpty
def isDeleted: Option[Boolean]
+ def lastMarketingAgreementSignedDate: Option[Date]
}
case class UserPrimaryKey(val value : Long) {
From ad4bfc125ca5432a5c3104c0245ae2770b6a2e19 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Marko=20Mili=C4=87?=
Date: Thu, 16 Sep 2021 17:40:08 +0200
Subject: [PATCH 020/185] feature/Add last_marketing_agreement_signed_date to
the users JSON
---
.../code/api/ResourceDocs1_4_0/SwaggerDefinitionsJSON.scala | 6 ++++--
.../src/main/scala/code/api/v4_0_0/JSONFactory4.0.0.scala | 6 ++++--
2 files changed, 8 insertions(+), 4 deletions(-)
diff --git a/obp-api/src/main/scala/code/api/ResourceDocs1_4_0/SwaggerDefinitionsJSON.scala b/obp-api/src/main/scala/code/api/ResourceDocs1_4_0/SwaggerDefinitionsJSON.scala
index 60e866dad..583196c6f 100644
--- a/obp-api/src/main/scala/code/api/ResourceDocs1_4_0/SwaggerDefinitionsJSON.scala
+++ b/obp-api/src/main/scala/code/api/ResourceDocs1_4_0/SwaggerDefinitionsJSON.scala
@@ -1711,7 +1711,8 @@ object SwaggerDefinitionsJSON {
entitlements = entitlementJSONs,
views = None,
agreements = None,
- is_deleted = false
+ is_deleted = false,
+ last_marketing_agreement_signed_date = Some(DateWithDayExampleObject)
)
val userJsonWithAgreementsV400 = UserJsonV400(
user_id = ExampleValue.userIdExample.value,
@@ -1722,7 +1723,8 @@ object SwaggerDefinitionsJSON {
entitlements = entitlementJSONs,
views = None,
agreements = Some(Nil),
- is_deleted = false
+ is_deleted = false,
+ last_marketing_agreement_signed_date = Some(DateWithDayExampleObject)
)
val userIdJsonV400 = UserIdJsonV400(
user_id = ExampleValue.userIdExample.value
diff --git a/obp-api/src/main/scala/code/api/v4_0_0/JSONFactory4.0.0.scala b/obp-api/src/main/scala/code/api/v4_0_0/JSONFactory4.0.0.scala
index 05713e556..f855ba317 100644
--- a/obp-api/src/main/scala/code/api/v4_0_0/JSONFactory4.0.0.scala
+++ b/obp-api/src/main/scala/code/api/v4_0_0/JSONFactory4.0.0.scala
@@ -899,7 +899,8 @@ case class UserJsonV400(
entitlements : EntitlementJSONs,
views: Option[ViewsJSON300],
agreements: Option[List[UserAgreementJson]],
- is_deleted: Boolean
+ is_deleted: Boolean,
+ last_marketing_agreement_signed_date: Option[Date]
)
case class UsersJsonV400(users: List[UserJsonV400])
@@ -917,7 +918,8 @@ object JSONFactory400 {
agreements = agreements.map(_.map( i =>
UserAgreementJson(`type` = i.agreementType, text = i.agreementText))
),
- is_deleted = user.isDeleted.getOrElse(false)
+ is_deleted = user.isDeleted.getOrElse(false),
+ last_marketing_agreement_signed_date = user.lastMarketingAgreementSignedDate
)
}
From e65cd51d37fd0329b318f2456a81d8e265375173 Mon Sep 17 00:00:00 2001
From: hongwei
Date: Fri, 17 Sep 2021 14:26:35 +0200
Subject: [PATCH 021/185] feature/added the new props
webui_main_faq_external_link
---
.../resources/props/sample.props.template | 5 +
.../src/main/scala/code/snippet/WebUI.scala | 16 ++
obp-api/src/main/webapp/index.html | 161 +-----------------
obp-api/src/main/webapp/mainFaq.html | 160 +++++++++++++++++
4 files changed, 182 insertions(+), 160 deletions(-)
create mode 100644 obp-api/src/main/webapp/mainFaq.html
diff --git a/obp-api/src/main/resources/props/sample.props.template b/obp-api/src/main/resources/props/sample.props.template
index c5fefe45c..039fc9e93 100644
--- a/obp-api/src/main/resources/props/sample.props.template
+++ b/obp-api/src/main/resources/props/sample.props.template
@@ -420,6 +420,11 @@ webui_sdks_url = https://github.com/OpenBankProject/OBP-API/wiki/OAuth-Client-SD
# then OBP-API can show the content to the HomePage `SDK Showcases`. Please check it over the sandbox homepage first.
#webui_featured_sdks_external_link = https://static.openbankproject.com/obp/sdks.html
+
+# the external html page for the FAQ section. the default link is the obp one. Please following the div to modify it. This link should be anonymous access.
+# then OBP-API can show the content to the HomePage `FAQs`. Please check it over the sandbox homepage first.
+#webui_main_faq_external_link = /mainFaq.html
+
# Text about data in FAQ
webui_faq_data_text = We use real data and customer profiles which have been anonymized.
diff --git a/obp-api/src/main/scala/code/snippet/WebUI.scala b/obp-api/src/main/scala/code/snippet/WebUI.scala
index abcc9b833..165c526df 100644
--- a/obp-api/src/main/scala/code/snippet/WebUI.scala
+++ b/obp-api/src/main/scala/code/snippet/WebUI.scala
@@ -142,7 +142,23 @@ class WebUI extends MdcLoggable{
"#main-showcases *" #> scala.xml.Unparsed(sdksHtmlContent)
}
+ val mainFaqHtmlLink = getWebUiPropsValue("webui_main_faq_external_link","")
+
+ val mainFaqHtmlContent = try{
+ if (mainFaqHtmlLink.isEmpty)//If the webui_featured_sdks_external_link is not set, we will read the internal sdks.html file instead.
+ LiftRules.getResource("/mainFaq.html").map{ url =>
+ Source.fromURL(url, "UTF-8").mkString
+ }.openOrThrowException("Please check the content of this file: src/main/webapp/mainFaq.html")
+ else
+ Source.fromURL(sdksHtmlLink, "UTF-8").mkString
+ }catch {
+ case _ : Throwable => "
SDK Showcases is wrong, please check the props `webui_featured_sdks_external_link`
"
+ }
+ // webui_featured_sdks_external_link props, we can set the sdks here. check the `SDK Showcases` in Homepage, and you can see all the sdks.
+ def mainFaqHtml: CssSel = {
+ "#main-faq *" #> scala.xml.Unparsed(mainFaqHtmlContent)
+ }
val brandString = activeBrand match {
case Some(v) => s"&brand=$v"
diff --git a/obp-api/src/main/webapp/index.html b/obp-api/src/main/webapp/index.html
index f3c9fea12..dec65bd91 100644
--- a/obp-api/src/main/webapp/index.html
+++ b/obp-api/src/main/webapp/index.html
@@ -255,166 +255,7 @@ Berlin 13359, Germany
-
- Avoid using trailing slashes, else, you would get a 404 error. Example:
- .../obp/v1.4.0 200 OK
- .../obp/v1.4.0/ 404 Not Found
-
-
Double check parameters are spelt correctly (including http vs https etc.)
-
Check your encoding (use UTF8)
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- There are two ways to authenticate a user: OAuth and Direct Login. If you
- are using this sandbox for a hackathon, we recommend you use Direct Login to authenticate as it
- is easier than the OAuth workflow.
-
-
-
-
-
-
-
-
-
-
-
-
- If you want to use OBP with OAuth, we recommend you use (and fork) one of our OAuth Starter
- SDKs.
- If you are using this sandbox for a hackathon, we recommend you use Direct Login.
- For an OAuth walkthrough example with sample code, please see here.
- We use OAuth 1.0a. For deepish technical details of the flow see
- here.
- We also support OAuth 2.0. For the technical details of using OBP API with OAuth 2.0, please see
- here.
-
- You will need to login to the API as a sandbox customer. You can do this using the API Explorer,
- or any REST client. You can find some example credentials here.
+ Avoid using trailing slashes, else, you would get a 404 error. Example:
+ .../obp/v1.4.0 200 OK
+ .../obp/v1.4.0/ 404 Not Found
+
+
Double check parameters are spelt correctly (including http vs https etc.)
+
Check your encoding (use UTF8)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ There are two ways to authenticate a user: OAuth and Direct Login. If you
+ are using this sandbox for a hackathon, we recommend you use Direct Login to authenticate as it
+ is easier than the OAuth workflow.
+
+
+
+
+
+
+
+
+
+
+
+
+ If you want to use OBP with OAuth, we recommend you use (and fork) one of our OAuth Starter
+ SDKs.
+ If you are using this sandbox for a hackathon, we recommend you use Direct Login.
+ For an OAuth walkthrough example with sample code, please see here.
+ We use OAuth 1.0a. For deepish technical details of the flow see
+ here.
+ We also support OAuth 2.0. For the technical details of using OBP API with OAuth 2.0, please see
+ here.
+
+ You will need to login to the API as a sandbox customer. You can do this using the API Explorer,
+ or any REST client. You can find some example credentials here.
+
From b0ae1ca5c128647044968734ed82b50c802b9ed2 Mon Sep 17 00:00:00 2001
From: hongwei
Date: Fri, 17 Sep 2021 14:33:37 +0200
Subject: [PATCH 022/185] docfix/added the webui_main_faq_external_link to
release_notes.md
---
release_notes.md | 1 +
1 file changed, 1 insertion(+)
diff --git a/release_notes.md b/release_notes.md
index acf52bb2c..3919b7f94 100644
--- a/release_notes.md
+++ b/release_notes.md
@@ -3,6 +3,7 @@
### Most recent changes at top of file
```
Date Commit Action
+17/09/2021 e65cd51d Added props: webui_main_faq_external_link, default is obp static file: /mainFaq.html
09/09/2021 65952225 Added props: webui_support_email, default is contact@openbankproject.com
02/09/2021 a826d908 Renamed Web UI props:
webui_post_user_invitation_privacy_conditions_value => webui_privacy_policy
From 7ebb47b55093e3010bc827719411a3f777c8ff74 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Marko=20Mili=C4=87?=
Date: Mon, 20 Sep 2021 09:03:53 +0200
Subject: [PATCH 023/185] bugfix/Use subject instead of given_name for username
---
obp-api/src/main/scala/code/api/openidconnect.scala | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/obp-api/src/main/scala/code/api/openidconnect.scala b/obp-api/src/main/scala/code/api/openidconnect.scala
index 6df50fdc2..a6ef5051a 100644
--- a/obp-api/src/main/scala/code/api/openidconnect.scala
+++ b/obp-api/src/main/scala/code/api/openidconnect.scala
@@ -182,7 +182,7 @@ object OpenIdConnect extends OBPRestHelper with MdcLoggable {
provider = issuer,
providerId = subject,
createdByConsentId = None,
- name = getClaim(name = "given_name", idToken = idToken).orElse(subject),
+ name = subject,
email = getClaim(name = "email", idToken = idToken),
userId = None,
createdByUserInvitationId = None,
From 282b1af7ce452d7ee03f19f0f162e58dc4db536d Mon Sep 17 00:00:00 2001
From: hongwei
Date: Mon, 20 Sep 2021 11:51:21 +0200
Subject: [PATCH 024/185] factor/tweaked mainFaq.html -> main-faq.html
---
obp-api/src/main/resources/props/sample.props.template | 2 +-
obp-api/src/main/scala/code/snippet/WebUI.scala | 4 ++--
obp-api/src/main/webapp/{mainFaq.html => main-faq.html} | 2 +-
release_notes.md | 2 +-
4 files changed, 5 insertions(+), 5 deletions(-)
rename obp-api/src/main/webapp/{mainFaq.html => main-faq.html} (99%)
diff --git a/obp-api/src/main/resources/props/sample.props.template b/obp-api/src/main/resources/props/sample.props.template
index 039fc9e93..2cd0951af 100644
--- a/obp-api/src/main/resources/props/sample.props.template
+++ b/obp-api/src/main/resources/props/sample.props.template
@@ -423,7 +423,7 @@ webui_sdks_url = https://github.com/OpenBankProject/OBP-API/wiki/OAuth-Client-SD
# the external html page for the FAQ section. the default link is the obp one. Please following the div to modify it. This link should be anonymous access.
# then OBP-API can show the content to the HomePage `FAQs`. Please check it over the sandbox homepage first.
-#webui_main_faq_external_link = /mainFaq.html
+#webui_main_faq_external_link = /main-faq.html
# Text about data in FAQ
webui_faq_data_text = We use real data and customer profiles which have been anonymized.
diff --git a/obp-api/src/main/scala/code/snippet/WebUI.scala b/obp-api/src/main/scala/code/snippet/WebUI.scala
index 165c526df..df1722300 100644
--- a/obp-api/src/main/scala/code/snippet/WebUI.scala
+++ b/obp-api/src/main/scala/code/snippet/WebUI.scala
@@ -146,9 +146,9 @@ class WebUI extends MdcLoggable{
val mainFaqHtmlContent = try{
if (mainFaqHtmlLink.isEmpty)//If the webui_featured_sdks_external_link is not set, we will read the internal sdks.html file instead.
- LiftRules.getResource("/mainFaq.html").map{ url =>
+ LiftRules.getResource("/main-faq.html").map{ url =>
Source.fromURL(url, "UTF-8").mkString
- }.openOrThrowException("Please check the content of this file: src/main/webapp/mainFaq.html")
+ }.openOrThrowException("Please check the content of this file: src/main/webapp/main-faq.html")
else
Source.fromURL(sdksHtmlLink, "UTF-8").mkString
}catch {
diff --git a/obp-api/src/main/webapp/mainFaq.html b/obp-api/src/main/webapp/main-faq.html
similarity index 99%
rename from obp-api/src/main/webapp/mainFaq.html
rename to obp-api/src/main/webapp/main-faq.html
index 97737a6e1..769160174 100644
--- a/obp-api/src/main/webapp/mainFaq.html
+++ b/obp-api/src/main/webapp/main-faq.html
@@ -1,4 +1,4 @@
-
FAQs-xxxxx
+
FAQs
diff --git a/release_notes.md b/release_notes.md
index 3919b7f94..4159cc333 100644
--- a/release_notes.md
+++ b/release_notes.md
@@ -3,7 +3,7 @@
### Most recent changes at top of file
```
Date Commit Action
-17/09/2021 e65cd51d Added props: webui_main_faq_external_link, default is obp static file: /mainFaq.html
+17/09/2021 e65cd51d Added props: webui_main_faq_external_link, default is obp static file: /main-faq.html
09/09/2021 65952225 Added props: webui_support_email, default is contact@openbankproject.com
02/09/2021 a826d908 Renamed Web UI props:
webui_post_user_invitation_privacy_conditions_value => webui_privacy_policy
From d2ab9652edf99289ba321d7dbcfaafc3f8ce6f0c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Marko=20Mili=C4=87?=
Date: Mon, 20 Sep 2021 11:51:44 +0200
Subject: [PATCH 025/185] feature/Allow an email as a valid username
---
obp-api/src/main/scala/code/model/dataAccess/AuthUser.scala | 2 ++
1 file changed, 2 insertions(+)
diff --git a/obp-api/src/main/scala/code/model/dataAccess/AuthUser.scala b/obp-api/src/main/scala/code/model/dataAccess/AuthUser.scala
index 2bed203ff..121b96988 100644
--- a/obp-api/src/main/scala/code/model/dataAccess/AuthUser.scala
+++ b/obp-api/src/main/scala/code/model/dataAccess/AuthUser.scala
@@ -140,6 +140,7 @@ class AuthUser extends MegaProtoUser[AuthUser] with MdcLoggable {
}
/**
+ * Username is a valid email address or the regex below:
* Regex to validate a username
*
* ^(?=.{8,100}$)(?![_.])(?!.*[_.]{2})[a-zA-Z0-9._]+(? String)(e: String) = e match {
case null => List(FieldError(this, Text(msg)))
case e if e.trim.isEmpty => List(FieldError(this, Text(msg)))
+ case e if emailRegex.findFirstMatchIn(e).isDefined => Nil // Email is valid username
case e if usernameRegex.findFirstMatchIn(e).isDefined => Nil
case _ => List(FieldError(this, Text(msg)))
}
From 85e205e9dfd2b46efcebae4b91c28d20826ec258 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Marko=20Mili=C4=87?=
Date: Tue, 21 Sep 2021 13:31:08 +0200
Subject: [PATCH 026/185] feature/Make OBP API return Bad Request on Duplicate
Query Parameters or Header keys
---
.../main/scala/code/api/util/APIUtil.scala | 42 +++++++++++++++++++
.../scala/code/api/util/ErrorMessages.scala | 2 +
2 files changed, 44 insertions(+)
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 ee9975643..3f7dd48e8 100644
--- a/obp-api/src/main/scala/code/api/util/APIUtil.scala
+++ b/obp-api/src/main/scala/code/api/util/APIUtil.scala
@@ -3848,6 +3848,40 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{
}
}
}
+ /**
+ * validate whether current request's query parameters
+ * @param operationId
+ * @param callContext
+ * @return Full(errorResponse) if validate fail
+ */
+ def validateQueryParams(operationId: String, callContext: CallContext): Box[JsonResponse] = {
+ val queryString: String = if (callContext.url.contains("?")) callContext.url.split("\\?",2)(1) else ""
+ val queryParams: Array[String] = queryString.split("&").map(_.split("=")(0))
+ val queryParamsGrouped: Map[String, Array[String]] = queryParams.groupBy(x => x)
+ queryParamsGrouped.toList.forall(_._2.size == 1) match {
+ case true => Empty
+ case false =>
+ Box.tryo(
+ createErrorJsonResponse(s"${ErrorMessages.DuplicatedQueryParameters}", 400, callContext.correlationId)
+ )
+ }
+ }
+ /**
+ * validate whether current request's header keys
+ * @param operationId
+ * @param callContext
+ * @return Full(errorResponse) if validate fail
+ */
+ def validateRequestHeadersKeys(operationId: String, callContext: CallContext): Box[JsonResponse] = {
+ val headerKeysGrouped: Map[String, List[HTTPParam]] = callContext.requestHeaders.groupBy(x => x.name)
+ headerKeysGrouped.toList.forall(_._2.size == 1) match {
+ case true => Empty
+ case false =>
+ Box.tryo(
+ createErrorJsonResponse(s"${ErrorMessages.DuplicatedHeaderKeys}", 400, callContext.correlationId)
+ )
+ }
+ }
def createErrorJsonResponse(errorMsg: String, errorCode: Int, correlationId: String): JsonResponse = {
import net.liftweb.json.JsonDSL._
@@ -3911,6 +3945,14 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{
// validate auth type
{
case (Some(callContext), operationId) => validateAuthType(operationId, callContext)
+ },
+ // validate query params
+ {
+ case (Some(callContext), operationId) => validateQueryParams(operationId, callContext)
+ },
+ // validate request header keys
+ {
+ case (Some(callContext), operationId) => validateRequestHeadersKeys(operationId, callContext)
}
)
diff --git a/obp-api/src/main/scala/code/api/util/ErrorMessages.scala b/obp-api/src/main/scala/code/api/util/ErrorMessages.scala
index 474afa523..33cc20776 100644
--- a/obp-api/src/main/scala/code/api/util/ErrorMessages.scala
+++ b/obp-api/src/main/scala/code/api/util/ErrorMessages.scala
@@ -58,6 +58,8 @@ object ErrorMessages {
val InvalidRequestPayload = "OBP-09014: Incorrect request body Format, it should be a valid json that matches Validation rule."
val DynamicDataNotFound = "OBP-09015: Dynamic Data not found. Please specify a valid value."
+ val DuplicatedQueryParameters = "OBP-09016: Duplicated Query Parameters are not allowed."
+ val DuplicatedHeaderKeys = "OBP-09017: Duplicated Header Keys are not allowed."
// General messages (OBP-10XXX)
From 9bc3af947435927f4d6984e3d539e6c83a460f4b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Marko=20Mili=C4=87?=
Date: Wed, 22 Sep 2021 07:24:16 +0200
Subject: [PATCH 027/185] refactor/Tweak error messages Duplicated => Duplicate
---
obp-api/src/main/scala/code/api/util/APIUtil.scala | 4 ++--
obp-api/src/main/scala/code/api/util/ErrorMessages.scala | 4 ++--
2 files changed, 4 insertions(+), 4 deletions(-)
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 3f7dd48e8..32ef1d00e 100644
--- a/obp-api/src/main/scala/code/api/util/APIUtil.scala
+++ b/obp-api/src/main/scala/code/api/util/APIUtil.scala
@@ -3862,7 +3862,7 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{
case true => Empty
case false =>
Box.tryo(
- createErrorJsonResponse(s"${ErrorMessages.DuplicatedQueryParameters}", 400, callContext.correlationId)
+ createErrorJsonResponse(s"${ErrorMessages.DuplicateQueryParameters}", 400, callContext.correlationId)
)
}
}
@@ -3878,7 +3878,7 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{
case true => Empty
case false =>
Box.tryo(
- createErrorJsonResponse(s"${ErrorMessages.DuplicatedHeaderKeys}", 400, callContext.correlationId)
+ createErrorJsonResponse(s"${ErrorMessages.DuplicateHeaderKeys}", 400, callContext.correlationId)
)
}
}
diff --git a/obp-api/src/main/scala/code/api/util/ErrorMessages.scala b/obp-api/src/main/scala/code/api/util/ErrorMessages.scala
index 33cc20776..aada7e286 100644
--- a/obp-api/src/main/scala/code/api/util/ErrorMessages.scala
+++ b/obp-api/src/main/scala/code/api/util/ErrorMessages.scala
@@ -58,8 +58,8 @@ object ErrorMessages {
val InvalidRequestPayload = "OBP-09014: Incorrect request body Format, it should be a valid json that matches Validation rule."
val DynamicDataNotFound = "OBP-09015: Dynamic Data not found. Please specify a valid value."
- val DuplicatedQueryParameters = "OBP-09016: Duplicated Query Parameters are not allowed."
- val DuplicatedHeaderKeys = "OBP-09017: Duplicated Header Keys are not allowed."
+ val DuplicateQueryParameters = "OBP-09016: Duplicate Query Parameters are not allowed."
+ val DuplicateHeaderKeys = "OBP-09017: Duplicate Header Keys are not allowed."
// General messages (OBP-10XXX)
From 09e44eba249954085cd5eba9ac3a31c785725da4 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Marko=20Mili=C4=87?=
Date: Wed, 22 Sep 2021 09:34:48 +0200
Subject: [PATCH 028/185] refactor/User Invitation Page
---
.../scala/code/snippet/UserInvitation.scala | 6 +-
.../src/main/webapp/media/css/data-area.css | 151 ++++++++++++++++++
obp-api/src/main/webapp/media/css/website.css | 2 +
obp-api/src/main/webapp/media/js/website.js | 7 +
obp-api/src/main/webapp/user-invitation.html | 12 +-
5 files changed, 169 insertions(+), 9 deletions(-)
create mode 100644 obp-api/src/main/webapp/media/css/data-area.css
diff --git a/obp-api/src/main/scala/code/snippet/UserInvitation.scala b/obp-api/src/main/scala/code/snippet/UserInvitation.scala
index 5cd61f183..7ff0609e7 100644
--- a/obp-api/src/main/scala/code/snippet/UserInvitation.scala
+++ b/obp-api/src/main/scala/code/snippet/UserInvitation.scala
@@ -128,9 +128,9 @@ class UserInvitation extends MdcLoggable {
}
def showError(usernameError: String) = {
- S.error("register-consumer-errors", usernameError)
+ S.error("data-area-errors", usernameError)
register &
- "#register-consumer-errors *" #> {
+ "#data-area-errors *" #> {
".error *" #>
List(usernameError).map({ e =>
".errorContent *" #> e
@@ -170,7 +170,7 @@ class UserInvitation extends MdcLoggable {
"#marketing_info_checkbox" #> SHtml.checkbox(marketingInfoCheckboxVar, marketingInfoCheckboxVar(_)) &
"type=submit" #> SHtml.submit(s"$registrationConsumerButtonValue", () => submitButtonDefense)
} &
- "#register-consumer-success" #> ""
+ "#data-area-success" #> ""
}
userInvitation match {
case Full(payload) if payload.status == "CREATED" => // All good
diff --git a/obp-api/src/main/webapp/media/css/data-area.css b/obp-api/src/main/webapp/media/css/data-area.css
new file mode 100644
index 000000000..a3cd199ca
--- /dev/null
+++ b/obp-api/src/main/webapp/media/css/data-area.css
@@ -0,0 +1,151 @@
+#data-area {
+ background-color: #53C4EF;
+ padding: 20px 10px;
+ color: white;
+ margin: 30px 0 90px;
+}
+
+#data-area #data-area-explanation,
+#data-area form,
+#data-area-errors,
+#data-area-success {
+ max-width: 610px;
+ margin: 0 auto;
+ color: #333333;
+}
+#data-area-errors span{
+ color: black;
+}
+#data-area h1 {
+ font-family: Roboto-Light;
+ font-size: 28px;
+ color: #333333;
+ letter-spacing: 0;
+ line-height: 36px;
+ text-align: left;
+ margin-bottom: 32px;
+ font-weight: normal;
+}
+
+#data-area #data-area-explanation p:nth-child(2) {
+ font-family: Roboto-Light;
+ font-size: 22px;
+ color: #333333;
+ letter-spacing: 0;
+ line-height: 31px;
+ margin-bottom: 8px;
+ font-weight: normal;
+ text-align: left;
+}
+
+#data-area #data-area-explanation p:last-child{
+ margin-top: 8px;
+ font-family: Roboto-Regular;
+ font-size: 14px;
+ color: #333333;
+ letter-spacing: 0;
+ line-height: 20px;
+ margin-bottom: 32px;
+}
+
+#data-area-input form label{
+ margin-bottom: 8px;
+ margin-top: 17px;
+ font-family: Roboto-Regular;
+ font-size: 16px;
+ color: #333333;
+ line-height: 24px;
+}
+
+#data-area-input form select{
+ height: 44px;
+ border: 1px solid #767676;
+ -webkit-appearance: none;
+ -webkit-border-radius: 0px;
+}
+
+#data-area #data-area-explanation {
+ margin-top: 32px;
+ margin-bottom: 20px;
+ color: #333333;
+ padding: 0 15px;
+}
+
+#data-area textarea {
+ height: 96px;
+ border: 1px solid #767676;
+ border-radius: 0px;
+}
+#data-area #data-area-errors {
+ margin-bottom: 20px;
+}
+#data-area #data-area-success {
+ margin-top: 30px;
+}
+
+#data-area #data-area-success h1 {
+ padding-left: 15px;
+}
+
+#data-area #data-area-success #data-area-success-message{
+ font-family: Roboto-Light;
+ font-size: 22px;
+ color: #333333;
+ letter-spacing: 0;
+ line-height: 31px;
+ padding-left: 15px;
+}
+
+
+#data-area #data-area-success p {
+ font-family: Roboto-Light;
+ font-size: 22px;
+ color: #333333;
+ letter-spacing: 0;
+ margin-bottom: 44px;
+ line-height: 31px;
+ padding-left: 15px;
+}
+
+#data-area-success a{
+ text-decoration:underline;
+}
+
+
+#data-area-success span,
+#data-area-success a{
+ font-family: Roboto-Regular;
+ font-size: 16px;
+ color: #333333;
+ line-height: 24px;
+}
+#data-area #data-area-success .row {
+ margin-bottom: 20px;
+}
+#data-area #data-area-success .row div:nth-child(1) {
+ font-family: Roboto-Medium;
+ font-size: 16px;
+ color: #333333;
+ line-height: 24px;
+}
+#data-area-input form .btn-danger{
+ margin-left: 0;
+ margin-top: 33px;
+}
+
+#data-area-input #data-area-errors-div{
+ margin-top: 8px;
+}
+
+#data-area-input #data-area-errors{
+ font-family: Roboto-Regular;
+ font-size: 14px;
+ color: #333333;
+ line-height: 20px;
+ background-image: url(/media/images/icons/status_error_onlight.svg);
+ background-repeat:no-repeat;
+ background-position: left 12px;
+ background-size: 18px 15.70px;
+ padding-top: 8px;
+ padding-left: 26px;
+}
\ No newline at end of file
diff --git a/obp-api/src/main/webapp/media/css/website.css b/obp-api/src/main/webapp/media/css/website.css
index 0bd4ea9b1..c4a621bd8 100644
--- a/obp-api/src/main/webapp/media/css/website.css
+++ b/obp-api/src/main/webapp/media/css/website.css
@@ -8,6 +8,7 @@
@import url(/media/css/main-start.css);
@import url(/media/css/authorise.css);
@import url(/media/css/register-consumer.css);
+@import url(/media/css/data-area.css);
@import url(/media/css/signup.css);
@import url(/media/css/recover-password.css);
@import url(/media/css/main-showcases.css);
@@ -284,6 +285,7 @@ header #header-decoration,
#signup,
#recover-password,
#register-consumer,
+#data-area ,
#create-account,
.navbar-default .navbar-toggle:hover,
.navbar-default .navbar-toggle:focus,
diff --git a/obp-api/src/main/webapp/media/js/website.js b/obp-api/src/main/webapp/media/js/website.js
index a740571c9..ac02198c5 100644
--- a/obp-api/src/main/webapp/media/js/website.js
+++ b/obp-api/src/main/webapp/media/js/website.js
@@ -340,6 +340,13 @@ $(document).ready(function() {
} else{
consumerRegistrationAppRequestUriError.parent().addClass('hide');
}
+
+ var dataAreaErrors = $('#data-area-input #data-area-errors');
+ if (dataAreaErrors.length > 0 && dataAreaErrors.html().length > 0) {
+ dataAreaErrors.parent().removeClass('hide');
+ } else{
+ dataAreaErrors.parent().addClass('hide');
+ }
{
var consumerRegistrationJwksError = $('#register-consumer-input #consumer-registration-app-signing_jwks-error');
diff --git a/obp-api/src/main/webapp/user-invitation.html b/obp-api/src/main/webapp/user-invitation.html
index 29bd87f1d..1d8901434 100644
--- a/obp-api/src/main/webapp/user-invitation.html
+++ b/obp-api/src/main/webapp/user-invitation.html
@@ -23,11 +23,11 @@ Berlin 13359, Germany
This product includes software developed at
TESOBE (http://www.tesobe.com/)
-->
-
+
-
-
+
+
Complete your user invitation
Please complete the information about the user invitation application below.
All fields are required unless marked as 'optional'
@@ -94,9 +94,9 @@ Berlin 13359, Germany
-
-
-
+
+
+
From 071b28a633bc9629e200cbe86b6f62ad9cd9243e Mon Sep 17 00:00:00 2001
From: hongwei
Date: Fri, 15 Oct 2021 12:22:38 +0200
Subject: [PATCH 073/185] docfix/typo
---
obp-api/src/main/scala/code/api/util/Glossary.scala | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/obp-api/src/main/scala/code/api/util/Glossary.scala b/obp-api/src/main/scala/code/api/util/Glossary.scala
index 00a82de98..2bd3a303e 100644
--- a/obp-api/src/main/scala/code/api/util/Glossary.scala
+++ b/obp-api/src/main/scala/code/api/util/Glossary.scala
@@ -2287,7 +2287,7 @@ object Glossary extends MdcLoggable {
|
|To enable Endpoint Mapping for your Dynamic Endpoints, either set the `host` in the swagger file to "dynamic_entity" upon creation of the Dynamic Endpoints - or update the host using the Update Dynamic Endpoint Host endpoints.
|
- |Once the `host` is thus set, you can the Endpoint Mapping endpoints to map the Dynamic Endpoint fields to Dynamic Entity data.
+ |Once the `host` is thus set, you can use the Endpoint Mapping endpoints to map the Dynamic Endpoint fields to Dynamic Entity data.
|
|See the [Create Endpoint Mapping](/index#OBPv4.0.0-createEndpointMapping) JSON body. You will need to know the operation_id in advance and you can prepare the request_mapping and response_mapping objects. You can get the operation ID from the API Explorer or Get Dynamic Endpoints endpoints.
|
From 3a95a6d6267e7c2e26b189554f92f3c73e099db1 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Marko=20Mili=C4=87?=
Date: Mon, 18 Oct 2021 12:07:51 +0200
Subject: [PATCH 074/185] feature/Add link to the Hola app i.e.
webui_api_hola_url props
---
obp-api/src/main/resources/props/sample.props.template | 3 +++
obp-api/src/main/scala/code/snippet/WebUI.scala | 4 ++++
obp-api/src/main/webapp/index.html | 2 ++
3 files changed, 9 insertions(+)
diff --git a/obp-api/src/main/resources/props/sample.props.template b/obp-api/src/main/resources/props/sample.props.template
index 816064d10..c88d5ffa0 100644
--- a/obp-api/src/main/resources/props/sample.props.template
+++ b/obp-api/src/main/resources/props/sample.props.template
@@ -387,6 +387,9 @@ webui_obp_cli_url = https://github.com/OpenBankProject/OBP-CLI
# API Tester URL, change to your instance
webui_api_tester_url = https://apitester.openbankproject.com
+# API Hola app URL, change to your instance
+webui_api_hola_url = #
+
diff --git a/obp-api/src/main/scala/code/snippet/WebUI.scala b/obp-api/src/main/scala/code/snippet/WebUI.scala
index d225a9a3d..a3581422d 100644
--- a/obp-api/src/main/scala/code/snippet/WebUI.scala
+++ b/obp-api/src/main/scala/code/snippet/WebUI.scala
@@ -193,6 +193,10 @@ class WebUI extends MdcLoggable{
def apiTesterLink: CssSel = {
".api-tester-link a [href]" #> scala.xml.Unparsed(getWebUiPropsValue("webui_api_tester_url", ""))
}
+ // Link to Hola app
+ def apiHolaLink: CssSel = {
+ ".api-hola-link a [href]" #> scala.xml.Unparsed(getWebUiPropsValue("webui_api_hola_url", "#"))
+ }
// Link to API
def apiLink: CssSel = {
diff --git a/obp-api/src/main/webapp/index.html b/obp-api/src/main/webapp/index.html
index dec65bd91..4a5944c90 100644
--- a/obp-api/src/main/webapp/index.html
+++ b/obp-api/src/main/webapp/index.html
@@ -325,6 +325,8 @@ Berlin 13359, Germany
href="">OBP CLI
API Tester
+ Hola
From c32e8c26084b304fc871ac9444ebe406c1e3ff93 Mon Sep 17 00:00:00 2001
From: hongwei
Date: Mon, 18 Oct 2021 13:04:23 +0200
Subject: [PATCH 075/185] feature/added the sort_direction parameter and ORDER
BY accountId for fastFirehoseAccounts
---
.../main/scala/code/api/v4_0_0/APIMethods400.scala | 11 ++---------
.../code/bankconnectors/LocalMappedConnector.scala | 10 ++++++++--
2 files changed, 10 insertions(+), 11 deletions(-)
diff --git a/obp-api/src/main/scala/code/api/v4_0_0/APIMethods400.scala b/obp-api/src/main/scala/code/api/v4_0_0/APIMethods400.scala
index f28c9fe16..ef83826cc 100644
--- a/obp-api/src/main/scala/code/api/v4_0_0/APIMethods400.scala
+++ b/obp-api/src/main/scala/code/api/v4_0_0/APIMethods400.scala
@@ -3327,14 +3327,7 @@ trait APIMethods400 {
|This endpoint allows bulk access to accounts.
|
|optional pagination parameters for filter with accounts
- |${urlParametersDocument(true, false)
- .replace(s"""
- |
- |* sort_direction=ASC/DESC ==> default value: DESC.
- |
- |eg2:?limit=100&offset=0&sort_direction=ASC
- |
- |""". stripMargin,"")}
+ |${urlParametersDocument(true, false)}
|
|${authenticationRequiredMessage(true)}
|
@@ -3355,7 +3348,7 @@ trait APIMethods400 {
_ <- Helper.booleanToFuture(failMsg = AccountFirehoseNotAllowedOnThisInstance, cc=cc.callContext) {
allowAccountFirehose
}
- allowedParams = List("limit", "offset")
+ allowedParams = List("limit", "offset", "sort_direction")
httpParams <- NewStyle.function.extractHttpParamsFromUrl(cc.url)
obpQueryParams <- NewStyle.function.createObpParams(httpParams, allowedParams, callContext)
(firehoseAccounts, callContext) <- NewStyle.function.getBankAccountsWithAttributes(bankId, obpQueryParams, cc.callContext)
diff --git a/obp-api/src/main/scala/code/bankconnectors/LocalMappedConnector.scala b/obp-api/src/main/scala/code/bankconnectors/LocalMappedConnector.scala
index cf25e3a6a..32d945ecd 100644
--- a/obp-api/src/main/scala/code/bankconnectors/LocalMappedConnector.scala
+++ b/obp-api/src/main/scala/code/bankconnectors/LocalMappedConnector.scala
@@ -832,14 +832,20 @@ object LocalMappedConnector extends Connector with MdcLoggable {
override def getBankAccountsWithAttributes(bankId: BankId, queryParams: List[OBPQueryParam], callContext: Option[CallContext]): OBPReturnType[Box[List[FastFirehoseAccount]]] =
Future{
- val limit = queryParams.collect { case OBPLimit(value) => value }.headOption.getOrElse(1000)
+ val limit = queryParams.collect { case OBPLimit(value) => value }.headOption.getOrElse(50)
val offset = queryParams.collect { case OBPOffset(value) => value }.headOption.getOrElse(0)
+ val orderBy = queryParams.collect {
+ case OBPOrdering(_, OBPDescending) => "DESC"
+ }.headOption.getOrElse("ASC")
+
+ val ordering = if (orderBy =="DESC" ) sqls"DESC" else sqls"ASC"
val firehoseAccounts = {
scalikeDB readOnly { implicit session =>
- val sqlResult =sql"""
+ val sqlResult = sql"""
select * from v_fast_firehose_accounts
WHERE v_fast_firehose_accounts.bank_id = ${bankId.value}
+ ORDER BY v_fast_firehose_accounts.account_id $ordering
LIMIT $limit
OFFSET $offset
""".stripMargin
From 80ec3716ed2ad084b7e9c888d14e8d8e49aa043c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Marko=20Mili=C4=87?=
Date: Tue, 19 Oct 2021 07:39:07 +0200
Subject: [PATCH 076/185] feature/3rd party developer consents to collecting
personal data 2
---
obp-api/src/main/resources/props/sample.props.template | 4 ++--
obp-api/src/main/scala/code/snippet/UserInvitation.scala | 6 +++---
2 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/obp-api/src/main/resources/props/sample.props.template b/obp-api/src/main/resources/props/sample.props.template
index c88d5ffa0..8ea3dc84f 100644
--- a/obp-api/src/main/resources/props/sample.props.template
+++ b/obp-api/src/main/resources/props/sample.props.template
@@ -1083,5 +1083,5 @@ webui_developer_user_invitation_email_html_text=\