From 40579252ee95283ec82199aafd908ebe7c9d71c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marko=20Mili=C4=87?= Date: Fri, 21 May 2021 11:28:04 +0200 Subject: [PATCH 001/147] bugfix/Fiy OIDC library version issue; Uncomment login GUI --- obp-api/pom.xml | 4 +-- obp-api/src/main/webapp/oauth/authorize.html | 28 +++++++++---------- .../main/webapp/templates-hidden/_login.html | 28 +++++++++---------- 3 files changed, 30 insertions(+), 30 deletions(-) diff --git a/obp-api/pom.xml b/obp-api/pom.xml index 2246aea1e..92fbb7418 100644 --- a/obp-api/pom.xml +++ b/obp-api/pom.xml @@ -324,7 +324,7 @@ com.nimbusds nimbus-jose-jwt - 9.7 + 9.9.3 com.github.OpenBankProject @@ -347,7 +347,7 @@ com.nimbusds oauth2-oidc-sdk - 7.4 + 9.5.2 com.vladsch.flexmark diff --git a/obp-api/src/main/webapp/oauth/authorize.html b/obp-api/src/main/webapp/oauth/authorize.html index 482f7f091..397c5fbc8 100644 --- a/obp-api/src/main/webapp/oauth/authorize.html +++ b/obp-api/src/main/webapp/oauth/authorize.html @@ -43,20 +43,20 @@ Register - - - - - - - - - - - - - - + +
+
+
+ +
+
+
+
+
OIDC 2 +
+
+
diff --git a/obp-api/src/main/webapp/templates-hidden/_login.html b/obp-api/src/main/webapp/templates-hidden/_login.html index 41a52bd00..4517637b6 100644 --- a/obp-api/src/main/webapp/templates-hidden/_login.html +++ b/obp-api/src/main/webapp/templates-hidden/_login.html @@ -45,20 +45,20 @@ Register - - - - - - - - - - - - - - + +
+
+
+ +
+
+
+
+
OIDC 2 +
+
+
From a1bbb6980b62eeb8d11dc274cd30597c83826844 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marko=20Mili=C4=87?= Date: Mon, 24 May 2021 20:03:42 +0200 Subject: [PATCH 002/147] feature/Rewrite getOtherAccountForTransaction as the new style endpoint --- .../src/main/scala/code/api/util/NewStyle.scala | 1 + .../scala/code/api/v1_2_1/APIMethods121.scala | 17 +++++++++++------ .../scala/code/api/v1_2_1/API1_2_1Test.scala | 14 +++++++------- 3 files changed, 19 insertions(+), 13 deletions(-) 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 a334297b3..b57d70186 100644 --- a/obp-api/src/main/scala/code/api/util/NewStyle.scala +++ b/obp-api/src/main/scala/code/api/util/NewStyle.scala @@ -71,6 +71,7 @@ import net.liftweb.json object NewStyle { lazy val endpoints: List[(String, String)] = List( (nameOf(Implementations1_2_1.deleteWhereTagForViewOnTransaction), ApiVersion.v1_2_1.toString), + (nameOf(Implementations1_2_1.getOtherAccountForTransaction), ApiVersion.v1_2_1.toString), (nameOf(Implementations1_2_1.getOtherAccountMetadata), ApiVersion.v1_2_1.toString), (nameOf(Implementations1_2_1.getCounterpartyPublicAlias), ApiVersion.v1_2_1.toString), (nameOf(Implementations1_2_1.addCounterpartyMoreInfo), ApiVersion.v1_2_1.toString), diff --git a/obp-api/src/main/scala/code/api/v1_2_1/APIMethods121.scala b/obp-api/src/main/scala/code/api/v1_2_1/APIMethods121.scala index fbdf4ec47..3e45426b8 100644 --- a/obp-api/src/main/scala/code/api/v1_2_1/APIMethods121.scala +++ b/obp-api/src/main/scala/code/api/v1_2_1/APIMethods121.scala @@ -3106,13 +3106,18 @@ trait APIMethods121 { case "banks" :: BankId(bankId) :: "accounts" :: AccountId(accountId) :: ViewId(viewId) :: "transactions":: TransactionId(transactionId) :: "other_account" :: Nil JsonGet req => { cc => for { - account <- BankAccountX(bankId, accountId) ?~! BankAccountNotFound - view <- APIUtil.checkViewAccessAndReturnView(viewId, BankIdAccountId(account.bankId, account.accountId), cc.user) ?~! ViewNotFound - (transaction, callerContext) <- account.moderatedTransaction(transactionId, view, BankIdAccountId(bankId,accountId), cc.user, Some(cc)) - moderatedOtherBankAccount <- transaction.otherBankAccount + (Full(u), callContext) <- authenticatedAccess(cc) + (account, callContext) <- NewStyle.function.checkBankAccountExists(bankId, accountId, callContext) + view <- NewStyle.function.checkViewAccessAndReturnView(viewId, BankIdAccountId(bankId, accountId), Some(u), callContext) + (moderatedTransaction, callContext) <- account.moderatedTransactionFuture(transactionId, view, Full(u), callContext) map { + unboxFullOrFail(_, callContext, GetTransactionsException) + } + _ <- NewStyle.function.tryons(GetTransactionsException, 400, callContext) { + moderatedTransaction.otherBankAccount.isDefined + } } yield { - val otherBankAccountJson = JSONFactory.createOtherBankAccount(moderatedOtherBankAccount) - successJsonResponse(Extraction.decompose(otherBankAccountJson)) + val otherBankAccountJson = JSONFactory.createOtherBankAccount(moderatedTransaction.otherBankAccount.get) + (otherBankAccountJson, HttpCode.`200`(callContext)) } } diff --git a/obp-api/src/test/scala/code/api/v1_2_1/API1_2_1Test.scala b/obp-api/src/test/scala/code/api/v1_2_1/API1_2_1Test.scala index e5e5c8bad..9afa3373c 100644 --- a/obp-api/src/test/scala/code/api/v1_2_1/API1_2_1Test.scala +++ b/obp-api/src/test/scala/code/api/v1_2_1/API1_2_1Test.scala @@ -6471,8 +6471,8 @@ class API1_2_1Test extends ServerSetupWithTestData with DefaultUsers with Privat val transaction = randomTransaction(bankId, bankAccount.id, view) When("the request is sent") val reply = getTheCounterpartyOfOneTransaction(bankId, bankAccount.id, view, transaction.id, None) - Then("we should get a 400 code") - reply.code should equal (400) + Then("we should get a 401 code") + reply.code should equal (401) And("we should get an error message") reply.body.extract[ErrorMessage].message.nonEmpty should equal (true) } @@ -6485,8 +6485,8 @@ class API1_2_1Test extends ServerSetupWithTestData with DefaultUsers with Privat val transaction = randomTransaction(bankId, bankAccount.id, view) When("the request is sent") val reply = getTheCounterpartyOfOneTransaction(bankId, bankAccount.id, view, transaction.id, user3) - Then("we should get a 400 code") - reply.code should equal (400) + Then("we should get a 403 code") + reply.code should equal (403) And("we should get an error message") reply.body.extract[ErrorMessage].message.nonEmpty should equal (true) } @@ -6499,10 +6499,10 @@ class API1_2_1Test extends ServerSetupWithTestData with DefaultUsers with Privat val transaction = randomTransaction(bankId, bankAccount.id, view) When("the request is sent") val reply = getTheCounterpartyOfOneTransaction(bankId, bankAccount.id, randomString(5), transaction.id, user1) - Then("we should get a 400 code") - reply.code should equal (400) + Then("we should get a 403 code") + reply.code should equal (403) And("we should get an error message") - reply.body.extract[ErrorMessage].message should equal (ViewNotFound) + reply.body.extract[ErrorMessage].message should equal (UserNoPermissionAccessView) } scenario("we will not get get the other bank account of a random transaction because the transaction does not exist", API1_2_1, GetTransactionAccount){ From d6432786a829336f1c1590b53bfa2af4c7a75153 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marko=20Mili=C4=87?= Date: Fri, 28 May 2021 16:51:26 +0200 Subject: [PATCH 003/147] feature/Add User Invitation Flow - WIP --- .../main/scala/bootstrap/liftweb/Boot.scala | 2 + .../code/remotedata/RemotedataActors.scala | 3 +- .../remotedata/RemotedataUserInvitation.scala | 16 ++++++++ .../RemotedataUserInvitationActor.scala | 24 ++++++++++++ .../code/users/UserInitationProvider.scala | 39 +++++++++++++++++++ .../scala/code/users/UserInvitation.scala | 37 ++++++++++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) create mode 100644 obp-api/src/main/scala/code/remotedata/RemotedataUserInvitation.scala create mode 100644 obp-api/src/main/scala/code/remotedata/RemotedataUserInvitationActor.scala create mode 100644 obp-api/src/main/scala/code/users/UserInitationProvider.scala create mode 100644 obp-api/src/main/scala/code/users/UserInvitation.scala diff --git a/obp-api/src/main/scala/bootstrap/liftweb/Boot.scala b/obp-api/src/main/scala/bootstrap/liftweb/Boot.scala index 3332161c4..15022e7fc 100644 --- a/obp-api/src/main/scala/bootstrap/liftweb/Boot.scala +++ b/obp-api/src/main/scala/bootstrap/liftweb/Boot.scala @@ -118,6 +118,7 @@ import code.transactionattribute.MappedTransactionAttribute import code.transactionrequests.{MappedTransactionRequest, MappedTransactionRequestTypeCharge, TransactionRequestReasons} import code.usercustomerlinks.MappedUserCustomerLink import code.userlocks.UserLocks +import code.users.UserInvitation import code.util.Helper.MdcLoggable import code.util.{Helper, HydraUtil} import code.validation.JsonSchemaValidation @@ -821,6 +822,7 @@ object ToSchemify { AccountAccess, ViewDefinition, ResourceUser, + UserInvitation, MappedComment, MappedTag, MappedWhereTag, diff --git a/obp-api/src/main/scala/code/remotedata/RemotedataActors.scala b/obp-api/src/main/scala/code/remotedata/RemotedataActors.scala index 1251848c1..bb0470e3c 100644 --- a/obp-api/src/main/scala/code/remotedata/RemotedataActors.scala +++ b/obp-api/src/main/scala/code/remotedata/RemotedataActors.scala @@ -62,7 +62,8 @@ object RemotedataActors extends MdcLoggable { ActorProps[RemotedataCustomerAttributeActor] -> RemotedataCustomerAttribute.actorName, ActorProps[RemotedataTransactionAttributeActor] -> RemotedataTransactionAttribute.actorName, ActorProps[RemotedataRateLimitingActor] -> RemotedataRateLimiting.actorName, - ActorProps[RemotedataAttributeDefinitionActor] -> RemotedataAttributeDefinition.actorName + ActorProps[RemotedataAttributeDefinitionActor] -> RemotedataAttributeDefinition.actorName, + ActorProps[RemotedataUserInvitationActor] -> RemotedataUserInvitation.actorName ) actorsRemotedata.foreach { a => logger.info(actorSystem.actorOf(a._1, name = a._2)) } diff --git a/obp-api/src/main/scala/code/remotedata/RemotedataUserInvitation.scala b/obp-api/src/main/scala/code/remotedata/RemotedataUserInvitation.scala new file mode 100644 index 000000000..a342cada2 --- /dev/null +++ b/obp-api/src/main/scala/code/remotedata/RemotedataUserInvitation.scala @@ -0,0 +1,16 @@ +package code.remotedata + +import akka.pattern.ask +import code.actorsystem.ObpActorInit +import code.users.{RemotedataUserInvitationProviderCaseClass, UserInvitation, UserInvitationProvider} +import net.liftweb.common._ + + +object RemotedataUserInvitation extends ObpActorInit with UserInvitationProvider { + + val cc = RemotedataUserInvitationProviderCaseClass + + def createUserInvitation(firstName: String, lastName: String, email: String, company: String, country: String): Box[UserInvitation] = getValueFromFuture( + (actor ? cc.createUserInvitation(firstName, lastName, email, company, country)).mapTo[Box[UserInvitation]] + ) +} diff --git a/obp-api/src/main/scala/code/remotedata/RemotedataUserInvitationActor.scala b/obp-api/src/main/scala/code/remotedata/RemotedataUserInvitationActor.scala new file mode 100644 index 000000000..0d88ec08b --- /dev/null +++ b/obp-api/src/main/scala/code/remotedata/RemotedataUserInvitationActor.scala @@ -0,0 +1,24 @@ +package code.remotedata + +import akka.actor.Actor +import code.actorsystem.ObpActorHelper +import code.users.{MappedUserInvitationProvider, RemotedataUserInvitationProviderCaseClass} +import code.util.Helper.MdcLoggable + +class RemotedataUserInvitationActor extends Actor with ObpActorHelper with MdcLoggable { + + val mapper = MappedUserInvitationProvider + val cc = RemotedataUserInvitationProviderCaseClass + + def receive: PartialFunction[Any, Unit] = { + + case cc.createUserInvitation(firstName: String, lastName: String, email: String, company: String, country: String) => + logger.debug(s"createUserCustomerLink($firstName, $lastName, $email, $company, $country)") + sender ! (mapper.createUserInvitation(firstName, lastName, email, company, country)) + + case message => logger.warn("[AKKA ACTOR ERROR - REQUEST NOT RECOGNIZED] " + message) + + } + +} + diff --git a/obp-api/src/main/scala/code/users/UserInitationProvider.scala b/obp-api/src/main/scala/code/users/UserInitationProvider.scala new file mode 100644 index 000000000..2ab5045c3 --- /dev/null +++ b/obp-api/src/main/scala/code/users/UserInitationProvider.scala @@ -0,0 +1,39 @@ +package code.users + +import code.api.util.APIUtil +import code.remotedata.RemotedataUserInvitation +import net.liftweb.common.Box +import net.liftweb.util.SimpleInjector + + +object UserInvitationProvider extends SimpleInjector { + + val userInvitationProvider = new Inject(buildOne _) {} + + def buildOne: UserInvitationProvider = + APIUtil.getPropsAsBoolValue("use_akka", false) match { + case false => MappedUserInvitationProvider + case true => RemotedataUserInvitation // We will use Akka as a middleware + } + +} + +trait UserInvitationProvider { + def createUserInvitation(firstName: String, lastName: String, email: String, company: String, country: String): Box[UserInvitation] +} + +class RemotedataUserInvitationProviderCaseClass { + case class createUserInvitation(firstName: String, lastName: String, email: String, company: String, country: String) +} + +object RemotedataUserInvitationProviderCaseClass extends RemotedataUserInvitationProviderCaseClass + +trait UserInvitationTrait { + def userInvitationId: String + def firstName: String + def lastName: String + def email: String + def company: String + def country: String + def status: String +} \ No newline at end of file diff --git a/obp-api/src/main/scala/code/users/UserInvitation.scala b/obp-api/src/main/scala/code/users/UserInvitation.scala new file mode 100644 index 000000000..a253afd54 --- /dev/null +++ b/obp-api/src/main/scala/code/users/UserInvitation.scala @@ -0,0 +1,37 @@ +package code.users + +import code.util.UUIDString +import net.liftweb.common.Box +import net.liftweb.mapper._ +import net.liftweb.util.Helpers.tryo + +object MappedUserInvitationProvider extends UserInvitationProvider { + override def createUserInvitation(firstName: String, lastName: String, email: String, company: String, country: String): Box[UserInvitation] = tryo { + UserInvitation.create.FirstName(firstName).saveMe() + } +} +class UserInvitation extends UserInvitationTrait with LongKeyedMapper[UserInvitation] with IdPK with CreatedUpdated { + + def getSingleton = UserInvitation + + object UserInvitationId extends UUIDString(this) + object FirstName extends MappedString(this, 50) + object LastName extends MappedString(this, 50) + object Email extends MappedString(this, 50) + object Company extends MappedString(this, 50) + object Country extends MappedString(this, 50) + object Status extends MappedString(this, 50) + + override def userInvitationId: String = UserInvitationId.get + override def firstName: String = FirstName.get + override def lastName: String = LastName.get + override def email: String = Email.get + override def company: String = Company.get + override def country: String = Country.get + override def status: String = Status.get +} + +object UserInvitation extends UserInvitation with LongKeyedMetaMapper[UserInvitation] { + override def dbIndexes: List[BaseIndex[UserInvitation]] = UniqueIndex(UserInvitationId) :: super.dbIndexes +} + From aef1976cc4059751d4810e5c6826a057f6221230 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marko=20Mili=C4=87?= Date: Mon, 31 May 2021 18:12:50 +0200 Subject: [PATCH 004/147] feature/User Invitation Flow; Add endpoint createUserInvitation - WIP --- .../SwaggerDefinitionsJSON.scala | 33 ++++++++-------- .../main/scala/code/api/util/ApiRole.scala | 3 ++ .../scala/code/api/util/ErrorMessages.scala | 1 + .../main/scala/code/api/util/NewStyle.scala | 8 +++- .../scala/code/api/v4_0_0/APIMethods400.scala | 38 +++++++++++++++++++ .../code/api/v4_0_0/JSONFactory4.0.0.scala | 25 ++++++++++++ .../code/users/UserInitationProvider.scala | 1 + .../scala/code/users/UserInvitation.scala | 20 +++++++++- 8 files changed, 109 insertions(+), 20 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 8f11ecdc3..8970512fa 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 @@ -16,22 +16,7 @@ import code.api.v3_0_0.JSONFactory300.createBranchJsonV300 import code.api.v3_0_0.custom.JSONFactoryCustom300 import code.api.v3_0_0.{LobbyJsonV330, _} import code.api.v3_1_0.{AccountBalanceV310, AccountsBalancesV310Json, BadLoginStatusJson, ContactDetailsJson, CustomerWithAttributesJsonV310, InviteeJson, ObpApiLoopbackJson, PhysicalCardWithAttributesJsonV310, PutUpdateCustomerEmailJsonV310, _} -import code.api.v4_0_0.{APIInfoJson400, AccountBalanceJsonV400, AccountTagJSON, AccountTagsJSON, AccountsBalancesJsonV400, - ApiCollectionEndpointJson400, ApiCollectionEndpointsJson400, ApiCollectionJson400, ApiCollectionsJson400, AttributeDefinitionJsonV400, - AttributeDefinitionResponseJsonV400, AttributeDefinitionsResponseJsonV400, AttributeJsonV400, BalanceJsonV400, BankAccountRoutingJson, - BankJson400, BanksJson400, CallLimitPostJsonV400, ChallengeAnswerJson400, ChallengeJsonV400, ConsentInfoJsonV400, ConsentInfosJsonV400, - ConsentJsonV400, ConsentsJsonV400, CounterpartiesJson400, CounterpartyJson400, CounterpartyWithMetadataJson400, CustomerAttributeJsonV400, - CustomerAttributesResponseJson, DirectDebitJsonV400, DoubleEntryTransactionJson, DynamicEndpointHostJson400, EnergySource400, HostedAt400, - HostedBy400, IbanCheckerJsonV400, IbanDetailsJsonV400, JsonSchemaV400, JsonValidationV400, LogoutLinkJson, ModeratedAccountJSON400, - ModeratedAccountsJSON400, ModeratedCoreAccountJsonV400, ModeratedFirehoseAccountJsonV400, ModeratedFirehoseAccountsJsonV400, - PostAccountAccessJsonV400, PostAccountTagJSON, PostApiCollectionEndpointJson400, PostApiCollectionJson400, PostCounterpartyJson400, - PostCustomerPhoneNumberJsonV400, PostDirectDebitJsonV400, PostRevokeGrantAccountAccessJsonV400, PostStandingOrderJsonV400, - PostViewJsonV400, Properties, RefundJson, ResourceDocFragment, RevokedJsonV400, SettlementAccountJson, SettlementAccountRequestJson, - SettlementAccountResponseJson, SettlementAccountsJson, StandingOrderJsonV400, TransactionAttributeJsonV400, TransactionAttributeResponseJson, - TransactionAttributesResponseJson, TransactionBankAccountJson, TransactionRequestAttributeJsonV400, TransactionRequestAttributeResponseJson, - TransactionRequestAttributesResponseJson, TransactionRequestBankAccountJson, TransactionRequestBodyRefundJsonV400, TransactionRequestBodySEPAJsonV400, - TransactionRequestReasonJsonV400, TransactionRequestRefundFrom, TransactionRequestRefundTo, TransactionRequestWithChargeJSON400, UpdateAccountJsonV400, - UserLockStatusJson, When, XxxId} +import code.api.v4_0_0.{APIInfoJson400, AccountBalanceJsonV400, AccountTagJSON, AccountTagsJSON, AccountsBalancesJsonV400, ApiCollectionEndpointJson400, ApiCollectionEndpointsJson400, ApiCollectionJson400, ApiCollectionsJson400, AttributeDefinitionJsonV400, AttributeDefinitionResponseJsonV400, AttributeDefinitionsResponseJsonV400, AttributeJsonV400, BalanceJsonV400, BankAccountRoutingJson, BankJson400, BanksJson400, CallLimitPostJsonV400, ChallengeAnswerJson400, ChallengeJsonV400, ConsentInfoJsonV400, ConsentInfosJsonV400, ConsentJsonV400, ConsentsJsonV400, CounterpartiesJson400, CounterpartyJson400, CounterpartyWithMetadataJson400, CustomerAttributeJsonV400, CustomerAttributesResponseJson, DirectDebitJsonV400, DoubleEntryTransactionJson, DynamicEndpointHostJson400, EnergySource400, HostedAt400, HostedBy400, IbanCheckerJsonV400, IbanDetailsJsonV400, JsonSchemaV400, JsonValidationV400, LogoutLinkJson, ModeratedAccountJSON400, ModeratedAccountsJSON400, ModeratedCoreAccountJsonV400, ModeratedFirehoseAccountJsonV400, ModeratedFirehoseAccountsJsonV400, PostAccountAccessJsonV400, PostAccountTagJSON, PostApiCollectionEndpointJson400, PostApiCollectionJson400, PostCounterpartyJson400, PostCustomerPhoneNumberJsonV400, PostDirectDebitJsonV400, PostRevokeGrantAccountAccessJsonV400, PostStandingOrderJsonV400, PostUserInvitationJsonV400, PostViewJsonV400, Properties, RefundJson, ResourceDocFragment, RevokedJsonV400, SettlementAccountJson, SettlementAccountRequestJson, SettlementAccountResponseJson, SettlementAccountsJson, StandingOrderJsonV400, TransactionAttributeJsonV400, TransactionAttributeResponseJson, TransactionAttributesResponseJson, TransactionBankAccountJson, TransactionRequestAttributeJsonV400, TransactionRequestAttributeResponseJson, TransactionRequestAttributesResponseJson, TransactionRequestBankAccountJson, TransactionRequestBodyRefundJsonV400, TransactionRequestBodySEPAJsonV400, TransactionRequestReasonJsonV400, TransactionRequestRefundFrom, TransactionRequestRefundTo, TransactionRequestWithChargeJSON400, UpdateAccountJsonV400, UserInvitationJsonV400, UserLockStatusJson, When, XxxId} import code.api.v3_1_0.{AccountBalanceV310, AccountsBalancesV310Json, BadLoginStatusJson, ContactDetailsJson, InviteeJson, ObpApiLoopbackJson, PhysicalCardWithAttributesJsonV310, PutUpdateCustomerEmailJsonV310, _} import code.branches.Branches.{Branch, DriveUpString, LobbyString} import code.consent.ConsentStatus @@ -1707,6 +1692,22 @@ object SwaggerDefinitionsJSON { username = usernameExample.value, entitlements = entitlementJSONs ) + + val userInvitationPostJsonV200 = PostUserInvitationJsonV400( + first_name = ExampleValue.nameExample.value, + last_name = ExampleValue.nameExample.value, + email = ExampleValue.emailExample.value, + company = "Tesobe", + country = "Germany" + ) + val userInvitationJsonV200 = UserInvitationJsonV400( + first_name = ExampleValue.nameExample.value, + last_name = ExampleValue.nameExample.value, + email = ExampleValue.emailExample.value, + company = "Tesobe", + country = "Germany", + status = "Created" + ) val entitlementRequestJSON = code.api.v3_0_0.EntitlementRequestJSON( diff --git a/obp-api/src/main/scala/code/api/util/ApiRole.scala b/obp-api/src/main/scala/code/api/util/ApiRole.scala index ba6390544..413cc9cf1 100644 --- a/obp-api/src/main/scala/code/api/util/ApiRole.scala +++ b/obp-api/src/main/scala/code/api/util/ApiRole.scala @@ -707,6 +707,9 @@ object ApiRole { case class CanDeleteEndpointMapping(requiresBankId: Boolean = false) extends ApiRole lazy val canDeleteEndpointMapping = CanDeleteEndpointMapping() + + case class CanCreateUserInvitation(requiresBankId: Boolean = false) extends ApiRole + lazy val canCreateUserInvitation = CanCreateUserInvitation() private val dynamicApiRoles = new ConcurrentHashMap[String, ApiRole] 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 437c0aa3d..c4932ed8a 100644 --- a/obp-api/src/main/scala/code/api/util/ErrorMessages.scala +++ b/obp-api/src/main/scala/code/api/util/ErrorMessages.scala @@ -238,6 +238,7 @@ object ErrorMessages { val CounterpartyNotFoundByCounterpartyId = "OBP-30017: Counterparty not found. Please specify a valid value for COUNTERPARTY_ID." val BankAccountNotFound = "OBP-30018: Bank Account not found. Please specify valid values for BANK_ID and ACCOUNT_ID. " val ConsumerNotFoundByConsumerId = "OBP-30019: Consumer not found. Please specify a valid value for CONSUMER_ID." + val CannotCreateUserInvitation = "OBP-30020: Cannot create user invitation." val CreateBankError = "OBP-30020: Could not create the Bank" 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 8da67b92a..064b52ceb 100644 --- a/obp-api/src/main/scala/code/api/util/NewStyle.scala +++ b/obp-api/src/main/scala/code/api/util/NewStyle.scala @@ -34,7 +34,7 @@ import code.apicollection.{ApiCollectionTrait, MappedApiCollectionsProvider} import code.model.dataAccess.BankAccountRouting import code.standingorders.StandingOrderTrait import code.usercustomerlinks.UserCustomerLink -import code.users.Users +import code.users.{UserInvitation, UserInvitationProvider, Users} import code.util.Helper import com.openbankproject.commons.util.{ApiVersion, JsonUtils} import code.views.Views @@ -684,7 +684,11 @@ object NewStyle { i => (connectorEmptyResponse(i._1, callContext), i._2) } } - + def createUserInvitation(firstName: String, lastName: String, email: String, company: String, country: String, callContext: Option[CallContext]): OBPReturnType[UserInvitation] = Future { + val response: Box[UserInvitation] = UserInvitationProvider.userInvitationProvider.vend.createUserInvitation(firstName, lastName, email, company, country) + (unboxFullOrFail(response, callContext, s"$CannotCreateUserInvitation", 400), callContext) + } + def getAdapterInfo(callContext: Option[CallContext]): OBPReturnType[InboundAdapterInfoInternal] = { Connector.connector.vend.getAdapterInfo(callContext) map { connectorEmptyResponse(_, callContext) 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 a8ab26958..563bc7f9d 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 @@ -3145,6 +3145,44 @@ trait APIMethods400 { } + staticResourceDocs += ResourceDoc( + createUserInvitation, + implementedInApiVersion, + nameOf(createUserInvitation), + "POST", + "/banks/BANK_ID/user-invitation", + "Create User Invitation", + s"""Create User Invitation. + | + |""", + userInvitationPostJsonV200, + userInvitationJsonV200, + List( + $UserNotLoggedIn, + $BankNotFound, + UserCustomerLinksNotFoundForUser, + UnknownError + ), + List(apiTagUser, apiTagKyc ,apiTagNewStyle), + Some(canCreateUserInvitation :: Nil) + ) + + lazy val createUserInvitation : OBPEndpoint = { + case "banks" :: BankId(bankId) :: "user-invitation" :: Nil JsonPost json -> _ => { + cc => + val failMsg = s"$InvalidJsonFormat The Json body should be the $PostUserInvitationJsonV400 " + for { + postedData <- NewStyle.function.tryons(failMsg, 400, cc.callContext) { + json.extract[PostUserInvitationJsonV400] + } + (invitation, callContext) <- NewStyle.function.createUserInvitation(postedData.first_name, postedData.last_name , postedData.email , postedData.company , postedData.country , cc.callContext) + } yield { + (JSONFactory400.createUserInvitationJson(invitation), HttpCode.`201`(callContext)) + } + } + } + + staticResourceDocs += ResourceDoc( createBank, 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 8ca760b5d..760e9cebc 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 @@ -51,6 +51,7 @@ import code.ratelimiting.RateLimiting import code.standingorders.StandingOrderTrait import code.transactionrequests.TransactionRequests.TransactionChallengeTypes import code.userlocks.UserLocks +import code.users.UserInvitation import com.openbankproject.commons.model.{DirectDebitTrait, _} import net.liftweb.common.{Box, Full} import net.liftweb.json.JValue @@ -122,6 +123,19 @@ case class TransactionRequestWithChargeJSON400( case class PostResetPasswordUrlJsonV400(username: String, email: String, user_id: String) case class ResetPasswordUrlJsonV400(reset_password_url: String) +case class PostUserInvitationJsonV400(first_name: String, + last_name: String, + email: String, + company: String, + country: String) +case class UserInvitationJsonV400(first_name: String, + last_name: String, + email: String, + company: String, + country: String, + status: String) + + case class APIInfoJson400( version : String, version_status: String, @@ -1059,6 +1073,17 @@ object JSONFactory400 { def createCounterpartiesJson400(counterparties: List[CounterpartyTrait]): CounterpartiesJson400 = CounterpartiesJson400(counterparties.map(createCounterpartyJson400)) + def createUserInvitationJson(userInvitation: UserInvitation): UserInvitationJsonV400 = { + UserInvitationJsonV400( + first_name = userInvitation.firstName, + last_name = userInvitation.lastName, + email = userInvitation.email, + company = userInvitation.company, + country = userInvitation.country, + status = userInvitation.status + ) + } + def createBalancesJson(accountsBalances: AccountsBalances) = { AccountsBalancesJsonV400( accounts = accountsBalances.accounts.map( diff --git a/obp-api/src/main/scala/code/users/UserInitationProvider.scala b/obp-api/src/main/scala/code/users/UserInitationProvider.scala index 2ab5045c3..699fb098a 100644 --- a/obp-api/src/main/scala/code/users/UserInitationProvider.scala +++ b/obp-api/src/main/scala/code/users/UserInitationProvider.scala @@ -36,4 +36,5 @@ trait UserInvitationTrait { def company: String def country: String def status: String + def secretLink: Long } \ No newline at end of file diff --git a/obp-api/src/main/scala/code/users/UserInvitation.scala b/obp-api/src/main/scala/code/users/UserInvitation.scala index a253afd54..7d58cc3a8 100644 --- a/obp-api/src/main/scala/code/users/UserInvitation.scala +++ b/obp-api/src/main/scala/code/users/UserInvitation.scala @@ -1,5 +1,8 @@ package code.users +import java.util.UUID.randomUUID + +import code.api.util.SecureRandomUtil import code.util.UUIDString import net.liftweb.common.Box import net.liftweb.mapper._ @@ -7,20 +10,32 @@ import net.liftweb.util.Helpers.tryo object MappedUserInvitationProvider extends UserInvitationProvider { override def createUserInvitation(firstName: String, lastName: String, email: String, company: String, country: String): Box[UserInvitation] = tryo { - UserInvitation.create.FirstName(firstName).saveMe() + UserInvitation.create + .FirstName(firstName) + .LastName(lastName) + .Email(email) + .Company(company) + .Country(country) + .Status("CREATED") + .saveMe() } } class UserInvitation extends UserInvitationTrait with LongKeyedMapper[UserInvitation] with IdPK with CreatedUpdated { def getSingleton = UserInvitation - object UserInvitationId extends UUIDString(this) + object UserInvitationId extends UUIDString(this) { + override def defaultValue = randomUUID().toString + } object FirstName extends MappedString(this, 50) object LastName extends MappedString(this, 50) object Email extends MappedString(this, 50) object Company extends MappedString(this, 50) object Country extends MappedString(this, 50) object Status extends MappedString(this, 50) + object SecretLink extends MappedLong(this) { + override def defaultValue: Long = SecureRandomUtil.csprng.nextLong() + } override def userInvitationId: String = UserInvitationId.get override def firstName: String = FirstName.get @@ -29,6 +44,7 @@ class UserInvitation extends UserInvitationTrait with LongKeyedMapper[UserInvita override def company: String = Company.get override def country: String = Country.get override def status: String = Status.get + override def secretLink: Long = SecretLink.get } object UserInvitation extends UserInvitation with LongKeyedMetaMapper[UserInvitation] { From 292f51204b4861140595069642f424423a4c8c2b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marko=20Mili=C4=87?= Date: Tue, 1 Jun 2021 12:58:06 +0200 Subject: [PATCH 005/147] feature/User Invitation Flow; Add endpoint getUserInvitation - WIP --- .../SwaggerDefinitionsJSON.scala | 7 ++-- .../main/scala/code/api/util/ApiRole.scala | 6 ++- .../scala/code/api/util/ErrorMessages.scala | 1 + .../main/scala/code/api/util/NewStyle.scala | 4 ++ .../scala/code/api/v4_0_0/APIMethods400.scala | 39 ++++++++++++++++++- .../code/api/v4_0_0/JSONFactory4.0.0.scala | 6 ++- .../remotedata/RemotedataUserInvitation.scala | 3 ++ .../RemotedataUserInvitationActor.scala | 4 ++ .../code/users/UserInitationProvider.scala | 2 + .../scala/code/users/UserInvitation.scala | 3 ++ 10 files changed, 66 insertions(+), 9 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 8970512fa..251eafe7f 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 @@ -1693,20 +1693,21 @@ object SwaggerDefinitionsJSON { entitlements = entitlementJSONs ) - val userInvitationPostJsonV200 = PostUserInvitationJsonV400( + val userInvitationPostJsonV400 = PostUserInvitationJsonV400( first_name = ExampleValue.nameExample.value, last_name = ExampleValue.nameExample.value, email = ExampleValue.emailExample.value, company = "Tesobe", country = "Germany" ) - val userInvitationJsonV200 = UserInvitationJsonV400( + val userInvitationJsonV400 = UserInvitationJsonV400( first_name = ExampleValue.nameExample.value, last_name = ExampleValue.nameExample.value, email = ExampleValue.emailExample.value, company = "Tesobe", country = "Germany", - status = "Created" + status = "Created", + secret_link = 5819479115482092878L ) val entitlementRequestJSON = diff --git a/obp-api/src/main/scala/code/api/util/ApiRole.scala b/obp-api/src/main/scala/code/api/util/ApiRole.scala index 413cc9cf1..196abc1a5 100644 --- a/obp-api/src/main/scala/code/api/util/ApiRole.scala +++ b/obp-api/src/main/scala/code/api/util/ApiRole.scala @@ -708,8 +708,10 @@ object ApiRole { case class CanDeleteEndpointMapping(requiresBankId: Boolean = false) extends ApiRole lazy val canDeleteEndpointMapping = CanDeleteEndpointMapping() - case class CanCreateUserInvitation(requiresBankId: Boolean = false) extends ApiRole - lazy val canCreateUserInvitation = CanCreateUserInvitation() + case class CanCreateUserInvitation(requiresBankId: Boolean = true) extends ApiRole + lazy val canCreateUserInvitation = CanCreateUserInvitation() + case class CanGetUserInvitation(requiresBankId: Boolean = true) extends ApiRole + lazy val canGetUserInvitation = CanGetUserInvitation() private val dynamicApiRoles = new ConcurrentHashMap[String, ApiRole] 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 c4932ed8a..e65ff17ab 100644 --- a/obp-api/src/main/scala/code/api/util/ErrorMessages.scala +++ b/obp-api/src/main/scala/code/api/util/ErrorMessages.scala @@ -239,6 +239,7 @@ object ErrorMessages { val BankAccountNotFound = "OBP-30018: Bank Account not found. Please specify valid values for BANK_ID and ACCOUNT_ID. " val ConsumerNotFoundByConsumerId = "OBP-30019: Consumer not found. Please specify a valid value for CONSUMER_ID." val CannotCreateUserInvitation = "OBP-30020: Cannot create user invitation." + val CannotGetUserInvitation = "OBP-30021: Cannot get user invitation." val CreateBankError = "OBP-30020: Could not create the Bank" 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 064b52ceb..6bbc639ca 100644 --- a/obp-api/src/main/scala/code/api/util/NewStyle.scala +++ b/obp-api/src/main/scala/code/api/util/NewStyle.scala @@ -688,6 +688,10 @@ object NewStyle { val response: Box[UserInvitation] = UserInvitationProvider.userInvitationProvider.vend.createUserInvitation(firstName, lastName, email, company, country) (unboxFullOrFail(response, callContext, s"$CannotCreateUserInvitation", 400), callContext) } + def getUserInvitation(secretLink: Long, callContext: Option[CallContext]): OBPReturnType[UserInvitation] = Future { + val response: Box[UserInvitation] = UserInvitationProvider.userInvitationProvider.vend.getUserInvitation(secretLink) + (unboxFullOrFail(response, callContext, s"$CannotGetUserInvitation", 400), callContext) + } def getAdapterInfo(callContext: Option[CallContext]): OBPReturnType[InboundAdapterInfoInternal] = { Connector.connector.vend.getAdapterInfo(callContext) 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 563bc7f9d..1b44aec50 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 @@ -3155,8 +3155,8 @@ trait APIMethods400 { s"""Create User Invitation. | |""", - userInvitationPostJsonV200, - userInvitationJsonV200, + userInvitationPostJsonV400, + userInvitationJsonV400, List( $UserNotLoggedIn, $BankNotFound, @@ -3182,6 +3182,41 @@ trait APIMethods400 { } } + staticResourceDocs += ResourceDoc( + getUserInvitation, + implementedInApiVersion, + nameOf(getUserInvitation), + "GET", + "/banks/BANK_ID/user-invitation/SECRET_LINK", + "Get User Invitation", + s""" Get User Invitation + | + |${authenticationRequiredMessage(true)} + | + |""", + emptyObjectJson, + userInvitationJsonV400, + List( + $UserNotLoggedIn, + $BankNotFound, + InvalidJsonFormat, + UnknownError + ), + List(apiTagUser, apiTagNewStyle), + Some(List(canGetUserInvitation)) + ) + + lazy val getUserInvitation : OBPEndpoint = { + case "banks" :: BankId(bankId) :: "user-invitation" :: secretLink :: Nil JsonGet _ => { + cc => + for { + (invitation, callContext) <- NewStyle.function.getUserInvitation(secretLink.toLong, cc.callContext) + } yield { + (JSONFactory400.createUserInvitationJson(invitation), HttpCode.`200`(callContext)) + } + } + } + staticResourceDocs += ResourceDoc( 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 760e9cebc..e3d11ca68 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 @@ -133,7 +133,8 @@ case class UserInvitationJsonV400(first_name: String, email: String, company: String, country: String, - status: String) + status: String, + secret_link: Long) case class APIInfoJson400( @@ -1080,7 +1081,8 @@ object JSONFactory400 { email = userInvitation.email, company = userInvitation.company, country = userInvitation.country, - status = userInvitation.status + status = userInvitation.status, + secret_link = userInvitation.secretLink ) } diff --git a/obp-api/src/main/scala/code/remotedata/RemotedataUserInvitation.scala b/obp-api/src/main/scala/code/remotedata/RemotedataUserInvitation.scala index a342cada2..03c26d2a1 100644 --- a/obp-api/src/main/scala/code/remotedata/RemotedataUserInvitation.scala +++ b/obp-api/src/main/scala/code/remotedata/RemotedataUserInvitation.scala @@ -13,4 +13,7 @@ object RemotedataUserInvitation extends ObpActorInit with UserInvitationProvider def createUserInvitation(firstName: String, lastName: String, email: String, company: String, country: String): Box[UserInvitation] = getValueFromFuture( (actor ? cc.createUserInvitation(firstName, lastName, email, company, country)).mapTo[Box[UserInvitation]] ) + def getUserInvitation(secretLink: Long): Box[UserInvitation] = getValueFromFuture( + (actor ? cc.getUserInvitation(secretLink)).mapTo[Box[UserInvitation]] + ) } diff --git a/obp-api/src/main/scala/code/remotedata/RemotedataUserInvitationActor.scala b/obp-api/src/main/scala/code/remotedata/RemotedataUserInvitationActor.scala index 0d88ec08b..966c5a6df 100644 --- a/obp-api/src/main/scala/code/remotedata/RemotedataUserInvitationActor.scala +++ b/obp-api/src/main/scala/code/remotedata/RemotedataUserInvitationActor.scala @@ -15,6 +15,10 @@ class RemotedataUserInvitationActor extends Actor with ObpActorHelper with MdcLo case cc.createUserInvitation(firstName: String, lastName: String, email: String, company: String, country: String) => logger.debug(s"createUserCustomerLink($firstName, $lastName, $email, $company, $country)") sender ! (mapper.createUserInvitation(firstName, lastName, email, company, country)) + + case cc.getUserInvitation(secretLink: Long) => + logger.debug(s"createUserCustomerLink($secretLink)") + sender ! (mapper.getUserInvitation(secretLink)) case message => logger.warn("[AKKA ACTOR ERROR - REQUEST NOT RECOGNIZED] " + message) diff --git a/obp-api/src/main/scala/code/users/UserInitationProvider.scala b/obp-api/src/main/scala/code/users/UserInitationProvider.scala index 699fb098a..664002fca 100644 --- a/obp-api/src/main/scala/code/users/UserInitationProvider.scala +++ b/obp-api/src/main/scala/code/users/UserInitationProvider.scala @@ -20,10 +20,12 @@ object UserInvitationProvider extends SimpleInjector { trait UserInvitationProvider { def createUserInvitation(firstName: String, lastName: String, email: String, company: String, country: String): Box[UserInvitation] + def getUserInvitation(secretLink: Long): Box[UserInvitation] } class RemotedataUserInvitationProviderCaseClass { case class createUserInvitation(firstName: String, lastName: String, email: String, company: String, country: String) + case class getUserInvitation(secretLink: Long) } object RemotedataUserInvitationProviderCaseClass extends RemotedataUserInvitationProviderCaseClass diff --git a/obp-api/src/main/scala/code/users/UserInvitation.scala b/obp-api/src/main/scala/code/users/UserInvitation.scala index 7d58cc3a8..f91aedda7 100644 --- a/obp-api/src/main/scala/code/users/UserInvitation.scala +++ b/obp-api/src/main/scala/code/users/UserInvitation.scala @@ -19,6 +19,9 @@ object MappedUserInvitationProvider extends UserInvitationProvider { .Status("CREATED") .saveMe() } + override def getUserInvitation(secretLink: Long): Box[UserInvitation] = { + UserInvitation.find(By(UserInvitation.SecretLink, secretLink)) + } } class UserInvitation extends UserInvitationTrait with LongKeyedMapper[UserInvitation] with IdPK with CreatedUpdated { From 123e0d90d790c2171c09ae71b7d945bf87cb5873 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marko=20Mili=C4=87?= Date: Tue, 1 Jun 2021 17:18:13 +0200 Subject: [PATCH 006/147] feature/User Invitation Flow - WIP 2 --- .../SwaggerDefinitionsJSON.scala | 10 ++++++---- .../src/main/scala/code/api/util/ApiTag.scala | 1 + .../src/main/scala/code/api/util/NewStyle.scala | 4 ++-- .../scala/code/api/v4_0_0/APIMethods400.scala | 12 +++++++++--- .../code/api/v4_0_0/JSONFactory4.0.0.scala | 17 ++++++++++------- .../remotedata/RemotedataUserInvitation.scala | 4 ++-- .../RemotedataUserInvitationActor.scala | 6 +++--- .../code/users/UserInitationProvider.scala | 5 +++-- .../main/scala/code/users/UserInvitation.scala | 11 +++++++---- 9 files changed, 43 insertions(+), 27 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 b83d80110..6d0f9695e 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 @@ -1696,16 +1696,18 @@ object SwaggerDefinitionsJSON { last_name = ExampleValue.nameExample.value, email = ExampleValue.emailExample.value, company = "Tesobe", - country = "Germany" + country = "Germany", + purpose = "Developer" ) val userInvitationJsonV400 = UserInvitationJsonV400( first_name = ExampleValue.nameExample.value, last_name = ExampleValue.nameExample.value, email = ExampleValue.emailExample.value, - company = "Tesobe", + company = "TESOBE", country = "Germany", - status = "Created", - secret_link = 5819479115482092878L + purpose = "Developer", + status = "CREATED", + secret_key = 5819479115482092878L ) val entitlementRequestJSON = diff --git a/obp-api/src/main/scala/code/api/util/ApiTag.scala b/obp-api/src/main/scala/code/api/util/ApiTag.scala index acbb21aa5..5896433f7 100644 --- a/obp-api/src/main/scala/code/api/util/ApiTag.scala +++ b/obp-api/src/main/scala/code/api/util/ApiTag.scala @@ -42,6 +42,7 @@ object ApiTag { val apiTagCustomer = ResourceDocTag("Customer") val apiTagOnboarding = ResourceDocTag("Onboarding") val apiTagUser = ResourceDocTag("User") // Use for User Management / Info APIs + val apiTagUserInvitation = ResourceDocTag("User-Invitation") val apiTagMeeting = ResourceDocTag("Customer-Meeting") val apiTagExperimental = ResourceDocTag("Experimental") val apiTagPerson = ResourceDocTag("Person") 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 ec0827393..efbd4847d 100644 --- a/obp-api/src/main/scala/code/api/util/NewStyle.scala +++ b/obp-api/src/main/scala/code/api/util/NewStyle.scala @@ -725,8 +725,8 @@ object NewStyle { i => (connectorEmptyResponse(i._1, callContext), i._2) } } - def createUserInvitation(firstName: String, lastName: String, email: String, company: String, country: String, callContext: Option[CallContext]): OBPReturnType[UserInvitation] = Future { - val response: Box[UserInvitation] = UserInvitationProvider.userInvitationProvider.vend.createUserInvitation(firstName, lastName, email, company, country) + def createUserInvitation(firstName: String, lastName: String, email: String, company: String, country: String, purpose: String, callContext: Option[CallContext]): OBPReturnType[UserInvitation] = Future { + val response: Box[UserInvitation] = UserInvitationProvider.userInvitationProvider.vend.createUserInvitation(firstName, lastName, email, company, country, purpose) (unboxFullOrFail(response, callContext, s"$CannotCreateUserInvitation", 400), callContext) } def getUserInvitation(secretLink: Long, callContext: Option[CallContext]): OBPReturnType[UserInvitation] = Future { 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 7cff8a6ed..5824dfc2b 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 @@ -3163,7 +3163,7 @@ trait APIMethods400 { UserCustomerLinksNotFoundForUser, UnknownError ), - List(apiTagUser, apiTagKyc ,apiTagNewStyle), + List(apiTagUserInvitation, apiTagKyc ,apiTagNewStyle), Some(canCreateUserInvitation :: Nil) ) @@ -3175,7 +3175,13 @@ trait APIMethods400 { postedData <- NewStyle.function.tryons(failMsg, 400, cc.callContext) { json.extract[PostUserInvitationJsonV400] } - (invitation, callContext) <- NewStyle.function.createUserInvitation(postedData.first_name, postedData.last_name , postedData.email , postedData.company , postedData.country , cc.callContext) + (invitation, callContext) <- NewStyle.function.createUserInvitation(postedData.first_name, + postedData.last_name, + postedData.email, + postedData.company, + postedData.country, + postedData.purpose, + cc.callContext) } yield { (JSONFactory400.createUserInvitationJson(invitation), HttpCode.`201`(callContext)) } @@ -3202,7 +3208,7 @@ trait APIMethods400 { InvalidJsonFormat, UnknownError ), - List(apiTagUser, apiTagNewStyle), + List(apiTagUserInvitation, apiTagNewStyle), Some(List(canGetUserInvitation)) ) 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 5d7035a69..239fa2674 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 @@ -127,14 +127,16 @@ case class PostUserInvitationJsonV400(first_name: String, last_name: String, email: String, company: String, - country: String) -case class UserInvitationJsonV400(first_name: String, - last_name: String, - email: String, - company: String, + country: String, + purpose: String) +case class UserInvitationJsonV400(first_name: String, + last_name: String, + email: String, + company: String, country: String, + purpose: String, status: String, - secret_link: Long) + secret_key: Long) case class APIInfoJson400( @@ -1136,8 +1138,9 @@ object JSONFactory400 { email = userInvitation.email, company = userInvitation.company, country = userInvitation.country, + purpose = userInvitation.purpose, status = userInvitation.status, - secret_link = userInvitation.secretLink + secret_key = userInvitation.secretLink ) } diff --git a/obp-api/src/main/scala/code/remotedata/RemotedataUserInvitation.scala b/obp-api/src/main/scala/code/remotedata/RemotedataUserInvitation.scala index 03c26d2a1..6e0ee8a66 100644 --- a/obp-api/src/main/scala/code/remotedata/RemotedataUserInvitation.scala +++ b/obp-api/src/main/scala/code/remotedata/RemotedataUserInvitation.scala @@ -10,8 +10,8 @@ object RemotedataUserInvitation extends ObpActorInit with UserInvitationProvider val cc = RemotedataUserInvitationProviderCaseClass - def createUserInvitation(firstName: String, lastName: String, email: String, company: String, country: String): Box[UserInvitation] = getValueFromFuture( - (actor ? cc.createUserInvitation(firstName, lastName, email, company, country)).mapTo[Box[UserInvitation]] + def createUserInvitation(firstName: String, lastName: String, email: String, company: String, country: String, purpose: String): Box[UserInvitation] = getValueFromFuture( + (actor ? cc.createUserInvitation(firstName, lastName, email, company, country, purpose)).mapTo[Box[UserInvitation]] ) def getUserInvitation(secretLink: Long): Box[UserInvitation] = getValueFromFuture( (actor ? cc.getUserInvitation(secretLink)).mapTo[Box[UserInvitation]] diff --git a/obp-api/src/main/scala/code/remotedata/RemotedataUserInvitationActor.scala b/obp-api/src/main/scala/code/remotedata/RemotedataUserInvitationActor.scala index 966c5a6df..b7a3fb4c8 100644 --- a/obp-api/src/main/scala/code/remotedata/RemotedataUserInvitationActor.scala +++ b/obp-api/src/main/scala/code/remotedata/RemotedataUserInvitationActor.scala @@ -12,9 +12,9 @@ class RemotedataUserInvitationActor extends Actor with ObpActorHelper with MdcLo def receive: PartialFunction[Any, Unit] = { - case cc.createUserInvitation(firstName: String, lastName: String, email: String, company: String, country: String) => - logger.debug(s"createUserCustomerLink($firstName, $lastName, $email, $company, $country)") - sender ! (mapper.createUserInvitation(firstName, lastName, email, company, country)) + case cc.createUserInvitation(firstName: String, lastName: String, email: String, company: String, country: String, purpose: String) => + logger.debug(s"createUserCustomerLink($firstName, $lastName, $email, $company, $country, $purpose)") + sender ! (mapper.createUserInvitation(firstName, lastName, email, company, country, purpose)) case cc.getUserInvitation(secretLink: Long) => logger.debug(s"createUserCustomerLink($secretLink)") diff --git a/obp-api/src/main/scala/code/users/UserInitationProvider.scala b/obp-api/src/main/scala/code/users/UserInitationProvider.scala index 664002fca..f5fef420d 100644 --- a/obp-api/src/main/scala/code/users/UserInitationProvider.scala +++ b/obp-api/src/main/scala/code/users/UserInitationProvider.scala @@ -19,12 +19,12 @@ object UserInvitationProvider extends SimpleInjector { } trait UserInvitationProvider { - def createUserInvitation(firstName: String, lastName: String, email: String, company: String, country: String): Box[UserInvitation] + def createUserInvitation(firstName: String, lastName: String, email: String, company: String, country: String, purpose: String): Box[UserInvitation] def getUserInvitation(secretLink: Long): Box[UserInvitation] } class RemotedataUserInvitationProviderCaseClass { - case class createUserInvitation(firstName: String, lastName: String, email: String, company: String, country: String) + case class createUserInvitation(firstName: String, lastName: String, email: String, company: String, country: String, purpose: String) case class getUserInvitation(secretLink: Long) } @@ -38,5 +38,6 @@ trait UserInvitationTrait { def company: String def country: String def status: String + def purpose: String def secretLink: Long } \ No newline at end of file diff --git a/obp-api/src/main/scala/code/users/UserInvitation.scala b/obp-api/src/main/scala/code/users/UserInvitation.scala index f91aedda7..03b14c821 100644 --- a/obp-api/src/main/scala/code/users/UserInvitation.scala +++ b/obp-api/src/main/scala/code/users/UserInvitation.scala @@ -9,7 +9,7 @@ import net.liftweb.mapper._ import net.liftweb.util.Helpers.tryo object MappedUserInvitationProvider extends UserInvitationProvider { - override def createUserInvitation(firstName: String, lastName: String, email: String, company: String, country: String): Box[UserInvitation] = tryo { + override def createUserInvitation(firstName: String, lastName: String, email: String, company: String, country: String, purpose: String): Box[UserInvitation] = tryo { UserInvitation.create .FirstName(firstName) .LastName(lastName) @@ -17,10 +17,11 @@ object MappedUserInvitationProvider extends UserInvitationProvider { .Company(company) .Country(country) .Status("CREATED") + .Purpose(purpose) .saveMe() } override def getUserInvitation(secretLink: Long): Box[UserInvitation] = { - UserInvitation.find(By(UserInvitation.SecretLink, secretLink)) + UserInvitation.find(By(UserInvitation.SecretKey, secretLink)) } } class UserInvitation extends UserInvitationTrait with LongKeyedMapper[UserInvitation] with IdPK with CreatedUpdated { @@ -36,7 +37,8 @@ class UserInvitation extends UserInvitationTrait with LongKeyedMapper[UserInvita object Company extends MappedString(this, 50) object Country extends MappedString(this, 50) object Status extends MappedString(this, 50) - object SecretLink extends MappedLong(this) { + object Purpose extends MappedString(this, 50) + object SecretKey extends MappedLong(this) { override def defaultValue: Long = SecureRandomUtil.csprng.nextLong() } @@ -47,7 +49,8 @@ class UserInvitation extends UserInvitationTrait with LongKeyedMapper[UserInvita override def company: String = Company.get override def country: String = Country.get override def status: String = Status.get - override def secretLink: Long = SecretLink.get + override def purpose: String = Purpose.get + override def secretLink: Long = SecretKey.get } object UserInvitation extends UserInvitation with LongKeyedMetaMapper[UserInvitation] { From 497a092ac0160304c7c19520719f03f658f6336f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marko=20Mili=C4=87?= Date: Wed, 2 Jun 2021 12:48:53 +0200 Subject: [PATCH 007/147] feature/User Invitation Flow; Add endpoint getUserInvitations - WIP --- .../main/scala/code/api/util/NewStyle.scala | 12 +++-- .../scala/code/api/v4_0_0/APIMethods400.scala | 45 +++++++++++++++++-- .../code/api/v4_0_0/JSONFactory4.0.0.scala | 6 ++- .../remotedata/RemotedataUserInvitation.scala | 12 +++-- .../RemotedataUserInvitationActor.scala | 17 ++++--- .../code/users/UserInitationProvider.scala | 12 +++-- .../scala/code/users/UserInvitation.scala | 16 +++++-- 7 files changed, 94 insertions(+), 26 deletions(-) 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 efbd4847d..479d31ea4 100644 --- a/obp-api/src/main/scala/code/api/util/NewStyle.scala +++ b/obp-api/src/main/scala/code/api/util/NewStyle.scala @@ -725,12 +725,16 @@ object NewStyle { i => (connectorEmptyResponse(i._1, callContext), i._2) } } - def createUserInvitation(firstName: String, lastName: String, email: String, company: String, country: String, purpose: String, callContext: Option[CallContext]): OBPReturnType[UserInvitation] = Future { - val response: Box[UserInvitation] = UserInvitationProvider.userInvitationProvider.vend.createUserInvitation(firstName, lastName, email, company, country, purpose) + def createUserInvitation(bankId: BankId, firstName: String, lastName: String, email: String, company: String, country: String, purpose: String, callContext: Option[CallContext]): OBPReturnType[UserInvitation] = Future { + val response: Box[UserInvitation] = UserInvitationProvider.userInvitationProvider.vend.createUserInvitation(bankId, firstName, lastName, email, company, country, purpose) (unboxFullOrFail(response, callContext, s"$CannotCreateUserInvitation", 400), callContext) } - def getUserInvitation(secretLink: Long, callContext: Option[CallContext]): OBPReturnType[UserInvitation] = Future { - val response: Box[UserInvitation] = UserInvitationProvider.userInvitationProvider.vend.getUserInvitation(secretLink) + def getUserInvitation(bankId: BankId, secretLink: Long, callContext: Option[CallContext]): OBPReturnType[UserInvitation] = Future { + val response: Box[UserInvitation] = UserInvitationProvider.userInvitationProvider.vend.getUserInvitation(bankId, secretLink) + (unboxFullOrFail(response, callContext, s"$CannotGetUserInvitation", 400), callContext) + } + def getUserInvitations(bankId: BankId, callContext: Option[CallContext]): OBPReturnType[List[UserInvitation]] = Future { + val response = UserInvitationProvider.userInvitationProvider.vend.getUserInvitations(bankId) (unboxFullOrFail(response, callContext, s"$CannotGetUserInvitation", 400), callContext) } 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 5824dfc2b..91cc4fcf7 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 @@ -3175,7 +3175,9 @@ trait APIMethods400 { postedData <- NewStyle.function.tryons(failMsg, 400, cc.callContext) { json.extract[PostUserInvitationJsonV400] } - (invitation, callContext) <- NewStyle.function.createUserInvitation(postedData.first_name, + (invitation, callContext) <- NewStyle.function.createUserInvitation( + bankId, + postedData.first_name, postedData.last_name, postedData.email, postedData.company, @@ -3193,7 +3195,7 @@ trait APIMethods400 { implementedInApiVersion, nameOf(getUserInvitation), "GET", - "/banks/BANK_ID/user-invitation/SECRET_LINK", + "/banks/BANK_ID/user-invitations/SECRET_LINK", "Get User Invitation", s""" Get User Invitation | @@ -3213,15 +3215,50 @@ trait APIMethods400 { ) lazy val getUserInvitation : OBPEndpoint = { - case "banks" :: BankId(bankId) :: "user-invitation" :: secretLink :: Nil JsonGet _ => { + case "banks" :: BankId(bankId) :: "user-invitations" :: secretLink :: Nil JsonGet _ => { cc => for { - (invitation, callContext) <- NewStyle.function.getUserInvitation(secretLink.toLong, cc.callContext) + (invitation, callContext) <- NewStyle.function.getUserInvitation(bankId, secretLink.toLong, cc.callContext) } yield { (JSONFactory400.createUserInvitationJson(invitation), HttpCode.`200`(callContext)) } } } + + staticResourceDocs += ResourceDoc( + getUserInvitations, + implementedInApiVersion, + nameOf(getUserInvitations), + "GET", + "/banks/BANK_ID/user-invitations", + "Get User Invitations", + s""" Get User Invitations + | + |${authenticationRequiredMessage(true)} + | + |""", + emptyObjectJson, + userInvitationJsonV400, + List( + $UserNotLoggedIn, + $BankNotFound, + InvalidJsonFormat, + UnknownError + ), + List(apiTagUserInvitation, apiTagNewStyle), + Some(List(canGetUserInvitation)) + ) + + lazy val getUserInvitations : OBPEndpoint = { + case "banks" :: BankId(bankId) :: "user-invitations" :: Nil JsonGet _ => { + cc => + for { + (invitations, callContext) <- NewStyle.function.getUserInvitations(bankId, cc.callContext) + } yield { + (JSONFactory400.createUserInvitationJson(invitations), HttpCode.`200`(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 239fa2674..2e53a4839 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 @@ -137,7 +137,7 @@ case class UserInvitationJsonV400(first_name: String, purpose: String, status: String, secret_key: Long) - +case class UserInvitationsJsonV400(user_invitations: List[UserInvitationJsonV400]) case class APIInfoJson400( version : String, @@ -1143,6 +1143,10 @@ object JSONFactory400 { secret_key = userInvitation.secretLink ) } + + def createUserInvitationJson(userInvitations: List[UserInvitation]): UserInvitationsJsonV400 = { + UserInvitationsJsonV400(userInvitations.map(createUserInvitationJson)) + } def createBalancesJson(accountsBalances: AccountsBalances) = { AccountsBalancesJsonV400( diff --git a/obp-api/src/main/scala/code/remotedata/RemotedataUserInvitation.scala b/obp-api/src/main/scala/code/remotedata/RemotedataUserInvitation.scala index 6e0ee8a66..1b0663210 100644 --- a/obp-api/src/main/scala/code/remotedata/RemotedataUserInvitation.scala +++ b/obp-api/src/main/scala/code/remotedata/RemotedataUserInvitation.scala @@ -3,6 +3,7 @@ package code.remotedata import akka.pattern.ask import code.actorsystem.ObpActorInit import code.users.{RemotedataUserInvitationProviderCaseClass, UserInvitation, UserInvitationProvider} +import com.openbankproject.commons.model.BankId import net.liftweb.common._ @@ -10,10 +11,13 @@ object RemotedataUserInvitation extends ObpActorInit with UserInvitationProvider val cc = RemotedataUserInvitationProviderCaseClass - def createUserInvitation(firstName: String, lastName: String, email: String, company: String, country: String, purpose: String): Box[UserInvitation] = getValueFromFuture( - (actor ? cc.createUserInvitation(firstName, lastName, email, company, country, purpose)).mapTo[Box[UserInvitation]] + def createUserInvitation(bankId: BankId, firstName: String, lastName: String, email: String, company: String, country: String, purpose: String): Box[UserInvitation] = getValueFromFuture( + (actor ? cc.createUserInvitation(bankId, firstName, lastName, email, company, country, purpose)).mapTo[Box[UserInvitation]] ) - def getUserInvitation(secretLink: Long): Box[UserInvitation] = getValueFromFuture( - (actor ? cc.getUserInvitation(secretLink)).mapTo[Box[UserInvitation]] + def getUserInvitation(bankId: BankId, secretLink: Long): Box[UserInvitation] = getValueFromFuture( + (actor ? cc.getUserInvitation(bankId, secretLink)).mapTo[Box[UserInvitation]] + ) + def getUserInvitations(bankId: BankId): Box[List[UserInvitation]] = getValueFromFuture( + (actor ? cc.getUserInvitations(bankId)).mapTo[Box[List[UserInvitation]]] ) } diff --git a/obp-api/src/main/scala/code/remotedata/RemotedataUserInvitationActor.scala b/obp-api/src/main/scala/code/remotedata/RemotedataUserInvitationActor.scala index b7a3fb4c8..8d7652d9c 100644 --- a/obp-api/src/main/scala/code/remotedata/RemotedataUserInvitationActor.scala +++ b/obp-api/src/main/scala/code/remotedata/RemotedataUserInvitationActor.scala @@ -4,6 +4,7 @@ import akka.actor.Actor import code.actorsystem.ObpActorHelper import code.users.{MappedUserInvitationProvider, RemotedataUserInvitationProviderCaseClass} import code.util.Helper.MdcLoggable +import com.openbankproject.commons.model.BankId class RemotedataUserInvitationActor extends Actor with ObpActorHelper with MdcLoggable { @@ -12,13 +13,17 @@ class RemotedataUserInvitationActor extends Actor with ObpActorHelper with MdcLo def receive: PartialFunction[Any, Unit] = { - case cc.createUserInvitation(firstName: String, lastName: String, email: String, company: String, country: String, purpose: String) => - logger.debug(s"createUserCustomerLink($firstName, $lastName, $email, $company, $country, $purpose)") - sender ! (mapper.createUserInvitation(firstName, lastName, email, company, country, purpose)) + case cc.createUserInvitation(bankId: BankId, firstName: String, lastName: String, email: String, company: String, country: String, purpose: String) => + logger.debug(s"createUserInvitation($bankId, $firstName, $lastName, $email, $company, $country, $purpose)") + sender ! (mapper.createUserInvitation(bankId, firstName, lastName, email, company, country, purpose)) - case cc.getUserInvitation(secretLink: Long) => - logger.debug(s"createUserCustomerLink($secretLink)") - sender ! (mapper.getUserInvitation(secretLink)) + case cc.getUserInvitation(bankId: BankId, secretLink: Long) => + logger.debug(s"getUserInvitation($bankId, $secretLink)") + sender ! (mapper.getUserInvitation(bankId, secretLink)) + + case cc.getUserInvitations(bankId: BankId) => + logger.debug(s"getUserInvitations($bankId)") + sender ! (mapper.getUserInvitations(bankId)) case message => logger.warn("[AKKA ACTOR ERROR - REQUEST NOT RECOGNIZED] " + message) diff --git a/obp-api/src/main/scala/code/users/UserInitationProvider.scala b/obp-api/src/main/scala/code/users/UserInitationProvider.scala index f5fef420d..953bb1506 100644 --- a/obp-api/src/main/scala/code/users/UserInitationProvider.scala +++ b/obp-api/src/main/scala/code/users/UserInitationProvider.scala @@ -2,6 +2,7 @@ package code.users import code.api.util.APIUtil import code.remotedata.RemotedataUserInvitation +import com.openbankproject.commons.model.BankId import net.liftweb.common.Box import net.liftweb.util.SimpleInjector @@ -19,19 +20,22 @@ object UserInvitationProvider extends SimpleInjector { } trait UserInvitationProvider { - def createUserInvitation(firstName: String, lastName: String, email: String, company: String, country: String, purpose: String): Box[UserInvitation] - def getUserInvitation(secretLink: Long): Box[UserInvitation] + def createUserInvitation(bankId: BankId, firstName: String, lastName: String, email: String, company: String, country: String, purpose: String): Box[UserInvitation] + def getUserInvitation(bankId: BankId, secretLink: Long): Box[UserInvitation] + def getUserInvitations(bankId: BankId): Box[List[UserInvitation]] } class RemotedataUserInvitationProviderCaseClass { - case class createUserInvitation(firstName: String, lastName: String, email: String, company: String, country: String, purpose: String) - case class getUserInvitation(secretLink: Long) + case class createUserInvitation(bankId: BankId, firstName: String, lastName: String, email: String, company: String, country: String, purpose: String) + case class getUserInvitation(bankId: BankId, secretLink: Long) + case class getUserInvitations(bankId: BankId) } object RemotedataUserInvitationProviderCaseClass extends RemotedataUserInvitationProviderCaseClass trait UserInvitationTrait { def userInvitationId: String + def bankId: String def firstName: String def lastName: String def email: String diff --git a/obp-api/src/main/scala/code/users/UserInvitation.scala b/obp-api/src/main/scala/code/users/UserInvitation.scala index 03b14c821..c5bc769d3 100644 --- a/obp-api/src/main/scala/code/users/UserInvitation.scala +++ b/obp-api/src/main/scala/code/users/UserInvitation.scala @@ -4,13 +4,15 @@ import java.util.UUID.randomUUID import code.api.util.SecureRandomUtil import code.util.UUIDString +import com.openbankproject.commons.model.BankId import net.liftweb.common.Box import net.liftweb.mapper._ import net.liftweb.util.Helpers.tryo object MappedUserInvitationProvider extends UserInvitationProvider { - override def createUserInvitation(firstName: String, lastName: String, email: String, company: String, country: String, purpose: String): Box[UserInvitation] = tryo { + override def createUserInvitation(bankId: BankId, firstName: String, lastName: String, email: String, company: String, country: String, purpose: String): Box[UserInvitation] = tryo { UserInvitation.create + .BankId(bankId.value) .FirstName(firstName) .LastName(lastName) .Email(email) @@ -20,8 +22,14 @@ object MappedUserInvitationProvider extends UserInvitationProvider { .Purpose(purpose) .saveMe() } - override def getUserInvitation(secretLink: Long): Box[UserInvitation] = { - UserInvitation.find(By(UserInvitation.SecretKey, secretLink)) + override def getUserInvitation(bankId: BankId, secretLink: Long): Box[UserInvitation] = { + UserInvitation.find( + By(UserInvitation.BankId, bankId.value), + By(UserInvitation.SecretKey, secretLink) + ) + } + override def getUserInvitations(bankId: BankId): Box[List[UserInvitation]] = tryo { + UserInvitation.findAll(By(UserInvitation.BankId, bankId.value)) } } class UserInvitation extends UserInvitationTrait with LongKeyedMapper[UserInvitation] with IdPK with CreatedUpdated { @@ -31,6 +39,7 @@ class UserInvitation extends UserInvitationTrait with LongKeyedMapper[UserInvita object UserInvitationId extends UUIDString(this) { override def defaultValue = randomUUID().toString } + object BankId extends MappedString(this, 255) object FirstName extends MappedString(this, 50) object LastName extends MappedString(this, 50) object Email extends MappedString(this, 50) @@ -43,6 +52,7 @@ class UserInvitation extends UserInvitationTrait with LongKeyedMapper[UserInvita } override def userInvitationId: String = UserInvitationId.get + override def bankId: String = BankId.get override def firstName: String = FirstName.get override def lastName: String = LastName.get override def email: String = Email.get From 5a59faae2b76127c52adee30fd497456e84bf329 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marko=20Mili=C4=87?= Date: Wed, 2 Jun 2021 13:06:14 +0200 Subject: [PATCH 008/147] docfix/Fix duplicated error messages' numbers --- obp-api/src/main/scala/code/api/util/ErrorMessages.scala | 8 +++++--- 1 file changed, 5 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 51f110e70..8b33cb7ba 100644 --- a/obp-api/src/main/scala/code/api/util/ErrorMessages.scala +++ b/obp-api/src/main/scala/code/api/util/ErrorMessages.scala @@ -239,8 +239,6 @@ object ErrorMessages { val CounterpartyNotFoundByCounterpartyId = "OBP-30017: Counterparty not found. Please specify a valid value for COUNTERPARTY_ID." val BankAccountNotFound = "OBP-30018: Bank Account not found. Please specify valid values for BANK_ID and ACCOUNT_ID. " val ConsumerNotFoundByConsumerId = "OBP-30019: Consumer not found. Please specify a valid value for CONSUMER_ID." - val CannotCreateUserInvitation = "OBP-30020: Cannot create user invitation." - val CannotGetUserInvitation = "OBP-30021: Cannot get user invitation." val CreateBankError = "OBP-30020: Could not create the Bank" @@ -457,6 +455,10 @@ object ErrorMessages { val InvalidEndpointMapping = "OBP-36006: Invalid Endpoint Mapping. " // General Resource related messages above here + // User Invitation + val CannotCreateUserInvitation = "OBP-37081: Cannot create user invitation." + val CannotGetUserInvitation = "OBP-37882: Cannot get user invitation." + // Transaction Request related messages (OBP-40XXX) val InvalidTransactionRequestType = "OBP-40001: Invalid value for TRANSACTION_REQUEST_TYPE" @@ -698,7 +700,7 @@ object ErrorMessages { def getDuplicatedMessageNumbers = { import scala.meta._ - val source: Source = new java.io.File("src/main/scala/code/api/util/ErrorMessages.scala").parse[Source].get + val source: Source = new java.io.File("obp-api/src/main/scala/code/api/util/ErrorMessages.scala").parse[Source].get val listOfMessaegeNumbers = source.collect { case obj: Defn.Object if obj.name.value == "ErrorMessages" => From 779293404794912e087f809a80763ac4337ab791 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marko=20Mili=C4=87?= Date: Wed, 2 Jun 2021 15:18:09 +0200 Subject: [PATCH 009/147] docfix/Fix duplicated error messages' numbers --- obp-api/src/main/scala/code/api/util/ErrorMessages.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 8b33cb7ba..12c556cd6 100644 --- a/obp-api/src/main/scala/code/api/util/ErrorMessages.scala +++ b/obp-api/src/main/scala/code/api/util/ErrorMessages.scala @@ -700,7 +700,7 @@ object ErrorMessages { def getDuplicatedMessageNumbers = { import scala.meta._ - val source: Source = new java.io.File("obp-api/src/main/scala/code/api/util/ErrorMessages.scala").parse[Source].get + val source: Source = new java.io.File("src/main/scala/code/api/util/ErrorMessages.scala").parse[Source].get val listOfMessaegeNumbers = source.collect { case obj: Defn.Object if obj.name.value == "ErrorMessages" => From 4040a462f2609ee678f3a8b3152d7fc0072ecfa2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marko=20Mili=C4=87?= Date: Wed, 2 Jun 2021 17:16:35 +0200 Subject: [PATCH 010/147] feature/User Invitation Flow; Add endpoint getUserInvitationAnonymous - WIP --- .../scala/code/api/util/ErrorMessages.scala | 1 + .../scala/code/api/v4_0_0/APIMethods400.scala | 51 ++++++++++++++++++- .../code/api/v4_0_0/JSONFactory4.0.0.scala | 1 + 3 files changed, 52 insertions(+), 1 deletion(-) 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 12c556cd6..5d653f8c4 100644 --- a/obp-api/src/main/scala/code/api/util/ErrorMessages.scala +++ b/obp-api/src/main/scala/code/api/util/ErrorMessages.scala @@ -458,6 +458,7 @@ object ErrorMessages { // User Invitation val CannotCreateUserInvitation = "OBP-37081: Cannot create user invitation." val CannotGetUserInvitation = "OBP-37882: Cannot get user invitation." + val CannotFindUserInvitation = "OBP-37882: Cannot find user invitation." // Transaction Request related messages (OBP-40XXX) 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 91cc4fcf7..d009726fe 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 @@ -70,10 +70,12 @@ import net.liftweb.util.Helpers.now import net.liftweb.util.{Helpers, StringHelpers} import org.apache.commons.collections4.CollectionUtils import org.apache.commons.lang3.StringUtils -import java.util.Date +import java.util.{Calendar, Date} + import code.dynamicMessageDoc.JsonDynamicMessageDoc import code.dynamicResourceDoc.JsonDynamicResourceDoc import java.net.URLEncoder + import code.api.v4_0_0.dynamic.practise.DynamicEndpointCodeGenerator import code.endpointMapping.EndpointMappingCommons import net.liftweb.json @@ -3189,6 +3191,53 @@ trait APIMethods400 { } } } + + + staticResourceDocs += ResourceDoc( + getUserInvitationAnonymous, + implementedInApiVersion, + nameOf(getUserInvitationAnonymous), + "POST", + "/banks/BANK_ID/user-invitations", + "Get User Invitation Information", + s"""Create User Invitation Information. + | + |${authenticationRequiredMessage(false)} + |""", + PostUserInvitationAnonymousJsonV400(secret_key = 5819479115482092878L), + userInvitationJsonV400, + List( + UserNotLoggedIn, + $BankNotFound, + UserCustomerLinksNotFoundForUser, + UnknownError + ), + List(apiTagUserInvitation, apiTagKyc ,apiTagNewStyle) + ) + + lazy val getUserInvitationAnonymous : OBPEndpoint = { + case "banks" :: BankId(bankId) :: "user-invitations" :: Nil JsonPost json -> _ => { + cc => + val failMsg = s"$InvalidJsonFormat The Json body should be the $PostUserInvitationAnonymousJsonV400 " + for { + postedData <- NewStyle.function.tryons(failMsg, 400, cc.callContext) { + json.extract[PostUserInvitationAnonymousJsonV400] + } + (invitation, callContext) <- NewStyle.function.getUserInvitation(bankId, postedData.secret_key, cc.callContext) + _ <- Helper.booleanToFuture(CannotFindUserInvitation, 404, cc.callContext) { + invitation.status == "CREATED" + } + _ <- Helper.booleanToFuture(CannotFindUserInvitation, 404, cc.callContext) { + val validUntil = Calendar.getInstance + validUntil.setTime(invitation.createdAt.get) + validUntil.add(Calendar.HOUR, 24) + validUntil.getTime.after(new Date()) + } + } yield { + (JSONFactory400.createUserInvitationJson(invitation), HttpCode.`201`(callContext)) + } + } + } staticResourceDocs += ResourceDoc( getUserInvitation, 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 2e53a4839..1b26d5a2d 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 @@ -123,6 +123,7 @@ case class TransactionRequestWithChargeJSON400( case class PostResetPasswordUrlJsonV400(username: String, email: String, user_id: String) case class ResetPasswordUrlJsonV400(reset_password_url: String) +case class PostUserInvitationAnonymousJsonV400(secret_key: Long) case class PostUserInvitationJsonV400(first_name: String, last_name: String, email: String, From 0c488828cabccd8c397e0d07ac21bd151457ee0a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marko=20Mili=C4=87?= Date: Thu, 3 Jun 2021 08:33:12 +0200 Subject: [PATCH 011/147] docfix/Fix duplicated error messages' numbers 2 --- obp-api/src/main/scala/code/api/util/ErrorMessages.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 5d653f8c4..b03b7d7c4 100644 --- a/obp-api/src/main/scala/code/api/util/ErrorMessages.scala +++ b/obp-api/src/main/scala/code/api/util/ErrorMessages.scala @@ -458,7 +458,7 @@ object ErrorMessages { // User Invitation val CannotCreateUserInvitation = "OBP-37081: Cannot create user invitation." val CannotGetUserInvitation = "OBP-37882: Cannot get user invitation." - val CannotFindUserInvitation = "OBP-37882: Cannot find user invitation." + val CannotFindUserInvitation = "OBP-37883: Cannot find user invitation." // Transaction Request related messages (OBP-40XXX) From 351cdc3bdf17074846ce823ba126d9c3e97a5a7c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marko=20Mili=C4=87?= Date: Thu, 3 Jun 2021 10:01:46 +0200 Subject: [PATCH 012/147] refactor/Tweak the name secretLink => secretKey --- obp-api/src/main/scala/code/api/v4_0_0/JSONFactory4.0.0.scala | 2 +- obp-api/src/main/scala/code/users/UserInitationProvider.scala | 2 +- obp-api/src/main/scala/code/users/UserInvitation.scala | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) 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 a1916c831..f0fcda799 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 @@ -1179,7 +1179,7 @@ object JSONFactory400 { country = userInvitation.country, purpose = userInvitation.purpose, status = userInvitation.status, - secret_key = userInvitation.secretLink + secret_key = userInvitation.secretKey ) } diff --git a/obp-api/src/main/scala/code/users/UserInitationProvider.scala b/obp-api/src/main/scala/code/users/UserInitationProvider.scala index 953bb1506..1a18e1d45 100644 --- a/obp-api/src/main/scala/code/users/UserInitationProvider.scala +++ b/obp-api/src/main/scala/code/users/UserInitationProvider.scala @@ -43,5 +43,5 @@ trait UserInvitationTrait { def country: String def status: String def purpose: String - def secretLink: Long + def secretKey: Long } \ No newline at end of file diff --git a/obp-api/src/main/scala/code/users/UserInvitation.scala b/obp-api/src/main/scala/code/users/UserInvitation.scala index c5bc769d3..6ae181cec 100644 --- a/obp-api/src/main/scala/code/users/UserInvitation.scala +++ b/obp-api/src/main/scala/code/users/UserInvitation.scala @@ -60,7 +60,7 @@ class UserInvitation extends UserInvitationTrait with LongKeyedMapper[UserInvita override def country: String = Country.get override def status: String = Status.get override def purpose: String = Purpose.get - override def secretLink: Long = SecretKey.get + override def secretKey: Long = SecretKey.get } object UserInvitation extends UserInvitation with LongKeyedMetaMapper[UserInvitation] { From dc85d03953f9389902169af0ba4deaa077dd0277 Mon Sep 17 00:00:00 2001 From: hongwei Date: Thu, 3 Jun 2021 11:52:28 +0200 Subject: [PATCH 013/147] docfix/tweaked the error handling for roles and scopes --- obp-api/src/main/scala/code/api/util/NewStyle.scala | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) 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 a91da667a..6e92aed4e 100644 --- a/obp-api/src/main/scala/code/api/util/NewStyle.scala +++ b/obp-api/src/main/scala/code/api/util/NewStyle.scala @@ -878,11 +878,18 @@ object NewStyle { APIUtil.hasAtLeastOneEntitlement(bankId, userId, roles) } map validateRequestPayload(callContext) - def hasAtLeastOneEntitlement(bankId: String, userId: String, roles: List[ApiRole], callContext: Option[CallContext]): Future[Box[Unit]] = - hasAtLeastOneEntitlement(UserHasMissingRoles + roles.mkString(" or "))(bankId, userId, roles, callContext) + def hasAtLeastOneEntitlement(bankId: String, userId: String, roles: List[ApiRole], callContext: Option[CallContext]): Future[Box[Unit]] = { + val errorMessage = if (bankId.isEmpty) UserHasMissingRoles + roles.mkString(" or ") else UserHasMissingRoles + roles.mkString(" or ") + s" for BankId($bankId)." + hasAtLeastOneEntitlement(errorMessage)(bankId, userId, roles, callContext) + } def hasAllEntitlements(bankId: String, userId: String, roles: List[ApiRole], callContext: Option[CallContext]): Box[Unit] = { - val boxResult = Helper.booleanToBox(APIUtil.hasAllEntitlements(bankId, userId, roles), s"$UserHasMissingRoles${roles.mkString(" and ")} entitlements are required.") + val errorMessage = if (bankId.isEmpty) + s"$UserHasMissingRoles${roles.mkString(" and ")} entitlements are required." + else + s"$UserHasMissingRoles${roles.mkString(" and ")} entitlements are required for BankId($bankId)." + + val boxResult = Helper.booleanToBox(APIUtil.hasAllEntitlements(bankId, userId, roles), errorMessage) validateRequestPayload(callContext)(boxResult) } From b5d1932d3fecbda793a752efc89246c1cc5f5090 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marko=20Mili=C4=87?= Date: Thu, 3 Jun 2021 12:42:56 +0200 Subject: [PATCH 014/147] feature/Add email sending during user invitation flow --- obp-api/src/main/scala/code/api/v4_0_0/APIMethods400.scala | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) 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 7676ef172..02df1a71b 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 @@ -68,7 +68,7 @@ import net.liftweb.json.Serialization.write import net.liftweb.json.{compactRender, prettyRender, _} import net.liftweb.mapper.By import net.liftweb.util.Helpers.now -import net.liftweb.util.{Helpers, StringHelpers} +import net.liftweb.util.{Helpers, Mailer, StringHelpers} import org.apache.commons.collections4.CollectionUtils import org.apache.commons.lang3.StringUtils import java.util.{Calendar, Date} @@ -80,6 +80,7 @@ import java.net.URLEncoder import code.api.v4_0_0.dynamic.practise.DynamicEndpointCodeGenerator import code.endpointMapping.EndpointMappingCommons import net.liftweb.json +import net.liftweb.util.Mailer.{From, PlainMailBodyType, Subject, To} import scala.collection.immutable.{List, Nil} import scala.collection.mutable.ArrayBuffer @@ -3188,6 +3189,9 @@ trait APIMethods400 { postedData.purpose, cc.callContext) } yield { + val invitationText = s"Your registration link: ${APIUtil.getPropsValue("hostname", "")}/registration/${invitation.secretKey}" + val params = PlainMailBodyType(invitationText) :: List(To(invitation.email)) + Mailer.sendMail(From("invitation@tesobe.com"), Subject("User invitation"), params :_*) (JSONFactory400.createUserInvitationJson(invitation), HttpCode.`201`(callContext)) } } From 4b85387520841917b5a49a674245f6eea8a7eb98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marko=20Mili=C4=87?= Date: Thu, 3 Jun 2021 14:15:01 +0200 Subject: [PATCH 015/147] feature/User Invitation Flow; Remove secretKey from json response --- .../code/api/ResourceDocs1_4_0/SwaggerDefinitionsJSON.scala | 3 +-- .../src/main/scala/code/api/v4_0_0/JSONFactory4.0.0.scala | 6 ++---- 2 files changed, 3 insertions(+), 6 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 961f92e7c..aa00c34b3 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 @@ -1707,8 +1707,7 @@ object SwaggerDefinitionsJSON { company = "TESOBE", country = "Germany", purpose = "Developer", - status = "CREATED", - secret_key = 5819479115482092878L + status = "CREATED" ) val entitlementRequestJSON = 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 f0fcda799..583c5b4e5 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 @@ -137,8 +137,7 @@ case class UserInvitationJsonV400(first_name: String, company: String, country: String, purpose: String, - status: String, - secret_key: Long) + status: String) case class UserInvitationsJsonV400(user_invitations: List[UserInvitationJsonV400]) case class APIInfoJson400( @@ -1178,8 +1177,7 @@ object JSONFactory400 { company = userInvitation.company, country = userInvitation.country, purpose = userInvitation.purpose, - status = userInvitation.status, - secret_key = userInvitation.secretKey + status = userInvitation.status ) } From a72332b80a1f7482e9bf9a74b3b726b1225a2738 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marko=20Mili=C4=87?= Date: Thu, 3 Jun 2021 16:40:13 +0200 Subject: [PATCH 016/147] docfix/Enhance Debug Log --- obp-api/src/main/scala/code/api/OAuth2.scala | 2 ++ 1 file changed, 2 insertions(+) diff --git a/obp-api/src/main/scala/code/api/OAuth2.scala b/obp-api/src/main/scala/code/api/OAuth2.scala index f86b7c3d2..0bc8b39b2 100644 --- a/obp-api/src/main/scala/code/api/OAuth2.scala +++ b/obp-api/src/main/scala/code/api/OAuth2.scala @@ -154,6 +154,8 @@ object OAuth2Login extends RestHelper with MdcLoggable { hydraAdmin.deleteOAuth2Client(clientId) hydraAdmin.createOAuth2Client(oAuth2Client) } else if(stringNotEq(certInConsumer, cert)) { + logger.debug("Cert in Consumer: " + certInConsumer) + logger.debug("Cert in Request: " + cert) return (Failure(Oauth2TokenMatchCertificateFail), Some(cc.copy(consumer = Failure(Oauth2TokenMatchCertificateFail)))) } } From a57063c2386b9e681e27b6ca373f1d59f2cc5ab2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marko=20Mili=C4=87?= Date: Thu, 3 Jun 2021 17:45:10 +0200 Subject: [PATCH 017/147] docfix/Enhance Debug Log 2 --- obp-api/src/main/scala/code/api/util/APIUtil.scala | 9 ++++++--- 1 file changed, 6 insertions(+), 3 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 e757b1e28..145702cc8 100644 --- a/obp-api/src/main/scala/code/api/util/APIUtil.scala +++ b/obp-api/src/main/scala/code/api/util/APIUtil.scala @@ -3432,14 +3432,17 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{ case true => `getPSD2-CERT`(cc.map(_.requestHeaders).getOrElse(Nil)) match { case Some(pem) => + logger.debug("PEM: " + pem) val validatedPem = X509.validate(pem) + logger.debug("Validated PEM: " + validatedPem) validatedPem match { case Full(true) => - val roles = X509.extractPsd2Roles(pem).map(_.exists(_ == serviceProvider)) - roles match { + val hasServiceProvider = X509.extractPsd2Roles(pem).map(_.exists(_ == serviceProvider)) + logger.debug("hasServiceProvider: " + hasServiceProvider) + hasServiceProvider match { case Full(true) => Full(true) case Full(false) => Failure(X509ActionIsNotAllowed) - case _ => roles + case _ => hasServiceProvider } case _ => validatedPem From 8b958667c86f727452c691ff756594a821d2051c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marko=20Mili=C4=87?= Date: Thu, 3 Jun 2021 18:49:23 +0200 Subject: [PATCH 018/147] feature/PEM URL Decode Magic --- obp-api/src/main/scala/code/api/util/APIUtil.scala | 6 +++--- 1 file changed, 3 insertions(+), 3 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 145702cc8..d1d60cc94 100644 --- a/obp-api/src/main/scala/code/api/util/APIUtil.scala +++ b/obp-api/src/main/scala/code/api/util/APIUtil.scala @@ -3432,9 +3432,9 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{ case true => `getPSD2-CERT`(cc.map(_.requestHeaders).getOrElse(Nil)) match { case Some(pem) => - logger.debug("PEM: " + pem) - val validatedPem = X509.validate(pem) - logger.debug("Validated PEM: " + validatedPem) + logger.debug("PSD2-CERT pem: " + pem) + val validatedPem = X509.validate(URLDecoder.decode(pem,"UTF-8")) + logger.debug("validatedPem: " + validatedPem) validatedPem match { case Full(true) => val hasServiceProvider = X509.extractPsd2Roles(pem).map(_.exists(_ == serviceProvider)) From 5e1cf7fc1f2e609c33e861079213a60f783436ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marko=20Mili=C4=87?= Date: Thu, 3 Jun 2021 19:37:27 +0200 Subject: [PATCH 019/147] feature/PEM URL Decode Magic 2 --- obp-api/src/main/scala/code/api/util/APIUtil.scala | 5 +++-- 1 file changed, 3 insertions(+), 2 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 d1d60cc94..84232d62a 100644 --- a/obp-api/src/main/scala/code/api/util/APIUtil.scala +++ b/obp-api/src/main/scala/code/api/util/APIUtil.scala @@ -3433,11 +3433,12 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{ `getPSD2-CERT`(cc.map(_.requestHeaders).getOrElse(Nil)) match { case Some(pem) => logger.debug("PSD2-CERT pem: " + pem) - val validatedPem = X509.validate(URLDecoder.decode(pem,"UTF-8")) + val decodedPem = URLDecoder.decode(pem,"UTF-8") + val validatedPem = X509.validate(decodedPem) logger.debug("validatedPem: " + validatedPem) validatedPem match { case Full(true) => - val hasServiceProvider = X509.extractPsd2Roles(pem).map(_.exists(_ == serviceProvider)) + val hasServiceProvider = X509.extractPsd2Roles(decodedPem).map(_.exists(_ == serviceProvider)) logger.debug("hasServiceProvider: " + hasServiceProvider) hasServiceProvider match { case Full(true) => Full(true) From 916322c3fd17d9ffbb44a6046dd380039da5ce02 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 4 Jun 2021 01:49:33 +0000 Subject: [PATCH 020/147] Bump httpclient from 4.5.2 to 4.5.13 in /obp-api Bumps httpclient from 4.5.2 to 4.5.13. --- updated-dependencies: - dependency-name: org.apache.httpcomponents:httpclient dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- obp-api/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/obp-api/pom.xml b/obp-api/pom.xml index 2246aea1e..286c53d86 100644 --- a/obp-api/pom.xml +++ b/obp-api/pom.xml @@ -178,7 +178,7 @@ org.apache.httpcomponents httpclient - 4.5.2 + 4.5.13 org.eclipse.jetty From 436620bcbe3f169cc026919d9274329a31defb2c Mon Sep 17 00:00:00 2001 From: hongwei Date: Fri, 4 Jun 2021 09:33:55 +0200 Subject: [PATCH 021/147] bugfix/added a guard for showIndicatorCookiePage --- obp-api/src/main/webapp/media/js/cookies-consent.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/obp-api/src/main/webapp/media/js/cookies-consent.js b/obp-api/src/main/webapp/media/js/cookies-consent.js index a7e8976f0..31d709800 100644 --- a/obp-api/src/main/webapp/media/js/cookies-consent.js +++ b/obp-api/src/main/webapp/media/js/cookies-consent.js @@ -31,7 +31,7 @@ function showIndicatorCookiePage(id) { var cookieValue = getCookieValue('we-use-cookies-indicator'); //if the value of 'we-use-cookies-indicator' is not the same as we set before (set it '1' before). - if (cookieValue!= undefined && cookieValue != '1') + if (cookieValue!= undefined && cookieValue != '1' && document.getElementById(id) != null) { //show the cookie page document.getElementById(id).style.display="block"; From e36e27401cd7179b95f853cda8d4a83131f7fb75 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marko=20Mili=C4=87?= Date: Fri, 4 Jun 2021 13:03:58 +0200 Subject: [PATCH 022/147] docfix/Add comments regarding QWAC certificate --- .../src/main/scala/code/api/util/CertificateUtil.scala | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/obp-api/src/main/scala/code/api/util/CertificateUtil.scala b/obp-api/src/main/scala/code/api/util/CertificateUtil.scala index 3af62e525..d6cd3bdca 100644 --- a/obp-api/src/main/scala/code/api/util/CertificateUtil.scala +++ b/obp-api/src/main/scala/code/api/util/CertificateUtil.scala @@ -69,6 +69,7 @@ object CertificateUtil extends MdcLoggable { val jkspath = APIUtil.getPropsValue("keystore.path").getOrElse("") val jkspasswd = APIUtil.getPropsValue("keystore.password").getOrElse(APIUtil.initPasswd) val keypasswd = APIUtil.getPropsValue("keystore.passphrase").getOrElse(APIUtil.initPasswd) + // This is used for QWAC certificate. Alias needs to be of that certificate. val alias = APIUtil.getPropsValue("keystore.alias").getOrElse("") val keyStore = KeyStore.getInstance(KeyStore.getDefaultType) val inputStream = new FileInputStream(jkspath) @@ -103,7 +104,14 @@ object CertificateUtil extends MdcLoggable { .keyIDFromThumbprint() .build() jwk.toJSONString() - } + } + + /** + * This is used for QWAC certificate. + * x5s is the part of te JOSE Protected header we use it in case of Java Web Signature. + * We sign response with rsaSigner and send it via "x-jws-signature" response header. + * it's verified via x5c value at third party app. + */ lazy val (rsaSigner, x5c, rsaPublicKey) = { val (privateKey: PrivateKey, certificate: Certificate) = Props.mode match { From 7fd576ca89c6b7dd79521480c9c7a209ecb105c4 Mon Sep 17 00:00:00 2001 From: hongwei Date: Mon, 7 Jun 2021 11:50:52 +0200 Subject: [PATCH 023/147] feature/OBPV400 added the bank level dynamic entity endpoints --- .../main/scala/code/api/util/ApiRole.scala | 9 + .../scala/code/api/v4_0_0/APIMethods400.scala | 217 ++++++++++++++---- 2 files changed, 180 insertions(+), 46 deletions(-) diff --git a/obp-api/src/main/scala/code/api/util/ApiRole.scala b/obp-api/src/main/scala/code/api/util/ApiRole.scala index 5a2fdc1d8..06ae4c8dc 100644 --- a/obp-api/src/main/scala/code/api/util/ApiRole.scala +++ b/obp-api/src/main/scala/code/api/util/ApiRole.scala @@ -456,12 +456,21 @@ object ApiRole { case class CanCreateDynamicEntity(requiresBankId: Boolean = false) extends ApiRole lazy val canCreateDynamicEntity = CanCreateDynamicEntity() + case class CanCreateBankLevelDynamicEntity(requiresBankId: Boolean = true) extends ApiRole + lazy val canCreateDynamicEntityAtBank = CanCreateBankLevelDynamicEntity() + case class CanUpdateDynamicEntity(requiresBankId: Boolean = false) extends ApiRole lazy val canUpdateDynamicEntity = CanUpdateDynamicEntity() + + case class CanUpdateBankLevelDynamicEntity(requiresBankId: Boolean = true) extends ApiRole + lazy val canUpdateBankLevelDynamicEntity = CanUpdateBankLevelDynamicEntity() case class CanDeleteDynamicEntity(requiresBankId: Boolean = false) extends ApiRole lazy val canDeleteDynamicEntity = CanDeleteDynamicEntity() + case class CanDeleteBankLevelDynamicEntity(requiresBankId: Boolean = true) extends ApiRole + lazy val canDeleteBankLevelDynamicEntity = CanDeleteBankLevelDynamicEntity() + case class CanGetBankLevelDynamicEntities(requiresBankId: Boolean = true) extends ApiRole lazy val canGetBankLevelDynamicEntities = CanGetBankLevelDynamicEntities() 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 02df1a71b..d43c03eff 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 @@ -98,7 +98,8 @@ trait APIMethods400 { private val staticResourceDocs = ArrayBuffer[ResourceDoc]() // createDynamicEntityDoc and updateDynamicEntityDoc are dynamic, So here dynamic create resourceDocs - def resourceDocs = staticResourceDocs ++ ArrayBuffer[ResourceDoc](createDynamicEntityDoc, updateDynamicEntityDoc, updateMyDynamicEntityDoc) + def resourceDocs = staticResourceDocs ++ ArrayBuffer[ResourceDoc](createDynamicEntityDoc, + createBankLevelDynamicEntityDoc, updateDynamicEntityDoc, updateBankLevelDynamicEntityDoc, updateMyDynamicEntityDoc) val apiRelations = ArrayBuffer[ApiRelation]() val codeContext = CodeContext(staticResourceDocs, apiRelations) @@ -1709,7 +1710,7 @@ trait APIMethods400 { "GET", "/management/banks/BANK_ID/dynamic-entities", "Get Bank Level Dynamic Entities", - s"""Get all the bank level Dynamic Entities.""", + s"""Get all the bank level Dynamic Entities for one bank.""", EmptyBody, ListResult( "dynamic_entities", @@ -1737,6 +1738,23 @@ trait APIMethods400 { } } + private def createDynamicEntityMethod(cc: CallContext, dynamicEntity: DynamicEntityCommons) = { + for { + Full(result) <- NewStyle.function.createOrUpdateDynamicEntity(dynamicEntity, cc.callContext) + //granted the CRUD roles to the loggedIn User + curdRoles = List( + DynamicEntityInfo.canCreateRole(result.entityName, dynamicEntity.bankId), + DynamicEntityInfo.canUpdateRole(result.entityName, dynamicEntity.bankId), + DynamicEntityInfo.canGetRole(result.entityName, dynamicEntity.bankId), + DynamicEntityInfo.canDeleteRole(result.entityName, dynamicEntity.bankId) + ) + } yield { + curdRoles.map(role => Entitlement.entitlement.vend.addEntitlement(dynamicEntity.bankId.getOrElse(""), cc.userId, role.toString())) + val commonsData: DynamicEntityCommons = result + (commonsData.jValue, HttpCode.`201`(cc.callContext)) + } + } + private def createDynamicEntityDoc = ResourceDoc( createDynamicEntity, implementedInApiVersion, @@ -1780,20 +1798,72 @@ trait APIMethods400 { case "management" :: "dynamic-entities" :: Nil JsonPost json -> _ => { cc => val dynamicEntity = DynamicEntityCommons(json.asInstanceOf[JObject], None, cc.userId) - for { - Full(result) <- NewStyle.function.createOrUpdateDynamicEntity(dynamicEntity, cc.callContext) - //granted the CRUD roles to the loggedIn User - curdRoles = List( - DynamicEntityInfo.canCreateRole(result.entityName, dynamicEntity.bankId), - DynamicEntityInfo.canUpdateRole(result.entityName, dynamicEntity.bankId), - DynamicEntityInfo.canGetRole(result.entityName, dynamicEntity.bankId), - DynamicEntityInfo.canDeleteRole(result.entityName, dynamicEntity.bankId) - ) - } yield { - curdRoles.map(role => Entitlement.entitlement.vend.addEntitlement(dynamicEntity.bankId.getOrElse(""), cc.userId, role.toString())) - val commonsData: DynamicEntityCommons = result - (commonsData.jValue, HttpCode.`201`(cc.callContext)) - } + createDynamicEntityMethod(cc, dynamicEntity) + } + } + + private def createBankLevelDynamicEntityDoc = ResourceDoc( + createBankLevelDynamicEntity, + implementedInApiVersion, + nameOf(createBankLevelDynamicEntity), + "POST", + "/management/banks/BANK_ID/dynamic-entities", + "Create Bank Level Dynamic Entity", + s"""Create a Bank Level DynamicEntity. + | + |${authenticationRequiredMessage(true)} + | + |Create one DynamicEntity, after created success, the corresponding CRUD endpoints will be generated automatically + | + |Current support field types as follow: + |${DynamicEntityFieldType.values.map(_.toString).mkString("[", ", ", ", reference]")} + | + |${DynamicEntityFieldType.DATE_WITH_DAY} format: ${DynamicEntityFieldType.DATE_WITH_DAY.dateFormat} + | + |Value of reference type is corresponding ids, please look at the following examples. + |Current supporting reference types and corresponding examples as follow: + |``` + |${ReferenceType.referenceTypeAndExample.mkString("\n")} + |``` + | Note: BankId filed is optional, + | if you add it, the entity will be the Bank level. + | if you omit it, the entity will be the System level. + |""", + dynamicEntityRequestBodyExample.copy(bankId = None), + dynamicEntityResponseBodyExample, + List( + $UserNotLoggedIn, + UserHasMissingRoles, + InvalidJsonFormat, + UnknownError + ), + List(apiTagManageDynamicEntity, apiTagApi, apiTagNewStyle), + Some(List(canCreateDynamicEntityAtBank))) + lazy val createBankLevelDynamicEntity: OBPEndpoint = { + case "management" ::"banks" :: BankId(bankId) :: "dynamic-entities" :: Nil JsonPost json -> _ => { + cc => + val dynamicEntity = DynamicEntityCommons(json.asInstanceOf[JObject], None, cc.userId).copy(bankId = Some(bankId.value)) + createDynamicEntityMethod(cc, dynamicEntity) + } + } + + //bankId is option, if it is bankLevelEntity, we need BankId, if system Level Entity, bankId is None. + private def updateDynamicEntityMethod(bankId: Option[String], dynamicEntityId: String, json: JValue, cc: CallContext) = { + for { + // Check whether there are uploaded data, only if no uploaded data allow to update DynamicEntity. + (entity, _) <- NewStyle.function.getDynamicEntityById(dynamicEntityId, cc.callContext) + (box, _) <- NewStyle.function.invokeDynamicConnector(GET_ALL, entity.entityName, None, None, entity.bankId, None, cc.callContext) + resultList: JArray = unboxResult(box.asInstanceOf[Box[JArray]], entity.entityName) + _ <- Helper.booleanToFuture(DynamicEntityOperationNotAllowed, cc = cc.callContext) { + resultList.arr.isEmpty + } + + jsonObject = json.asInstanceOf[JObject] + dynamicEntity = DynamicEntityCommons(jsonObject, Some(dynamicEntityId), cc.userId).copy(bankId = bankId) + Full(result) <- NewStyle.function.createOrUpdateDynamicEntity(dynamicEntity, cc.callContext) + } yield { + val commonsData: DynamicEntityCommons = result + (commonsData.jValue, HttpCode.`200`(cc.callContext)) } } @@ -1833,26 +1903,52 @@ trait APIMethods400 { ), List(apiTagManageDynamicEntity, apiTagApi, apiTagNewStyle), Some(List(canUpdateDynamicEntity))) - lazy val updateDynamicEntity: OBPEndpoint = { case "management" :: "dynamic-entities" :: dynamicEntityId :: Nil JsonPut json -> _ => { cc => - for { - // Check whether there are uploaded data, only if no uploaded data allow to update DynamicEntity. - (entity, _) <- NewStyle.function.getDynamicEntityById(dynamicEntityId, cc.callContext) - (box, _) <- NewStyle.function.invokeDynamicConnector(GET_ALL, entity.entityName, None, None, entity.bankId, None, cc.callContext) - resultList: JArray = unboxResult(box.asInstanceOf[Box[JArray]], entity.entityName) - _ <- Helper.booleanToFuture(DynamicEntityOperationNotAllowed, cc=cc.callContext) { - resultList.arr.isEmpty - } - - jsonObject = json.asInstanceOf[JObject] - dynamicEntity = DynamicEntityCommons(jsonObject, Some(dynamicEntityId), cc.userId) - Full(result) <- NewStyle.function.createOrUpdateDynamicEntity(dynamicEntity, cc.callContext) - } yield { - val commonsData: DynamicEntityCommons = result - (commonsData.jValue, HttpCode.`200`(cc.callContext)) - } + updateDynamicEntityMethod(None, dynamicEntityId, json, cc) + } + } + + private def updateBankLevelDynamicEntityDoc = ResourceDoc( + updateBankLevelDynamicEntityAtBank, + implementedInApiVersion, + nameOf(updateBankLevelDynamicEntityAtBank), + "PUT", + "/management/banks/BANK_ID/dynamic-entities/DYNAMIC_ENTITY_ID", + "Update Bank Level Dynamic Entity", + s"""Update a Bank Level DynamicEntity. + | + | + |${authenticationRequiredMessage(true)} + | + |Update one DynamicEntity, after update finished, the corresponding CRUD endpoints will be changed. + | + |Current support field types as follow: + |${DynamicEntityFieldType.values.map(_.toString).mkString("[", ", ", ", reference]")} + | + |${DynamicEntityFieldType.DATE_WITH_DAY} format: ${DynamicEntityFieldType.DATE_WITH_DAY.dateFormat} + | + |Value of reference type is corresponding ids, please look at the following examples. + |Current supporting reference types and corresponding examples as follow: + |``` + |${ReferenceType.referenceTypeAndExample.mkString("\n")} + |``` + |""", + dynamicEntityRequestBodyExample.copy(bankId=None), + dynamicEntityResponseBodyExample, + List( + $UserNotLoggedIn, + UserHasMissingRoles, + InvalidJsonFormat, + UnknownError + ), + List(apiTagManageDynamicEntity, apiTagApi, apiTagNewStyle), + Some(List(canUpdateBankLevelDynamicEntity))) + lazy val updateBankLevelDynamicEntityAtBank: OBPEndpoint = { + case "management" :: "banks" :: bankId :: "dynamic-entities" :: dynamicEntityId :: Nil JsonPut json -> _ => { + cc => + updateDynamicEntityMethod(Some(bankId),dynamicEntityId, json, cc) } } @@ -1875,22 +1971,51 @@ trait APIMethods400 { ), List(apiTagManageDynamicEntity, apiTagApi, apiTagNewStyle), Some(List(canDeleteDynamicEntity))) - lazy val deleteDynamicEntity: OBPEndpoint = { case "management" :: "dynamic-entities" :: dynamicEntityId :: Nil JsonDelete _ => { cc => - for { - // Check whether there are uploaded data, only if no uploaded data allow to delete DynamicEntity. - (entity, _) <- NewStyle.function.getDynamicEntityById(dynamicEntityId, cc.callContext) - (box, _) <- NewStyle.function.invokeDynamicConnector(GET_ALL, entity.entityName, None, None, entity.bankId, None, cc.callContext) - resultList: JArray = unboxResult(box.asInstanceOf[Box[JArray]], entity.entityName) - _ <- Helper.booleanToFuture(DynamicEntityOperationNotAllowed, cc=cc.callContext) { - resultList.arr.isEmpty - } - deleted: Box[Boolean] <- NewStyle.function.deleteDynamicEntity(dynamicEntityId) - } yield { - (deleted, HttpCode.`204`(cc.callContext)) - } + deleteDynamicEntityMethod(dynamicEntityId, cc) + } + } + + private def deleteDynamicEntityMethod(dynamicEntityId: String, cc: CallContext) = { + for { + // Check whether there are uploaded data, only if no uploaded data allow to delete DynamicEntity. + (entity, _) <- NewStyle.function.getDynamicEntityById(dynamicEntityId, cc.callContext) + (box, _) <- NewStyle.function.invokeDynamicConnector(GET_ALL, entity.entityName, None, None, entity.bankId, None, cc.callContext) + resultList: JArray = unboxResult(box.asInstanceOf[Box[JArray]], entity.entityName) + _ <- Helper.booleanToFuture(DynamicEntityOperationNotAllowed, cc = cc.callContext) { + resultList.arr.isEmpty + } + deleted: Box[Boolean] <- NewStyle.function.deleteDynamicEntity(dynamicEntityId) + } yield { + (deleted, HttpCode.`204`(cc.callContext)) + } + } + + staticResourceDocs += ResourceDoc( + deleteBankLevelDynamicEntity, + implementedInApiVersion, + nameOf(deleteBankLevelDynamicEntity), + "DELETE", + "/management/banks/BANK_ID/dynamic-entities/DYNAMIC_ENTITY_ID", + "Delete Bank Level Dynamic Entity", + s"""Delete a Bank Level DynamicEntity specified by DYNAMIC_ENTITY_ID. + | + |""", + EmptyBody, + EmptyBody, + List( + $UserNotLoggedIn, + UserHasMissingRoles, + UnknownError + ), + List(apiTagManageDynamicEntity, apiTagApi, apiTagNewStyle), + Some(List(canDeleteBankLevelDynamicEntity))) + lazy val deleteBankLevelDynamicEntity: OBPEndpoint = { + case "management" :: "banks" :: BankId(bankId) :: "dynamic-entities" :: dynamicEntityId :: Nil JsonDelete _ => { + cc => + deleteDynamicEntityMethod(dynamicEntityId, cc) } } From b45edd0f6bf46e6af0b9311fd6d1c227d6e8ec10 Mon Sep 17 00:00:00 2001 From: hongwei Date: Mon, 7 Jun 2021 13:37:00 +0200 Subject: [PATCH 024/147] feature/OBPv400 added bankId to dynamicEndpoint --- .../src/main/scala/code/api/util/ApiTag.scala | 1 - .../main/scala/code/api/util/NewStyle.scala | 4 +- .../scala/code/api/v4_0_0/APIMethods400.scala | 91 ++++++++++++++----- .../DynamicEndpointProvider.scala | 6 +- .../MapppedDynamicEndpointProvider.scala | 12 ++- 5 files changed, 82 insertions(+), 32 deletions(-) diff --git a/obp-api/src/main/scala/code/api/util/ApiTag.scala b/obp-api/src/main/scala/code/api/util/ApiTag.scala index 5896433f7..4bbe50307 100644 --- a/obp-api/src/main/scala/code/api/util/ApiTag.scala +++ b/obp-api/src/main/scala/code/api/util/ApiTag.scala @@ -72,7 +72,6 @@ object ApiTag { val apiTagEndpointMapping = ResourceDocTag("Endpoint-Mapping") val apiTagWebUiProps = ResourceDocTag("WebUi-Props") val apiTagManageDynamicEntity = ResourceDocTag("Dynamic-Entity-Manage") - val apiTagDynamicSwaggerDoc = ResourceDocTag("Dynamic-Swagger-Doc-Manage") val apiTagDynamicResourceDoc = ResourceDocTag("Dynamic-Resource-Doc-Manage") val apiTagDynamicMessageDoc = ResourceDocTag("Dynamic-Message-Doc-Manage") val apiTagApiCollection = ResourceDocTag("Api-Collection") 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 6e92aed4e..4babefa22 100644 --- a/obp-api/src/main/scala/code/api/util/NewStyle.scala +++ b/obp-api/src/main/scala/code/api/util/NewStyle.scala @@ -2708,8 +2708,8 @@ object NewStyle { getConnectorByName(connectorName).flatMap(_.callableMethods.get(methodName)) } - def createDynamicEndpoint(userId: String, swaggerString: String, callContext: Option[CallContext]): OBPReturnType[DynamicEndpointT] = Future { - (DynamicEndpointProvider.connectorMethodProvider.vend.create(userId, swaggerString), callContext) + def createDynamicEndpoint(bankId:Option[String], userId: String, swaggerString: String, callContext: Option[CallContext]): OBPReturnType[DynamicEndpointT] = Future { + (DynamicEndpointProvider.connectorMethodProvider.vend.create(bankId:Option[String], userId, swaggerString), callContext) } map { i => (connectorEmptyResponse(i._1, callContext), i._2) } 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 d43c03eff..1003b1a5e 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 @@ -4633,31 +4633,50 @@ trait APIMethods400 { InvalidJsonFormat, UnknownError ), - List(apiTagDynamicSwaggerDoc, apiTagApi, apiTagNewStyle), + List(apiTagDynamicEndpoint, apiTagApi, apiTagNewStyle), Some(List(canCreateDynamicEndpoint))) lazy val createDynamicEndpoint: OBPEndpoint = { case "management" :: "dynamic-endpoints" :: Nil JsonPost json -> _ => { cc => - for { - (postedJson, openAPI) <- NewStyle.function.tryons(InvalidJsonFormat, 400, cc.callContext) { - val swaggerContent = compactRender(json) + createDynamicEndpointMethod(None, json, cc) + } + } - (DynamicEndpointSwagger(swaggerContent), DynamicEndpointHelper.parseSwaggerContent(swaggerContent)) - } - duplicatedUrl = DynamicEndpointHelper.findExistsEndpoints(openAPI).map(kv => s"${kv._1}:${kv._2}") - errorMsg = s"""$DynamicEndpointExists Duplicated ${if(duplicatedUrl.size > 1) "endpoints" else "endpoint"}: ${duplicatedUrl.mkString("; ")}""" - _ <- Helper.booleanToFuture(errorMsg, cc=cc.callContext) { - duplicatedUrl.isEmpty - } - (dynamicEndpoint, callContext) <- NewStyle.function.createDynamicEndpoint(cc.userId, postedJson.swaggerString, cc.callContext) - } yield { - val roles = DynamicEndpointHelper.getRoles(dynamicEndpoint.dynamicEndpointId.getOrElse("")) - roles.map(role => Entitlement.entitlement.vend.addEntitlement("", cc.userId, role.toString())) - val swaggerJson = parse(dynamicEndpoint.swaggerString) - val responseJson: JObject = ("user_id", cc.userId) ~ ("dynamic_endpoint_id", dynamicEndpoint.dynamicEndpointId) ~ ("swagger_string", swaggerJson) - (responseJson, HttpCode.`201`(callContext)) - } + staticResourceDocs += ResourceDoc( + createBankLevelDynamicEndpoint, + implementedInApiVersion, + nameOf(createBankLevelDynamicEndpoint), + "POST", + "/management/banks/BANK_ID/dynamic-endpoints", + " Create Bank Level Dynamic Endpoint", + s"""Create dynamic endpoints. + | + |Create dynamic endpoints with one json format swagger content. + | + |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,\n + |when create MethodRouting for given dynamic endpoint, it will be routed to given url. + | + |""", + dynamicEndpointRequestBodyExample, + dynamicEndpointResponseBodyExample, + List( + $UserNotLoggedIn, + UserHasMissingRoles, + DynamicEndpointExists, + InvalidJsonFormat, + UnknownError + ), + List(apiTagDynamicEndpoint, apiTagApi, apiTagNewStyle), + Some(List(canCreateDynamicEndpoint))) + + lazy val createBankLevelDynamicEndpoint: OBPEndpoint = { + case "management" :: "banks" :: bankId ::"dynamic-endpoints" :: Nil JsonPost json -> _ => { + cc => + createDynamicEndpointMethod(Some(bankId), json, cc) } } @@ -4680,7 +4699,7 @@ trait APIMethods400 { InvalidJsonFormat, UnknownError ), - List(apiTagDynamicSwaggerDoc, apiTagApi, apiTagNewStyle), + List(apiTagDynamicEndpoint, apiTagApi, apiTagNewStyle), Some(List(canUpdateDynamicEndpoint))) lazy val updateDynamicEndpointHost: OBPEndpoint = { @@ -4721,7 +4740,7 @@ trait APIMethods400 { InvalidJsonFormat, UnknownError ), - List(apiTagDynamicSwaggerDoc, apiTagApi, apiTagNewStyle), + List(apiTagDynamicEndpoint, apiTagApi, apiTagNewStyle), Some(List(canGetDynamicEndpoint))) lazy val getDynamicEndpoint: OBPEndpoint = { @@ -4760,7 +4779,7 @@ trait APIMethods400 { InvalidJsonFormat, UnknownError ), - List(apiTagDynamicSwaggerDoc, apiTagApi, apiTagNewStyle), + List(apiTagDynamicEndpoint, apiTagApi, apiTagNewStyle), Some(List(canGetDynamicEndpoints))) lazy val getDynamicEndpoints: OBPEndpoint = { @@ -4793,7 +4812,7 @@ trait APIMethods400 { DynamicEndpointNotFoundByDynamicEndpointId, UnknownError ), - List(apiTagDynamicSwaggerDoc, apiTagApi, apiTagNewStyle), + List(apiTagDynamicEndpoint, apiTagApi, apiTagNewStyle), Some(List(canDeleteDynamicEndpoint))) lazy val deleteDynamicEndpoint : OBPEndpoint = { @@ -4825,7 +4844,7 @@ trait APIMethods400 { InvalidJsonFormat, UnknownError ), - List(apiTagDynamicSwaggerDoc, apiTagApi, apiTagNewStyle) + List(apiTagDynamicEndpoint, apiTagApi, apiTagNewStyle) ) lazy val getMyDynamicEndpoints: OBPEndpoint = { @@ -4858,7 +4877,7 @@ trait APIMethods400 { DynamicEndpointNotFoundByDynamicEndpointId, UnknownError ), - List(apiTagDynamicSwaggerDoc, apiTagApi, apiTagNewStyle), + List(apiTagDynamicEndpoint, apiTagApi, apiTagNewStyle), ) lazy val deleteMyDynamicEndpoint : OBPEndpoint = { @@ -8646,6 +8665,28 @@ trait APIMethods400 { } } } + + private def createDynamicEndpointMethod(bankId: Option[String], json: JValue, cc: CallContext) = { + for { + (postedJson, openAPI) <- NewStyle.function.tryons(InvalidJsonFormat, 400, cc.callContext) { + val swaggerContent = compactRender(json) + + (DynamicEndpointSwagger(swaggerContent), DynamicEndpointHelper.parseSwaggerContent(swaggerContent)) + } + duplicatedUrl = DynamicEndpointHelper.findExistsEndpoints(openAPI).map(kv => s"${kv._1}:${kv._2}") + errorMsg = s"""$DynamicEndpointExists Duplicated ${if (duplicatedUrl.size > 1) "endpoints" else "endpoint"}: ${duplicatedUrl.mkString("; ")}""" + _ <- Helper.booleanToFuture(errorMsg, cc = cc.callContext) { + duplicatedUrl.isEmpty + } + (dynamicEndpoint, callContext) <- NewStyle.function.createDynamicEndpoint(bankId, cc.userId, postedJson.swaggerString, cc.callContext) + } yield { + val roles = DynamicEndpointHelper.getRoles(dynamicEndpoint.dynamicEndpointId.getOrElse("")) + roles.map(role => Entitlement.entitlement.vend.addEntitlement(bankId.getOrElse(""), cc.userId, role.toString())) + val swaggerJson = parse(dynamicEndpoint.swaggerString) + val responseJson: JObject = ("bank_id", dynamicEndpoint.bankId) ~ ("user_id", cc.userId) ~ ("dynamic_endpoint_id", dynamicEndpoint.dynamicEndpointId) ~ ("swagger_string", swaggerJson) + (responseJson, HttpCode.`201`(callContext)) + } + } } object APIMethods400 extends RestHelper with APIMethods400 { diff --git a/obp-api/src/main/scala/code/dynamicEndpoint/DynamicEndpointProvider.scala b/obp-api/src/main/scala/code/dynamicEndpoint/DynamicEndpointProvider.scala index 328c88604..cbb8b1072 100644 --- a/obp-api/src/main/scala/code/dynamicEndpoint/DynamicEndpointProvider.scala +++ b/obp-api/src/main/scala/code/dynamicEndpoint/DynamicEndpointProvider.scala @@ -18,12 +18,14 @@ trait DynamicEndpointT { * The user who create this DynamicEndpoint */ def userId: String + def bankId: Option[String] } case class DynamicEndpointCommons( dynamicEndpointId: Option[String] = None, swaggerString: String, - userId: String + userId: String, + bankId: Option[String] ) extends DynamicEndpointT with JsonFieldReName object DynamicEndpointCommons extends Converter[DynamicEndpointT, DynamicEndpointCommons] @@ -31,7 +33,7 @@ object DynamicEndpointCommons extends Converter[DynamicEndpointT, DynamicEndpoin case class DynamicEndpointSwagger(swaggerString: String, dynamicEndpointId: Option[String] = None) trait DynamicEndpointProvider { - def create(userId: String, swaggerString: String): Box[DynamicEndpointT] + def create(bankId:Option[String], userId: String, swaggerString: String): Box[DynamicEndpointT] def update(dynamicEndpointId: String, swaggerString: String): Box[DynamicEndpointT] def updateHost(dynamicEndpointId: String, hostString: String): Box[DynamicEndpointT] def get(dynamicEndpointId: String): Box[DynamicEndpointT] diff --git a/obp-api/src/main/scala/code/dynamicEndpoint/MapppedDynamicEndpointProvider.scala b/obp-api/src/main/scala/code/dynamicEndpoint/MapppedDynamicEndpointProvider.scala index 437512cb4..7a2248e09 100644 --- a/obp-api/src/main/scala/code/dynamicEndpoint/MapppedDynamicEndpointProvider.scala +++ b/obp-api/src/main/scala/code/dynamicEndpoint/MapppedDynamicEndpointProvider.scala @@ -20,8 +20,13 @@ object MappedDynamicEndpointProvider extends DynamicEndpointProvider with Custom else APIUtil.getPropsValue(s"dynamicEndpoint.cache.ttl.seconds", "32").toInt } - override def create(userId: String, swaggerString: String): Box[DynamicEndpointT] = { - tryo{DynamicEndpoint.create.UserId(userId).SwaggerString(swaggerString).saveMe()} + override def create(bankId:Option[String], userId: String, swaggerString: String): Box[DynamicEndpointT] = { + tryo{DynamicEndpoint.create + .UserId(userId) + .BankId(bankId.getOrElse(null)) + .SwaggerString(swaggerString) + .saveMe() + } } override def update(dynamicEndpointId: String, swaggerString: String): Box[DynamicEndpointT] = { DynamicEndpoint.find(By(DynamicEndpoint.DynamicEndpointId, dynamicEndpointId)).map(_.SwaggerString(swaggerString).saveMe()) @@ -59,10 +64,13 @@ class DynamicEndpoint extends DynamicEndpointT with LongKeyedMapper[DynamicEndpo object SwaggerString extends MappedText(this) object UserId extends MappedString(this, 255) + + object BankId extends MappedString(this, 255) override def dynamicEndpointId: Option[String] = Option(DynamicEndpointId.get) override def swaggerString: String = SwaggerString.get override def userId: String = UserId.get + override def bankId: Option[String] = if (BankId.get == null || BankId.get.isEmpty) None else Some(BankId.get) } object DynamicEndpoint extends DynamicEndpoint with LongKeyedMetaMapper[DynamicEndpoint] { From 177c4b91d703b320952f4e955bbfda71cdfeb5e9 Mon Sep 17 00:00:00 2001 From: hongwei Date: Tue, 8 Jun 2021 09:56:45 +0200 Subject: [PATCH 025/147] refactor/tweak the balanceType to `OpeningBooked` --- .../berlin/group/v1_3/JSONFactory_BERLIN_GROUP_1_3.scala | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/obp-api/src/main/scala/code/api/berlin/group/v1_3/JSONFactory_BERLIN_GROUP_1_3.scala b/obp-api/src/main/scala/code/api/berlin/group/v1_3/JSONFactory_BERLIN_GROUP_1_3.scala index 963121679..8e3bcc6f2 100644 --- a/obp-api/src/main/scala/code/api/berlin/group/v1_3/JSONFactory_BERLIN_GROUP_1_3.scala +++ b/obp-api/src/main/scala/code/api/berlin/group/v1_3/JSONFactory_BERLIN_GROUP_1_3.scala @@ -280,7 +280,7 @@ object JSONFactory_BERLIN_GROUP_1_3 extends CustomJsonFormats { val balance = CoreAccountBalancesJson( balanceAmount = AmountOfMoneyV13(x.currency,x.balance.toString()), - balanceType = APIUtil.stringOrNull(x.accountType), + balanceType = "OpeningBooked", lastChangeDateTime= APIUtil.dateOrNull(x.lastUpdate), referenceDate = APIUtil.dateOrNull(x.lastUpdate), lastCommittedTransaction = "" @@ -309,7 +309,7 @@ object JSONFactory_BERLIN_GROUP_1_3 extends CustomJsonFormats { val balance = CoreAccountBalancesJson( balanceAmount = AmountOfMoneyV13(x.currency,x.balance.toString()), - balanceType = APIUtil.stringOrNull(x.accountType), + balanceType = "OpeningBooked", lastChangeDateTime= APIUtil.dateOrNull(x.lastUpdate), referenceDate = APIUtil.dateOrNull(x.lastUpdate), lastCommittedTransaction = "String" @@ -377,7 +377,7 @@ object JSONFactory_BERLIN_GROUP_1_3 extends CustomJsonFormats { currency = APIUtil.stringOrNull(bankAccount.currency), amount = bankAccount.balance.toString() ), - balanceType = APIUtil.stringOrNull(bankAccount.accountType), + balanceType = "OpeningBooked", lastChangeDateTime = APIUtil.dateOrNull(bankAccount.lastUpdate), referenceDate = APIUtil.dateOrNull(bankAccount.lastUpdate), lastCommittedTransaction = "String" From 5124f084cb054ed5c88d213f43553efac9098507 Mon Sep 17 00:00:00 2001 From: hongwei Date: Tue, 8 Jun 2021 09:59:31 +0200 Subject: [PATCH 026/147] refactor/remove getBalances method --- obp-api/src/main/scala/code/api/util/NewStyle.scala | 5 ----- 1 file changed, 5 deletions(-) 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 4babefa22..6231d3551 100644 --- a/obp-api/src/main/scala/code/api/util/NewStyle.scala +++ b/obp-api/src/main/scala/code/api/util/NewStyle.scala @@ -312,11 +312,6 @@ object NewStyle { } } map { unboxFull(_) } } - def getBalances(callContext: Option[CallContext]) : OBPReturnType[List[Bank]] = { - Connector.connector.vend.getBanks(callContext: Option[CallContext]) map { - connectorEmptyResponse(_, callContext) - } - } def getBankAccount(bankId : BankId, accountId : AccountId, callContext: Option[CallContext]): OBPReturnType[BankAccount] = { Connector.connector.vend.checkBankAccountExists(bankId, accountId, callContext) map { i => (unboxFullOrFail(i._1, callContext,s"$BankAccountNotFound Current BankId is $bankId and Current AccountId is $accountId", 404 ), i._2) From 4f0eb8b3c57096758632be877e544c72f070c8fb Mon Sep 17 00:00:00 2001 From: hongwei Date: Tue, 8 Jun 2021 13:16:54 +0200 Subject: [PATCH 027/147] feature/OBPv400 added bankId for create bank level dynamicEndpoint --- .../main/scala/code/api/util/NewStyle.scala | 6 +- .../scala/code/api/v4_0_0/APIMethods400.scala | 8 +- .../dynamic/DynamicEndpointHelper.scala | 51 +- .../v4_0_0/DynamicEndpointHelperTest.scala | 2230 +++++++++++++++++ 4 files changed, 2275 insertions(+), 20 deletions(-) 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 6231d3551..971ab2d4e 100644 --- a/obp-api/src/main/scala/code/api/util/NewStyle.scala +++ b/obp-api/src/main/scala/code/api/util/NewStyle.scala @@ -853,8 +853,10 @@ object NewStyle { } def hasEntitlement(bankId: String, userId: String, role: ApiRole, callContext: Option[CallContext], errorMsg: String = ""): Future[Box[Unit]] = { - val errorInfo = if(StringUtils.isBlank(errorMsg)) UserHasMissingRoles + role.toString() - else errorMsg + val errorInfo = + if(StringUtils.isBlank(errorMsg)&& !bankId.isEmpty) UserHasMissingRoles + role.toString() + s" at Bank($bankId)" + else if(StringUtils.isBlank(errorMsg)&& bankId.isEmpty) UserHasMissingRoles + role.toString() + else errorMsg Helper.booleanToFuture(errorInfo, cc=callContext) { APIUtil.hasEntitlement(bankId, userId, role) 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 1003b1a5e..164b2eb3f 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 @@ -4897,7 +4897,7 @@ trait APIMethods400 { } lazy val dynamicEndpoint: OBPEndpoint = { - case DynamicReq(url, json, method, params, pathParams, role, operationId, mockResponse) => { cc => + case DynamicReq(url, json, method, params, pathParams, role, operationId, mockResponse, bankId) => { cc => // process before authentication interceptor, get intercept result val resourceDoc = DynamicEndpointHelper.doc.find(_.operationId == operationId) val callContext = cc.copy(operationId = Some(operationId), resourceDocument = resourceDoc) @@ -4905,7 +4905,7 @@ trait APIMethods400 { if(beforeInterceptResult.isDefined) beforeInterceptResult else for { (Full(u), callContext) <- authenticatedAccess(callContext) // Inject operationId into Call Context. It's used by Rate Limiting. - _ <- NewStyle.function.hasEntitlement("", u.userId, role, callContext) + _ <- NewStyle.function.hasEntitlement(bankId.getOrElse(""), u.userId, role, callContext) // validate request json payload httpRequestMethod = cc.verb @@ -8669,7 +8669,9 @@ trait APIMethods400 { private def createDynamicEndpointMethod(bankId: Option[String], json: JValue, cc: CallContext) = { for { (postedJson, openAPI) <- NewStyle.function.tryons(InvalidJsonFormat, 400, cc.callContext) { - val swaggerContent = compactRender(json) + //If it is bank level, we manully added /banks/bankId in all the paths: + val jsonTweakedPath = DynamicEndpointHelper.addedBankToPath(json, bankId) + val swaggerContent = compactRender(jsonTweakedPath) (DynamicEndpointSwagger(swaggerContent), DynamicEndpointHelper.parseSwaggerContent(swaggerContent)) } diff --git a/obp-api/src/main/scala/code/api/v4_0_0/dynamic/DynamicEndpointHelper.scala b/obp-api/src/main/scala/code/api/v4_0_0/dynamic/DynamicEndpointHelper.scala index abc0b5de5..f29114768 100644 --- a/obp-api/src/main/scala/code/api/v4_0_0/dynamic/DynamicEndpointHelper.scala +++ b/obp-api/src/main/scala/code/api/v4_0_0/dynamic/DynamicEndpointHelper.scala @@ -57,21 +57,21 @@ object DynamicEndpointHelper extends RestHelper { private def dynamicEndpointInfos: List[DynamicEndpointInfo] = { val dynamicEndpoints: List[DynamicEndpointT] = DynamicEndpointProvider.connectorMethodProvider.vend.getAll() - val infos = dynamicEndpoints.map(it => buildDynamicEndpointInfo(it.swaggerString, it.dynamicEndpointId.get)) + val infos = dynamicEndpoints.map(it => buildDynamicEndpointInfo(it.swaggerString, it.dynamicEndpointId.get, it.bankId)) infos } def allDynamicEndpointRoles: List[ApiRole] = { for { dynamicEndpoint <- DynamicEndpointProvider.connectorMethodProvider.vend.getAll() - info = buildDynamicEndpointInfo(dynamicEndpoint.swaggerString, dynamicEndpoint.dynamicEndpointId.get) + info = buildDynamicEndpointInfo(dynamicEndpoint.swaggerString, dynamicEndpoint.dynamicEndpointId.get, dynamicEndpoint.bankId) role <- getRoles(info) } yield role } def getRoles(dynamicEndpointId: String): List[ApiRole] = { val foundInfos: Box[DynamicEndpointInfo] = DynamicEndpointProvider.connectorMethodProvider.vend.get(dynamicEndpointId) - .map(dynamicEndpoint => buildDynamicEndpointInfo(dynamicEndpoint.swaggerString, dynamicEndpoint.dynamicEndpointId.get)) + .map(dynamicEndpoint => buildDynamicEndpointInfo(dynamicEndpoint.swaggerString, dynamicEndpoint.dynamicEndpointId.get, dynamicEndpoint.bankId)) val roles: List[ApiRole] = foundInfos match { @@ -108,7 +108,7 @@ object DynamicEndpointHelper extends RestHelper { * @param r HttpRequest * @return (adapterUrl, requestBodyJson, httpMethod, requestParams, pathParams, role, operationId, mockResponseCode->mockResponseBody) */ - def unapply(r: Req): Option[(String, JValue, AkkaHttpMethod, Map[String, List[String]], Map[String, String], ApiRole, String, Option[(Int, JValue)])] = { + def unapply(r: Req): Option[(String, JValue, AkkaHttpMethod, Map[String, List[String]], Map[String, String], ApiRole, String, Option[(Int, JValue)], Option[String])] = { val partPath = r.path.partPath//eg: List("dynamic","feature-test") if (!testResponse_?(r) || partPath.headOption != Option(urlPrefix))//if check the Content-Type contains json or not, and check the if it is the `dynamic_endpoints_url_prefix` None //if do not match `URL and Content-Type`, then can not find this endpoint. return None. @@ -118,7 +118,7 @@ object DynamicEndpointHelper extends RestHelper { val urlQueryParameters = r.params // url that match original swagger endpoint. val url = partPath.tail.mkString("/", "/", "") // eg: --> /feature-test - val foundDynamicEndpoint: Option[(String, String, Int, ResourceDoc)] = dynamicEndpointInfos + val foundDynamicEndpoint: Option[(String, String, Int, ResourceDoc, Option[String])] = dynamicEndpointInfos .map(_.findDynamicEndpoint(httpMethod, url)) .collectFirst { case Some(x) => x @@ -126,7 +126,7 @@ object DynamicEndpointHelper extends RestHelper { foundDynamicEndpoint .flatMap { it => - val (serverUrl, endpointUrl, code, doc) = it + val (serverUrl, endpointUrl, code, doc, bankId) = it val pathParams: Map[String, String] = if(endpointUrl == url) { Map.empty[String, String] @@ -152,7 +152,7 @@ object DynamicEndpointHelper extends RestHelper { val Some(role::_) = doc.roles val requestBodyJValue = body(r).getOrElse(JNothing) - Full(s"""$serverUrl$url""", requestBodyJValue, akkaHttpMethod, urlQueryParameters, pathParams, role, doc.operationId, mockResponse) + Full(s"""$serverUrl$url""", requestBodyJValue, akkaHttpMethod, urlQueryParameters, pathParams, role, doc.operationId, mockResponse, bankId) } } @@ -171,13 +171,13 @@ object DynamicEndpointHelper extends RestHelper { private val dynamicEndpointInfoMemo = new Memo[String, DynamicEndpointInfo] - private def buildDynamicEndpointInfo(content: String, id: String): DynamicEndpointInfo = + private def buildDynamicEndpointInfo(content: String, id: String, bankId:Option[String]): DynamicEndpointInfo = dynamicEndpointInfoMemo.memoize(content) { val openAPI: OpenAPI = parseSwaggerContent(content) - buildDynamicEndpointInfo(openAPI, id) + buildDynamicEndpointInfo(openAPI, id, bankId) } - private def buildDynamicEndpointInfo(openAPI: OpenAPI, id: String): DynamicEndpointInfo = { + private def buildDynamicEndpointInfo(openAPI: OpenAPI, id: String, bankId:Option[String]): DynamicEndpointInfo = { val tags: List[ResourceDocTag] = List(ApiTag(openAPI.getInfo.getTitle), apiTagNewStyle, apiTagDynamicEntity, apiTagDynamic) val serverUrl = { @@ -267,7 +267,7 @@ object DynamicEndpointHelper extends RestHelper { } Some(List( - ApiRole.getOrCreateDynamicApiRole(roleName) + ApiRole.getOrCreateDynamicApiRole(roleName, bankId.isDefined) )) } val doc = ResourceDoc( @@ -287,7 +287,7 @@ object DynamicEndpointHelper extends RestHelper { DynamicEndpointItem(path, successCode, doc) } - DynamicEndpointInfo(id, dynamicEndpointItems, serverUrl) + DynamicEndpointInfo(id, dynamicEndpointItems, serverUrl, bankId) } private val PathParamRegx = """\{(.+?)\}""".r @@ -805,6 +805,27 @@ object DynamicEndpointHelper extends RestHelper { val (dynamicEntityName, dynamicDateId) = findDynamicData(dynamicDataList, dynamicDataJson) JBool(DynamicDataProvider.connectorMethodProvider.vend.delete(dynamicEntityName, dynamicDateId).getOrElse(false)) } + + def addedBankToPath(swagger: String, bankId: Option[String]): JValue = { + val jvalue = json.parse(swagger) + addedBankToPath(jvalue, bankId) + } + + // If it is bank is defined, we will add the bank into the path, better check the scala tests + // eg: /fashion-brand-list --> /banks/gh.29.uk/fashion-brand-list + def addedBankToPath(swagger: JValue, bankId: Option[String]): JValue = { + if(bankId.isDefined){ + swagger transformField { + case JField(name, JObject(obj)) => + if (name.startsWith("/")) + JField(s"/banks/${bankId.get}$name", JObject(obj)) + else + JField(name, JObject(obj)) + } + } else{ + swagger + } + } } @@ -822,7 +843,7 @@ case class DynamicEndpointItem(path: String, successCode: Int, resourceDoc: Reso * @param dynamicEndpointItems ResourceDoc to url that defined in swagger content * @param serverUrl base url that defined in swagger content */ -case class DynamicEndpointInfo(id: String, dynamicEndpointItems: mutable.Iterable[DynamicEndpointItem], serverUrl: String) { +case class DynamicEndpointInfo(id: String, dynamicEndpointItems: mutable.Iterable[DynamicEndpointItem], serverUrl: String, bankId:Option[String]) { val resourceDocs: mutable.Iterable[ResourceDoc] = dynamicEndpointItems.map(_.resourceDoc) private val existsUrlToMethod: mutable.Iterable[(HttpMethod, String, Int, ResourceDoc)] = @@ -833,10 +854,10 @@ case class DynamicEndpointInfo(id: String, dynamicEndpointItems: mutable.Iterabl }) // return (serverUrl, endpointUrl, successCode, resourceDoc) - def findDynamicEndpoint(newMethod: HttpMethod, newUrl: String): Option[(String, String, Int, ResourceDoc)] = + def findDynamicEndpoint(newMethod: HttpMethod, newUrl: String): Option[(String, String, Int, ResourceDoc, Option[String])] = existsUrlToMethod collectFirst { case (method, url, code, doc) if isSameUrl(newUrl, url) && newMethod == method => - (this.serverUrl, url, code, doc) + (this.serverUrl, url, code, doc, this.bankId) } def existsEndpoint(newMethod: HttpMethod, newUrl: String): Boolean = findDynamicEndpoint(newMethod, newUrl).isDefined diff --git a/obp-api/src/test/scala/code/api/v4_0_0/DynamicEndpointHelperTest.scala b/obp-api/src/test/scala/code/api/v4_0_0/DynamicEndpointHelperTest.scala index 2e6300c2f..b1801d7bc 100644 --- a/obp-api/src/test/scala/code/api/v4_0_0/DynamicEndpointHelperTest.scala +++ b/obp-api/src/test/scala/code/api/v4_0_0/DynamicEndpointHelperTest.scala @@ -794,4 +794,2234 @@ class DynamicEndpointHelperTest extends FlatSpec with Matchers { result should equal(expectedResult) } + + "addedBankToPath with one path swagger input" should "work well" taggedAs FunctionsTag in { + + val dataJsonString = """{ + | "swagger": "2.0", + | "info": { + | "description": "Entity Data Test bank Level", + | "version": "1.0.0", + | "title": "Entity Data Test bank Level" + | }, + | "host": "obp_mock", + | "basePath": "/obp/v4.0.0/dynamic", + | "schemes": [ + | "https", + | "http" + | ], + | "paths": { + | "/fashion-brand-list": { + | "get": { + | "tags": [ + | "Dynamic Entity Data POC" + | ], + | "summary": "Get Brand bank Level", + | "description": "Get Brand bank Level", + | "produces": [ + | "application/json" + | ], + | "responses": { + | "200": { + | "description": "Success Response", + | "schema": { + | "$ref": "#/definitions/FashionBrandNames" + | } + | } + | } + | } + | } + | }, + | "definitions": { + | "FashionBrandNames": { + | "type": "object", + | "properties": { + | "name": { + | "type": "string" + | } + | } + | } + | } + |}""".stripMargin + val bankId = Some("gh.29.uk") + + val expectedJsonString = """{ + | "swagger": "2.0", + | "info": { + | "description": "Entity Data Test bank Level", + | "version": "1.0.0", + | "title": "Entity Data Test bank Level" + | }, + | "host": "obp_mock", + | "basePath": "/obp/v4.0.0/dynamic", + | "schemes": [ + | "https", + | "http" + | ], + | "paths": { + | "/banks/gh.29.uk/fashion-brand-list": { + | "get": { + | "tags": [ + | "Dynamic Entity Data POC" + | ], + | "summary": "Get Brand bank Level", + | "description": "Get Brand bank Level", + | "produces": [ + | "application/json" + | ], + | "responses": { + | "200": { + | "description": "Success Response", + | "schema": { + | "$ref": "#/definitions/FashionBrandNames" + | } + | } + | } + | } + | } + | }, + | "definitions": { + | "FashionBrandNames": { + | "type": "object", + | "properties": { + | "name": { + | "type": "string" + | } + | } + | } + | } + |}""".stripMargin + + val expectedJson = json.parse(expectedJsonString) + + val result= DynamicEndpointHelper.addedBankToPath(dataJsonString, bankId) + + expectedJson should equal(result) + } + + "addedBankToPath with multiple paths swagger input" should "work well" taggedAs FunctionsTag in { + + val dataJsonString = """{ + | "swagger": "2.0", + | "info": { + | "description": "This is a sample server Petstore server. You can find out more about Swagger at [http://swagger.io](http://swagger.io) or on [irc.freenode.net, #swagger](http://swagger.io/irc/). For this sample, you can use the api key `special-key` to test the authorization filters.", + | "version": "1.0.5", + | "title": "Swagger Petstore", + | "termsOfService": "http://swagger.io/terms/", + | "contact": { + | "email": "apiteam@swagger.io" + | }, + | "license": { + | "name": "Apache 2.0", + | "url": "http://www.apache.org/licenses/LICENSE-2.0.html" + | } + | }, + | "host": "obp_mock", + | "host": "DynamicEntity", + | + | "basePath": "/v2", + | "tags": [ + | { + | "name": "pet", + | "description": "Everything about your Pets", + | "externalDocs": { + | "description": "Find out more", + | "url": "http://swagger.io" + | } + | }, + | { + | "name": "store", + | "description": "Access to Petstore orders" + | }, + | { + | "name": "user", + | "description": "Operations about user", + | "externalDocs": { + | "description": "Find out more about our store", + | "url": "http://swagger.io" + | } + | } + | ], + | "schemes": [ + | "https", + | "http" + | ], + | "paths": { + | "/pet/{petId}/uploadImage": { + | "post": { + | "tags": [ + | "pet" + | ], + | "summary": "uploads an image", + | "description": "", + | "operationId": "uploadFile", + | "consumes": [ + | "multipart/form-data" + | ], + | "produces": [ + | "application/json" + | ], + | "parameters": [ + | { + | "name": "petId", + | "in": "path", + | "description": "ID of pet to update", + | "required": true, + | "type": "integer", + | "format": "int64" + | }, + | { + | "name": "additionalMetadata", + | "in": "formData", + | "description": "Additional data to pass to server", + | "required": false, + | "type": "string" + | }, + | { + | "name": "file", + | "in": "formData", + | "description": "file to upload", + | "required": false, + | "type": "file" + | } + | ], + | "responses": { + | "200": { + | "description": "successful operation", + | "schema": { + | "$ref": "#/definitions/ApiResponse" + | } + | } + | }, + | "security": [ + | { + | "petstore_auth": [ + | "write:pets", + | "read:pets" + | ] + | } + | ] + | } + | }, + | "/pet": { + | "post": { + | "tags": [ + | "pet" + | ], + | "summary": "Add a new pet to the store", + | "description": "", + | "operationId": "addPet", + | "consumes": [ + | "application/json", + | "application/xml" + | ], + | "produces": [ + | "application/json", + | "application/xml" + | ], + | "parameters": [ + | { + | "in": "body", + | "name": "body", + | "description": "Pet object that needs to be added to the store", + | "required": true, + | "schema": { + | "$ref": "#/definitions/Pet" + | } + | } + | ], + | "responses": { + | "405": { + | "description": "Invalid input" + | } + | }, + | "security": [ + | { + | "petstore_auth": [ + | "write:pets", + | "read:pets" + | ] + | } + | ] + | }, + | "put": { + | "tags": [ + | "pet" + | ], + | "summary": "Update an existing pet", + | "description": "", + | "operationId": "updatePet", + | "consumes": [ + | "application/json", + | "application/xml" + | ], + | "produces": [ + | "application/json", + | "application/xml" + | ], + | "parameters": [ + | { + | "in": "body", + | "name": "body", + | "description": "Pet object that needs to be added to the store", + | "required": true, + | "schema": { + | "$ref": "#/definitions/Pet" + | } + | } + | ], + | "responses": { + | "400": { + | "description": "Invalid ID supplied" + | }, + | "404": { + | "description": "Pet not found" + | }, + | "405": { + | "description": "Validation exception" + | } + | }, + | "security": [ + | { + | "petstore_auth": [ + | "write:pets", + | "read:pets" + | ] + | } + | ] + | } + | }, + | "/pet/findByStatus": { + | "get": { + | "tags": [ + | "pet" + | ], + | "summary": "Finds Pets by status", + | "description": "Multiple status values can be provided with comma separated strings", + | "operationId": "findPetsByStatus", + | "produces": [ + | "application/json", + | "application/xml" + | ], + | "parameters": [ + | { + | "name": "status", + | "in": "query", + | "description": "Status values that need to be considered for filter", + | "required": true, + | "type": "array", + | "items": { + | "type": "string", + | "enum": [ + | "available", + | "pending", + | "sold" + | ], + | "default": "available" + | }, + | "collectionFormat": "multi" + | } + | ], + | "responses": { + | "200": { + | "description": "successful operation", + | "schema": { + | "type": "array", + | "items": { + | "$ref": "#/definitions/Pet" + | } + | } + | }, + | "400": { + | "description": "Invalid status value" + | } + | }, + | "security": [ + | { + | "petstore_auth": [ + | "write:pets", + | "read:pets" + | ] + | } + | ] + | } + | }, + | "/pet/findByTags": { + | "get": { + | "tags": [ + | "pet" + | ], + | "summary": "Finds Pets by tags", + | "description": "Multiple tags can be provided with comma separated strings. Use tag1, tag2, tag3 for testing.", + | "operationId": "findPetsByTags", + | "produces": [ + | "application/json", + | "application/xml" + | ], + | "parameters": [ + | { + | "name": "tags", + | "in": "query", + | "description": "Tags to filter by", + | "required": true, + | "type": "array", + | "items": { + | "type": "string" + | }, + | "collectionFormat": "multi" + | } + | ], + | "responses": { + | "200": { + | "description": "successful operation", + | "schema": { + | "type": "array", + | "items": { + | "$ref": "#/definitions/Pet" + | } + | } + | }, + | "400": { + | "description": "Invalid tag value" + | } + | }, + | "security": [ + | { + | "petstore_auth": [ + | "write:pets", + | "read:pets" + | ] + | } + | ], + | "deprecated": true + | } + | }, + | "/pet/{petId}": { + | "get": { + | "tags": [ + | "pet" + | ], + | "summary": "Find pet by ID", + | "description": "Returns a single pet", + | "operationId": "getPetById", + | "produces": [ + | "application/json", + | "application/xml" + | ], + | "parameters": [ + | { + | "name": "petId", + | "in": "path", + | "description": "ID of pet to return", + | "required": true, + | "type": "integer", + | "format": "int64" + | } + | ], + | "responses": { + | "200": { + | "description": "successful operation", + | "schema": { + | "$ref": "#/definitions/Pet" + | } + | }, + | "400": { + | "description": "Invalid ID supplied" + | }, + | "404": { + | "description": "Pet not found" + | } + | }, + | "security": [ + | { + | "api_key": [] + | } + | ] + | }, + | "post": { + | "tags": [ + | "pet" + | ], + | "summary": "Updates a pet in the store with form data", + | "description": "", + | "operationId": "updatePetWithForm", + | "consumes": [ + | "application/x-www-form-urlencoded" + | ], + | "produces": [ + | "application/json", + | "application/xml" + | ], + | "parameters": [ + | { + | "name": "petId", + | "in": "path", + | "description": "ID of pet that needs to be updated", + | "required": true, + | "type": "integer", + | "format": "int64" + | }, + | { + | "name": "name", + | "in": "formData", + | "description": "Updated name of the pet", + | "required": false, + | "type": "string" + | }, + | { + | "name": "status", + | "in": "formData", + | "description": "Updated status of the pet", + | "required": false, + | "type": "string" + | } + | ], + | "responses": { + | "405": { + | "description": "Invalid input" + | } + | }, + | "security": [ + | { + | "petstore_auth": [ + | "write:pets", + | "read:pets" + | ] + | } + | ] + | }, + | "delete": { + | "tags": [ + | "pet" + | ], + | "summary": "Deletes a pet", + | "description": "", + | "operationId": "deletePet", + | "produces": [ + | "application/json", + | "application/xml" + | ], + | "parameters": [ + | { + | "name": "api_key", + | "in": "header", + | "required": false, + | "type": "string" + | }, + | { + | "name": "petId", + | "in": "path", + | "description": "Pet id to delete", + | "required": true, + | "type": "integer", + | "format": "int64" + | } + | ], + | "responses": { + | "400": { + | "description": "Invalid ID supplied" + | }, + | "404": { + | "description": "Pet not found" + | } + | }, + | "security": [ + | { + | "petstore_auth": [ + | "write:pets", + | "read:pets" + | ] + | } + | ] + | } + | }, + | "/store/order": { + | "post": { + | "tags": [ + | "store" + | ], + | "summary": "Place an order for a pet", + | "description": "", + | "operationId": "placeOrder", + | "consumes": [ + | "application/json" + | ], + | "produces": [ + | "application/json", + | "application/xml" + | ], + | "parameters": [ + | { + | "in": "body", + | "name": "body", + | "description": "order placed for purchasing the pet", + | "required": true, + | "schema": { + | "$ref": "#/definitions/Order" + | } + | } + | ], + | "responses": { + | "200": { + | "description": "successful operation", + | "schema": { + | "$ref": "#/definitions/Order" + | } + | }, + | "400": { + | "description": "Invalid Order" + | } + | } + | } + | }, + | "/store/order/{orderId}": { + | "get": { + | "tags": [ + | "store" + | ], + | "summary": "Find purchase order by ID", + | "description": "For valid response try integer IDs with value >= 1 and <= 10. Other values will generated exceptions", + | "operationId": "getOrderById", + | "produces": [ + | "application/json", + | "application/xml" + | ], + | "parameters": [ + | { + | "name": "orderId", + | "in": "path", + | "description": "ID of pet that needs to be fetched", + | "required": true, + | "type": "integer", + | "maximum": 10, + | "minimum": 1, + | "format": "int64" + | } + | ], + | "responses": { + | "200": { + | "description": "successful operation", + | "schema": { + | "$ref": "#/definitions/Order" + | } + | }, + | "400": { + | "description": "Invalid ID supplied" + | }, + | "404": { + | "description": "Order not found" + | } + | } + | }, + | "delete": { + | "tags": [ + | "store" + | ], + | "summary": "Delete purchase order by ID", + | "description": "For valid response try integer IDs with positive integer value. Negative or non-integer values will generate API errors", + | "operationId": "deleteOrder", + | "produces": [ + | "application/json", + | "application/xml" + | ], + | "parameters": [ + | { + | "name": "orderId", + | "in": "path", + | "description": "ID of the order that needs to be deleted", + | "required": true, + | "type": "integer", + | "minimum": 1, + | "format": "int64" + | } + | ], + | "responses": { + | "400": { + | "description": "Invalid ID supplied" + | }, + | "404": { + | "description": "Order not found" + | } + | } + | } + | }, + | "/store/inventory": { + | "get": { + | "tags": [ + | "store" + | ], + | "summary": "Returns pet inventories by status", + | "description": "Returns a map of status codes to quantities", + | "operationId": "getInventory", + | "produces": [ + | "application/json" + | ], + | "parameters": [], + | "responses": { + | "200": { + | "description": "successful operation", + | "schema": { + | "type": "object", + | "additionalProperties": { + | "type": "integer", + | "format": "int32" + | } + | } + | } + | }, + | "security": [ + | { + | "api_key": [] + | } + | ] + | } + | }, + | "/user/createWithArray": { + | "post": { + | "tags": [ + | "user" + | ], + | "summary": "Creates list of users with given input array", + | "description": "", + | "operationId": "createUsersWithArrayInput", + | "consumes": [ + | "application/json" + | ], + | "produces": [ + | "application/json", + | "application/xml" + | ], + | "parameters": [ + | { + | "in": "body", + | "name": "body", + | "description": "List of user object", + | "required": true, + | "schema": { + | "type": "array", + | "items": { + | "$ref": "#/definitions/User" + | } + | } + | } + | ], + | "responses": { + | "default": { + | "description": "successful operation" + | } + | } + | } + | }, + | "/user/createWithList": { + | "post": { + | "tags": [ + | "user" + | ], + | "summary": "Creates list of users with given input array", + | "description": "", + | "operationId": "createUsersWithListInput", + | "consumes": [ + | "application/json" + | ], + | "produces": [ + | "application/json", + | "application/xml" + | ], + | "parameters": [ + | { + | "in": "body", + | "name": "body", + | "description": "List of user object", + | "required": true, + | "schema": { + | "type": "array", + | "items": { + | "$ref": "#/definitions/User" + | } + | } + | } + | ], + | "responses": { + | "default": { + | "description": "successful operation" + | } + | } + | } + | }, + | "/user/{username}": { + | "get": { + | "tags": [ + | "user" + | ], + | "summary": "Get user by user name", + | "description": "", + | "operationId": "getUserByName", + | "produces": [ + | "application/json", + | "application/xml" + | ], + | "parameters": [ + | { + | "name": "username", + | "in": "path", + | "description": "The name that needs to be fetched. Use user1 for testing. ", + | "required": true, + | "type": "string" + | } + | ], + | "responses": { + | "200": { + | "description": "successful operation", + | "schema": { + | "$ref": "#/definitions/User" + | } + | }, + | "400": { + | "description": "Invalid username supplied" + | }, + | "404": { + | "description": "User not found" + | } + | } + | }, + | "put": { + | "tags": [ + | "user" + | ], + | "summary": "Updated user", + | "description": "This can only be done by the logged in user.", + | "operationId": "updateUser", + | "consumes": [ + | "application/json" + | ], + | "produces": [ + | "application/json", + | "application/xml" + | ], + | "parameters": [ + | { + | "name": "username", + | "in": "path", + | "description": "name that need to be updated", + | "required": true, + | "type": "string" + | }, + | { + | "in": "body", + | "name": "body", + | "description": "Updated user object", + | "required": true, + | "schema": { + | "$ref": "#/definitions/User" + | } + | } + | ], + | "responses": { + | "400": { + | "description": "Invalid user supplied" + | }, + | "404": { + | "description": "User not found" + | } + | } + | }, + | "delete": { + | "tags": [ + | "user" + | ], + | "summary": "Delete user", + | "description": "This can only be done by the logged in user.", + | "operationId": "deleteUser", + | "produces": [ + | "application/json", + | "application/xml" + | ], + | "parameters": [ + | { + | "name": "username", + | "in": "path", + | "description": "The name that needs to be deleted", + | "required": true, + | "type": "string" + | } + | ], + | "responses": { + | "400": { + | "description": "Invalid username supplied" + | }, + | "404": { + | "description": "User not found" + | } + | } + | } + | }, + | "/user/login": { + | "get": { + | "tags": [ + | "user" + | ], + | "summary": "Logs user into the system", + | "description": "", + | "operationId": "loginUser", + | "produces": [ + | "application/json", + | "application/xml" + | ], + | "parameters": [ + | { + | "name": "username", + | "in": "query", + | "description": "The user name for login", + | "required": true, + | "type": "string" + | }, + | { + | "name": "password", + | "in": "query", + | "description": "The password for login in clear text", + | "required": true, + | "type": "string" + | } + | ], + | "responses": { + | "200": { + | "description": "successful operation", + | "headers": { + | "X-Expires-After": { + | "type": "string", + | "format": "date-time", + | "description": "date in UTC when token expires" + | }, + | "X-Rate-Limit": { + | "type": "integer", + | "format": "int32", + | "description": "calls per hour allowed by the user" + | } + | }, + | "schema": { + | "type": "string" + | } + | }, + | "400": { + | "description": "Invalid username/password supplied" + | } + | } + | } + | }, + | "/user/logout": { + | "get": { + | "tags": [ + | "user" + | ], + | "summary": "Logs out current logged in user session", + | "description": "", + | "operationId": "logoutUser", + | "produces": [ + | "application/json", + | "application/xml" + | ], + | "parameters": [], + | "responses": { + | "default": { + | "description": "successful operation" + | } + | } + | } + | }, + | "/user": { + | "post": { + | "tags": [ + | "user" + | ], + | "summary": "Create user", + | "description": "This can only be done by the logged in user.", + | "operationId": "createUser", + | "consumes": [ + | "application/json" + | ], + | "produces": [ + | "application/json", + | "application/xml" + | ], + | "parameters": [ + | { + | "in": "body", + | "name": "body", + | "description": "Created user object", + | "required": true, + | "schema": { + | "$ref": "#/definitions/User" + | } + | } + | ], + | "responses": { + | "default": { + | "description": "successful operation" + | } + | } + | } + | } + | }, + | "securityDefinitions": { + | "api_key": { + | "type": "apiKey", + | "name": "api_key", + | "in": "header" + | }, + | "petstore_auth": { + | "type": "oauth2", + | "authorizationUrl": "https://petstore.swagger.io/oauth/authorize", + | "flow": "implicit", + | "scopes": { + | "read:pets": "read your pets", + | "write:pets": "modify pets in your account" + | } + | } + | }, + | "definitions": { + | "ApiResponse": { + | "type": "object", + | "properties": { + | "code": { + | "type": "integer", + | "format": "int32" + | }, + | "type": { + | "type": "string" + | }, + | "message": { + | "type": "string" + | } + | } + | }, + | "Category": { + | "type": "object", + | "properties": { + | "id": { + | "type": "integer", + | "format": "int64" + | }, + | "name": { + | "type": "string" + | } + | }, + | "xml": { + | "name": "Category" + | } + | }, + | "Pet": { + | "type": "object", + | "required": [ + | "name", + | "photoUrls" + | ], + | "properties": { + | "id": { + | "type": "integer", + | "format": "int64" + | }, + | "category": { + | "$ref": "#/definitions/Category" + | }, + | "name": { + | "type": "string", + | "example": "doggie" + | }, + | "photoUrls": { + | "type": "array", + | "xml": { + | "wrapped": true + | }, + | "items": { + | "type": "string", + | "xml": { + | "name": "photoUrl" + | } + | } + | }, + | "tags": { + | "type": "array", + | "xml": { + | "wrapped": true + | }, + | "items": { + | "xml": { + | "name": "tag" + | }, + | "$ref": "#/definitions/Tag" + | } + | }, + | "status": { + | "type": "string", + | "description": "pet status in the store", + | "enum": [ + | "available", + | "pending", + | "sold" + | ] + | } + | }, + | "xml": { + | "name": "Pet" + | } + | }, + | "Tag": { + | "type": "object", + | "properties": { + | "id": { + | "type": "integer", + | "format": "int64" + | }, + | "name": { + | "type": "string" + | } + | }, + | "xml": { + | "name": "Tag" + | } + | }, + | "Order": { + | "type": "object", + | "properties": { + | "id": { + | "type": "integer", + | "format": "int64" + | }, + | "petId": { + | "type": "integer", + | "format": "int64" + | }, + | "quantity": { + | "type": "integer", + | "format": "int32" + | }, + | "shipDate": { + | "type": "string", + | "format": "date-time" + | }, + | "status": { + | "type": "string", + | "description": "Order Status", + | "enum": [ + | "placed", + | "approved", + | "delivered" + | ] + | }, + | "complete": { + | "type": "boolean" + | } + | }, + | "xml": { + | "name": "Order" + | } + | }, + | "User": { + | "type": "object", + | "properties": { + | "id": { + | "type": "integer", + | "format": "int64" + | }, + | "username": { + | "type": "string" + | }, + | "firstName": { + | "type": "string" + | }, + | "lastName": { + | "type": "string" + | }, + | "email": { + | "type": "string" + | }, + | "password": { + | "type": "string" + | }, + | "phone": { + | "type": "string" + | }, + | "userStatus": { + | "type": "integer", + | "format": "int32", + | "description": "User Status" + | } + | }, + | "xml": { + | "name": "User" + | } + | } + | }, + | "externalDocs": { + | "description": "Find out more about Swagger", + | "url": "http://swagger.io" + | } + |}""".stripMargin + val bankId = Some("gh.29.uk") + + val expectedJsonString = """{ + | "swagger": "2.0", + | "info": { + | "description": "This is a sample server Petstore server. You can find out more about Swagger at [http://swagger.io](http://swagger.io) or on [irc.freenode.net, #swagger](http://swagger.io/irc/). For this sample, you can use the api key `special-key` to test the authorization filters.", + | "version": "1.0.5", + | "title": "Swagger Petstore", + | "termsOfService": "http://swagger.io/terms/", + | "contact": { + | "email": "apiteam@swagger.io" + | }, + | "license": { + | "name": "Apache 2.0", + | "url": "http://www.apache.org/licenses/LICENSE-2.0.html" + | } + | }, + | "host": "obp_mock", + | "host": "DynamicEntity", + | + | "basePath": "/v2", + | "tags": [ + | { + | "name": "pet", + | "description": "Everything about your Pets", + | "externalDocs": { + | "description": "Find out more", + | "url": "http://swagger.io" + | } + | }, + | { + | "name": "store", + | "description": "Access to Petstore orders" + | }, + | { + | "name": "user", + | "description": "Operations about user", + | "externalDocs": { + | "description": "Find out more about our store", + | "url": "http://swagger.io" + | } + | } + | ], + | "schemes": [ + | "https", + | "http" + | ], + | "paths": { + | "/banks/gh.29.uk/pet/{petId}/uploadImage": { + | "post": { + | "tags": [ + | "pet" + | ], + | "summary": "uploads an image", + | "description": "", + | "operationId": "uploadFile", + | "consumes": [ + | "multipart/form-data" + | ], + | "produces": [ + | "application/json" + | ], + | "parameters": [ + | { + | "name": "petId", + | "in": "path", + | "description": "ID of pet to update", + | "required": true, + | "type": "integer", + | "format": "int64" + | }, + | { + | "name": "additionalMetadata", + | "in": "formData", + | "description": "Additional data to pass to server", + | "required": false, + | "type": "string" + | }, + | { + | "name": "file", + | "in": "formData", + | "description": "file to upload", + | "required": false, + | "type": "file" + | } + | ], + | "responses": { + | "200": { + | "description": "successful operation", + | "schema": { + | "$ref": "#/definitions/ApiResponse" + | } + | } + | }, + | "security": [ + | { + | "petstore_auth": [ + | "write:pets", + | "read:pets" + | ] + | } + | ] + | } + | }, + | "/banks/gh.29.uk/pet": { + | "post": { + | "tags": [ + | "pet" + | ], + | "summary": "Add a new pet to the store", + | "description": "", + | "operationId": "addPet", + | "consumes": [ + | "application/json", + | "application/xml" + | ], + | "produces": [ + | "application/json", + | "application/xml" + | ], + | "parameters": [ + | { + | "in": "body", + | "name": "body", + | "description": "Pet object that needs to be added to the store", + | "required": true, + | "schema": { + | "$ref": "#/definitions/Pet" + | } + | } + | ], + | "responses": { + | "405": { + | "description": "Invalid input" + | } + | }, + | "security": [ + | { + | "petstore_auth": [ + | "write:pets", + | "read:pets" + | ] + | } + | ] + | }, + | "put": { + | "tags": [ + | "pet" + | ], + | "summary": "Update an existing pet", + | "description": "", + | "operationId": "updatePet", + | "consumes": [ + | "application/json", + | "application/xml" + | ], + | "produces": [ + | "application/json", + | "application/xml" + | ], + | "parameters": [ + | { + | "in": "body", + | "name": "body", + | "description": "Pet object that needs to be added to the store", + | "required": true, + | "schema": { + | "$ref": "#/definitions/Pet" + | } + | } + | ], + | "responses": { + | "400": { + | "description": "Invalid ID supplied" + | }, + | "404": { + | "description": "Pet not found" + | }, + | "405": { + | "description": "Validation exception" + | } + | }, + | "security": [ + | { + | "petstore_auth": [ + | "write:pets", + | "read:pets" + | ] + | } + | ] + | } + | }, + | "/banks/gh.29.uk/pet/findByStatus": { + | "get": { + | "tags": [ + | "pet" + | ], + | "summary": "Finds Pets by status", + | "description": "Multiple status values can be provided with comma separated strings", + | "operationId": "findPetsByStatus", + | "produces": [ + | "application/json", + | "application/xml" + | ], + | "parameters": [ + | { + | "name": "status", + | "in": "query", + | "description": "Status values that need to be considered for filter", + | "required": true, + | "type": "array", + | "items": { + | "type": "string", + | "enum": [ + | "available", + | "pending", + | "sold" + | ], + | "default": "available" + | }, + | "collectionFormat": "multi" + | } + | ], + | "responses": { + | "200": { + | "description": "successful operation", + | "schema": { + | "type": "array", + | "items": { + | "$ref": "#/definitions/Pet" + | } + | } + | }, + | "400": { + | "description": "Invalid status value" + | } + | }, + | "security": [ + | { + | "petstore_auth": [ + | "write:pets", + | "read:pets" + | ] + | } + | ] + | } + | }, + | "/banks/gh.29.uk/pet/findByTags": { + | "get": { + | "tags": [ + | "pet" + | ], + | "summary": "Finds Pets by tags", + | "description": "Multiple tags can be provided with comma separated strings. Use tag1, tag2, tag3 for testing.", + | "operationId": "findPetsByTags", + | "produces": [ + | "application/json", + | "application/xml" + | ], + | "parameters": [ + | { + | "name": "tags", + | "in": "query", + | "description": "Tags to filter by", + | "required": true, + | "type": "array", + | "items": { + | "type": "string" + | }, + | "collectionFormat": "multi" + | } + | ], + | "responses": { + | "200": { + | "description": "successful operation", + | "schema": { + | "type": "array", + | "items": { + | "$ref": "#/definitions/Pet" + | } + | } + | }, + | "400": { + | "description": "Invalid tag value" + | } + | }, + | "security": [ + | { + | "petstore_auth": [ + | "write:pets", + | "read:pets" + | ] + | } + | ], + | "deprecated": true + | } + | }, + | "/banks/gh.29.uk/pet/{petId}": { + | "get": { + | "tags": [ + | "pet" + | ], + | "summary": "Find pet by ID", + | "description": "Returns a single pet", + | "operationId": "getPetById", + | "produces": [ + | "application/json", + | "application/xml" + | ], + | "parameters": [ + | { + | "name": "petId", + | "in": "path", + | "description": "ID of pet to return", + | "required": true, + | "type": "integer", + | "format": "int64" + | } + | ], + | "responses": { + | "200": { + | "description": "successful operation", + | "schema": { + | "$ref": "#/definitions/Pet" + | } + | }, + | "400": { + | "description": "Invalid ID supplied" + | }, + | "404": { + | "description": "Pet not found" + | } + | }, + | "security": [ + | { + | "api_key": [] + | } + | ] + | }, + | "post": { + | "tags": [ + | "pet" + | ], + | "summary": "Updates a pet in the store with form data", + | "description": "", + | "operationId": "updatePetWithForm", + | "consumes": [ + | "application/x-www-form-urlencoded" + | ], + | "produces": [ + | "application/json", + | "application/xml" + | ], + | "parameters": [ + | { + | "name": "petId", + | "in": "path", + | "description": "ID of pet that needs to be updated", + | "required": true, + | "type": "integer", + | "format": "int64" + | }, + | { + | "name": "name", + | "in": "formData", + | "description": "Updated name of the pet", + | "required": false, + | "type": "string" + | }, + | { + | "name": "status", + | "in": "formData", + | "description": "Updated status of the pet", + | "required": false, + | "type": "string" + | } + | ], + | "responses": { + | "405": { + | "description": "Invalid input" + | } + | }, + | "security": [ + | { + | "petstore_auth": [ + | "write:pets", + | "read:pets" + | ] + | } + | ] + | }, + | "delete": { + | "tags": [ + | "pet" + | ], + | "summary": "Deletes a pet", + | "description": "", + | "operationId": "deletePet", + | "produces": [ + | "application/json", + | "application/xml" + | ], + | "parameters": [ + | { + | "name": "api_key", + | "in": "header", + | "required": false, + | "type": "string" + | }, + | { + | "name": "petId", + | "in": "path", + | "description": "Pet id to delete", + | "required": true, + | "type": "integer", + | "format": "int64" + | } + | ], + | "responses": { + | "400": { + | "description": "Invalid ID supplied" + | }, + | "404": { + | "description": "Pet not found" + | } + | }, + | "security": [ + | { + | "petstore_auth": [ + | "write:pets", + | "read:pets" + | ] + | } + | ] + | } + | }, + | "/banks/gh.29.uk/store/order": { + | "post": { + | "tags": [ + | "store" + | ], + | "summary": "Place an order for a pet", + | "description": "", + | "operationId": "placeOrder", + | "consumes": [ + | "application/json" + | ], + | "produces": [ + | "application/json", + | "application/xml" + | ], + | "parameters": [ + | { + | "in": "body", + | "name": "body", + | "description": "order placed for purchasing the pet", + | "required": true, + | "schema": { + | "$ref": "#/definitions/Order" + | } + | } + | ], + | "responses": { + | "200": { + | "description": "successful operation", + | "schema": { + | "$ref": "#/definitions/Order" + | } + | }, + | "400": { + | "description": "Invalid Order" + | } + | } + | } + | }, + | "/banks/gh.29.uk/store/order/{orderId}": { + | "get": { + | "tags": [ + | "store" + | ], + | "summary": "Find purchase order by ID", + | "description": "For valid response try integer IDs with value >= 1 and <= 10. Other values will generated exceptions", + | "operationId": "getOrderById", + | "produces": [ + | "application/json", + | "application/xml" + | ], + | "parameters": [ + | { + | "name": "orderId", + | "in": "path", + | "description": "ID of pet that needs to be fetched", + | "required": true, + | "type": "integer", + | "maximum": 10, + | "minimum": 1, + | "format": "int64" + | } + | ], + | "responses": { + | "200": { + | "description": "successful operation", + | "schema": { + | "$ref": "#/definitions/Order" + | } + | }, + | "400": { + | "description": "Invalid ID supplied" + | }, + | "404": { + | "description": "Order not found" + | } + | } + | }, + | "delete": { + | "tags": [ + | "store" + | ], + | "summary": "Delete purchase order by ID", + | "description": "For valid response try integer IDs with positive integer value. Negative or non-integer values will generate API errors", + | "operationId": "deleteOrder", + | "produces": [ + | "application/json", + | "application/xml" + | ], + | "parameters": [ + | { + | "name": "orderId", + | "in": "path", + | "description": "ID of the order that needs to be deleted", + | "required": true, + | "type": "integer", + | "minimum": 1, + | "format": "int64" + | } + | ], + | "responses": { + | "400": { + | "description": "Invalid ID supplied" + | }, + | "404": { + | "description": "Order not found" + | } + | } + | } + | }, + | "/banks/gh.29.uk/store/inventory": { + | "get": { + | "tags": [ + | "store" + | ], + | "summary": "Returns pet inventories by status", + | "description": "Returns a map of status codes to quantities", + | "operationId": "getInventory", + | "produces": [ + | "application/json" + | ], + | "parameters": [], + | "responses": { + | "200": { + | "description": "successful operation", + | "schema": { + | "type": "object", + | "additionalProperties": { + | "type": "integer", + | "format": "int32" + | } + | } + | } + | }, + | "security": [ + | { + | "api_key": [] + | } + | ] + | } + | }, + | "/banks/gh.29.uk/user/createWithArray": { + | "post": { + | "tags": [ + | "user" + | ], + | "summary": "Creates list of users with given input array", + | "description": "", + | "operationId": "createUsersWithArrayInput", + | "consumes": [ + | "application/json" + | ], + | "produces": [ + | "application/json", + | "application/xml" + | ], + | "parameters": [ + | { + | "in": "body", + | "name": "body", + | "description": "List of user object", + | "required": true, + | "schema": { + | "type": "array", + | "items": { + | "$ref": "#/definitions/User" + | } + | } + | } + | ], + | "responses": { + | "default": { + | "description": "successful operation" + | } + | } + | } + | }, + | "/banks/gh.29.uk/user/createWithList": { + | "post": { + | "tags": [ + | "user" + | ], + | "summary": "Creates list of users with given input array", + | "description": "", + | "operationId": "createUsersWithListInput", + | "consumes": [ + | "application/json" + | ], + | "produces": [ + | "application/json", + | "application/xml" + | ], + | "parameters": [ + | { + | "in": "body", + | "name": "body", + | "description": "List of user object", + | "required": true, + | "schema": { + | "type": "array", + | "items": { + | "$ref": "#/definitions/User" + | } + | } + | } + | ], + | "responses": { + | "default": { + | "description": "successful operation" + | } + | } + | } + | }, + | "/banks/gh.29.uk/user/{username}": { + | "get": { + | "tags": [ + | "user" + | ], + | "summary": "Get user by user name", + | "description": "", + | "operationId": "getUserByName", + | "produces": [ + | "application/json", + | "application/xml" + | ], + | "parameters": [ + | { + | "name": "username", + | "in": "path", + | "description": "The name that needs to be fetched. Use user1 for testing. ", + | "required": true, + | "type": "string" + | } + | ], + | "responses": { + | "200": { + | "description": "successful operation", + | "schema": { + | "$ref": "#/definitions/User" + | } + | }, + | "400": { + | "description": "Invalid username supplied" + | }, + | "404": { + | "description": "User not found" + | } + | } + | }, + | "put": { + | "tags": [ + | "user" + | ], + | "summary": "Updated user", + | "description": "This can only be done by the logged in user.", + | "operationId": "updateUser", + | "consumes": [ + | "application/json" + | ], + | "produces": [ + | "application/json", + | "application/xml" + | ], + | "parameters": [ + | { + | "name": "username", + | "in": "path", + | "description": "name that need to be updated", + | "required": true, + | "type": "string" + | }, + | { + | "in": "body", + | "name": "body", + | "description": "Updated user object", + | "required": true, + | "schema": { + | "$ref": "#/definitions/User" + | } + | } + | ], + | "responses": { + | "400": { + | "description": "Invalid user supplied" + | }, + | "404": { + | "description": "User not found" + | } + | } + | }, + | "delete": { + | "tags": [ + | "user" + | ], + | "summary": "Delete user", + | "description": "This can only be done by the logged in user.", + | "operationId": "deleteUser", + | "produces": [ + | "application/json", + | "application/xml" + | ], + | "parameters": [ + | { + | "name": "username", + | "in": "path", + | "description": "The name that needs to be deleted", + | "required": true, + | "type": "string" + | } + | ], + | "responses": { + | "400": { + | "description": "Invalid username supplied" + | }, + | "404": { + | "description": "User not found" + | } + | } + | } + | }, + | "/banks/gh.29.uk/user/login": { + | "get": { + | "tags": [ + | "user" + | ], + | "summary": "Logs user into the system", + | "description": "", + | "operationId": "loginUser", + | "produces": [ + | "application/json", + | "application/xml" + | ], + | "parameters": [ + | { + | "name": "username", + | "in": "query", + | "description": "The user name for login", + | "required": true, + | "type": "string" + | }, + | { + | "name": "password", + | "in": "query", + | "description": "The password for login in clear text", + | "required": true, + | "type": "string" + | } + | ], + | "responses": { + | "200": { + | "description": "successful operation", + | "headers": { + | "X-Expires-After": { + | "type": "string", + | "format": "date-time", + | "description": "date in UTC when token expires" + | }, + | "X-Rate-Limit": { + | "type": "integer", + | "format": "int32", + | "description": "calls per hour allowed by the user" + | } + | }, + | "schema": { + | "type": "string" + | } + | }, + | "400": { + | "description": "Invalid username/password supplied" + | } + | } + | } + | }, + | "/banks/gh.29.uk/user/logout": { + | "get": { + | "tags": [ + | "user" + | ], + | "summary": "Logs out current logged in user session", + | "description": "", + | "operationId": "logoutUser", + | "produces": [ + | "application/json", + | "application/xml" + | ], + | "parameters": [], + | "responses": { + | "default": { + | "description": "successful operation" + | } + | } + | } + | }, + | "/banks/gh.29.uk/user": { + | "post": { + | "tags": [ + | "user" + | ], + | "summary": "Create user", + | "description": "This can only be done by the logged in user.", + | "operationId": "createUser", + | "consumes": [ + | "application/json" + | ], + | "produces": [ + | "application/json", + | "application/xml" + | ], + | "parameters": [ + | { + | "in": "body", + | "name": "body", + | "description": "Created user object", + | "required": true, + | "schema": { + | "$ref": "#/definitions/User" + | } + | } + | ], + | "responses": { + | "default": { + | "description": "successful operation" + | } + | } + | } + | } + | }, + | "securityDefinitions": { + | "api_key": { + | "type": "apiKey", + | "name": "api_key", + | "in": "header" + | }, + | "petstore_auth": { + | "type": "oauth2", + | "authorizationUrl": "https://petstore.swagger.io/oauth/authorize", + | "flow": "implicit", + | "scopes": { + | "read:pets": "read your pets", + | "write:pets": "modify pets in your account" + | } + | } + | }, + | "definitions": { + | "ApiResponse": { + | "type": "object", + | "properties": { + | "code": { + | "type": "integer", + | "format": "int32" + | }, + | "type": { + | "type": "string" + | }, + | "message": { + | "type": "string" + | } + | } + | }, + | "Category": { + | "type": "object", + | "properties": { + | "id": { + | "type": "integer", + | "format": "int64" + | }, + | "name": { + | "type": "string" + | } + | }, + | "xml": { + | "name": "Category" + | } + | }, + | "Pet": { + | "type": "object", + | "required": [ + | "name", + | "photoUrls" + | ], + | "properties": { + | "id": { + | "type": "integer", + | "format": "int64" + | }, + | "category": { + | "$ref": "#/definitions/Category" + | }, + | "name": { + | "type": "string", + | "example": "doggie" + | }, + | "photoUrls": { + | "type": "array", + | "xml": { + | "wrapped": true + | }, + | "items": { + | "type": "string", + | "xml": { + | "name": "photoUrl" + | } + | } + | }, + | "tags": { + | "type": "array", + | "xml": { + | "wrapped": true + | }, + | "items": { + | "xml": { + | "name": "tag" + | }, + | "$ref": "#/definitions/Tag" + | } + | }, + | "status": { + | "type": "string", + | "description": "pet status in the store", + | "enum": [ + | "available", + | "pending", + | "sold" + | ] + | } + | }, + | "xml": { + | "name": "Pet" + | } + | }, + | "Tag": { + | "type": "object", + | "properties": { + | "id": { + | "type": "integer", + | "format": "int64" + | }, + | "name": { + | "type": "string" + | } + | }, + | "xml": { + | "name": "Tag" + | } + | }, + | "Order": { + | "type": "object", + | "properties": { + | "id": { + | "type": "integer", + | "format": "int64" + | }, + | "petId": { + | "type": "integer", + | "format": "int64" + | }, + | "quantity": { + | "type": "integer", + | "format": "int32" + | }, + | "shipDate": { + | "type": "string", + | "format": "date-time" + | }, + | "status": { + | "type": "string", + | "description": "Order Status", + | "enum": [ + | "placed", + | "approved", + | "delivered" + | ] + | }, + | "complete": { + | "type": "boolean" + | } + | }, + | "xml": { + | "name": "Order" + | } + | }, + | "User": { + | "type": "object", + | "properties": { + | "id": { + | "type": "integer", + | "format": "int64" + | }, + | "username": { + | "type": "string" + | }, + | "firstName": { + | "type": "string" + | }, + | "lastName": { + | "type": "string" + | }, + | "email": { + | "type": "string" + | }, + | "password": { + | "type": "string" + | }, + | "phone": { + | "type": "string" + | }, + | "userStatus": { + | "type": "integer", + | "format": "int32", + | "description": "User Status" + | } + | }, + | "xml": { + | "name": "User" + | } + | } + | }, + | "externalDocs": { + | "description": "Find out more about Swagger", + | "url": "http://swagger.io" + | } + |}""".stripMargin + + val expectedJson = json.parse(expectedJsonString) + + val result= DynamicEndpointHelper.addedBankToPath(dataJsonString, bankId) + + expectedJson should equal(result) + } + + } From 7c4cc53ccaabefb35ffe023b0a986d07b4a8a26e Mon Sep 17 00:00:00 2001 From: hongwei Date: Tue, 8 Jun 2021 14:52:21 +0200 Subject: [PATCH 028/147] feature/OBPv400 added bankId for create bank level dynamicEndpoint --- .../main/scala/code/api/util/ApiRole.scala | 15 ++ .../src/main/scala/code/api/util/ApiTag.scala | 1 + .../scala/code/api/v4_0_0/APIMethods400.scala | 211 ++++++++++++++---- 3 files changed, 187 insertions(+), 40 deletions(-) diff --git a/obp-api/src/main/scala/code/api/util/ApiRole.scala b/obp-api/src/main/scala/code/api/util/ApiRole.scala index 06ae4c8dc..324ba160c 100644 --- a/obp-api/src/main/scala/code/api/util/ApiRole.scala +++ b/obp-api/src/main/scala/code/api/util/ApiRole.scala @@ -480,14 +480,29 @@ object ApiRole { case class CanGetDynamicEndpoints(requiresBankId: Boolean = false) extends ApiRole lazy val canGetDynamicEndpoints = CanGetDynamicEndpoints() + case class CanGetBankLevelDynamicEndpoint(requiresBankId: Boolean = true) extends ApiRole + lazy val canGetBankLevelDynamicEndpoint = CanGetBankLevelDynamicEndpoint() + + case class CanGetBankLevelDynamicEndpoints(requiresBankId: Boolean = true) extends ApiRole + lazy val canGetBankLevelDynamicEndpoints = CanGetBankLevelDynamicEndpoints() + case class CanCreateDynamicEndpoint(requiresBankId: Boolean = false) extends ApiRole lazy val canCreateDynamicEndpoint = CanCreateDynamicEndpoint() + case class CanCreateBankLevelDynamicEndpoint(requiresBankId: Boolean = true) extends ApiRole + lazy val canCreateBankLevelDynamicEndpoint = CanCreateBankLevelDynamicEndpoint() + case class CanUpdateDynamicEndpoint(requiresBankId: Boolean = false) extends ApiRole lazy val canUpdateDynamicEndpoint = CanUpdateDynamicEndpoint() + case class CanUpdateBankLevelDynamicEndpoint(requiresBankId: Boolean = true) extends ApiRole + lazy val canUpdateBankLevelDynamicEndpoint = CanUpdateBankLevelDynamicEndpoint() + case class CanDeleteDynamicEndpoint(requiresBankId: Boolean = false) extends ApiRole lazy val canDeleteDynamicEndpoint = CanDeleteDynamicEndpoint() + + case class CanDeleteBankLevelDynamicEndpoint(requiresBankId: Boolean = true) extends ApiRole + lazy val canDeleteBankLevelDynamicEndpoint = CanDeleteBankLevelDynamicEndpoint() case class CanCreateResetPasswordUrl(requiresBankId: Boolean = false) extends ApiRole lazy val canCreateResetPasswordUrl = CanCreateResetPasswordUrl() diff --git a/obp-api/src/main/scala/code/api/util/ApiTag.scala b/obp-api/src/main/scala/code/api/util/ApiTag.scala index 4bbe50307..7f874c5b4 100644 --- a/obp-api/src/main/scala/code/api/util/ApiTag.scala +++ b/obp-api/src/main/scala/code/api/util/ApiTag.scala @@ -71,6 +71,7 @@ object ApiTag { val apiTagMethodRouting = ResourceDocTag("Method-Routing") val apiTagEndpointMapping = ResourceDocTag("Endpoint-Mapping") val apiTagWebUiProps = ResourceDocTag("WebUi-Props") + val apiTagManageDynamicEndpoint = ResourceDocTag("Dynamic-Endpoint-Manage") val apiTagManageDynamicEntity = ResourceDocTag("Dynamic-Entity-Manage") val apiTagDynamicResourceDoc = ResourceDocTag("Dynamic-Resource-Doc-Manage") val apiTagDynamicMessageDoc = ResourceDocTag("Dynamic-Message-Doc-Manage") 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 164b2eb3f..16d252b65 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 @@ -4633,7 +4633,7 @@ trait APIMethods400 { InvalidJsonFormat, UnknownError ), - List(apiTagDynamicEndpoint, apiTagApi, apiTagNewStyle), + List(apiTagManageDynamicEndpoint, apiTagApi, apiTagNewStyle), Some(List(canCreateDynamicEndpoint))) lazy val createDynamicEndpoint: OBPEndpoint = { @@ -4670,8 +4670,8 @@ trait APIMethods400 { InvalidJsonFormat, UnknownError ), - List(apiTagDynamicEndpoint, apiTagApi, apiTagNewStyle), - Some(List(canCreateDynamicEndpoint))) + List(apiTagManageDynamicEndpoint, apiTagApi, apiTagNewStyle), + Some(List(canCreateBankLevelDynamicEndpoint))) lazy val createBankLevelDynamicEndpoint: OBPEndpoint = { case "management" :: "banks" :: bankId ::"dynamic-endpoints" :: Nil JsonPost json -> _ => { @@ -4699,22 +4699,55 @@ trait APIMethods400 { InvalidJsonFormat, UnknownError ), - List(apiTagDynamicEndpoint, apiTagApi, apiTagNewStyle), + List(apiTagManageDynamicEndpoint, apiTagApi, apiTagNewStyle), Some(List(canUpdateDynamicEndpoint))) lazy val updateDynamicEndpointHost: OBPEndpoint = { case "management" :: "dynamic-endpoints" :: dynamicEndpointId :: "host" :: Nil JsonPut json -> _ => { cc => - for { - (_, callContext) <- NewStyle.function.getDynamicEndpoint(dynamicEndpointId, cc.callContext) - failMsg = s"$InvalidJsonFormat The Json body should be the $DynamicEndpointHostJson400" - postedData <- NewStyle.function.tryons(failMsg, 400, callContext) { - json.extract[DynamicEndpointHostJson400] - } - (dynamicEndpoint, callContext) <- NewStyle.function.updateDynamicEndpointHost(dynamicEndpointId, postedData.host, cc.callContext) - } yield { - (postedData, HttpCode.`201`(callContext)) - } + updateDynamicEndpointHostMethod(dynamicEndpointId, json, cc) + } + } + + private def updateDynamicEndpointHostMethod(dynamicEndpointId: String, json: JValue, cc: CallContext) = { + for { + (_, callContext) <- NewStyle.function.getDynamicEndpoint(dynamicEndpointId, cc.callContext) + failMsg = s"$InvalidJsonFormat The Json body should be the $DynamicEndpointHostJson400" + postedData <- NewStyle.function.tryons(failMsg, 400, callContext) { + json.extract[DynamicEndpointHostJson400] + } + (dynamicEndpoint, callContext) <- NewStyle.function.updateDynamicEndpointHost(dynamicEndpointId, postedData.host, cc.callContext) + } yield { + (postedData, HttpCode.`201`(callContext)) + } + } + + staticResourceDocs += ResourceDoc( + updateBankLevelDynamicEndpointHost, + implementedInApiVersion, + nameOf(updateBankLevelDynamicEndpointHost), + "PUT", + "/management/banks/BANK_ID/dynamic-endpoints/DYNAMIC_ENDPOINT_ID/host", + " Update Bank Level Dynamic Endpoint Host", + s"""Update Bank Level dynamic endpoint Host. + |The value can be obp_mock, dynamic_entity, or some service url. + |""", + dynamicEndpointHostJson400, + dynamicEndpointHostJson400, + List( + $UserNotLoggedIn, + UserHasMissingRoles, + DynamicEntityNotFoundByDynamicEntityId, + InvalidJsonFormat, + UnknownError + ), + List(apiTagManageDynamicEndpoint, apiTagApi, apiTagNewStyle), + Some(List(canUpdateDynamicEndpoint))) + + lazy val updateBankLevelDynamicEndpointHost: OBPEndpoint = { + case "management" :: "banks" :: BankId(bankId) :: "dynamic-endpoints" :: dynamicEndpointId :: "host" :: Nil JsonPut json -> _ => { + cc => + updateDynamicEndpointHostMethod(dynamicEndpointId, json, cc) } } @@ -4740,19 +4773,13 @@ trait APIMethods400 { InvalidJsonFormat, UnknownError ), - List(apiTagDynamicEndpoint, apiTagApi, apiTagNewStyle), + List(apiTagManageDynamicEndpoint, apiTagApi, apiTagNewStyle), Some(List(canGetDynamicEndpoint))) lazy val getDynamicEndpoint: OBPEndpoint = { case "management" :: "dynamic-endpoints" :: dynamicEndpointId :: Nil JsonGet req => { cc => - for { - (dynamicEndpoint, callContext) <- NewStyle.function.getDynamicEndpoint(dynamicEndpointId, cc.callContext) - } yield { - val swaggerJson = parse(dynamicEndpoint.swaggerString) - val responseJson: JObject = ("user_id", cc.userId) ~ ("dynamic_endpoint_id", dynamicEndpoint.dynamicEndpointId) ~ ("swagger_string", swaggerJson) - (responseJson, HttpCode.`200`(callContext)) - } + getDynamicEndpointMethod(dynamicEndpointId, cc) } } @@ -4779,21 +4806,104 @@ trait APIMethods400 { InvalidJsonFormat, UnknownError ), - List(apiTagDynamicEndpoint, apiTagApi, apiTagNewStyle), + List(apiTagManageDynamicEndpoint, apiTagApi, apiTagNewStyle), Some(List(canGetDynamicEndpoints))) lazy val getDynamicEndpoints: OBPEndpoint = { case "management" :: "dynamic-endpoints" :: Nil JsonGet _ => { cc => - for { - (dynamicEndpoints, _) <- NewStyle.function.getDynamicEndpoints(cc.callContext) - } yield { - val resultList = dynamicEndpoints.map[JObject, List[JObject]] { dynamicEndpoint=> - val swaggerJson = parse(dynamicEndpoint.swaggerString) - ("user_id", cc.userId) ~ ("dynamic_endpoint_id", dynamicEndpoint.dynamicEndpointId) ~ ("swagger_string", swaggerJson) - } - (ListResult("dynamic_endpoints", resultList), HttpCode.`200`(cc.callContext)) - } + getDynamicEndpointsMethod(cc) + } + } + + private def getDynamicEndpointsMethod(cc: CallContext) = { + for { + (dynamicEndpoints, _) <- NewStyle.function.getDynamicEndpoints(cc.callContext) + } yield { + val resultList = dynamicEndpoints.map[JObject, List[JObject]] { dynamicEndpoint => + val swaggerJson = parse(dynamicEndpoint.swaggerString) + ("user_id", cc.userId) ~ ("dynamic_endpoint_id", dynamicEndpoint.dynamicEndpointId) ~ ("swagger_string", swaggerJson) + } + (ListResult("dynamic_endpoints", resultList), HttpCode.`200`(cc.callContext)) + } + } + + staticResourceDocs += ResourceDoc( + getBankLevelDynamicEndpoint, + implementedInApiVersion, + nameOf(getBankLevelDynamicEndpoint), + "GET", + "/management/banks/BANK_ID/dynamic-endpoints/DYNAMIC_ENDPOINT_ID", + " Get Bank Level Dynamic Endpoint", + s"""Get a Bank Level Dynamic Endpoint. + |""", + EmptyBody, + dynamicEndpointResponseBodyExample, + List( + $UserNotLoggedIn, + UserHasMissingRoles, + DynamicEndpointNotFoundByDynamicEndpointId, + InvalidJsonFormat, + UnknownError + ), + List(apiTagManageDynamicEndpoint, apiTagApi, apiTagNewStyle), + Some(List(canGetBankLevelDynamicEndpoint))) + + lazy val getBankLevelDynamicEndpoint: OBPEndpoint = { + case "management" :: "banks" :: BankId(bankId) :: "dynamic-endpoints" :: dynamicEndpointId :: Nil JsonGet req => { + cc => + getDynamicEndpointMethod(dynamicEndpointId, cc) + } + } + + private def getDynamicEndpointMethod(dynamicEndpointId: String, cc: CallContext) = { + for { + (dynamicEndpoint, callContext) <- NewStyle.function.getDynamicEndpoint(dynamicEndpointId, cc.callContext) + } yield { + val swaggerJson = parse(dynamicEndpoint.swaggerString) + val responseJson: JObject = ("user_id", cc.userId) ~ ("dynamic_endpoint_id", dynamicEndpoint.dynamicEndpointId) ~ ("swagger_string", swaggerJson) + (responseJson, HttpCode.`200`(callContext)) + } + } + + staticResourceDocs += ResourceDoc( + getBankLevelDynamicEndpoints, + implementedInApiVersion, + nameOf(getBankLevelDynamicEndpoints), + "GET", + "/management/dynamic-endpoints", + "Get Bank Level Dynamic Endpoints", + s""" + | + |Get Bank Level Dynamic Endpoints. + | + |""", + EmptyBody, + ListResult( + "dynamic_endpoints", + List(dynamicEndpointResponseBodyExample) + ), + List( + $UserNotLoggedIn, + UserHasMissingRoles, + InvalidJsonFormat, + UnknownError + ), + List(apiTagManageDynamicEndpoint, apiTagApi, apiTagNewStyle), + Some(List(canGetDynamicEndpoints))) + + lazy val getBankLevelDynamicEndpoints: OBPEndpoint = { + case "management" :: "banks" :: BankId(bankId) :: "dynamic-endpoints" :: Nil JsonGet _ => { + cc => + getDynamicEndpointsMethod(cc) + } + } + + private def deleteDynamicEndpointMethod(dynamicEndpointId: String, cc: CallContext) = { + for { + deleted <- NewStyle.function.deleteDynamicEndpoint(dynamicEndpointId, cc.callContext) + } yield { + (deleted, HttpCode.`204`(cc.callContext)) } } @@ -4812,17 +4922,38 @@ trait APIMethods400 { DynamicEndpointNotFoundByDynamicEndpointId, UnknownError ), - List(apiTagDynamicEndpoint, apiTagApi, apiTagNewStyle), + List(apiTagManageDynamicEndpoint, apiTagApi, apiTagNewStyle), Some(List(canDeleteDynamicEndpoint))) lazy val deleteDynamicEndpoint : OBPEndpoint = { case "management" :: "dynamic-endpoints" :: dynamicEndpointId :: Nil JsonDelete _ => { cc => - for { - deleted <- NewStyle.function.deleteDynamicEndpoint(dynamicEndpointId, cc.callContext) - } yield { - (deleted, HttpCode.`204`(cc.callContext)) - } + deleteDynamicEndpointMethod(dynamicEndpointId, cc) + } + } + + staticResourceDocs += ResourceDoc( + deleteBankLevelDynamicEndpoint, + implementedInApiVersion, + nameOf(deleteBankLevelDynamicEndpoint), + "DELETE", + "/management/banks/BANK_ID/dynamic-endpoints/DYNAMIC_ENDPOINT_ID", + " Delete Bank Level Dynamic Endpoint", + s"""Delete a Bank Level DynamicEndpoint specified by DYNAMIC_ENDPOINT_ID.""".stripMargin, + EmptyBody, + EmptyBody, + List( + $UserNotLoggedIn, + DynamicEndpointNotFoundByDynamicEndpointId, + UnknownError + ), + List(apiTagManageDynamicEndpoint, apiTagApi, apiTagNewStyle), + Some(List(canDeleteBankLevelDynamicEndpoint))) + + lazy val deleteBankLevelDynamicEndpoint : OBPEndpoint = { + case "management" :: "banks" :: bankId :: "dynamic-endpoints" :: dynamicEndpointId :: Nil JsonDelete _ => { + cc => + deleteDynamicEndpointMethod(dynamicEndpointId, cc) } } @@ -4844,7 +4975,7 @@ trait APIMethods400 { InvalidJsonFormat, UnknownError ), - List(apiTagDynamicEndpoint, apiTagApi, apiTagNewStyle) + List(apiTagManageDynamicEndpoint, apiTagApi, apiTagNewStyle) ) lazy val getMyDynamicEndpoints: OBPEndpoint = { @@ -4877,7 +5008,7 @@ trait APIMethods400 { DynamicEndpointNotFoundByDynamicEndpointId, UnknownError ), - List(apiTagDynamicEndpoint, apiTagApi, apiTagNewStyle), + List(apiTagManageDynamicEndpoint, apiTagApi, apiTagNewStyle), ) lazy val deleteMyDynamicEndpoint : OBPEndpoint = { From d62ffe1d628ce609d5a2a1eb1d8606c52b8623c5 Mon Sep 17 00:00:00 2001 From: hongwei Date: Tue, 8 Jun 2021 17:02:57 +0200 Subject: [PATCH 029/147] feature/OBPv400 added bankId for Dynamic Entity Endpoints --- .../SwaggerDefinitionsJSON.scala | 3 +- .../main/scala/code/api/util/ApiRole.scala | 15 + .../src/main/scala/code/api/util/ApiTag.scala | 2 +- .../main/scala/code/api/util/NewStyle.scala | 55 ++-- .../scala/code/api/v4_0_0/APIMethods400.scala | 267 ++++++++++++++---- .../v4_0_0/dynamic/DynamicEntityHelper.scala | 4 +- .../dynamicEntity/DynamicEntityProvider.scala | 12 +- .../MapppedDynamicEntityProvider.scala | 40 ++- .../EndpointMappingProvider.scala | 19 +- .../MappedEndpointMappingProvider.scala | 33 ++- 10 files changed, 327 insertions(+), 123 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 aa00c34b3..f37a99ab8 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 @@ -4158,7 +4158,8 @@ object SwaggerDefinitionsJSON { Some("b4e0352a-9a0f-4bfa-b30b-9003aa467f50"), "OBPv4.0.0-dynamicEndpoint_GET_pet_PET_ID", """{}""".stripMargin, - """{}""".stripMargin + """{}""".stripMargin, + Some(bankIdExample.value) ) val supportedCurrenciesJson = SupportedCurrenciesJson( diff --git a/obp-api/src/main/scala/code/api/util/ApiRole.scala b/obp-api/src/main/scala/code/api/util/ApiRole.scala index 324ba160c..e2cd9bd35 100644 --- a/obp-api/src/main/scala/code/api/util/ApiRole.scala +++ b/obp-api/src/main/scala/code/api/util/ApiRole.scala @@ -737,6 +737,21 @@ object ApiRole { case class CanDeleteEndpointMapping(requiresBankId: Boolean = false) extends ApiRole lazy val canDeleteEndpointMapping = CanDeleteEndpointMapping() + + case class CanCreateBankLevelEndpointMapping(requiresBankId: Boolean = true) extends ApiRole + lazy val canCreateBankLevelEndpointMapping = CanCreateBankLevelEndpointMapping() + + case class CanUpdateBankLevelEndpointMapping(requiresBankId: Boolean = true) extends ApiRole + lazy val canUpdateBankLevelEndpointMapping = CanUpdateBankLevelEndpointMapping() + + case class CanGetBankLevelEndpointMapping(requiresBankId: Boolean = true) extends ApiRole + lazy val canGetBankLevelEndpointMapping = CanGetBankLevelEndpointMapping() + + case class CanGetAllBankLevelEndpointMappings(requiresBankId: Boolean = true) extends ApiRole + lazy val canGetAllBankLevelEndpointMappings = CanGetAllBankLevelEndpointMappings() + + case class CanDeleteBankLevelEndpointMapping(requiresBankId: Boolean = true) extends ApiRole + lazy val canDeleteBankLevelEndpointMapping = CanDeleteBankLevelEndpointMapping() case class CanCreateUserInvitation(requiresBankId: Boolean = true) extends ApiRole lazy val canCreateUserInvitation = CanCreateUserInvitation() diff --git a/obp-api/src/main/scala/code/api/util/ApiTag.scala b/obp-api/src/main/scala/code/api/util/ApiTag.scala index 7f874c5b4..f426ca289 100644 --- a/obp-api/src/main/scala/code/api/util/ApiTag.scala +++ b/obp-api/src/main/scala/code/api/util/ApiTag.scala @@ -69,8 +69,8 @@ object ApiTag { val apiTagMockedData = ResourceDocTag("Mocked-Data") val apiTagConsent = ResourceDocTag("Consent") val apiTagMethodRouting = ResourceDocTag("Method-Routing") - val apiTagEndpointMapping = ResourceDocTag("Endpoint-Mapping") val apiTagWebUiProps = ResourceDocTag("WebUi-Props") + val apiTagEndpointMapping = ResourceDocTag("Endpoint-Mapping-Manage") val apiTagManageDynamicEndpoint = ResourceDocTag("Dynamic-Endpoint-Manage") val apiTagManageDynamicEntity = ResourceDocTag("Dynamic-Entity-Manage") val apiTagDynamicResourceDoc = ResourceDocTag("Dynamic-Resource-Doc-Manage") 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 971ab2d4e..b820d3174 100644 --- a/obp-api/src/main/scala/code/api/util/NewStyle.scala +++ b/obp-api/src/main/scala/code/api/util/NewStyle.scala @@ -2357,28 +2357,28 @@ object NewStyle { } } - def createOrUpdateEndpointMapping(endpointMapping: EndpointMappingT, callContext: Option[CallContext]) = Future { - (EndpointMappingProvider.endpointMappingProvider.vend.createOrUpdate(endpointMapping), callContext) + def createOrUpdateEndpointMapping(bankId: Option[String], endpointMapping: EndpointMappingT, callContext: Option[CallContext]) = Future { + (EndpointMappingProvider.endpointMappingProvider.vend.createOrUpdate(bankId, endpointMapping), callContext) } map { i => (connectorEmptyResponse(i._1, callContext), i._2) } - def deleteEndpointMapping(endpointMappingId: String, callContext: Option[CallContext]) = Future { - (EndpointMappingProvider.endpointMappingProvider.vend.delete(endpointMappingId), callContext) + def deleteEndpointMapping(bankId: Option[String], endpointMappingId: String, callContext: Option[CallContext]) = Future { + (EndpointMappingProvider.endpointMappingProvider.vend.delete(bankId, endpointMappingId), callContext) } map { i => (connectorEmptyResponse(i._1, callContext), i._2) } - def getEndpointMappingById(endpointMappingId : String, callContext: Option[CallContext]): OBPReturnType[EndpointMappingT] = { - val endpointMappingBox: Box[EndpointMappingT] = EndpointMappingProvider.endpointMappingProvider.vend.getById(endpointMappingId) + def getEndpointMappingById(bankId: Option[String], endpointMappingId : String, callContext: Option[CallContext]): OBPReturnType[EndpointMappingT] = { + val endpointMappingBox: Box[EndpointMappingT] = EndpointMappingProvider.endpointMappingProvider.vend.getById(bankId, endpointMappingId) Future{ val endpointMapping = unboxFullOrFail(endpointMappingBox, callContext, s"$EndpointMappingNotFoundByEndpointMappingId Current ENDPOINT_MAPPING_ID is $endpointMappingId", 404) (endpointMapping, callContext) } } - def getEndpointMappingByOperationId(operationId : String, callContext: Option[CallContext]): OBPReturnType[EndpointMappingT] = { - val endpointMappingBox: Box[EndpointMappingT] = EndpointMappingProvider.endpointMappingProvider.vend.getByOperationId(operationId) + def getEndpointMappingByOperationId(bankId: Option[String], operationId : String, callContext: Option[CallContext]): OBPReturnType[EndpointMappingT] = { + val endpointMappingBox: Box[EndpointMappingT] = EndpointMappingProvider.endpointMappingProvider.vend.getByOperationId(bankId, operationId) Future{ val endpointMapping = unboxFullOrFail(endpointMappingBox, callContext, s"$EndpointMappingNotFoundByOperationId Current OPERATION_ID is $operationId",404) (endpointMapping, callContext) @@ -2387,19 +2387,19 @@ object NewStyle { private[this] val endpointMappingTTL = APIUtil.getPropsValue(s"endpointMapping.cache.ttl.seconds", "0").toInt - def getEndpointMappings(callContext: Option[CallContext]): OBPReturnType[List[EndpointMappingT]] = { + def getEndpointMappings(bankId: Option[String], callContext: Option[CallContext]): OBPReturnType[List[EndpointMappingT]] = { import scala.concurrent.duration._ var cacheKey = (randomUUID().toString, randomUUID().toString, randomUUID().toString) CacheKeyFromArguments.buildCacheKey { Caching.memoizeSyncWithProvider(Some(cacheKey.toString()))(endpointMappingTTL second) { - Future{(EndpointMappingProvider.endpointMappingProvider.vend.getAllEndpointMappings(), callContext)} + Future{(EndpointMappingProvider.endpointMappingProvider.vend.getAllEndpointMappings(bankId), callContext)} } } } private def createDynamicEntity(dynamicEntity: DynamicEntityT, callContext: Option[CallContext]): Future[Box[DynamicEntityT]] = { - val existsDynamicEntity = DynamicEntityProvider.connectorMethodProvider.vend.getByEntityName(dynamicEntity.entityName) + val existsDynamicEntity = DynamicEntityProvider.connectorMethodProvider.vend.getByEntityName(dynamicEntity.bankId, dynamicEntity.entityName) if(existsDynamicEntity.isDefined) { val errorMsg = s"$DynamicEntityNameAlreadyExists current entityName is '${dynamicEntity.entityName}'." @@ -2412,7 +2412,7 @@ object NewStyle { } private def updateDynamicEntity(dynamicEntity: DynamicEntityT, dynamicEntityId: String , callContext: Option[CallContext]): Future[Box[DynamicEntityT]] = { - val originEntity = DynamicEntityProvider.connectorMethodProvider.vend.getById(dynamicEntityId) + val originEntity = DynamicEntityProvider.connectorMethodProvider.vend.getById(dynamicEntity.bankId, dynamicEntityId) // if can't find by id, return 404 error val idNotExistsMsg = s"$DynamicEntityNotFoundByDynamicEntityId dynamicEntityId = ${dynamicEntity.dynamicEntityId.get}." @@ -2423,7 +2423,7 @@ object NewStyle { val originEntityName = originEntity.map(_.entityName).orNull // if entityName changed and the new entityName already exists, return error message if(dynamicEntity.entityName != originEntityName) { - val existsDynamicEntity = DynamicEntityProvider.connectorMethodProvider.vend.getByEntityName(dynamicEntity.entityName) + val existsDynamicEntity = DynamicEntityProvider.connectorMethodProvider.vend.getByEntityName(dynamicEntity.bankId, dynamicEntity.entityName) if(existsDynamicEntity.isDefined) { val errorMsg = s"$DynamicEntityNameAlreadyExists current entityName is '${dynamicEntity.entityName}'." @@ -2448,9 +2448,9 @@ object NewStyle { * @param dynamicEntityId * @return */ - def deleteDynamicEntity(dynamicEntityId: String): Future[Box[Boolean]] = Future { + def deleteDynamicEntity(bankId: Option[String], dynamicEntityId: String): Future[Box[Boolean]] = Future { for { - entity <- DynamicEntityProvider.connectorMethodProvider.vend.getById(dynamicEntityId) + entity <- DynamicEntityProvider.connectorMethodProvider.vend.getById(bankId, dynamicEntityId) deleteEntityResult <- DynamicEntityProvider.connectorMethodProvider.vend.delete(entity) deleteEntitleMentResult <- if(deleteEntityResult) { Entitlement.entitlement.vend.deleteDynamicEntityEntitlement(entity.entityName, entity.bankId) @@ -2465,16 +2465,16 @@ object NewStyle { } } - def getDynamicEntityById(dynamicEntityId : String, callContext: Option[CallContext]): OBPReturnType[DynamicEntityT] = { - val dynamicEntityBox: Box[DynamicEntityT] = DynamicEntityProvider.connectorMethodProvider.vend.getById(dynamicEntityId) + def getDynamicEntityById(bankId: Option[String], dynamicEntityId : String, callContext: Option[CallContext]): OBPReturnType[DynamicEntityT] = { + val dynamicEntityBox: Box[DynamicEntityT] = DynamicEntityProvider.connectorMethodProvider.vend.getById(bankId, dynamicEntityId) val dynamicEntity = unboxFullOrFail(dynamicEntityBox, callContext, DynamicEntityNotFoundByDynamicEntityId, 404) Future{ (dynamicEntity, callContext) } } - def getDynamicEntityByEntityName(entityName : String, callContext: Option[CallContext]): OBPReturnType[Box[DynamicEntityT]] = Future { - val boxedDynamicEntity = DynamicEntityProvider.connectorMethodProvider.vend.getByEntityName(entityName) + def getDynamicEntityByEntityName(bankId: Option[String], entityName : String, callContext: Option[CallContext]): OBPReturnType[Box[DynamicEntityT]] = Future { + val boxedDynamicEntity = DynamicEntityProvider.connectorMethodProvider.vend.getByEntityName(bankId, entityName) (boxedDynamicEntity, callContext) } @@ -2483,28 +2483,17 @@ object NewStyle { else APIUtil.getPropsValue(s"dynamicEntity.cache.ttl.seconds", "30").toInt } - def getDynamicEntities(): List[DynamicEntityT] = { + def getDynamicEntities(bankId: Option[String]): List[DynamicEntityT] = { import scala.concurrent.duration._ var cacheKey = (randomUUID().toString, randomUUID().toString, randomUUID().toString) CacheKeyFromArguments.buildCacheKey { Caching.memoizeSyncWithProvider(Some(cacheKey.toString()))(dynamicEntityTTL second) { - DynamicEntityProvider.connectorMethodProvider.vend.getDynamicEntities() + DynamicEntityProvider.connectorMethodProvider.vend.getDynamicEntities(bankId) } } } - def getDynamicEntitiesByBankId(bankId: String): List[DynamicEntityT] = { - import scala.concurrent.duration._ - - var cacheKey = (randomUUID().toString, randomUUID().toString, randomUUID().toString) - CacheKeyFromArguments.buildCacheKey { - Caching.memoizeSyncWithProvider(Some(cacheKey.toString()))(dynamicEntityTTL second) { - DynamicEntityProvider.connectorMethodProvider.vend.getDynamicEntitiesByBankId(bankId) - } - } - } - def getDynamicEntitiesByUserId(userId: String): List[DynamicEntityT] = { import scala.concurrent.duration._ @@ -2551,7 +2540,7 @@ object NewStyle { queryParameters: Option[Map[String, List[String]]], callContext: Option[CallContext]): OBPReturnType[Box[JValue]] = { import DynamicEntityOperation._ - val dynamicEntityBox = DynamicEntityProvider.connectorMethodProvider.vend.getByEntityName(entityName) + val dynamicEntityBox = DynamicEntityProvider.connectorMethodProvider.vend.getByEntityName(bankId, entityName) // do validate, any validate process fail will return immediately if(dynamicEntityBox.isEmpty) { return Helper.booleanToFuture(s"$DynamicEntityNotExists entity's name is '$entityName'", cc=callContext)(false) 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 16d252b65..09bd0f755 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 @@ -1694,7 +1694,7 @@ trait APIMethods400 { case "management" :: "dynamic-entities" :: Nil JsonGet req => { cc => for { - dynamicEntities <- Future(NewStyle.function.getDynamicEntities()) + dynamicEntities <- Future(NewStyle.function.getDynamicEntities(None)) } yield { val listCommons: List[DynamicEntityCommons] = dynamicEntities val jObjects = listCommons.map(_.jValue) @@ -1729,7 +1729,7 @@ trait APIMethods400 { case "management" :: "banks" :: bankId :: "dynamic-entities" :: Nil JsonGet req => { cc => for { - dynamicEntities <- Future(NewStyle.function.getDynamicEntitiesByBankId(bankId)) + dynamicEntities <- Future(NewStyle.function.getDynamicEntities(Some(bankId))) } yield { val listCommons: List[DynamicEntityCommons] = dynamicEntities val jObjects = listCommons.map(_.jValue) @@ -1851,7 +1851,7 @@ trait APIMethods400 { private def updateDynamicEntityMethod(bankId: Option[String], dynamicEntityId: String, json: JValue, cc: CallContext) = { for { // Check whether there are uploaded data, only if no uploaded data allow to update DynamicEntity. - (entity, _) <- NewStyle.function.getDynamicEntityById(dynamicEntityId, cc.callContext) + (entity, _) <- NewStyle.function.getDynamicEntityById(bankId, dynamicEntityId, cc.callContext) (box, _) <- NewStyle.function.invokeDynamicConnector(GET_ALL, entity.entityName, None, None, entity.bankId, None, cc.callContext) resultList: JArray = unboxResult(box.asInstanceOf[Box[JArray]], entity.entityName) _ <- Helper.booleanToFuture(DynamicEntityOperationNotAllowed, cc = cc.callContext) { @@ -1974,20 +1974,20 @@ trait APIMethods400 { lazy val deleteDynamicEntity: OBPEndpoint = { case "management" :: "dynamic-entities" :: dynamicEntityId :: Nil JsonDelete _ => { cc => - deleteDynamicEntityMethod(dynamicEntityId, cc) + deleteDynamicEntityMethod(None, dynamicEntityId, cc) } } - private def deleteDynamicEntityMethod(dynamicEntityId: String, cc: CallContext) = { + private def deleteDynamicEntityMethod(bankId: Option[String], dynamicEntityId: String, cc: CallContext) = { for { // Check whether there are uploaded data, only if no uploaded data allow to delete DynamicEntity. - (entity, _) <- NewStyle.function.getDynamicEntityById(dynamicEntityId, cc.callContext) + (entity, _) <- NewStyle.function.getDynamicEntityById(bankId, dynamicEntityId, cc.callContext) (box, _) <- NewStyle.function.invokeDynamicConnector(GET_ALL, entity.entityName, None, None, entity.bankId, None, cc.callContext) resultList: JArray = unboxResult(box.asInstanceOf[Box[JArray]], entity.entityName) _ <- Helper.booleanToFuture(DynamicEntityOperationNotAllowed, cc = cc.callContext) { resultList.arr.isEmpty } - deleted: Box[Boolean] <- NewStyle.function.deleteDynamicEntity(dynamicEntityId) + deleted: Box[Boolean] <- NewStyle.function.deleteDynamicEntity(bankId, dynamicEntityId) } yield { (deleted, HttpCode.`204`(cc.callContext)) } @@ -2013,9 +2013,9 @@ trait APIMethods400 { List(apiTagManageDynamicEntity, apiTagApi, apiTagNewStyle), Some(List(canDeleteBankLevelDynamicEntity))) lazy val deleteBankLevelDynamicEntity: OBPEndpoint = { - case "management" :: "banks" :: BankId(bankId) :: "dynamic-entities" :: dynamicEntityId :: Nil JsonDelete _ => { + case "management" :: "banks" :: bankId :: "dynamic-entities" :: dynamicEntityId :: Nil JsonDelete _ => { cc => - deleteDynamicEntityMethod(dynamicEntityId, cc) + deleteDynamicEntityMethod(Some(bankId), dynamicEntityId, cc) } } @@ -2092,7 +2092,7 @@ trait APIMethods400 { cc => for { // Check whether there are uploaded data, only if no uploaded data allow to update DynamicEntity. - (entity, _) <- NewStyle.function.getDynamicEntityById(dynamicEntityId, cc.callContext) + (entity, _) <- NewStyle.function.getDynamicEntityById(None, dynamicEntityId, cc.callContext) _ <- Helper.booleanToFuture(InvalidMyDynamicEntityUser, cc=cc.callContext) { entity.userId.equals(cc.userId) } @@ -2135,7 +2135,7 @@ trait APIMethods400 { cc => for { // Check whether there are uploaded data, only if no uploaded data allow to delete DynamicEntity. - (entity, _) <- NewStyle.function.getDynamicEntityById(dynamicEntityId, cc.callContext) + (entity, _) <- NewStyle.function.getDynamicEntityById(None, dynamicEntityId, cc.callContext) _ <- Helper.booleanToFuture(InvalidMyDynamicEntityUser, cc=cc.callContext) { entity.userId.equals(cc.userId) } @@ -2144,7 +2144,7 @@ trait APIMethods400 { _ <- Helper.booleanToFuture(DynamicEntityOperationNotAllowed, cc=cc.callContext) { resultList.arr.isEmpty } - deleted: Box[Boolean] <- NewStyle.function.deleteDynamicEntity(dynamicEntityId) + deleted: Box[Boolean] <- NewStyle.function.deleteDynamicEntity(None, dynamicEntityId) } yield { (deleted, HttpCode.`200`(cc.callContext)) } @@ -5052,9 +5052,9 @@ trait APIMethods400 { (box, callContext) <- if (DynamicEndpointHelper.isDynamicEntityResponse(url)) { for { (endpointMapping, callContext) <- if (DynamicEndpointHelper.isDynamicEntityResponse(url)) { - NewStyle.function.getEndpointMappingByOperationId(operationId, cc.callContext) + NewStyle.function.getEndpointMappingByOperationId(bankId, operationId, cc.callContext) } else{ - Future.successful((EndpointMappingCommons(None,"","",""), callContext)) + Future.successful((EndpointMappingCommons(None,"","","", None), callContext)) } requestMappingString = endpointMapping.requestMapping // requestMappingJvalue = net.liftweb.json.parse(requestMappingString) @@ -8158,7 +8158,7 @@ trait APIMethods400 { nameOf(getDynamicMessageDoc), "GET", "/management/dynamic-message-docs/DYNAMIC_MESSAGE_DOC_ID", - "Get Dynamic Message Doc by Id", + "Get Dynamic Message Doc", s"""Get a Dynamic Message Doc by DYNAMIC_MESSAGE_DOC_ID. | |""", @@ -8271,15 +8271,21 @@ trait APIMethods400 { lazy val createEndpointMapping: OBPEndpoint = { case "management" :: "endpoint-mappings" :: Nil JsonPost json -> _ => { cc => - for { - endpointMapping <- NewStyle.function.tryons(s"$InvalidJsonFormat The Json body should be the ${classOf[EndpointMappingCommons]}", 400, cc.callContext) { - json.extract[EndpointMappingCommons] - } - (endpointMapping, callContext) <- NewStyle.function.createOrUpdateEndpointMapping(endpointMapping.copy(endpointMappingId= None), cc.callContext) - } yield { - val commonsData: EndpointMappingCommons = endpointMapping - (commonsData.toJson, HttpCode.`201`(callContext)) - } + createEndpointMappingMethod(None, json, cc) + } + } + + private def createEndpointMappingMethod(bankId: Option[String],json: JValue, cc: CallContext) = { + for { + endpointMapping <- NewStyle.function.tryons(s"$InvalidJsonFormat The Json body should be the ${classOf[EndpointMappingCommons]}", 400, cc.callContext) { + json.extract[EndpointMappingCommons].copy(bankId= bankId) + } + (endpointMapping, callContext) <- NewStyle.function.createOrUpdateEndpointMapping(bankId, + endpointMapping.copy(endpointMappingId = None, bankId= bankId), // create need to make sure, endpointMappingId is None, and bankId must be from URL. + cc.callContext) + } yield { + val commonsData: EndpointMappingCommons = endpointMapping + (commonsData.toJson, HttpCode.`201`(callContext)) } } @@ -8306,16 +8312,26 @@ trait APIMethods400 { lazy val updateEndpointMapping: OBPEndpoint = { case "management" :: "endpoint-mappings" :: endpointMappingId :: Nil JsonPut json -> _ => { cc => - for { - endpointMappingBody <- NewStyle.function.tryons(s"$InvalidJsonFormat The Json body should be the ${classOf[EndpointMappingCommons]}", 400, cc.callContext) { - json.extract[EndpointMappingCommons] - } - (_, callContext) <- NewStyle.function.getEndpointMappingById(endpointMappingId, cc.callContext) - (endpointMapping, callContext) <- NewStyle.function.createOrUpdateEndpointMapping(endpointMappingBody.copy(endpointMappingId = Some(endpointMappingId)), callContext) - } yield { - val commonsData: EndpointMappingCommons = endpointMapping - (commonsData.toJson, HttpCode.`201`(callContext)) - } + updateEndpointMappingMethod(None, endpointMappingId, json, cc) + } + } + + private def updateEndpointMappingMethod(bankId: Option[String], endpointMappingId: String, json: JValue, cc: CallContext) = { + for { + endpointMappingBody <- NewStyle.function.tryons(s"$InvalidJsonFormat The Json body should be the ${classOf[EndpointMappingCommons]}", 400, cc.callContext) { + json.extract[EndpointMappingCommons].copy(bankId = bankId) + } + (endpointMapping, callContext) <- NewStyle.function.getEndpointMappingById(bankId, endpointMappingId, cc.callContext) + _ <- Helper.booleanToFuture(s"$InvalidJsonFormat operation_id has to be the same in the URL (${endpointMapping.operationId}) and Body (${endpointMappingBody.operationId}). ", 400, cc.callContext){ + endpointMapping.operationId == endpointMappingBody.operationId + } + (endpointMapping, callContext) <- NewStyle.function.createOrUpdateEndpointMapping( + bankId, + endpointMappingBody.copy(endpointMappingId = Some(endpointMappingId), bankId = bankId), //Update must set the endpointId and BankId must be from URL + callContext) + } yield { + val commonsData: EndpointMappingCommons = endpointMapping + (commonsData.toJson, HttpCode.`201`(callContext)) } } @@ -8342,12 +8358,16 @@ trait APIMethods400 { lazy val getEndpointMapping: OBPEndpoint = { case "management" :: "endpoint-mappings" :: endpointMappingId :: Nil JsonGet _ => { cc => - for { - (endpointMapping, callContext) <- NewStyle.function.getEndpointMappingById(endpointMappingId, cc.callContext) - } yield { - val commonsData: EndpointMappingCommons = endpointMapping - (commonsData.toJson, HttpCode.`201`(callContext)) - } + getEndpointMappingMethod(None, endpointMappingId, cc) + } + } + + private def getEndpointMappingMethod(bankId: Option[String], endpointMappingId: String, cc: CallContext) = { + for { + (endpointMapping, callContext) <- NewStyle.function.getEndpointMappingById(bankId, endpointMappingId, cc.callContext) + } yield { + val commonsData: EndpointMappingCommons = endpointMapping + (commonsData.toJson, HttpCode.`201`(callContext)) } } @@ -8374,12 +8394,16 @@ trait APIMethods400 { lazy val getAllEndpointMappings: OBPEndpoint = { case "management" :: "endpoint-mappings" :: Nil JsonGet _ => { cc => - for { - (endpointMappings, callContext) <- NewStyle.function.getEndpointMappings(cc.callContext) - } yield { - val listCommons: List[EndpointMappingCommons] = endpointMappings - (ListResult("endpoint-mappings", listCommons.map(_.toJson)), HttpCode.`200`(callContext)) - } + getEndpointMappingsMethod(None, cc) + } + } + + private def getEndpointMappingsMethod(bankId: Option[String], cc: CallContext) = { + for { + (endpointMappings, callContext) <- NewStyle.function.getEndpointMappings(bankId, cc.callContext) + } yield { + val listCommons: List[EndpointMappingCommons] = endpointMappings + (ListResult("endpoint-mappings", listCommons.map(_.toJson)), HttpCode.`200`(callContext)) } } @@ -8406,14 +8430,155 @@ trait APIMethods400 { lazy val deleteEndpointMapping: OBPEndpoint = { case "management" :: "endpoint-mappings" :: endpointMappingId :: Nil JsonDelete _ => { cc => - for { - (deleted, callContext) <- NewStyle.function.deleteEndpointMapping(endpointMappingId, cc.callContext) - } yield { - (deleted, HttpCode.`200`(callContext)) - } + deleteEndpointMappingMethod(None, endpointMappingId, cc) + } + } + + private def deleteEndpointMappingMethod(bankId: Option[String], endpointMappingId: String, cc: CallContext) = { + for { + (deleted, callContext) <- NewStyle.function.deleteEndpointMapping(bankId, endpointMappingId, cc.callContext) + } yield { + (deleted, HttpCode.`200`(callContext)) } } + staticResourceDocs += ResourceDoc( + createBankLevelEndpointMapping, + implementedInApiVersion, + nameOf(createBankLevelEndpointMapping), + "POST", + "/management/banks/BANK_ID/endpoint-mappings", + "Create Bank Level Endpoint Mapping", + s"""Create an Bank Level Endpoint Mapping. + | + |Note: at moment only support the dynamic endpoints + |""", + endpointMappingJson.copy(endpointMappingId = None), + endpointMappingJson, + List( + $UserNotLoggedIn, + UserHasMissingRoles, + InvalidJsonFormat, + UnknownError + ), + List(apiTagEndpointMapping, apiTagNewStyle), + Some(List(canCreateBankLevelEndpointMapping))) + + lazy val createBankLevelEndpointMapping: OBPEndpoint = { + case "management" :: "banks" :: bankId :: "endpoint-mappings" :: Nil JsonPost json -> _ => { + cc => + createEndpointMappingMethod(Some(bankId), json, cc) + } + } + + staticResourceDocs += ResourceDoc( + updateBankLevelEndpointMapping, + implementedInApiVersion, + nameOf(updateBankLevelEndpointMapping), + "PUT", + "/management/banks/BANK_ID/endpoint-mappings/ENDPOINT_MAPPING_ID", + "Update Bank Level Endpoint Mapping", + s"""Update an Bank Level Endpoint Mapping. + |""", + endpointMappingJson.copy(endpointMappingId = None), + endpointMappingJson, + List( + $UserNotLoggedIn, + UserHasMissingRoles, + InvalidJsonFormat, + UnknownError + ), + List(apiTagEndpointMapping, apiTagNewStyle), + Some(List(canUpdateBankLevelEndpointMapping))) + + lazy val updateBankLevelEndpointMapping: OBPEndpoint = { + case "management" :: "banks" :: bankId :: "endpoint-mappings" :: endpointMappingId :: Nil JsonPut json -> _ => { + cc => + updateEndpointMappingMethod(Some(bankId), endpointMappingId, json, cc) + } + } + + staticResourceDocs += ResourceDoc( + getBankLevelEndpointMapping, + implementedInApiVersion, + nameOf(getBankLevelEndpointMapping), + "GET", + "/management/banks/BANK_ID/endpoint-mappings/ENDPOINT_MAPPING_ID", + "Get Bank Level Endpoint Mapping", + s"""Get an Bank Level Endpoint Mapping by ENDPOINT_MAPPING_ID. + | + |""", + EmptyBody, + endpointMappingJson, + List( + $UserNotLoggedIn, + UserHasMissingRoles, + UnknownError + ), + List(apiTagEndpointMapping, apiTagNewStyle), + Some(List(canGetBankLevelEndpointMapping))) + + lazy val getBankLevelEndpointMapping: OBPEndpoint = { + case "management" :: "banks" :: bankId :: "endpoint-mappings" :: endpointMappingId :: Nil JsonGet _ => { + cc => + getEndpointMappingMethod(Some(bankId), endpointMappingId, cc) + } + } + + staticResourceDocs += ResourceDoc( + getAllBankLevelEndpointMappings, + implementedInApiVersion, + nameOf(getAllBankLevelEndpointMappings), + "GET", + "/management/banks/BANK_ID/endpoint-mappings", + "Get all Bank Level Endpoint Mappings", + s"""Get all Bank Level Endpoint Mappings. + | + |""", + EmptyBody, + ListResult("endpoint-mappings", endpointMappingJson::Nil), + List( + $UserNotLoggedIn, + UserHasMissingRoles, + UnknownError + ), + List(apiTagEndpointMapping, apiTagNewStyle), + Some(List(canGetAllBankLevelEndpointMappings))) + + lazy val getAllBankLevelEndpointMappings: OBPEndpoint = { + case "management" :: "banks" :: bankId :: "endpoint-mappings" :: Nil JsonGet _ => { + cc => + getEndpointMappingsMethod(Some(bankId), cc) + } + } + + staticResourceDocs += ResourceDoc( + deleteBankLevelEndpointMapping, + implementedInApiVersion, + nameOf(deleteBankLevelEndpointMapping), + "DELETE", + "/management/banks/BANK_ID/endpoint-mappings/ENDPOINT_MAPPING_ID", + "Delete Bank Level Endpoint Mapping", + s"""Delete a Bank Level Endpoint Mapping. + |""", + EmptyBody, + BooleanBody(true), + List( + $UserNotLoggedIn, + UserHasMissingRoles, + InvalidJsonFormat, + UnknownError + ), + List(apiTagEndpointMapping, apiTagNewStyle), + Some(List(canDeleteBankLevelEndpointMapping))) + + lazy val deleteBankLevelEndpointMapping: OBPEndpoint = { + case "management" :: "banks" :: bankId :: "endpoint-mappings" :: endpointMappingId :: Nil JsonDelete _ => { + cc => + deleteEndpointMappingMethod(Some(bankId), endpointMappingId, cc) + } + } + staticResourceDocs += ResourceDoc( updateAtmSupportedCurrencies, implementedInApiVersion, diff --git a/obp-api/src/main/scala/code/api/v4_0_0/dynamic/DynamicEntityHelper.scala b/obp-api/src/main/scala/code/api/v4_0_0/dynamic/DynamicEntityHelper.scala index 4b524e3f3..afe53d2bb 100644 --- a/obp-api/src/main/scala/code/api/v4_0_0/dynamic/DynamicEntityHelper.scala +++ b/obp-api/src/main/scala/code/api/v4_0_0/dynamic/DynamicEntityHelper.scala @@ -47,9 +47,9 @@ object EntityName { object DynamicEntityHelper { private val implementedInApiVersion = ApiVersion.v4_0_0 - def definitionsMap: Map[String, DynamicEntityInfo] = NewStyle.function.getDynamicEntities().map(it => (it.entityName, DynamicEntityInfo(it.metadataJson, it.entityName, it.bankId))).toMap + def definitionsMap: Map[String, DynamicEntityInfo] = NewStyle.function.getDynamicEntities(None).map(it => (it.entityName, DynamicEntityInfo(it.metadataJson, it.entityName, it.bankId))).toMap - def dynamicEntityRoles: List[String] = NewStyle.function.getDynamicEntities().flatMap(dEntity => DynamicEntityInfo.roleNames(dEntity.entityName, dEntity.bankId)) + def dynamicEntityRoles: List[String] = NewStyle.function.getDynamicEntities(None).flatMap(dEntity => DynamicEntityInfo.roleNames(dEntity.entityName, dEntity.bankId)) def doc: ArrayBuffer[ResourceDoc] = { val docs = operationToResourceDoc.values.toList diff --git a/obp-api/src/main/scala/code/dynamicEntity/DynamicEntityProvider.scala b/obp-api/src/main/scala/code/dynamicEntity/DynamicEntityProvider.scala index 0d39a2495..3bf4ca991 100644 --- a/obp-api/src/main/scala/code/dynamicEntity/DynamicEntityProvider.scala +++ b/obp-api/src/main/scala/code/dynamicEntity/DynamicEntityProvider.scala @@ -169,7 +169,7 @@ object ReferenceType { .recover(recoverFn(fieldName, value, "MethodRouting")) }, "reference:DynamicEntity" -> {(fieldName, value, callContext) => - NewStyle.function.getDynamicEntityById(value, callContext) + NewStyle.function.getDynamicEntityById(None, value, callContext) .map(mapFn(fieldName, value, "DynamicEntity")) .recover(recoverFn(fieldName, value, "DynamicEntity")) }, @@ -283,7 +283,7 @@ object ReferenceType { ) def referenceTypeNames: List[String] = { - val dynamicRefs: List[String] = NewStyle.function.getDynamicEntities() + val dynamicRefs: List[String] = NewStyle.function.getDynamicEntities(None) .map(entity => s"reference:${entity.entityName}") val staticRefs: List[String] = staticRefTypeToValidateFunction.keys.toList @@ -534,16 +534,14 @@ case class DynamicEntityIntTypeExample(`type`: DynamicEntityFieldType, example: trait DynamicEntityProvider { - def getById(dynamicEntityId: String): Box[DynamicEntityT] + def getById(bankId: Option[String], dynamicEntityId: String): Box[DynamicEntityT] - def getByEntityName(entityName: String): Box[DynamicEntityT] + def getByEntityName(bankId: Option[String], entityName: String): Box[DynamicEntityT] - def getDynamicEntities(): List[DynamicEntityT] + def getDynamicEntities(bankId: Option[String]): List[DynamicEntityT] def getDynamicEntitiesByUserId(userId: String): List[DynamicEntity] - def getDynamicEntitiesByBankId(bankId: String): List[DynamicEntity] - def createOrUpdate(dynamicEntity: DynamicEntityT): Box[DynamicEntityT] def delete(dynamicEntity: DynamicEntityT):Box[Boolean] diff --git a/obp-api/src/main/scala/code/dynamicEntity/MapppedDynamicEntityProvider.scala b/obp-api/src/main/scala/code/dynamicEntity/MapppedDynamicEntityProvider.scala index bc8797786..912fa5b9d 100644 --- a/obp-api/src/main/scala/code/dynamicEntity/MapppedDynamicEntityProvider.scala +++ b/obp-api/src/main/scala/code/dynamicEntity/MapppedDynamicEntityProvider.scala @@ -10,22 +10,36 @@ import org.apache.commons.lang3.StringUtils object MappedDynamicEntityProvider extends DynamicEntityProvider with CustomJsonFormats with MdcLoggable { - override def getById(dynamicEntityId: String): Box[DynamicEntityT] = DynamicEntity.find( - By(DynamicEntity.DynamicEntityId, dynamicEntityId) - ) - - override def getByEntityName(entityName: String): Box[DynamicEntityT] = DynamicEntity.find( - By(DynamicEntity.EntityName, entityName) - ) - - override def getDynamicEntities(): List[DynamicEntity] = { - DynamicEntity.findAll() + override def getById(bankId: Option[String], dynamicEntityId: String): Box[DynamicEntityT] = { + if (bankId.isEmpty) + DynamicEntity.find(By(DynamicEntity.DynamicEntityId, dynamicEntityId)) + else + DynamicEntity.find( + By(DynamicEntity.DynamicEntityId, dynamicEntityId), + By(DynamicEntity.BankId, bankId.getOrElse("") + )) } - override def getDynamicEntitiesByBankId(bankId: String): List[DynamicEntity] = { - DynamicEntity.findAll(By(DynamicEntity.BankId, bankId)) - } + + override def getByEntityName(bankId: Option[String], entityName: String): Box[DynamicEntityT] = { + if (bankId.isEmpty) + DynamicEntity.find(By(DynamicEntity.EntityName, entityName)) + else + DynamicEntity.find( + By(DynamicEntity.EntityName, entityName), + By(DynamicEntity.BankId, bankId.getOrElse("") + )) + + } + + override def getDynamicEntities(bankId: Option[String]): List[DynamicEntity] = { + if (bankId.isEmpty) + DynamicEntity.findAll() + else + DynamicEntity.findAll(By(DynamicEntity.BankId, bankId.getOrElse(""))) + } + override def getDynamicEntitiesByUserId(userId: String): List[DynamicEntity] = { DynamicEntity.findAll(By(DynamicEntity.UserId, userId)) } diff --git a/obp-api/src/main/scala/code/endpointMapping/EndpointMappingProvider.scala b/obp-api/src/main/scala/code/endpointMapping/EndpointMappingProvider.scala index 906fd156a..981d4659f 100644 --- a/obp-api/src/main/scala/code/endpointMapping/EndpointMappingProvider.scala +++ b/obp-api/src/main/scala/code/endpointMapping/EndpointMappingProvider.scala @@ -22,14 +22,16 @@ trait EndpointMappingT { def operationId: String def requestMapping: String def responseMapping: String + def bankId: Option[String] } case class EndpointMappingCommons( endpointMappingId: Option[String], operationId: String, requestMapping: String, - responseMapping: String -) extends EndpointMappingT with JsonFieldReName { + responseMapping: String, + bankId: Option[String] + ) extends EndpointMappingT with JsonFieldReName { /** * when serialized to json, the Option field will be not shown, this endpoint just generate a full fields json, include all None value fields * @return JObject include all fields @@ -39,7 +41,8 @@ case class EndpointMappingCommons( JField("operation_id", JString(this.operationId)), JField("request_mapping", json.parse(this.requestMapping)), JField("response_mapping", json.parse(this.responseMapping)), - JField("endpoint_mapping_id", this.endpointMappingId.map(JString(_)).getOrElse(JNull)) + JField("endpoint_mapping_id", this.endpointMappingId.map(JString(_)).getOrElse(JNull)), + JField("bank_id", this.bankId.map(JString(_)).getOrElse(JNull)) )) } } @@ -47,15 +50,15 @@ case class EndpointMappingCommons( object EndpointMappingCommons extends Converter[EndpointMappingT, EndpointMappingCommons] trait EndpointMappingProvider { - def getById(endpointMappingId: String): Box[EndpointMappingT] + def getById(bankId: Option[String], endpointMappingId: String): Box[EndpointMappingT] - def getByOperationId(operationId: String): Box[EndpointMappingT] + def getByOperationId(bankId: Option[String], operationId: String): Box[EndpointMappingT] - def getAllEndpointMappings: List[EndpointMappingT] + def getAllEndpointMappings(bankId: Option[String]): List[EndpointMappingT] - def createOrUpdate(endpointMapping: EndpointMappingT): Box[EndpointMappingT] + def createOrUpdate(bankId: Option[String], endpointMapping: EndpointMappingT): Box[EndpointMappingT] - def delete(endpointMappingId: String):Box[Boolean] + def delete(bankId: Option[String], endpointMappingId: String):Box[Boolean] } diff --git a/obp-api/src/main/scala/code/endpointMapping/MappedEndpointMappingProvider.scala b/obp-api/src/main/scala/code/endpointMapping/MappedEndpointMappingProvider.scala index 7dacadcc3..2a52b6bbd 100644 --- a/obp-api/src/main/scala/code/endpointMapping/MappedEndpointMappingProvider.scala +++ b/obp-api/src/main/scala/code/endpointMapping/MappedEndpointMappingProvider.scala @@ -13,11 +13,20 @@ import net.liftweb.json.JsonAST.JArray object MappedEndpointMappingProvider extends EndpointMappingProvider with CustomJsonFormats{ - override def getById(endpointMappingId: String): Box[EndpointMappingT] = getByEndpointMappingId(endpointMappingId) - - override def getByOperationId(operationId: String): Box[EndpointMappingT] = EndpointMapping.find(By(EndpointMapping.OperationId, operationId)) + override def getById(bankId: Option[String], endpointMappingId: String): Box[EndpointMappingT] = { + if (bankId.isEmpty) getByEndpointMappingId(endpointMappingId) + else getByEndpointMappingId(bankId.getOrElse(""), endpointMappingId) + } - override def createOrUpdate(endpointMapping: EndpointMappingT): Box[EndpointMappingT] = { + override def getByOperationId(bankId: Option[String], operationId: String): Box[EndpointMappingT] = { + if (bankId.isEmpty) EndpointMapping.find(By(EndpointMapping.OperationId, operationId)) + else EndpointMapping.find( + By(EndpointMapping.OperationId, operationId), + By(EndpointMapping.BankId, bankId.getOrElse("")) + ) + } + + override def createOrUpdate(bankId: Option[String], endpointMapping: EndpointMappingT): Box[EndpointMappingT] = { //to find exists endpointMapping, if endpointMappingId supplied, query by endpointMappingId, or use endpointName and endpointMappingId to do query val existsEndpointMapping: Box[EndpointMapping] = endpointMapping.endpointMappingId match { case Some(id) if (StringUtils.isNotBlank(id)) => getByEndpointMappingId(id) @@ -33,16 +42,24 @@ object MappedEndpointMappingProvider extends EndpointMappingProvider with Custom .OperationId(endpointMapping.operationId) .RequestMapping(endpointMapping.requestMapping) .ResponseMapping(endpointMapping.responseMapping) + .BankId(endpointMapping.bankId.getOrElse(null)) .saveMe() } } - override def delete(endpointMappingId: String): Box[Boolean] = getByEndpointMappingId(endpointMappingId).map(_.delete_!) + override def delete(bankId: Option[String], endpointMappingId: String): Box[Boolean] = + if (bankId.isEmpty) getByEndpointMappingId(endpointMappingId).map(_.delete_!) + else getByEndpointMappingId(bankId.getOrElse(""),endpointMappingId).map(_.delete_!) private[this] def getByEndpointMappingId(endpointMappingId: String): Box[EndpointMapping] = EndpointMapping.find(By(EndpointMapping.EndpointMappingId, endpointMappingId)) + private[this] def getByEndpointMappingId(bankId: String, endpointMappingId: String): Box[EndpointMapping] = EndpointMapping.find( + By(EndpointMapping.EndpointMappingId, endpointMappingId), + By(EndpointMapping.BankId, bankId), + ) - override def getAllEndpointMappings(): List[EndpointMappingT] = EndpointMapping.findAll() - + override def getAllEndpointMappings(bankId: Option[String]): List[EndpointMappingT] = + if (bankId.isEmpty) EndpointMapping.findAll() + else EndpointMapping.findAll(By(EndpointMapping.BankId, bankId.getOrElse(""))) } class EndpointMapping extends EndpointMappingT with LongKeyedMapper[EndpointMapping] with IdPK with CustomJsonFormats{ @@ -53,11 +70,13 @@ class EndpointMapping extends EndpointMappingT with LongKeyedMapper[EndpointMapp object OperationId extends MappedString(this, 255) object RequestMapping extends MappedText(this) object ResponseMapping extends MappedText(this) + object BankId extends MappedString(this, 255) override def endpointMappingId: Option[String] = Option(EndpointMappingId.get) override def operationId: String = OperationId.get override def requestMapping: String = RequestMapping.get override def responseMapping: String = ResponseMapping.get + override def bankId: Option[String] = if (BankId.get == null || BankId.get.isEmpty) None else Some(BankId.get) } object EndpointMapping extends EndpointMapping with LongKeyedMetaMapper[EndpointMapping] { From 33ddefecf3a2d2c87c2955a71c3b412ac9d63e82 Mon Sep 17 00:00:00 2001 From: hongwei Date: Wed, 9 Jun 2021 12:11:31 +0200 Subject: [PATCH 030/147] feature/OBPv400 added bankId for Dynamic Endpoint Endpoints --- .../main/scala/code/api/util/ApiRole.scala | 2 +- .../main/scala/code/api/util/NewStyle.scala | 27 ++++--- .../scala/code/api/v4_0_0/APIMethods400.scala | 76 +++++++++---------- .../dynamic/DynamicEndpointHelper.scala | 8 +- .../DynamicEndpointProvider.scala | 10 +-- .../MapppedDynamicEndpointProvider.scala | 56 +++++++++++--- 6 files changed, 110 insertions(+), 69 deletions(-) diff --git a/obp-api/src/main/scala/code/api/util/ApiRole.scala b/obp-api/src/main/scala/code/api/util/ApiRole.scala index e2cd9bd35..926504f94 100644 --- a/obp-api/src/main/scala/code/api/util/ApiRole.scala +++ b/obp-api/src/main/scala/code/api/util/ApiRole.scala @@ -457,7 +457,7 @@ object ApiRole { lazy val canCreateDynamicEntity = CanCreateDynamicEntity() case class CanCreateBankLevelDynamicEntity(requiresBankId: Boolean = true) extends ApiRole - lazy val canCreateDynamicEntityAtBank = CanCreateBankLevelDynamicEntity() + lazy val canCreateBankLevelDynamicEntity = CanCreateBankLevelDynamicEntity() case class CanUpdateDynamicEntity(requiresBankId: Boolean = false) extends ApiRole lazy val canUpdateDynamicEntity = CanUpdateDynamicEntity() 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 b820d3174..0b2b1b072 100644 --- a/obp-api/src/main/scala/code/api/util/NewStyle.scala +++ b/obp-api/src/main/scala/code/api/util/NewStyle.scala @@ -2700,22 +2700,27 @@ object NewStyle { i => (connectorEmptyResponse(i._1, callContext), i._2) } - def updateDynamicEndpointHost(userId: String, swaggerString: String, callContext: Option[CallContext]): OBPReturnType[DynamicEndpointT] = Future { - (DynamicEndpointProvider.connectorMethodProvider.vend.updateHost(userId, swaggerString), callContext) + def updateDynamicEndpointHost(bankId: Option[String], userId: String, swaggerString: String, callContext: Option[CallContext]): OBPReturnType[DynamicEndpointT] = Future { + (DynamicEndpointProvider.connectorMethodProvider.vend.updateHost(bankId, userId, swaggerString), callContext) } map { i => (connectorEmptyResponse(i._1, callContext), i._2) } - def getDynamicEndpoint(dynamicEndpointId: String, callContext: Option[CallContext]): OBPReturnType[DynamicEndpointT] = { - val dynamicEndpointBox: Box[DynamicEndpointT] = DynamicEndpointProvider.connectorMethodProvider.vend.get(dynamicEndpointId) - val dynamicEndpoint = unboxFullOrFail(dynamicEndpointBox, callContext, DynamicEndpointNotFoundByDynamicEndpointId, 404) + def getDynamicEndpoint(bankId: Option[String], dynamicEndpointId: String, callContext: Option[CallContext]): OBPReturnType[DynamicEndpointT] = { + val dynamicEndpointBox: Box[DynamicEndpointT] = DynamicEndpointProvider.connectorMethodProvider.vend.get(bankId, dynamicEndpointId) + val errorMessage = + if(bankId.isEmpty) + DynamicEndpointNotFoundByDynamicEndpointId.replace("DYNAMIC_ENDPOINT_ID.", s"DYNAMIC_ENDPOINT_ID($dynamicEndpointId).") + else + DynamicEndpointNotFoundByDynamicEndpointId.replace("for DYNAMIC_ENDPOINT_ID.",s"for DYNAMIC_ENDPOINT_ID($dynamicEndpointId) and BANK_ID(${bankId.getOrElse("")}).") + val dynamicEndpoint = unboxFullOrFail(dynamicEndpointBox, callContext, errorMessage, 404) Future{ (dynamicEndpoint, callContext) } } - def getDynamicEndpoints(callContext: Option[CallContext]): OBPReturnType[List[DynamicEndpointT]] = Future { - (DynamicEndpointProvider.connectorMethodProvider.vend.getAll(), callContext) + def getDynamicEndpoints(bankId: Option[String], callContext: Option[CallContext]): OBPReturnType[List[DynamicEndpointT]] = Future { + (DynamicEndpointProvider.connectorMethodProvider.vend.getAll(bankId), callContext) } def getDynamicEndpointsByUserId(userId: String, callContext: Option[CallContext]): OBPReturnType[List[DynamicEndpointT]] = Future { @@ -2727,18 +2732,18 @@ object NewStyle { * @param callContext * @return */ - def deleteDynamicEndpoint(dynamicEndpointId: String, callContext: Option[CallContext]): Future[Box[Boolean]] = { - val dynamicEndpoint: OBPReturnType[DynamicEndpointT] = this.getDynamicEndpoint(dynamicEndpointId, callContext) + def deleteDynamicEndpoint(bankId: Option[String], dynamicEndpointId: String, callContext: Option[CallContext]): Future[Box[Boolean]] = { + val dynamicEndpoint: OBPReturnType[DynamicEndpointT] = this.getDynamicEndpoint(bankId, dynamicEndpointId, callContext) for { (entity, _) <- dynamicEndpoint deleteEndpointResult: Box[Boolean] = { - val roles = DynamicEndpointHelper.getRoles(dynamicEndpointId).map(_.toString()) + val roles = DynamicEndpointHelper.getRoles(bankId, dynamicEndpointId).map(_.toString()) roles.foreach(ApiRole.removeDynamicApiRole(_)) val rolesDeleteResult: Box[Boolean] = Entitlement.entitlement.vend.deleteEntitlements(roles) Box !! (rolesDeleteResult == Full(true)) } deleteSuccess = if(deleteEndpointResult.isDefined && deleteEndpointResult.head) { - tryo {DynamicEndpointProvider.connectorMethodProvider.vend.delete(dynamicEndpointId)} + tryo {DynamicEndpointProvider.connectorMethodProvider.vend.delete(bankId, dynamicEndpointId)} }else{ Full(false) } 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 09bd0f755..8eb1f4549 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 @@ -1722,7 +1722,7 @@ trait APIMethods400 { UnknownError ), List(apiTagManageDynamicEntity, apiTagApi, apiTagNewStyle), - Some(List(canGetBankLevelDynamicEntities)) + Some(List(canGetBankLevelDynamicEntities, canGetDynamicEntities)) ) lazy val getBankLevelDynamicEntities: OBPEndpoint = { @@ -1838,7 +1838,7 @@ trait APIMethods400 { UnknownError ), List(apiTagManageDynamicEntity, apiTagApi, apiTagNewStyle), - Some(List(canCreateDynamicEntityAtBank))) + Some(List(canCreateBankLevelDynamicEntity, canCreateDynamicEntity))) lazy val createBankLevelDynamicEntity: OBPEndpoint = { case "management" ::"banks" :: BankId(bankId) :: "dynamic-entities" :: Nil JsonPost json -> _ => { cc => @@ -1944,7 +1944,7 @@ trait APIMethods400 { UnknownError ), List(apiTagManageDynamicEntity, apiTagApi, apiTagNewStyle), - Some(List(canUpdateBankLevelDynamicEntity))) + Some(List(canUpdateBankLevelDynamicEntity, canUpdateDynamicEntity))) lazy val updateBankLevelDynamicEntityAtBank: OBPEndpoint = { case "management" :: "banks" :: bankId :: "dynamic-entities" :: dynamicEntityId :: Nil JsonPut json -> _ => { cc => @@ -2011,7 +2011,7 @@ trait APIMethods400 { UnknownError ), List(apiTagManageDynamicEntity, apiTagApi, apiTagNewStyle), - Some(List(canDeleteBankLevelDynamicEntity))) + Some(List(canDeleteBankLevelDynamicEntity, canDeleteDynamicEntity))) lazy val deleteBankLevelDynamicEntity: OBPEndpoint = { case "management" :: "banks" :: bankId :: "dynamic-entities" :: dynamicEntityId :: Nil JsonDelete _ => { cc => @@ -4671,7 +4671,7 @@ trait APIMethods400 { UnknownError ), List(apiTagManageDynamicEndpoint, apiTagApi, apiTagNewStyle), - Some(List(canCreateBankLevelDynamicEndpoint))) + Some(List(canCreateBankLevelDynamicEndpoint, canCreateDynamicEndpoint))) lazy val createBankLevelDynamicEndpoint: OBPEndpoint = { case "management" :: "banks" :: bankId ::"dynamic-endpoints" :: Nil JsonPost json -> _ => { @@ -4705,18 +4705,18 @@ trait APIMethods400 { lazy val updateDynamicEndpointHost: OBPEndpoint = { case "management" :: "dynamic-endpoints" :: dynamicEndpointId :: "host" :: Nil JsonPut json -> _ => { cc => - updateDynamicEndpointHostMethod(dynamicEndpointId, json, cc) + updateDynamicEndpointHostMethod(None, dynamicEndpointId, json, cc) } } - private def updateDynamicEndpointHostMethod(dynamicEndpointId: String, json: JValue, cc: CallContext) = { + private def updateDynamicEndpointHostMethod(bankId: Option[String], dynamicEndpointId: String, json: JValue, cc: CallContext) = { for { - (_, callContext) <- NewStyle.function.getDynamicEndpoint(dynamicEndpointId, cc.callContext) + (_, callContext) <- NewStyle.function.getDynamicEndpoint(bankId, dynamicEndpointId, cc.callContext) failMsg = s"$InvalidJsonFormat The Json body should be the $DynamicEndpointHostJson400" postedData <- NewStyle.function.tryons(failMsg, 400, callContext) { json.extract[DynamicEndpointHostJson400] } - (dynamicEndpoint, callContext) <- NewStyle.function.updateDynamicEndpointHost(dynamicEndpointId, postedData.host, cc.callContext) + (dynamicEndpoint, callContext) <- NewStyle.function.updateDynamicEndpointHost(bankId, dynamicEndpointId, postedData.host, cc.callContext) } yield { (postedData, HttpCode.`201`(callContext)) } @@ -4742,12 +4742,12 @@ trait APIMethods400 { UnknownError ), List(apiTagManageDynamicEndpoint, apiTagApi, apiTagNewStyle), - Some(List(canUpdateDynamicEndpoint))) + Some(List(canUpdateBankLevelDynamicEndpoint, canUpdateDynamicEndpoint))) lazy val updateBankLevelDynamicEndpointHost: OBPEndpoint = { - case "management" :: "banks" :: BankId(bankId) :: "dynamic-endpoints" :: dynamicEndpointId :: "host" :: Nil JsonPut json -> _ => { + case "management" :: "banks" :: bankId :: "dynamic-endpoints" :: dynamicEndpointId :: "host" :: Nil JsonPut json -> _ => { cc => - updateDynamicEndpointHostMethod(dynamicEndpointId, json, cc) + updateDynamicEndpointHostMethod(Some(bankId), dynamicEndpointId, json, cc) } } @@ -4779,7 +4779,7 @@ trait APIMethods400 { lazy val getDynamicEndpoint: OBPEndpoint = { case "management" :: "dynamic-endpoints" :: dynamicEndpointId :: Nil JsonGet req => { cc => - getDynamicEndpointMethod(dynamicEndpointId, cc) + getDynamicEndpointMethod(None, dynamicEndpointId, cc) } } @@ -4812,13 +4812,13 @@ trait APIMethods400 { lazy val getDynamicEndpoints: OBPEndpoint = { case "management" :: "dynamic-endpoints" :: Nil JsonGet _ => { cc => - getDynamicEndpointsMethod(cc) + getDynamicEndpointsMethod(None, cc) } } - private def getDynamicEndpointsMethod(cc: CallContext) = { + private def getDynamicEndpointsMethod(bankId: Option[String], cc: CallContext) = { for { - (dynamicEndpoints, _) <- NewStyle.function.getDynamicEndpoints(cc.callContext) + (dynamicEndpoints, _) <- NewStyle.function.getDynamicEndpoints(bankId, cc.callContext) } yield { val resultList = dynamicEndpoints.map[JObject, List[JObject]] { dynamicEndpoint => val swaggerJson = parse(dynamicEndpoint.swaggerString) @@ -4847,18 +4847,18 @@ trait APIMethods400 { UnknownError ), List(apiTagManageDynamicEndpoint, apiTagApi, apiTagNewStyle), - Some(List(canGetBankLevelDynamicEndpoint))) + Some(List(canGetBankLevelDynamicEndpoint, canGetDynamicEndpoint))) lazy val getBankLevelDynamicEndpoint: OBPEndpoint = { - case "management" :: "banks" :: BankId(bankId) :: "dynamic-endpoints" :: dynamicEndpointId :: Nil JsonGet req => { + case "management" :: "banks" :: bankId :: "dynamic-endpoints" :: dynamicEndpointId :: Nil JsonGet req => { cc => - getDynamicEndpointMethod(dynamicEndpointId, cc) + getDynamicEndpointMethod(Some(bankId), dynamicEndpointId, cc) } } - private def getDynamicEndpointMethod(dynamicEndpointId: String, cc: CallContext) = { + private def getDynamicEndpointMethod(bankId: Option[String], dynamicEndpointId: String, cc: CallContext) = { for { - (dynamicEndpoint, callContext) <- NewStyle.function.getDynamicEndpoint(dynamicEndpointId, cc.callContext) + (dynamicEndpoint, callContext) <- NewStyle.function.getDynamicEndpoint(bankId, dynamicEndpointId, cc.callContext) } yield { val swaggerJson = parse(dynamicEndpoint.swaggerString) val responseJson: JObject = ("user_id", cc.userId) ~ ("dynamic_endpoint_id", dynamicEndpoint.dynamicEndpointId) ~ ("swagger_string", swaggerJson) @@ -4871,7 +4871,7 @@ trait APIMethods400 { implementedInApiVersion, nameOf(getBankLevelDynamicEndpoints), "GET", - "/management/dynamic-endpoints", + "/management/banks/BANK_ID/dynamic-endpoints", "Get Bank Level Dynamic Endpoints", s""" | @@ -4890,18 +4890,18 @@ trait APIMethods400 { UnknownError ), List(apiTagManageDynamicEndpoint, apiTagApi, apiTagNewStyle), - Some(List(canGetDynamicEndpoints))) + Some(List(canGetBankLevelDynamicEndpoints, canGetDynamicEndpoints))) lazy val getBankLevelDynamicEndpoints: OBPEndpoint = { - case "management" :: "banks" :: BankId(bankId) :: "dynamic-endpoints" :: Nil JsonGet _ => { + case "management" :: "banks" :: bankId :: "dynamic-endpoints" :: Nil JsonGet _ => { cc => - getDynamicEndpointsMethod(cc) + getDynamicEndpointsMethod(Some(bankId), cc) } } - private def deleteDynamicEndpointMethod(dynamicEndpointId: String, cc: CallContext) = { + private def deleteDynamicEndpointMethod(bankId: Option[String], dynamicEndpointId: String, cc: CallContext) = { for { - deleted <- NewStyle.function.deleteDynamicEndpoint(dynamicEndpointId, cc.callContext) + deleted <- NewStyle.function.deleteDynamicEndpoint(bankId, dynamicEndpointId, cc.callContext) } yield { (deleted, HttpCode.`204`(cc.callContext)) } @@ -4928,7 +4928,7 @@ trait APIMethods400 { lazy val deleteDynamicEndpoint : OBPEndpoint = { case "management" :: "dynamic-endpoints" :: dynamicEndpointId :: Nil JsonDelete _ => { cc => - deleteDynamicEndpointMethod(dynamicEndpointId, cc) + deleteDynamicEndpointMethod(None, dynamicEndpointId, cc) } } @@ -4948,12 +4948,12 @@ trait APIMethods400 { UnknownError ), List(apiTagManageDynamicEndpoint, apiTagApi, apiTagNewStyle), - Some(List(canDeleteBankLevelDynamicEndpoint))) + Some(List(canDeleteBankLevelDynamicEndpoint ,canDeleteDynamicEndpoint))) lazy val deleteBankLevelDynamicEndpoint : OBPEndpoint = { case "management" :: "banks" :: bankId :: "dynamic-endpoints" :: dynamicEndpointId :: Nil JsonDelete _ => { cc => - deleteDynamicEndpointMethod(dynamicEndpointId, cc) + deleteDynamicEndpointMethod(Some(bankId), dynamicEndpointId, cc) } } @@ -5015,11 +5015,11 @@ trait APIMethods400 { case "my" :: "dynamic-endpoints" :: dynamicEndpointId :: Nil JsonDelete _ => { cc => for { - (dynamicEndpoint, callContext) <- NewStyle.function.getDynamicEndpoint(dynamicEndpointId, cc.callContext) + (dynamicEndpoint, callContext) <- NewStyle.function.getDynamicEndpoint(None, dynamicEndpointId, cc.callContext) _ <- Helper.booleanToFuture(InvalidMyDynamicEndpointUser, cc=callContext) { dynamicEndpoint.userId.equals(cc.userId) } - deleted <- NewStyle.function.deleteDynamicEndpoint(dynamicEndpointId, callContext) + deleted <- NewStyle.function.deleteDynamicEndpoint(None, dynamicEndpointId, callContext) } yield { (deleted, HttpCode.`204`(callContext)) @@ -8462,7 +8462,7 @@ trait APIMethods400 { UnknownError ), List(apiTagEndpointMapping, apiTagNewStyle), - Some(List(canCreateBankLevelEndpointMapping))) + Some(List(canCreateBankLevelEndpointMapping, canCreateEndpointMapping))) lazy val createBankLevelEndpointMapping: OBPEndpoint = { case "management" :: "banks" :: bankId :: "endpoint-mappings" :: Nil JsonPost json -> _ => { @@ -8489,7 +8489,7 @@ trait APIMethods400 { UnknownError ), List(apiTagEndpointMapping, apiTagNewStyle), - Some(List(canUpdateBankLevelEndpointMapping))) + Some(List(canUpdateBankLevelEndpointMapping, canUpdateEndpointMapping))) lazy val updateBankLevelEndpointMapping: OBPEndpoint = { case "management" :: "banks" :: bankId :: "endpoint-mappings" :: endpointMappingId :: Nil JsonPut json -> _ => { @@ -8516,7 +8516,7 @@ trait APIMethods400 { UnknownError ), List(apiTagEndpointMapping, apiTagNewStyle), - Some(List(canGetBankLevelEndpointMapping))) + Some(List(canGetBankLevelEndpointMapping, canGetEndpointMapping))) lazy val getBankLevelEndpointMapping: OBPEndpoint = { case "management" :: "banks" :: bankId :: "endpoint-mappings" :: endpointMappingId :: Nil JsonGet _ => { @@ -8543,7 +8543,7 @@ trait APIMethods400 { UnknownError ), List(apiTagEndpointMapping, apiTagNewStyle), - Some(List(canGetAllBankLevelEndpointMappings))) + Some(List(canGetAllBankLevelEndpointMappings, canGetAllEndpointMappings))) lazy val getAllBankLevelEndpointMappings: OBPEndpoint = { case "management" :: "banks" :: bankId :: "endpoint-mappings" :: Nil JsonGet _ => { @@ -8570,7 +8570,7 @@ trait APIMethods400 { UnknownError ), List(apiTagEndpointMapping, apiTagNewStyle), - Some(List(canDeleteBankLevelEndpointMapping))) + Some(List(canDeleteBankLevelEndpointMapping, canDeleteEndpointMapping))) lazy val deleteBankLevelEndpointMapping: OBPEndpoint = { case "management" :: "banks" :: bankId :: "endpoint-mappings" :: endpointMappingId :: Nil JsonDelete _ => { @@ -8978,7 +8978,7 @@ trait APIMethods400 { } (dynamicEndpoint, callContext) <- NewStyle.function.createDynamicEndpoint(bankId, cc.userId, postedJson.swaggerString, cc.callContext) } yield { - val roles = DynamicEndpointHelper.getRoles(dynamicEndpoint.dynamicEndpointId.getOrElse("")) + val roles = DynamicEndpointHelper.getRoles(bankId: Option[String], dynamicEndpoint.dynamicEndpointId.getOrElse("")) roles.map(role => Entitlement.entitlement.vend.addEntitlement(bankId.getOrElse(""), cc.userId, role.toString())) val swaggerJson = parse(dynamicEndpoint.swaggerString) val responseJson: JObject = ("bank_id", dynamicEndpoint.bankId) ~ ("user_id", cc.userId) ~ ("dynamic_endpoint_id", dynamicEndpoint.dynamicEndpointId) ~ ("swagger_string", swaggerJson) diff --git a/obp-api/src/main/scala/code/api/v4_0_0/dynamic/DynamicEndpointHelper.scala b/obp-api/src/main/scala/code/api/v4_0_0/dynamic/DynamicEndpointHelper.scala index f29114768..b08c8bf7f 100644 --- a/obp-api/src/main/scala/code/api/v4_0_0/dynamic/DynamicEndpointHelper.scala +++ b/obp-api/src/main/scala/code/api/v4_0_0/dynamic/DynamicEndpointHelper.scala @@ -56,21 +56,21 @@ object DynamicEndpointHelper extends RestHelper { def isDynamicEntityResponse (serverUrl : String) = serverUrl matches (IsDynamicEntityUrl) private def dynamicEndpointInfos: List[DynamicEndpointInfo] = { - val dynamicEndpoints: List[DynamicEndpointT] = DynamicEndpointProvider.connectorMethodProvider.vend.getAll() + val dynamicEndpoints: List[DynamicEndpointT] = DynamicEndpointProvider.connectorMethodProvider.vend.getAll(None) val infos = dynamicEndpoints.map(it => buildDynamicEndpointInfo(it.swaggerString, it.dynamicEndpointId.get, it.bankId)) infos } def allDynamicEndpointRoles: List[ApiRole] = { for { - dynamicEndpoint <- DynamicEndpointProvider.connectorMethodProvider.vend.getAll() + dynamicEndpoint <- DynamicEndpointProvider.connectorMethodProvider.vend.getAll(None) info = buildDynamicEndpointInfo(dynamicEndpoint.swaggerString, dynamicEndpoint.dynamicEndpointId.get, dynamicEndpoint.bankId) role <- getRoles(info) } yield role } - def getRoles(dynamicEndpointId: String): List[ApiRole] = { - val foundInfos: Box[DynamicEndpointInfo] = DynamicEndpointProvider.connectorMethodProvider.vend.get(dynamicEndpointId) + def getRoles(bankId: Option[String], dynamicEndpointId: String): List[ApiRole] = { + val foundInfos: Box[DynamicEndpointInfo] = DynamicEndpointProvider.connectorMethodProvider.vend.get(bankId, dynamicEndpointId) .map(dynamicEndpoint => buildDynamicEndpointInfo(dynamicEndpoint.swaggerString, dynamicEndpoint.dynamicEndpointId.get, dynamicEndpoint.bankId)) diff --git a/obp-api/src/main/scala/code/dynamicEndpoint/DynamicEndpointProvider.scala b/obp-api/src/main/scala/code/dynamicEndpoint/DynamicEndpointProvider.scala index cbb8b1072..2473e8e68 100644 --- a/obp-api/src/main/scala/code/dynamicEndpoint/DynamicEndpointProvider.scala +++ b/obp-api/src/main/scala/code/dynamicEndpoint/DynamicEndpointProvider.scala @@ -34,10 +34,10 @@ case class DynamicEndpointSwagger(swaggerString: String, dynamicEndpointId: Opti trait DynamicEndpointProvider { def create(bankId:Option[String], userId: String, swaggerString: String): Box[DynamicEndpointT] - def update(dynamicEndpointId: String, swaggerString: String): Box[DynamicEndpointT] - def updateHost(dynamicEndpointId: String, hostString: String): Box[DynamicEndpointT] - def get(dynamicEndpointId: String): Box[DynamicEndpointT] - def getAll(): List[DynamicEndpointT] + def update(bankId:Option[String], dynamicEndpointId: String, swaggerString: String): Box[DynamicEndpointT] + def updateHost(bankId:Option[String], dynamicEndpointId: String, hostString: String): Box[DynamicEndpointT] + def get(bankId:Option[String],dynamicEndpointId: String): Box[DynamicEndpointT] + def getAll(bankId:Option[String]): List[DynamicEndpointT] def getDynamicEndpointsByUserId(userId: String): List[DynamicEndpointT] - def delete(dynamicEndpointId: String): Boolean + def delete(bankId:Option[String], dynamicEndpointId: String): Boolean } \ No newline at end of file diff --git a/obp-api/src/main/scala/code/dynamicEndpoint/MapppedDynamicEndpointProvider.scala b/obp-api/src/main/scala/code/dynamicEndpoint/MapppedDynamicEndpointProvider.scala index 7a2248e09..bd18a1659 100644 --- a/obp-api/src/main/scala/code/dynamicEndpoint/MapppedDynamicEndpointProvider.scala +++ b/obp-api/src/main/scala/code/dynamicEndpoint/MapppedDynamicEndpointProvider.scala @@ -28,30 +28,66 @@ object MappedDynamicEndpointProvider extends DynamicEndpointProvider with Custom .saveMe() } } - override def update(dynamicEndpointId: String, swaggerString: String): Box[DynamicEndpointT] = { - DynamicEndpoint.find(By(DynamicEndpoint.DynamicEndpointId, dynamicEndpointId)).map(_.SwaggerString(swaggerString).saveMe()) + override def update(bankId:Option[String], dynamicEndpointId: String, swaggerString: String): Box[DynamicEndpointT] = { + (if (bankId.isEmpty) + DynamicEndpoint.find(By(DynamicEndpoint.DynamicEndpointId, dynamicEndpointId)) + else + DynamicEndpoint.find( + By(DynamicEndpoint.DynamicEndpointId, dynamicEndpointId), + By(DynamicEndpoint.BankId, bankId.getOrElse("")) + ) + ).map(_.SwaggerString(swaggerString).saveMe()) + + } - override def updateHost(dynamicEndpointId: String, hostString: String): Box[DynamicEndpointT] = { - DynamicEndpoint.find(By(DynamicEndpoint.DynamicEndpointId, dynamicEndpointId)) - .map(dynamicEndpoint => { + override def updateHost(bankId: Option[String], dynamicEndpointId: String, hostString: String): Box[DynamicEndpointT] = { + (if (bankId.isEmpty) + DynamicEndpoint.find(By(DynamicEndpoint.DynamicEndpointId, dynamicEndpointId)) + else + DynamicEndpoint.find( + By(DynamicEndpoint.DynamicEndpointId, dynamicEndpointId), + By(DynamicEndpoint.BankId, bankId.getOrElse("")) + ) + ).map(dynamicEndpoint => { dynamicEndpoint.SwaggerString(json.compactRender(json.parse(dynamicEndpoint.swaggerString).replace("host" :: Nil, JString(hostString)))).saveMe() } ) } - override def get(dynamicEndpointId: String): Box[DynamicEndpointT] = DynamicEndpoint.find(By(DynamicEndpoint.DynamicEndpointId, dynamicEndpointId)) + override def get(bankId: Option[String], dynamicEndpointId: String): Box[DynamicEndpointT] = { + if (bankId.isEmpty) + DynamicEndpoint.find(By(DynamicEndpoint.DynamicEndpointId, dynamicEndpointId)) + else + DynamicEndpoint.find( + By(DynamicEndpoint.DynamicEndpointId, dynamicEndpointId), + By(DynamicEndpoint.BankId, bankId.getOrElse("")) + ) + + } - override def getAll(): List[DynamicEndpointT] = { + override def getAll(bankId: Option[String]): List[DynamicEndpointT] = { var cacheKey = (randomUUID().toString, randomUUID().toString, randomUUID().toString) CacheKeyFromArguments.buildCacheKey { Caching.memoizeSyncWithProvider (Some(cacheKey.toString())) (dynamicEndpointTTL second) { - DynamicEndpoint.findAll() - }} + if (bankId.isEmpty) + DynamicEndpoint.findAll() + else + DynamicEndpoint.findAll(By(DynamicEndpoint.BankId, bankId.getOrElse(""))) + } + } } override def getDynamicEndpointsByUserId(userId: String): List[DynamicEndpointT] = DynamicEndpoint.findAll(By(DynamicEndpoint.UserId, userId)) - override def delete(dynamicEndpointId: String): Boolean = DynamicEndpoint.bulkDelete_!!(By(DynamicEndpoint.DynamicEndpointId, dynamicEndpointId)) + override def delete(bankId: Option[String], dynamicEndpointId: String): Boolean = { + if (bankId.isEmpty) + DynamicEndpoint.bulkDelete_!!(By(DynamicEndpoint.DynamicEndpointId, dynamicEndpointId)) + else + DynamicEndpoint.bulkDelete_!!( + By(DynamicEndpoint.DynamicEndpointId, dynamicEndpointId), + By(DynamicEndpoint.BankId, bankId.getOrElse("")) + ) + } } From e49b7dcbce7573c2d9cf001ef8889feec5db2683 Mon Sep 17 00:00:00 2001 From: hongwei Date: Wed, 9 Jun 2021 17:13:52 +0200 Subject: [PATCH 031/147] test/fixed the failed tests --- .../ResourceDocsAPIMethods.scala | 9 ++-- .../scala/code/api/v4_0_0/APIMethods400.scala | 14 +++--- .../api/v3_1_0/AccountAttributeTest.scala | 9 +++- .../code/api/v3_1_0/CustomerAddressTest.scala | 12 +++-- .../scala/code/api/v3_1_0/CustomerTest.scala | 44 ++++++++++++++----- .../api/v3_1_0/ProductAttributeTest.scala | 16 +++++-- .../code/api/v3_1_0/TaxResidenceTest.scala | 12 +++-- .../scala/code/api/v3_1_0/WebhooksTest.scala | 12 +++-- .../api/v4_0_0/DeleteAccountCascadeTest.scala | 4 +- .../api/v4_0_0/DeleteProductCascadeTest.scala | 4 +- .../v4_0_0/DeleteTransactionCascadeTest.scala | 4 +- .../code/api/v4_0_0/DirectDebitTest.scala | 4 +- .../code/api/v4_0_0/DynamicEntityTest.scala | 10 ++++- .../code/api/v4_0_0/EndpointMappingTest.scala | 14 +++++- .../api/v4_0_0/ForceErrorValidationTest.scala | 5 ++- .../code/api/v4_0_0/StandingOrderTest.scala | 4 +- .../api/v4_0_0/UserCustomerLinkTest.scala | 12 +++-- 17 files changed, 140 insertions(+), 49 deletions(-) diff --git a/obp-api/src/main/scala/code/api/ResourceDocs1_4_0/ResourceDocsAPIMethods.scala b/obp-api/src/main/scala/code/api/ResourceDocs1_4_0/ResourceDocsAPIMethods.scala index b37032b76..cfd205fa1 100644 --- a/obp-api/src/main/scala/code/api/ResourceDocs1_4_0/ResourceDocsAPIMethods.scala +++ b/obp-api/src/main/scala/code/api/ResourceDocs1_4_0/ResourceDocsAPIMethods.scala @@ -582,16 +582,19 @@ trait ResourceDocsAPIMethods extends MdcLoggable with APIMethods220 with APIMeth * dynamic endpoints related structure is not STABLE structure, no need be parsed to a static structure. * So here filter out them. */ - case doc if doc.partialFunctionName == nameOf(APIMethods400.Implementations4_0_0.createDynamicEndpoint) => + case doc if (doc.partialFunctionName == nameOf(APIMethods400.Implementations4_0_0.createDynamicEndpoint) || + doc.partialFunctionName == nameOf(APIMethods400.Implementations4_0_0.createBankLevelDynamicEndpoint) ) => doc.copy(exampleRequestBody = ExampleValue.dynamicEndpointRequestBodyEmptyExample, successResponseBody = ExampleValue.dynamicEndpointResponseBodyEmptyExample ) - case doc if doc.partialFunctionName == nameOf(APIMethods400.Implementations4_0_0.getDynamicEndpoint) => + case doc if ( doc.partialFunctionName == nameOf(APIMethods400.Implementations4_0_0.getDynamicEndpoint) || + doc.partialFunctionName == nameOf(APIMethods400.Implementations4_0_0.getBankLevelDynamicEndpoint)) => doc.copy(successResponseBody = ExampleValue.dynamicEndpointResponseBodyEmptyExample) case doc if (doc.partialFunctionName == nameOf(APIMethods400.Implementations4_0_0.getDynamicEndpoints) || - doc.partialFunctionName == nameOf(APIMethods400.Implementations4_0_0.getMyDynamicEndpoints) + doc.partialFunctionName == nameOf(APIMethods400.Implementations4_0_0.getMyDynamicEndpoints) || + doc.partialFunctionName == nameOf(APIMethods400.Implementations4_0_0.getBankLevelDynamicEndpoints) )=> doc.copy(successResponseBody = ListResult( "dynamic_endpoints", 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 8eb1f4549..657d7e586 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 @@ -1893,7 +1893,7 @@ trait APIMethods400 { |${ReferenceType.referenceTypeAndExample.mkString("\n")} |``` |""", - dynamicEntityRequestBodyExample, + dynamicEntityRequestBodyExample.copy(bankId = None), dynamicEntityResponseBodyExample, List( $UserNotLoggedIn, @@ -2077,7 +2077,7 @@ trait APIMethods400 { |${ReferenceType.referenceTypeAndExample.mkString("\n")} |``` |""", - dynamicEntityRequestBodyExample, + dynamicEntityRequestBodyExample.copy(bankId=None), dynamicEntityResponseBodyExample, List( $UserNotLoggedIn, @@ -4649,7 +4649,7 @@ trait APIMethods400 { nameOf(createBankLevelDynamicEndpoint), "POST", "/management/banks/BANK_ID/dynamic-endpoints", - " Create Bank Level Dynamic Endpoint", + "Create Bank Level Dynamic Endpoint", s"""Create dynamic endpoints. | |Create dynamic endpoints with one json format swagger content. @@ -8257,7 +8257,7 @@ trait APIMethods400 { | |Note: at moment only support the dynamic endpoints |""", - endpointMappingJson.copy(endpointMappingId = None), + endpointMappingJson.copy(endpointMappingId = None, bankId = None), endpointMappingJson, List( $UserNotLoggedIn, @@ -8298,7 +8298,7 @@ trait APIMethods400 { "Update Endpoint Mapping", s"""Update an Endpoint Mapping. |""", - endpointMappingJson.copy(endpointMappingId = None), + endpointMappingJson.copy(endpointMappingId = None, bankId = None), endpointMappingJson, List( $UserNotLoggedIn, @@ -8453,7 +8453,7 @@ trait APIMethods400 { | |Note: at moment only support the dynamic endpoints |""", - endpointMappingJson.copy(endpointMappingId = None), + endpointMappingJson.copy(endpointMappingId = None, bankId = None), endpointMappingJson, List( $UserNotLoggedIn, @@ -8480,7 +8480,7 @@ trait APIMethods400 { "Update Bank Level Endpoint Mapping", s"""Update an Bank Level Endpoint Mapping. |""", - endpointMappingJson.copy(endpointMappingId = None), + endpointMappingJson.copy(endpointMappingId = None, bankId = None), endpointMappingJson, List( $UserNotLoggedIn, diff --git a/obp-api/src/test/scala/code/api/v3_1_0/AccountAttributeTest.scala b/obp-api/src/test/scala/code/api/v3_1_0/AccountAttributeTest.scala index 896c02548..a4a2f4475 100644 --- a/obp-api/src/test/scala/code/api/v3_1_0/AccountAttributeTest.scala +++ b/obp-api/src/test/scala/code/api/v3_1_0/AccountAttributeTest.scala @@ -131,7 +131,10 @@ class AccountAttributeTest extends V310ServerSetup { response310.code should equal(403) val errorMessageText = UserHasMissingRoles + canCreateAccountAttributeAtOneBank And("error should be " + errorMessageText) - response310.body.extract[ErrorMessage].message should equal (errorMessageText) + val errorMessage = response310.body.extract[ErrorMessage].message + errorMessage contains (UserHasMissingRoles) should be (true) + errorMessage contains (canCreateAccountAttributeAtOneBank.toString()) should be (true) + } scenario("We will call the Create endpoint but wrong `type` ", ApiEndpoint1, VersionOfApi) { When(s"We make a request $VersionOfApi") @@ -163,7 +166,9 @@ class AccountAttributeTest extends V310ServerSetup { response310.code should equal(403) val errorMessageText = UserHasMissingRoles + canUpdateAccountAttribute And("error should be " + errorMessageText) - response310.body.extract[ErrorMessage].message should equal (errorMessageText) + val errorMessage = response310.body.extract[ErrorMessage].message + errorMessage contains (UserHasMissingRoles) should be (true) + errorMessage contains (canUpdateAccountAttribute.toString()) should be (true) } } diff --git a/obp-api/src/test/scala/code/api/v3_1_0/CustomerAddressTest.scala b/obp-api/src/test/scala/code/api/v3_1_0/CustomerAddressTest.scala index 49cf692e4..de6dfceac 100644 --- a/obp-api/src/test/scala/code/api/v3_1_0/CustomerAddressTest.scala +++ b/obp-api/src/test/scala/code/api/v3_1_0/CustomerAddressTest.scala @@ -88,7 +88,9 @@ class CustomerAddressTest extends V310ServerSetup { Then("We should get a 403") response310.code should equal(403) And("error should be " + UserHasMissingRoles + CanCreateCustomerAddress) - response310.body.extract[ErrorMessage].message should equal (UserHasMissingRoles + CanCreateCustomerAddress) + val errorMessage = response310.body.extract[ErrorMessage].message + errorMessage contains (UserHasMissingRoles) should be (true) + errorMessage contains (CanCreateCustomerAddress.toString()) should be (true) } scenario("We will call the Get endpoint without a user credentials", ApiEndpoint2, VersionOfApi) { @@ -107,7 +109,9 @@ class CustomerAddressTest extends V310ServerSetup { Then("We should get a 403") response310.code should equal(403) And("error should be " + UserHasMissingRoles + CanGetCustomerAddress) - response310.body.extract[ErrorMessage].message should equal (UserHasMissingRoles + CanGetCustomerAddress) + val errorMessage = response310.body.extract[ErrorMessage].message + errorMessage contains (UserHasMissingRoles) should be (true) + errorMessage contains (CanGetCustomerAddress.toString()) should be (true) } scenario("We will call the Delete endpoint without a user credentials", ApiEndpoint3, VersionOfApi) { @@ -126,7 +130,9 @@ class CustomerAddressTest extends V310ServerSetup { Then("We should get a 403") response310.code should equal(403) And("error should be " + UserHasMissingRoles + CanDeleteCustomerAddress) - response310.body.extract[ErrorMessage].message should equal (UserHasMissingRoles + CanDeleteCustomerAddress) + val errorMessage = response310.body.extract[ErrorMessage].message + errorMessage contains (UserHasMissingRoles) should be (true) + errorMessage contains (CanDeleteCustomerAddress.toString()) should be (true) } scenario("We will call the Add, Get and Delete endpoints with user credentials and role", ApiEndpoint1, ApiEndpoint2, ApiEndpoint3, ApiEndpoint4, VersionOfApi) { diff --git a/obp-api/src/test/scala/code/api/v3_1_0/CustomerTest.scala b/obp-api/src/test/scala/code/api/v3_1_0/CustomerTest.scala index 6bf82da70..cedbe445b 100644 --- a/obp-api/src/test/scala/code/api/v3_1_0/CustomerTest.scala +++ b/obp-api/src/test/scala/code/api/v3_1_0/CustomerTest.scala @@ -104,7 +104,9 @@ class CustomerTest extends V310ServerSetup { response310.code should equal(403) val errorMsg = UserHasMissingRoles + canCreateCustomer + " or " + canCreateCustomerAtAnyBank And("error should be " + errorMsg) - response310.body.extract[ErrorMessage].message should equal (errorMsg) + val errorMessage = response310.body.extract[ErrorMessage].message + errorMessage contains (UserHasMissingRoles) should be (true) + errorMessage contains (canCreateCustomerAtAnyBank.toString()) should be (true) } scenario("We will call the endpoint with a user credentials and a proper role", ApiEndpoint3, VersionOfApi) { Entitlement.entitlement.vend.addEntitlement(bankId, resourceUser1.userId, CanCreateCustomer.toString) @@ -137,7 +139,9 @@ class CustomerTest extends V310ServerSetup { Then("We should get a 403") response310.code should equal(403) And("error should be " + UserHasMissingRoles + CanGetCustomer) - response310.body.extract[ErrorMessage].message should equal (UserHasMissingRoles + CanGetCustomer) + val errorMessage = response310.body.extract[ErrorMessage].message + errorMessage contains (UserHasMissingRoles) should be (true) + errorMessage contains (CanGetCustomer.toString()) should be (true) } scenario("We will call the endpoint with the proper Role " + canGetCustomer, ApiEndpoint1, VersionOfApi) { Entitlement.entitlement.vend.addEntitlement(bankId, resourceUser1.userId, CanGetCustomer.toString) @@ -171,7 +175,9 @@ class CustomerTest extends V310ServerSetup { Then("We should get a 403") response310.code should equal(403) And("error should be " + UserHasMissingRoles + CanGetCustomer) - response310.body.extract[ErrorMessage].message should equal (UserHasMissingRoles + CanGetCustomer) + val errorMessage = response310.body.extract[ErrorMessage].message + errorMessage contains (UserHasMissingRoles) should be (true) + errorMessage contains (CanGetCustomer.toString()) should be (true) } scenario("We will call the endpoint with the proper Role " + canGetCustomer, ApiEndpoint2, VersionOfApi) { Entitlement.entitlement.vend.addEntitlement(bankId, resourceUser1.userId, CanGetCustomer.toString) @@ -205,7 +211,9 @@ class CustomerTest extends V310ServerSetup { response310.code should equal(403) val errorMsg = UserHasMissingRoles + canUpdateCustomerEmail And("error should be " + errorMsg) - response310.body.extract[ErrorMessage].message should equal (errorMsg) + val errorMessage = response310.body.extract[ErrorMessage].message + errorMessage contains (UserHasMissingRoles) should be (true) + errorMessage contains (canUpdateCustomerEmail.toString()) should be (true) } scenario("We will call the endpoint with user credentials and the proper role", ApiEndpoint3, ApiEndpoint4, VersionOfApi) { Entitlement.entitlement.vend.addEntitlement(bankId, resourceUser1.userId, CanCreateCustomer.toString) @@ -248,7 +256,9 @@ class CustomerTest extends V310ServerSetup { response310.code should equal(403) val errorMsg = UserHasMissingRoles + canUpdateCustomerMobilePhoneNumber And("error should be " + errorMsg) - response310.body.extract[ErrorMessage].message should equal (errorMsg) + val errorMessage = response310.body.extract[ErrorMessage].message + errorMessage contains (UserHasMissingRoles) should be (true) + errorMessage contains (canUpdateCustomerMobilePhoneNumber.toString()) should be (true) } scenario("We will call the endpoint with user credentials and the proper role", ApiEndpoint5, VersionOfApi) { Entitlement.entitlement.vend.addEntitlement(bankId, resourceUser1.userId, CanCreateCustomer.toString) @@ -292,7 +302,9 @@ class CustomerTest extends V310ServerSetup { response310.code should equal(403) val errorMsg = UserHasMissingRoles + canUpdateCustomerIdentity And("error should be " + errorMsg) - response310.body.extract[ErrorMessage].message should equal (errorMsg) + val errorMessage = response310.body.extract[ErrorMessage].message + errorMessage contains (UserHasMissingRoles) should be (true) + errorMessage contains (canUpdateCustomerIdentity.toString()) should be (true) } scenario("We will call the endpoint with user credentials and the proper role", ApiEndpoint6, VersionOfApi) { Entitlement.entitlement.vend.addEntitlement(bankId, resourceUser1.userId, CanCreateCustomer.toString) @@ -339,7 +351,9 @@ class CustomerTest extends V310ServerSetup { response310.code should equal(403) val errorMsg = UserHasMissingRoles + canUpdateCustomerCreditLimit And("error should be " + errorMsg) - response310.body.extract[ErrorMessage].message should equal (errorMsg) + val errorMessage = response310.body.extract[ErrorMessage].message + errorMessage contains (UserHasMissingRoles) should be (true) + errorMessage contains (canUpdateCustomerCreditLimit.toString()) should be (true) } scenario("We will call the endpoint with user credentials and the proper role", ApiEndpoint7, VersionOfApi) { Entitlement.entitlement.vend.addEntitlement(bankId, resourceUser1.userId, CanCreateCustomer.toString) @@ -384,7 +398,9 @@ class CustomerTest extends V310ServerSetup { response310.code should equal(403) val errorMsg = UserHasMissingRoles + canUpdateCustomerCreditRatingAndSource And("error should be " + errorMsg) - response310.body.extract[ErrorMessage].message should equal (errorMsg) + val errorMessage = response310.body.extract[ErrorMessage].message + errorMessage contains (UserHasMissingRoles) should be (true) + errorMessage contains (canUpdateCustomerCreditRatingAndSource.toString()) should be (true) } scenario("We will call the endpoint with user credentials and the proper role", ApiEndpoint8, VersionOfApi) { Entitlement.entitlement.vend.addEntitlement(bankId, resourceUser1.userId, CanCreateCustomer.toString) @@ -429,7 +445,9 @@ class CustomerTest extends V310ServerSetup { response310.code should equal(403) val errorMsg = UserHasMissingRoles + canUpdateCustomerBranch And("error should be " + errorMsg) - response310.body.extract[ErrorMessage].message should equal (errorMsg) + val errorMessage = response310.body.extract[ErrorMessage].message + errorMessage contains (UserHasMissingRoles) should be (true) + errorMessage contains (canUpdateCustomerBranch.toString()) should be (true) } scenario("We will call the endpoint with user credentials and the proper role", ApiEndpoint9, VersionOfApi) { Entitlement.entitlement.vend.addEntitlement(bankId, resourceUser1.userId, CanCreateCustomer.toString) @@ -473,7 +491,9 @@ class CustomerTest extends V310ServerSetup { response310.code should equal(403) val errorMsg = UserHasMissingRoles + canUpdateCustomerData And("error should be " + errorMsg) - response310.body.extract[ErrorMessage].message should equal (errorMsg) + val errorMessage = response310.body.extract[ErrorMessage].message + errorMessage contains (UserHasMissingRoles) should be (true) + errorMessage contains (canUpdateCustomerData.toString()) should be (true) } scenario("We will call the endpoint with user credentials and the proper role", ApiEndpoint10, VersionOfApi) { Entitlement.entitlement.vend.addEntitlement(bankId, resourceUser1.userId, CanCreateCustomer.toString) @@ -521,7 +541,9 @@ class CustomerTest extends V310ServerSetup { response310.code should equal(403) val errorMsg = UserHasMissingRoles + canUpdateCustomerNumber And("error should be " + errorMsg) - response310.body.extract[ErrorMessage].message should equal (errorMsg) + val errorMessage = response310.body.extract[ErrorMessage].message + errorMessage contains (UserHasMissingRoles) should be (true) + errorMessage contains (canUpdateCustomerNumber.toString()) should be (true) } scenario("We will call the endpoint with user credentials and the proper role", ApiEndpoint3, ApiEndpoint11, VersionOfApi) { Entitlement.entitlement.vend.addEntitlement(bankId, resourceUser1.userId, CanCreateCustomer.toString) diff --git a/obp-api/src/test/scala/code/api/v3_1_0/ProductAttributeTest.scala b/obp-api/src/test/scala/code/api/v3_1_0/ProductAttributeTest.scala index 5b1989b3d..d9bdf60b6 100644 --- a/obp-api/src/test/scala/code/api/v3_1_0/ProductAttributeTest.scala +++ b/obp-api/src/test/scala/code/api/v3_1_0/ProductAttributeTest.scala @@ -144,7 +144,9 @@ class ProductAttributeTest extends V310ServerSetup { response310.code should equal(403) val createProductEntitlementsRequiredText = UserHasMissingRoles + canCreateProductAttribute And("error should be " + createProductEntitlementsRequiredText) - response310.body.extract[ErrorMessage].message should equal (createProductEntitlementsRequiredText) + val errorMessage = response310.body.extract[ErrorMessage].message + errorMessage contains (UserHasMissingRoles) should be (true) + errorMessage contains (createProductEntitlementsRequiredText.toString()) should be (true) } scenario("We will call the Create endpoint but wrong `type` ", ApiEndpoint1, VersionOfApi) { When("We make a request v3.1.0") @@ -176,7 +178,9 @@ class ProductAttributeTest extends V310ServerSetup { response310.code should equal(403) val createProductEntitlementsRequiredText = UserHasMissingRoles + canGetProductAttribute And("error should be " + createProductEntitlementsRequiredText) - response310.body.extract[ErrorMessage].message should equal (createProductEntitlementsRequiredText) + val errorMessage = response310.body.extract[ErrorMessage].message + errorMessage contains (UserHasMissingRoles) should be (true) + errorMessage contains (canGetProductAttribute.toString()) should be (true) } } @@ -198,7 +202,9 @@ class ProductAttributeTest extends V310ServerSetup { response310.code should equal(403) val createProductEntitlementsRequiredText = UserHasMissingRoles + canUpdateProductAttribute And("error should be " + createProductEntitlementsRequiredText) - response310.body.extract[ErrorMessage].message should equal (createProductEntitlementsRequiredText) + val errorMessage = response310.body.extract[ErrorMessage].message + errorMessage contains (UserHasMissingRoles) should be (true) + errorMessage contains (createProductEntitlementsRequiredText.toString()) should be (true) } } @@ -220,7 +226,9 @@ class ProductAttributeTest extends V310ServerSetup { response310.code should equal(403) val createProductEntitlementsRequiredText = UserHasMissingRoles + canDeleteProductAttribute And("error should be " + createProductEntitlementsRequiredText) - response310.body.extract[ErrorMessage].message should equal (createProductEntitlementsRequiredText) + val errorMessage = response310.body.extract[ErrorMessage].message + errorMessage contains (UserHasMissingRoles) should be (true) + errorMessage contains (canDeleteProductAttribute.toString()) should be (true) } } diff --git a/obp-api/src/test/scala/code/api/v3_1_0/TaxResidenceTest.scala b/obp-api/src/test/scala/code/api/v3_1_0/TaxResidenceTest.scala index 288e06d31..99318c57d 100644 --- a/obp-api/src/test/scala/code/api/v3_1_0/TaxResidenceTest.scala +++ b/obp-api/src/test/scala/code/api/v3_1_0/TaxResidenceTest.scala @@ -98,7 +98,9 @@ class TaxResidenceTest extends V310ServerSetup { Then("We should get a 403") response310.code should equal(403) And("error should be " + UserHasMissingRoles + CanCreateTaxResidence) - response310.body.extract[ErrorMessage].message should equal (UserHasMissingRoles + CanCreateTaxResidence) + val errorMessage = response310.body.extract[ErrorMessage].message + errorMessage contains (UserHasMissingRoles) should be (true) + errorMessage contains (CanCreateTaxResidence.toString()) should be (true) } scenario("We will call the endpoint with the proper Role " + canCreateTaxResidence, ApiEndpoint1, VersionOfApi) { Entitlement.entitlement.vend.addEntitlement(bankId, resourceUser1.userId, CanCreateTaxResidence.toString) @@ -159,7 +161,9 @@ class TaxResidenceTest extends V310ServerSetup { Then("We should get a 403") response310.code should equal(403) And("error should be " + UserHasMissingRoles + CanGetTaxResidence) - response310.body.extract[ErrorMessage].message should equal (UserHasMissingRoles + CanGetTaxResidence) + val errorMessage = response310.body.extract[ErrorMessage].message + errorMessage contains (UserHasMissingRoles) should be (true) + errorMessage contains (CanGetTaxResidence.toString()) should be (true) } scenario("We will call the endpoint with the proper Role " + canGetTaxResidence, ApiEndpoint2, VersionOfApi) { Entitlement.entitlement.vend.addEntitlement(bankId, resourceUser1.userId, CanGetTaxResidence.toString) @@ -182,7 +186,9 @@ class TaxResidenceTest extends V310ServerSetup { Then("We should get a 403") response310.code should equal(403) And("error should be " + UserHasMissingRoles + CanDeleteTaxResidence) - response310.body.extract[ErrorMessage].message should equal (UserHasMissingRoles + CanDeleteTaxResidence) + val errorMessage = response310.body.extract[ErrorMessage].message + errorMessage contains (UserHasMissingRoles) should be (true) + errorMessage contains (CanDeleteTaxResidence.toString()) should be (true) } scenario("We will call the endpoint with the proper Role " + canDeleteTaxResidence, ApiEndpoint3, VersionOfApi) { Entitlement.entitlement.vend.addEntitlement(bankId, resourceUser1.userId, CanDeleteTaxResidence.toString) diff --git a/obp-api/src/test/scala/code/api/v3_1_0/WebhooksTest.scala b/obp-api/src/test/scala/code/api/v3_1_0/WebhooksTest.scala index 721cc4150..b6dced98f 100644 --- a/obp-api/src/test/scala/code/api/v3_1_0/WebhooksTest.scala +++ b/obp-api/src/test/scala/code/api/v3_1_0/WebhooksTest.scala @@ -80,7 +80,9 @@ class WebhooksTest extends V310ServerSetup { Then("We should get a 403") response310.code should equal(403) And("error should be " + UserHasMissingRoles + CanCreateWebhook) - response310.body.extract[ErrorMessage].message should equal (UserHasMissingRoles + CanCreateWebhook) + val errorMessage = response310.body.extract[ErrorMessage].message + errorMessage contains (UserHasMissingRoles) should be (true) + errorMessage contains (CanCreateWebhook.toString()) should be (true) } scenario("We will try to create the web hook with a proper Role " + canCreateWebhook + " but without proper trigger name", ApiEndpoint2, VersionOfApi) { @@ -131,7 +133,9 @@ class WebhooksTest extends V310ServerSetup { Then("We should get a 403") response310.code should equal(403) And("error should be " + UserHasMissingRoles + CanGetWebhooks) - response310.body.extract[ErrorMessage].message should equal (UserHasMissingRoles + CanGetWebhooks) + val errorMessage = response310.body.extract[ErrorMessage].message + errorMessage contains (UserHasMissingRoles) should be (true) + errorMessage contains (CanGetWebhooks.toString()) should be (true) } scenario("We will try to get web hooks with a proper Role " + canGetWebhooks, ApiEndpoint1, VersionOfApi) { val bankId = randomBankId @@ -155,7 +159,9 @@ class WebhooksTest extends V310ServerSetup { Then("We should get a 403") response310.code should equal(403) And("error should be " + UserHasMissingRoles + CanUpdateWebhook) - response310.body.extract[ErrorMessage].message should equal (UserHasMissingRoles + CanUpdateWebhook) + val errorMessage = response310.body.extract[ErrorMessage].message + errorMessage contains (UserHasMissingRoles) should be (true) + errorMessage contains (CanUpdateWebhook.toString()) should be (true) } scenario("We will try to Update an Account Web Hook with a proper Role " + canUpdateWebhook, ApiEndpoint3, VersionOfApi) { val bankId = randomBankId diff --git a/obp-api/src/test/scala/code/api/v4_0_0/DeleteAccountCascadeTest.scala b/obp-api/src/test/scala/code/api/v4_0_0/DeleteAccountCascadeTest.scala index 1ebdf26e1..bb4349d59 100644 --- a/obp-api/src/test/scala/code/api/v4_0_0/DeleteAccountCascadeTest.scala +++ b/obp-api/src/test/scala/code/api/v4_0_0/DeleteAccountCascadeTest.scala @@ -51,7 +51,9 @@ class DeleteAccountCascadeTest extends V400ServerSetup { val response400 = makeDeleteRequest(request400) Then("We should get a 403") response400.code should equal(403) - response400.body.extract[ErrorMessage].message should equal(UserHasMissingRoles + CanDeleteAccountCascade) + val errorMessage = response400.body.extract[ErrorMessage].message + errorMessage contains (UserHasMissingRoles) should be (true) + errorMessage contains (CanDeleteAccountCascade.toString()) should be (true) } } feature(s"test $ApiEndpoint1 - Authorized access") { diff --git a/obp-api/src/test/scala/code/api/v4_0_0/DeleteProductCascadeTest.scala b/obp-api/src/test/scala/code/api/v4_0_0/DeleteProductCascadeTest.scala index 568eac41a..30ee154ae 100644 --- a/obp-api/src/test/scala/code/api/v4_0_0/DeleteProductCascadeTest.scala +++ b/obp-api/src/test/scala/code/api/v4_0_0/DeleteProductCascadeTest.scala @@ -50,7 +50,9 @@ class DeleteProductCascadeTest extends V400ServerSetup { val response400 = makeDeleteRequest(request400) Then("We should get a 403") response400.code should equal(403) - response400.body.extract[ErrorMessage].message should equal(UserHasMissingRoles + CanDeleteProductCascade) + val errorMessage = response400.body.extract[ErrorMessage].message + errorMessage contains (UserHasMissingRoles) should be (true) + errorMessage contains (CanDeleteProductCascade.toString()) should be (true) } } diff --git a/obp-api/src/test/scala/code/api/v4_0_0/DeleteTransactionCascadeTest.scala b/obp-api/src/test/scala/code/api/v4_0_0/DeleteTransactionCascadeTest.scala index 8c6644bf9..da2cc96ba 100644 --- a/obp-api/src/test/scala/code/api/v4_0_0/DeleteTransactionCascadeTest.scala +++ b/obp-api/src/test/scala/code/api/v4_0_0/DeleteTransactionCascadeTest.scala @@ -52,7 +52,9 @@ class DeleteTransactionCascadeTest extends V400ServerSetup { val response400 = makeDeleteRequest(request400) Then("We should get a 403") response400.code should equal(403) - response400.body.extract[ErrorMessage].message should equal(UserHasMissingRoles + CanDeleteTransactionCascade) + val errorMessage = response400.body.extract[ErrorMessage].message + errorMessage contains (UserHasMissingRoles) should be (true) + errorMessage contains (CanDeleteTransactionCascade.toString()) should be (true) } } feature(s"test $ApiEndpoint1 - Authorized access") { diff --git a/obp-api/src/test/scala/code/api/v4_0_0/DirectDebitTest.scala b/obp-api/src/test/scala/code/api/v4_0_0/DirectDebitTest.scala index 16446f749..2ab92e80b 100644 --- a/obp-api/src/test/scala/code/api/v4_0_0/DirectDebitTest.scala +++ b/obp-api/src/test/scala/code/api/v4_0_0/DirectDebitTest.scala @@ -67,7 +67,9 @@ class DirectDebitTest extends V400ServerSetup { val response400 = makePostRequest(request400, write(postDirectDebitJsonV400)) Then("We should get a 403") response400.code should equal(403) - response400.body.extract[ErrorMessage].message should equal(UserHasMissingRoles + CanCreateDirectDebitAtOneBank) + val errorMessage = response400.body.extract[ErrorMessage].message + errorMessage contains (UserHasMissingRoles) should be (true) + errorMessage contains (CanCreateDirectDebitAtOneBank.toString()) should be (true) } } diff --git a/obp-api/src/test/scala/code/api/v4_0_0/DynamicEntityTest.scala b/obp-api/src/test/scala/code/api/v4_0_0/DynamicEntityTest.scala index 6d8878dd2..09b3abb41 100644 --- a/obp-api/src/test/scala/code/api/v4_0_0/DynamicEntityTest.scala +++ b/obp-api/src/test/scala/code/api/v4_0_0/DynamicEntityTest.scala @@ -514,7 +514,10 @@ class DynamicEntityTest extends V400ServerSetup { Then("We should get a 403") responseGet.code should equal(403) And("error should be " + UserHasMissingRoles + CanGetBankLevelDynamicEntities) - responseGet.body.extract[ErrorMessage].message should equal (UserHasMissingRoles + CanGetBankLevelDynamicEntities) + val errorMessage = responseGet.body.extract[ErrorMessage].message + errorMessage contains UserHasMissingRoles should be (true) + errorMessage contains CanGetBankLevelDynamicEntities.toString() should be (true) + errorMessage contains CanGetDynamicEntities.toString() should be (true) { Then("We grant the role and call it again") @@ -536,7 +539,10 @@ class DynamicEntityTest extends V400ServerSetup { Then("We should get a 403") responseGet.code should equal(403) And("error should be " + UserHasMissingRoles + CanGetBankLevelDynamicEntities) - responseGet.body.extract[ErrorMessage].message should equal (UserHasMissingRoles + CanGetBankLevelDynamicEntities) + val errorMessage = responseGet.body.extract[ErrorMessage].message + errorMessage contains UserHasMissingRoles should be (true) + errorMessage contains CanGetBankLevelDynamicEntities.toString() should be (true) + errorMessage contains CanGetDynamicEntities.toString() should be (true) { Entitlement.entitlement.vend.addEntitlement(testBankId2.value, resourceUser1.userId, CanGetBankLevelDynamicEntities.toString) diff --git a/obp-api/src/test/scala/code/api/v4_0_0/EndpointMappingTest.scala b/obp-api/src/test/scala/code/api/v4_0_0/EndpointMappingTest.scala index faf02e5c4..3760909de 100644 --- a/obp-api/src/test/scala/code/api/v4_0_0/EndpointMappingTest.scala +++ b/obp-api/src/test/scala/code/api/v4_0_0/EndpointMappingTest.scala @@ -66,7 +66,7 @@ class EndpointMappingTest extends V400ServerSetup { } } feature("Delete the EndpointMapping specified by METHOD_ROUTING_ID v4.0.0- Unauthorized access") { - scenario("We will call the endpoint without user credentials", ApiEndpoint4, VersionOfApi) { + 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 / "management" / "endpoint-mappings" / "METHOD_ROUTING_ID").DELETE val response400 = makeDeleteRequest(request400) @@ -104,12 +104,22 @@ class EndpointMappingTest extends V400ServerSetup { { // update success val request400 = (v4_0_0_Request / "management" / "endpoint-mappings" / customerJson.endpointMappingId.get ).PUT <@(user1) - val response400 = makePutRequest(request400, write(customerJson.copy(operationId = "properId"))) + val response400 = makePutRequest(request400, write(customerJson.copy(requestMapping = "{}"))) Then("We should get a 201") response400.code should equal(201) val endpointMappingsJson = response400.body.extract[EndpointMappingCommons] } + { + // error case, can not update with different operationid + val request400 = (v4_0_0_Request / "management" / "endpoint-mappings" / customerJson.endpointMappingId.get ).PUT <@(user1) + val response400 = makePutRequest(request400, write(customerJson.copy(operationId = "newOperationId"))) + Then("We should get a 400") + response400.code should equal(400) + val errorMessage = response400.body.extract[ErrorMessage].message + errorMessage contains (s"$InvalidJsonFormat operation_id has to be the same in ") should be (true) + } + { // update a not exists EndpointMapping val request400 = (v4_0_0_Request / "management" / "endpoint-mappings" / "not-exists-id" ).PUT <@(user1) diff --git a/obp-api/src/test/scala/code/api/v4_0_0/ForceErrorValidationTest.scala b/obp-api/src/test/scala/code/api/v4_0_0/ForceErrorValidationTest.scala index 8cb08f2b7..a4ae46775 100644 --- a/obp-api/src/test/scala/code/api/v4_0_0/ForceErrorValidationTest.scala +++ b/obp-api/src/test/scala/code/api/v4_0_0/ForceErrorValidationTest.scala @@ -64,7 +64,10 @@ class ForceErrorValidationTest extends V400ServerSetup with PropsReset { response310.code should equal(403) val errorMsg = UserHasMissingRoles + canCreateCustomer + " or " + canCreateCustomerAtAnyBank And("error should be " + errorMsg) - response310.body.extract[ErrorMessage].message should equal(errorMsg) + val errorMessage = response310.body.extract[ErrorMessage].message + errorMessage contains(UserHasMissingRoles) should be (true) + errorMessage contains(canCreateCustomer.toString()) should be (true) + errorMessage contains(canCreateCustomerAtAnyBank.toString()) should be (true) } scenario("We will call the endpoint without user credentials", ApiEndpoint2, VersionOfApi) { diff --git a/obp-api/src/test/scala/code/api/v4_0_0/StandingOrderTest.scala b/obp-api/src/test/scala/code/api/v4_0_0/StandingOrderTest.scala index 6087392e3..70675732c 100644 --- a/obp-api/src/test/scala/code/api/v4_0_0/StandingOrderTest.scala +++ b/obp-api/src/test/scala/code/api/v4_0_0/StandingOrderTest.scala @@ -67,7 +67,9 @@ class StandingOrderTest extends V400ServerSetup { val response400 = makePostRequest(request400, write(postStandingOrderJsonV400)) Then("We should get a 403") response400.code should equal(403) - response400.body.extract[ErrorMessage].message should equal(UserHasMissingRoles + CanCreateStandingOrderAtOneBank) + val errorMessage = response400.body.extract[ErrorMessage].message + errorMessage contains (UserHasMissingRoles) should be (true) + errorMessage contains (CanCreateStandingOrderAtOneBank.toString()) should be (true) } } diff --git a/obp-api/src/test/scala/code/api/v4_0_0/UserCustomerLinkTest.scala b/obp-api/src/test/scala/code/api/v4_0_0/UserCustomerLinkTest.scala index 582ecd0f2..00f873983 100644 --- a/obp-api/src/test/scala/code/api/v4_0_0/UserCustomerLinkTest.scala +++ b/obp-api/src/test/scala/code/api/v4_0_0/UserCustomerLinkTest.scala @@ -52,7 +52,9 @@ class UserCustomerLinkTest extends V400ServerSetup { val response400 = makeGetRequest(request400) Then("We should get a 403") response400.code should equal(403) - response400.body.extract[ErrorMessage].message should equal(UserHasMissingRoles + CanGetUserCustomerLink) + val errorMessage = response400.body.extract[ErrorMessage].message + errorMessage contains (UserHasMissingRoles) should be (true) + errorMessage contains (CanGetUserCustomerLink.toString()) should be (true) } } @@ -75,7 +77,9 @@ class UserCustomerLinkTest extends V400ServerSetup { val response400 = makeGetRequest(request400) Then("We should get a 403") response400.code should equal(403) - response400.body.extract[ErrorMessage].message should equal(UserHasMissingRoles + CanGetUserCustomerLink) + val errorMessage = response400.body.extract[ErrorMessage].message + errorMessage contains (UserHasMissingRoles) should be (true) + errorMessage contains (CanGetUserCustomerLink.toString()) should be (true) } } @@ -97,7 +101,9 @@ class UserCustomerLinkTest extends V400ServerSetup { val response400 = makeDeleteRequest(request400) Then("We should get a 403") response400.code should equal(403) - response400.body.extract[ErrorMessage].message should equal(UserHasMissingRoles + CanDeleteUserCustomerLink) + val errorMessage = response400.body.extract[ErrorMessage].message + errorMessage contains (UserHasMissingRoles) should be (true) + errorMessage contains (CanDeleteUserCustomerLink.toString()) should be (true) } } From ee29d9523ef35775d9b7d034ce331bc951faa105 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marko=20Mili=C4=87?= Date: Thu, 10 Jun 2021 16:24:14 +0200 Subject: [PATCH 032/147] refactor/Get rid of unused fuction --- obp-api/src/main/scala/code/api/util/JwsUtil.scala | 1 - 1 file changed, 1 deletion(-) diff --git a/obp-api/src/main/scala/code/api/util/JwsUtil.scala b/obp-api/src/main/scala/code/api/util/JwsUtil.scala index 1c5e0f614..0b61582a4 100644 --- a/obp-api/src/main/scala/code/api/util/JwsUtil.scala +++ b/obp-api/src/main/scala/code/api/util/JwsUtil.scala @@ -81,7 +81,6 @@ object JwsUtil extends MdcLoggable { requestHeaders.find(_.name == "x-jws-signature").isDefined || requestHeaders.find(_.name == "digest").isDefined } - def createDigestHeader(input: String): String = s"digest: SHA-256=$input" private def getDeferredCriticalHeaders() = { val deferredCriticalHeaders = new util.HashSet[String]() deferredCriticalHeaders.add("sigT") From d00f8b306d6d9e98ea8f47a037e21973b5e7437d Mon Sep 17 00:00:00 2001 From: hongwei Date: Thu, 10 Jun 2021 16:53:36 +0200 Subject: [PATCH 033/147] bugfix/support the empty fields for swagger object --- .../scala/code/api/v4_0_0/APIMethods400.scala | 19 ++++++++++++++----- .../dynamic/DynamicEndpointHelper.scala | 11 +++++++++-- 2 files changed, 23 insertions(+), 7 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 657d7e586..d27cc1c93 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 @@ -17,6 +17,7 @@ import code.api.util.newstyle.AttributeDefinition._ import code.api.util.newstyle.Consumer._ import code.api.util.newstyle.UserCustomerLinkNewStyle import code.api.v1_2_1.{JSONFactory, PostTransactionTagJSON} +import code.api.v1_4_0.JSONFactory1_4_0 import code.api.v1_4_0.JSONFactory1_4_0.TransactionRequestAccountJsonV140 import code.api.v2_0_0.OBPAPI2_0_0.Implementations2_0_0 import code.api.v2_0_0.{EntitlementJSONs, JSONFactory200} @@ -72,11 +73,9 @@ import net.liftweb.util.{Helpers, Mailer, StringHelpers} import org.apache.commons.collections4.CollectionUtils import org.apache.commons.lang3.StringUtils import java.util.{Calendar, Date} - import code.dynamicMessageDoc.JsonDynamicMessageDoc import code.dynamicResourceDoc.JsonDynamicResourceDoc import java.net.URLEncoder - import code.api.v4_0_0.dynamic.practise.DynamicEndpointCodeGenerator import code.endpointMapping.EndpointMappingCommons import net.liftweb.json @@ -4612,7 +4611,7 @@ trait APIMethods400 { nameOf(createDynamicEndpoint), "POST", "/management/dynamic-endpoints", - " Create Dynamic Endpoint", + "Create Dynamic Endpoint", s"""Create dynamic endpoints. | |Create dynamic endpoints with one json format swagger content. @@ -8976,10 +8975,20 @@ trait APIMethods400 { _ <- Helper.booleanToFuture(errorMsg, cc = cc.callContext) { duplicatedUrl.isEmpty } + dynamicEndpointInfo <- NewStyle.function.tryons(InvalidJsonFormat+"Can not convert to OBP Internal Resource Docs", 400, cc.callContext) { + DynamicEndpointHelper.buildDynamicEndpointInfo(openAPI, "current_request_json_body", bankId) + } + roles <- NewStyle.function.tryons(InvalidJsonFormat+"Can not generate OBP roles", 400, cc.callContext) { + DynamicEndpointHelper.getRoles(dynamicEndpointInfo) + } + _ <- NewStyle.function.tryons(InvalidJsonFormat+"Can not generate OBP external Resource Docs", 400, cc.callContext) { + JSONFactory1_4_0.createResourceDocsJson(dynamicEndpointInfo.resourceDocs.toList) + } (dynamicEndpoint, callContext) <- NewStyle.function.createDynamicEndpoint(bankId, cc.userId, postedJson.swaggerString, cc.callContext) + _ <- NewStyle.function.tryons(InvalidJsonFormat+s"Can not grant these roles ${roles.toString} ", 400, cc.callContext) { + roles.map(role => Entitlement.entitlement.vend.addEntitlement(bankId.getOrElse(""), cc.userId, role.toString())) + } } yield { - val roles = DynamicEndpointHelper.getRoles(bankId: Option[String], dynamicEndpoint.dynamicEndpointId.getOrElse("")) - roles.map(role => Entitlement.entitlement.vend.addEntitlement(bankId.getOrElse(""), cc.userId, role.toString())) val swaggerJson = parse(dynamicEndpoint.swaggerString) val responseJson: JObject = ("bank_id", dynamicEndpoint.bankId) ~ ("user_id", cc.userId) ~ ("dynamic_endpoint_id", dynamicEndpoint.dynamicEndpointId) ~ ("swagger_string", swaggerJson) (responseJson, HttpCode.`201`(callContext)) diff --git a/obp-api/src/main/scala/code/api/v4_0_0/dynamic/DynamicEndpointHelper.scala b/obp-api/src/main/scala/code/api/v4_0_0/dynamic/DynamicEndpointHelper.scala index b08c8bf7f..4e595d582 100644 --- a/obp-api/src/main/scala/code/api/v4_0_0/dynamic/DynamicEndpointHelper.scala +++ b/obp-api/src/main/scala/code/api/v4_0_0/dynamic/DynamicEndpointHelper.scala @@ -177,7 +177,7 @@ object DynamicEndpointHelper extends RestHelper { buildDynamicEndpointInfo(openAPI, id, bankId) } - private def buildDynamicEndpointInfo(openAPI: OpenAPI, id: String, bankId:Option[String]): DynamicEndpointInfo = { + def buildDynamicEndpointInfo(openAPI: OpenAPI, id: String, bankId:Option[String]): DynamicEndpointInfo = { val tags: List[ResourceDocTag] = List(ApiTag(openAPI.getInfo.getTitle), apiTagNewStyle, apiTagDynamicEntity, apiTagDynamic) val serverUrl = { @@ -548,7 +548,14 @@ object DynamicEndpointHelper extends RestHelper { } }) case v: MapSchema => getDefaultValue(v, Map("name"-> "John", "age" -> 12)) - + //The swagger object schema may not contain any properties: eg: + // "Account": { + // "title": "accountTransactibility", + // "type": "object" + // } + case v if v.isInstanceOf[ObjectSchema] && MapUtils.isEmpty(v.getProperties()) => + EmptyBody + case v if v.isInstanceOf[ObjectSchema] || MapUtils.isNotEmpty(v.getProperties()) => val properties: util.Map[String, Schema[_]] = v.getProperties From f7bad5f1f71fc123ab1dc7d73e8ab04c2058e8a1 Mon Sep 17 00:00:00 2001 From: hongwei Date: Fri, 11 Jun 2021 10:55:33 +0200 Subject: [PATCH 034/147] refactor/added missing BankId error messages --- .../scala/code/api/v4_0_0/APIMethods400.scala | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 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 d27cc1c93..44ce13054 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 @@ -1716,6 +1716,7 @@ trait APIMethods400 { List(dynamicEntityResponseBodyExample) ), List( + $BankNotFound, $UserNotLoggedIn, UserHasMissingRoles, UnknownError @@ -1831,6 +1832,7 @@ trait APIMethods400 { dynamicEntityRequestBodyExample.copy(bankId = None), dynamicEntityResponseBodyExample, List( + $BankNotFound, $UserNotLoggedIn, UserHasMissingRoles, InvalidJsonFormat, @@ -1937,6 +1939,7 @@ trait APIMethods400 { dynamicEntityRequestBodyExample.copy(bankId=None), dynamicEntityResponseBodyExample, List( + $BankNotFound, $UserNotLoggedIn, UserHasMissingRoles, InvalidJsonFormat, @@ -2005,6 +2008,7 @@ trait APIMethods400 { EmptyBody, EmptyBody, List( + $BankNotFound, $UserNotLoggedIn, UserHasMissingRoles, UnknownError @@ -4663,6 +4667,7 @@ trait APIMethods400 { dynamicEndpointRequestBodyExample, dynamicEndpointResponseBodyExample, List( + $BankNotFound, $UserNotLoggedIn, UserHasMissingRoles, DynamicEndpointExists, @@ -4673,9 +4678,9 @@ trait APIMethods400 { Some(List(canCreateBankLevelDynamicEndpoint, canCreateDynamicEndpoint))) lazy val createBankLevelDynamicEndpoint: OBPEndpoint = { - case "management" :: "banks" :: bankId ::"dynamic-endpoints" :: Nil JsonPost json -> _ => { + case "management" :: "banks" :: BankId(bankId) ::"dynamic-endpoints" :: Nil JsonPost json -> _ => { cc => - createDynamicEndpointMethod(Some(bankId), json, cc) + createDynamicEndpointMethod(Some(bankId.value), json, cc) } } @@ -4734,6 +4739,7 @@ trait APIMethods400 { dynamicEndpointHostJson400, dynamicEndpointHostJson400, List( + $BankNotFound, $UserNotLoggedIn, UserHasMissingRoles, DynamicEntityNotFoundByDynamicEntityId, @@ -4839,6 +4845,7 @@ trait APIMethods400 { EmptyBody, dynamicEndpointResponseBodyExample, List( + $BankNotFound, $UserNotLoggedIn, UserHasMissingRoles, DynamicEndpointNotFoundByDynamicEndpointId, @@ -4883,6 +4890,7 @@ trait APIMethods400 { List(dynamicEndpointResponseBodyExample) ), List( + $BankNotFound, $UserNotLoggedIn, UserHasMissingRoles, InvalidJsonFormat, @@ -4942,6 +4950,7 @@ trait APIMethods400 { EmptyBody, EmptyBody, List( + $BankNotFound, $UserNotLoggedIn, DynamicEndpointNotFoundByDynamicEndpointId, UnknownError @@ -8455,6 +8464,7 @@ trait APIMethods400 { endpointMappingJson.copy(endpointMappingId = None, bankId = None), endpointMappingJson, List( + $BankNotFound, $UserNotLoggedIn, UserHasMissingRoles, InvalidJsonFormat, @@ -8482,6 +8492,7 @@ trait APIMethods400 { endpointMappingJson.copy(endpointMappingId = None, bankId = None), endpointMappingJson, List( + $BankNotFound, $UserNotLoggedIn, UserHasMissingRoles, InvalidJsonFormat, @@ -8510,6 +8521,7 @@ trait APIMethods400 { EmptyBody, endpointMappingJson, List( + $BankNotFound, $UserNotLoggedIn, UserHasMissingRoles, UnknownError @@ -8537,6 +8549,7 @@ trait APIMethods400 { EmptyBody, ListResult("endpoint-mappings", endpointMappingJson::Nil), List( + $BankNotFound, $UserNotLoggedIn, UserHasMissingRoles, UnknownError @@ -8563,6 +8576,7 @@ trait APIMethods400 { EmptyBody, BooleanBody(true), List( + $BankNotFound, $UserNotLoggedIn, UserHasMissingRoles, InvalidJsonFormat, From 5758fe6bddf9c99a5e28a2dec29fd6d2d0d9259f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marko=20Mili=C4=87?= Date: Fri, 11 Jun 2021 11:23:31 +0200 Subject: [PATCH 035/147] feature/Add GUI form for completing user invitation flow --- .../main/scala/bootstrap/liftweb/Boot.scala | 1 + .../scala/code/api/util/ErrorMessages.scala | 1 + .../scala/code/api/v4_0_0/APIMethods400.scala | 2 +- .../remotedata/RemotedataUserInvitation.scala | 3 + .../RemotedataUserInvitationActor.scala | 4 + .../scala/code/snippet/UserInvitation.scala | 85 +++++++++++++++++++ .../code/users/UserInitationProvider.scala | 2 + .../scala/code/users/UserInvitation.scala | 5 ++ obp-api/src/main/webapp/user-invitation.html | 84 ++++++++++++++++++ 9 files changed, 186 insertions(+), 1 deletion(-) create mode 100644 obp-api/src/main/scala/code/snippet/UserInvitation.scala create mode 100644 obp-api/src/main/webapp/user-invitation.html diff --git a/obp-api/src/main/scala/bootstrap/liftweb/Boot.scala b/obp-api/src/main/scala/bootstrap/liftweb/Boot.scala index 15022e7fc..3342cec77 100644 --- a/obp-api/src/main/scala/bootstrap/liftweb/Boot.scala +++ b/obp-api/src/main/scala/bootstrap/liftweb/Boot.scala @@ -500,6 +500,7 @@ class Boot extends MdcLoggable { Menu("Dummy user tokens", "Get Dummy user tokens") / "dummy-user-tokens" >> AuthUser.loginFirst, Menu("Validate OTP", "Validate OTP") / "otp" >> AuthUser.loginFirst, + Menu("User Invitation", "User Invitation") / "user-invitation", // Menu.i("Metrics") / "metrics", //TODO: allow this page once we can make the account number anonymous in the URL Menu.i("OAuth") / "oauth" / "authorize", //OAuth authorization page Menu.i("Consent") / "consent" >> AuthUser.loginFirst,//OAuth consent page 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 b03b7d7c4..4cebe6a20 100644 --- a/obp-api/src/main/scala/code/api/util/ErrorMessages.scala +++ b/obp-api/src/main/scala/code/api/util/ErrorMessages.scala @@ -531,6 +531,7 @@ object ErrorMessages { val InternalServerError = "OBP-50015: The server encountered an unexpected condition which prevented it from fulfilling the request." val KafkaServerUnavailable = "OBP-50016: The kafka server is unavailable." val NotAllowedEndpoint = "OBP-50017: The endpoint is forbidden at this API instance." + val UnderConstructionError = "OBP-50018: Under Construction Error." // Connector Data Exceptions (OBP-502XX) 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 657d7e586..f5ed940c2 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 @@ -3314,7 +3314,7 @@ trait APIMethods400 { postedData.purpose, cc.callContext) } yield { - val invitationText = s"Your registration link: ${APIUtil.getPropsValue("hostname", "")}/registration/${invitation.secretKey}" + val invitationText = s"Your registration link: ${APIUtil.getPropsValue("hostname", "")}/user-invitation?id=${invitation.secretKey}" val params = PlainMailBodyType(invitationText) :: List(To(invitation.email)) Mailer.sendMail(From("invitation@tesobe.com"), Subject("User invitation"), params :_*) (JSONFactory400.createUserInvitationJson(invitation), HttpCode.`201`(callContext)) diff --git a/obp-api/src/main/scala/code/remotedata/RemotedataUserInvitation.scala b/obp-api/src/main/scala/code/remotedata/RemotedataUserInvitation.scala index 1b0663210..36426645b 100644 --- a/obp-api/src/main/scala/code/remotedata/RemotedataUserInvitation.scala +++ b/obp-api/src/main/scala/code/remotedata/RemotedataUserInvitation.scala @@ -14,6 +14,9 @@ object RemotedataUserInvitation extends ObpActorInit with UserInvitationProvider def createUserInvitation(bankId: BankId, firstName: String, lastName: String, email: String, company: String, country: String, purpose: String): Box[UserInvitation] = getValueFromFuture( (actor ? cc.createUserInvitation(bankId, firstName, lastName, email, company, country, purpose)).mapTo[Box[UserInvitation]] ) + def getUserInvitationBySecretLink(secretLink: Long): Box[UserInvitation] = getValueFromFuture( + (actor ? cc.getUserInvitationBySecretLink(secretLink)).mapTo[Box[UserInvitation]] + ) def getUserInvitation(bankId: BankId, secretLink: Long): Box[UserInvitation] = getValueFromFuture( (actor ? cc.getUserInvitation(bankId, secretLink)).mapTo[Box[UserInvitation]] ) diff --git a/obp-api/src/main/scala/code/remotedata/RemotedataUserInvitationActor.scala b/obp-api/src/main/scala/code/remotedata/RemotedataUserInvitationActor.scala index 8d7652d9c..8276dc12c 100644 --- a/obp-api/src/main/scala/code/remotedata/RemotedataUserInvitationActor.scala +++ b/obp-api/src/main/scala/code/remotedata/RemotedataUserInvitationActor.scala @@ -17,6 +17,10 @@ class RemotedataUserInvitationActor extends Actor with ObpActorHelper with MdcLo logger.debug(s"createUserInvitation($bankId, $firstName, $lastName, $email, $company, $country, $purpose)") sender ! (mapper.createUserInvitation(bankId, firstName, lastName, email, company, country, purpose)) + case cc.getUserInvitationBySecretLink(secretLink: Long) => + logger.debug(s"getUserInvitationBySecretLink($secretLink)") + sender ! (mapper.getUserInvitationBySecretLink(secretLink)) + case cc.getUserInvitation(bankId: BankId, secretLink: Long) => logger.debug(s"getUserInvitation($bankId, $secretLink)") sender ! (mapper.getUserInvitation(bankId, secretLink)) diff --git a/obp-api/src/main/scala/code/snippet/UserInvitation.scala b/obp-api/src/main/scala/code/snippet/UserInvitation.scala new file mode 100644 index 000000000..2b7e36d0e --- /dev/null +++ b/obp-api/src/main/scala/code/snippet/UserInvitation.scala @@ -0,0 +1,85 @@ +/** +Open Bank Project - API +Copyright (C) 2011-2019, TESOBE GmbH. + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with this program. If not, see . + +Email: contact@tesobe.com +TESOBE GmbH. +Osloer Strasse 16/17 +Berlin 13359, Germany + +This product includes software developed at +TESOBE (http://www.tesobe.com/) + + */ +package code.snippet + +import code.api.util.ErrorMessages +import code.users.UserInvitationProvider +import code.util.Helper.MdcLoggable +import code.webuiprops.MappedWebUiPropsProvider.getWebUiPropsValue +import com.openbankproject.commons.model.BankId +import net.liftweb.common.Box +import net.liftweb.http.{RequestVar, S, SHtml} +import net.liftweb.util.CssSel +import net.liftweb.util.Helpers._ + +import scala.collection.immutable.List + +class UserInvitation extends MdcLoggable { + + private object firstNameVar extends RequestVar("") + private object lastNameVar extends RequestVar("") + private object companyVar extends RequestVar("") + private object countryVar extends RequestVar("None") + private object devEmailVar extends RequestVar("") + + // Can be used to show link to an online form to collect more information about the App / Startup + val registrationMoreInfoUrl = getWebUiPropsValue("webui_post_user_invitation_more_info_url", "") + + val registrationConsumerButtonValue: String = getWebUiPropsValue("webui_post_user_invitation_submit_button_value", "Proceed") + + + def registerForm: CssSel = { + + val countries = List(("None", "None"), ("Bahrain", "Bahrain"), ("Germany", "Germany"), ("Mexico", "Mexico"), ("UK", "UK")) + val secretLink = S.param("id").getOrElse("0") + val userInvitation = UserInvitationProvider.userInvitationProvider.vend.getUserInvitationBySecretLink(secretLink.toLong) + firstNameVar.set(userInvitation.map(_.firstName).getOrElse("None")) + lastNameVar.set(userInvitation.map(_.lastName).getOrElse("None")) + devEmailVar.set(userInvitation.map(_.email).getOrElse("None")) + companyVar.set(userInvitation.map(_.company).getOrElse("None")) + countryVar.set(userInvitation.map(_.country).getOrElse("Bahrain")) + + def submitButtonDefense: Unit = { + + } + + def register = { + "form" #> { + "#country" #> SHtml.select(countries, Box!! countryVar.is, countryVar(_)) & + "#firstName" #> SHtml.text(firstNameVar.is, firstNameVar(_)) & + "#lastName" #> SHtml.text(lastNameVar.is, lastNameVar(_)) & + "#companyName" #> SHtml.text(companyVar.is, companyVar(_)) & + "#devEmail" #> SHtml.text(devEmailVar, devEmailVar(_)) & + "type=submit" #> SHtml.submit(s"$registrationConsumerButtonValue", () => submitButtonDefense) + } & + "#register-consumer-success" #> "" + } + register + + } + +} diff --git a/obp-api/src/main/scala/code/users/UserInitationProvider.scala b/obp-api/src/main/scala/code/users/UserInitationProvider.scala index 1a18e1d45..18959c27d 100644 --- a/obp-api/src/main/scala/code/users/UserInitationProvider.scala +++ b/obp-api/src/main/scala/code/users/UserInitationProvider.scala @@ -21,12 +21,14 @@ object UserInvitationProvider extends SimpleInjector { trait UserInvitationProvider { def createUserInvitation(bankId: BankId, firstName: String, lastName: String, email: String, company: String, country: String, purpose: String): Box[UserInvitation] + def getUserInvitationBySecretLink(secretLink: Long): Box[UserInvitation] def getUserInvitation(bankId: BankId, secretLink: Long): Box[UserInvitation] def getUserInvitations(bankId: BankId): Box[List[UserInvitation]] } class RemotedataUserInvitationProviderCaseClass { case class createUserInvitation(bankId: BankId, firstName: String, lastName: String, email: String, company: String, country: String, purpose: String) + case class getUserInvitationBySecretLink(secretLink: Long) case class getUserInvitation(bankId: BankId, secretLink: Long) case class getUserInvitations(bankId: BankId) } diff --git a/obp-api/src/main/scala/code/users/UserInvitation.scala b/obp-api/src/main/scala/code/users/UserInvitation.scala index 6ae181cec..dbbb283f3 100644 --- a/obp-api/src/main/scala/code/users/UserInvitation.scala +++ b/obp-api/src/main/scala/code/users/UserInvitation.scala @@ -22,6 +22,11 @@ object MappedUserInvitationProvider extends UserInvitationProvider { .Purpose(purpose) .saveMe() } + override def getUserInvitationBySecretLink(secretLink: Long): Box[UserInvitation] = { + UserInvitation.find( + By(UserInvitation.SecretKey, secretLink) + ) + } override def getUserInvitation(bankId: BankId, secretLink: Long): Box[UserInvitation] = { UserInvitation.find( By(UserInvitation.BankId, bankId.value), diff --git a/obp-api/src/main/webapp/user-invitation.html b/obp-api/src/main/webapp/user-invitation.html new file mode 100644 index 000000000..770799608 --- /dev/null +++ b/obp-api/src/main/webapp/user-invitation.html @@ -0,0 +1,84 @@ + +
+
+ +
+
+

Complete your user invitation

+

Please complete the information about the user invitation application below.

+

All fields are required unless marked as 'optional'

+
+ +
+
+
+ +
+ + +
+ +
+
+
+ + +
+ +
+
+
+ + +
+ +
+
+
+ + +
+ +
+
+
+ + +
+
+
+ + +
+ +
+
+
+
+
From 97a98a2a2808c66acdc7796517d0bc65ab586da0 Mon Sep 17 00:00:00 2001 From: hongwei Date: Fri, 11 Jun 2021 16:01:30 +0200 Subject: [PATCH 036/147] test/added the unit test for obp_mock and dynamic mode --- .../dynamic/DynamicEndpointHelper.scala | 6 ++-- .../MapppedDynamicEndpointProvider.scala | 3 +- .../v4_0_0/DynamicEndpointHelperTest.scala | 33 +++++++++++++++++++ 3 files changed, 39 insertions(+), 3 deletions(-) diff --git a/obp-api/src/main/scala/code/api/v4_0_0/dynamic/DynamicEndpointHelper.scala b/obp-api/src/main/scala/code/api/v4_0_0/dynamic/DynamicEndpointHelper.scala index 4e595d582..b2f87b1dc 100644 --- a/obp-api/src/main/scala/code/api/v4_0_0/dynamic/DynamicEndpointHelper.scala +++ b/obp-api/src/main/scala/code/api/v4_0_0/dynamic/DynamicEndpointHelper.scala @@ -51,9 +51,12 @@ object DynamicEndpointHelper extends RestHelper { */ val urlPrefix = APIUtil.getPropsValue("dynamic_endpoints_url_prefix", "dynamic") private val implementedInApiVersion = ApiVersion.v4_0_0 - private val IsDynamicEntityUrl = """https?://dynamic_entity.*""" + private val IsDynamicEntityUrl = """.*dynamic_entity.*""" + private val IsMockUrlString = """.*obp_mock(?::\d+)?.*""" + private val IsMockUrl = IsMockUrlString.r def isDynamicEntityResponse (serverUrl : String) = serverUrl matches (IsDynamicEntityUrl) + def isMockedResponse (serverUrl : String) = serverUrl matches (IsMockUrlString) private def dynamicEndpointInfos: List[DynamicEndpointInfo] = { val dynamicEndpoints: List[DynamicEndpointT] = DynamicEndpointProvider.connectorMethodProvider.vend.getAll(None) @@ -96,7 +99,6 @@ object DynamicEndpointHelper extends RestHelper { object DynamicReq extends JsonTest with JsonBody { private val ExpressionRegx = """\{(.+?)\}""".r - private val IsMockUrl = """https?://obp_mock(?::\d+)?/.*""".r /** * unapply Request to (request url, json, http method, request parameters, path parameters, role) * request url is current request target url to remote server diff --git a/obp-api/src/main/scala/code/dynamicEndpoint/MapppedDynamicEndpointProvider.scala b/obp-api/src/main/scala/code/dynamicEndpoint/MapppedDynamicEndpointProvider.scala index bd18a1659..a16c3c651 100644 --- a/obp-api/src/main/scala/code/dynamicEndpoint/MapppedDynamicEndpointProvider.scala +++ b/obp-api/src/main/scala/code/dynamicEndpoint/MapppedDynamicEndpointProvider.scala @@ -17,7 +17,8 @@ import scala.concurrent.duration.DurationInt object MappedDynamicEndpointProvider extends DynamicEndpointProvider with CustomJsonFormats{ val dynamicEndpointTTL : Int = { if(Props.testMode) 0 - else APIUtil.getPropsValue(s"dynamicEndpoint.cache.ttl.seconds", "32").toInt + else //Better set this to 0, we maybe create multiple endpoints, when we create new ones. + APIUtil.getPropsValue(s"dynamicEndpoint.cache.ttl.seconds", "0").toInt } override def create(bankId:Option[String], userId: String, swaggerString: String): Box[DynamicEndpointT] = { diff --git a/obp-api/src/test/scala/code/api/v4_0_0/DynamicEndpointHelperTest.scala b/obp-api/src/test/scala/code/api/v4_0_0/DynamicEndpointHelperTest.scala index b1801d7bc..8eab6954c 100644 --- a/obp-api/src/test/scala/code/api/v4_0_0/DynamicEndpointHelperTest.scala +++ b/obp-api/src/test/scala/code/api/v4_0_0/DynamicEndpointHelperTest.scala @@ -3023,5 +3023,38 @@ class DynamicEndpointHelperTest extends FlatSpec with Matchers { expectedJson should equal(result) } + "isDynamicEntityResponse" should "work well" taggedAs FunctionsTag in { + // swagger with schemes: + val serverUrl1 = "https://dynamic_entity" + + // swagger not schemes: + val serverUrl2 = "//dynamic_entity" + + val result1= DynamicEndpointHelper.isDynamicEntityResponse(serverUrl1) + val result2= DynamicEndpointHelper.isDynamicEntityResponse(serverUrl2) + + result1 should equal(true) + result2 should equal(true) + + } + + "isMockedResponse" should "work well" taggedAs FunctionsTag in { + + // swagger with schemes: + val serverUrl1 = "https://obp_mock:3/" + + // swagger not schemes: + val serverUrl2 = "//obp_mock/" + val serverUrl3 = "//obp_mock" + + val result1= DynamicEndpointHelper.isMockedResponse(serverUrl1) + val result2= DynamicEndpointHelper.isMockedResponse(serverUrl2) + val result3= DynamicEndpointHelper.isMockedResponse(serverUrl3) + + result1 should equal(true) + result2 should equal(true) + result3 should equal(true) + + } } From ddbc3223314d0ef3b661672e3ba4290acbb80f38 Mon Sep 17 00:00:00 2001 From: hongwei Date: Mon, 14 Jun 2021 09:30:50 +0200 Subject: [PATCH 037/147] tests/added the tests for DynamicEndpoints --- .../api/v4_0_0/DynamicendPointsTest.scala | 216 +++++++++++++++++- 1 file changed, 213 insertions(+), 3 deletions(-) diff --git a/obp-api/src/test/scala/code/api/v4_0_0/DynamicendPointsTest.scala b/obp-api/src/test/scala/code/api/v4_0_0/DynamicendPointsTest.scala index 8c6656ac0..61e64dbc5 100644 --- a/obp-api/src/test/scala/code/api/v4_0_0/DynamicendPointsTest.scala +++ b/obp-api/src/test/scala/code/api/v4_0_0/DynamicendPointsTest.scala @@ -3,7 +3,7 @@ package code.api.v4_0_0 import code.api.ResourceDocs1_4_0.SwaggerDefinitionsJSON import code.api.util.APIUtil.OAuth._ import code.api.util.ApiRole._ -import code.api.util.ErrorMessages.{DynamicEndpointExists, InvalidMyDynamicEndpointUser, UserHasMissingRoles, UserNotLoggedIn} +import code.api.util.ErrorMessages.{DynamicEndpointExists, EndpointMappingNotFoundByOperationId, InvalidMyDynamicEndpointUser, UserHasMissingRoles, UserNotLoggedIn} import code.api.util.ExampleValue import code.api.v4_0_0.OBPAPI4_0_0.Implementations4_0_0 import code.entitlement.Entitlement @@ -31,6 +31,7 @@ class DynamicEndpointsTest extends V400ServerSetup { object ApiEndpoint5 extends Tag(nameOf(Implementations4_0_0.getMyDynamicEndpoints)) object ApiEndpoint6 extends Tag(nameOf(Implementations4_0_0.deleteMyDynamicEndpoint)) object ApiEndpoint7 extends Tag(nameOf(Implementations4_0_0.updateDynamicEndpointHost)) + object ApiEndpoint8 extends Tag(nameOf(Implementations4_0_0.dynamicEndpoint)) feature(s"test $ApiEndpoint1 version $VersionOfApi - Unauthorized access") { @@ -213,7 +214,7 @@ class DynamicEndpointsTest extends V400ServerSetup { response400.body.toString contains("Swagger Petstore") should be (true) response400.body.toString contains("This is a sample server Petstore server.") should be (true) response400.body.toString contains("apiteam@swagger.io") should be (true) - + } } @@ -326,7 +327,7 @@ class DynamicEndpointsTest extends V400ServerSetup { val json = response400.body \ "dynamic_endpoints" val dynamicEntitiesGetJson = json.asInstanceOf[JArray] dynamicEntitiesGetJson.values should have size 0 - + } { @@ -457,5 +458,214 @@ class DynamicEndpointsTest extends V400ServerSetup { } } + feature(s"test $ApiEndpoint1 and $ApiEndpoint8 version $VersionOfApi - authorized access - with role - should be success!") { + scenario("we test new endpoints - system level", ApiEndpoint8, VersionOfApi) { + When("We make a request v4.0.0") + val dynamicEndpointSwagger = """{ + | "swagger": "2.0", + | "info": { + | "title": "Documents", + | "version": "1.0.0" + | }, + | "definitions": { + | "FashionBrandNames": { + | "type": "object", + | "properties": { + | "name": { + | "type": "string" + | } + | } + | } + | }, + | "paths": { + | "/documents": { + | "post": { + | "operationId": "POST_documents", + | "produces": [ + | "application/json" + | ], + | "responses": { + | "201": { + | "description": "Success Response", + | "schema": { + | "$ref": "#/definitions/FashionBrandNames" + | } + | } + | }, + | "consumes": [ + | "application/json" + | ], + | "description": "POST Documents", + | "summary": "POST Documents" + | } + | } + | }, + | "host": "obp_mock" + |}""".stripMargin + Entitlement.entitlement.vend.addEntitlement("", resourceUser1.userId, canCreateDynamicEndpoint.toString) + val request = (v4_0_0_Request / "management" / "dynamic-endpoints").POST<@ (user1) + val responseWithRole = makePostRequest(request, dynamicEndpointSwagger) + Then("We should get a 201") + responseWithRole.code should equal(201) + val dynamicEndpointId = (responseWithRole.body \"dynamic_endpoint_id").asInstanceOf[JString].s + + Then("we test authentication error") + + { + val request = (v4_0_0_Request / "dynamic" / "documents").POST + val response = makePostRequest(request, dynamicEndpointSwagger) + response.code should equal(401) + response.body.toString contains(UserNotLoggedIn) should be (true) + } + + Then("we test missing role error") + + { + val request = (v4_0_0_Request / "dynamic" / "documents").POST<@ (user2) + val response = makePostRequest(request, dynamicEndpointSwagger) + response.code should equal(403) + response.body.toString contains(UserHasMissingRoles) should be (true) + } + + Then("we test successful cases") + + { + val request = (v4_0_0_Request / "dynamic" / "documents").POST<@ (user1) + val response = makePostRequest(request, dynamicEndpointSwagger) + response.code should equal(201) + response.body.toString contains("name") should be (true) + response.body.toString contains("String") should be (true) + } + + + Then(s"we test $ApiEndpoint7, if we change the host, then the response is different") + + { + val dynamicEndpointHostJson = SwaggerDefinitionsJSON.dynamicEndpointHostJson400 + Then("We grant the role to the user1 and update the host") + Entitlement.entitlement.vend.addEntitlement("", resourceUser1.userId, canUpdateDynamicEndpoint.toString) + val requestPut = (v4_0_0_Request / "management" / "dynamic-endpoints"/dynamicEndpointId/ "host").PUT<@ (user1) + val responsePut = makePutRequest(requestPut, write(dynamicEndpointHostJson)) + + Then("if we changed the host, the response should be the errors") + val request = (v4_0_0_Request / "dynamic" / "documents").POST<@ (user1) + val response = makePostRequest(request, dynamicEndpointSwagger) + response.code should equal(404) + response.body.toString contains(EndpointMappingNotFoundByOperationId) should be (true) + } + + } + + scenario("we test new endpoints - bank level", ApiEndpoint8, VersionOfApi) { + When("We make a request v4.0.0 with the role canCreateDynamicEndpoint") + val dynamicEndpointSwagger = """{ + | "swagger": "2.0", + | "info": { + | "title": "Documents", + | "version": "1.0.0" + | }, + | "definitions": { + | "FashionBrandNames": { + | "type": "object", + | "properties": { + | "name": { + | "type": "string" + | } + | } + | } + | }, + | "paths": { + | "/documents": { + | "post": { + | "operationId": "POST_documents", + | "produces": [ + | "application/json" + | ], + | "responses": { + | "201": { + | "description": "Success Response", + | "schema": { + | "$ref": "#/definitions/FashionBrandNames" + | } + | } + | }, + | "consumes": [ + | "application/json" + | ], + | "description": "POST Documents", + | "summary": "POST Documents" + | } + | } + | }, + | "host": "obp_mock" + |}""".stripMargin + + Then("First test the bank Level role") + + { + Entitlement.entitlement.vend.addEntitlement("", resourceUser1.userId, canCreateBankLevelDynamicEndpoint.toString) + val request = (v4_0_0_Request / "management" /"banks"/testBankId1.value/ "dynamic-endpoints").POST<@ (user1) + val responseWithRole = makePostRequest(request, dynamicEndpointSwagger) + Then("We should get a 201") + responseWithRole.code should equal(201) + val dynamicEndpointId = (responseWithRole.body \"dynamic_endpoint_id").asInstanceOf[JString].s + } + + Then("First test the system Level role") + Entitlement.entitlement.vend.addEntitlement("", resourceUser1.userId, canCreateDynamicEndpoint.toString) + val request = (v4_0_0_Request / "management" /"banks"/testBankId1.value/ "dynamic-endpoints").POST<@ (user1) + val responseWithRole = makePostRequest(request, dynamicEndpointSwagger) + Then("We should get a 201") + responseWithRole.code should equal(201) + val dynamicEndpointId = (responseWithRole.body \"dynamic_endpoint_id").asInstanceOf[JString].s + + Then("we test authentication error") + + { + val request = (v4_0_0_Request / "dynamic" / "documents").POST + val response = makePostRequest(request, dynamicEndpointSwagger) + response.code should equal(401) + response.body.toString contains(UserNotLoggedIn) should be (true) + } + + Then("we test missing role error") + + { + val request = (v4_0_0_Request / "dynamic" / "documents").POST<@ (user2) + val response = makePostRequest(request, dynamicEndpointSwagger) + response.code should equal(403) + response.body.toString contains(UserHasMissingRoles) should be (true) + } + + Then("we test successful cases") + + { + val request = (v4_0_0_Request / "dynamic" / "documents").POST<@ (user1) + val response = makePostRequest(request, dynamicEndpointSwagger) + response.code should equal(201) + response.body.toString contains("name") should be (true) + response.body.toString contains("String") should be (true) + } + + + Then(s"we test $ApiEndpoint7, if we change the host, then the response is different") + + { + val dynamicEndpointHostJson = SwaggerDefinitionsJSON.dynamicEndpointHostJson400 + Then("We grant the role to the user1 and update the host") + Entitlement.entitlement.vend.addEntitlement("", resourceUser1.userId, canUpdateDynamicEndpoint.toString) + val requestPut = (v4_0_0_Request / "management" / "dynamic-endpoints"/dynamicEndpointId/ "host").PUT<@ (user1) + val responsePut = makePutRequest(requestPut, write(dynamicEndpointHostJson)) + + Then("if we changed the host, the response should be the errors") + val request = (v4_0_0_Request / "dynamic" / "documents").POST<@ (user1) + val response = makePostRequest(request, dynamicEndpointSwagger) + response.code should equal(404) + response.body.toString contains(EndpointMappingNotFoundByOperationId) should be (true) + } + + } + } + } From 903f9edeaafd5b6229f9ae3c787d1266050f7318 Mon Sep 17 00:00:00 2001 From: hongwei Date: Mon, 14 Jun 2021 12:22:17 +0200 Subject: [PATCH 038/147] test/added tests for the bank level dynamic endpoints --- .../scala/code/api/util/ExampleValue.scala | 1098 +---------------- .../api/v4_0_0/DynamicendPointsTest.scala | 307 ++--- 2 files changed, 226 insertions(+), 1179 deletions(-) diff --git a/obp-api/src/main/scala/code/api/util/ExampleValue.scala b/obp-api/src/main/scala/code/api/util/ExampleValue.scala index 19a446f85..7b4e38335 100644 --- a/obp-api/src/main/scala/code/api/util/ExampleValue.scala +++ b/obp-api/src/main/scala/code/api/util/ExampleValue.scala @@ -2193,1060 +2193,78 @@ object ExampleValue { lazy val dynamicEntityResponseBodyExample = dynamicEntityRequestBodyExample.copy(dynamicEntityId = Some("dynamic-entity-id"), userId =Some(ExampleValue.userIdExample.value)) - private val dynamicEndpointSwagger = + val dynamicEndpointSwagger = """{ | "swagger": "2.0", | "info": { - | "description": "This is a sample server Petstore server. You can find out more about Swagger at [http://swagger.io](http://swagger.io) or on [irc.freenode.net, #swagger](http://swagger.io/irc/). For this sample, you can use the api key `special-key` to test the authorization filters.", - | "version": "1.0.5", - | "title": "Swagger Petstore", - | "termsOfService": "http://swagger.io/terms/", - | "contact": { - | "email": "apiteam@swagger.io" - | }, - | "license": { - | "name": "Apache 2.0", - | "url": "http://www.apache.org/licenses/LICENSE-2.0.html" - | } - | }, - | "host": "petstore.swagger.io", - | "basePath": "/v2", - | "tags": [ - | { - | "name": "pet", - | "description": "Everything about your Pets", - | "externalDocs": { - | "description": "Find out more", - | "url": "http://swagger.io" - | } - | }, - | { - | "name": "store", - | "description": "Access to Petstore orders" - | }, - | { - | "name": "user", - | "description": "Operations about user", - | "externalDocs": { - | "description": "Find out more about our store", - | "url": "http://swagger.io" - | } - | } - | ], - | "schemes": [ - | "https", - | "http" - | ], - | "paths": { - | "/pet/{petId}/uploadImage": { - | "post": { - | "tags": [ - | "pet" - | ], - | "summary": "uploads an image", - | "description": "", - | "operationId": "uploadFile", - | "consumes": [ - | "multipart/form-data" - | ], - | "produces": [ - | "application/json" - | ], - | "parameters": [ - | { - | "name": "petId", - | "in": "path", - | "description": "ID of pet to update", - | "required": true, - | "type": "integer", - | "format": "int64" - | }, - | { - | "name": "additionalMetadata", - | "in": "formData", - | "description": "Additional data to pass to server", - | "required": false, - | "type": "string" - | }, - | { - | "name": "file", - | "in": "formData", - | "description": "file to upload", - | "required": false, - | "type": "file" - | } - | ], - | "responses": { - | "200": { - | "description": "successful operation", - | "schema": { - | "$ref": "#/definitions/ApiResponse" - | } - | } - | }, - | "security": [ - | { - | "petstore_auth": [ - | "write:pets", - | "read:pets" - | ] - | } - | ] - | } - | }, - | "/pet": { - | "post": { - | "tags": [ - | "pet" - | ], - | "summary": "Add a new pet to the store", - | "description": "", - | "operationId": "addPet", - | "consumes": [ - | "application/json", - | "application/xml" - | ], - | "produces": [ - | "application/json", - | "application/xml" - | ], - | "parameters": [ - | { - | "in": "body", - | "name": "body", - | "description": "Pet object that needs to be added to the store", - | "required": true, - | "schema": { - | "$ref": "#/definitions/Pet" - | } - | } - | ], - | "responses": { - | "405": { - | "description": "Invalid input" - | } - | }, - | "security": [ - | { - | "petstore_auth": [ - | "write:pets", - | "read:pets" - | ] - | } - | ] - | }, - | "put": { - | "tags": [ - | "pet" - | ], - | "summary": "Update an existing pet", - | "description": "", - | "operationId": "updatePet", - | "consumes": [ - | "application/json", - | "application/xml" - | ], - | "produces": [ - | "application/json", - | "application/xml" - | ], - | "parameters": [ - | { - | "in": "body", - | "name": "body", - | "description": "Pet object that needs to be added to the store", - | "required": true, - | "schema": { - | "$ref": "#/definitions/Pet" - | } - | } - | ], - | "responses": { - | "400": { - | "description": "Invalid ID supplied" - | }, - | "404": { - | "description": "Pet not found" - | }, - | "405": { - | "description": "Validation exception" - | } - | }, - | "security": [ - | { - | "petstore_auth": [ - | "write:pets", - | "read:pets" - | ] - | } - | ] - | } - | }, - | "/pet/findByStatus": { - | "get": { - | "tags": [ - | "pet" - | ], - | "summary": "Finds Pets by status", - | "description": "Multiple status values can be provided with comma separated strings", - | "operationId": "findPetsByStatus", - | "produces": [ - | "application/json", - | "application/xml" - | ], - | "parameters": [ - | { - | "name": "status", - | "in": "query", - | "description": "Status values that need to be considered for filter", - | "required": true, - | "type": "array", - | "items": { - | "type": "string", - | "enum": [ - | "available", - | "pending", - | "sold" - | ], - | "default": "available" - | }, - | "collectionFormat": "multi" - | } - | ], - | "responses": { - | "200": { - | "description": "successful operation", - | "schema": { - | "type": "array", - | "items": { - | "$ref": "#/definitions/Pet" - | } - | } - | }, - | "400": { - | "description": "Invalid status value" - | } - | }, - | "security": [ - | { - | "petstore_auth": [ - | "write:pets", - | "read:pets" - | ] - | } - | ] - | } - | }, - | "/pet/findByTags": { - | "get": { - | "tags": [ - | "pet" - | ], - | "summary": "Finds Pets by tags", - | "description": "Multiple tags can be provided with comma separated strings. Use tag1, tag2, tag3 for testing.", - | "operationId": "findPetsByTags", - | "produces": [ - | "application/json", - | "application/xml" - | ], - | "parameters": [ - | { - | "name": "tags", - | "in": "query", - | "description": "Tags to filter by", - | "required": true, - | "type": "array", - | "items": { - | "type": "string" - | }, - | "collectionFormat": "multi" - | } - | ], - | "responses": { - | "200": { - | "description": "successful operation", - | "schema": { - | "type": "array", - | "items": { - | "$ref": "#/definitions/Pet" - | } - | } - | }, - | "400": { - | "description": "Invalid tag value" - | } - | }, - | "security": [ - | { - | "petstore_auth": [ - | "write:pets", - | "read:pets" - | ] - | } - | ], - | "deprecated": true - | } - | }, - | "/pet/{petId}": { - | "get": { - | "tags": [ - | "pet" - | ], - | "summary": "Find pet by ID", - | "description": "Returns a single pet", - | "operationId": "getPetById", - | "produces": [ - | "application/json", - | "application/xml" - | ], - | "parameters": [ - | { - | "name": "petId", - | "in": "path", - | "description": "ID of pet to return", - | "required": true, - | "type": "integer", - | "format": "int64" - | } - | ], - | "responses": { - | "200": { - | "description": "successful operation", - | "schema": { - | "$ref": "#/definitions/Pet" - | } - | }, - | "400": { - | "description": "Invalid ID supplied" - | }, - | "404": { - | "description": "Pet not found" - | } - | }, - | "security": [ - | { - | "api_key": [] - | } - | ] - | }, - | "post": { - | "tags": [ - | "pet" - | ], - | "summary": "Updates a pet in the store with form data", - | "description": "", - | "operationId": "updatePetWithForm", - | "consumes": [ - | "application/x-www-form-urlencoded" - | ], - | "produces": [ - | "application/json", - | "application/xml" - | ], - | "parameters": [ - | { - | "name": "petId", - | "in": "path", - | "description": "ID of pet that needs to be updated", - | "required": true, - | "type": "integer", - | "format": "int64" - | }, - | { - | "name": "name", - | "in": "formData", - | "description": "Updated name of the pet", - | "required": false, - | "type": "string" - | }, - | { - | "name": "status", - | "in": "formData", - | "description": "Updated status of the pet", - | "required": false, - | "type": "string" - | } - | ], - | "responses": { - | "405": { - | "description": "Invalid input" - | } - | }, - | "security": [ - | { - | "petstore_auth": [ - | "write:pets", - | "read:pets" - | ] - | } - | ] - | }, - | "delete": { - | "tags": [ - | "pet" - | ], - | "summary": "Deletes a pet", - | "description": "", - | "operationId": "deletePet", - | "produces": [ - | "application/json", - | "application/xml" - | ], - | "parameters": [ - | { - | "name": "api_key", - | "in": "header", - | "required": false, - | "type": "string" - | }, - | { - | "name": "petId", - | "in": "path", - | "description": "Pet id to delete", - | "required": true, - | "type": "integer", - | "format": "int64" - | } - | ], - | "responses": { - | "400": { - | "description": "Invalid ID supplied" - | }, - | "404": { - | "description": "Pet not found" - | } - | }, - | "security": [ - | { - | "petstore_auth": [ - | "write:pets", - | "read:pets" - | ] - | } - | ] - | } - | }, - | "/store/order": { - | "post": { - | "tags": [ - | "store" - | ], - | "summary": "Place an order for a pet", - | "description": "", - | "operationId": "placeOrder", - | "consumes": [ - | "application/json" - | ], - | "produces": [ - | "application/json", - | "application/xml" - | ], - | "parameters": [ - | { - | "in": "body", - | "name": "body", - | "description": "order placed for purchasing the pet", - | "required": true, - | "schema": { - | "$ref": "#/definitions/Order" - | } - | } - | ], - | "responses": { - | "200": { - | "description": "successful operation", - | "schema": { - | "$ref": "#/definitions/Order" - | } - | }, - | "400": { - | "description": "Invalid Order" - | } - | } - | } - | }, - | "/store/order/{orderId}": { - | "get": { - | "tags": [ - | "store" - | ], - | "summary": "Find purchase order by ID", - | "description": "For valid response try integer IDs with value >= 1 and <= 10. Other values will generated exceptions", - | "operationId": "getOrderById", - | "produces": [ - | "application/json", - | "application/xml" - | ], - | "parameters": [ - | { - | "name": "orderId", - | "in": "path", - | "description": "ID of pet that needs to be fetched", - | "required": true, - | "type": "integer", - | "maximum": 10, - | "minimum": 1, - | "format": "int64" - | } - | ], - | "responses": { - | "200": { - | "description": "successful operation", - | "schema": { - | "$ref": "#/definitions/Order" - | } - | }, - | "400": { - | "description": "Invalid ID supplied" - | }, - | "404": { - | "description": "Order not found" - | } - | } - | }, - | "delete": { - | "tags": [ - | "store" - | ], - | "summary": "Delete purchase order by ID", - | "description": "For valid response try integer IDs with positive integer value. Negative or non-integer values will generate API errors", - | "operationId": "deleteOrder", - | "produces": [ - | "application/json", - | "application/xml" - | ], - | "parameters": [ - | { - | "name": "orderId", - | "in": "path", - | "description": "ID of the order that needs to be deleted", - | "required": true, - | "type": "integer", - | "minimum": 1, - | "format": "int64" - | } - | ], - | "responses": { - | "400": { - | "description": "Invalid ID supplied" - | }, - | "404": { - | "description": "Order not found" - | } - | } - | } - | }, - | "/store/inventory": { - | "get": { - | "tags": [ - | "store" - | ], - | "summary": "Returns pet inventories by status", - | "description": "Returns a map of status codes to quantities", - | "operationId": "getInventory", - | "produces": [ - | "application/json" - | ], - | "parameters": [], - | "responses": { - | "200": { - | "description": "successful operation", - | "schema": { - | "type": "object", - | "additionalProperties": { - | "type": "integer", - | "format": "int32" - | } - | } - | } - | }, - | "security": [ - | { - | "api_key": [] - | } - | ] - | } - | }, - | "/user/createWithArray": { - | "post": { - | "tags": [ - | "user" - | ], - | "summary": "Creates list of users with given input array", - | "description": "", - | "operationId": "createUsersWithArrayInput", - | "consumes": [ - | "application/json" - | ], - | "produces": [ - | "application/json", - | "application/xml" - | ], - | "parameters": [ - | { - | "in": "body", - | "name": "body", - | "description": "List of user object", - | "required": true, - | "schema": { - | "type": "array", - | "items": { - | "$ref": "#/definitions/User" - | } - | } - | } - | ], - | "responses": { - | "default": { - | "description": "successful operation" - | } - | } - | } - | }, - | "/user/createWithList": { - | "post": { - | "tags": [ - | "user" - | ], - | "summary": "Creates list of users with given input array", - | "description": "", - | "operationId": "createUsersWithListInput", - | "consumes": [ - | "application/json" - | ], - | "produces": [ - | "application/json", - | "application/xml" - | ], - | "parameters": [ - | { - | "in": "body", - | "name": "body", - | "description": "List of user object", - | "required": true, - | "schema": { - | "type": "array", - | "items": { - | "$ref": "#/definitions/User" - | } - | } - | } - | ], - | "responses": { - | "default": { - | "description": "successful operation" - | } - | } - | } - | }, - | "/user/{username}": { - | "get": { - | "tags": [ - | "user" - | ], - | "summary": "Get user by user name", - | "description": "", - | "operationId": "getUserByName", - | "produces": [ - | "application/json", - | "application/xml" - | ], - | "parameters": [ - | { - | "name": "username", - | "in": "path", - | "description": "The name that needs to be fetched. Use user1 for testing. ", - | "required": true, - | "type": "string" - | } - | ], - | "responses": { - | "200": { - | "description": "successful operation", - | "schema": { - | "$ref": "#/definitions/User" - | } - | }, - | "400": { - | "description": "Invalid username supplied" - | }, - | "404": { - | "description": "User not found" - | } - | } - | }, - | "put": { - | "tags": [ - | "user" - | ], - | "summary": "Updated user", - | "description": "This can only be done by the logged in user.", - | "operationId": "updateUser", - | "consumes": [ - | "application/json" - | ], - | "produces": [ - | "application/json", - | "application/xml" - | ], - | "parameters": [ - | { - | "name": "username", - | "in": "path", - | "description": "name that need to be updated", - | "required": true, - | "type": "string" - | }, - | { - | "in": "body", - | "name": "body", - | "description": "Updated user object", - | "required": true, - | "schema": { - | "$ref": "#/definitions/User" - | } - | } - | ], - | "responses": { - | "400": { - | "description": "Invalid user supplied" - | }, - | "404": { - | "description": "User not found" - | } - | } - | }, - | "delete": { - | "tags": [ - | "user" - | ], - | "summary": "Delete user", - | "description": "This can only be done by the logged in user.", - | "operationId": "deleteUser", - | "produces": [ - | "application/json", - | "application/xml" - | ], - | "parameters": [ - | { - | "name": "username", - | "in": "path", - | "description": "The name that needs to be deleted", - | "required": true, - | "type": "string" - | } - | ], - | "responses": { - | "400": { - | "description": "Invalid username supplied" - | }, - | "404": { - | "description": "User not found" - | } - | } - | } - | }, - | "/user/login": { - | "get": { - | "tags": [ - | "user" - | ], - | "summary": "Logs user into the system", - | "description": "", - | "operationId": "loginUser", - | "produces": [ - | "application/json", - | "application/xml" - | ], - | "parameters": [ - | { - | "name": "username", - | "in": "query", - | "description": "The user name for login", - | "required": true, - | "type": "string" - | }, - | { - | "name": "password", - | "in": "query", - | "description": "The password for login in clear text", - | "required": true, - | "type": "string" - | } - | ], - | "responses": { - | "200": { - | "description": "successful operation", - | "headers": { - | "X-Expires-After": { - | "type": "string", - | "format": "date-time", - | "description": "date in UTC when token expires" - | }, - | "X-Rate-Limit": { - | "type": "integer", - | "format": "int32", - | "description": "calls per hour allowed by the user" - | } - | }, - | "schema": { - | "type": "string" - | } - | }, - | "400": { - | "description": "Invalid username/password supplied" - | } - | } - | } - | }, - | "/user/logout": { - | "get": { - | "tags": [ - | "user" - | ], - | "summary": "Logs out current logged in user session", - | "description": "", - | "operationId": "logoutUser", - | "produces": [ - | "application/json", - | "application/xml" - | ], - | "parameters": [], - | "responses": { - | "default": { - | "description": "successful operation" - | } - | } - | } - | }, - | "/user": { - | "post": { - | "tags": [ - | "user" - | ], - | "summary": "Create user", - | "description": "This can only be done by the logged in user.", - | "operationId": "createUser", - | "consumes": [ - | "application/json" - | ], - | "produces": [ - | "application/json", - | "application/xml" - | ], - | "parameters": [ - | { - | "in": "body", - | "name": "body", - | "description": "Created user object", - | "required": true, - | "schema": { - | "$ref": "#/definitions/User" - | } - | } - | ], - | "responses": { - | "default": { - | "description": "successful operation" - | } - | } - | } - | } - | }, - | "securityDefinitions": { - | "api_key": { - | "type": "apiKey", - | "name": "api_key", - | "in": "header" - | }, - | "petstore_auth": { - | "type": "oauth2", - | "authorizationUrl": "https://petstore.swagger.io/oauth/authorize", - | "flow": "implicit", - | "scopes": { - | "read:pets": "read your pets", - | "write:pets": "modify pets in your account" - | } - | } + | "title": "Bank Accounts (Dynamic Endpoint)", + | "version": "1.0.0" | }, | "definitions": { - | "ApiResponse": { + | "AccountName": { | "type": "object", | "properties": { - | "code": { - | "type": "integer", - | "format": "int32" - | }, - | "type": { - | "type": "string" - | }, - | "message": { - | "type": "string" - | } - | } - | }, - | "Category": { - | "type": "object", - | "properties": { - | "id": { - | "type": "integer", - | "format": "int64" - | }, - | "name": { - | "type": "string" - | } - | }, - | "xml": { - | "name": "Category" - | } - | }, - | "Pet": { - | "type": "object", - | "required": [ - | "name", - | "photoUrls" - | ], - | "properties": { - | "id": { - | "type": "integer", - | "format": "int64" - | }, - | "category": { - | "$ref": "#/definitions/Category" - | }, | "name": { | "type": "string", - | "example": "doggie" + | "example": "family account" | }, - | "photoUrls": { - | "type": "array", - | "xml": { - | "wrapped": true - | }, - | "items": { - | "type": "string", - | "xml": { - | "name": "photoUrl" - | } - | } - | }, - | "tags": { - | "type": "array", - | "xml": { - | "wrapped": true - | }, - | "items": { - | "xml": { - | "name": "tag" - | }, - | "$ref": "#/definitions/Tag" - | } - | }, - | "status": { - | "type": "string", - | "description": "pet status in the store", - | "enum": [ - | "available", - | "pending", - | "sold" - | ] + | "balance": { + | "type": "integer", + | "format": "int64", + | "example": 1000.123 | } - | }, - | "xml": { - | "name": "Pet" - | } - | }, - | "Tag": { - | "type": "object", - | "properties": { - | "id": { - | "type": "integer", - | "format": "int64" - | }, - | "name": { - | "type": "string" - | } - | }, - | "xml": { - | "name": "Tag" - | } - | }, - | "Order": { - | "type": "object", - | "properties": { - | "id": { - | "type": "integer", - | "format": "int64" - | }, - | "petId": { - | "type": "integer", - | "format": "int64" - | }, - | "quantity": { - | "type": "integer", - | "format": "int32" - | }, - | "shipDate": { - | "type": "string", - | "format": "date-time" - | }, - | "status": { - | "type": "string", - | "description": "Order Status", - | "enum": [ - | "placed", - | "approved", - | "delivered" - | ] - | }, - | "complete": { - | "type": "boolean" - | } - | }, - | "xml": { - | "name": "Order" - | } - | }, - | "User": { - | "type": "object", - | "properties": { - | "id": { - | "type": "integer", - | "format": "int64" - | }, - | "username": { - | "type": "string" - | }, - | "firstName": { - | "type": "string" - | }, - | "lastName": { - | "type": "string" - | }, - | "email": { - | "type": "string" - | }, - | "password": { - | "type": "string" - | }, - | "phone": { - | "type": "string" - | }, - | "userStatus": { - | "type": "integer", - | "format": "int32", - | "description": "User Status" - | } - | }, - | "xml": { - | "name": "User" | } | } | }, - | "externalDocs": { - | "description": "Find out more about Swagger", - | "url": "http://swagger.io" - | } + | "paths": { + | "/accounts": { + | "post": { + | "operationId": "POST_account", + | "produces": [ + | "application/json" + | ], + | "responses": { + | "201": { + | "description": "Success Response", + | "schema": { + | "$ref": "#/definitions/AccountName" + | } + | } + | }, + | "consumes": [ + | "application/json" + | ], + | "description": "POST Accounts", + | "summary": "POST Accounts" + | } + | }, + | "/accounts/{account_id}": { + | "get": { + | "operationId": "GET_account", + | "produces": [ + | "application/json" + | ], + | "responses": { + | "200": { + | "description": "Success Response", + | "schema": { + | "$ref": "#/definitions/AccountName" + | } + | } + | }, + | "consumes": [ + | "application/json" + | ], + | "description": "Get Bank Account", + | "summary": "Get Bank Account by Id" + | } + | } + | }, + | "host": "obp_mock", + | "schemes": [ + | "http", + | "https" + | ] |}""".stripMargin lazy val dynamicEndpointRequestBodyExample: JObject = json.parse(dynamicEndpointSwagger).asInstanceOf[JObject] lazy val dynamicEndpointResponseBodyExample: JObject = ("user_id", ExampleValue.userIdExample.value) ~ ("dynamic_endpoint_id", "dynamic-endpoint-id") ~ ("swagger_string", dynamicEndpointRequestBodyExample) diff --git a/obp-api/src/test/scala/code/api/v4_0_0/DynamicendPointsTest.scala b/obp-api/src/test/scala/code/api/v4_0_0/DynamicendPointsTest.scala index 61e64dbc5..aa6a6ed70 100644 --- a/obp-api/src/test/scala/code/api/v4_0_0/DynamicendPointsTest.scala +++ b/obp-api/src/test/scala/code/api/v4_0_0/DynamicendPointsTest.scala @@ -32,6 +32,146 @@ class DynamicEndpointsTest extends V400ServerSetup { object ApiEndpoint6 extends Tag(nameOf(Implementations4_0_0.deleteMyDynamicEndpoint)) object ApiEndpoint7 extends Tag(nameOf(Implementations4_0_0.updateDynamicEndpointHost)) object ApiEndpoint8 extends Tag(nameOf(Implementations4_0_0.dynamicEndpoint)) + object ApiEndpoint9 extends Tag(nameOf(Implementations4_0_0.createBankLevelDynamicEndpoint)) + object ApiEndpoint10 extends Tag(nameOf(Implementations4_0_0.getBankLevelDynamicEndpoints)) + object ApiEndpoint11 extends Tag(nameOf(Implementations4_0_0.getBankLevelDynamicEndpoint)) + object ApiEndpoint12 extends Tag(nameOf(Implementations4_0_0.deleteBankLevelDynamicEndpoint)) + + val postDynamicEndpointSwagger = ExampleValue.dynamicEndpointSwagger + + feature(s"test $ApiEndpoint9, $ApiEndpoint10, $ApiEndpoint11, $ApiEndpoint12 version $VersionOfApi") { + + scenario(s"$ApiEndpoint9 $ApiEndpoint10 $ApiEndpoint11 $ApiEndpoint12 test the bank level role", ApiEndpoint1, VersionOfApi) { + When("We make a request v4.0.0") + Entitlement.entitlement.vend.addEntitlement(testBankId1.value, resourceUser1.userId, canCreateBankLevelDynamicEndpoint.toString) + val request = (v4_0_0_Request / "management" /"banks"/testBankId1.value/ "dynamic-endpoints").POST<@ (user1) + val responseWithRole = makePostRequest(request, postDynamicEndpointSwagger) + Then("We should get a 201") + responseWithRole.code should equal(201) + responseWithRole.body.toString contains("dynamic_endpoint_id") should be (true) + val dynamicEndpointId = (responseWithRole.body \"dynamic_endpoint_id").asInstanceOf[JString].s + + + Then(s"We call $ApiEndpoint10 - missing role ") + val requestGet = (v4_0_0_Request / "management" /"banks"/testBankId1.value/ "dynamic-endpoints"/ dynamicEndpointId).GET<@ (user1) + val responseGet = makeGetRequest(requestGet) + responseGet.code should equal(403) + responseGet.body.toString contains(UserHasMissingRoles) should be (true) + + Then(s"We call $ApiEndpoint10 - grant role ") + + { + Entitlement.entitlement.vend.addEntitlement(testBankId1.value, resourceUser1.userId, canGetBankLevelDynamicEndpoint.toString) + val requestGet = (v4_0_0_Request / "management" /"banks"/testBankId1.value/ "dynamic-endpoints"/ dynamicEndpointId).GET<@ (user1) + val responseGet = makeGetRequest(requestGet) + responseGet.code should equal(200) + responseWithRole.body.toString contains("dynamic_endpoint_id") should be (true) + responseWithRole.body.toString contains(s"/banks/${testBankId1.value}/accounts") should be (true) + responseWithRole.body.toString contains(s"/banks/${testBankId1.value}/accounts/{account_id}") should be (true) + } + + Then(s"We call $ApiEndpoint11 -- missing role") + val requestGetAll = (v4_0_0_Request / "management" /"banks"/testBankId1.value/ "dynamic-endpoints").GET<@ (user1) + val responseGetAll = makeGetRequest(requestGetAll) + responseGetAll.code should equal(403) + responseGetAll.body.toString contains(UserHasMissingRoles) should be (true) + + Then(s"We call $ApiEndpoint11 -- grant role") + + { + Entitlement.entitlement.vend.addEntitlement(testBankId1.value, resourceUser1.userId, canGetBankLevelDynamicEndpoints.toString) + val requestGetAll = (v4_0_0_Request / "management" /"banks"/testBankId1.value/ "dynamic-endpoints").GET<@ (user1) + val responseGetAll = makeGetRequest(requestGetAll) + responseGetAll.code should equal(200) + responseWithRole.body.toString contains("dynamic_endpoint_id") should be (true) + responseWithRole.body.toString contains(s"/banks/${testBankId1.value}/accounts") should be (true) + responseWithRole.body.toString contains(s"/banks/${testBankId1.value}/accounts/{account_id}") should be (true) + } + + Then(s"We call $ApiEndpoint12 -- Missing Role ") + val requestDelete = (v4_0_0_Request / "management" /"banks"/testBankId1.value/ "dynamic-endpoints"/ dynamicEndpointId).DELETE<@ (user1) + val responseDelete = makeDeleteRequest(requestDelete) + responseDelete.code should equal(403) + responseDelete.body.toString contains(UserHasMissingRoles) should be (true) + + + Then("We call $ApiEndpoint12 -- grant Role ") + + { + Entitlement.entitlement.vend.addEntitlement(testBankId1.value, resourceUser1.userId, canDeleteBankLevelDynamicEndpoint.toString) + val requestDelete = (v4_0_0_Request / "management" /"banks"/testBankId1.value/ "dynamic-endpoints"/ dynamicEndpointId).DELETE<@ (user1) + val responseDelete = makeDeleteRequest(requestDelete) + responseDelete.code should equal(204) + } + + } + + scenario(s"$ApiEndpoint9 test the bank level role", ApiEndpoint1, VersionOfApi) { + When("We make a request v4.0.0") + Entitlement.entitlement.vend.addEntitlement(testBankId1.value, resourceUser1.userId, canCreateBankLevelDynamicEndpoint.toString) + val request = (v4_0_0_Request / "management" /"banks"/testBankId1.value/ "dynamic-endpoints").POST<@ (user1) + val responseWithRole = makePostRequest(request, postDynamicEndpointSwagger) + Then("We should get a 201") + responseWithRole.code should equal(201) + responseWithRole.body.toString contains("dynamic_endpoint_id") should be (true) + + Then("When we create the endpoint properly, then we can call the `accounts` endpoint") + + { + val request = (v4_0_0_Request / "dynamic" / "banks"/testBankId1.value/ "accounts").POST<@ (user1) + val response = makePostRequest(request, postDynamicEndpointSwagger) + response.code should equal(201) + response.body.toString contains("name") should be (true) + response.body.toString contains("String") should be (true) + } + + Then("we test the other user missing roles.") + + { + val request = (v4_0_0_Request / "dynamic" / "banks"/testBankId1.value/ "accounts").POST<@ (user2) + val response = makePostRequest(request, postDynamicEndpointSwagger) + response.code should equal(403) + response.body.toString contains(UserHasMissingRoles) should be (true) + } + + + Then("we test the duplicated urls.") + + { + val duplicatedRequest = makePostRequest(request, postDynamicEndpointSwagger) + Then("We should get a 400") + duplicatedRequest.code should equal(400) + duplicatedRequest.body.extract[ErrorMessage].message contains (DynamicEndpointExists) should be (true) + } + + } + + scenario(s" $ApiEndpoint9 the the system level role", ApiEndpoint1, VersionOfApi) { + Then("We grant the role to the user1") + Entitlement.entitlement.vend.addEntitlement("", resourceUser1.userId, canCreateDynamicEndpoint.toString) + When("We make a request v4.0.0") + val request = (v4_0_0_Request / "management" /"banks"/testBankId1.value / "dynamic-endpoints").POST<@ (user1) + + val responseWithRole = makePostRequest(request, postDynamicEndpointSwagger) + Then("We should get a 201") + responseWithRole.code should equal(201) + responseWithRole.body.toString contains("dynamic_endpoint_id") should be (true) + + { + val request = (v4_0_0_Request / "dynamic" / "banks"/testBankId1.value/ "accounts").POST<@ (user1) + val response = makePostRequest(request, postDynamicEndpointSwagger) + response.code should equal(201) + response.body.toString contains("name") should be (true) + response.body.toString contains("String") should be (true) + } + + val duplicatedRequest = makePostRequest(request, postDynamicEndpointSwagger) + Then("We should get a 400") + duplicatedRequest.code should equal(400) + duplicatedRequest.body.extract[ErrorMessage].message.toString contains (DynamicEndpointExists) should be (true) + } + + } feature(s"test $ApiEndpoint1 version $VersionOfApi - Unauthorized access") { @@ -79,10 +219,7 @@ class DynamicEndpointsTest extends V400ServerSetup { Then("We should get a 201") responseWithRole.code should equal(201) responseWithRole.body.toString contains("dynamic_endpoint_id") should be (true) - responseWithRole.body.toString contains("swagger_string") should be (true) - responseWithRole.body.toString contains("Swagger Petstore") should be (true) - responseWithRole.body.toString contains("This is a sample server Petstore server.") should be (true) - responseWithRole.body.toString contains("apiteam@swagger.io") should be (true) + } } @@ -143,11 +280,6 @@ class DynamicEndpointsTest extends V400ServerSetup { val request400 = (v4_0_0_Request / "management" / "dynamic-endpoints").GET<@ (user1) val response400 = makeGetRequest(request400) response400.code should be (200) - - response400.body.toString contains("Swagger Petstore") should be (true) - response400.body.toString contains("This is a sample server Petstore server.") should be (true) - response400.body.toString contains("apiteam@swagger.io") should be (true) - } } @@ -210,10 +342,7 @@ class DynamicEndpointsTest extends V400ServerSetup { val response400 = makeGetRequest(request400) response400.code should be (200) response400.body.toString contains("dynamic_endpoint_id") should be (true) - response400.body.toString contains("swagger_string") should be (true) - response400.body.toString contains("Swagger Petstore") should be (true) - response400.body.toString contains("This is a sample server Petstore server.") should be (true) - response400.body.toString contains("apiteam@swagger.io") should be (true) + } } @@ -272,10 +401,7 @@ class DynamicEndpointsTest extends V400ServerSetup { val response400 = makeGetRequest(request400) response400.code should be (200) response400.body.toString contains("dynamic_endpoint_id") should be (true) - response400.body.toString contains("swagger_string") should be (true) - response400.body.toString contains("Swagger Petstore") should be (true) - response400.body.toString contains("This is a sample server Petstore server.") should be (true) - response400.body.toString contains("apiteam@swagger.io") should be (true) + val requestDelete = (v4_0_0_Request / "management" / "dynamic-endpoints" /id).DELETE<@ (user1) @@ -313,10 +439,6 @@ class DynamicEndpointsTest extends V400ServerSetup { val response400 = makeGetRequest(request400) response400.code should be (200) response400.body.toString contains("dynamic_endpoint_id") should be (true) - response400.body.toString contains("swagger_string") should be (true) - response400.body.toString contains("Swagger Petstore") should be (true) - response400.body.toString contains("This is a sample server Petstore server.") should be (true) - response400.body.toString contains("apiteam@swagger.io") should be (true) { // we use the wrong user2 to get the dynamic-endpoints @@ -461,77 +583,36 @@ class DynamicEndpointsTest extends V400ServerSetup { feature(s"test $ApiEndpoint1 and $ApiEndpoint8 version $VersionOfApi - authorized access - with role - should be success!") { scenario("we test new endpoints - system level", ApiEndpoint8, VersionOfApi) { When("We make a request v4.0.0") - val dynamicEndpointSwagger = """{ - | "swagger": "2.0", - | "info": { - | "title": "Documents", - | "version": "1.0.0" - | }, - | "definitions": { - | "FashionBrandNames": { - | "type": "object", - | "properties": { - | "name": { - | "type": "string" - | } - | } - | } - | }, - | "paths": { - | "/documents": { - | "post": { - | "operationId": "POST_documents", - | "produces": [ - | "application/json" - | ], - | "responses": { - | "201": { - | "description": "Success Response", - | "schema": { - | "$ref": "#/definitions/FashionBrandNames" - | } - | } - | }, - | "consumes": [ - | "application/json" - | ], - | "description": "POST Documents", - | "summary": "POST Documents" - | } - | } - | }, - | "host": "obp_mock" - |}""".stripMargin Entitlement.entitlement.vend.addEntitlement("", resourceUser1.userId, canCreateDynamicEndpoint.toString) val request = (v4_0_0_Request / "management" / "dynamic-endpoints").POST<@ (user1) - val responseWithRole = makePostRequest(request, dynamicEndpointSwagger) + val responseWithRole = makePostRequest(request, postDynamicEndpointSwagger) Then("We should get a 201") responseWithRole.code should equal(201) val dynamicEndpointId = (responseWithRole.body \"dynamic_endpoint_id").asInstanceOf[JString].s - + Then("we test authentication error") - + { - val request = (v4_0_0_Request / "dynamic" / "documents").POST - val response = makePostRequest(request, dynamicEndpointSwagger) + val request = (v4_0_0_Request / "dynamic" / "accounts").POST + val response = makePostRequest(request, postDynamicEndpointSwagger) response.code should equal(401) response.body.toString contains(UserNotLoggedIn) should be (true) } Then("we test missing role error") - + { - val request = (v4_0_0_Request / "dynamic" / "documents").POST<@ (user2) - val response = makePostRequest(request, dynamicEndpointSwagger) + val request = (v4_0_0_Request / "dynamic" / "accounts").POST<@ (user2) + val response = makePostRequest(request, postDynamicEndpointSwagger) response.code should equal(403) response.body.toString contains(UserHasMissingRoles) should be (true) } Then("we test successful cases") - + { - val request = (v4_0_0_Request / "dynamic" / "documents").POST<@ (user1) - val response = makePostRequest(request, dynamicEndpointSwagger) + val request = (v4_0_0_Request / "dynamic" / "accounts").POST<@ (user1) + val response = makePostRequest(request, postDynamicEndpointSwagger) response.code should equal(201) response.body.toString contains("name") should be (true) response.body.toString contains("String") should be (true) @@ -539,17 +620,17 @@ class DynamicEndpointsTest extends V400ServerSetup { Then(s"we test $ApiEndpoint7, if we change the host, then the response is different") - + { val dynamicEndpointHostJson = SwaggerDefinitionsJSON.dynamicEndpointHostJson400 Then("We grant the role to the user1 and update the host") Entitlement.entitlement.vend.addEntitlement("", resourceUser1.userId, canUpdateDynamicEndpoint.toString) val requestPut = (v4_0_0_Request / "management" / "dynamic-endpoints"/dynamicEndpointId/ "host").PUT<@ (user1) val responsePut = makePutRequest(requestPut, write(dynamicEndpointHostJson)) - + Then("if we changed the host, the response should be the errors") - val request = (v4_0_0_Request / "dynamic" / "documents").POST<@ (user1) - val response = makePostRequest(request, dynamicEndpointSwagger) + val request = (v4_0_0_Request / "dynamic" / "accounts").POST<@ (user1) + val response = makePostRequest(request, postDynamicEndpointSwagger) response.code should equal(404) response.body.toString contains(EndpointMappingNotFoundByOperationId) should be (true) } @@ -558,72 +639,20 @@ class DynamicEndpointsTest extends V400ServerSetup { scenario("we test new endpoints - bank level", ApiEndpoint8, VersionOfApi) { When("We make a request v4.0.0 with the role canCreateDynamicEndpoint") - val dynamicEndpointSwagger = """{ - | "swagger": "2.0", - | "info": { - | "title": "Documents", - | "version": "1.0.0" - | }, - | "definitions": { - | "FashionBrandNames": { - | "type": "object", - | "properties": { - | "name": { - | "type": "string" - | } - | } - | } - | }, - | "paths": { - | "/documents": { - | "post": { - | "operationId": "POST_documents", - | "produces": [ - | "application/json" - | ], - | "responses": { - | "201": { - | "description": "Success Response", - | "schema": { - | "$ref": "#/definitions/FashionBrandNames" - | } - | } - | }, - | "consumes": [ - | "application/json" - | ], - | "description": "POST Documents", - | "summary": "POST Documents" - | } - | } - | }, - | "host": "obp_mock" - |}""".stripMargin - - Then("First test the bank Level role") - - { - Entitlement.entitlement.vend.addEntitlement("", resourceUser1.userId, canCreateBankLevelDynamicEndpoint.toString) - val request = (v4_0_0_Request / "management" /"banks"/testBankId1.value/ "dynamic-endpoints").POST<@ (user1) - val responseWithRole = makePostRequest(request, dynamicEndpointSwagger) - Then("We should get a 201") - responseWithRole.code should equal(201) - val dynamicEndpointId = (responseWithRole.body \"dynamic_endpoint_id").asInstanceOf[JString].s - } Then("First test the system Level role") Entitlement.entitlement.vend.addEntitlement("", resourceUser1.userId, canCreateDynamicEndpoint.toString) val request = (v4_0_0_Request / "management" /"banks"/testBankId1.value/ "dynamic-endpoints").POST<@ (user1) - val responseWithRole = makePostRequest(request, dynamicEndpointSwagger) + val responseWithRole = makePostRequest(request, postDynamicEndpointSwagger) Then("We should get a 201") responseWithRole.code should equal(201) val dynamicEndpointId = (responseWithRole.body \"dynamic_endpoint_id").asInstanceOf[JString].s - + Then("we test authentication error") { - val request = (v4_0_0_Request / "dynamic" / "documents").POST - val response = makePostRequest(request, dynamicEndpointSwagger) + val request = (v4_0_0_Request / "dynamic"/"banks"/testBankId1.value / "accounts").POST + val response = makePostRequest(request, postDynamicEndpointSwagger) response.code should equal(401) response.body.toString contains(UserNotLoggedIn) should be (true) } @@ -631,8 +660,8 @@ class DynamicEndpointsTest extends V400ServerSetup { Then("we test missing role error") { - val request = (v4_0_0_Request / "dynamic" / "documents").POST<@ (user2) - val response = makePostRequest(request, dynamicEndpointSwagger) + val request = (v4_0_0_Request / "dynamic"/"banks"/testBankId1.value / "accounts").POST<@ (user2) + val response = makePostRequest(request, postDynamicEndpointSwagger) response.code should equal(403) response.body.toString contains(UserHasMissingRoles) should be (true) } @@ -640,8 +669,8 @@ class DynamicEndpointsTest extends V400ServerSetup { Then("we test successful cases") { - val request = (v4_0_0_Request / "dynamic" / "documents").POST<@ (user1) - val response = makePostRequest(request, dynamicEndpointSwagger) + val request = (v4_0_0_Request / "dynamic" /"banks"/testBankId1.value / "accounts").POST<@ (user1) + val response = makePostRequest(request, postDynamicEndpointSwagger) response.code should equal(201) response.body.toString contains("name") should be (true) response.body.toString contains("String") should be (true) @@ -658,8 +687,8 @@ class DynamicEndpointsTest extends V400ServerSetup { val responsePut = makePutRequest(requestPut, write(dynamicEndpointHostJson)) Then("if we changed the host, the response should be the errors") - val request = (v4_0_0_Request / "dynamic" / "documents").POST<@ (user1) - val response = makePostRequest(request, dynamicEndpointSwagger) + val request = (v4_0_0_Request / "dynamic" /"banks"/testBankId1.value / "accounts").POST<@ (user1) + val response = makePostRequest(request, postDynamicEndpointSwagger) response.code should equal(404) response.body.toString contains(EndpointMappingNotFoundByOperationId) should be (true) } From 85af736e8d413912500b427453b6d3cd2a6a8567 Mon Sep 17 00:00:00 2001 From: hongwei Date: Mon, 14 Jun 2021 13:38:32 +0200 Subject: [PATCH 039/147] test/add the tests for bank level dynamic Entity --- .../scala/code/api/v4_0_0/APIMethods400.scala | 6 +- .../code/api/v4_0_0/DynamicEntityTest.scala | 71 +++++++++++++++++-- 2 files changed, 69 insertions(+), 8 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 44ce13054..6aa812b24 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 @@ -1912,9 +1912,9 @@ trait APIMethods400 { } private def updateBankLevelDynamicEntityDoc = ResourceDoc( - updateBankLevelDynamicEntityAtBank, + updateBankLevelDynamicEntity, implementedInApiVersion, - nameOf(updateBankLevelDynamicEntityAtBank), + nameOf(updateBankLevelDynamicEntity), "PUT", "/management/banks/BANK_ID/dynamic-entities/DYNAMIC_ENTITY_ID", "Update Bank Level Dynamic Entity", @@ -1947,7 +1947,7 @@ trait APIMethods400 { ), List(apiTagManageDynamicEntity, apiTagApi, apiTagNewStyle), Some(List(canUpdateBankLevelDynamicEntity, canUpdateDynamicEntity))) - lazy val updateBankLevelDynamicEntityAtBank: OBPEndpoint = { + lazy val updateBankLevelDynamicEntity: OBPEndpoint = { case "management" :: "banks" :: bankId :: "dynamic-entities" :: dynamicEntityId :: Nil JsonPut json -> _ => { cc => updateDynamicEntityMethod(Some(bankId),dynamicEntityId, json, cc) diff --git a/obp-api/src/test/scala/code/api/v4_0_0/DynamicEntityTest.scala b/obp-api/src/test/scala/code/api/v4_0_0/DynamicEntityTest.scala index 09b3abb41..07551328d 100644 --- a/obp-api/src/test/scala/code/api/v4_0_0/DynamicEntityTest.scala +++ b/obp-api/src/test/scala/code/api/v4_0_0/DynamicEntityTest.scala @@ -55,6 +55,10 @@ class DynamicEntityTest extends V400ServerSetup { object ApiEndpoint6 extends Tag(nameOf(Implementations4_0_0.updateMyDynamicEntity)) object ApiEndpoint7 extends Tag(nameOf(Implementations4_0_0.deleteMyDynamicEntity)) object ApiEndpoint8 extends Tag(nameOf(Implementations4_0_0.getBankLevelDynamicEntities)) + object ApiEndpoint9 extends Tag(nameOf(Implementations4_0_0.createBankLevelDynamicEntity)) + object ApiEndpoint10 extends Tag(nameOf(Implementations4_0_0.getBankLevelDynamicEntities)) + object ApiEndpoint11 extends Tag(nameOf(Implementations4_0_0.deleteBankLevelDynamicEntity)) + object ApiEndpoint12 extends Tag(nameOf(Implementations4_0_0.updateBankLevelDynamicEntity)) val rightEntity = parse( """ @@ -155,7 +159,7 @@ class DynamicEntityTest extends V400ServerSetup { |""".stripMargin) feature("Add a DynamicEntity v4.0.4- Unauthorized access") { - scenario("We will call the endpoint without user credentials", ApiEndpoint1, VersionOfApi) { + scenario("We will call the endpoint without user credentials", ApiEndpoint1, ApiEndpoint9, VersionOfApi) { When("We make a request v4.0.0") val request400 = (v4_0_0_Request / "management" / "dynamic-entities").POST val response400 = makePostRequest(request400, write(rightEntity)) @@ -163,10 +167,21 @@ class DynamicEntityTest extends V400ServerSetup { response400.code should equal(401) And("error should be " + UserNotLoggedIn) response400.body.extract[ErrorMessage].message should equal (UserNotLoggedIn) + + Then("Test the bank level") + + { + val request400 = (v4_0_0_Request / "management" / "banks" / testBankId1.value / "dynamic-entities").POST + val response400 = makePostRequest(request400, write(rightEntity)) + Then("We should get a 401") + response400.code should equal(401) + And("error should be " + UserNotLoggedIn) + response400.body.extract[ErrorMessage].message should equal (UserNotLoggedIn) + } } } feature("Update a DynamicEntity v4.0.4- Unauthorized access") { - scenario("We will call the endpoint without user credentials", ApiEndpoint2, VersionOfApi) { + scenario("We will call the endpoint without user credentials", ApiEndpoint2, ApiEndpoint12, VersionOfApi) { When("We make a request v4.0.0") val request400 = (v4_0_0_Request / "management" / "dynamic-entities"/ "some-method-routing-id").PUT val response400 = makePutRequest(request400, write(rightEntity)) @@ -174,10 +189,23 @@ class DynamicEntityTest extends V400ServerSetup { response400.code should equal(401) And("error should be " + UserNotLoggedIn) response400.body.extract[ErrorMessage].message should equal (UserNotLoggedIn) + + Then("Test the bank level") + + { + val request400 = (v4_0_0_Request / "management" / "banks" / testBankId1.value / "dynamic-entities"/ "some-method-routing-id").PUT + val response400 = makePutRequest(request400, write(rightEntity)) + Then("We should get a 401") + response400.code should equal(401) + And("error should be " + UserNotLoggedIn) + response400.body.extract[ErrorMessage].message should equal (UserNotLoggedIn) + + } + } } feature("Get DynamicEntities v4.0.4- Unauthorized access") { - scenario("We will call the endpoint without user credentials", ApiEndpoint3, VersionOfApi) { + scenario("We will call the endpoint without user credentials", ApiEndpoint3, ApiEndpoint10, VersionOfApi) { When("We make a request v4.0.0") val request400 = (v4_0_0_Request / "management" / "dynamic-entities").GET val response400 = makeGetRequest(request400) @@ -185,10 +213,21 @@ class DynamicEntityTest extends V400ServerSetup { response400.code should equal(401) And("error should be " + UserNotLoggedIn) response400.body.extract[ErrorMessage].message should equal (UserNotLoggedIn) + + Then("Test the bank level") + + { + val request400 = (v4_0_0_Request / "management" / "banks" / testBankId1.value / "dynamic-entities").GET + val response400 = makeGetRequest(request400) + Then("We should get a 401") + response400.code should equal(401) + And("error should be " + UserNotLoggedIn) + response400.body.extract[ErrorMessage].message should equal (UserNotLoggedIn) + } } } feature("Delete the DynamicEntity specified by METHOD_ROUTING_ID v4.0.4- Unauthorized access") { - scenario("We will call the endpoint without user credentials", ApiEndpoint4, VersionOfApi) { + scenario("We will call the endpoint without user credentials", ApiEndpoint4, ApiEndpoint11, VersionOfApi) { When("We make a request v4.0.0") val request400 = (v4_0_0_Request / "management" / "dynamic-entities" / "METHOD_ROUTING_ID").DELETE val response400 = makeDeleteRequest(request400) @@ -196,10 +235,21 @@ class DynamicEntityTest extends V400ServerSetup { response400.code should equal(401) And("error should be " + UserNotLoggedIn) response400.body.extract[ErrorMessage].message should equal (UserNotLoggedIn) + + Then("Test the bank level") + + { + val request400 = (v4_0_0_Request / "management" / "banks" / testBankId1.value / "dynamic-entities" / "METHOD_ROUTING_ID").DELETE + val response400 = makeDeleteRequest(request400) + Then("We should get a 401") + response400.code should equal(401) + And("error should be " + UserNotLoggedIn) + response400.body.extract[ErrorMessage].message should equal (UserNotLoggedIn) + } + } } - feature("Add a DynamicEntity v4.0.4- Unauthorized access - Authorized access") { scenario("We will call the endpoint without the proper Role " + canCreateDynamicEntity, ApiEndpoint1, VersionOfApi) { When("We make a request v4.0.0 without a Role " + canCreateDynamicEntity) @@ -209,6 +259,17 @@ class DynamicEntityTest extends V400ServerSetup { response400.code should equal(403) And("error should be " + UserHasMissingRoles + CanCreateDynamicEntity) response400.body.extract[ErrorMessage].message should equal (UserHasMissingRoles + CanCreateDynamicEntity) + + Then("Test the bank level") + + { + val request400 = (v4_0_0_Request / "management" / "banks" / testBankId1.value / "dynamic-entities").POST <@(user1) + val response400 = makePostRequest(request400, write(rightEntity)) + Then("We should get a 403") + response400.code should equal(403) + response400.body.extract[ErrorMessage].message contains UserHasMissingRoles should be (true) + } + } scenario("We will call the endpoint with the proper Role " + canCreateDynamicEntity , ApiEndpoint1, ApiEndpoint2, ApiEndpoint3, ApiEndpoint4, ApiEndpoint8, VersionOfApi) { From 2b832a1479f14ef4a89429e06ecd6005c6d4205a Mon Sep 17 00:00:00 2001 From: hongwei Date: Mon, 14 Jun 2021 14:28:55 +0200 Subject: [PATCH 040/147] test/added the bankLevelEndpointMapping --- .../v4_0_0/EndpointMappingBankLevelTest.scala | 155 ++++++++++++++++++ 1 file changed, 155 insertions(+) create mode 100644 obp-api/src/test/scala/code/api/v4_0_0/EndpointMappingBankLevelTest.scala diff --git a/obp-api/src/test/scala/code/api/v4_0_0/EndpointMappingBankLevelTest.scala b/obp-api/src/test/scala/code/api/v4_0_0/EndpointMappingBankLevelTest.scala new file mode 100644 index 000000000..a711c9431 --- /dev/null +++ b/obp-api/src/test/scala/code/api/v4_0_0/EndpointMappingBankLevelTest.scala @@ -0,0 +1,155 @@ +package code.api.v4_0_0 + +import code.api.ResourceDocs1_4_0.SwaggerDefinitionsJSON.{endpointMappingJson,jsonCodeTemplate} +import code.api.util.APIUtil.OAuth._ +import code.api.util.ApiRole._ +import code.api.util.ErrorMessages.{UserNotLoggedIn, _} +import code.api.v4_0_0.OBPAPI4_0_0.Implementations4_0_0 +import code.endpointMapping.EndpointMappingCommons +import code.entitlement.Entitlement +import com.github.dwickern.macros.NameOf.nameOf +import com.openbankproject.commons.model.ErrorMessage +import com.openbankproject.commons.util.ApiVersion +import net.liftweb.json.Serialization.write +import org.scalatest.Tag + +class EndpointMappingBankLevelTest extends V400ServerSetup { + /** + * Test tags + * Example: To run tests with tag "getPermissions": + * mvn test -D tagsToInclude + * + * This is made possible by the scalatest maven plugin + */ + object VersionOfApi extends Tag(ApiVersion.v4_0_0.toString) + object ApiEndpoint1 extends Tag(nameOf(Implementations4_0_0.createBankLevelEndpointMapping)) + object ApiEndpoint2 extends Tag(nameOf(Implementations4_0_0.getBankLevelEndpointMapping)) + object ApiEndpoint3 extends Tag(nameOf(Implementations4_0_0.getAllBankLevelEndpointMappings)) + object ApiEndpoint4 extends Tag(nameOf(Implementations4_0_0.updateBankLevelEndpointMapping)) + object ApiEndpoint5 extends Tag(nameOf(Implementations4_0_0.deleteBankLevelEndpointMapping)) + + val rightEntity = endpointMappingJson.copy(endpointMappingId = None) + val wrongEntity = jsonCodeTemplate + + feature("Add a EndpointMapping v4.0.0- Unauthorized access") { + scenario("We will call the endpoint without user credentials", ApiEndpoint1, VersionOfApi) { + When("We make a request v4.0.0") + val request400 = (v4_0_0_Request / "management" / "banks" / testBankId1.value / "endpoint-mappings").POST + + val response400 = makePostRequest(request400, write(rightEntity)) + Then("We should get a 401") + response400.code should equal(401) + And("error should be " + UserNotLoggedIn) + response400.body.extract[ErrorMessage].message should equal (UserNotLoggedIn) + } + } + feature("Update a EndpointMapping v4.0.0- Unauthorized access") { + scenario("We will call the endpoint without user credentials", ApiEndpoint2, VersionOfApi) { + When("We make a request v4.0.0") + val request400 = (v4_0_0_Request / "management" / "banks" / testBankId1.value / "endpoint-mappings"/ "some-method-routing-id").PUT + val response400 = makePutRequest(request400, write(rightEntity)) + Then("We should get a 401") + response400.code should equal(401) + And("error should be " + UserNotLoggedIn) + response400.body.extract[ErrorMessage].message should equal (UserNotLoggedIn) + } + } + feature("Get EndpointMappings v4.0.0- 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 / "management" / "banks" / testBankId1.value / "endpoint-mappings").GET < Date: Mon, 14 Jun 2021 16:10:13 +0200 Subject: [PATCH 041/147] feature/Implement connector function checkExternalUserExists in Mapper mode --- .../bankconnectors/LocalMappedConnector.scala | 18 ++++++++++++++++-- .../scala/code/model/dataAccess/AuthUser.scala | 2 +- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/obp-api/src/main/scala/code/bankconnectors/LocalMappedConnector.scala b/obp-api/src/main/scala/code/bankconnectors/LocalMappedConnector.scala index 4e0b168d0..3ff91d783 100644 --- a/obp-api/src/main/scala/code/bankconnectors/LocalMappedConnector.scala +++ b/obp-api/src/main/scala/code/bankconnectors/LocalMappedConnector.scala @@ -47,6 +47,7 @@ import code.metadata.tags.Tags import code.metadata.transactionimages.TransactionImages import code.metadata.wheretags.WhereTags import code.model._ +import code.model.dataAccess.AuthUser.findUserByUsernameLocally import code.model.dataAccess._ import code.productAttributeattribute.MappedProductAttribute import code.productattribute.ProductAttributeX @@ -81,7 +82,7 @@ import com.tesobe.model.UpdateBankAccount import net.liftweb.common._ import net.liftweb.json import net.liftweb.json.JsonAST.JField -import net.liftweb.json.{JArray, JBool, JInt, JObject, JValue,JString} +import net.liftweb.json.{JArray, JBool, JInt, JObject, JString, JValue} import net.liftweb.mapper.{By, _} import net.liftweb.util.Helpers.{hours, now, time, tryo} import net.liftweb.util.Mailer @@ -4943,7 +4944,20 @@ object LocalMappedConnector extends Connector with MdcLoggable { //NOTE: this method is not for mapped connector, we put it here for the star default implementation. // : we call that method only when we set external authentication and provider is not OBP-API - override def checkExternalUserExists(username: String, callContext: Option[CallContext]): Box[InboundExternalUser] = Failure("") + override def checkExternalUserExists(username: String, callContext: Option[CallContext]): Box[InboundExternalUser] = { + findUserByUsernameLocally(username).map( user => + InboundExternalUser(aud = "", + exp = "", + iat = "", + iss = "", + sub = user.username.get, + azp = None, + email = None, + emailVerified = None, + name = None + ) + ) + } override def validateUserAuthContextUpdateRequest( 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 4b2552455..1f2fc02e1 100644 --- a/obp-api/src/main/scala/code/model/dataAccess/AuthUser.scala +++ b/obp-api/src/main/scala/code/model/dataAccess/AuthUser.scala @@ -1149,7 +1149,7 @@ def restoreSomeSessions(): Unit = { * Find the authUser by author user name(authUser and resourceUser are the same). * Only search for the local database. */ - protected def findUserByUsernameLocally(name: String): Box[TheUserType] = { + def findUserByUsernameLocally(name: String): Box[TheUserType] = { find(By(this.username, name)) } From 60c37d86c8f0ccd0a2423542ec985d784acc6db5 Mon Sep 17 00:00:00 2001 From: hongwei Date: Tue, 15 Jun 2021 23:32:54 +0200 Subject: [PATCH 042/147] feature/OBPv400 added new getBankAccountBalances endpoint and tweaked the balanceType for BGV1.3 --- .../AccountInformationServiceAISApi.scala | 6 ++- .../v1_3/JSONFactory_BERLIN_GROUP_1_3.scala | 19 +++---- .../main/scala/code/api/util/NewStyle.scala | 6 +++ .../scala/code/api/v4_0_0/APIMethods400.scala | 30 ++++++++++- .../code/api/v4_0_0/JSONFactory4.0.0.scala | 23 ++++++++- .../scala/code/bankconnectors/Connector.scala | 2 + .../bankconnectors/LocalMappedConnector.scala | 18 +++++++ .../code/api/v4_0_0/AccountBalanceTest.scala | 51 +++++++++++++++++++ .../commons/model/BankingModel.scala | 15 ++++++ 9 files changed, 155 insertions(+), 15 deletions(-) create mode 100644 obp-api/src/test/scala/code/api/v4_0_0/AccountBalanceTest.scala diff --git a/obp-api/src/main/scala/code/api/berlin/group/v1_3/AccountInformationServiceAISApi.scala b/obp-api/src/main/scala/code/api/berlin/group/v1_3/AccountInformationServiceAISApi.scala index 51e29384b..222228bd6 100644 --- a/obp-api/src/main/scala/code/api/berlin/group/v1_3/AccountInformationServiceAISApi.scala +++ b/obp-api/src/main/scala/code/api/berlin/group/v1_3/AccountInformationServiceAISApi.scala @@ -357,8 +357,9 @@ The account-id is constant at least throughout the lifecycle of a given consent. _ <- passesPsd2Aisp(callContext) (account: BankAccount, callContext) <- NewStyle.function.getBankAccountByAccountId(accountId, callContext) _ <- checkAccountAccess(ViewId(SYSTEM_READ_BALANCES_BERLIN_GROUP_VIEW_ID), u, account, callContext) + (accountBalances, callContext)<- NewStyle.function.getBankAccountBalances(BankIdAccountId(account.bankId,account.accountId), callContext) } yield { - (JSONFactory_BERLIN_GROUP_1_3.createAccountBalanceJSON(account), HttpCode.`200`(callContext)) + (JSONFactory_BERLIN_GROUP_1_3.createAccountBalanceJSON(account, accountBalances), HttpCode.`200`(callContext)) } } } @@ -487,8 +488,9 @@ This account-id then can be retrieved by the _ <- passesPsd2Aisp(callContext) (account: BankAccount, callContext) <- NewStyle.function.getBankAccountByAccountId(AccountId(accountId), callContext) _ <- checkAccountAccess(ViewId(SYSTEM_READ_BALANCES_BERLIN_GROUP_VIEW_ID), u, account, callContext) + (accountBalances, callContext)<- NewStyle.function.getBankAccountBalances(BankIdAccountId(account.bankId,account.accountId), callContext) } yield { - (JSONFactory_BERLIN_GROUP_1_3.createCardAccountBalanceJSON(account), HttpCode.`200`(callContext)) + (JSONFactory_BERLIN_GROUP_1_3.createCardAccountBalanceJSON(account, accountBalances), HttpCode.`200`(callContext)) } } } diff --git a/obp-api/src/main/scala/code/api/berlin/group/v1_3/JSONFactory_BERLIN_GROUP_1_3.scala b/obp-api/src/main/scala/code/api/berlin/group/v1_3/JSONFactory_BERLIN_GROUP_1_3.scala index 8e3bcc6f2..a9f17eb7a 100644 --- a/obp-api/src/main/scala/code/api/berlin/group/v1_3/JSONFactory_BERLIN_GROUP_1_3.scala +++ b/obp-api/src/main/scala/code/api/berlin/group/v1_3/JSONFactory_BERLIN_GROUP_1_3.scala @@ -359,12 +359,12 @@ object JSONFactory_BERLIN_GROUP_1_3 extends CustomJsonFormats { (iBan, bBan) } - def createCardAccountBalanceJSON(bankAccount: BankAccount): CardAccountBalancesV13 = { - val accountBalancesV13 = createAccountBalanceJSON(bankAccount: BankAccount) + def createCardAccountBalanceJSON(bankAccount: BankAccount, accountBalances: AccountBalances): CardAccountBalancesV13 = { + val accountBalancesV13 = createAccountBalanceJSON(bankAccount: BankAccount, accountBalances) CardAccountBalancesV13(accountBalancesV13.account,accountBalancesV13.`balances`) } - def createAccountBalanceJSON(bankAccount: BankAccount): AccountBalancesV13 = { + def createAccountBalanceJSON(bankAccount: BankAccount, accountBalances: AccountBalances): AccountBalancesV13 = { val (iban: String, bban: String) = getIbanAndBban(bankAccount) @@ -372,17 +372,14 @@ object JSONFactory_BERLIN_GROUP_1_3 extends CustomJsonFormats { account = FromAccount( iban = iban, ), - `balances` = AccountBalance( - balanceAmount = AmountOfMoneyV13( - currency = APIUtil.stringOrNull(bankAccount.currency), - amount = bankAccount.balance.toString() - ), - balanceType = "OpeningBooked", + `balances` = accountBalances.balances.map(accountBalance => AccountBalance( + balanceAmount = AmountOfMoneyV13(accountBalance.balance.currency, accountBalance.balance.amount), + balanceType = accountBalance.balanceType, lastChangeDateTime = APIUtil.dateOrNull(bankAccount.lastUpdate), referenceDate = APIUtil.dateOrNull(bankAccount.lastUpdate), lastCommittedTransaction = "String" - ) :: Nil - ) + ) + )) } def createTransactionJSON(bankAccount: BankAccount, transaction : ModeratedTransaction, creditorAccount: CreditorAccountJson) : TransactionJsonV13 = { 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 0b2b1b072..bb6550d1e 100644 --- a/obp-api/src/main/scala/code/api/util/NewStyle.scala +++ b/obp-api/src/main/scala/code/api/util/NewStyle.scala @@ -340,6 +340,12 @@ object NewStyle { } } + def getBankAccountBalances(bankIdAccountId: BankIdAccountId, callContext: Option[CallContext]): OBPReturnType[AccountBalances] = { + Connector.connector.vend.getBankAccountBalances(bankIdAccountId: BankIdAccountId, callContext: Option[CallContext]) map { i => + (unboxFullOrFail(i._1, callContext,s"$InvalidConnectorResponseForGetBankAccounts", 400 ), i._2) + } + } + def getAccountRouting(bankId: Option[BankId], scheme: String, address: String, callContext: Option[CallContext]) : OBPReturnType[BankAccountRouting] = { Future(Connector.connector.vend.getAccountRouting(bankId: Option[BankId], scheme: String, address : String, callContext: Option[CallContext])) map { i => unboxFullOrFail(i, callContext,s"$AccountRoutingNotFound Current scheme is $scheme, current address is $address, current bankId is $bankId", 404 ) 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 d27cc1c93..d93ec0f63 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 @@ -26,7 +26,7 @@ import code.api.v3_0_0.JSONFactory300 import code.api.v3_0_0.JSONFactory300.transformToAtmFromV300 import code.api.v3_1_0._ import code.api.v4_0_0.dynamic.DynamicEndpointHelper.DynamicReq -import code.api.v4_0_0.JSONFactory400.{createBalancesJson, createBankAccountJSON, createCallsLimitJson, createNewCoreBankAccountJson} +import code.api.v4_0_0.JSONFactory400.{createBalancesJson, createBankAccountJSON, createCallsLimitJson, createNewCoreBankAccountJson,createAccountBalancesJson} import code.api.v4_0_0.dynamic.practise.PractiseEndpoint import code.api.v4_0_0.dynamic.{CompiledObjects, DynamicEndpointHelper, DynamicEntityHelper, DynamicEntityInfo, EntityName, MockResponseHolder} import code.apicollection.MappedApiCollectionsProvider @@ -3138,6 +3138,34 @@ trait APIMethods400 { } } + staticResourceDocs += ResourceDoc( + getBankAccountBalances, + implementedInApiVersion, + nameOf(getBankAccountBalances), + "GET", + "/banks/BANK_ID/accounts/ACCOUNT_ID/balances", + "Get Account Balances", + """Get the Balances for one Account of the current User at one bank.""", + emptyObjectJson, + accountBalanceV400, + List($UserNotLoggedIn, $BankNotFound, CannotFindAccountAccess, UnknownError), + apiTagAccount :: apiTagPSD2AIS :: apiTagPsd2 :: apiTagNewStyle :: Nil + ) + + lazy val getBankAccountBalances : OBPEndpoint = { + case "banks" :: BankId(bankId) :: "accounts" :: AccountId(accountId) :: "balances" :: Nil JsonGet _ => { + cc => + for { + (Full(u), callContext) <- SS.user + availablePrivateAccounts <- Views.views.vend.getPrivateBankAccountsFuture(u, bankId) + bankIdAcconutId <- NewStyle.function.tryons(s"$CannotFindAccountAccess AccountId(${accountId.value})", 400, cc.callContext) {availablePrivateAccounts.find(_.accountId==accountId).get} + (accountBalances, callContext)<- NewStyle.function.getBankAccountBalances(bankIdAcconutId, callContext) + } yield{ + (createAccountBalancesJson(accountBalances), HttpCode.`200`(callContext)) + } + } + } + staticResourceDocs += ResourceDoc( getFirehoseAccountsAtOneBank, implementedInApiVersion, 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 583c5b4e5..f9df7c3f5 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 @@ -249,6 +249,15 @@ case class AccountBalanceJsonV400( balances: List[BalanceJsonV400] ) +case class AccountBalancesJsonV400( + account_id: String, + bank_id: String, + account_routings: List[AccountRouting], + label: String, + balances: List[BalanceJsonV400], + +) + case class PostCustomerPhoneNumberJsonV400(mobile_phone_number: String) case class PostDirectDebitJsonV400(customer_id: String, user_id: String, @@ -1194,12 +1203,24 @@ object JSONFactory400 { account_routings = account.accountRoutings, label = account.label, balances = List( - BalanceJsonV400(`type` = "", currency = account.balance.currency, amount = account.balance.amount) + BalanceJsonV400(`type` = "OpeningBooked", currency = account.balance.currency, amount = account.balance.amount) ) ) ) ) } + + def createAccountBalancesJson(accountBalances: AccountBalances) = { + AccountBalanceJsonV400( + account_id = accountBalances.id, + bank_id = accountBalances.bankId, + account_routings = accountBalances.accountRoutings, + label = accountBalances.label, + balances = accountBalances.balances.map( balance => + BalanceJsonV400(`type`=balance.balanceType, currency = balance.balance.currency, amount = balance.balance.amount) + ) + ) + } def createConsentsJsonV400(consents: List[MappedConsent]): ConsentsJsonV400= { ConsentsJsonV400(consents.map(c => ConsentJsonV400(c.consentId, c.jsonWebToken, c.status, c.apiStandard, c.apiVersion))) diff --git a/obp-api/src/main/scala/code/bankconnectors/Connector.scala b/obp-api/src/main/scala/code/bankconnectors/Connector.scala index b09b3bfbe..f666c019c 100644 --- a/obp-api/src/main/scala/code/bankconnectors/Connector.scala +++ b/obp-api/src/main/scala/code/bankconnectors/Connector.scala @@ -492,6 +492,8 @@ trait Connector extends MdcLoggable { def getBankAccounts(bankIdAccountIds: List[BankIdAccountId], callContext: Option[CallContext]) : OBPReturnType[Box[List[BankAccount]]]= Future{(Failure(setUnimplementedError), callContext)} def getBankAccountsBalances(bankIdAccountIds: List[BankIdAccountId], callContext: Option[CallContext]) : OBPReturnType[Box[AccountsBalances]]= Future{(Failure(setUnimplementedError), callContext)} + + def getBankAccountBalances(bankIdAccountId: BankIdAccountId, callContext: Option[CallContext]) : OBPReturnType[Box[AccountBalances]]= Future{(Failure(setUnimplementedError), callContext)} def getCoreBankAccountsLegacy(bankIdAccountIds: List[BankIdAccountId], callContext: Option[CallContext]) : Box[(List[CoreAccount], Option[CallContext])] = Failure(setUnimplementedError) diff --git a/obp-api/src/main/scala/code/bankconnectors/LocalMappedConnector.scala b/obp-api/src/main/scala/code/bankconnectors/LocalMappedConnector.scala index 4e0b168d0..d8baad5c4 100644 --- a/obp-api/src/main/scala/code/bankconnectors/LocalMappedConnector.scala +++ b/obp-api/src/main/scala/code/bankconnectors/LocalMappedConnector.scala @@ -743,6 +743,24 @@ object LocalMappedConnector extends Connector with MdcLoggable { )), callContext) } + override def getBankAccountBalances(bankIdAccountId: BankIdAccountId, callContext: Option[CallContext]): OBPReturnType[Box[AccountBalances]] = + Future { + for { + bankAccount <- getBankAccountOld(bankIdAccountId.bankId, bankIdAccountId.accountId) ?~! s"${ErrorMessages.BankAccountNotFound} current BANK_ID(${bankIdAccountId.bankId}) and ACCOUNT_ID(${bankIdAccountId.accountId})" + accountBalances = AccountBalances( + id = bankAccount.accountId.value, + label = bankAccount.label, + bankId = bankAccount.bankId.value, + accountRoutings = bankAccount.accountRoutings.map(accountRounting => AccountRouting(accountRounting.scheme, accountRounting.address)), + balances = List(BankAccountBalance(AmountOfMoney(bankAccount.currency, bankAccount.balance.toString),"OpeningBooked")), + overallBalance = AmountOfMoney(bankAccount.currency, bankAccount.balance.toString), + overallBalanceDate = now + ) + } yield { + (accountBalances,callContext) + } + } + override def checkBankAccountExistsLegacy(bankId: BankId, accountId: AccountId, callContext: Option[CallContext]): Box[(BankAccount, Option[CallContext])] = { getBankAccountLegacy(bankId: BankId, accountId: AccountId, callContext) } diff --git a/obp-api/src/test/scala/code/api/v4_0_0/AccountBalanceTest.scala b/obp-api/src/test/scala/code/api/v4_0_0/AccountBalanceTest.scala new file mode 100644 index 000000000..843335ec3 --- /dev/null +++ b/obp-api/src/test/scala/code/api/v4_0_0/AccountBalanceTest.scala @@ -0,0 +1,51 @@ +package code.api.v4_0_0 + +import code.api.util.APIUtil.OAuth._ +import code.api.util.ErrorMessages.CannotFindAccountAccess +import code.api.v4_0_0.OBPAPI4_0_0.Implementations4_0_0 +import com.github.dwickern.macros.NameOf.nameOf +import com.openbankproject.commons.util.ApiVersion +import org.scalatest.Tag + +class AccountBalanceTest extends V400ServerSetup { + /** + * Test tags + * Example: To run tests with tag "getPermissions": + * mvn test -D tagsToInclude + * + * This is made possible by the scalatest maven plugin + */ + object VersionOfApi extends Tag(ApiVersion.v4_0_0.toString) + object ApiEndpoint1 extends Tag(nameOf(Implementations4_0_0.getBankAccountsBalances)) + object ApiEndpoint2 extends Tag(nameOf(Implementations4_0_0.getBankAccountBalances)) + + lazy val bankId = randomBankId + lazy val bankAccount = randomPrivateAccountViaEndpoint(bankId) + + feature(s"test $ApiEndpoint1 and $ApiEndpoint2 version $VersionOfApi - Authorized access") { + scenario("We will call the endpoint with user credentials", VersionOfApi, ApiEndpoint1, ApiEndpoint2) { + val requestGetAccountBalances = (v4_0_0_Request / "banks" / bankAccount.bank_id / "accounts" / bankAccount.id / "balances").GET <@ (user1) + val responseGetAccountBalances = makeGetRequest(requestGetAccountBalances) + Then("We should get a 200") + responseGetAccountBalances.code should equal(200) + + val requestGetAccountsBalances = (v4_0_0_Request / "banks" / bankId / "balances").GET <@ (user1) + val responseGetAccountsBalances = makeGetRequest(requestGetAccountsBalances) + Then("We should get a 200") + responseGetAccountsBalances.code should equal(200) + } + scenario("We will call the endpoint with user2 who has no account access ", VersionOfApi, ApiEndpoint1, ApiEndpoint2) { + val requestGetAccountBalances = (v4_0_0_Request / "banks" / bankAccount.bank_id / "accounts" / bankAccount.id / "balances").GET <@ (user2) + val responseGetAccountBalances = makeGetRequest(requestGetAccountBalances) + Then("We should get a 200") + responseGetAccountBalances.code should equal(400) + responseGetAccountBalances.body.toString contains(CannotFindAccountAccess) should be (true) + + val requestGetAccountsBalances = (v4_0_0_Request / "banks" / bankId / "balances").GET <@ (user2) + val responseGetAccountsBalances = makeGetRequest(requestGetAccountsBalances) + Then("We should get a 200") + responseGetAccountsBalances.code should equal(200) + responseGetAccountsBalances.body.extract[AccountsBalancesJsonV400].accounts.length should equal(0) + } + } +} diff --git a/obp-commons/src/main/scala/com/openbankproject/commons/model/BankingModel.scala b/obp-commons/src/main/scala/com/openbankproject/commons/model/BankingModel.scala index 35bfe735c..153605a6b 100644 --- a/obp-commons/src/main/scala/com/openbankproject/commons/model/BankingModel.scala +++ b/obp-commons/src/main/scala/com/openbankproject/commons/model/BankingModel.scala @@ -357,6 +357,21 @@ case class AccountBalance( balance: AmountOfMoney ) +case class AccountBalances( + id: String, + label: String, + bankId: String, + accountRoutings: List[AccountRouting], + balances: List[BankAccountBalance], + overallBalance: AmountOfMoney, + overallBalanceDate: Date +) + +case class BankAccountBalance( + balance: AmountOfMoney, + balanceType: String, +) + case class AccountsBalances( accounts: List[AccountBalance], overallBalance: AmountOfMoney, From 1819b61ebed25abef91988d710eb471c9b738297 Mon Sep 17 00:00:00 2001 From: hongwei Date: Wed, 16 Jun 2021 12:07:40 +0200 Subject: [PATCH 043/147] refactor/tweaked the BGv1.3 document --- .../AccountInformationServiceAISApi.scala | 141 ++++++++---------- .../v1_3/JSONFactory_BERLIN_GROUP_1_3.scala | 21 +-- 2 files changed, 66 insertions(+), 96 deletions(-) diff --git a/obp-api/src/main/scala/code/api/berlin/group/v1_3/AccountInformationServiceAISApi.scala b/obp-api/src/main/scala/code/api/berlin/group/v1_3/AccountInformationServiceAISApi.scala index 222228bd6..942784d42 100644 --- a/obp-api/src/main/scala/code/api/berlin/group/v1_3/AccountInformationServiceAISApi.scala +++ b/obp-api/src/main/scala/code/api/berlin/group/v1_3/AccountInformationServiceAISApi.scala @@ -260,33 +260,35 @@ of the PSU at this ASPSP. """, emptyObjectJson, json.parse("""{ - | "accounts":[{ - | "resourceId":"8ca8a7e4-6d02-40e3-a129-0b2bf89de9f0", - | "iban":"DE91 1000 0000 0123 4567 89", - | "bban":" 1000 0000 0123 4567 89", - | "currency":"EUR", - | "name":"TOM", - | "product":"AC", - | "cashAccountType":"AC", - | "bic":"AAAADEBBXXX", - | "balances":{ - | "balanceAmount":{ - | "currency":"EUR", - | "amount":"50.89" - | }, - | "balanceType":"AC", - | "lastChangeDateTime":"2020-07-02T10:23:57.81Z", - | "referenceDate":"2020-07-02", - | "lastCommittedTransaction":"entryReference of the last commited transaction to support the TPP in identifying whether all PSU transactions are already known." + | "accounts": [ + | { + | "resourceId": "3dc3d5b3-7023-4848-9853-f5400a64e80f", + | "iban": "DE2310010010123456789", + | "currency": "EUR", + | "product": "Girokonto", + | "cashAccountType": "CACC", + | "name": "Main Account", + | "_links": { + | "balances": { + | "href": "/v1/accounts/3dc3d5b3-7023-4848-9853-f5400a64e80f/balances" + | } + | } | }, - | "_links":{ - | "balances":{ - | "href":"/v1.3/accounts/8ca8a7e4-6d02-40e3-a129-0b2bf89de9f0/balances" + | { + | "resourceId": "3dc3d5b3-7023-4848-9853-f5400a64e81g", + | "iban": "DE2310010010123456788", + | "currency": "USD", + | "product": "Fremdwährungskonto", + | "cashAccountType": "CACC", + | "name": "US Dollar Account", + | "_links": { + | "balances": { + | "href": "/v1/accounts/3dc3d5b3-7023-4848-9853-f5400a64e81g/balances" + | } | } | } - | }] - |} - |""".stripMargin), + | ] + |}""".stripMargin), List(UserNotLoggedIn, UnknownError), ApiTag("Account Information Service (AIS)") :: apiTagBerlinGroupM :: Nil ) @@ -391,24 +393,8 @@ respectively the OAuth2 access token. "currency": "EUR", "amount": 15000 }, - "balances": [ - { - "balanceType": "interimBooked", - "balanceAmount": { - "currency": "EUR", - "amount": 14355.78 - } - }, - { - "balanceType": "nonBilled", - "balanceAmount": { - "currency": "EUR", - "amount": 4175.86 - } - } - ], "_links": { - "transactions": { + "balances": { "href": "/v1/card-accounts/3d9a81b3-a47d-4130-8765-a9c0ff861b99/transactions" } } @@ -944,24 +930,22 @@ Give detailed information about the addressed account together with balance info """, emptyObjectJson, json.parse("""{ - "cashAccountType" : { }, - "product" : "product", - "resourceId" : "resourceId", - "bban" : "BARC12345612345678", - "_links" : { - "balances" : "/v1.3/payments/sepa-credit-transfers/1234-wertiq-983", - "transactions" : "/v1.3/payments/sepa-credit-transfers/1234-wertiq-983" - }, - "usage" : "PRIV", - "balances" : "", - "iban" : "FR7612345987650123456789014", - "linkedAccounts" : "linkedAccounts", - "name" : "name", - "currency" : "EUR", - "details" : "details", - "msisdn" : "+49 170 1234567", - "bic" : "AAAADEBBXXX", - "status" : { } + "account": { + "resourceId": "3dc3d5b3-7023-4848-9853-f5400a64e80f", + "iban": "FR7612345987650123456789014", + "currency": "EUR", + "product": "Girokonto", + "cashAccountType": "CACC", + "name": "Main Account", + "_links": { + "balances": { + "href": "/v1/accounts/3dc3d5b3-7023-4848-9853-f5400a64e80f/balances" + }, + "transactions": { + "href": "/v1/accounts/3dc3d5b3-7023-4848-9853-f5400a64e80f/transactions" + } + } + } }"""), List(UserNotLoggedIn, UnknownError), ApiTag("Account Information Service (AIS)") :: apiTagBerlinGroupM :: Nil @@ -996,24 +980,27 @@ respectively the OAuth2 access token. """, emptyObjectJson, json.parse("""{ - "balances" : "", - "product" : "product", - "resourceId" : "resourceId", - "maskedPan" : "123456xxxxxx1234", - "_links" : { - "balances" : "/v1.3/payments/sepa-credit-transfers/1234-wertiq-983", - "transactions" : "/v1.3/payments/sepa-credit-transfers/1234-wertiq-983" - }, - "usage" : "PRIV", - "name" : "name", - "creditLimit" : { - "amount" : "123", - "currency" : "EUR" - }, - "currency" : "EUR", - "details" : "details", - "status" : { } -}"""), + | "cardAccount": { + | "resourceId": "3d9a81b3-a47d-4130-8765-a9c0ff861b99", + | "maskedPan": "525412******3241", + | "currency": "EUR", + | "name": "Main", + | "product": "Basic Credit", + | "status": "enabled", + | "creditLimit": { + | "currency": "EUR", + | "amount": "15000" + | }, + | "_links": { + | "balances": { + | "href": "/v1/card-accounts/3d9a81b3-a47d-4130-8765-a9c0ff861b99/balances" + | }, + | "transactions": { + | "href": "/v1/card-accounts/3d9a81b3-a47d-4130-8765-a9c0ff861b99/transactions" + | } + | } + | } + |}""".stripMargin), List(UserNotLoggedIn, UnknownError), ApiTag("Account Information Service (AIS)") :: Nil ) diff --git a/obp-api/src/main/scala/code/api/berlin/group/v1_3/JSONFactory_BERLIN_GROUP_1_3.scala b/obp-api/src/main/scala/code/api/berlin/group/v1_3/JSONFactory_BERLIN_GROUP_1_3.scala index a9f17eb7a..8dfbadbb3 100644 --- a/obp-api/src/main/scala/code/api/berlin/group/v1_3/JSONFactory_BERLIN_GROUP_1_3.scala +++ b/obp-api/src/main/scala/code/api/berlin/group/v1_3/JSONFactory_BERLIN_GROUP_1_3.scala @@ -51,7 +51,7 @@ object JSONFactory_BERLIN_GROUP_1_3 extends CustomJsonFormats { // linkedAccounts: String ="string", // usage: String ="PRIV", // details: String ="", - balances: CoreAccountBalancesJson, +// balances: CoreAccountBalancesJson,// We put this under the _links, not need to show it here. _links: CoreAccountLinksJsonV13, ) @@ -277,14 +277,7 @@ object JSONFactory_BERLIN_GROUP_1_3 extends CustomJsonFormats { x => val (iBan: String, bBan: String) = getIbanAndBban(x) - val balance = - CoreAccountBalancesJson( - balanceAmount = AmountOfMoneyV13(x.currency,x.balance.toString()), - balanceType = "OpeningBooked", - lastChangeDateTime= APIUtil.dateOrNull(x.lastUpdate), - referenceDate = APIUtil.dateOrNull(x.lastUpdate), - lastCommittedTransaction = "" - ) + CoreAccountJsonV13( resourceId = x.accountId.value, iban = iBan, @@ -294,7 +287,6 @@ object JSONFactory_BERLIN_GROUP_1_3 extends CustomJsonFormats { bic = getBicFromBankId(x.bankId.value), cashAccountType = x.accountType, product = x.accountType, - balances = balance, _links = CoreAccountLinksJsonV13(LinkHrefJson(s"/${OBP_BERLIN_GROUP_1_3.apiVersion.urlPrefix}/${OBP_BERLIN_GROUP_1_3.version}/accounts/${x.accountId.value}/balances")) ) } @@ -306,14 +298,6 @@ object JSONFactory_BERLIN_GROUP_1_3 extends CustomJsonFormats { x => val (iBan: String, bBan: String) = getIbanAndBban(x) - val balance = - CoreAccountBalancesJson( - balanceAmount = AmountOfMoneyV13(x.currency,x.balance.toString()), - balanceType = "OpeningBooked", - lastChangeDateTime= APIUtil.dateOrNull(x.lastUpdate), - referenceDate = APIUtil.dateOrNull(x.lastUpdate), - lastCommittedTransaction = "String" - ) CoreAccountJsonV13( resourceId = x.accountId.value, iban = iBan, @@ -323,7 +307,6 @@ object JSONFactory_BERLIN_GROUP_1_3 extends CustomJsonFormats { bic = getBicFromBankId(x.bankId.value), cashAccountType = x.accountType, product = x.accountType, - balances = balance, _links = CoreAccountLinksJsonV13(LinkHrefJson(s"/${OBP_BERLIN_GROUP_1_3.apiVersion.urlPrefix}/${OBP_BERLIN_GROUP_1_3.version}/accounts/${x.accountId.value}/balances")) ) } From 87935eca74e341ebde870b0a37fd5d2e63e0b2cd Mon Sep 17 00:00:00 2001 From: hongwei Date: Wed, 16 Jun 2021 15:00:06 +0200 Subject: [PATCH 044/147] bugfix/added the support for AccountBalances.id convertIds --- obp-api/src/main/scala/code/util/Helper.scala | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/obp-api/src/main/scala/code/util/Helper.scala b/obp-api/src/main/scala/code/util/Helper.scala index 951233a6a..3f9a93eb4 100644 --- a/obp-api/src/main/scala/code/util/Helper.scala +++ b/obp-api/src/main/scala/code/util/Helper.scala @@ -15,7 +15,7 @@ import net.liftweb.json.JsonAST._ import net.liftweb.json.{DateFormat, Formats} import org.apache.commons.lang3.StringUtils import com.openbankproject.commons.ExecutionContext.Implicits.global -import com.openbankproject.commons.model.{AccountBalance, AccountHeld, AccountId, CoreAccount, Customer, CustomerId} +import com.openbankproject.commons.model.{AccountBalance, AccountBalances, AccountHeld, AccountId, CoreAccount, Customer, CustomerId} import com.openbankproject.commons.util.{ReflectUtils, RequiredFieldValidation, RequiredInfo} import com.tesobe.CacheKeyFromArguments import net.liftweb.http.S @@ -415,6 +415,7 @@ object Helper{ (fieldName.equalsIgnoreCase("accountId") && fieldType =:= typeOf[String])|| (ownerType <:< typeOf[CoreAccount] && fieldName.equalsIgnoreCase("id") && fieldType =:= typeOf[String])|| (ownerType <:< typeOf[AccountBalance] && fieldName.equalsIgnoreCase("id") && fieldType =:= typeOf[String])|| + (ownerType <:< typeOf[AccountBalances] && fieldName.equalsIgnoreCase("id") && fieldType =:= typeOf[String])|| (ownerType <:< typeOf[AccountHeld] && fieldName.equalsIgnoreCase("id") && fieldType =:= typeOf[String]) } From 876d894fb4bb7d0367a7870d4baabfe5be94cd55 Mon Sep 17 00:00:00 2001 From: hongwei Date: Wed, 16 Jun 2021 16:03:45 +0200 Subject: [PATCH 045/147] bugfix/remove the
-
The main content gets bound here From c54518d5afc7fbdf27f6d0f0fbdba8f75467483f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marko=20Mili=C4=87?= Date: Wed, 16 Jun 2021 17:48:26 +0200 Subject: [PATCH 046/147] feature/User Invitation Flow - POW --- .../scala/code/snippet/UserInvitation.scala | 77 ++++++++++++++++--- obp-api/src/main/webapp/user-invitation.html | 35 ++++++--- 2 files changed, 91 insertions(+), 21 deletions(-) diff --git a/obp-api/src/main/scala/code/snippet/UserInvitation.scala b/obp-api/src/main/scala/code/snippet/UserInvitation.scala index 2b7e36d0e..93aa3eac6 100644 --- a/obp-api/src/main/scala/code/snippet/UserInvitation.scala +++ b/obp-api/src/main/scala/code/snippet/UserInvitation.scala @@ -26,15 +26,17 @@ TESOBE (http://www.tesobe.com/) */ package code.snippet -import code.api.util.ErrorMessages -import code.users.UserInvitationProvider +import code.model.dataAccess.{AuthUser, ResourceUser} +import code.users.{UserInvitationProvider, Users} +import code.util.Helper import code.util.Helper.MdcLoggable import code.webuiprops.MappedWebUiPropsProvider.getWebUiPropsValue -import com.openbankproject.commons.model.BankId +import com.openbankproject.commons.model.User import net.liftweb.common.Box import net.liftweb.http.{RequestVar, S, SHtml} import net.liftweb.util.CssSel import net.liftweb.util.Helpers._ +import org.apache.commons.lang3.StringUtils import scala.collection.immutable.List @@ -45,41 +47,94 @@ class UserInvitation extends MdcLoggable { private object companyVar extends RequestVar("") private object countryVar extends RequestVar("None") private object devEmailVar extends RequestVar("") + private object usernameVar extends RequestVar("") // Can be used to show link to an online form to collect more information about the App / Startup val registrationMoreInfoUrl = getWebUiPropsValue("webui_post_user_invitation_more_info_url", "") - val registrationConsumerButtonValue: String = getWebUiPropsValue("webui_post_user_invitation_submit_button_value", "Proceed") + val registrationConsumerButtonValue: String = getWebUiPropsValue("webui_post_user_invitation_submit_button_value", "Register as a Developer") def registerForm: CssSel = { - val countries = List(("None", "None"), ("Bahrain", "Bahrain"), ("Germany", "Germany"), ("Mexico", "Mexico"), ("UK", "UK")) val secretLink = S.param("id").getOrElse("0") val userInvitation = UserInvitationProvider.userInvitationProvider.vend.getUserInvitationBySecretLink(secretLink.toLong) - firstNameVar.set(userInvitation.map(_.firstName).getOrElse("None")) - lastNameVar.set(userInvitation.map(_.lastName).getOrElse("None")) - devEmailVar.set(userInvitation.map(_.email).getOrElse("None")) + val firstName = userInvitation.map(_.firstName).getOrElse("None") + firstNameVar.set(firstName) + val lastName = userInvitation.map(_.lastName).getOrElse("None") + lastNameVar.set(lastName) + val email = userInvitation.map(_.email).getOrElse("None") + devEmailVar.set(email) companyVar.set(userInvitation.map(_.company).getOrElse("None")) countryVar.set(userInvitation.map(_.country).getOrElse("Bahrain")) + val username = firstName.toLowerCase + "." + lastName.toLowerCase() + usernameVar.set(username) - def submitButtonDefense: Unit = { + def submitButtonDefense(): Unit = { + val username = firstNameVar.is + "." + lastNameVar.is + createResourceUser( + provider = "OBP-User-Invitation", + providerId = Some(username), + name = Some(firstName + " " + lastName), + email = Some(email) + ).map{ u => + createAuthUser(user = u, firstName = firstName, lastName = lastName, password = "") + val resetLink = AuthUser.passwordResetUrl(u.idGivenByProvider, u.emailAddress, u.userId) + S.redirectTo(resetLink) + } } + def showErrorsForUsername() = { + val usernameError = Helper.i18n("unique.username") + S.error("register-consumer-errors", usernameError) + register & + "#register-consumer-errors *" #> { + ".error *" #> + List(usernameError).map({ e=> + ".errorContent *" #> e + }) + } + } + def register = { "form" #> { - "#country" #> SHtml.select(countries, Box!! countryVar.is, countryVar(_)) & + "#country" #> SHtml.text(countryVar.is, countryVar(_)) & "#firstName" #> SHtml.text(firstNameVar.is, firstNameVar(_)) & "#lastName" #> SHtml.text(lastNameVar.is, lastNameVar(_)) & "#companyName" #> SHtml.text(companyVar.is, companyVar(_)) & "#devEmail" #> SHtml.text(devEmailVar, devEmailVar(_)) & + "#username" #> SHtml.text(usernameVar, usernameVar(_)) & "type=submit" #> SHtml.submit(s"$registrationConsumerButtonValue", () => submitButtonDefense) } & "#register-consumer-success" #> "" } - register + if(Users.users.vend.getUserByUserName(username).isDefined) showErrorsForUsername() else register + } + private def createAuthUser(user: User, firstName: String, lastName: String, password: String): Box[AuthUser] = tryo { + val newUser = AuthUser.create + .firstName(firstName) + .lastName(lastName) + .email(user.emailAddress) + .user(user.userPrimaryKey.value) + .username(user.idGivenByProvider) + .provider(user.provider) + .password(password) + .validated(true) + // Save the user + newUser.saveMe() + } + + private def createResourceUser(provider: String, providerId: Option[String], name: Option[String], email: Option[String]): Box[ResourceUser] = { + Users.users.vend.createResourceUser( + provider = provider, + providerId = providerId, + createdByConsentId = None, + name = name, + email = email, + userId = None + ) } } diff --git a/obp-api/src/main/webapp/user-invitation.html b/obp-api/src/main/webapp/user-invitation.html index 770799608..3ead45759 100644 --- a/obp-api/src/main/webapp/user-invitation.html +++ b/obp-api/src/main/webapp/user-invitation.html @@ -34,42 +34,57 @@ Berlin 13359, Germany
+
- -
- + +
+
- +
- +
- +
- - + + +
+ +
+
+
+ + +
+ +
From 4ec2f07ce6fc2b1343d30c20639a3b20cf27c704 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marko=20Mili=C4=87?= Date: Thu, 17 Jun 2021 00:35:57 +0200 Subject: [PATCH 047/147] feature/User Invitation Flow - POW 2 --- .../main/scala/code/model/dataAccess/AuthUser.scala | 11 +++++++++++ .../src/main/scala/code/snippet/UserInvitation.scala | 12 +++--------- 2 files changed, 14 insertions(+), 9 deletions(-) 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 1f2fc02e1..7b790f5d6 100644 --- a/obp-api/src/main/scala/code/model/dataAccess/AuthUser.scala +++ b/obp-api/src/main/scala/code/model/dataAccess/AuthUser.scala @@ -1169,6 +1169,17 @@ def restoreSomeSessions(): Unit = { case _ => "" } } + + override def passwordResetXhtml = { + ( + + + + +
{if(S.queryString.isDefined) Helper.i18n("set.your.password") else S.?("reset.your.password")}
{S.?("enter.your.new.password")}
{S.?("repeat.your.new.password")}
 
+ ) + } + /** * Find the authUsers by author email(authUser and resourceUser are the same). * Only search for the local database. diff --git a/obp-api/src/main/scala/code/snippet/UserInvitation.scala b/obp-api/src/main/scala/code/snippet/UserInvitation.scala index 93aa3eac6..e58fcce71 100644 --- a/obp-api/src/main/scala/code/snippet/UserInvitation.scala +++ b/obp-api/src/main/scala/code/snippet/UserInvitation.scala @@ -36,7 +36,6 @@ import net.liftweb.common.Box import net.liftweb.http.{RequestVar, S, SHtml} import net.liftweb.util.CssSel import net.liftweb.util.Helpers._ -import org.apache.commons.lang3.StringUtils import scala.collection.immutable.List @@ -49,11 +48,7 @@ class UserInvitation extends MdcLoggable { private object devEmailVar extends RequestVar("") private object usernameVar extends RequestVar("") - // Can be used to show link to an online form to collect more information about the App / Startup - val registrationMoreInfoUrl = getWebUiPropsValue("webui_post_user_invitation_more_info_url", "") - val registrationConsumerButtonValue: String = getWebUiPropsValue("webui_post_user_invitation_submit_button_value", "Register as a Developer") - def registerForm: CssSel = { @@ -66,20 +61,19 @@ class UserInvitation extends MdcLoggable { val email = userInvitation.map(_.email).getOrElse("None") devEmailVar.set(email) companyVar.set(userInvitation.map(_.company).getOrElse("None")) - countryVar.set(userInvitation.map(_.country).getOrElse("Bahrain")) + countryVar.set(userInvitation.map(_.country).getOrElse("None")) val username = firstName.toLowerCase + "." + lastName.toLowerCase() usernameVar.set(username) def submitButtonDefense(): Unit = { - val username = firstNameVar.is + "." + lastNameVar.is createResourceUser( provider = "OBP-User-Invitation", - providerId = Some(username), + providerId = Some(usernameVar.is), name = Some(firstName + " " + lastName), email = Some(email) ).map{ u => createAuthUser(user = u, firstName = firstName, lastName = lastName, password = "") - val resetLink = AuthUser.passwordResetUrl(u.idGivenByProvider, u.emailAddress, u.userId) + val resetLink = AuthUser.passwordResetUrl(u.idGivenByProvider, u.emailAddress, u.userId) + "?action=set" S.redirectTo(resetLink) } From 11580558b0b73aedd27413453ad71aac8c066939 Mon Sep 17 00:00:00 2001 From: hongwei Date: Thu, 17 Jun 2021 09:24:05 +0200 Subject: [PATCH 048/147] docfix/tweaked the document --- .../api/berlin/group/v1_3/AccountInformationServiceAISApi.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/obp-api/src/main/scala/code/api/berlin/group/v1_3/AccountInformationServiceAISApi.scala b/obp-api/src/main/scala/code/api/berlin/group/v1_3/AccountInformationServiceAISApi.scala index 942784d42..9a15ceff0 100644 --- a/obp-api/src/main/scala/code/api/berlin/group/v1_3/AccountInformationServiceAISApi.scala +++ b/obp-api/src/main/scala/code/api/berlin/group/v1_3/AccountInformationServiceAISApi.scala @@ -395,7 +395,7 @@ respectively the OAuth2 access token. }, "_links": { "balances": { - "href": "/v1/card-accounts/3d9a81b3-a47d-4130-8765-a9c0ff861b99/transactions" + "href": "/v1/card-accounts/3d9a81b3-a47d-4130-8765-a9c0ff861b99/balances" } } } From c56d2196af022f8b9b1179d768c712d6ee09f038 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marko=20Mili=C4=87?= Date: Thu, 17 Jun 2021 12:01:56 +0200 Subject: [PATCH 049/147] feature/User Invitation Flow - POW 3 --- .../code/model/dataAccess/AuthUser.scala | 21 +++++--- .../scala/code/snippet/UserInvitation.scala | 49 +++++++++++-------- obp-api/src/main/webapp/user-invitation.html | 20 +------- 3 files changed, 43 insertions(+), 47 deletions(-) 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 7b790f5d6..eb2141fa4 100644 --- a/obp-api/src/main/scala/code/model/dataAccess/AuthUser.scala +++ b/obp-api/src/main/scala/code/model/dataAccess/AuthUser.scala @@ -1171,13 +1171,20 @@ def restoreSomeSessions(): Unit = { } override def passwordResetXhtml = { - (
- - - - -
{if(S.queryString.isDefined) Helper.i18n("set.your.password") else S.?("reset.your.password")}
{S.?("enter.your.new.password")}
{S.?("repeat.your.new.password")}
 
-
) +
+

{if(S.queryString.isDefined) Helper.i18n("set.your.password") else S.?("reset.your.password")}

+
+
+ +
+
+ +
+
+ +
+
+
} /** diff --git a/obp-api/src/main/scala/code/snippet/UserInvitation.scala b/obp-api/src/main/scala/code/snippet/UserInvitation.scala index e58fcce71..856bb5890 100644 --- a/obp-api/src/main/scala/code/snippet/UserInvitation.scala +++ b/obp-api/src/main/scala/code/snippet/UserInvitation.scala @@ -54,43 +54,50 @@ class UserInvitation extends MdcLoggable { val secretLink = S.param("id").getOrElse("0") val userInvitation = UserInvitationProvider.userInvitationProvider.vend.getUserInvitationBySecretLink(secretLink.toLong) - val firstName = userInvitation.map(_.firstName).getOrElse("None") - firstNameVar.set(firstName) - val lastName = userInvitation.map(_.lastName).getOrElse("None") - lastNameVar.set(lastName) + firstNameVar.set(userInvitation.map(_.firstName).getOrElse("None")) + lastNameVar.set(userInvitation.map(_.lastName).getOrElse("None")) val email = userInvitation.map(_.email).getOrElse("None") devEmailVar.set(email) companyVar.set(userInvitation.map(_.company).getOrElse("None")) countryVar.set(userInvitation.map(_.country).getOrElse("None")) - val username = firstName.toLowerCase + "." + lastName.toLowerCase() - usernameVar.set(username) + usernameVar.set(firstNameVar.is.toLowerCase + "." + lastNameVar.is.toLowerCase()) def submitButtonDefense(): Unit = { - createResourceUser( - provider = "OBP-User-Invitation", - providerId = Some(usernameVar.is), - name = Some(firstName + " " + lastName), - email = Some(email) - ).map{ u => - createAuthUser(user = u, firstName = firstName, lastName = lastName, password = "") - val resetLink = AuthUser.passwordResetUrl(u.idGivenByProvider, u.emailAddress, u.userId) + "?action=set" - S.redirectTo(resetLink) - } - + if(Users.users.vend.getUserByUserName(usernameVar.is).isDefined) showErrorsForUsername() + else if(userInvitation.map(_.status != "CREATED").getOrElse(false)) showErrorsForStatus() + else { + createResourceUser( + provider = "OBP-User-Invitation", + providerId = Some(usernameVar.is), + name = Some(firstNameVar.is + " " + lastNameVar.is), + email = Some(email) + ).map{ u => + createAuthUser(user = u, firstName = firstNameVar.is, lastName = lastNameVar.is, password = "") + val resetLink = AuthUser.passwordResetUrl(u.idGivenByProvider, u.emailAddress, u.userId) + "?action=set" + S.redirectTo(resetLink) + } + } } - def showErrorsForUsername() = { - val usernameError = Helper.i18n("unique.username") + def showError(usernameError: String) = { S.error("register-consumer-errors", usernameError) register & "#register-consumer-errors *" #> { ".error *" #> - List(usernameError).map({ e=> + List(usernameError).map({ e => ".errorContent *" #> e }) } } + def showErrorsForUsername() = { + showError(Helper.i18n("unique.username")) + } + + def showErrorsForStatus() = { + showError(Helper.i18n("user.invitation.is.already.finished")) + } + def register = { "form" #> { "#country" #> SHtml.text(countryVar.is, countryVar(_)) & @@ -103,7 +110,7 @@ class UserInvitation extends MdcLoggable { } & "#register-consumer-success" #> "" } - if(Users.users.vend.getUserByUserName(username).isDefined) showErrorsForUsername() else register + register } private def createAuthUser(user: User, firstName: String, lastName: String, password: String): Box[AuthUser] = tryo { diff --git a/obp-api/src/main/webapp/user-invitation.html b/obp-api/src/main/webapp/user-invitation.html index 3ead45759..99ee87534 100644 --- a/obp-api/src/main/webapp/user-invitation.html +++ b/obp-api/src/main/webapp/user-invitation.html @@ -47,44 +47,26 @@ Berlin 13359, Germany
-
- -
-
- -
-
- -
-
- -
- + -
- -
-
- -
From 1171f280396e56474d570e42703f703db9bb839e Mon Sep 17 00:00:00 2001 From: hongwei Date: Thu, 17 Jun 2021 14:45:13 +0200 Subject: [PATCH 050/147] bugfix/added the guard for DynamicEntity duplicated creation --- .../SwaggerDefinitionsJSON.scala | 15 +++- .../main/scala/code/api/util/NewStyle.scala | 8 +- .../dynamicEntity/DynamicEntityProvider.scala | 4 +- .../MapppedDynamicEntityProvider.scala | 13 +--- .../code/api/v4_0_0/DynamicEntityTest.scala | 50 +++++++++++- .../api/v4_0_0/DynamicIntegrationTest.scala | 76 +++++++++++++++++++ .../api/v4_0_0/DynamicendPointsTest.scala | 19 +++++ 7 files changed, 165 insertions(+), 20 deletions(-) create mode 100644 obp-api/src/test/scala/code/api/v4_0_0/DynamicIntegrationTest.scala 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 f37a99ab8..2337731bd 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 @@ -4156,9 +4156,20 @@ object SwaggerDefinitionsJSON { val endpointMappingJson = EndpointMappingCommons( Some("b4e0352a-9a0f-4bfa-b30b-9003aa467f50"), - "OBPv4.0.0-dynamicEndpoint_GET_pet_PET_ID", - """{}""".stripMargin, + "OBPv4.0.0-dynamicEndpoint_POST_account", """{}""".stripMargin, + """{ + | "name": { + | "entity": "FooBar", + | "field": "name", + | "query": "number" + | }, + | "balance": { + | "entity": "FashionBrand", + | "field": "number", + | "query": "number" + | } + | }""".stripMargin, Some(bankIdExample.value) ) 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 0b2b1b072..ee9ab9fe9 100644 --- a/obp-api/src/main/scala/code/api/util/NewStyle.scala +++ b/obp-api/src/main/scala/code/api/util/NewStyle.scala @@ -2399,7 +2399,7 @@ object NewStyle { } private def createDynamicEntity(dynamicEntity: DynamicEntityT, callContext: Option[CallContext]): Future[Box[DynamicEntityT]] = { - val existsDynamicEntity = DynamicEntityProvider.connectorMethodProvider.vend.getByEntityName(dynamicEntity.bankId, dynamicEntity.entityName) + val existsDynamicEntity = DynamicEntityProvider.connectorMethodProvider.vend.getByEntityName(dynamicEntity.entityName) if(existsDynamicEntity.isDefined) { val errorMsg = s"$DynamicEntityNameAlreadyExists current entityName is '${dynamicEntity.entityName}'." @@ -2423,7 +2423,7 @@ object NewStyle { val originEntityName = originEntity.map(_.entityName).orNull // if entityName changed and the new entityName already exists, return error message if(dynamicEntity.entityName != originEntityName) { - val existsDynamicEntity = DynamicEntityProvider.connectorMethodProvider.vend.getByEntityName(dynamicEntity.bankId, dynamicEntity.entityName) + val existsDynamicEntity = DynamicEntityProvider.connectorMethodProvider.vend.getByEntityName(dynamicEntity.entityName) if(existsDynamicEntity.isDefined) { val errorMsg = s"$DynamicEntityNameAlreadyExists current entityName is '${dynamicEntity.entityName}'." @@ -2474,7 +2474,7 @@ object NewStyle { } def getDynamicEntityByEntityName(bankId: Option[String], entityName : String, callContext: Option[CallContext]): OBPReturnType[Box[DynamicEntityT]] = Future { - val boxedDynamicEntity = DynamicEntityProvider.connectorMethodProvider.vend.getByEntityName(bankId, entityName) + val boxedDynamicEntity = DynamicEntityProvider.connectorMethodProvider.vend.getByEntityName(entityName) (boxedDynamicEntity, callContext) } @@ -2540,7 +2540,7 @@ object NewStyle { queryParameters: Option[Map[String, List[String]]], callContext: Option[CallContext]): OBPReturnType[Box[JValue]] = { import DynamicEntityOperation._ - val dynamicEntityBox = DynamicEntityProvider.connectorMethodProvider.vend.getByEntityName(bankId, entityName) + val dynamicEntityBox = DynamicEntityProvider.connectorMethodProvider.vend.getByEntityName(entityName) // do validate, any validate process fail will return immediately if(dynamicEntityBox.isEmpty) { return Helper.booleanToFuture(s"$DynamicEntityNotExists entity's name is '$entityName'", cc=callContext)(false) diff --git a/obp-api/src/main/scala/code/dynamicEntity/DynamicEntityProvider.scala b/obp-api/src/main/scala/code/dynamicEntity/DynamicEntityProvider.scala index 3bf4ca991..904364716 100644 --- a/obp-api/src/main/scala/code/dynamicEntity/DynamicEntityProvider.scala +++ b/obp-api/src/main/scala/code/dynamicEntity/DynamicEntityProvider.scala @@ -536,7 +536,9 @@ case class DynamicEntityIntTypeExample(`type`: DynamicEntityFieldType, example: trait DynamicEntityProvider { def getById(bankId: Option[String], dynamicEntityId: String): Box[DynamicEntityT] - def getByEntityName(bankId: Option[String], entityName: String): Box[DynamicEntityT] + //Note, we use entity name to create the roles, and bank level and system level can not be mixed, + // so --> here can not use bankId as parameters: + def getByEntityName(entityName: String): Box[DynamicEntityT] def getDynamicEntities(bankId: Option[String]): List[DynamicEntityT] diff --git a/obp-api/src/main/scala/code/dynamicEntity/MapppedDynamicEntityProvider.scala b/obp-api/src/main/scala/code/dynamicEntity/MapppedDynamicEntityProvider.scala index 912fa5b9d..5eb587be5 100644 --- a/obp-api/src/main/scala/code/dynamicEntity/MapppedDynamicEntityProvider.scala +++ b/obp-api/src/main/scala/code/dynamicEntity/MapppedDynamicEntityProvider.scala @@ -20,18 +20,9 @@ object MappedDynamicEntityProvider extends DynamicEntityProvider with CustomJson )) } - - - override def getByEntityName(bankId: Option[String], entityName: String): Box[DynamicEntityT] = { - if (bankId.isEmpty) - DynamicEntity.find(By(DynamicEntity.EntityName, entityName)) - else - DynamicEntity.find( - By(DynamicEntity.EntityName, entityName), - By(DynamicEntity.BankId, bankId.getOrElse("") - )) + override def getByEntityName(entityName: String): Box[DynamicEntityT] = + DynamicEntity.find(By(DynamicEntity.EntityName, entityName)) - } override def getDynamicEntities(bankId: Option[String]): List[DynamicEntity] = { if (bankId.isEmpty) diff --git a/obp-api/src/test/scala/code/api/v4_0_0/DynamicEntityTest.scala b/obp-api/src/test/scala/code/api/v4_0_0/DynamicEntityTest.scala index 07551328d..426440bb5 100644 --- a/obp-api/src/test/scala/code/api/v4_0_0/DynamicEntityTest.scala +++ b/obp-api/src/test/scala/code/api/v4_0_0/DynamicEntityTest.scala @@ -84,6 +84,31 @@ class DynamicEntityTest extends V400ServerSetup { | } |} |""".stripMargin) + + val rightEntityBankLevel = parse( + """ + |{ + | "FooBarBankLevel": { + | "description": "description of this entity, can be markdown text.", + | "required": [ + | "name" + | ], + | "properties": { + | "name": { + | "type": "string", + | "maxLength": 20, + | "minLength": 3, + | "example": "James Brown", + | "description":"description of **name** field, can be markdown text." + | }, + | "number": { + | "type": "integer", + | "example": 69876172 + | } + | } + | } + |} + |""".stripMargin) // wrong required name val wrongRequiredEntity = parse( """ @@ -272,14 +297,35 @@ class DynamicEntityTest extends V400ServerSetup { } - scenario("We will call the endpoint with the proper Role " + canCreateDynamicEntity , ApiEndpoint1, ApiEndpoint2, ApiEndpoint3, ApiEndpoint4, ApiEndpoint8, VersionOfApi) { + scenario("We will call the endpoint with the proper Role " + canCreateDynamicEntity , ApiEndpoint1, ApiEndpoint2, ApiEndpoint3, ApiEndpoint4, ApiEndpoint8,ApiEndpoint9, VersionOfApi) { Entitlement.entitlement.vend.addEntitlement("", resourceUser1.userId, CanCreateDynamicEntity.toString) When("We make a request v4.0.0") val request = (v4_0_0_Request / "management" / "dynamic-entities").POST <@(user1) val response = makePostRequest(request, write(rightEntity)) Then("We should get a 201") response.code should equal(201) + + {//Test the bank level create entity + val request = (v4_0_0_Request / "management" / "banks" / testBankId1.value / "dynamic-entities").POST<@(user1) + val response = makePostRequest(request, write(rightEntityBankLevel)) + Then("We should get a 201") + response.code should equal(201) + } + {// create duplicated entityName FooBar, cause 400 + val request = (v4_0_0_Request / "management" / "banks" / testBankId1.value / "dynamic-entities").POST<@(user1) + val response = makePostRequest(request, write(rightEntityBankLevel)) + Then("We should get a 400") + response.code should equal(400) + } + + {// create duplicated entityName FooBar with bank level, cause 400. (we already create the system Foobar + val request = (v4_0_0_Request / "management" / "banks" / testBankId1.value / "dynamic-entities").POST<@(user1) + val response = makePostRequest(request, write(rightEntity)) + Then("We should get a 400") + response.code should equal(400) + } + { // create duplicated entityName FooBar, cause 400 val response400 = makePostRequest(request, write(rightEntity)) response400.code should equal(400) @@ -367,7 +413,7 @@ class DynamicEntityTest extends V400ServerSetup { val json = responseGet.body \ "dynamic_entities" val dynamicEntitiesGetJson = json.asInstanceOf[JArray] - dynamicEntitiesGetJson.values should have size 1 + dynamicEntitiesGetJson.values should have size 2 val JArray(head :: Nil) = dynamicEntitiesGetJson diff --git a/obp-api/src/test/scala/code/api/v4_0_0/DynamicIntegrationTest.scala b/obp-api/src/test/scala/code/api/v4_0_0/DynamicIntegrationTest.scala new file mode 100644 index 000000000..5f161aa5f --- /dev/null +++ b/obp-api/src/test/scala/code/api/v4_0_0/DynamicIntegrationTest.scala @@ -0,0 +1,76 @@ +package code.api.v4_0_0 + +import code.api.ResourceDocs1_4_0.SwaggerDefinitionsJSON._ +import code.api.util.APIUtil.OAuth._ +import code.api.util.ApiRole._ +import code.api.util.ExampleValue._ +import code.api.util.ErrorMessages.{UserNotLoggedIn, _} +import code.api.v4_0_0.APIMethods400.Implementations4_0_0 +import code.endpointMapping.EndpointMappingCommons +import code.entitlement.Entitlement +import com.github.dwickern.macros.NameOf.nameOf +import com.openbankproject.commons.model.ErrorMessage +import com.openbankproject.commons.util.ApiVersion +import net.liftweb.json.Serialization.write +import org.scalatest.Tag + +class DynamicIntegrationTest extends V400ServerSetup { + /** + * Test tags + * Example: To run tests with tag "getPermissions": + * mvn test -D tagsToInclude + * + * This is made possible by the scalatest maven plugin + */ + object VersionOfApi extends Tag(ApiVersion.v4_0_0.toString) + object DynamicIntegration extends Tag("Dynamic Entity/Dynamic/Mapping") + object ApiEndpoint1 extends Tag(nameOf(Implementations4_0_0.createBankLevelEndpointMapping)) + object ApiEndpoint2 extends Tag(nameOf(Implementations4_0_0.getBankLevelEndpointMapping)) + object ApiEndpoint3 extends Tag(nameOf(Implementations4_0_0.getAllBankLevelEndpointMappings)) + object ApiEndpoint4 extends Tag(nameOf(Implementations4_0_0.updateBankLevelEndpointMapping)) + object ApiEndpoint5 extends Tag(nameOf(Implementations4_0_0.deleteBankLevelEndpointMapping)) + + object ApiEndpoint6 extends Tag(nameOf(Implementations4_0_0.createBankLevelDynamicEndpoint)) + object ApiEndpoint7 extends Tag(nameOf(Implementations4_0_0.getBankLevelDynamicEndpoints)) + object ApiEndpoint8 extends Tag(nameOf(Implementations4_0_0.getBankLevelDynamicEndpoint)) + object ApiEndpoint9 extends Tag(nameOf(Implementations4_0_0.deleteBankLevelDynamicEndpoint)) + + object ApiEndpoint10 extends Tag(nameOf(Implementations4_0_0.getBankLevelDynamicEntities)) + object ApiEndpoint11 extends Tag(nameOf(Implementations4_0_0.createBankLevelDynamicEntity)) + object ApiEndpoint12 extends Tag(nameOf(Implementations4_0_0.getBankLevelDynamicEntities)) + object ApiEndpoint13 extends Tag(nameOf(Implementations4_0_0.deleteBankLevelDynamicEntity)) + object ApiEndpoint14 extends Tag(nameOf(Implementations4_0_0.updateBankLevelDynamicEntity)) + + + val mapping = endpointMappingJson.copy(endpointMappingId = None) + val dynamicEntity = dynamicEntityRequestBodyExample.copy(bankId = None) + val dynamicEndpoint = dynamicEndpointRequestBodyExample + + feature("test Dynamic Entity/Endpoint and endpoint mappings together") { + scenario("test Dynamic Entity/Endpoint and endpoint mappings together ", DynamicIntegration, VersionOfApi) { + //First, we need to prepare the dynamic entity, it should have two fields: name, balance. + Entitlement.entitlement.vend.addEntitlement(testBankId1.value, resourceUser1.userId, CanCreateBankLevelDynamicEntity.toString) + val requestEntity = (v4_0_0_Request / "management" / "banks" / testBankId1.value / "dynamic-entities").POST <@(user1) + val responseEntity = makePostRequest(requestEntity, write(dynamicEntity)) + Then("We should get a 201") + responseEntity.code should equal(201) + + //second, we need to prepare the dynamic endpoint, It has the filed name, balance. + Entitlement.entitlement.vend.addEntitlement(testBankId1.value, resourceUser1.userId, CanCreateBankLevelDynamicEndpoint.toString) + val requestEndpoint = (v4_0_0_Request / "management" / "banks" / testBankId1.value / "dynamic-endpoints").POST <@(user1) + val responseEndpoint = makePostRequest(requestEndpoint, write(dynamicEndpoint)) + Then("We should get a 201") + responseEndpoint.code should equal(201) + + + // 3rd, we need to prepare the mappings, we need to mapping entity.name --> swagger.name , entity.balance --> swagger.balance + Entitlement.entitlement.vend.addEntitlement(testBankId1.value, resourceUser1.userId, CanCreateBankLevelEndpointMapping.toString) + val requestMapping = (v4_0_0_Request / "management" / "banks" / testBankId1.value / "endpoint-mappings").POST <@(user1) + val responseMapping = makePostRequest(requestMapping, write(mapping)) + Then("We should get a 201") + responseMapping.code should equal(201) + val customerJson = responseMapping.body.extract[EndpointMappingCommons] + } + } + +} diff --git a/obp-api/src/test/scala/code/api/v4_0_0/DynamicendPointsTest.scala b/obp-api/src/test/scala/code/api/v4_0_0/DynamicendPointsTest.scala index aa6a6ed70..b7be1daa2 100644 --- a/obp-api/src/test/scala/code/api/v4_0_0/DynamicendPointsTest.scala +++ b/obp-api/src/test/scala/code/api/v4_0_0/DynamicendPointsTest.scala @@ -41,6 +41,25 @@ class DynamicEndpointsTest extends V400ServerSetup { feature(s"test $ApiEndpoint9, $ApiEndpoint10, $ApiEndpoint11, $ApiEndpoint12 version $VersionOfApi") { + scenario(s"If we create one entity for system, we should allow to create the bank level, otherwise, it will break the roles", ApiEndpoint1,ApiEndpoint9, VersionOfApi) { + When("We make a request v4.0.0") + Entitlement.entitlement.vend.addEntitlement(testBankId1.value, resourceUser1.userId, canCreateDynamicEndpoint.toString) + val requestSystemLevle = (v4_0_0_Request / "management" / "dynamic-endpoints").POST<@ (user1) + val responseSystemLevel = makePostRequest(requestSystemLevle, postDynamicEndpointSwagger) + Then("We should get a 201") + responseSystemLevel.code should equal(201) + responseSystemLevel.body.toString contains("dynamic_endpoint_id") should be (true) + + When("We make a request v4.0.0") + Entitlement.entitlement.vend.addEntitlement(testBankId1.value, resourceUser1.userId, canCreateBankLevelDynamicEndpoint.toString) + val request = (v4_0_0_Request / "management" /"banks"/testBankId1.value/ "dynamic-endpoints").POST<@ (user1) + val responseWithRole = makePostRequest(request, postDynamicEndpointSwagger) + Then("We should get a 400") + responseWithRole.code should equal(400) + responseWithRole.body.toString contains(DynamicEndpointExists) should be (true) + } + + scenario(s"$ApiEndpoint9 $ApiEndpoint10 $ApiEndpoint11 $ApiEndpoint12 test the bank level role", ApiEndpoint1, VersionOfApi) { When("We make a request v4.0.0") Entitlement.entitlement.vend.addEntitlement(testBankId1.value, resourceUser1.userId, canCreateBankLevelDynamicEndpoint.toString) From 50b601209902361797e73cbf1772fed9dc2fd0c1 Mon Sep 17 00:00:00 2001 From: hongwei Date: Thu, 17 Jun 2021 16:01:54 +0200 Subject: [PATCH 051/147] bugfix/fixed the issue for firehose endpoints --- obp-api/src/main/scala/code/api/v4_0_0/APIMethods400.scala | 6 ++++-- 1 file changed, 4 insertions(+), 2 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 1d45d5642..053169091 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 @@ -3199,7 +3199,7 @@ trait APIMethods400 { |""".stripMargin, emptyObjectJson, moderatedFirehoseAccountsJsonV400, - List($UserNotLoggedIn, $BankNotFound, $UserNoPermissionAccessView,UnknownError), + List($BankNotFound), List(apiTagAccount, apiTagAccountFirehose, apiTagFirehoseData, apiTagNewStyle), Some(List(canUseAccountFirehoseAtAnyBank)) ) @@ -3209,7 +3209,9 @@ trait APIMethods400 { case "banks" :: BankId(bankId):: "firehose" :: "accounts" :: "views" :: ViewId(viewId):: Nil JsonGet req => { cc => for { - (Full(u), bank, view, callContext) <- SS.userBankView + (Full(u), bank, callContext) <- SS.userBank + // here must be a system view, not accountIds in the URL + view <- NewStyle.function.checkViewAccessAndReturnView(viewId, BankIdAccountId(BankId(""), AccountId("")), Some(u), callContext) _ <- Helper.booleanToFuture(failMsg = AccountFirehoseNotAllowedOnThisInstance +" or " + UserHasMissingRoles + CanUseAccountFirehoseAtAnyBank , cc=callContext) { canUseAccountFirehose(u) } From f4c42c652af291ef6aaf2d8922c9b3e3f25e8f8c Mon Sep 17 00:00:00 2001 From: hongwei Date: Thu, 17 Jun 2021 16:25:30 +0200 Subject: [PATCH 052/147] bugfix/added the null guard for transaction.balance --- obp-api/src/main/scala/code/model/View.scala | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/obp-api/src/main/scala/code/model/View.scala b/obp-api/src/main/scala/code/model/View.scala index 2d71f81b3..9bb73da10 100644 --- a/obp-api/src/main/scala/code/model/View.scala +++ b/obp-api/src/main/scala/code/model/View.scala @@ -161,7 +161,7 @@ case class ViewExtended(val view: View) { else None val transactionBalance = - if (view.canSeeTransactionBalance) transaction.balance.toString() + if (view.canSeeTransactionBalance && transaction.balance != null) transaction.balance.toString() else "" new ModeratedTransaction( @@ -228,7 +228,7 @@ case class ViewExtended(val view: View) { else None val transactionBalance = - if (view.canSeeTransactionBalance) transactionCore.balance.toString() + if (view.canSeeTransactionBalance && transactionCore.balance != null) transactionCore.balance.toString() else "" new ModeratedTransactionCore( @@ -312,7 +312,7 @@ case class ViewExtended(val view: View) { if(view.canSeeTransactionThisBankAccount) { val owners : Set[User] = if(view.canSeeBankAccountOwners) bankAccount.userOwners else Set() - val balance = if(view.canSeeBankAccountBalance) bankAccount.balance.toString else "" + val balance = if(view.canSeeBankAccountBalance && bankAccount.balance != null) bankAccount.balance.toString else "" val accountType = if(view.canSeeBankAccountType) Some(bankAccount.accountType) else None val currency = if(view.canSeeBankAccountCurrency) Some(bankAccount.currency) else None val label = if(view.canSeeBankAccountLabel) Some(bankAccount.label) else None @@ -362,7 +362,7 @@ case class ViewExtended(val view: View) { if(view.canSeeTransactionThisBankAccount) { val owners : Set[User] = if(view.canSeeBankAccountOwners) bankAccount.userOwners else Set() - val balance = if(view.canSeeBankAccountBalance) bankAccount.balance.toString else "" + val balance = if(view.canSeeBankAccountBalance && bankAccount.balance !=null) bankAccount.balance.toString else "" val accountType = if(view.canSeeBankAccountType) Some(bankAccount.accountType) else None val currency = if(view.canSeeBankAccountCurrency) Some(bankAccount.currency) else None val label = if(view.canSeeBankAccountLabel) Some(bankAccount.label) else None @@ -409,7 +409,7 @@ case class ViewExtended(val view: View) { if(view.canSeeTransactionThisBankAccount) { val owners : Set[User] = if(view.canSeeBankAccountOwners) bankAccount.userOwners else Set() - val balance = if(view.canSeeBankAccountBalance) Some(bankAccount.balance.toString) else None + val balance = if(view.canSeeBankAccountBalance && bankAccount.balance != null) Some(bankAccount.balance.toString) else None val accountType = if(view.canSeeBankAccountType) Some(bankAccount.accountType) else None val currency = if(view.canSeeBankAccountCurrency) Some(bankAccount.currency) else None val label = if(view.canSeeBankAccountLabel) Some(bankAccount.label) else None From fbdc10c5409ea0d5ef8dd708513e4c274e8c745d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marko=20Mili=C4=87?= Date: Fri, 18 Jun 2021 11:52:32 +0200 Subject: [PATCH 053/147] docfix/Fix resource doc at endpoint createConsumer v4.0.0 --- .../SwaggerDefinitionsJSON.scala | 18 ++++++++++++++++++ .../scala/code/api/util/ExampleValue.scala | 6 ++++++ .../scala/code/api/v4_0_0/APIMethods400.scala | 14 +------------- 3 files changed, 25 insertions(+), 13 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 f37a99ab8..0ead28e00 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 @@ -2308,6 +2308,24 @@ object SwaggerDefinitionsJSON { created = DateWithDayExampleObject ) + val consumerJsonV400 = ConsumerJson( + consumer_id = ExampleValue.consumerIdExample.value, + key = ExampleValue.consumerSecretExample.value, + secret = ExampleValue.consumerKeyExample.value, + app_name = "SOFI", + app_type = "Web", + description = "Account Management", + client_certificate = """-----BEGIN CERTIFICATE----- + |client_certificate_content + |-----END CERTIFICATE-----""".stripMargin, + developer_email = ExampleValue.emailExample.value, + redirect_url = "www.openbankproject.com", + created_by_user_id = ExampleValue.userIdExample.value, + created_by_user = resourceUserJSON, + enabled = true, + created = DateWithDayExampleObject + ) + val consumersJson310 = ConsumersJsonV310( List(consumerJsonV310) ) diff --git a/obp-api/src/main/scala/code/api/util/ExampleValue.scala b/obp-api/src/main/scala/code/api/util/ExampleValue.scala index 19a446f85..daeb5985a 100644 --- a/obp-api/src/main/scala/code/api/util/ExampleValue.scala +++ b/obp-api/src/main/scala/code/api/util/ExampleValue.scala @@ -322,6 +322,12 @@ object ExampleValue { lazy val keyExample = ConnectorField(s"CustomerNumber", s"This key should be used with Adapter.value together. They are a pair.") glossaryItems += makeGlossaryItem("Adapter.key", keyExample) + lazy val consumerSecretExample = ConnectorField(s"xwdgylv3vau0n2gkxu1aize4glapftfldp5y1bic", s"This key should be used with Adapter.value together. They are a pair.") + glossaryItems += makeGlossaryItem("Customer.secret", consumerSecretExample) + + lazy val consumerKeyExample = ConnectorField(s"bwf0ykmwoirip1yjxcn15wnhuyxcziwgtcoaildq", s"This key should be used with Adapter.value together. They are a pair.") + glossaryItems += makeGlossaryItem("Customer.key", consumerKeyExample) + lazy val valueExample = ConnectorField(s"${customerNumberExample.value}", s"This key should be used with Adapter.key together. They are a pair.") glossaryItems += makeGlossaryItem("Adapter.value", valueExample) 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 1d45d5642..10b3566f6 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 @@ -4534,19 +4534,7 @@ trait APIMethods400 { |client_certificate_content |-----END CERTIFICATE-----""".stripMargin ), - ConsumerPostJSON( - "Some app name", - "App type", - "Description", - "some.email@example.com", - "Some redirect url", - "Created by UUID", - true, - new Date(), - """-----BEGIN CERTIFICATE----- - |client_certificate_content - |-----END CERTIFICATE-----""".stripMargin - ), + consumerJsonV400, List( UserNotLoggedIn, UserHasMissingRoles, From da139de9bca0074924ee51eea3b8270e3b0e98c1 Mon Sep 17 00:00:00 2001 From: hongwei Date: Sat, 19 Jun 2021 14:48:54 +0200 Subject: [PATCH 054/147] test/added tests for firehose endpoints --- .../main/scala/code/api/util/APIUtil.scala | 6 +- .../code/api/util/ApiPropsWithAlias.scala | 8 +- .../main/scala/code/api/util/NewStyle.scala | 4 +- .../scala/code/api/v3_0_0/APIMethods300.scala | 12 +- .../scala/code/api/v3_1_0/APIMethods310.scala | 7 +- .../scala/code/api/v4_0_0/APIMethods400.scala | 7 +- obp-api/src/main/scala/code/views/Views.scala | 1 - .../scala/code/api/v3_0_0/FirehoseTest.scala | 119 ++++++++++++++++++ .../scala/code/api/v3_1_0/CustomerTest.scala | 63 +++++++++- .../scala/code/api/v4_0_0/FirehoseTest.scala | 68 ++++++++++ 10 files changed, 272 insertions(+), 23 deletions(-) create mode 100644 obp-api/src/test/scala/code/api/v3_0_0/FirehoseTest.scala create mode 100644 obp-api/src/test/scala/code/api/v4_0_0/FirehoseTest.scala 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 84232d62a..c172ea5be 100644 --- a/obp-api/src/main/scala/code/api/util/APIUtil.scala +++ b/obp-api/src/main/scala/code/api/util/APIUtil.scala @@ -3168,9 +3168,9 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{ - val ALLOW_PUBLIC_VIEWS: Boolean = getPropsAsBoolValue("allow_public_views", false) - val ALLOW_ACCOUNT_FIREHOSE: Boolean = ApiPropsWithAlias.allowAccountFirehose - val ALLOW_CUSTOMER_FIREHOSE: Boolean = ApiPropsWithAlias.allowCustomerFirehose + def ALLOW_PUBLIC_VIEWS: Boolean = getPropsAsBoolValue("allow_public_views", false) + def ALLOW_ACCOUNT_FIREHOSE: Boolean = ApiPropsWithAlias.allowAccountFirehose + def ALLOW_CUSTOMER_FIREHOSE: Boolean = ApiPropsWithAlias.allowCustomerFirehose def canUseAccountFirehose(user: User): Boolean = { ALLOW_ACCOUNT_FIREHOSE && hasEntitlement("", user.userId, ApiRole.canUseAccountFirehoseAtAnyBank) } diff --git a/obp-api/src/main/scala/code/api/util/ApiPropsWithAlias.scala b/obp-api/src/main/scala/code/api/util/ApiPropsWithAlias.scala index 0bf965790..1492be369 100644 --- a/obp-api/src/main/scala/code/api/util/ApiPropsWithAlias.scala +++ b/obp-api/src/main/scala/code/api/util/ApiPropsWithAlias.scala @@ -13,19 +13,19 @@ import net.liftweb.common.Full */ object ApiPropsWithAlias { import HelperFunctions._ - val requireScopesForAllRoles = getValueByNameOrAliasAsBoolean( + def requireScopesForAllRoles = getValueByNameOrAliasAsBoolean( name="require_scopes_for_all_roles", alias="require_scopes", defaultValue="false") - val migrationScriptsEnabled = getValueByNameOrAliasAsBoolean( + def migrationScriptsEnabled = getValueByNameOrAliasAsBoolean( name="migration_scripts.enabled", alias="migration_scripts.execute", defaultValue="false") - val allowAccountFirehose = getValueByNameOrAliasAsBoolean( + def allowAccountFirehose = getValueByNameOrAliasAsBoolean( name="allow_account_firehose", alias="allow_firehose_views", defaultValue="false") - val allowCustomerFirehose = getValueByNameOrAliasAsBoolean( + def allowCustomerFirehose = getValueByNameOrAliasAsBoolean( name="allow_customer_firehose", alias="allow_firehose_views", defaultValue="false") 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 bb6550d1e..593b7b071 100644 --- a/obp-api/src/main/scala/code/api/util/NewStyle.scala +++ b/obp-api/src/main/scala/code/api/util/NewStyle.scala @@ -882,12 +882,12 @@ object NewStyle { } map validateRequestPayload(callContext) def hasAtLeastOneEntitlement(bankId: String, userId: String, roles: List[ApiRole], callContext: Option[CallContext]): Future[Box[Unit]] = { - val errorMessage = if (bankId.isEmpty) UserHasMissingRoles + roles.mkString(" or ") else UserHasMissingRoles + roles.mkString(" or ") + s" for BankId($bankId)." + val errorMessage = if (roles.filter(_.requiresBankId).isEmpty) UserHasMissingRoles + roles.mkString(" or ") else UserHasMissingRoles + roles.mkString(" or ") + s" for BankId($bankId)." hasAtLeastOneEntitlement(errorMessage)(bankId, userId, roles, callContext) } def hasAllEntitlements(bankId: String, userId: String, roles: List[ApiRole], callContext: Option[CallContext]): Box[Unit] = { - val errorMessage = if (bankId.isEmpty) + val errorMessage = if (roles.filter(_.requiresBankId).isEmpty) s"$UserHasMissingRoles${roles.mkString(" and ")} entitlements are required." else s"$UserHasMissingRoles${roles.mkString(" and ")} entitlements are required for BankId($bankId)." diff --git a/obp-api/src/main/scala/code/api/v3_0_0/APIMethods300.scala b/obp-api/src/main/scala/code/api/v3_0_0/APIMethods300.scala index bb8304ccd..928d12ee0 100644 --- a/obp-api/src/main/scala/code/api/v3_0_0/APIMethods300.scala +++ b/obp-api/src/main/scala/code/api/v3_0_0/APIMethods300.scala @@ -481,7 +481,7 @@ trait APIMethods300 { |""".stripMargin, emptyObjectJson, moderatedCoreAccountsJsonV300, - List(UserNotLoggedIn,UnknownError), + List(UserNotLoggedIn,AccountFirehoseNotAllowedOnThisInstance,UnknownError), List(apiTagAccount, apiTagAccountFirehose, apiTagFirehoseData, apiTagNewStyle), Some(List(canUseAccountFirehoseAtAnyBank)) ) @@ -492,9 +492,10 @@ trait APIMethods300 { cc => for { (Full(u), callContext) <- authenticatedAccess(cc) - _ <- Helper.booleanToFuture(failMsg = AccountFirehoseNotAllowedOnThisInstance +" or " + UserHasMissingRoles + CanUseAccountFirehoseAtAnyBank, cc=callContext) { - canUseAccountFirehose(u) + _ <- Helper.booleanToFuture(failMsg = AccountFirehoseNotAllowedOnThisInstance , cc=cc.callContext) { + ALLOW_ACCOUNT_FIREHOSE } + _ <- NewStyle.function.hasEntitlement("", u.userId, ApiRole.canUseAccountFirehoseAtAnyBank, callContext) (bank, callContext) <- NewStyle.function.getBank(bankId, callContext) view <- NewStyle.function.checkViewAccessAndReturnView(viewId, BankIdAccountId(BankId(""), AccountId("")), Some(u), callContext) availableBankIdAccountIdList <- Future { @@ -578,9 +579,10 @@ trait APIMethods300 { cc => for { (Full(u), callContext) <- authenticatedAccess(cc) - _ <- Helper.booleanToFuture(failMsg = AccountFirehoseNotAllowedOnThisInstance +" or " + UserHasMissingRoles + CanUseAccountFirehoseAtAnyBank , cc=callContext) { - canUseAccountFirehose(u) + _ <- Helper.booleanToFuture(failMsg = AccountFirehoseNotAllowedOnThisInstance , cc=callContext) { + ALLOW_ACCOUNT_FIREHOSE } + _ <- NewStyle.function.hasEntitlement("", u.userId, ApiRole.canUseAccountFirehoseAtAnyBank, callContext) (bank, callContext) <- NewStyle.function.getBank(bankId, callContext) (bankAccount, callContext) <- NewStyle.function.getBankAccount(bankId, accountId, callContext) view <- NewStyle.function.checkViewAccessAndReturnView(viewId, BankIdAccountId(bankAccount.bankId, bankAccount.accountId),Some(u), callContext) diff --git a/obp-api/src/main/scala/code/api/v3_1_0/APIMethods310.scala b/obp-api/src/main/scala/code/api/v3_1_0/APIMethods310.scala index ec6296d8b..9b4bfad2e 100644 --- a/obp-api/src/main/scala/code/api/v3_1_0/APIMethods310.scala +++ b/obp-api/src/main/scala/code/api/v3_1_0/APIMethods310.scala @@ -465,10 +465,11 @@ trait APIMethods310 { cc => for { (Full(u), callContext) <- authenticatedAccess(cc) - (_, callContext) <- NewStyle.function.getBank(bankId, callContext) - _ <- Helper.booleanToFuture(failMsg = CustomerFirehoseNotAllowedOnThisInstance +" or " + UserHasMissingRoles + CanUseCustomerFirehoseAtAnyBank, cc=callContext) { - canUseCustomerFirehose(u) + _ <- Helper.booleanToFuture(failMsg = AccountFirehoseNotAllowedOnThisInstance , cc=callContext) { + ALLOW_CUSTOMER_FIREHOSE } + _ <- NewStyle.function.hasEntitlement("", u.userId, ApiRole.canUseCustomerFirehoseAtAnyBank, callContext) + (_, callContext) <- NewStyle.function.getBank(bankId, callContext) allowedParams = List("sort_direction", "limit", "offset", "from_date", "to_date") httpParams <- NewStyle.function.extractHttpParamsFromUrl(cc.url) obpQueryParams <- NewStyle.function.createObpParams(httpParams, allowedParams, callContext) 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 053169091..17eb24d46 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 @@ -3210,12 +3210,11 @@ trait APIMethods400 { cc => for { (Full(u), bank, callContext) <- SS.userBank + _ <- Helper.booleanToFuture(failMsg = AccountFirehoseNotAllowedOnThisInstance, cc=cc.callContext) { + ALLOW_ACCOUNT_FIREHOSE + } // here must be a system view, not accountIds in the URL view <- NewStyle.function.checkViewAccessAndReturnView(viewId, BankIdAccountId(BankId(""), AccountId("")), Some(u), callContext) - _ <- Helper.booleanToFuture(failMsg = AccountFirehoseNotAllowedOnThisInstance +" or " + UserHasMissingRoles + CanUseAccountFirehoseAtAnyBank , cc=callContext) { - canUseAccountFirehose(u) - } - availableBankIdAccountIdList <- Future { Views.views.vend.getAllFirehoseAccounts(bank.bankId).map(a => BankIdAccountId(a.bankId,a.accountId)) } diff --git a/obp-api/src/main/scala/code/views/Views.scala b/obp-api/src/main/scala/code/views/Views.scala index 0ec7edd91..ef703885f 100644 --- a/obp-api/src/main/scala/code/views/Views.scala +++ b/obp-api/src/main/scala/code/views/Views.scala @@ -1,7 +1,6 @@ package code.views import code.api.util.APIUtil -import code.api.util.APIUtil.canUseAccountFirehose import code.model.dataAccess.{MappedBankAccount, ViewImpl, ViewPrivileges} import code.remotedata.RemotedataViews import code.views.MapperViews.getPrivateBankAccounts diff --git a/obp-api/src/test/scala/code/api/v3_0_0/FirehoseTest.scala b/obp-api/src/test/scala/code/api/v3_0_0/FirehoseTest.scala new file mode 100644 index 000000000..bf0544d00 --- /dev/null +++ b/obp-api/src/test/scala/code/api/v3_0_0/FirehoseTest.scala @@ -0,0 +1,119 @@ +package code.api.v3_0_0 + +import code.api.util.APIUtil.OAuth._ +import code.api.util.ApiRole +import code.api.util.ApiRole.CanUseAccountFirehoseAtAnyBank +import code.api.util.ErrorMessages.AccountFirehoseNotAllowedOnThisInstance +import code.api.v3_0_0.OBPAPI3_0_0.Implementations3_0_0 +import code.entitlement.Entitlement +import code.setup.PropsReset +import com.github.dwickern.macros.NameOf.nameOf +import com.openbankproject.commons.util.ApiVersion +import org.scalatest.Tag + +class FirehoseTest extends V300ServerSetup with PropsReset{ + /** + * Test tags + * Example: To run tests with tag "getPermissions": + * mvn test -D tagsToInclude + * + * This is made possible by the scalatest maven plugin + */ + object VersionOfApi extends Tag(ApiVersion.v3_0_0.toString) + object ApiEndpoint2 extends Tag(nameOf(Implementations3_0_0.getFirehoseAccountsAtOneBank)) + object ApiEndpoint4 extends Tag(nameOf(Implementations3_0_0.getFirehoseTransactionsForBankAccount)) + + + feature(s"test ${ApiEndpoint2}") { + + scenario("We will call the endpoint with user credentials", VersionOfApi, ApiEndpoint2) { + setPropsValues("allow_account_firehose" -> "true") + setPropsValues("enable.force_error"->"true") + Entitlement.entitlement.vend.addEntitlement("", resourceUser1.userId, ApiRole.CanUseAccountFirehoseAtAnyBank.toString) + When("We send the request") + val request = (v3_0Request / "banks" / testBankId1.value /"firehose" / "accounts" / "views"/"owner").GET <@ (user1) + val response = makeGetRequest(request) + Then("We should get a 200 and check the response body") + response.code should equal(200) + response.body.extract[ModeratedCoreAccountsJsonV300] + } + scenario("We will call the endpoint with user credentials, props alias", VersionOfApi, ApiEndpoint2) { + setPropsValues("allow_firehose_views" -> "true") + Entitlement.entitlement.vend.addEntitlement("", resourceUser1.userId, ApiRole.CanUseAccountFirehoseAtAnyBank.toString) + When("We send the request") + val request = (v3_0Request / "banks" / testBankId1.value /"firehose" / "accounts" / "views"/"owner").GET <@ (user1) + val response = makeGetRequest(request) + Then("We should get a 200 and check the response body") + response.code should equal(200) + response.body.extract[ModeratedCoreAccountsJsonV300] + } + + + scenario("We will call the endpoint missing role", VersionOfApi, ApiEndpoint2) { + setPropsValues("allow_account_firehose" -> "true") + When("We send the request") + val request = (v3_0Request / "banks" / testBankId1.value / "firehose" / "accounts" / "views" / "owner").GET <@ (user1) + val response = makeGetRequest(request) + Then("We should get a 403 and check the response body") + response.code should equal(403) + response.body.toString contains (CanUseAccountFirehoseAtAnyBank.toString()) should be(true) + } + + scenario("We will call the endpoint missing props ", VersionOfApi, ApiEndpoint2) { + Entitlement.entitlement.vend.addEntitlement("", resourceUser1.userId, ApiRole.CanUseAccountFirehoseAtAnyBank.toString) + When("We send the request") + val request = (v3_0Request / "banks" / testBankId1.value /"firehose" / "accounts" / "views"/"owner").GET <@ (user1) + val response = makeGetRequest(request) + Then("We should get a 400 and check the response body") + response.code should equal(400) + response.body.toString contains (AccountFirehoseNotAllowedOnThisInstance) should be (true) + } + } + + feature(s"test ${ApiEndpoint4.name}") { + + scenario("We will call the endpoint with user credentials", VersionOfApi, ApiEndpoint4) { + setPropsValues("allow_account_firehose" -> "true") + setPropsValues("enable.force_error"->"true") + Entitlement.entitlement.vend.addEntitlement("", resourceUser1.userId, ApiRole.CanUseAccountFirehoseAtAnyBank.toString) + When("We send the request") + val request = (v3_0Request / "banks" / testBankId1.value /"firehose" / "accounts" / testAccountId1.value / "views"/"owner"/"transactions").GET <@ (user1) + val response = makeGetRequest(request) + Then("We should get a 200 and check the response body") + response.code should equal(200) + response.body.extract[ModeratedCoreAccountsJsonV300] + } + + scenario("We will call the endpoint with user credentials, props alias", VersionOfApi, ApiEndpoint4) { + setPropsValues("allow_firehose_views" -> "true") + Entitlement.entitlement.vend.addEntitlement("", resourceUser1.userId, ApiRole.CanUseAccountFirehoseAtAnyBank.toString) + When("We send the request") + val request = (v3_0Request / "banks" / testBankId1.value /"firehose" / "accounts" / testAccountId1.value / "views"/"owner"/"transactions").GET <@ (user1) + val response = makeGetRequest(request) + Then("We should get a 200 and check the response body") + response.code should equal(200) + response.body.extract[ModeratedCoreAccountsJsonV300] + } + + + scenario("We will call the endpoint missing role", VersionOfApi, ApiEndpoint4) { + setPropsValues("allow_account_firehose" -> "true") + When("We send the request") + val request = (v3_0Request / "banks" / testBankId1.value / "firehose" / "accounts" / testAccountId1.value /"views" / "owner"/"transactions").GET <@ (user1) + val response = makeGetRequest(request) + Then("We should get a 403 and check the response body") + response.code should equal(403) + response.body.toString contains (CanUseAccountFirehoseAtAnyBank.toString()) should be(true) + } + + scenario("We will call the endpoint missing props ", VersionOfApi, ApiEndpoint4) { + Entitlement.entitlement.vend.addEntitlement("", resourceUser1.userId, ApiRole.CanUseAccountFirehoseAtAnyBank.toString) + When("We send the request") + val request = (v3_0Request / "banks" / testBankId1.value /"firehose" / "accounts" / testAccountId1.value / "views"/"owner"/"transactions").GET <@ (user1) + val response = makeGetRequest(request) + Then("We should get a 400 and check the response body") + response.code should equal(400) + response.body.toString contains (AccountFirehoseNotAllowedOnThisInstance) should be (true) + } + } +} \ No newline at end of file diff --git a/obp-api/src/test/scala/code/api/v3_1_0/CustomerTest.scala b/obp-api/src/test/scala/code/api/v3_1_0/CustomerTest.scala index cedbe445b..d4d0712cb 100644 --- a/obp-api/src/test/scala/code/api/v3_1_0/CustomerTest.scala +++ b/obp-api/src/test/scala/code/api/v3_1_0/CustomerTest.scala @@ -28,18 +28,21 @@ package code.api.v3_1_0 import com.openbankproject.commons.model.ErrorMessage import code.api.ResourceDocs1_4_0.SwaggerDefinitionsJSON import code.api.util.APIUtil.OAuth._ +import code.api.util.ApiRole import code.api.util.ApiRole._ import com.openbankproject.commons.util.ApiVersion import code.api.util.ErrorMessages._ +import code.api.v3_0_0.ModeratedCoreAccountsJsonV300 import code.api.v3_1_0.OBPAPI3_1_0.Implementations3_1_0 import code.customer.CustomerX import code.entitlement.Entitlement +import code.setup.PropsReset import code.usercustomerlinks.UserCustomerLink import com.github.dwickern.macros.NameOf.nameOf import net.liftweb.json.Serialization.write import org.scalatest.Tag -class CustomerTest extends V310ServerSetup { +class CustomerTest extends V310ServerSetup with PropsReset{ override def beforeAll(): Unit = { super.beforeAll() @@ -70,6 +73,7 @@ class CustomerTest extends V310ServerSetup { object ApiEndpoint9 extends Tag(nameOf(Implementations3_1_0.updateCustomerBranch)) object ApiEndpoint10 extends Tag(nameOf(Implementations3_1_0.updateCustomerData)) object ApiEndpoint11 extends Tag(nameOf(Implementations3_1_0.updateCustomerNumber)) + object ApiEndpoint12 extends Tag(nameOf(Implementations3_1_0.getFirehoseCustomers)) val customerNumberJson = PostCustomerNumberJsonV310(customer_number = "123") val postCustomerJson = SwaggerDefinitionsJSON.postCustomerJsonV310 @@ -582,4 +586,61 @@ class CustomerTest extends V310ServerSetup { } } + + feature(s" $ApiEndpoint12- Authorized access") { + + //first we create the customers: + Entitlement.entitlement.vend.addEntitlement(bankId, resourceUser1.userId, CanCreateCustomer.toString) + When("We make a request v3.1.0") + val request310 = (v3_1_0_Request / "banks" / bankId / "customers").POST <@(user1) + val response310 = makePostRequest(request310, write(postCustomerJson)) + Then("We should get a 201") + response310.code should equal(201) + + scenario("We will call the endpoint with user credentials", VersionOfApi, ApiEndpoint4) { + setPropsValues("allow_customer_firehose" -> "true") + setPropsValues("enable.force_error"->"true") + Entitlement.entitlement.vend.addEntitlement("", resourceUser1.userId, ApiRole.CanUseCustomerFirehoseAtAnyBank.toString) + When("We send the request") + val request = (v3_1_0_Request / "banks" / testBankId1.value /"firehose" / "customers" ).GET <@ (user1) + val response = makeGetRequest(request) + Then("We should get a 200 and check the response body") + response.code should equal(200) + response.body.extract[ModeratedCoreAccountsJsonV300] + } + + scenario("We will call the endpoint with user credentials, props alias", VersionOfApi, ApiEndpoint4) { + setPropsValues("allow_firehose_views" -> "true") + Entitlement.entitlement.vend.addEntitlement("", resourceUser1.userId, ApiRole.CanUseCustomerFirehoseAtAnyBank.toString) + When("We send the request") + val request = (v3_1_0_Request / "banks" / testBankId1.value /"firehose" / "customers" ).GET <@ (user1) + val response = makeGetRequest(request) + Then("We should get a 200 and check the response body") + response.code should equal(200) + response.body.extract[ModeratedCoreAccountsJsonV300] + } + + + scenario("We will call the endpoint missing role", VersionOfApi, ApiEndpoint4) { + setPropsValues("allow_customer_firehose" -> "true") + When("We send the request") + val request = (v3_1_0_Request / "banks" / testBankId1.value / "firehose" / "customers").GET <@ (user1) + val response = makeGetRequest(request) + Then("We should get a 403 and check the response body") + response.code should equal(403) + response.body.toString contains (CanUseCustomerFirehoseAtAnyBank.toString()) should be(true) + } + + scenario("We will call the endpoint missing props ", VersionOfApi, ApiEndpoint4) { + Entitlement.entitlement.vend.addEntitlement("", resourceUser1.userId, ApiRole.CanUseCustomerFirehoseAtAnyBank.toString) + When("We send the request") + val request = (v3_1_0_Request / "banks" / testBankId1.value /"firehose" / "customers" ).GET <@ (user1) + val response = makeGetRequest(request) + Then("We should get a 400 and check the response body") + response.code should equal(400) + response.body.toString contains (AccountFirehoseNotAllowedOnThisInstance) should be (true) + } + + } + } diff --git a/obp-api/src/test/scala/code/api/v4_0_0/FirehoseTest.scala b/obp-api/src/test/scala/code/api/v4_0_0/FirehoseTest.scala new file mode 100644 index 000000000..02260b02f --- /dev/null +++ b/obp-api/src/test/scala/code/api/v4_0_0/FirehoseTest.scala @@ -0,0 +1,68 @@ +package code.api.v4_0_0 + +import code.api.util.APIUtil.OAuth._ +import code.api.util.ApiRole +import code.api.util.ApiRole.CanUseAccountFirehoseAtAnyBank +import code.api.util.ErrorMessages.AccountFirehoseNotAllowedOnThisInstance +import code.api.v4_0_0.OBPAPI4_0_0.Implementations4_0_0 +import code.entitlement.Entitlement +import code.setup.PropsReset +import com.github.dwickern.macros.NameOf.nameOf +import com.openbankproject.commons.util.ApiVersion +import org.scalatest.Tag + +class FirehoseTest extends V400ServerSetup with PropsReset{ + /** + * Test tags + * Example: To run tests with tag "getPermissions": + * mvn test -D tagsToInclude + * + * This is made possible by the scalatest maven plugin + */ + object VersionOfApi extends Tag(ApiVersion.v4_0_0.toString) + object ApiEndpoint1 extends Tag(nameOf(Implementations4_0_0.getFirehoseAccountsAtOneBank)) + + feature(s"test $ApiEndpoint1 version $VersionOfApi - Authorized access") { + scenario("We will call the endpoint with user credentials", VersionOfApi, ApiEndpoint1) { + setPropsValues("allow_account_firehose" -> "true") + Entitlement.entitlement.vend.addEntitlement("", resourceUser1.userId, ApiRole.CanUseAccountFirehoseAtAnyBank.toString) + When("We send the request") + val request = (v4_0_0_Request / "banks" / testBankId1.value /"firehose" / "accounts" / "views"/"owner").GET <@ (user1) + val response = makeGetRequest(request) + Then("We should get a 200 and check the response body") + response.code should equal(200) + response.body.extract[ModeratedFirehoseAccountsJsonV400] + } + scenario("We will call the endpoint with user credentials, props alias", VersionOfApi, ApiEndpoint1) { + setPropsValues("allow_firehose_views" -> "true") + Entitlement.entitlement.vend.addEntitlement("", resourceUser1.userId, ApiRole.CanUseAccountFirehoseAtAnyBank.toString) + When("We send the request") + val request = (v4_0_0_Request / "banks" / testBankId1.value /"firehose" / "accounts" / "views"/"owner").GET <@ (user1) + val response = makeGetRequest(request) + Then("We should get a 200 and check the response body") + response.code should equal(200) + response.body.extract[ModeratedFirehoseAccountsJsonV400] + } + + scenario("We will call the endpoint missing role", VersionOfApi, ApiEndpoint1) { + setPropsValues("allow_account_firehose" -> "true") + When("We send the request") + val request = (v4_0_0_Request / "banks" / testBankId1.value / "firehose" / "accounts" / "views" / "owner").GET <@ (user1) + val response = makeGetRequest(request) + Then("We should get a 403 and check the response body") + response.code should equal(403) + response.body.toString contains (CanUseAccountFirehoseAtAnyBank.toString()) should be(true) + } + + scenario("We will call the endpoint missing props ", VersionOfApi, ApiEndpoint1) { + Entitlement.entitlement.vend.addEntitlement("", resourceUser1.userId, ApiRole.CanUseAccountFirehoseAtAnyBank.toString) + When("We send the request") + val request = (v4_0_0_Request / "banks" / testBankId1.value /"firehose" / "accounts" / "views"/"owner").GET <@ (user1) + val response = makeGetRequest(request) + Then("We should get a 400 and check the response body") + response.code should equal(400) + response.body.toString contains (AccountFirehoseNotAllowedOnThisInstance) should be (true) + } + } + +} From 436001ec2f1bc2ed159dcc8633b6673f158893ce Mon Sep 17 00:00:00 2001 From: hongwei Date: Sun, 20 Jun 2021 14:06:55 +0200 Subject: [PATCH 055/147] tests/fixed the failed test --- obp-api/src/test/scala/code/api/v3_0_0/AccountTest.scala | 6 +++--- .../src/test/scala/code/api/v3_0_0/TransactionsTest.scala | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/obp-api/src/test/scala/code/api/v3_0_0/AccountTest.scala b/obp-api/src/test/scala/code/api/v3_0_0/AccountTest.scala index 5b8682d05..d7d759406 100644 --- a/obp-api/src/test/scala/code/api/v3_0_0/AccountTest.scala +++ b/obp-api/src/test/scala/code/api/v3_0_0/AccountTest.scala @@ -51,9 +51,9 @@ class AccountTest extends V300ServerSetup { val requestGet = (v3_0Request / "banks" / "BANK_ID" / "firehose" / "accounts" / "views" / "VIEW_ID").GET <@ (user1) val responseGet = makeGetRequest(requestGet) - And("We should get a 403") - responseGet.code should equal(403) - responseGet.body.extract[ErrorMessage].message should equal(AccountFirehoseNotAllowedOnThisInstance +" or " + UserHasMissingRoles + CanUseAccountFirehoseAtAnyBank ) + And("We should get a 400") + responseGet.code should equal(400) + responseGet.body.extract[ErrorMessage].message contains AccountFirehoseNotAllowedOnThisInstance should be (true) }} diff --git a/obp-api/src/test/scala/code/api/v3_0_0/TransactionsTest.scala b/obp-api/src/test/scala/code/api/v3_0_0/TransactionsTest.scala index 67a51d524..487c22e05 100644 --- a/obp-api/src/test/scala/code/api/v3_0_0/TransactionsTest.scala +++ b/obp-api/src/test/scala/code/api/v3_0_0/TransactionsTest.scala @@ -401,9 +401,9 @@ class TransactionsTest extends V300ServerSetup { val requestGet = (v3_0Request / "banks" / "BANK_ID" / "firehose" / "accounts" / "AccountId(accountId)" / "views" / "ViewId(viewId)" / "transactions").GET <@ (user1) val responseGet = makeGetRequest(requestGet) - And("We should get a 403") - responseGet.code should equal(403) - responseGet.body.extract[ErrorMessage].message should equal(AccountFirehoseNotAllowedOnThisInstance +" or " + UserHasMissingRoles + CanUseAccountFirehoseAtAnyBank ) + And("We should get a 400") + responseGet.code should equal(400) + responseGet.body.extract[ErrorMessage].message contains AccountFirehoseNotAllowedOnThisInstance should be (true) }} From 95b3cc9824d125f75a2420c8396a9df8ea71e313 Mon Sep 17 00:00:00 2001 From: hongwei Date: Mon, 21 Jun 2021 11:11:35 +0200 Subject: [PATCH 056/147] refactor/tweaked the method names --- .../main/scala/code/api/util/APIUtil.scala | 16 ++++++------ .../scala/code/api/v3_0_0/APIMethods300.scala | 4 +-- .../scala/code/api/v3_1_0/APIMethods310.scala | 2 +- .../scala/code/api/v4_0_0/APIMethods400.scala | 2 +- .../main/scala/code/views/MapperViews.scala | 26 +++++++++---------- 5 files changed, 25 insertions(+), 25 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 c172ea5be..95a750906 100644 --- a/obp-api/src/main/scala/code/api/util/APIUtil.scala +++ b/obp-api/src/main/scala/code/api/util/APIUtil.scala @@ -3168,14 +3168,14 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{ - def ALLOW_PUBLIC_VIEWS: Boolean = getPropsAsBoolValue("allow_public_views", false) - def ALLOW_ACCOUNT_FIREHOSE: Boolean = ApiPropsWithAlias.allowAccountFirehose - def ALLOW_CUSTOMER_FIREHOSE: Boolean = ApiPropsWithAlias.allowCustomerFirehose + def allowPublicViews: Boolean = getPropsAsBoolValue("allow_public_views", false) + def allowAccountFirehose: Boolean = ApiPropsWithAlias.allowAccountFirehose + def allowCustomerFirehose: Boolean = ApiPropsWithAlias.allowCustomerFirehose def canUseAccountFirehose(user: User): Boolean = { - ALLOW_ACCOUNT_FIREHOSE && hasEntitlement("", user.userId, ApiRole.canUseAccountFirehoseAtAnyBank) + allowAccountFirehose && hasEntitlement("", user.userId, ApiRole.canUseAccountFirehoseAtAnyBank) } def canUseCustomerFirehose(user: User): Boolean = { - ALLOW_CUSTOMER_FIREHOSE && hasEntitlement("", user.userId, ApiRole.canUseCustomerFirehoseAtAnyBank) + allowCustomerFirehose && hasEntitlement("", user.userId, ApiRole.canUseCustomerFirehoseAtAnyBank) } /** * This will accept all kinds of view and user. @@ -3205,7 +3205,7 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{ val customView = Views.views.vend.customView(viewId, bankIdAccountId) customView match { // CHECK CUSTOM VIEWS // 1st: View is Pubic and Public views are NOT allowed on this instance. - case Full(v) if(v.isPublic && !ALLOW_PUBLIC_VIEWS) => Failure(PublicViewsNotAllowedOnThisInstance) + case Full(v) if(v.isPublic && !allowPublicViews) => Failure(PublicViewsNotAllowedOnThisInstance) // 2nd: View is Pubic and Public views are allowed on this instance. case Full(v) if(isPublicView(v)) => customView // 3rd: The user has account access to this custom view @@ -3215,7 +3215,7 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{ val systemView = Views.views.vend.systemView(viewId) systemView match { // CHECK SYSTEM VIEWS // 1st: View is Pubic and Public views are NOT allowed on this instance. - case Full(v) if(v.isPublic && !ALLOW_PUBLIC_VIEWS) => Failure(PublicViewsNotAllowedOnThisInstance) + case Full(v) if(v.isPublic && !allowPublicViews) => Failure(PublicViewsNotAllowedOnThisInstance) // 2nd: View is Pubic and Public views are allowed on this instance. case Full(v) if(isPublicView(v)) => systemView // 3rd: The user has account access to this system view @@ -3243,7 +3243,7 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{ case true if view.isPublic => // Sanity check. We don't want a public owner view. logger.warn(s"Public owner encountered. Primary view id: ${view.id}") false - case _ => view.isPublic && APIUtil.ALLOW_PUBLIC_VIEWS + case _ => view.isPublic && APIUtil.allowPublicViews } } /** diff --git a/obp-api/src/main/scala/code/api/v3_0_0/APIMethods300.scala b/obp-api/src/main/scala/code/api/v3_0_0/APIMethods300.scala index 928d12ee0..e489af8c4 100644 --- a/obp-api/src/main/scala/code/api/v3_0_0/APIMethods300.scala +++ b/obp-api/src/main/scala/code/api/v3_0_0/APIMethods300.scala @@ -493,7 +493,7 @@ trait APIMethods300 { for { (Full(u), callContext) <- authenticatedAccess(cc) _ <- Helper.booleanToFuture(failMsg = AccountFirehoseNotAllowedOnThisInstance , cc=cc.callContext) { - ALLOW_ACCOUNT_FIREHOSE + allowAccountFirehose } _ <- NewStyle.function.hasEntitlement("", u.userId, ApiRole.canUseAccountFirehoseAtAnyBank, callContext) (bank, callContext) <- NewStyle.function.getBank(bankId, callContext) @@ -580,7 +580,7 @@ trait APIMethods300 { for { (Full(u), callContext) <- authenticatedAccess(cc) _ <- Helper.booleanToFuture(failMsg = AccountFirehoseNotAllowedOnThisInstance , cc=callContext) { - ALLOW_ACCOUNT_FIREHOSE + allowAccountFirehose } _ <- NewStyle.function.hasEntitlement("", u.userId, ApiRole.canUseAccountFirehoseAtAnyBank, callContext) (bank, callContext) <- NewStyle.function.getBank(bankId, callContext) diff --git a/obp-api/src/main/scala/code/api/v3_1_0/APIMethods310.scala b/obp-api/src/main/scala/code/api/v3_1_0/APIMethods310.scala index 9b4bfad2e..6b6f0e062 100644 --- a/obp-api/src/main/scala/code/api/v3_1_0/APIMethods310.scala +++ b/obp-api/src/main/scala/code/api/v3_1_0/APIMethods310.scala @@ -466,7 +466,7 @@ trait APIMethods310 { for { (Full(u), callContext) <- authenticatedAccess(cc) _ <- Helper.booleanToFuture(failMsg = AccountFirehoseNotAllowedOnThisInstance , cc=callContext) { - ALLOW_CUSTOMER_FIREHOSE + allowCustomerFirehose } _ <- NewStyle.function.hasEntitlement("", u.userId, ApiRole.canUseCustomerFirehoseAtAnyBank, callContext) (_, callContext) <- NewStyle.function.getBank(bankId, callContext) 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 3ea11d3b7..73670698c 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 @@ -3211,7 +3211,7 @@ trait APIMethods400 { for { (Full(u), bank, callContext) <- SS.userBank _ <- Helper.booleanToFuture(failMsg = AccountFirehoseNotAllowedOnThisInstance, cc=cc.callContext) { - ALLOW_ACCOUNT_FIREHOSE + allowAccountFirehose } // here must be a system view, not accountIds in the URL view <- NewStyle.function.checkViewAccessAndReturnView(viewId, BankIdAccountId(BankId(""), AccountId("")), Some(u), callContext) diff --git a/obp-api/src/main/scala/code/views/MapperViews.scala b/obp-api/src/main/scala/code/views/MapperViews.scala index 37d3fe6eb..9b184c706 100644 --- a/obp-api/src/main/scala/code/views/MapperViews.scala +++ b/obp-api/src/main/scala/code/views/MapperViews.scala @@ -53,7 +53,7 @@ object MapperViews extends Views with MdcLoggable { .map(v => v.bank_id(a.bank_id.get).account_id(a.account_id.get)) ).filter( v => - if (ALLOW_PUBLIC_VIEWS) { + if (allowPublicViews) { true // All views } else { v.isPrivate == true // Only private views @@ -122,7 +122,7 @@ object MapperViews extends Views with MdcLoggable { viewDefinition match { case Full(v) => { - if(v.isPublic && !ALLOW_PUBLIC_VIEWS) return Failure(PublicViewsNotAllowedOnThisInstance) + if(v.isPublic && !allowPublicViews) return Failure(PublicViewsNotAllowedOnThisInstance) // SQL Select Count ViewPrivileges where // This is idempotent getOrGrantAccessToCustomView(user, v, viewIdBankIdAccountId.bankId.value, viewIdBankIdAccountId.accountId.value) //privilege already exists, no need to create one @@ -133,7 +133,7 @@ object MapperViews extends Views with MdcLoggable { } } def grantAccessToSystemView(bankId: BankId, accountId: AccountId, view: View, user: User): Box[View] = { - { view.isPublic && !ALLOW_PUBLIC_VIEWS } match { + { view.isPublic && !allowPublicViews } match { case true => Failure(PublicViewsNotAllowedOnThisInstance) case false => getOrGrantAccessToSystemView(bankId: BankId, accountId: AccountId, user, view) } @@ -153,7 +153,7 @@ object MapperViews extends Views with MdcLoggable { //TODO: APIFailures with http response codes belong at a higher level in the code } else { viewDefinitions.foreach(v => { - if(v._1.isPublic && !ALLOW_PUBLIC_VIEWS) return Failure(PublicViewsNotAllowedOnThisInstance) + if(v._1.isPublic && !allowPublicViews) return Failure(PublicViewsNotAllowedOnThisInstance) val viewDefinition = v._1 val viewIdBankIdAccountId = v._2 // This is idempotent @@ -176,7 +176,7 @@ object MapperViews extends Views with MdcLoggable { //TODO: APIFailures with http response codes belong at a higher level in the code } else { viewDefinitions.foreach(v => { - if(v._1.isPublic && !ALLOW_PUBLIC_VIEWS) return Failure(PublicViewsNotAllowedOnThisInstance) + if(v._1.isPublic && !allowPublicViews) return Failure(PublicViewsNotAllowedOnThisInstance) val viewDefinition = v._1 val viewIdBankIdAccountId = v._2 // This is idempotent @@ -300,7 +300,7 @@ object MapperViews extends Views with MdcLoggable { def customView(viewId : ViewId, account: BankIdAccountId) : Box[View] = { val view = ViewDefinition.findCustomView(account.bankId.value, account.accountId.value, viewId.value) - if(view.isDefined && view.openOrThrowException(attemptedToOpenAnEmptyBox).isPublic && !ALLOW_PUBLIC_VIEWS) return Failure(PublicViewsNotAllowedOnThisInstance) + if(view.isDefined && view.openOrThrowException(attemptedToOpenAnEmptyBox).isPublic && !allowPublicViews) return Failure(PublicViewsNotAllowedOnThisInstance) view } @@ -360,7 +360,7 @@ object MapperViews extends Views with MdcLoggable { * */ def createView(bankAccountId: BankIdAccountId, view: CreateViewJson): Box[View] = { - if(view.is_public && !ALLOW_PUBLIC_VIEWS) { + if(view.is_public && !allowPublicViews) { return Failure(PublicViewsNotAllowedOnThisInstance) } @@ -456,7 +456,7 @@ object MapperViews extends Views with MdcLoggable { } def publicViews: (List[View], List[AccountAccess]) = { - if (APIUtil.ALLOW_PUBLIC_VIEWS) { + if (APIUtil.allowPublicViews) { val publicViews = ViewDefinition.findAll(By(ViewDefinition.isPublic_, true)) // Custom and System views val publicAccountAccesses = AccountAccess.findAll(ByList(AccountAccess.view_fk, publicViews.map(_.id))) (publicViews, publicAccountAccesses) @@ -466,7 +466,7 @@ object MapperViews extends Views with MdcLoggable { } def publicViewsForBank(bankId: BankId): (List[View], List[AccountAccess]) ={ - if (APIUtil.ALLOW_PUBLIC_VIEWS) { + if (APIUtil.allowPublicViews) { val publicViews = ViewDefinition.findAll(By(ViewDefinition.isPublic_, true), By(ViewDefinition.bank_id, bankId.value), By(ViewDefinition.isSystem_, false)) ::: // Custom views ViewDefinition.findAll(By(ViewDefinition.isPublic_, true), By(ViewDefinition.isSystem_, true)) ::: // System views @@ -723,7 +723,7 @@ object MapperViews extends Views with MdcLoggable { } def createDefaultPublicView(bankId: BankId, accountId: AccountId, name: String): Box[View] = { - if(!ALLOW_PUBLIC_VIEWS) { + if(!allowPublicViews) { return Failure(PublicViewsNotAllowedOnThisInstance) } createAndSaveDefaultPublicView(bankId, accountId, "Public View") @@ -739,12 +739,12 @@ object MapperViews extends Views with MdcLoggable { def getExistingView(bankId: BankId, accountId: AccountId, name: String): Box[View] = { val res = ViewDefinition.findCustomView(bankId.value, accountId.value, name) - if(res.isDefined && res.openOrThrowException(attemptedToOpenAnEmptyBox).isPublic && !ALLOW_PUBLIC_VIEWS) return Failure(PublicViewsNotAllowedOnThisInstance) + if(res.isDefined && res.openOrThrowException(attemptedToOpenAnEmptyBox).isPublic && !allowPublicViews) return Failure(PublicViewsNotAllowedOnThisInstance) res } def getExistingSystemView(name: String): Box[View] = { val res = ViewDefinition.findSystemView(name) - if(res.isDefined && res.openOrThrowException(attemptedToOpenAnEmptyBox).isPublic && !ALLOW_PUBLIC_VIEWS) return Failure(PublicViewsNotAllowedOnThisInstance) + if(res.isDefined && res.openOrThrowException(attemptedToOpenAnEmptyBox).isPublic && !allowPublicViews) return Failure(PublicViewsNotAllowedOnThisInstance) res } @@ -1135,7 +1135,7 @@ object MapperViews extends Views with MdcLoggable { } def createAndSaveDefaultPublicView(bankId : BankId, accountId: AccountId, description: String) : Box[View] = { - if(!ALLOW_PUBLIC_VIEWS) { + if(!allowPublicViews) { return Failure(PublicViewsNotAllowedOnThisInstance) } val res = unsavedDefaultPublicView(bankId, accountId, description).saveMe From fb0b24ba4fbe1c0d21d47b464fc6818d8a6c6c41 Mon Sep 17 00:00:00 2001 From: hongwei Date: Mon, 21 Jun 2021 12:02:17 +0200 Subject: [PATCH 057/147] bugfix/tweaked the bankId in the test --- .../src/test/scala/code/api/v1_3_0/PhysicalCardsTest.scala | 6 +++--- obp-api/src/test/scala/code/management/ImporterTest.scala | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/obp-api/src/test/scala/code/api/v1_3_0/PhysicalCardsTest.scala b/obp-api/src/test/scala/code/api/v1_3_0/PhysicalCardsTest.scala index eb3d12390..71bc655ff 100644 --- a/obp-api/src/test/scala/code/api/v1_3_0/PhysicalCardsTest.scala +++ b/obp-api/src/test/scala/code/api/v1_3_0/PhysicalCardsTest.scala @@ -1,9 +1,8 @@ package code.api.v1_3_0 import java.util.Date - import code.api.util.APIUtil.OAuth._ -import code.api.util.{CallContext, OBPQueryParam} +import code.api.util.{APIUtil, CallContext, OBPQueryParam} import code.bankconnectors.Connector import code.setup.{DefaultConnectorTestSetup, DefaultUsers, ServerSetup} import code.util.Helper.MdcLoggable @@ -14,7 +13,7 @@ class PhysicalCardsTest extends ServerSetup with DefaultUsers with DefaultConne def v1_3Request = baseRequest / "obp" / "v1.3.0" - lazy val bank = createBank("a-bank") + lazy val bank = createBank(APIUtil.defaultBankId) lazy val accId = "a-account" lazy val accountCurrency = "EUR" lazy val account = createAccount(bank.bankId, AccountId(accId), accountCurrency) @@ -94,6 +93,7 @@ class PhysicalCardsTest extends ServerSetup with DefaultUsers with DefaultConne super.afterAll() //reset the default connector Connector.connector.default.set(Connector.buildOne) + wipeTestData() } feature("Getting details of physical cards") { diff --git a/obp-api/src/test/scala/code/management/ImporterTest.scala b/obp-api/src/test/scala/code/management/ImporterTest.scala index eb7bed36c..2188afff6 100644 --- a/obp-api/src/test/scala/code/management/ImporterTest.scala +++ b/obp-api/src/test/scala/code/management/ImporterTest.scala @@ -35,7 +35,7 @@ class ImporterTest extends ServerSetup with MdcLoggable with DefaultConnectorTes def fixture(accId : String) = new Fixture(accId) class Fixture(accId : String) { - lazy val bank = createBank("a-bank") + lazy val bank = createBank(APIUtil.defaultBankId) lazy val accountCurrency = "EUR" lazy val account = createAccount(bank.bankId, AccountId(accId), accountCurrency) val originalBalance = account.balance.toString From fee2bfd7000c16e89c89664886a7b23f6f88fb60 Mon Sep 17 00:00:00 2001 From: hongwei Date: Mon, 21 Jun 2021 12:52:58 +0200 Subject: [PATCH 058/147] bugfix/added the bankId checking for the bank creation --- .../scala/code/setup/LocalMappedConnectorTestSetup.scala | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/obp-api/src/test/scala/code/setup/LocalMappedConnectorTestSetup.scala b/obp-api/src/test/scala/code/setup/LocalMappedConnectorTestSetup.scala index ab9834d58..7ab810f18 100644 --- a/obp-api/src/test/scala/code/setup/LocalMappedConnectorTestSetup.scala +++ b/obp-api/src/test/scala/code/setup/LocalMappedConnectorTestSetup.scala @@ -26,6 +26,9 @@ trait LocalMappedConnectorTestSetup extends TestConnectorSetupWithStandardPermis // (same in LocalRecordConnectorTestSetup) // Tests should simply use the currently selected connector override protected def createBank(id : String) : Bank = { + //Note: we do not have the `UniqueIndex` for bank.id(permalink) yet, we but when we have getBankById endpoint, + //Better set only create one bank for one id. + MappedBank.findByBankId(BankId(id)).getOrElse( MappedBank.create .fullBankName(randomString(5)) .shortBankName(randomString(5)) @@ -33,7 +36,7 @@ trait LocalMappedConnectorTestSetup extends TestConnectorSetupWithStandardPermis .national_identifier(randomString(5)) .mBankRoutingScheme(randomString(5)) .mBankRoutingAddress(randomString(5)) - .saveMe + .saveMe) } override protected def createCounterparty(bankId: String, accountId: String, counterpartyObpRoutingAddress: String, isBeneficiary: Boolean, createdByUserId:String): CounterpartyTrait = { From 8fe0b0c1d06b51e890c61f6dcaa01b08dcfdcc2b Mon Sep 17 00:00:00 2001 From: Simon Redfern Date: Mon, 21 Jun 2021 14:59:30 +0200 Subject: [PATCH 059/147] docfix/Mentioning developers can use update customer number in Create Customer --- obp-api/src/main/scala/code/api/v3_1_0/APIMethods310.scala | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/obp-api/src/main/scala/code/api/v3_1_0/APIMethods310.scala b/obp-api/src/main/scala/code/api/v3_1_0/APIMethods310.scala index ec6296d8b..900677e19 100644 --- a/obp-api/src/main/scala/code/api/v3_1_0/APIMethods310.scala +++ b/obp-api/src/main/scala/code/api/v3_1_0/APIMethods310.scala @@ -1191,8 +1191,10 @@ trait APIMethods310 { "/banks/BANK_ID/customers", "Create Customer", s""" - |The Customer resource stores the customer number, legal name, email, phone number, their date of birth, relationship status, education attained, a url for a profile image, KYC status etc. + |The Customer resource stores the customer number (which is set by the backend), legal name, email, phone number, their date of birth, relationship status, education attained, a url for a profile image, KYC status etc. |Dates need to be in the format 2013-01-21T23:08:00Z + | + |Note: If you need to set a specific customer number, use the Update Customer Number endpoint after this call. | |${authenticationRequiredMessage(true)} |""", From e3bedeee69b11d4db1d31e689aaa3e90f960cc4d Mon Sep 17 00:00:00 2001 From: shuang Date: Mon, 21 Jun 2021 22:56:13 +0800 Subject: [PATCH 060/147] bugfix/fix recurse ref problem of swagger entity. --- .../dynamic/DynamicEndpointHelper.scala | 185 ++++++++++-------- 1 file changed, 101 insertions(+), 84 deletions(-) diff --git a/obp-api/src/main/scala/code/api/v4_0_0/dynamic/DynamicEndpointHelper.scala b/obp-api/src/main/scala/code/api/v4_0_0/dynamic/DynamicEndpointHelper.scala index b2f87b1dc..818c388e7 100644 --- a/obp-api/src/main/scala/code/api/v4_0_0/dynamic/DynamicEndpointHelper.scala +++ b/obp-api/src/main/scala/code/api/v4_0_0/dynamic/DynamicEndpointHelper.scala @@ -25,7 +25,7 @@ import net.liftweb.json.JsonDSL._ import net.liftweb.json.JsonParser.ParseException import org.apache.commons.lang3.{StringUtils, Validate} import net.liftweb.util.{StringHelpers, ThreadGlobal} -import org.apache.commons.collections4.MapUtils +import org.apache.commons.collections4.{ListUtils, MapUtils} import org.apache.commons.io.FileUtils import org.apache.commons.lang3.StringUtils @@ -40,7 +40,7 @@ import net.liftweb.json.Formats import scala.collection.JavaConverters._ import scala.collection.immutable.List import scala.collection.mutable -import scala.collection.mutable.ArrayBuffer +import scala.collection.mutable.{ArrayBuffer, ListBuffer} object DynamicEndpointHelper extends RestHelper { @@ -251,7 +251,7 @@ object DynamicEndpointHelper extends RestHelper { val opName = method.name().toLowerCase().capitalize s"Can${opName}DynamicEndpoint_" } - val roleName = if(StringUtils.isNotBlank(op.getOperationId)) { + var roleName = if(StringUtils.isNotBlank(op.getOperationId)) { val prettyOperationId = op.getOperationId .replaceAll("""(?i)(get|find|search|add|create|delete|update|of|new|the|one|that|\s)""", "") .capitalize @@ -267,7 +267,10 @@ object DynamicEndpointHelper extends RestHelper { s"$roleNamePrefix$prettySummary${entitlementSuffix(path)}" } - + // substring role name to avoid it have over the maximum length of db column. + if(roleName.size > 64) { + roleName = StringUtils.substring(roleName, 0, 53) + roleName.hashCode() + } Some(List( ApiRole.getOrCreateDynamicApiRole(roleName, bankId.isDefined) )) @@ -479,103 +482,117 @@ object DynamicEndpointHelper extends RestHelper { implicit val formats = CustomJsonFormats.formats val example: Any = getExampleBySchema(openAPI, schema) + convertToProduct(example) + } - example match { - case null => EmptyBody - case v: String => StringBody(v) - case v: Boolean => BooleanBody(v) - case v: Int => IntBody(v) - case v: Long => LongBody(v) - case v: BigInt => BigIntBody(v) - case v: Float => FloatBody(v) - case v: Double => DoubleBody(v) - case v: BigDecimal => BigDecimalBody(v) - case v: JArray => JArrayBody(v) - case v: JObject => v - case v :scala.Product => v - case v => json.Extraction.decompose(v) match { - case o: JObject => o - case JArray(arr) => arr - case _ => throw new RuntimeException(s"Not supporting example type: $v, ${v.getClass}") - } + private def convertToProduct(example: Any): Product = example match { + case null => EmptyBody + case v: String => StringBody(v) + case v: Boolean => BooleanBody(v) + case v: Int => IntBody(v) + case v: Long => LongBody(v) + case v: BigInt => BigIntBody(v) + case v: Float => FloatBody(v) + case v: Double => DoubleBody(v) + case v: BigDecimal => BigDecimalBody(v) + case v: JArray => JArrayBody(v) + case v: JObject => v + case v :scala.Product => v + case v => json.Extraction.decompose(v) match { + case o: JObject => o + case JArray(arr) => arr + case _ => throw new RuntimeException(s"Not supporting example type: $v, ${v.getClass}") } } private def getExampleBySchema(openAPI: OpenAPI, schema: Schema[_]):Any = { + def getDefaultValue[T](schema: Schema[_<:T], t: => T): T = Option(schema.getExample.asInstanceOf[T]) .orElse(Option(schema.getDefault)) .orElse{ - Option(schema.getEnum()) - .filterNot(_.isEmpty) - .map(_.get(0)) + schema.getEnum() match { + case null => None + case l if l.isEmpty => None + case l => Option(l.get(0)) + } } .getOrElse(t) - schema match { - case null => null - case v: BooleanSchema => getDefaultValue(v, true) - case v if v.getType() =="boolean" => true - case v: DateSchema => getDefaultValue(v, { - APIUtil.DateWithDayFormat.format(new Date()) - }) - case v if v.getFormat() == "date" => getDefaultValue(v, { - APIUtil.DateWithDayFormat.format(new Date()) - }) - case v: DateTimeSchema => getDefaultValue(v, { - APIUtil.DateWithSecondsFormat.format(new Date()) - }) - case v if v.getFormat() == "date-time" => getDefaultValue(v, { - APIUtil.DateWithSecondsFormat.format(new Date()) - }) - case v: IntegerSchema => getDefaultValue(v, 1) - case v if v.getFormat() == "int32" => 1 - case v: NumberSchema => getDefaultValue(v, 1.2) - case v if v.getType() == "number" => 1.2 - case v: StringSchema => getDefaultValue(v, "string") - case v: UUIDSchema => getDefaultValue(v, UUID.randomUUID()) - case v if v.getFormat() == "uuid" => UUID.randomUUID() - case v: EmailSchema => getDefaultValue(v, "example@tesobe.com") - case v if v.getFormat() == "email" => "example@tesobe.com" - case v: FileSchema => getDefaultValue(v, "file_example.txt") - case v if v.getFormat() == "binary" => "file_example.txt" - case v: PasswordSchema => getDefaultValue(v, "very_complex_password_I_promise_!!") - case v if v.getFormat() == "password" => "very_complex_password_I_promise_!!" - case v: ArraySchema => - getDefaultValue(v, { - val itemsSchema: Schema[_] = v.getItems - val singleItemExample = getExampleBySchema(openAPI, itemsSchema) - singleItemExample match { - case v: JValue => JArray(v::Nil) - case v => json.Extraction.decompose(Array(v)) - } + val schemas:ListBuffer[Schema[_]] = ListBuffer() + def rec(schema: Schema[_]): Any = { + if(schema.isInstanceOf[ObjectSchema]) { + schemas += schema + } + // check whether this schema already recurse two times + if(schemas.count(schema ==) > 3) { + return JObject(Nil) + } + + schema match { + + case null => null + case v: BooleanSchema => getDefaultValue(v, true) + case v if v.getType() =="boolean" => true + case v: DateSchema => getDefaultValue(v, { + APIUtil.DateWithDayFormat.format(new Date()) }) - case v: MapSchema => getDefaultValue(v, Map("name"-> "John", "age" -> 12)) - //The swagger object schema may not contain any properties: eg: - // "Account": { - // "title": "accountTransactibility", - // "type": "object" - // } - case v if v.isInstanceOf[ObjectSchema] && MapUtils.isEmpty(v.getProperties()) => - EmptyBody - - case v if v.isInstanceOf[ObjectSchema] || MapUtils.isNotEmpty(v.getProperties()) => - val properties: util.Map[String, Schema[_]] = v.getProperties + case v if v.getFormat() == "date" => getDefaultValue(v, { + APIUtil.DateWithDayFormat.format(new Date()) + }) + case v: DateTimeSchema => getDefaultValue(v, { + APIUtil.DateWithSecondsFormat.format(new Date()) + }) + case v if v.getFormat() == "date-time" => getDefaultValue(v, { + APIUtil.DateWithSecondsFormat.format(new Date()) + }) + case v: IntegerSchema => getDefaultValue(v, 1) + case v if v.getFormat() == "int32" => 1 + case v: NumberSchema => getDefaultValue(v, 1.2) + case v if v.getType() == "number" => 1.2 + case v: StringSchema => getDefaultValue(v, "string") + case v: UUIDSchema => getDefaultValue(v, UUID.randomUUID()) + case v if v.getFormat() == "uuid" => UUID.randomUUID() + case v: EmailSchema => getDefaultValue(v, "example@tesobe.com") + case v if v.getFormat() == "email" => "example@tesobe.com" + case v: FileSchema => getDefaultValue(v, "file_example.txt") + case v if v.getFormat() == "binary" => "file_example.txt" + case v: PasswordSchema => getDefaultValue(v, "very_complex_password_I_promise_!!") + case v if v.getFormat() == "password" => "very_complex_password_I_promise_!!" + case v: ArraySchema => + getDefaultValue(v, { + rec(v.getItems) match { + case v: JValue => JArray(v::Nil) + case v => json.Extraction.decompose(Array(v)) + } + }) + case v: MapSchema => getDefaultValue(v, Map("name"-> "John", "age" -> 12)) + //The swagger object schema may not contain any properties: eg: + // "Account": { + // "title": "accountTransactibility", + // "type": "object" + // } + case v if v.isInstanceOf[ObjectSchema] && MapUtils.isEmpty(v.getProperties()) => + EmptyBody - val jFields: mutable.Iterable[JField] = properties.asScala.map { kv => - val (name, value) = kv - val valueExample = getExampleBySchema(openAPI, value) - JField(name, json.Extraction.decompose(valueExample)) - } - JObject(jFields.toList) + case v if v.isInstanceOf[ObjectSchema] || MapUtils.isNotEmpty(v.getProperties()) => + val properties: util.Map[String, Schema[_]] = v.getProperties - case v: Schema[_] if StringUtils.isNotBlank(v.get$ref()) => - val refSchema = getRefSchema(openAPI, v.get$ref()) + val jFields: mutable.Iterable[JField] = properties.asScala.map { kv => + val (name, value) = kv + val valueExample = rec(value) + JField(name, json.Extraction.decompose(valueExample)) + } + JObject(jFields.toList) - getExample(openAPI, refSchema) + case v: Schema[_] if StringUtils.isNotBlank(v.get$ref()) => + val refSchema = getRefSchema(openAPI, v.get$ref()) + convertToProduct(rec(refSchema)) - case v if v.getType() == "string" => "string" - case _ => throw new RuntimeException(s"Not support type $schema, please support it if necessary.") + case v if v.getType() == "string" => "string" + case _ => throw new RuntimeException(s"Not support type $schema, please support it if necessary.") + } } + rec(schema) } From 0954aaa3db32ade7957d4a44b38ff5a182d75881 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marko=20Mili=C4=87?= Date: Mon, 21 Jun 2021 17:25:34 +0200 Subject: [PATCH 061/147] feature/Add terms and conditions for user invitation flow --- .../scala/code/snippet/UserInvitation.scala | 18 +++++++++++++++++- obp-api/src/main/webapp/user-invitation.html | 16 ++++++++++++++++ 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/obp-api/src/main/scala/code/snippet/UserInvitation.scala b/obp-api/src/main/scala/code/snippet/UserInvitation.scala index 856bb5890..776cce383 100644 --- a/obp-api/src/main/scala/code/snippet/UserInvitation.scala +++ b/obp-api/src/main/scala/code/snippet/UserInvitation.scala @@ -47,8 +47,13 @@ class UserInvitation extends MdcLoggable { private object countryVar extends RequestVar("None") private object devEmailVar extends RequestVar("") private object usernameVar extends RequestVar("") + private object termsCheckboxVar extends RequestVar(false) + private object privacyCheckboxVar extends RequestVar(false) val registrationConsumerButtonValue: String = getWebUiPropsValue("webui_post_user_invitation_submit_button_value", "Register as a Developer") + val privacyConditionsValue: String = getWebUiPropsValue("webui_post_user_invitation_privacy_conditions_value", "Privacy conditions") + val termsAndConditionsValue: String = getWebUiPropsValue("webui_post_user_invitation_terms_and_conditions_value", "Terms and Conditions") + val termsAndConditionsCheckboxValue: String = getWebUiPropsValue("webui_post_user_invitation_terms_and_conditions_checkbox_value", "I agree to the above Developer Terms and Conditions") def registerForm: CssSel = { @@ -65,6 +70,8 @@ class UserInvitation extends MdcLoggable { def submitButtonDefense(): Unit = { if(Users.users.vend.getUserByUserName(usernameVar.is).isDefined) showErrorsForUsername() else if(userInvitation.map(_.status != "CREATED").getOrElse(false)) showErrorsForStatus() + else if(privacyCheckboxVar.is == false) showErrorsForPrivacyConditions() + else if(termsCheckboxVar.is == false) showErrorsForTermsAndConditions() else { createResourceUser( provider = "OBP-User-Invitation", @@ -93,10 +100,15 @@ class UserInvitation extends MdcLoggable { def showErrorsForUsername() = { showError(Helper.i18n("unique.username")) } - def showErrorsForStatus() = { showError(Helper.i18n("user.invitation.is.already.finished")) } + def showErrorsForTermsAndConditions() = { + showError(Helper.i18n("terms.and.conditions.are.not.selected")) + } + def showErrorsForPrivacyConditions() = { + showError(Helper.i18n("privacy.conditions.are.not.selected")) + } def register = { "form" #> { @@ -106,6 +118,10 @@ class UserInvitation extends MdcLoggable { "#companyName" #> SHtml.text(companyVar.is, companyVar(_)) & "#devEmail" #> SHtml.text(devEmailVar, devEmailVar(_)) & "#username" #> SHtml.text(usernameVar, usernameVar(_)) & + "#privacy" #> SHtml.textarea(privacyConditionsValue, privacyConditionsValue => privacyConditionsValue) & + "#privacy_checkbox" #> SHtml.checkbox(privacyCheckboxVar, privacyCheckboxVar(_)) & + "#terms" #> SHtml.textarea(termsAndConditionsValue, termsAndConditionsValue => termsAndConditionsValue) & + "#terms_checkbox" #> SHtml.checkbox(termsCheckboxVar, termsCheckboxVar(_)) & "type=submit" #> SHtml.submit(s"$registrationConsumerButtonValue", () => submitButtonDefense) } & "#register-consumer-success" #> "" diff --git a/obp-api/src/main/webapp/user-invitation.html b/obp-api/src/main/webapp/user-invitation.html index 99ee87534..8d624a332 100644 --- a/obp-api/src/main/webapp/user-invitation.html +++ b/obp-api/src/main/webapp/user-invitation.html @@ -68,6 +68,22 @@ Berlin 13359, Germany +
+ + + + +
+
+ + +
+
+ + + + +
From 1bd82e64a86d67ea61b4e42427390799d71c8bd1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marko=20Mili=C4=87?= Date: Tue, 22 Jun 2021 14:02:25 +0200 Subject: [PATCH 062/147] feature/Add table UserAgreement to the database --- .../main/scala/bootstrap/liftweb/Boot.scala | 3 +- .../code/remotedata/RemotedataActors.scala | 3 +- .../remotedata/RemotedataUserAgreement.scala | 16 +++++++ .../RemotedataUserAgreementActor.scala | 24 ++++++++++ .../main/scala/code/users/UserAgreement.scala | 48 +++++++++++++++++++ .../code/users/UserAgreementProvider.scala | 38 +++++++++++++++ 6 files changed, 130 insertions(+), 2 deletions(-) create mode 100644 obp-api/src/main/scala/code/remotedata/RemotedataUserAgreement.scala create mode 100644 obp-api/src/main/scala/code/remotedata/RemotedataUserAgreementActor.scala create mode 100644 obp-api/src/main/scala/code/users/UserAgreement.scala create mode 100644 obp-api/src/main/scala/code/users/UserAgreementProvider.scala diff --git a/obp-api/src/main/scala/bootstrap/liftweb/Boot.scala b/obp-api/src/main/scala/bootstrap/liftweb/Boot.scala index 3342cec77..ab7ea1da2 100644 --- a/obp-api/src/main/scala/bootstrap/liftweb/Boot.scala +++ b/obp-api/src/main/scala/bootstrap/liftweb/Boot.scala @@ -118,7 +118,7 @@ import code.transactionattribute.MappedTransactionAttribute import code.transactionrequests.{MappedTransactionRequest, MappedTransactionRequestTypeCharge, TransactionRequestReasons} import code.usercustomerlinks.MappedUserCustomerLink import code.userlocks.UserLocks -import code.users.UserInvitation +import code.users.{UserAgreement, UserInvitation} import code.util.Helper.MdcLoggable import code.util.{Helper, HydraUtil} import code.validation.JsonSchemaValidation @@ -824,6 +824,7 @@ object ToSchemify { ViewDefinition, ResourceUser, UserInvitation, + UserAgreement, MappedComment, MappedTag, MappedWhereTag, diff --git a/obp-api/src/main/scala/code/remotedata/RemotedataActors.scala b/obp-api/src/main/scala/code/remotedata/RemotedataActors.scala index bb0470e3c..1ff21d8ff 100644 --- a/obp-api/src/main/scala/code/remotedata/RemotedataActors.scala +++ b/obp-api/src/main/scala/code/remotedata/RemotedataActors.scala @@ -63,7 +63,8 @@ object RemotedataActors extends MdcLoggable { ActorProps[RemotedataTransactionAttributeActor] -> RemotedataTransactionAttribute.actorName, ActorProps[RemotedataRateLimitingActor] -> RemotedataRateLimiting.actorName, ActorProps[RemotedataAttributeDefinitionActor] -> RemotedataAttributeDefinition.actorName, - ActorProps[RemotedataUserInvitationActor] -> RemotedataUserInvitation.actorName + ActorProps[RemotedataUserInvitationActor] -> RemotedataUserInvitation.actorName, + ActorProps[RemotedataUserAgreementActor] -> RemotedataUserAgreement.actorName ) actorsRemotedata.foreach { a => logger.info(actorSystem.actorOf(a._1, name = a._2)) } diff --git a/obp-api/src/main/scala/code/remotedata/RemotedataUserAgreement.scala b/obp-api/src/main/scala/code/remotedata/RemotedataUserAgreement.scala new file mode 100644 index 000000000..e8ab6afac --- /dev/null +++ b/obp-api/src/main/scala/code/remotedata/RemotedataUserAgreement.scala @@ -0,0 +1,16 @@ +package code.remotedata + +import akka.pattern.ask +import code.actorsystem.ObpActorInit +import code.users.{RemotedataUserAgreementProviderCaseClass, UserAgreement, UserAgreementProvider} +import net.liftweb.common._ + + +object RemotedataUserAgreement extends ObpActorInit with UserAgreementProvider { + + val cc = RemotedataUserAgreementProviderCaseClass + + def createUserAgreement(userId: String, summary: String, agreementText: String): Box[UserAgreement] = getValueFromFuture( + (actor ? cc.createUserAgreement(userId, summary, agreementText)).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 new file mode 100644 index 000000000..306902212 --- /dev/null +++ b/obp-api/src/main/scala/code/remotedata/RemotedataUserAgreementActor.scala @@ -0,0 +1,24 @@ +package code.remotedata + +import akka.actor.Actor +import code.actorsystem.ObpActorHelper +import code.users.{MappedUserAgreementProvider, RemotedataUserAgreementProviderCaseClass} +import code.util.Helper.MdcLoggable + +class RemotedataUserAgreementActor extends Actor with ObpActorHelper with MdcLoggable { + + val mapper = MappedUserAgreementProvider + val cc = RemotedataUserAgreementProviderCaseClass + + def receive: PartialFunction[Any, Unit] = { + + case cc.createUserAgreement(userId: String, summary: String, agreementText: String) => + logger.debug(s"createUserAgreement($userId, $summary, $agreementText)") + sender ! (mapper.createUserAgreement(userId, summary, agreementText)) + + case message => logger.warn("[AKKA ACTOR ERROR - REQUEST NOT RECOGNIZED] " + message) + + } + +} + diff --git a/obp-api/src/main/scala/code/users/UserAgreement.scala b/obp-api/src/main/scala/code/users/UserAgreement.scala new file mode 100644 index 000000000..0e396cc22 --- /dev/null +++ b/obp-api/src/main/scala/code/users/UserAgreement.scala @@ -0,0 +1,48 @@ +package code.users + +import java.util.Date +import java.util.UUID.randomUUID + +import code.api.util.{HashUtil, SecureRandomUtil} +import code.util.UUIDString +import com.openbankproject.commons.model.BankId +import net.liftweb.common.Box +import net.liftweb.mapper.{MappedDateTime, _} +import net.liftweb.util.Helpers.tryo + +object MappedUserAgreementProvider extends UserAgreementProvider { + override def createUserAgreement(userId: String, summary: String, agreementText: String): Box[UserAgreement] = tryo { + UserAgreement.create + .UserId(userId) + .Summary(summary) + .AgreementText(agreementText) + .Date(new Date) + .saveMe() + } +} +class UserAgreement extends UserAgreementTrait with LongKeyedMapper[UserAgreement] with IdPK with CreatedUpdated { + + def getSingleton = UserAgreement + + object UserAgreementId extends UUIDString(this) { + override def defaultValue = randomUUID().toString + } + object UserId extends MappedString(this, 255) + object Date extends MappedDate(this) + object Summary extends MappedString(this, 50) + object AgreementText extends MappedText(this) + object AgreementHash extends MappedString(this, 50) { + override def defaultValue: String = HashUtil.Sha256Hash(AgreementText.get) + } + + override def userInvitationId: String = UserAgreementId.get + override def userId: String = UserId.get + override def summary: String = Summary.get + override def agreementText: String = AgreementText.get + override def agreementHash: String = AgreementHash.get +} + +object UserAgreement extends UserAgreement with LongKeyedMetaMapper[UserAgreement] { + override def dbIndexes: List[BaseIndex[UserAgreement]] = UniqueIndex(UserAgreementId) :: super.dbIndexes +} + diff --git a/obp-api/src/main/scala/code/users/UserAgreementProvider.scala b/obp-api/src/main/scala/code/users/UserAgreementProvider.scala new file mode 100644 index 000000000..9a3463d98 --- /dev/null +++ b/obp-api/src/main/scala/code/users/UserAgreementProvider.scala @@ -0,0 +1,38 @@ +package code.users + +import code.api.util.APIUtil +import code.remotedata.{RemotedataUserAgreement, RemotedataUserInvitation} +import com.openbankproject.commons.model.BankId +import net.liftweb.common.Box +import net.liftweb.util.SimpleInjector + + +object UserAgreementProvider extends SimpleInjector { + + val userAgreementProvider = new Inject(buildOne _) {} + + def buildOne: UserAgreementProvider = + APIUtil.getPropsAsBoolValue("use_akka", false) match { + case false => MappedUserAgreementProvider + case true => RemotedataUserAgreement // We will use Akka as a middleware + } + +} + +trait UserAgreementProvider { + def createUserAgreement(userId: String, summary: String, agreementText: String): Box[UserAgreement] +} + +class RemotedataUserAgreementProviderCaseClass { + case class createUserAgreement(userId: String, summary: String, agreementText: String) +} + +object RemotedataUserAgreementProviderCaseClass extends RemotedataUserAgreementProviderCaseClass + +trait UserAgreementTrait { + def userInvitationId: String + def userId: String + def summary: String + def agreementText: String + def agreementHash: String +} \ No newline at end of file From 8cd8ef78cd6699d8ef8ce590e9f5ddada60d19fc Mon Sep 17 00:00:00 2001 From: hongwei Date: Wed, 23 Jun 2021 10:25:14 +0200 Subject: [PATCH 063/147] test/fixed the dynamic tests --- .../ResourceDocsAPIMethods.scala | 8 +++++++ .../SwaggerDefinitionsJSON.scala | 19 --------------- .../scala/code/api/util/ExampleValue.scala | 22 +++++++++++++++++ .../scala/code/api/v4_0_0/APIMethods400.scala | 24 +++++++++---------- .../EndpointMappingProvider.scala | 2 +- .../code/api/v4_0_0/DynamicEntityTest.scala | 13 +++++++--- .../api/v4_0_0/DynamicIntegrationTest.scala | 2 +- .../api/v4_0_0/DynamicendPointsTest.scala | 24 ++++++++++--------- .../v4_0_0/EndpointMappingBankLevelTest.scala | 5 ++-- .../code/api/v4_0_0/EndpointMappingTest.scala | 5 ++-- .../code/api/v4_0_0/RateLimitingTest.scala | 18 +++++++------- 11 files changed, 82 insertions(+), 60 deletions(-) diff --git a/obp-api/src/main/scala/code/api/ResourceDocs1_4_0/ResourceDocsAPIMethods.scala b/obp-api/src/main/scala/code/api/ResourceDocs1_4_0/ResourceDocsAPIMethods.scala index cfd205fa1..b2c4d0d6f 100644 --- a/obp-api/src/main/scala/code/api/ResourceDocs1_4_0/ResourceDocsAPIMethods.scala +++ b/obp-api/src/main/scala/code/api/ResourceDocs1_4_0/ResourceDocsAPIMethods.scala @@ -6,6 +6,7 @@ import code.api.builder.OBP_APIBuilder import code.api.cache.Caching import code.api.util.APIUtil._ import code.api.util.ApiTag._ +import code.api.util.ExampleValue.endpointMappingRequestBodyExample import code.api.util.{APIUtil, _} import code.api.v1_4_0.JSONFactory1_4_0.ResourceDocsJson import code.api.v1_4_0.{APIMethods140, JSONFactory1_4_0, OBPAPI1_4_0} @@ -588,6 +589,13 @@ trait ResourceDocsAPIMethods extends MdcLoggable with APIMethods220 with APIMeth successResponseBody = ExampleValue.dynamicEndpointResponseBodyEmptyExample ) + case doc if (doc.partialFunctionName == nameOf(APIMethods400.Implementations4_0_0.createEndpointMapping) || + doc.partialFunctionName == nameOf(APIMethods400.Implementations4_0_0.createBankLevelEndpointMapping) ) => + doc.copy( + exampleRequestBody = endpointMappingRequestBodyExample, + successResponseBody = endpointMappingRequestBodyExample + ) + case doc if ( doc.partialFunctionName == nameOf(APIMethods400.Implementations4_0_0.getDynamicEndpoint) || doc.partialFunctionName == nameOf(APIMethods400.Implementations4_0_0.getBankLevelDynamicEndpoint)) => doc.copy(successResponseBody = ExampleValue.dynamicEndpointResponseBodyEmptyExample) 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 2337731bd..4aeeba79d 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 @@ -4154,25 +4154,6 @@ object SwaggerDefinitionsJSON { val jsonCodeTemplate = "code" -> URLEncoder.encode("""println("hello")""", "UTF-8") - val endpointMappingJson = EndpointMappingCommons( - Some("b4e0352a-9a0f-4bfa-b30b-9003aa467f50"), - "OBPv4.0.0-dynamicEndpoint_POST_account", - """{}""".stripMargin, - """{ - | "name": { - | "entity": "FooBar", - | "field": "name", - | "query": "number" - | }, - | "balance": { - | "entity": "FashionBrand", - | "field": "number", - | "query": "number" - | } - | }""".stripMargin, - Some(bankIdExample.value) - ) - val supportedCurrenciesJson = SupportedCurrenciesJson( supportedCurrenciesExample.value .replaceAll(""""""","").replace("""[""","") diff --git a/obp-api/src/main/scala/code/api/util/ExampleValue.scala b/obp-api/src/main/scala/code/api/util/ExampleValue.scala index 7b4e38335..99c30415d 100644 --- a/obp-api/src/main/scala/code/api/util/ExampleValue.scala +++ b/obp-api/src/main/scala/code/api/util/ExampleValue.scala @@ -2274,6 +2274,28 @@ object ExampleValue { case JField("swagger_string", _) => JField("swagger_string", "swagger" -> "2.0") }.asInstanceOf[JObject] + val endpointMappingExample = + """{ + | "operation_id": "OBPv4.0.0-dynamicEndpoint_POST_account", + | "request_mapping": { + | + | }, + | "response_mapping": { + | "name": { + | "entity": "FooBar", + | "field": "name", + | "query": "number" + | }, + | "balance": { + | "entity": "FashionBrand", + | "field": "number", + | "query": "number" + | } + | } + |}""".stripMargin + lazy val endpointMappingRequestBodyExample: JObject = json.parse(endpointMappingExample).asInstanceOf[JObject] + lazy val endpointMappingResponseBodyExample: JObject = endpointMappingRequestBodyExample ~ ("endpoint_mapping_id", "b4e0352a-9a0f-4bfa-b30b-9003aa467f50") + /** * parse date example value to Date type * @param exampleValue example 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 5a760c671..b8f1eeada 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 @@ -8293,8 +8293,8 @@ trait APIMethods400 { | |Note: at moment only support the dynamic endpoints |""", - endpointMappingJson.copy(endpointMappingId = None, bankId = None), - endpointMappingJson, + endpointMappingRequestBodyExample, + endpointMappingResponseBodyExample, List( $UserNotLoggedIn, UserHasMissingRoles, @@ -8334,8 +8334,8 @@ trait APIMethods400 { "Update Endpoint Mapping", s"""Update an Endpoint Mapping. |""", - endpointMappingJson.copy(endpointMappingId = None, bankId = None), - endpointMappingJson, + endpointMappingRequestBodyExample, + endpointMappingResponseBodyExample, List( $UserNotLoggedIn, UserHasMissingRoles, @@ -8382,7 +8382,7 @@ trait APIMethods400 { | |""", EmptyBody, - endpointMappingJson, + endpointMappingResponseBodyExample, List( $UserNotLoggedIn, UserHasMissingRoles, @@ -8418,7 +8418,7 @@ trait APIMethods400 { | |""", EmptyBody, - ListResult("endpoint-mappings", endpointMappingJson::Nil), + ListResult("endpoint-mappings", endpointMappingResponseBodyExample::Nil), List( $UserNotLoggedIn, UserHasMissingRoles, @@ -8489,8 +8489,8 @@ trait APIMethods400 { | |Note: at moment only support the dynamic endpoints |""", - endpointMappingJson.copy(endpointMappingId = None, bankId = None), - endpointMappingJson, + endpointMappingRequestBodyExample, + endpointMappingResponseBodyExample, List( $BankNotFound, $UserNotLoggedIn, @@ -8517,8 +8517,8 @@ trait APIMethods400 { "Update Bank Level Endpoint Mapping", s"""Update an Bank Level Endpoint Mapping. |""", - endpointMappingJson.copy(endpointMappingId = None, bankId = None), - endpointMappingJson, + endpointMappingRequestBodyExample, + endpointMappingResponseBodyExample, List( $BankNotFound, $UserNotLoggedIn, @@ -8547,7 +8547,7 @@ trait APIMethods400 { | |""", EmptyBody, - endpointMappingJson, + endpointMappingResponseBodyExample, List( $BankNotFound, $UserNotLoggedIn, @@ -8575,7 +8575,7 @@ trait APIMethods400 { | |""", EmptyBody, - ListResult("endpoint-mappings", endpointMappingJson::Nil), + ListResult("endpoint-mappings", endpointMappingResponseBodyExample::Nil), List( $BankNotFound, $UserNotLoggedIn, diff --git a/obp-api/src/main/scala/code/endpointMapping/EndpointMappingProvider.scala b/obp-api/src/main/scala/code/endpointMapping/EndpointMappingProvider.scala index 981d4659f..3a15bfa16 100644 --- a/obp-api/src/main/scala/code/endpointMapping/EndpointMappingProvider.scala +++ b/obp-api/src/main/scala/code/endpointMapping/EndpointMappingProvider.scala @@ -7,7 +7,7 @@ import com.openbankproject.commons.model.{Converter, JsonFieldReName} import net.liftweb.common.Box import net.liftweb.json import net.liftweb.json.Formats -import net.liftweb.json.JsonAST.{JArray, JField, JNull, JObject, JString} +import net.liftweb.json.JsonAST.{JArray, JField, JNull, JObject, JString, JValue} import net.liftweb.util.SimpleInjector object EndpointMappingProvider extends SimpleInjector { diff --git a/obp-api/src/test/scala/code/api/v4_0_0/DynamicEntityTest.scala b/obp-api/src/test/scala/code/api/v4_0_0/DynamicEntityTest.scala index 426440bb5..d9c83a2f4 100644 --- a/obp-api/src/test/scala/code/api/v4_0_0/DynamicEntityTest.scala +++ b/obp-api/src/test/scala/code/api/v4_0_0/DynamicEntityTest.scala @@ -415,7 +415,7 @@ class DynamicEntityTest extends V400ServerSetup { dynamicEntitiesGetJson.values should have size 2 - val JArray(head :: Nil) = dynamicEntitiesGetJson + val head= dynamicEntitiesGetJson.arr.head head should equal(expectUpdatedResponseJson) @@ -437,6 +437,13 @@ class DynamicEntityTest extends V400ServerSetup { val response = makePostRequest(request, write(rightEntity)) Then("We should get a 201") response.code should equal(201) + + {//Test the bank level create entity + val request = (v4_0_0_Request / "management" / "banks" / testBankId1.value / "dynamic-entities").POST<@(user1) + val response = makePostRequest(request, write(rightEntityBankLevel)) + Then("We should get a 201") + response.code should equal(201) + } val responseJson = response.body val dynamicEntityId = (responseJson \ "dynamicEntityId").asInstanceOf[JString].s @@ -528,9 +535,9 @@ class DynamicEntityTest extends V400ServerSetup { val json = responseGet.body \ "dynamic_entities" val dynamicEntitiesGetJson = json.asInstanceOf[JArray] - dynamicEntitiesGetJson.values should have size 1 + dynamicEntitiesGetJson.values should have size 2 - val JArray(head :: Nil) = dynamicEntitiesGetJson + val head = dynamicEntitiesGetJson.arr.head head should equal(expectUpdatedResponseJson) diff --git a/obp-api/src/test/scala/code/api/v4_0_0/DynamicIntegrationTest.scala b/obp-api/src/test/scala/code/api/v4_0_0/DynamicIntegrationTest.scala index 5f161aa5f..8bf9fceef 100644 --- a/obp-api/src/test/scala/code/api/v4_0_0/DynamicIntegrationTest.scala +++ b/obp-api/src/test/scala/code/api/v4_0_0/DynamicIntegrationTest.scala @@ -42,7 +42,7 @@ class DynamicIntegrationTest extends V400ServerSetup { object ApiEndpoint14 extends Tag(nameOf(Implementations4_0_0.updateBankLevelDynamicEntity)) - val mapping = endpointMappingJson.copy(endpointMappingId = None) + val mapping = endpointMappingRequestBodyExample val dynamicEntity = dynamicEntityRequestBodyExample.copy(bankId = None) val dynamicEndpoint = dynamicEndpointRequestBodyExample diff --git a/obp-api/src/test/scala/code/api/v4_0_0/DynamicendPointsTest.scala b/obp-api/src/test/scala/code/api/v4_0_0/DynamicendPointsTest.scala index b7be1daa2..4ee00e40a 100644 --- a/obp-api/src/test/scala/code/api/v4_0_0/DynamicendPointsTest.scala +++ b/obp-api/src/test/scala/code/api/v4_0_0/DynamicendPointsTest.scala @@ -41,22 +41,24 @@ class DynamicEndpointsTest extends V400ServerSetup { feature(s"test $ApiEndpoint9, $ApiEndpoint10, $ApiEndpoint11, $ApiEndpoint12 version $VersionOfApi") { - scenario(s"If we create one entity for system, we should allow to create the bank level, otherwise, it will break the roles", ApiEndpoint1,ApiEndpoint9, VersionOfApi) { + scenario(s"If we create one entity for system, we should not allow to create the bank level as the same entity," + + s" otherwise it will break the roles", ApiEndpoint1,ApiEndpoint9, VersionOfApi) { When("We make a request v4.0.0") - Entitlement.entitlement.vend.addEntitlement(testBankId1.value, resourceUser1.userId, canCreateDynamicEndpoint.toString) - val requestSystemLevle = (v4_0_0_Request / "management" / "dynamic-endpoints").POST<@ (user1) - val responseSystemLevel = makePostRequest(requestSystemLevle, postDynamicEndpointSwagger) + Entitlement.entitlement.vend.addEntitlement("", resourceUser1.userId, canCreateDynamicEndpoint.toString) + val requestSystemLevel = (v4_0_0_Request / "management" / "dynamic-endpoints").POST<@ (user1) + val responseSystemLevel = makePostRequest(requestSystemLevel, postDynamicEndpointSwagger) Then("We should get a 201") responseSystemLevel.code should equal(201) responseSystemLevel.body.toString contains("dynamic_endpoint_id") should be (true) - When("We make a request v4.0.0") - Entitlement.entitlement.vend.addEntitlement(testBankId1.value, resourceUser1.userId, canCreateBankLevelDynamicEndpoint.toString) - val request = (v4_0_0_Request / "management" /"banks"/testBankId1.value/ "dynamic-endpoints").POST<@ (user1) - val responseWithRole = makePostRequest(request, postDynamicEndpointSwagger) - Then("We should get a 400") - responseWithRole.code should equal(400) - responseWithRole.body.toString contains(DynamicEndpointExists) should be (true) +// TODO , Need to think about if we should allow to create same entity as the bank level. +// When("We make a request v4.0.0") +// Entitlement.entitlement.vend.addEntitlement(testBankId1.value, resourceUser1.userId, canCreateBankLevelDynamicEndpoint.toString) +// val request = (v4_0_0_Request / "management" /"banks"/testBankId1.value/ "dynamic-endpoints").POST<@ (user1) +// val responseWithRole = makePostRequest(request, postDynamicEndpointSwagger) +// Then("We should get a 400") +// responseWithRole.code should equal(400) +// responseWithRole.body.toString contains(DynamicEndpointExists) should be (true) } diff --git a/obp-api/src/test/scala/code/api/v4_0_0/EndpointMappingBankLevelTest.scala b/obp-api/src/test/scala/code/api/v4_0_0/EndpointMappingBankLevelTest.scala index a711c9431..337ba386c 100644 --- a/obp-api/src/test/scala/code/api/v4_0_0/EndpointMappingBankLevelTest.scala +++ b/obp-api/src/test/scala/code/api/v4_0_0/EndpointMappingBankLevelTest.scala @@ -1,9 +1,10 @@ package code.api.v4_0_0 -import code.api.ResourceDocs1_4_0.SwaggerDefinitionsJSON.{endpointMappingJson,jsonCodeTemplate} +import code.api.ResourceDocs1_4_0.SwaggerDefinitionsJSON.jsonCodeTemplate import code.api.util.APIUtil.OAuth._ import code.api.util.ApiRole._ import code.api.util.ErrorMessages.{UserNotLoggedIn, _} +import code.api.util.ExampleValue.endpointMappingRequestBodyExample import code.api.v4_0_0.OBPAPI4_0_0.Implementations4_0_0 import code.endpointMapping.EndpointMappingCommons import code.entitlement.Entitlement @@ -28,7 +29,7 @@ class EndpointMappingBankLevelTest extends V400ServerSetup { object ApiEndpoint4 extends Tag(nameOf(Implementations4_0_0.updateBankLevelEndpointMapping)) object ApiEndpoint5 extends Tag(nameOf(Implementations4_0_0.deleteBankLevelEndpointMapping)) - val rightEntity = endpointMappingJson.copy(endpointMappingId = None) + val rightEntity = endpointMappingRequestBodyExample val wrongEntity = jsonCodeTemplate feature("Add a EndpointMapping v4.0.0- Unauthorized access") { diff --git a/obp-api/src/test/scala/code/api/v4_0_0/EndpointMappingTest.scala b/obp-api/src/test/scala/code/api/v4_0_0/EndpointMappingTest.scala index 3760909de..ea15d52a8 100644 --- a/obp-api/src/test/scala/code/api/v4_0_0/EndpointMappingTest.scala +++ b/obp-api/src/test/scala/code/api/v4_0_0/EndpointMappingTest.scala @@ -1,9 +1,10 @@ package code.api.v4_0_0 -import code.api.ResourceDocs1_4_0.SwaggerDefinitionsJSON.{endpointMappingJson,jsonCodeTemplate} +import code.api.ResourceDocs1_4_0.SwaggerDefinitionsJSON.jsonCodeTemplate import code.api.util.APIUtil.OAuth._ import code.api.util.ApiRole.{CanCreateEndpointMapping, _} import code.api.util.ErrorMessages.{UserNotLoggedIn, _} +import code.api.util.ExampleValue.endpointMappingRequestBodyExample import code.api.v4_0_0.OBPAPI4_0_0.Implementations4_0_0 import code.endpointMapping.EndpointMappingCommons import code.entitlement.Entitlement @@ -28,7 +29,7 @@ class EndpointMappingTest extends V400ServerSetup { object ApiEndpoint4 extends Tag(nameOf(Implementations4_0_0.updateEndpointMapping)) object ApiEndpoint5 extends Tag(nameOf(Implementations4_0_0.deleteEndpointMapping)) - val rightEntity = endpointMappingJson.copy(endpointMappingId = None) + val rightEntity = endpointMappingRequestBodyExample val wrongEntity = jsonCodeTemplate feature("Add a EndpointMapping v4.0.0- Unauthorized access") { diff --git a/obp-api/src/test/scala/code/api/v4_0_0/RateLimitingTest.scala b/obp-api/src/test/scala/code/api/v4_0_0/RateLimitingTest.scala index 0ff23cc60..145f03bb8 100644 --- a/obp-api/src/test/scala/code/api/v4_0_0/RateLimitingTest.scala +++ b/obp-api/src/test/scala/code/api/v4_0_0/RateLimitingTest.scala @@ -235,18 +235,18 @@ class RateLimitingTest extends V400ServerSetup with PropsReset { responseWithRole.code should equal(201) // Set Rate Limiting in case of a Dynamic Endpoint - val operationId = "OBPv4.0.0-dynamicEndpoint_GET_user_USERNAME" - val apiName = "dynamicEndpoint_GET_user_USERNAME" + val operationId = "OBPv4.0.0-dynamicEndpoint_GET_accounts_ACCOUNT_ID" + val apiName = "dynamicEndpoint_GET_accounts_ACCOUNT_ID" val apiVersion = "v4.0.0" val response01 = setRateLimiting(user1, callLimitJsonHour.copy(api_name = Some(apiName), api_version = Some(apiVersion))) Then("We should get a 200") response01.code should equal(200) - val requestDynamicEndpoint = baseRequest / "obp" / "v4.0.0" / "dynamic" / "user" / "NON_EXISTING_USERNAME" + val requestDynamicEndpoint = baseRequest / "obp" / "v4.0.0" / "dynamic" / "accounts" / "accountId" // 1st call dos NOT exceed rate limit When("We make the first call after update") - Then("We should get a 404") - makeGetRequest(requestDynamicEndpoint.GET <@(user1)).code should equal(404) + Then("We should get a 200") + makeGetRequest(requestDynamicEndpoint.GET <@(user1)).code should equal(200) // 2nd call exceeds rate limit When("We make the second call after update") Then("We should get a 429") @@ -259,12 +259,12 @@ class RateLimitingTest extends V400ServerSetup with PropsReset { // 1st call dos NOT exceed rate limit When("We make the first call after update") - Then("We should get a 404") - makeGetRequest(requestDynamicEndpoint.GET <@(user1)).code should equal(404) + Then("We should get a 200") + makeGetRequest(requestDynamicEndpoint.GET <@(user1)).code should equal(200) // 2nd call dos NOT exceed rate limit When("We make the first call after update") - Then("We should get a 404") - makeGetRequest(requestDynamicEndpoint.GET <@(user1)).code should equal(404) + Then("We should get a 200") + makeGetRequest(requestDynamicEndpoint.GET <@(user1)).code should equal(200) } } From 3d69c2bd2289f1caa9e342a6b0a5e385dbdc7203 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marko=20Mili=C4=87?= Date: Wed, 23 Jun 2021 11:07:42 +0200 Subject: [PATCH 064/147] feature/Finish User Agreement Flow --- .../remotedata/RemotedataUserAgreement.scala | 4 +- .../RemotedataUserAgreementActor.scala | 6 +-- .../scala/code/snippet/UserInvitation.scala | 15 +++++-- .../main/scala/code/users/UserAgreement.scala | 43 +++++++++++++------ .../code/users/UserAgreementProvider.scala | 5 ++- 5 files changed, 49 insertions(+), 24 deletions(-) diff --git a/obp-api/src/main/scala/code/remotedata/RemotedataUserAgreement.scala b/obp-api/src/main/scala/code/remotedata/RemotedataUserAgreement.scala index e8ab6afac..eb12787b9 100644 --- a/obp-api/src/main/scala/code/remotedata/RemotedataUserAgreement.scala +++ b/obp-api/src/main/scala/code/remotedata/RemotedataUserAgreement.scala @@ -10,7 +10,7 @@ object RemotedataUserAgreement extends ObpActorInit with UserAgreementProvider { val cc = RemotedataUserAgreementProviderCaseClass - def createUserAgreement(userId: String, summary: String, agreementText: String): Box[UserAgreement] = getValueFromFuture( - (actor ? cc.createUserAgreement(userId, summary, agreementText)).mapTo[Box[UserAgreement]] + def createOrUpdateUserAgreement(userId: String, summary: String, agreementText: String, acceptMarketingInfo: Boolean): Box[UserAgreement] = getValueFromFuture( + (actor ? cc.createOrUpdateUserAgreement(userId, summary, agreementText, acceptMarketingInfo)).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 306902212..0e7399898 100644 --- a/obp-api/src/main/scala/code/remotedata/RemotedataUserAgreementActor.scala +++ b/obp-api/src/main/scala/code/remotedata/RemotedataUserAgreementActor.scala @@ -12,9 +12,9 @@ class RemotedataUserAgreementActor extends Actor with ObpActorHelper with MdcLog def receive: PartialFunction[Any, Unit] = { - case cc.createUserAgreement(userId: String, summary: String, agreementText: String) => - logger.debug(s"createUserAgreement($userId, $summary, $agreementText)") - sender ! (mapper.createUserAgreement(userId, summary, agreementText)) + case cc.createOrUpdateUserAgreement(userId: String, summary: String, agreementText: String, acceptMarketingInfo: Boolean) => + logger.debug(s"createOrUpdateUserAgreement($userId, $summary, $agreementText, $acceptMarketingInfo)") + sender ! (mapper.createOrUpdateUserAgreement(userId, summary, agreementText, acceptMarketingInfo)) case message => logger.warn("[AKKA ACTOR ERROR - REQUEST NOT RECOGNIZED] " + message) diff --git a/obp-api/src/main/scala/code/snippet/UserInvitation.scala b/obp-api/src/main/scala/code/snippet/UserInvitation.scala index 776cce383..4e30bbe58 100644 --- a/obp-api/src/main/scala/code/snippet/UserInvitation.scala +++ b/obp-api/src/main/scala/code/snippet/UserInvitation.scala @@ -27,7 +27,7 @@ TESOBE (http://www.tesobe.com/) package code.snippet import code.model.dataAccess.{AuthUser, ResourceUser} -import code.users.{UserInvitationProvider, Users} +import code.users.{UserAgreementProvider, UserInvitationProvider, Users} import code.util.Helper import code.util.Helper.MdcLoggable import code.webuiprops.MappedWebUiPropsProvider.getWebUiPropsValue @@ -48,11 +48,12 @@ class UserInvitation extends MdcLoggable { private object devEmailVar extends RequestVar("") private object usernameVar extends RequestVar("") private object termsCheckboxVar extends RequestVar(false) + private object marketingInfoCheckboxVar extends RequestVar(false) private object privacyCheckboxVar extends RequestVar(false) val registrationConsumerButtonValue: String = getWebUiPropsValue("webui_post_user_invitation_submit_button_value", "Register as a Developer") - val privacyConditionsValue: String = getWebUiPropsValue("webui_post_user_invitation_privacy_conditions_value", "Privacy conditions") - val termsAndConditionsValue: String = getWebUiPropsValue("webui_post_user_invitation_terms_and_conditions_value", "Terms and Conditions") + val privacyConditionsValue: String = getWebUiPropsValue("webui_post_user_invitation_privacy_conditions_value", "Privacy conditions.") + val termsAndConditionsValue: String = getWebUiPropsValue("webui_post_user_invitation_terms_and_conditions_value", "Terms and Conditions.") val termsAndConditionsCheckboxValue: String = getWebUiPropsValue("webui_post_user_invitation_terms_and_conditions_checkbox_value", "I agree to the above Developer Terms and Conditions") def registerForm: CssSel = { @@ -73,16 +74,23 @@ class UserInvitation extends MdcLoggable { else if(privacyCheckboxVar.is == false) showErrorsForPrivacyConditions() else if(termsCheckboxVar.is == false) showErrorsForTermsAndConditions() else { + // Resource User table createResourceUser( provider = "OBP-User-Invitation", providerId = Some(usernameVar.is), name = Some(firstNameVar.is + " " + lastNameVar.is), email = Some(email) ).map{ u => + // AuthUser table createAuthUser(user = u, firstName = firstNameVar.is, lastName = lastNameVar.is, password = "") + // Use Agreement table + UserAgreementProvider.userAgreementProvider.vend.createOrUpdateUserAgreement( + u.userId, privacyConditionsValue, termsAndConditionsValue, marketingInfoCheckboxVar.is) + // Set a new password val resetLink = AuthUser.passwordResetUrl(u.idGivenByProvider, u.emailAddress, u.userId) + "?action=set" S.redirectTo(resetLink) } + } } @@ -122,6 +130,7 @@ class UserInvitation extends MdcLoggable { "#privacy_checkbox" #> SHtml.checkbox(privacyCheckboxVar, privacyCheckboxVar(_)) & "#terms" #> SHtml.textarea(termsAndConditionsValue, termsAndConditionsValue => termsAndConditionsValue) & "#terms_checkbox" #> SHtml.checkbox(termsCheckboxVar, termsCheckboxVar(_)) & + "#marketing_info_checkbox" #> SHtml.checkbox(marketingInfoCheckboxVar, marketingInfoCheckboxVar(_)) & "type=submit" #> SHtml.submit(s"$registrationConsumerButtonValue", () => submitButtonDefense) } & "#register-consumer-success" #> "" diff --git a/obp-api/src/main/scala/code/users/UserAgreement.scala b/obp-api/src/main/scala/code/users/UserAgreement.scala index 0e396cc22..b87ee9d43 100644 --- a/obp-api/src/main/scala/code/users/UserAgreement.scala +++ b/obp-api/src/main/scala/code/users/UserAgreement.scala @@ -3,21 +3,34 @@ package code.users import java.util.Date import java.util.UUID.randomUUID -import code.api.util.{HashUtil, SecureRandomUtil} +import code.api.util.HashUtil import code.util.UUIDString -import com.openbankproject.commons.model.BankId -import net.liftweb.common.Box -import net.liftweb.mapper.{MappedDateTime, _} -import net.liftweb.util.Helpers.tryo +import net.liftweb.common.{Box, Empty, Full} +import net.liftweb.mapper._ object MappedUserAgreementProvider extends UserAgreementProvider { - override def createUserAgreement(userId: String, summary: String, agreementText: String): Box[UserAgreement] = tryo { - UserAgreement.create - .UserId(userId) - .Summary(summary) - .AgreementText(agreementText) - .Date(new Date) - .saveMe() + override def createOrUpdateUserAgreement(userId: String, summary: String, agreementText: String, acceptMarketingInfo: Boolean): Box[UserAgreement] = { + UserAgreement.find(By(UserAgreement.UserId, userId)) match { + case Full(existingUser) => + Full( + existingUser + .Summary(summary) + .AgreementText(agreementText) + .AcceptMarketingInfo(acceptMarketingInfo) + .saveMe() + ) + case Empty => + Full( + UserAgreement.create + .UserId(userId) + .Summary(summary) + .AgreementText(agreementText) + .AcceptMarketingInfo(acceptMarketingInfo) + .Date(new Date) + .saveMe() + ) + case everythingElse => everythingElse + } } } class UserAgreement extends UserAgreementTrait with LongKeyedMapper[UserAgreement] with IdPK with CreatedUpdated { @@ -29,17 +42,19 @@ class UserAgreement extends UserAgreementTrait with LongKeyedMapper[UserAgreemen } object UserId extends MappedString(this, 255) object Date extends MappedDate(this) - object Summary extends MappedString(this, 50) + object Summary extends MappedText(this) object AgreementText extends MappedText(this) - object AgreementHash extends MappedString(this, 50) { + object AgreementHash extends MappedString(this, 64) { override def defaultValue: String = HashUtil.Sha256Hash(AgreementText.get) } + object AcceptMarketingInfo extends MappedBoolean(this) override def userInvitationId: String = UserAgreementId.get override def userId: String = UserId.get override def summary: String = Summary.get override def agreementText: String = AgreementText.get override def agreementHash: String = AgreementHash.get + override def acceptMarketingInfo: Boolean = AcceptMarketingInfo.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 9a3463d98..76a077cec 100644 --- a/obp-api/src/main/scala/code/users/UserAgreementProvider.scala +++ b/obp-api/src/main/scala/code/users/UserAgreementProvider.scala @@ -20,11 +20,11 @@ object UserAgreementProvider extends SimpleInjector { } trait UserAgreementProvider { - def createUserAgreement(userId: String, summary: String, agreementText: String): Box[UserAgreement] + def createOrUpdateUserAgreement(userId: String, summary: String, agreementText: String, acceptMarketingInfo: Boolean): Box[UserAgreement] } class RemotedataUserAgreementProviderCaseClass { - case class createUserAgreement(userId: String, summary: String, agreementText: String) + case class createOrUpdateUserAgreement(userId: String, summary: String, agreementText: String, acceptMarketingInfo: Boolean) } object RemotedataUserAgreementProviderCaseClass extends RemotedataUserAgreementProviderCaseClass @@ -35,4 +35,5 @@ trait UserAgreementTrait { def summary: String def agreementText: String def agreementHash: String + def acceptMarketingInfo: Boolean } \ No newline at end of file From e18ee8c7cecad25d0dbe125883b37535770387b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marko=20Mili=C4=87?= Date: Wed, 23 Jun 2021 16:58:42 +0200 Subject: [PATCH 065/147] feature/Add enable and disable button at the User Invitation page --- obp-api/src/main/webapp/user-invitation.html | 25 ++++++++++++++++---- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/obp-api/src/main/webapp/user-invitation.html b/obp-api/src/main/webapp/user-invitation.html index 8d624a332..6ed82331c 100644 --- a/obp-api/src/main/webapp/user-invitation.html +++ b/obp-api/src/main/webapp/user-invitation.html @@ -68,26 +68,39 @@ Berlin 13359, Germany -
+
- +
-
+
- +
- + + +
@@ -95,3 +108,5 @@ Berlin 13359, Germany + + From cc7233e6b06987ea3e9e3554968aa5c1cc784016 Mon Sep 17 00:00:00 2001 From: hongwei Date: Thu, 24 Jun 2021 10:45:44 +0200 Subject: [PATCH 066/147] bugfix/Unexpected shorthand "font" after "font-size" - sonarcloud --- obp-api/src/main/webapp/media/css/reset.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/obp-api/src/main/webapp/media/css/reset.css b/obp-api/src/main/webapp/media/css/reset.css index e9ca4487f..1fb79bec2 100644 --- a/obp-api/src/main/webapp/media/css/reset.css +++ b/obp-api/src/main/webapp/media/css/reset.css @@ -19,8 +19,8 @@ time, mark, audio, video { margin: 0; padding: 0; border: 0; - font-size: 100%; font: inherit; + font-size: 100%; vertical-align: baseline; font-family: Roboto-Light; } From 829bbff672f69becd9ca152aadc747772b92e8fb Mon Sep 17 00:00:00 2001 From: hongwei Date: Thu, 24 Jun 2021 10:51:02 +0200 Subject: [PATCH 067/147] bugfix/sonarcloud-Remove this conditional structure or edit its code blocks so that they're not all the same. --- .../code/api/ResourceDocs1_4_0/SwaggerJSONFactory.scala | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/obp-api/src/main/scala/code/api/ResourceDocs1_4_0/SwaggerJSONFactory.scala b/obp-api/src/main/scala/code/api/ResourceDocs1_4_0/SwaggerJSONFactory.scala index b5bb1515c..3b4c9c508 100644 --- a/obp-api/src/main/scala/code/api/ResourceDocs1_4_0/SwaggerJSONFactory.scala +++ b/obp-api/src/main/scala/code/api/ResourceDocs1_4_0/SwaggerJSONFactory.scala @@ -491,13 +491,7 @@ object SwaggerJSONFactory extends MdcLoggable { tags = rd.tags.map(_.tag), summary = rd.summary, description = PegdownOptions.convertPegdownToHtmlTweaked(rd.description.stripMargin).replaceAll("\n", ""), - operationId = - rd.partialFunctionName match { - //No longer need this special case since all transaction request Resource Docs have explicit URL - //case "createTransactionRequest" => s"${rd.apiVersion.toString }-${rd.apiFunction.toString}-${UUID.randomUUID().toString}" - // Note: The operationId should not start with a number becuase Javascript constructors may use it to build variables. - case _ => s"${rd.implementedInApiVersion.fullyQualifiedVersion }-${rd.partialFunctionName.toString }" - }, + operationId =s"${rd.implementedInApiVersion.fullyQualifiedVersion }-${rd.partialFunctionName.toString }", parameters ={ val description = rd.exampleRequestBody match { case EmptyBody => "" From 845d3a815320af6ed55a83c9eac0e35966579b2a Mon Sep 17 00:00:00 2001 From: tawoe Date: Thu, 24 Jun 2021 16:33:40 +0200 Subject: [PATCH 068/147] feature/add debug logging --- obp-api/src/main/scala/code/api/GatewayLogin.scala | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/obp-api/src/main/scala/code/api/GatewayLogin.scala b/obp-api/src/main/scala/code/api/GatewayLogin.scala index 9d1ed3ed6..fb53be2ca 100755 --- a/obp-api/src/main/scala/code/api/GatewayLogin.scala +++ b/obp-api/src/main/scala/code/api/GatewayLogin.scala @@ -105,10 +105,14 @@ object GatewayLogin extends RestHelper with MdcLoggable { def parseJwt(parameters: Map[String, String]): Box[String] = { val jwt = getToken(parameters) + logger.debug("parseJwt says jwt.toString is: " + jwt.toString) + logger.debug("parseJwt says: validateJwtToken(jwt) is:" + validateJwtToken(jwt)) validateJwtToken(jwt) match { case Full(jwtPayload) => + logger.debug("parseJwt says: Full: " + jwtPayload.toString) Full(compactRender(Extraction.decompose(jwtPayload))) case _ => + logger.debug("parseJwt says: Not Full(jwtPayload)") Failure(ErrorMessages.GatewayLoginJwtTokenIsNotValid) } } @@ -119,11 +123,16 @@ object GatewayLogin extends RestHelper with MdcLoggable { val claim = CertificateUtil.decryptJwtWithRsa(token) Box(parse(claim.toString).extractOpt[PayloadOfJwtJSON]) case false => + logger.debug("validateJwtToken says: verifying jwt token: " + token) + logger.debug(CertificateUtil.verifywtWithHmacProtection(token).toString) CertificateUtil.verifywtWithHmacProtection(token) match { case true => + logger.debug("validateJwtToken says: jwt is verified: " + token) val claim = CertificateUtil.parseJwtWithHmacProtection(token) + logger.debug("validateJwtToken says: this is claim of verified jwt: " + claim.toString()) Box(parse(claim.toString).extractOpt[PayloadOfJwtJSON]) case _ => + logger.debug("validateJwtToken says: could not verify jwt") Failure(ErrorMessages.GatewayLoginJwtTokenIsNotValid) } } @@ -418,7 +427,9 @@ object GatewayLogin extends RestHelper with MdcLoggable { } private def getToken(params: Map[String, String]): String = { + logger.debug("getToken params are: " + params.toString()) val token = params.getOrElse("token", "") + logger.debug("getToken wants to return token: " + token) token } From f0aabc4f1ad49f39468b376f660f61d7f93bb676 Mon Sep 17 00:00:00 2001 From: tawoe Date: Thu, 24 Jun 2021 16:49:31 +0200 Subject: [PATCH 069/147] bugfix/update glossary item --- .../main/scala/code/api/util/Glossary.scala | 43 ++++++++++++++++--- 1 file changed, 38 insertions(+), 5 deletions(-) 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 857513d5a..3e43750ad 100644 --- a/obp-api/src/main/scala/code/api/util/Glossary.scala +++ b/obp-api/src/main/scala/code/api/util/Glossary.scala @@ -1794,11 +1794,14 @@ object Glossary { | |``` |{ -| "username": "simonr", -| "is_first": true, -| "timestamp": "timestamp", -| "consumer_id": "123", -| "consumer_name": "Name of Consumer" +| "login_user_name": "username", +| "is_first": false, +| "app_id": "85a965f0-0d55-4e0a-8b1c-649c4b01c4fb", +| "app_name": "GWL", +| "time_stamp": "2018-08-20T14:13:40Z", +| "cbs_token": "your_token", +| "cbs_id": "your_cbs_id", +| "session_id": "123456789" |} |``` |VERIFY SIGNATURE @@ -1884,6 +1887,36 @@ object Glossary { |AS8D76F7A89S87D6F7A9SD876FA789SD78F6A7S9D78F6AS79DF87A6S7D9F7A6S7D9F78A6SD798F78679D786S789D78F6A7S9D78F6AS79DF876A7S89DF786AS9D87F69AS7D6FN1bWVyIn0. |KEuvjv3dmwkOhQ3JJ6dIShK8CG_fd2REApOGn1TRmgU" | +|### Example python script +|``` +|import jwt +|from datetime import datetime, timezone +|from obp_python.config import obp_api_host +|import requests +| +|env = 'local' +|DATE_FORMAT = '%Y-%m-%dT%H:%M:%SZ' +| +|payload = { +| "login_user_name": "username", +| "is_first": False, +| "app_id": "85a965f0-0d55-4e0a-8b1c-649c4b01c4fb", +| "app_name": "Name", +| "time_stamp": datetime.now(timezone.utc).strftime(DATE_FORMAT), +| "cbs_token": "yourtokenforcbs", +| "cbs_id": "yourcbs_id", +| "session_id": "123456789" +|} +| +| +|token = jwt.encode(payload, 'secretsecretsecretstsecretssssss', algorithm='HS256') +|authorization = 'GatewayLogin token="{}"'.format(token) +|headers = {'Authorization': authorization} +|url = obp_api_host + '/obp/v4.0.0/users/current' +|req = requests.get(url, headers=headers) +|print(req.text) +|``` +| |### Under the hood | |The file, GatewayLogin.scala handles the Gateway Login. From e863c9dd4fc2e06da2f4687adea8dc8a0b9be7b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marko=20Mili=C4=87?= Date: Thu, 24 Jun 2021 16:58:57 +0200 Subject: [PATCH 070/147] feature/Add props brands_enabled=false --- .../main/resources/props/sample.props.template | 6 +++++- .../src/main/scala/code/api/util/APIUtil.scala | 17 +++++++++++++---- 2 files changed, 18 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 0b4e28100..d5c678651 100644 --- a/obp-api/src/main/resources/props/sample.props.template +++ b/obp-api/src/main/resources/props/sample.props.template @@ -952,4 +952,8 @@ default_auth_context_update_request_key=CUSTOMER_NUMBER #featured_api_collection_ids= # the alias prefix path for BerlinGroupV1.3 (OBP built-in is berlin-group/v1.3), the format must be xxx/yyy, eg: 0.6/v1 -#berlin_group_v1.3_alias.path= \ No newline at end of file +#berlin_group_v1.3_alias.path= + + +# Support multiple brands on one instance. Note this needs checking on a clustered environment +#brands_enabled=false \ No newline at end of file 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 84232d62a..b956de239 100644 --- a/obp-api/src/main/scala/code/api/util/APIUtil.scala +++ b/obp-api/src/main/scala/code/api/util/APIUtil.scala @@ -3120,11 +3120,20 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{ Note there are Read and Write side effects here! */ def activeBrand() : Option[String] = { - + APIUtil.getPropsAsBoolValue("brands_enabled", false) match { + case true => + getActiveBrand() + case false => + None + } + } + + // TODO This function needs testing in a cluster environment + private def getActiveBrand(): Option[String] = { val brandParameter = "brand" // Use brand in parameter (query or form) - val brand : Option[String] = S.param(brandParameter) match { + val brand: Option[String] = S.param(brandParameter) match { case Full(value) => { // If found, and has a valid format, set the session. if (isValidID(value)) { @@ -3132,11 +3141,11 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{ logger.debug(s"activeBrand says: I found a $brandParameter param. $brandParameter session has been set to: ${S.getSessionAttribute(brandParameter)}") Some(value) } else { - logger.warn (s"activeBrand says: ${ErrorMessages.InvalidBankIdFormat}") + logger.warn(s"activeBrand says: ${ErrorMessages.InvalidBankIdFormat}") None } } - case _ => { + case _ => { // Else look in the session S.getSessionAttribute(brandParameter) } From b9987b3ea96e93b9839f86fb929c49f0fdedade3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marko=20Mili=C4=87?= Date: Thu, 24 Jun 2021 19:39:24 +0200 Subject: [PATCH 071/147] bugfix/getPropsAsBoolValue cannot be called directly inside the function activeBrand due to java.lang.StackOverflowError --- obp-api/src/main/scala/code/api/util/APIUtil.scala | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) 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 d8d57d758..2e2765ef5 100644 --- a/obp-api/src/main/scala/code/api/util/APIUtil.scala +++ b/obp-api/src/main/scala/code/api/util/APIUtil.scala @@ -3119,8 +3119,10 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{ Else just return the session Note there are Read and Write side effects here! */ + // getPropsAsBoolValue cannot be called directly inside the function activeBrand due to java.lang.StackOverflowError + val brandsEnabled = APIUtil.getPropsAsBoolValue("brands_enabled", false) def activeBrand() : Option[String] = { - APIUtil.getPropsAsBoolValue("brands_enabled", false) match { + brandsEnabled match { case true => getActiveBrand() case false => From 701c58a30b5727834d268574eb882f3f3bfc8158 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marko=20Mili=C4=87?= Date: Fri, 25 Jun 2021 16:26:40 +0200 Subject: [PATCH 072/147] feature/Add Endpoint Delete User v4.0.0 --- .../main/scala/code/api/util/ApiRole.scala | 3 ++ .../main/scala/code/api/util/NewStyle.scala | 12 +++++-- .../scala/code/api/v4_0_0/APIMethods400.scala | 36 +++++++++++++++++++ .../code/model/dataAccess/AuthUser.scala | 17 +++++++++ .../code/remotedata/RemotedataUsers.scala | 6 +++- .../remotedata/RemotedataUsersActor.scala | 5 +++ .../src/main/scala/code/users/LiftUsers.scala | 13 ++++++- obp-api/src/main/scala/code/users/Users.scala | 5 ++- 8 files changed, 92 insertions(+), 5 deletions(-) diff --git a/obp-api/src/main/scala/code/api/util/ApiRole.scala b/obp-api/src/main/scala/code/api/util/ApiRole.scala index 926504f94..a6e71fe05 100644 --- a/obp-api/src/main/scala/code/api/util/ApiRole.scala +++ b/obp-api/src/main/scala/code/api/util/ApiRole.scala @@ -343,6 +343,9 @@ object ApiRole { case class CanLockUser (requiresBankId: Boolean = false) extends ApiRole lazy val canLockUser = CanLockUser() + + case class CanDeleteUser (requiresBankId: Boolean = false) extends ApiRole + lazy val canDeleteUser = CanDeleteUser() case class CanReadUserLockedStatus(requiresBankId: Boolean = false) extends ApiRole lazy val canReadUserLockedStatus = CanReadUserLockedStatus() 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 53e5b508e..9c501bb31 100644 --- a/obp-api/src/main/scala/code/api/util/NewStyle.scala +++ b/obp-api/src/main/scala/code/api/util/NewStyle.scala @@ -31,7 +31,7 @@ import code.methodrouting.{MethodRoutingCommons, MethodRoutingProvider, MethodRo import code.model._ import code.apicollectionendpoint.{ApiCollectionEndpointTrait, MappedApiCollectionEndpointsProvider} import code.apicollection.{ApiCollectionTrait, MappedApiCollectionsProvider} -import code.model.dataAccess.BankAccountRouting +import code.model.dataAccess.{AuthUser, BankAccountRouting} import code.standingorders.StandingOrderTrait import code.usercustomerlinks.UserCustomerLink import code.users.{UserInvitation, UserInvitationProvider, Users} @@ -935,7 +935,15 @@ object NewStyle { i => (connectorEmptyResponse(i._1, callContext), i._2) } } - + + def deleteUser(userPrimaryKey: UserPrimaryKey, callContext: Option[CallContext]): OBPReturnType[Boolean] = Future { + AuthUser.scrambleAuthUser(userPrimaryKey) match { + case Full(true) => + (Users.users.vend.scrambleDataOfResourceUser(userPrimaryKey).getOrElse(false), callContext) + case _ => + (false, callContext) + } + } def findByUserId(userId: String, callContext: Option[CallContext]): OBPReturnType[User] = { Future { UserX.findByUserId(userId).map(user =>(user, callContext))} 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 209850e2d..0b18e9499 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 @@ -3472,6 +3472,42 @@ trait APIMethods400 { } + staticResourceDocs += ResourceDoc( + deleteUser, + implementedInApiVersion, + nameOf(deleteUser), + "DELETE", + "/users/USER_ID", + "Delete a User", + s"""Delete a User. + | + | + |${authenticationRequiredMessage(true)} + | + |""", + emptyObjectJson, + emptyObjectJson, + List( + $UserNotLoggedIn, + UserHasMissingRoles, + UnknownError + ), + List(apiTagUser, apiTagNewStyle), + Some(List(canDeleteUser))) + + lazy val deleteUser : OBPEndpoint = { + case "users" :: userId :: Nil JsonDelete _ => { + cc => + for { + (user, callContext) <- NewStyle.function.findByUserId(userId, cc.callContext) + (userDeleted, callContext) <- NewStyle.function.deleteUser(user.userPrimaryKey, callContext) + } yield { + (Full(userDeleted), HttpCode.`200`(callContext)) + } + } + } + + staticResourceDocs += ResourceDoc( createBank, 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 eb2141fa4..28abfe79a 100644 --- a/obp-api/src/main/scala/code/model/dataAccess/AuthUser.scala +++ b/obp-api/src/main/scala/code/model/dataAccess/AuthUser.scala @@ -1253,4 +1253,21 @@ def restoreSomeSessions(): Unit = { innerSignup } + + def scrambleAuthUser(userPrimaryKey: UserPrimaryKey): Box[Boolean] = tryo { + AuthUser.find(By(AuthUser.user, userPrimaryKey.value)) match { + case Full(user) => + val newUser = user.firstName(Helpers.randomString(user.firstName.get.length)) + .email(Helpers.randomString(10) + "@example.com") + .username(Helpers.randomString(user.username.get.length)) + .firstName(Helpers.randomString(user.firstName.get.length)) + .lastName(Helpers.randomString(user.lastName.get.length)) + .password(Helpers.randomString(40)) + .password(Helpers.randomString(40)) + .validated(false) + newUser.save() + case _ => false + } + } + } diff --git a/obp-api/src/main/scala/code/remotedata/RemotedataUsers.scala b/obp-api/src/main/scala/code/remotedata/RemotedataUsers.scala index 2db1ab4bd..9454926aa 100644 --- a/obp-api/src/main/scala/code/remotedata/RemotedataUsers.scala +++ b/obp-api/src/main/scala/code/remotedata/RemotedataUsers.scala @@ -6,7 +6,7 @@ import code.api.util.OBPQueryParam import code.entitlement.Entitlement import code.model.dataAccess.ResourceUser import code.users.{RemotedataUsersCaseClasses, Users} -import com.openbankproject.commons.model.User +import com.openbankproject.commons.model.{User, UserPrimaryKey} import net.liftweb.common.Box import scala.collection.immutable.List @@ -86,6 +86,10 @@ object RemotedataUsers extends ObpActorInit with Users { def deleteResourceUser(userId: Long) : Box[Boolean] = getValueFromFuture( (actor ? cc.deleteResourceUser(userId)).mapTo[Box[Boolean]] ) + + def scrambleDataOfResourceUser(userPrimaryKey: UserPrimaryKey) : Box[Boolean] = getValueFromFuture( + (actor ? cc.scrambleDataOfResourceUser(userPrimaryKey)).mapTo[Box[Boolean]] + ) def bulkDeleteAllResourceUsers(): Box[Boolean] = getValueFromFuture( (actor ? cc.bulkDeleteAllResourceUsers()).mapTo[Box[Boolean]] diff --git a/obp-api/src/main/scala/code/remotedata/RemotedataUsersActor.scala b/obp-api/src/main/scala/code/remotedata/RemotedataUsersActor.scala index 71650245e..48f76d9f5 100644 --- a/obp-api/src/main/scala/code/remotedata/RemotedataUsersActor.scala +++ b/obp-api/src/main/scala/code/remotedata/RemotedataUsersActor.scala @@ -10,6 +10,7 @@ import code.util.Helper.MdcLoggable import scala.collection.immutable.List import com.openbankproject.commons.ExecutionContext.Implicits.global +import com.openbankproject.commons.model.UserPrimaryKey class RemotedataUsersActor extends Actor with ObpActorHelper with MdcLoggable { @@ -93,6 +94,10 @@ class RemotedataUsersActor extends Actor with ObpActorHelper with MdcLoggable { case cc.deleteResourceUser(id: Long) => logger.debug("deleteResourceUser(" + id +")") sender ! (mapper.deleteResourceUser(id)) + + case cc.scrambleDataOfResourceUser(userPrimaryKey: UserPrimaryKey) => + logger.debug("scrambleDataOfResourceUser(" + userPrimaryKey +")") + sender ! (mapper.scrambleDataOfResourceUser(userPrimaryKey)) case cc.bulkDeleteAllResourceUsers() => logger.debug("bulkDeleteAllResourceUsers()") diff --git a/obp-api/src/main/scala/code/users/LiftUsers.scala b/obp-api/src/main/scala/code/users/LiftUsers.scala index 46f56e0ee..26b6c5d16 100644 --- a/obp-api/src/main/scala/code/users/LiftUsers.scala +++ b/obp-api/src/main/scala/code/users/LiftUsers.scala @@ -5,13 +5,15 @@ import code.entitlement.Entitlement import code.loginattempts.LoginAttempt.maxBadLoginAttempts import code.loginattempts.MappedBadLoginAttempt import code.model.dataAccess.ResourceUser +import code.util.Helper import code.util.Helper.MdcLoggable -import com.openbankproject.commons.model.User +import com.openbankproject.commons.model.{User, UserPrimaryKey} import net.liftweb.common.{Box, Full} import net.liftweb.mapper._ import scala.collection.immutable.List import com.openbankproject.commons.ExecutionContext.Implicits.global +import net.liftweb.util.Helpers import scala.collection.immutable import scala.concurrent.Future @@ -236,5 +238,14 @@ object LiftUsers extends Users with MdcLoggable{ u.delete_! } } + override def scrambleDataOfResourceUser(userPrimaryKey: UserPrimaryKey): Box[Boolean] = { + for { + u <- ResourceUser.find(By(ResourceUser.id, userPrimaryKey.value)) + } yield { + u.name_(Helpers.randomString(u.name.length)) + .email(Helpers.randomString(10) + "@example.com") + .save() + } + } } diff --git a/obp-api/src/main/scala/code/users/Users.scala b/obp-api/src/main/scala/code/users/Users.scala index 963b5f345..7c80649b6 100644 --- a/obp-api/src/main/scala/code/users/Users.scala +++ b/obp-api/src/main/scala/code/users/Users.scala @@ -4,7 +4,7 @@ import code.api.util.{APIUtil, OBPQueryParam} import code.entitlement.Entitlement import code.model.dataAccess.ResourceUser import code.remotedata.RemotedataUsers -import com.openbankproject.commons.model.User +import com.openbankproject.commons.model.{User, UserPrimaryKey} import net.liftweb.common.Box import net.liftweb.util.SimpleInjector @@ -58,6 +58,8 @@ trait Users { def saveResourceUser(resourceUser: ResourceUser) : Box[ResourceUser] def deleteResourceUser(userId: Long) : Box[Boolean] + + def scrambleDataOfResourceUser(userPrimaryKey: UserPrimaryKey) : Box[Boolean] def bulkDeleteAllResourceUsers() : Box[Boolean] } @@ -82,6 +84,7 @@ class RemotedataUsersCaseClasses { 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) + case class scrambleDataOfResourceUser(userPrimaryKey: UserPrimaryKey) case class bulkDeleteAllResourceUsers() } From b86a90adeacbf503a1ac1cab2d26713f661e7575 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marko=20Mili=C4=87?= Date: Fri, 25 Jun 2021 17:51:35 +0200 Subject: [PATCH 073/147] feature/Add tooltip for redirect URL --- obp-api/src/main/webapp/consumer-registration.html | 1 + obp-api/src/main/webapp/media/icons/info-square.svg | 4 ++++ obp-api/src/main/webapp/media/js/popper.min.js | 5 +++++ obp-api/src/main/webapp/templates-hidden/default.html | 1 + 4 files changed, 11 insertions(+) create mode 100644 obp-api/src/main/webapp/media/icons/info-square.svg create mode 100644 obp-api/src/main/webapp/media/js/popper.min.js diff --git a/obp-api/src/main/webapp/consumer-registration.html b/obp-api/src/main/webapp/consumer-registration.html index 9e45656a4..fcd376c73 100644 --- a/obp-api/src/main/webapp/consumer-registration.html +++ b/obp-api/src/main/webapp/consumer-registration.html @@ -60,6 +60,7 @@ Berlin 13359, Germany
+ i
diff --git a/obp-api/src/main/webapp/media/icons/info-square.svg b/obp-api/src/main/webapp/media/icons/info-square.svg new file mode 100644 index 000000000..71e2818f5 --- /dev/null +++ b/obp-api/src/main/webapp/media/icons/info-square.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/obp-api/src/main/webapp/media/js/popper.min.js b/obp-api/src/main/webapp/media/js/popper.min.js new file mode 100644 index 000000000..0f20d2a89 --- /dev/null +++ b/obp-api/src/main/webapp/media/js/popper.min.js @@ -0,0 +1,5 @@ +/* + Copyright (C) Federico Zivolo 2017 + Distributed under the MIT License (license terms are at http://opensource.org/licenses/MIT). + */(function(e,t){'object'==typeof exports&&'undefined'!=typeof module?module.exports=t():'function'==typeof define&&define.amd?define(t):e.Popper=t()})(this,function(){'use strict';function e(e){return e&&'[object Function]'==={}.toString.call(e)}function t(e,t){if(1!==e.nodeType)return[];var o=getComputedStyle(e,null);return t?o[t]:o}function o(e){return'HTML'===e.nodeName?e:e.parentNode||e.host}function n(e){if(!e)return document.body;switch(e.nodeName){case'HTML':case'BODY':return e.ownerDocument.body;case'#document':return e.body;}var i=t(e),r=i.overflow,p=i.overflowX,s=i.overflowY;return /(auto|scroll)/.test(r+s+p)?e:n(o(e))}function r(e){var o=e&&e.offsetParent,i=o&&o.nodeName;return i&&'BODY'!==i&&'HTML'!==i?-1!==['TD','TABLE'].indexOf(o.nodeName)&&'static'===t(o,'position')?r(o):o:e?e.ownerDocument.documentElement:document.documentElement}function p(e){var t=e.nodeName;return'BODY'!==t&&('HTML'===t||r(e.firstElementChild)===e)}function s(e){return null===e.parentNode?e:s(e.parentNode)}function d(e,t){if(!e||!e.nodeType||!t||!t.nodeType)return document.documentElement;var o=e.compareDocumentPosition(t)&Node.DOCUMENT_POSITION_FOLLOWING,i=o?e:t,n=o?t:e,a=document.createRange();a.setStart(i,0),a.setEnd(n,0);var l=a.commonAncestorContainer;if(e!==l&&t!==l||i.contains(n))return p(l)?l:r(l);var f=s(e);return f.host?d(f.host,t):d(e,s(t).host)}function a(e){var t=1=o.clientWidth&&i>=o.clientHeight}),l=0i[e]&&!t.escapeWithReference&&(n=_(p[o],i[e]-('right'===e?p.width:p.height))),pe({},o,n)}};return n.forEach(function(e){var t=-1===['left','top'].indexOf(e)?'secondary':'primary';p=se({},p,s[t](e))}),e.offsets.popper=p,e},priority:['left','right','top','bottom'],padding:5,boundariesElement:'scrollParent'},keepTogether:{order:400,enabled:!0,fn:function(e){var t=e.offsets,o=t.popper,i=t.reference,n=e.placement.split('-')[0],r=X,p=-1!==['top','bottom'].indexOf(n),s=p?'right':'bottom',d=p?'left':'top',a=p?'width':'height';return o[s]r(i[s])&&(e.offsets.popper[d]=r(i[s])),e}},arrow:{order:500,enabled:!0,fn:function(e,o){var i;if(!F(e.instance.modifiers,'arrow','keepTogether'))return e;var n=o.element;if('string'==typeof n){if(n=e.instance.popper.querySelector(n),!n)return e;}else if(!e.instance.popper.contains(n))return console.warn('WARNING: `arrow.element` must be child of its popper element!'),e;var r=e.placement.split('-')[0],p=e.offsets,s=p.popper,d=p.reference,a=-1!==['left','right'].indexOf(r),l=a?'height':'width',f=a?'Top':'Left',m=f.toLowerCase(),h=a?'left':'top',g=a?'bottom':'right',u=L(n)[l];d[g]-us[g]&&(e.offsets.popper[m]+=d[m]+u-s[g]),e.offsets.popper=c(e.offsets.popper);var b=d[m]+d[l]/2-u/2,w=t(e.instance.popper),y=parseFloat(w['margin'+f],10),E=parseFloat(w['border'+f+'Width'],10),v=b-e.offsets.popper[m]-y-E;return v=J(_(s[l]-u,v),0),e.arrowElement=n,e.offsets.arrow=(i={},pe(i,m,Math.round(v)),pe(i,h,''),i),e},element:'[x-arrow]'},flip:{order:600,enabled:!0,fn:function(e,t){if(k(e.instance.modifiers,'inner'))return e;if(e.flipped&&e.placement===e.originalPlacement)return e;var o=y(e.instance.popper,e.instance.reference,t.padding,t.boundariesElement),i=e.placement.split('-')[0],n=x(i),r=e.placement.split('-')[1]||'',p=[];switch(t.behavior){case le.FLIP:p=[i,n];break;case le.CLOCKWISE:p=q(i);break;case le.COUNTERCLOCKWISE:p=q(i,!0);break;default:p=t.behavior;}return p.forEach(function(s,d){if(i!==s||p.length===d+1)return e;i=e.placement.split('-')[0],n=x(i);var a=e.offsets.popper,l=e.offsets.reference,f=X,m='left'===i&&f(a.right)>f(l.left)||'right'===i&&f(a.left)f(l.top)||'bottom'===i&&f(a.top)f(o.right),g=f(a.top)f(o.bottom),b='left'===i&&h||'right'===i&&c||'top'===i&&g||'bottom'===i&&u,w=-1!==['top','bottom'].indexOf(i),y=!!t.flipVariations&&(w&&'start'===r&&h||w&&'end'===r&&c||!w&&'start'===r&&g||!w&&'end'===r&&u);(m||b||y)&&(e.flipped=!0,(m||b)&&(i=p[d+1]),y&&(r=K(r)),e.placement=i+(r?'-'+r:''),e.offsets.popper=se({},e.offsets.popper,S(e.instance.popper,e.offsets.reference,e.placement)),e=C(e.instance.modifiers,e,'flip'))}),e},behavior:'flip',padding:5,boundariesElement:'viewport'},inner:{order:700,enabled:!1,fn:function(e){var t=e.placement,o=t.split('-')[0],i=e.offsets,n=i.popper,r=i.reference,p=-1!==['left','right'].indexOf(o),s=-1===['top','left'].indexOf(o);return n[p?'left':'top']=r[o]-(s?n[p?'width':'height']:0),e.placement=x(t),e.offsets.popper=c(n),e}},hide:{order:800,enabled:!0,fn:function(e){if(!F(e.instance.modifiers,'hide','preventOverflow'))return e;var t=e.offsets.reference,o=T(e.instance.modifiers,function(e){return'preventOverflow'===e.name}).boundaries;if(t.bottomo.right||t.top>o.bottom||t.right +