add DynamicEntity feature, corresponding doc not finished.

This commit is contained in:
shuang 2019-08-19 22:27:04 +08:00
parent fc67401a26
commit 18c3ae9c02
13 changed files with 925 additions and 64 deletions

View File

@ -371,6 +371,13 @@
<version>2.9.8</version>
</dependency>
<!-- convert word to plural -->
<dependency>
<groupId>org.atteo</groupId>
<artifactId>evo-inflector</artifactId>
<version>1.2.2</version>
</dependency>
</dependencies>
<build>

View File

@ -39,6 +39,8 @@ connector=mapped
#methodRouting.cache.ttl.seconds=30
## webui props cache time-to-live in seconds
#webui.props.cache.ttl.seconds=20
## DynamicEntity cache time-to-live in seconds
#dynamicEntity.cache.ttl.seconds=20
## enable logging all the database queries in log file
#logging.database.queries.enable=true

View File

@ -54,6 +54,7 @@ import code.customer.internalMapping.MappedCustomerIDMapping
import code.customer.{MappedCustomer, MappedCustomerMessage}
import code.customeraddress.MappedCustomerAddress
import code.database.authorisation.Authorisation
import code.dynamicEntity.DynamicEntity
import code.entitlement.MappedEntitlement
import code.entitlementrequest.MappedEntitlementRequest
import code.fx.{MappedCurrency, MappedFXRate}
@ -655,5 +656,6 @@ object ToSchemify {
MethodRouting,
WebUiProps,
Authorisation,
DynamicEntity,
)++ APIBuilder_Connector.allAPIBuilderModels
}

View File

@ -351,6 +351,18 @@ object ApiRole {
case class CanDeleteWebUiProps(requiresBankId: Boolean = false) extends ApiRole
lazy val canDeleteWebUiProps = CanDeleteWebUiProps()
case class CanGetDynamicEntities(requiresBankId: Boolean = false) extends ApiRole
lazy val canGetDynamicEntities = CanGetDynamicEntities()
case class CanCreateDynamicEntity(requiresBankId: Boolean = false) extends ApiRole
lazy val canCreateDynamicEntity = CanCreateDynamicEntity()
case class CanUpdateDynamicEntity(requiresBankId: Boolean = false) extends ApiRole
lazy val canUpdateDynamicEntity = CanUpdateDynamicEntity()
case class CanDeleteDynamicEntity(requiresBankId: Boolean = false) extends ApiRole
lazy val canDeleteDynamicEntity = CanDeleteDynamicEntity()
private val roles =
canSearchAllTransactions ::
canSearchAllAccounts ::
@ -390,7 +402,7 @@ object ApiRole {
canUpdateCardsForBank ::
canGetCardsForBank ::
canCreateBranch ::
canCreateBranchAtAnyBank ::
canCreateBranchAtAnyBank ::
canUpdateBranch ::
canCreateAtm ::
canCreateAtmAtAnyBank ::
@ -454,12 +466,16 @@ object ApiRole {
canGetMethodRoutings ::
canCreateMethodRouting ::
canUpdateMethodRouting ::
canDeleteMethodRouting ::
canDeleteMethodRouting ::
canUpdateCustomerNumber ::
canCreateHistoricalTransaction ::
canGetWebUiProps ::
canCreateWebUiProps ::
canDeleteWebUiProps ::
canGetDynamicEntities ::
canCreateDynamicEntity ::
canUpdateDynamicEntity ::
canDeleteDynamicEntity ::
Nil
lazy val rolesMappedToClasses = roles.map(_.getClass)

View File

@ -59,6 +59,7 @@ object ApiTag {
val apiTagConsent = ResourceDocTag("Consent")
val apiTagMethodRouting = ResourceDocTag("Method-Routing")
val apiTagWebUiProps = ResourceDocTag("WebUi-Props")
val apiTagDynamicEntity= ResourceDocTag("Dynamic-Entity")
// To mark the Berlin Group APIs suggested order of implementation
val apiTagBerlinGroupM = ResourceDocTag("Berlin-Group-M")

View File

@ -402,6 +402,9 @@ object ErrorMessages {
// WebUiProps Exceptions (OBP-8XXXX)
val InvalidWebUiProps = "OBP-80001: Incorrect format of name."
// DynamicEntity Exceptions (OBP-9XXXX)
val DynamicEntityNotFoundByDynamicEntityId = "OBP-90001: DynamicEntity not found. Please specify a valid value for dynamic_entity_id."
///////////

View File

@ -16,6 +16,7 @@ import code.bankconnectors.Connector
import code.branches.Branches.{Branch, DriveUpString, LobbyString}
import code.consumer.Consumers
import code.context.UserAuthContextUpdate
import code.dynamicEntity.{DynamicEntityProvider, DynamicEntityT}
import code.entitlement.Entitlement
import code.entitlementrequest.EntitlementRequest
import code.fx.{FXRate, MappedFXRate, fx}
@ -84,6 +85,7 @@ object NewStyle {
object function {
import scala.concurrent.ExecutionContext.Implicits.global
def getBranch(bankId : BankId, branchId : BranchId, callContext: Option[CallContext]): OBPReturnType[BranchT] = {
@ -1395,6 +1397,35 @@ object NewStyle {
}
}
}
def createOrUpdateDynamicEntity(dynamicEntity: DynamicEntityT): Future[Box[DynamicEntityT]] = Future {
DynamicEntityProvider.connectorMethodProvider.vend.createOrUpdate(dynamicEntity)
}
def deleteDynamicEntity(dynamicEntityId: String): Future[Box[Boolean]] = Future {
DynamicEntityProvider.connectorMethodProvider.vend.delete(dynamicEntityId)
}
def getDynamicEntityById(dynamicEntityId : String, callContext: Option[CallContext]): OBPReturnType[DynamicEntityT] = {
val dynamicEntityBox: Box[DynamicEntityT] = DynamicEntityProvider.connectorMethodProvider.vend.getById(dynamicEntityId)
val dynamicEntity = unboxFullOrFail(dynamicEntityBox, callContext, DynamicEntityNotFoundByDynamicEntityId)
Future{
(dynamicEntity, callContext)
}
}
private[this] val dynamicEntityTTL = APIUtil.getPropsValue(s"dynamicEntity.cache.ttl.seconds", "0").toInt
def getDynamicEntities(): 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()
}
}
}
def makeHistoricalPayment(
fromAccount: BankAccount,

View File

@ -3,13 +3,15 @@ package code.api.v4_0_0
import code.api.ChargePolicy
import code.api.ResourceDocs1_4_0.SwaggerDefinitionsJSON._
import code.api.util.APIUtil._
import code.api.util.ApiRole.canCreateAnyTransactionRequest
import code.api.util.ApiRole._
import code.api.util.ApiTag._
import code.api.util.ErrorMessages.{AccountNotFound, AllowedAttemptsUsedUp, BankNotFound, CounterpartyBeneficiaryPermit, InsufficientAuthorisationToCreateTransactionRequest, InvalidAccountIdFormat, InvalidBankIdFormat, InvalidChallengeAnswer, InvalidChallengeType, InvalidChargePolicy, InvalidISOCurrencyCode, InvalidJsonFormat, InvalidNumber, InvalidTransactionRequesChallengeId, InvalidTransactionRequestCurrency, InvalidTransactionRequestType, NotPositiveAmount, TransactionDisabled, TransactionRequestStatusNotInitiated, TransactionRequestTypeHasChanged, UnknownError, UserNoPermissionAccessView, UserNotLoggedIn, ViewNotFound}
import code.api.util.ErrorMessages.{AccountNotFound, AllowedAttemptsUsedUp, BankNotFound, CounterpartyBeneficiaryPermit, InsufficientAuthorisationToCreateTransactionRequest, InvalidAccountIdFormat, InvalidBankIdFormat, InvalidChallengeAnswer, InvalidChallengeType, InvalidChargePolicy, InvalidISOCurrencyCode, InvalidJsonFormat, InvalidNumber, InvalidTransactionRequesChallengeId, InvalidTransactionRequestCurrency, InvalidTransactionRequestType, NotPositiveAmount, TransactionDisabled, TransactionRequestStatusNotInitiated, TransactionRequestTypeHasChanged, UnknownError, UserHasMissingRoles, UserNoPermissionAccessView, UserNotLoggedIn, ViewNotFound}
import code.api.util.NewStyle.HttpCode
import code.api.util._
import code.api.v1_4_0.JSONFactory1_4_0.{ChallengeAnswerJSON, TransactionRequestAccountJsonV140}
import code.api.v2_1_0._
import code.api.v3_1_0.ListResult
import code.dynamicEntity.DynamicEntityCommons
import code.model.toUserExtended
import code.transactionrequests.TransactionRequests.TransactionChallengeTypes._
import code.transactionrequests.TransactionRequests.TransactionRequestTypes
@ -17,15 +19,17 @@ import code.transactionrequests.TransactionRequests.TransactionRequestTypes.{app
import code.util.Helper
import com.github.dwickern.macros.NameOf.nameOf
import com.openbankproject.commons.model._
import net.liftweb.common.Full
import net.liftweb.common.{Box, Full}
import net.liftweb.http.rest.RestHelper
import net.liftweb.json.Serialization.write
import net.liftweb.json._
import net.liftweb.util.Props
import net.liftweb.util.StringHelpers
import org.atteo.evo.inflector.English
import scala.collection.immutable.{List, Nil}
import scala.collection.mutable.ArrayBuffer
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.Future
trait APIMethods400 {
self: RestHelper =>
@ -60,76 +64,74 @@ trait APIMethods400 {
List(UnknownError),
Catalogs(Core, PSD2, OBWG),
apiTagBank :: apiTagPSD2AIS :: apiTagNewStyle :: Nil)
lazy val getBanks : OBPEndpoint = {
lazy val getBanks: OBPEndpoint = {
case "banks" :: Nil JsonGet _ => {
cc =>
for {
(_, callContext) <- anonymousAccess(cc)
(banks, callContext) <- NewStyle.function.getBanks(callContext)
} yield{
} yield {
(JSONFactory400.createBanksJson(banks), HttpCode.`200`(callContext))
}
}
}
val exchangeRates =
val exchangeRates =
APIUtil.getPropsValue("webui_api_explorer_url", "") +
"/more?version=OBPv4.0.0&list-all-banks=false&core=&psd2=&obwg=#OBPv2_2_0-getCurrentFxRate"
"/more?version=OBPv4.0.0&list-all-banks=false&core=&psd2=&obwg=#OBPv2_2_0-getCurrentFxRate"
// This text is used in the various Create Transaction Request resource docs
val transactionRequestGeneralText =
s"""Initiate a Payment via creating a Transaction Request.
|
|In OBP, a `transaction request` may or may not result in a `transaction`. However, a `transaction` only has one possible state: completed.
|In OBP, a `transaction request` may or may not result in a `transaction`. However, a `transaction` only has one possible state: completed.
|
|A `Transaction Request` can have one of several states.
|A `Transaction Request` can have one of several states.
|
|`Transactions` are modeled on items in a bank statement that represent the movement of money.
|`Transactions` are modeled on items in a bank statement that represent the movement of money.
|
|`Transaction Requests` are requests to move money which may or may not succeeed and thus result in a `Transaction`.
|`Transaction Requests` are requests to move money which may or may not succeeed and thus result in a `Transaction`.
|
|A `Transaction Request` might create a security challenge that needs to be answered before the `Transaction Request` proceeds.
|A `Transaction Request` might create a security challenge that needs to be answered before the `Transaction Request` proceeds.
|
|Transaction Requests contain charge information giving the client the opportunity to proceed or not (as long as the challenge level is appropriate).
|Transaction Requests contain charge information giving the client the opportunity to proceed or not (as long as the challenge level is appropriate).
|
|Transaction Requests can have one of several Transaction Request Types which expect different bodies. The escaped body is returned in the details key of the GET response.
|Transaction Requests can have one of several Transaction Request Types which expect different bodies. The escaped body is returned in the details key of the GET response.
|This provides some commonality and one URL for many different payment or transfer types with enough flexibility to validate them differently.
|
|The payer is set in the URL. Money comes out of the BANK_ID and ACCOUNT_ID specified in the URL.
|The payer is set in the URL. Money comes out of the BANK_ID and ACCOUNT_ID specified in the URL.
|
|In sandbox mode, TRANSACTION_REQUEST_TYPE is commonly set to ACCOUNT. See getTransactionRequestTypesSupportedByBank for all supported types.
|In sandbox mode, TRANSACTION_REQUEST_TYPE is commonly set to ACCOUNT. See getTransactionRequestTypesSupportedByBank for all supported types.
|
|In sandbox mode, if the amount is less than 1000 EUR (any currency, unless it is set differently on this server), the transaction request will create a transaction without a challenge, else the Transaction Request will be set to INITIALISED and a challenge will need to be answered.
|In sandbox mode, if the amount is less than 1000 EUR (any currency, unless it is set differently on this server), the transaction request will create a transaction without a challenge, else the Transaction Request will be set to INITIALISED and a challenge will need to be answered.
|
|If a challenge is created you must answer it using Answer Transaction Request Challenge before the Transaction is created.
|If a challenge is created you must answer it using Answer Transaction Request Challenge before the Transaction is created.
|
|You can transfer between different currency accounts. (new in 2.0.0). The currency in body must match the sending account.
|You can transfer between different currency accounts. (new in 2.0.0). The currency in body must match the sending account.
|
|The following static FX rates are available in sandbox mode:
|The following static FX rates are available in sandbox mode:
|
|${exchangeRates}
|${exchangeRates}
|
|
|Transaction Requests satisfy PSD2 requirements thus:
|
|1) A transaction can be initiated by a third party application.
|Transaction Requests satisfy PSD2 requirements thus:
|
|2) The customer is informed of the charge that will incurred.
|1) A transaction can be initiated by a third party application.
|
|3) The call supports delegated authentication (OAuth)
|2) The customer is informed of the charge that will incurred.
|
|See [this python code](https://github.com/OpenBankProject/Hello-OBP-DirectLogin-Python/blob/master/hello_payments.py) for a complete example of this flow.
|3) The call supports delegated authentication (OAuth)
|
|There is further documentation [here](https://github.com/OpenBankProject/OBP-API/wiki/Transaction-Requests)
|See [this python code](https://github.com/OpenBankProject/Hello-OBP-DirectLogin-Python/blob/master/hello_payments.py) for a complete example of this flow.
|
|${authenticationRequiredMessage(true)}
|There is further documentation [here](https://github.com/OpenBankProject/OBP-API/wiki/Transaction-Requests)
|
|"""
|${authenticationRequiredMessage(true)}
|
|"""
// ACCOUNT. (we no longer create a resource doc for the general case)
@ -170,7 +172,7 @@ trait APIMethods400 {
),
Catalogs(Core, PSD2, OBWG),
List(apiTagTransactionRequest, apiTagPSD2PIS, apiTagNewStyle))
// ACCOUNT_OTP. (we no longer create a resource doc for the general case)
resourceDocs += ResourceDoc(
createTransactionRequestAccountOtp,
@ -252,7 +254,7 @@ trait APIMethods400 {
List(apiTagTransactionRequest, apiTagPSD2PIS, apiTagNewStyle))
val lowAmount = AmountOfMoneyJsonV121("EUR", "12.50")
val lowAmount = AmountOfMoneyJsonV121("EUR", "12.50")
val sharedChargePolicy = ChargePolicy.withName("SHARED")
// Transaction Request (SEPA)
@ -334,8 +336,6 @@ trait APIMethods400 {
Some(List(canCreateAnyTransactionRequest)))
// Different Transaction Request approaches:
lazy val createTransactionRequestAccount = createTransactionRequest
lazy val createTransactionRequestAccountOtp = createTransactionRequest
@ -351,14 +351,18 @@ trait APIMethods400 {
for {
(Full(u), callContext) <- authorizedAccess(cc)
_ <- NewStyle.function.isEnabledTransactionRequests()
_ <- Helper.booleanToFuture(InvalidAccountIdFormat) {isValidID(accountId.value)}
_ <- Helper.booleanToFuture(InvalidBankIdFormat) {isValidID(bankId.value)}
_ <- Helper.booleanToFuture(InvalidAccountIdFormat) {
isValidID(accountId.value)
}
_ <- Helper.booleanToFuture(InvalidBankIdFormat) {
isValidID(bankId.value)
}
(_, callContext) <- NewStyle.function.getBank(bankId, callContext)
(fromAccount, callContext) <- NewStyle.function.checkBankAccountExists(bankId, accountId, callContext)
_ <- NewStyle.function.view(viewId, BankIdAccountId(fromAccount.bankId, fromAccount.accountId), callContext)
_ <- Helper.booleanToFuture(InsufficientAuthorisationToCreateTransactionRequest) {
u.hasOwnerViewAccess(BankIdAccountId(fromAccount.bankId,fromAccount.accountId)) == true ||
u.hasOwnerViewAccess(BankIdAccountId(fromAccount.bankId, fromAccount.accountId)) == true ||
hasEntitlement(fromAccount.bankId.value, u.userId, ApiRole.canCreateAnyTransactionRequest) == true
}
@ -393,7 +397,7 @@ trait APIMethods400 {
transDetailsJson.value.currency == fromAccount.currency
}
(createdTransactionRequest,callContext) <- TransactionRequestTypes.withName(transactionRequestType.value) match {
(createdTransactionRequest, callContext) <- TransactionRequestTypes.withName(transactionRequestType.value) match {
case ACCOUNT | SANDBOX_TAN => {
for {
transactionRequestBodySandboxTan <- NewStyle.function.tryons(s"${InvalidJsonFormat}, it should be $ACCOUNT json format", 400, callContext) {
@ -404,7 +408,9 @@ trait APIMethods400 {
toAccountId = AccountId(transactionRequestBodySandboxTan.to.account_id)
(toAccount, callContext) <- NewStyle.function.checkBankAccountExists(toBankId, toAccountId, callContext)
transDetailsSerialized <- NewStyle.function.tryons (UnknownError, 400, callContext){write(transactionRequestBodySandboxTan)(Serialization.formats(NoTypeHints))}
transDetailsSerialized <- NewStyle.function.tryons(UnknownError, 400, callContext) {
write(transactionRequestBodySandboxTan)(Serialization.formats(NoTypeHints))
}
(createdTransactionRequest, callContext) <- NewStyle.function.createTransactionRequestv210(u,
viewId,
@ -429,7 +435,9 @@ trait APIMethods400 {
toAccountId = AccountId(transactionRequestBodySandboxTan.to.account_id)
(toAccount, callContext) <- NewStyle.function.checkBankAccountExists(toBankId, toAccountId, callContext)
transDetailsSerialized <- NewStyle.function.tryons (UnknownError, 400, callContext){write(transactionRequestBodySandboxTan)(Serialization.formats(NoTypeHints))}
transDetailsSerialized <- NewStyle.function.tryons(UnknownError, 400, callContext) {
write(transactionRequestBodySandboxTan)(Serialization.formats(NoTypeHints))
}
(createdTransactionRequest, callContext) <- NewStyle.function.createTransactionRequestv210(u,
viewId,
@ -453,7 +461,7 @@ trait APIMethods400 {
toCounterpartyId = transactionRequestBodyCounterparty.to.counterparty_id
(toCounterparty, callContext) <- NewStyle.function.getCounterpartyByCounterpartyId(CounterpartyId(toCounterpartyId), callContext)
toAccount <- NewStyle.function.toBankAccount(toCounterparty, callContext)
// Check we can send money to it.
// Check we can send money to it.
_ <- Helper.booleanToFuture(s"$CounterpartyBeneficiaryPermit") {
toCounterparty.isBeneficiary == true
}
@ -461,7 +469,9 @@ trait APIMethods400 {
_ <- Helper.booleanToFuture(s"$InvalidChargePolicy") {
ChargePolicy.values.contains(ChargePolicy.withName(chargePolicy))
}
transDetailsSerialized <- NewStyle.function.tryons (UnknownError, 400, callContext){write(transactionRequestBodyCounterparty)(Serialization.formats(NoTypeHints))}
transDetailsSerialized <- NewStyle.function.tryons(UnknownError, 400, callContext) {
write(transactionRequestBodyCounterparty)(Serialization.formats(NoTypeHints))
}
(createdTransactionRequest, callContext) <- NewStyle.function.createTransactionRequestv210(u,
viewId,
fromAccount,
@ -492,7 +502,9 @@ trait APIMethods400 {
_ <- Helper.booleanToFuture(s"$InvalidChargePolicy") {
ChargePolicy.values.contains(ChargePolicy.withName(chargePolicy))
}
transDetailsSerialized <- NewStyle.function.tryons (UnknownError, 400, callContext){write(transDetailsSEPAJson)(Serialization.formats(NoTypeHints))}
transDetailsSerialized <- NewStyle.function.tryons(UnknownError, 400, callContext) {
write(transDetailsSEPAJson)(Serialization.formats(NoTypeHints))
}
(createdTransactionRequest, callContext) <- NewStyle.function.createTransactionRequestv210(u,
viewId,
fromAccount,
@ -513,7 +525,9 @@ trait APIMethods400 {
}
// Following lines: just transfer the details body, add Bank_Id and Account_Id in the Detail part. This is for persistence and 'answerTransactionRequestChallenge'
transactionRequestAccountJSON = TransactionRequestAccountJsonV140(fromAccount.bankId.value, fromAccount.accountId.value)
transDetailsSerialized <- NewStyle.function.tryons (UnknownError, 400, callContext){write(transactionRequestBodyFreeForm)(Serialization.formats(NoTypeHints))}
transDetailsSerialized <- NewStyle.function.tryons(UnknownError, 400, callContext) {
write(transactionRequestBodyFreeForm)(Serialization.formats(NoTypeHints))
}
(createdTransactionRequest, callContext) <- NewStyle.function.createTransactionRequestv210(u,
viewId,
fromAccount,
@ -547,11 +561,11 @@ trait APIMethods400 {
|
|This endpoint totally depends on createTransactionRequest, it need get the following data from createTransactionRequest response body.
|
|1)`TRANSACTION_REQUEST_TYPE` : is the same as createTransactionRequest request URL .
|1)`TRANSACTION_REQUEST_TYPE` : is the same as createTransactionRequest request URL .
|
|2)`TRANSACTION_REQUEST_ID` : is the `id` field in createTransactionRequest response body.
|
|3) `id` : is `challenge.id` field in createTransactionRequest response body.
|3) `id` : is `challenge.id` field in createTransactionRequest response body.
|
|4) `answer` : must be `123`. if it is in sandbox mode. If it kafka mode, the answer can be got by phone message or other security ways.
|
@ -583,8 +597,12 @@ trait APIMethods400 {
// Check we have a User
(Full(u), callContext) <- authorizedAccess(cc)
_ <- NewStyle.function.isEnabledTransactionRequests()
_ <- Helper.booleanToFuture(InvalidAccountIdFormat) {isValidID(accountId.value)}
_ <- Helper.booleanToFuture(InvalidBankIdFormat) {isValidID(bankId.value)}
_ <- Helper.booleanToFuture(InvalidAccountIdFormat) {
isValidID(accountId.value)
}
_ <- Helper.booleanToFuture(InvalidBankIdFormat) {
isValidID(bankId.value)
}
challengeAnswerJson <- NewStyle.function.tryons(s"$InvalidJsonFormat The Json body should be the $ChallengeAnswerJSON ", 400, callContext) {
json.extract[ChallengeAnswerJSON]
}
@ -594,7 +612,7 @@ trait APIMethods400 {
_ <- NewStyle.function.view(viewId, BankIdAccountId(fromAccount.bankId, fromAccount.accountId), callContext)
_ <- Helper.booleanToFuture(InsufficientAuthorisationToCreateTransactionRequest) {
u.hasOwnerViewAccess(BankIdAccountId(fromAccount.bankId,fromAccount.accountId)) == true ||
u.hasOwnerViewAccess(BankIdAccountId(fromAccount.bankId, fromAccount.accountId)) == true ||
hasEntitlement(fromAccount.bankId.value, u.userId, ApiRole.canCreateAnyTransactionRequest) == true
}
@ -627,7 +645,7 @@ trait APIMethods400 {
List(
OTP_VIA_API.toString,
OTP_VIA_WEB_FORM.toString
).exists(_ == existingTransactionRequest.challenge.challenge_type)
).exists(_ == existingTransactionRequest.challenge.challenge_type)
}
challengeAnswerOBP <- NewStyle.function.validateChallengeAnswerInOBPSide(challengeAnswerJson.id, challengeAnswerJson.answer, callContext)
@ -644,7 +662,7 @@ trait APIMethods400 {
// All Good, proceed with the Transaction creation...
(transactionRequest, callContext) <- TransactionRequestTypes.withName(transactionRequestType.value) match {
case TRANSFER_TO_PHONE | TRANSFER_TO_ATM | TRANSFER_TO_ACCOUNT=>
case TRANSFER_TO_PHONE | TRANSFER_TO_ATM | TRANSFER_TO_ACCOUNT =>
NewStyle.function.createTransactionAfterChallengeV300(u, fromAccount, transReqId, transactionRequestType, callContext)
case _ =>
NewStyle.function.createTransactionAfterChallengeV210(fromAccount, existingTransactionRequest, callContext)
@ -655,8 +673,361 @@ trait APIMethods400 {
}
}
}
resourceDocs += ResourceDoc(
getDynamicEntities,
implementedInApiVersion,
nameOf(getDynamicEntities),
"GET",
"/management/dynamic_entities",
"Get DynamicEntities",
s"""Get the all DynamicEntities.""",
emptyObjectJson,
ListResult(
"dynamic_entities",
(List(DynamicEntityCommons(entityName = "FooBar", metadataJson =
"""
|{
| "definitions": {
| "FooBar": {
| "required": [
| "name"
| ],
| "properties": {
| "name": {
| "type": "string",
| "example": "James Brown"
| },
| "number": {
| "type": "integer",
| "example": "698761728934"
| }
| }
| }
| }
|}
|""".stripMargin)))
)
,
List(
UserNotLoggedIn,
UserHasMissingRoles,
UnknownError
),
Catalogs(notCore, notPSD2, notOBWG),
List(apiTagDynamicEntity, apiTagApi, apiTagNewStyle),
Some(List(canGetDynamicEntities))
)
lazy val getDynamicEntities: OBPEndpoint = {
case "management" :: "dynamic_entities" :: Nil JsonGet req => {
cc =>
for {
(Full(u), callContext) <- authorizedAccess(cc)
_ <- NewStyle.function.hasEntitlement("", u.userId, ApiRole.canGetDynamicEntities, callContext)
dynamicEntities <- Future(NewStyle.function.getDynamicEntities())
} yield {
val listCommons: List[DynamicEntityCommons] = dynamicEntities
(ListResult("dynamic_entities", listCommons), HttpCode.`200`(callContext))
}
}
}
private def validateDynamicEntityJson(data: DynamicEntityCommons) = {
val metadataJson = net.liftweb.json.parse(data.metadataJson)
val rqs = (metadataJson \ "definitions" \ data.entityName \ "required").extract[Array[String]]
val propertiesFields = (metadataJson \ "definitions" \ data.entityName \ "properties").asInstanceOf[JObject].values
require(rqs.toSet.diff(propertiesFields.keySet).isEmpty)
propertiesFields.values.foreach(pair => {
val map = pair.asInstanceOf[Map[String, _]]
require(map("type").isInstanceOf[String])
require(map("example") != null)
})
}
resourceDocs += ResourceDoc(
createDynamicEntity,
implementedInApiVersion,
nameOf(createDynamicEntity),
"POST",
"/management/dynamic_entities",
"Add DynamicEntity",
s"""Add a DynamicEntity.
|
|
|${authenticationRequiredMessage(true)}
|
|Explaination of Fields:
|
|* method_name is required String value
|* connector_name is required String value
|* is_bank_id_exact_match is required boolean value, if bank_id_pattern is exact bank_id value, this value is true; if bank_id_pattern is null or a regex, this value is false
|* bank_id_pattern is optional String value, it can be null, a exact bank_id or a regex
|* parameters is optional array of key value pairs. You can set some paremeters for this method
|
|note:
|
|* if bank_id_pattern is regex, special characters need to do escape, for example: bank_id_pattern = "some\\-id_pattern_\\d+"
|""",
DynamicEntityCommons(entityName = "FooBar", metadataJson =
"""
|{
| "definitions": {
| "FooBar": {
| "required": [
| "name"
| ],
| "properties": {
| "name": {
| "type": "string",
| "example": "James Brown"
| },
| "number": {
| "type": "integer",
| "example": "698761728934"
| }
| }
| }
| }
|}
|""".stripMargin),
DynamicEntityCommons(entityName = "FooBar", metadataJson =
"""
|{
| "definitions": {
| "FooBar": {
| "required": [
| "name"
| ],
| "properties": {
| "name": {
| "type": "string",
| "example": "James Brown"
| },
| "number": {
| "type": "integer",
| "example": "698761728934"
| }
| }
| }
| }
|}
|""".stripMargin, dynamicEntityId = Some("dynamic-entity-id")),
List(
UserNotLoggedIn,
UserHasMissingRoles,
InvalidJsonFormat,
UnknownError
),
Catalogs(notCore, notPSD2, notOBWG),
List(apiTagDynamicEntity, apiTagApi, apiTagNewStyle),
Some(List(canCreateDynamicEntity)))
lazy val createDynamicEntity: OBPEndpoint = {
case "management" :: "dynamic_entities" :: Nil JsonPost json -> _ => {
cc =>
for {
(Full(u), callContext) <- authorizedAccess(cc)
_ <- NewStyle.function.hasEntitlement("", u.userId, canCreateDynamicEntity, callContext)
failMsg = s"$InvalidJsonFormat The Json body should be the ${classOf[DynamicEntityCommons]}, and metadataJson should be the same structure as document example."
postedData <- NewStyle.function.tryons(failMsg, 400, callContext) {
val data = json.extract[DynamicEntityCommons]
validateDynamicEntityJson(data)
data
}
Full(dynamicEntity) <- NewStyle.function.createOrUpdateDynamicEntity(postedData)
} yield {
val commonsData: DynamicEntityCommons = dynamicEntity
(commonsData, HttpCode.`201`(callContext))
}
}
}
resourceDocs += ResourceDoc(
updateDynamicEntity,
implementedInApiVersion,
nameOf(updateDynamicEntity),
"PUT",
"/management/dynamic_entities/DYNAMIC_ENTITY_ID",
"Update DynamicEntity",
s"""Update a DynamicEntity.
|
|
|${authenticationRequiredMessage(true)}
|
|Explanations of Fields:
|
|* method_name is required String value
|* connector_name is required String value
|* is_bank_id_exact_match is required boolean value, if bank_id_pattern is exact bank_id value, this value is true; if bank_id_pattern is null or a regex, this value is false
|* bank_id_pattern is optional String value, it can be null, a exact bank_id or a regex
|* parameters is optional array of key value pairs. You can set some paremeters for this method
|note:
|
|* if bank_id_pattern is regex, special characters need to do escape, for example: bank_id_pattern = "some\\-id_pattern_\\d+"
|""",
DynamicEntityCommons(entityName = "FooBar", metadataJson =
"""
|{
| "definitions": {
| "FooBar": {
| "required": [
| "name"
| ],
| "properties": {
| "name": {
| "type": "string",
| "example": "James Brown"
| },
| "number": {
| "type": "integer",
| "example": "698761728934"
| }
| }
| }
| }
|}
|""".stripMargin),
DynamicEntityCommons(entityName = "FooBar", metadataJson =
"""
|{
| "definitions": {
| "FooBar": {
| "required": [
| "name"
| ],
| "properties": {
| "name": {
| "type": "string",
| "example": "James Brown"
| },
| "number": {
| "type": "integer",
| "example": "698761728934"
| }
| }
| }
| }
|}
|""".stripMargin, dynamicEntityId = Some("dynamic-entity-id")),
List(
UserNotLoggedIn,
UserHasMissingRoles,
InvalidJsonFormat,
UnknownError
),
Catalogs(notCore, notPSD2, notOBWG),
List(apiTagDynamicEntity, apiTagApi, apiTagNewStyle),
Some(List(canUpdateDynamicEntity)))
lazy val updateDynamicEntity: OBPEndpoint = {
case "management" :: "dynamic_entities" :: dynamicEntityId :: Nil JsonPut json -> _ => {
cc =>
for {
(Full(u), callContext) <- authorizedAccess(cc)
_ <- NewStyle.function.hasEntitlement("", u.userId, canUpdateDynamicEntity, callContext)
failMsg = s"$InvalidJsonFormat The Json body should be the ${classOf[DynamicEntityCommons]}, and metadataJson should be the same structure as document example."
putData <- NewStyle.function.tryons(failMsg, 400, callContext) {
val data = json.extract[DynamicEntityCommons].copy(dynamicEntityId = Some(dynamicEntityId))
validateDynamicEntityJson(data)
data
}
(_, _) <- NewStyle.function.getDynamicEntityById(dynamicEntityId, callContext)
Full(dynamicEntity) <- NewStyle.function.createOrUpdateDynamicEntity(putData)
} yield {
val commonsData: DynamicEntityCommons = dynamicEntity
(commonsData, HttpCode.`200`(callContext))
}
}
}
resourceDocs += ResourceDoc(
deleteDynamicEntity,
implementedInApiVersion,
nameOf(deleteDynamicEntity),
"DELETE",
"/management/dynamic_entities/DYNAMIC_ENTITY_ID",
"Delete DynamicEntity",
s"""Delete a DynamicEntity specified by DYNAMIC_ENTITY_ID.
|
|
|${authenticationRequiredMessage(true)}
|
|""",
emptyObjectJson,
emptyObjectJson,
List(
UserNotLoggedIn,
UserHasMissingRoles,
UnknownError
),
Catalogs(notCore, notPSD2, notOBWG),
List(apiTagDynamicEntity, apiTagApi, apiTagNewStyle),
Some(List(canDeleteDynamicEntity)))
lazy val deleteDynamicEntity: OBPEndpoint = {
case "management" :: "dynamic_entities" :: dynamicEntityId :: Nil JsonDelete _ => {
cc =>
for {
(Full(u), callContext) <- authorizedAccess(cc)
_ <- NewStyle.function.hasEntitlement("", u.userId, canDeleteDynamicEntity, callContext)
deleted: Box[Boolean] <- NewStyle.function.deleteDynamicEntity(dynamicEntityId)
} yield {
(deleted, HttpCode.`200`(callContext))
}
}
}
lazy val genericEndpoint: OBPEndpoint = {
case EntityName(entityName) :: Nil JsonGet req => {
cc =>
Future {
import net.liftweb.json.JsonDSL._
val listName = StringHelpers.snakify(English.plural(entityName))
val resultList = MockerConnector.getAll(entityName)
val jValue: JValue = listName -> resultList
(jValue, HttpCode.`200`(Some(cc)))
}
}
case EntityName(entityName, id) JsonGet req => {
cc =>
Future {
(MockerConnector.getSingle(entityName, id), HttpCode.`200`(Some(cc)))
}
}
case EntityName(entityName) :: Nil JsonPost json -> _ => {
cc =>
Future {
(MockerConnector.persist(entityName, json.asInstanceOf[JObject]), HttpCode.`201`(Some(cc)))
}
}
case EntityName(entityName, id) JsonPut json -> _ => {
cc =>
Future {
(MockerConnector.persist(entityName, json.asInstanceOf[JObject], Some(id)), HttpCode.`200`(Some(cc)))
}
}
case EntityName(entityName, id) JsonDelete req => {
cc =>
Future {
(MockerConnector.delete(entityName, id), HttpCode.`200`(Some(cc)))
}
}
}
}
}
object APIMethods400 extends RestHelper with APIMethods400 {

View File

@ -0,0 +1,94 @@
package code.api.v4_0_0
import code.api.util.APIUtil.generateUUID
import code.api.util.ErrorMessages.{InvalidJsonFormat, InvalidUrl}
import code.api.util.NewStyle
import net.liftweb.common.Box
import net.liftweb.json._
import scala.collection.immutable.{List, Nil}
object EntityName {
def unapply(entityName: String): Option[String] = MockerConnector.definitionsMap.keySet.find(entityName ==)
def unapply(url: List[String]): Option[(String, String)] = url match {
case entityName :: id :: Nil => MockerConnector.definitionsMap.keySet.find(entityName ==).map((_, id))
case _ => None
}
}
object MockerConnector {
lazy val definitionsMap = NewStyle.function.getDynamicEntities().map(it => (it.entityName, DynamicEntityInfo(it.metadataJson, it.entityName))).toMap
val persistedEntities = scala.collection.mutable.Map[String, (String, JObject)]()
def persist(entityName: String, requestBody: JObject, id: Option[String] = None) = {
val idValue = id.orElse(Some(generateUUID()))
val entityToPersist = this.definitionsMap(entityName).toReponse(requestBody, id)
val haveIdEntity = (entityToPersist \ "id") match {
case JNothing => JObject(JField("id", JString(idValue.get)) :: entityToPersist.obj)
case _ => entityToPersist
}
persistedEntities.put(idValue.get, (entityName, haveIdEntity))
haveIdEntity
}
def getSingle(entityName: String, id: String) = {
persistedEntities.find(pair => pair._1 == id && pair._2._1 == entityName).map(_._2._2).getOrElse(throw new RuntimeException(s"$InvalidUrl not exists entity of id = $id"))
}
def getAll(entityName: String) = persistedEntities.values.filter(_._1 == entityName).map(_._2)
def delete(entityName: String, id: String): Box[Boolean] = persistedEntities.exists(it => it._1 == id && it._2._1 == entityName) match {
case true => persistedEntities.remove(id).map(_ => true)
case false => Some(false)
}
}
case class DynamicEntityInfo(defination: String, entityName: String) {
import net.liftweb.json
val subEntities: List[DynamicEntityInfo] = Nil
val jsonTypeMap = Map[String, Class[_]](
("boolean", classOf[JBool]),
("string", classOf[JString]),
("array", classOf[JArray]),
("integer", classOf[JInt]),
("number", classOf[JDouble]),
)
val definationJson = json.parse(defination).asInstanceOf[JObject]
val entity = (definationJson \ "definitions" \ entityName).asInstanceOf[JObject]
def toReponse(result: JObject, id: Option[String]): JObject = {
val fieldNameToTypeName: Map[String, String] = (entity \ "properties")
.asInstanceOf[JObject]
.obj
.map(field => (field.name, (field.value \ "type").asInstanceOf[JString].s))
.toMap
val fieldNameToType: Map[String, Class[_]] = fieldNameToTypeName
.mapValues(jsonTypeMap(_))
val requiredFieldNames: Set[String] = (entity \ "required").asInstanceOf[JArray].arr.map(_.asInstanceOf[JString].s).toSet
val fields = result.obj.filter(it => fieldNameToType.keySet.contains(it.name))
def check(v: Boolean, msg: String) = if (!v) throw new RuntimeException(msg)
// if there are field type are not match the definitions, there must be bug.
fields.foreach(it => check(fieldNameToType(it.name).isInstance(it.value), s"""$InvalidJsonFormat "${it.name}" required type is "${fieldNameToTypeName(it.name)}"."""))
// if there are required field not presented, must be some bug.
requiredFieldNames.foreach(it => check(fields.exists(_.name == it), s"""$InvalidJsonFormat required field "$it" not presented."""))
(id, fields.exists(_.name == "id")) match {
case (Some(idValue), false) => JObject(JField("id", JString(idValue)) :: fields)
case _ => JObject(fields)
}
}
}

View File

@ -29,7 +29,7 @@ package code.api.v4_0_0
import code.api.OBPRestHelper
import code.api.util.APIUtil.{OBPEndpoint, ResourceDoc, getAllowedEndpoints}
import code.api.util.{ApiVersion, VersionedOBPApis}
import code.api.v1_3_0.{APIMethods130}
import code.api.v1_3_0.APIMethods130
import code.api.v1_4_0.APIMethods140
import code.api.v2_0_0.APIMethods200
import code.api.v2_1_0.APIMethods210
@ -385,7 +385,11 @@ object OBPAPI4_0_0 extends OBPRestHelper with APIMethods130 with APIMethods140 w
val endpointsOf4_0_0 =
Implementations4_0_0.getBanks ::
Implementations4_0_0.createTransactionRequest ::
Implementations4_0_0.answerTransactionRequestChallenge ::
Implementations4_0_0.answerTransactionRequestChallenge ::
Implementations4_0_0.getDynamicEntities ::
Implementations4_0_0.createDynamicEntity ::
Implementations4_0_0.updateDynamicEntity ::
Implementations4_0_0.deleteDynamicEntity ::
Nil
val allResourceDocs = Implementations4_0_0.resourceDocs ++
@ -398,7 +402,7 @@ object OBPAPI4_0_0 extends OBPRestHelper with APIMethods130 with APIMethods140 w
Implementations1_4_0.resourceDocs ++
Implementations1_3_0.resourceDocs ++
Implementations1_2_1.resourceDocs
def findResourceDoc(pf: OBPEndpoint): Option[ResourceDoc] = {
allResourceDocs.find(_.partialFunction==pf)
}
@ -422,7 +426,7 @@ object OBPAPI4_0_0 extends OBPRestHelper with APIMethods130 with APIMethods140 w
routes.foreach(route => {
oauthServe(apiPrefix{route}, findResourceDoc(route))
})
oauthServe(apiPrefix{Implementations4_0_0.genericEndpoint}, None)
logger.info(s"version $version has been run! There are ${routes.length} routes.")
}

View File

@ -0,0 +1,42 @@
package code.dynamicEntity
import com.openbankproject.commons.model.{Converter, JsonFieldReName}
import net.liftweb.common.Box
import net.liftweb.util.SimpleInjector
object DynamicEntityProvider extends SimpleInjector {
val connectorMethodProvider = new Inject(buildOne _) {}
def buildOne: MappedDynamicEntityProvider.type = MappedDynamicEntityProvider
}
trait DynamicEntityT {
def dynamicEntityId: Option[String]
def entityName: String
def metadataJson: String
}
case class DynamicEntityCommons(entityName: String,
metadataJson: String,
dynamicEntityId: Option[String] = None,
) extends DynamicEntityT with JsonFieldReName
object DynamicEntityCommons extends Converter[DynamicEntityT, DynamicEntityCommons]
trait DynamicEntityProvider {
def getById(dynamicEntityId: String): Box[DynamicEntityT]
def getDynamicEntities(): List[DynamicEntityT]
def createOrUpdate(dynamicEntity: DynamicEntityT): Box[DynamicEntityT]
def delete(dynamicEntityId: String):Box[Boolean]
}

View File

@ -0,0 +1,64 @@
package code.dynamicEntity
import code.api.util.CustomJsonFormats
import code.util.MappedUUID
import net.liftweb.common.{Box, Empty, EmptyBox, Full}
import net.liftweb.mapper._
import net.liftweb.util.Helpers.tryo
import org.apache.commons.lang3.StringUtils
object MappedDynamicEntityProvider extends DynamicEntityProvider with CustomJsonFormats{
override def getById(dynamicEntityId: String): Box[DynamicEntityT] = DynamicEntity.find(
By(DynamicEntity.DynamicEntityId, dynamicEntityId)
)
override def getDynamicEntities(): List[DynamicEntity] = {
DynamicEntity.findAll()
}
override def createOrUpdate(dynamicEntity: DynamicEntityT): Box[DynamicEntityT] = {
//to find exists dynamicEntity, if dynamicEntityId supplied, query by dynamicEntityId, or use entityName and dynamicEntityId to do query
val existsDynamicEntity: Box[DynamicEntity] = dynamicEntity.dynamicEntityId match {
case Some(id) if (StringUtils.isNotBlank(id)) => getByDynamicEntityId(id)
case _ => Empty
}
val entityToPersist = existsDynamicEntity match {
case _: EmptyBox => DynamicEntity.create
case Full(dynamicEntity) => dynamicEntity
}
tryo{
entityToPersist
.EntityName(dynamicEntity.entityName)
.MetadataJson(dynamicEntity.metadataJson)
.saveMe()
}
}
override def delete(dynamicEntityId: String): Box[Boolean] = getByDynamicEntityId(dynamicEntityId).map(_.delete_!)
private[this] def getByDynamicEntityId(dynamicEntityId: String): Box[DynamicEntity] = DynamicEntity.find(By(DynamicEntity.DynamicEntityId, dynamicEntityId))
}
class DynamicEntity extends DynamicEntityT with LongKeyedMapper[DynamicEntity] with IdPK with CustomJsonFormats{
override def getSingleton = DynamicEntity
object DynamicEntityId extends MappedUUID(this)
object EntityName extends MappedString(this, 255)
object MetadataJson extends MappedText(this)
override def dynamicEntityId: Option[String] = Option(DynamicEntityId.get)
override def entityName: String = EntityName.get
override def metadataJson: String = MetadataJson.get
}
object DynamicEntity extends DynamicEntity with LongKeyedMetaMapper[DynamicEntity] {
override def dbIndexes = UniqueIndex(DynamicEntityId) :: UniqueIndex(MetadataJson) :: super.dbIndexes
}

View File

@ -0,0 +1,224 @@
/**
Open Bank Project - API
Copyright (C) 2011-2018, TESOBE Ltd
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Email: contact@tesobe.com
TESOBE Ltd
Osloerstrasse 16/17
Berlin 13359, Germany
This product includes software developed at
TESOBE (http://www.tesobe.com/)
*/
package code.api.v4_0_0
import code.api.ErrorMessage
import code.api.util.ApiRole._
import code.api.util.ApiVersion
import code.api.util.ErrorMessages._
import code.api.v4_0_0.APIMethods400.Implementations4_0_0
import code.dynamicEntity.DynamicEntityCommons
import code.entitlement.Entitlement
import com.github.dwickern.macros.NameOf.nameOf
import net.liftweb.json.Serialization.write
import org.scalatest.Tag
import code.api.util.APIUtil.OAuth._
import scala.collection.immutable.List
class DynamicEntityTest 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.createDynamicEntity))
object ApiEndpoint2 extends Tag(nameOf(Implementations4_0_0.updateDynamicEntity))
object ApiEndpoint3 extends Tag(nameOf(Implementations4_0_0.getDynamicEntities))
object ApiEndpoint4 extends Tag(nameOf(Implementations4_0_0.deleteDynamicEntity))
val rightEntity = DynamicEntityCommons(entityName = "FooBar", metadataJson =
"""
|{
| "definitions": {
| "FooBar": {
| "required": [
| "name"
| ],
| "properties": {
| "name": {
| "type": "string",
| "example": "James Brown"
| },
| "number": {
| "type": "integer",
| "example": "698761728934"
| }
| }
| }
| }
|}
|""".stripMargin)
// wrong metadataJson
val wrongEntity = DynamicEntityCommons(entityName = "FooBar", metadataJson =
"""
|{
| "definitions": {
| "FooBar": {
| "required": [
| "name"
| ],
| "properties": {
| "name_wrong": {
| "type": "string",
| "example": "James Brown"
| },
| "number": {
| "type": "integer",
| "example": "698761728934"
| }
| }
| }
| }
|}
|""".stripMargin)
feature("Add a DynamicEntity v4.0.4- 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" / "dynamic_entities").POST
val response400 = makePostRequest(request400, write(rightEntity))
Then("We should get a 400")
response400.code should equal(400)
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) {
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))
Then("We should get a 400")
response400.code should equal(400)
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) {
When("We make a request v4.0.0")
val request400 = (v4_0_0_Request / "management" / "dynamic_entities").GET
val response400 = makeGetRequest(request400)
Then("We should get a 400")
response400.code should equal(400)
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) {
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)
Then("We should get a 400")
response400.code should equal(400)
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.4without a Role " + canCreateTaxResidence)
val request400 = (v4_0_0_Request / "management" / "dynamic_entities").POST <@(user1)
val response400 = makePostRequest(request400, write(rightEntity))
Then("We should get a 403")
response400.code should equal(403)
And("error should be " + UserHasMissingRoles + CanCreateDynamicEntity)
response400.body.extract[ErrorMessage].message should equal (UserHasMissingRoles + CanCreateDynamicEntity)
}
scenario("We will call the endpoint with the proper Role " + canCreateDynamicEntity , ApiEndpoint1, ApiEndpoint2, ApiEndpoint3, ApiEndpoint4, VersionOfApi) {
Entitlement.entitlement.vend.addEntitlement("", resourceUser1.userId, CanCreateDynamicEntity.toString)
When("We make a request v4.0.0")
val request400 = (v4_0_0_Request / "management" / "dynamic_entities").POST <@(user1)
val response400 = makePostRequest(request400, write(rightEntity))
Then("We should get a 201")
response400.code should equal(201)
val customerJson = response400.body.extract[DynamicEntityCommons]
Entitlement.entitlement.vend.addEntitlement("", resourceUser1.userId, CanUpdateDynamicEntity.toString)
When("We make a request v4.0.4with the Role " + canUpdateDynamicEntity)
{
// update success
val request400 = (v4_0_0_Request / "management" / "dynamic_entities" / customerJson.dynamicEntityId.get ).PUT <@(user1)
val metadataJson = customerJson.metadataJson
val entityName = customerJson.entityName
val response400 = makePutRequest(request400, write(customerJson.copy(entityName = "Hello", metadataJson.replace(entityName, "Hello"))))
Then("We should get a 200")
response400.code should equal(200)
val dynamicEntitiesJson = response400.body.extract[DynamicEntityCommons]
dynamicEntitiesJson.entityName should be ("Hello")
}
{
// update a not exists DynamicEntity
val request400 = (v4_0_0_Request / "management" / "dynamic_entities" / "not-exists-id" ).PUT <@(user1)
val response400 = makePutRequest(request400, write(customerJson))
Then("We should get a 400")
response400.code should equal(400)
response400.body.extract[ErrorMessage].message should startWith (DynamicEntityNotFoundByDynamicEntityId)
}
{
// update a DynamicEntity with wrong metadataJson
val request400 = (v4_0_0_Request / "management" / "dynamic_entities" / customerJson.dynamicEntityId.get ).PUT <@(user1)
val response400 = makePutRequest(request400, write(wrongEntity))
Then("We should get a 400")
response400.code should equal(400)
response400.body.extract[ErrorMessage].message should startWith (InvalidJsonFormat)
}
Entitlement.entitlement.vend.addEntitlement("", resourceUser1.userId, CanGetDynamicEntities.toString)
When("We make a request v4.0.4with the Role " + canGetDynamicEntities)
val requestGet400 = (v4_0_0_Request / "management" / "dynamic_entities").GET <@(user1)
val responseGet400 = makeGetRequest(requestGet400)
Then("We should get a 200")
responseGet400.code should equal(200)
val json = responseGet400.body \ "dynamic_entities"
val dynamicEntitiesGetJson = json.extract[List[DynamicEntityCommons]]
dynamicEntitiesGetJson.size should be (1)
Entitlement.entitlement.vend.addEntitlement("", resourceUser1.userId, CanDeleteDynamicEntity.toString)
When("We make a request v4.0.4with the Role " + canDeleteDynamicEntity)
val requestDelete400 = (v4_0_0_Request / "management" / "dynamic_entities" / dynamicEntitiesGetJson.head.dynamicEntityId.get).DELETE <@(user1)
val responseDelete400 = makeDeleteRequest(requestDelete400)
Then("We should get a 200")
responseDelete400.code should equal(200)
}
}
}