Merge remote-tracking branch 'upstream/develop' into develop

This commit is contained in:
Marko Milić 2019-09-30 17:05:34 +02:00
commit cb683064f6
22 changed files with 490 additions and 245 deletions

View File

@ -423,6 +423,9 @@ webui_dummy_customer_logins = Customer Logins\
\
Please ask a member of the Open Bank Project team for more logins if you require. You can use this [application](https://sofit.openbankproject.com) which also uses OAuth to browse your transaction data (use the above username/password).\
# when this value is set to true and webui_dummy_customer_logins value not empty, the register consumer key success page will show dummy customers Direct Login tokens.
webui_show_dummy_customer_tokens=false
# when developer register the consumer successfully, it will show this message to developer on the webpage or email.
webui_register_consumer_success_message_webpage = Thanks for registering your consumer with the Open Bank Project API! Here is your developer information. Please save it in a secure location.
webui_register_consumer_success_message_email = Thank you for registering to use the Open Bank Project API.

View File

@ -267,7 +267,7 @@ trait OBPRestHelper extends RestHelper with MdcLoggable {
fn(cc.copy(user = Full(u), consumer=consumer))
}// Authentication is successful
case _ => {
var (httpCode, message, directLoginParameters) = DirectLogin.validator("protectedResource", DirectLogin.getHttpMethod)
var (httpCode, message, directLoginParameters) = DirectLogin.validator("protectedResource")
Full(errorJsonResponse(message, httpCode))
}
}

View File

@ -245,17 +245,17 @@ object SwaggerDefinitionsJSON {
)
)
val transactionTypeId = TransactionTypeId(value = "123")
val transactionTypeIdSwagger = TransactionTypeId(value = "123")
val bankId = BankId(value = "gh.uk.9j")
val bankIdSwagger = BankId(value = "gh.uk.9j")
val transactionRequestId = TransactionRequestId(value = "123")
val transactionRequestIdSwagger = TransactionRequestId(value = "123")
val counterpartyId = CounterpartyId(value = "123")
val counterpartyIdSwagger = CounterpartyId(value = "123")
val accountId = model.AccountId(value = "123")
val accountIdSwagger = model.AccountId(value = "123")
val viewId = ViewId(value = "owner")
val viewIdSwagger = ViewId(value = "owner")
// from code.TransactionTypes.TransactionType, not from normal version Factory
@ -264,8 +264,8 @@ object SwaggerDefinitionsJSON {
import code.TransactionTypes.TransactionType._
val transactionType = TransactionType(
id = transactionTypeId,
bankId = bankId,
id = transactionTypeIdSwagger,
bankId = bankIdSwagger,
shortCode = "80080",
summary = SANDBOX_TAN.toString,
description = "This is the sandbox mode, charging litter money.",
@ -386,7 +386,7 @@ object SwaggerDefinitionsJSON {
)
val transactionRequest = TransactionRequest(
id= transactionRequestId,
id= transactionRequestIdSwagger,
`type`= "String",
from= transactionRequestAccount,
body= transactionRequestBodyAllTypes,
@ -397,11 +397,11 @@ object SwaggerDefinitionsJSON {
challenge= transactionRequestChallenge,
charge= transactionRequestCharge,
charge_policy= "String",
counterparty_id= counterpartyId,
counterparty_id= counterpartyIdSwagger,
name= "String",
this_bank_id= bankId,
this_account_id= accountId,
this_view_id= viewId,
this_bank_id= bankIdSwagger,
this_account_id= accountIdSwagger,
this_view_id= viewIdSwagger,
other_account_routing_scheme= "String",
other_account_routing_address= "String",
other_bank_routing_scheme= "String",
@ -1044,7 +1044,7 @@ object SwaggerDefinitionsJSON {
)
val transactionRequestJson = TransactionRequestJson(
id = transactionRequestId,
id = transactionRequestIdSwagger,
`type` = "String",
from = transactionRequestAccount,
details = transactionRequestBodyJson,
@ -1056,11 +1056,11 @@ object SwaggerDefinitionsJSON {
challenge = transactionRequestChallenge,
charge = transactionRequestCharge,
charge_policy = "String",
counterparty_id = counterpartyId,
counterparty_id = counterpartyIdSwagger,
name = "String",
this_bank_id = bankId,
this_account_id = accountId,
this_view_id = viewId,
this_bank_id = bankIdSwagger,
this_account_id = accountIdSwagger,
this_view_id = viewIdSwagger,
other_account_routing_scheme = "String",
other_account_routing_address = "String",
other_bank_routing_scheme = "String",
@ -1671,7 +1671,7 @@ object SwaggerDefinitionsJSON {
)
val transactionTypeJsonV200 = TransactionTypeJsonV200(
id = transactionTypeId,
id = transactionTypeIdSwagger,
bank_id = bankIdExample.value,
short_code = "PlaceholderString",
summary = "PlaceholderString",
@ -2841,7 +2841,7 @@ object SwaggerDefinitionsJSON {
)
val transactionInnerJson = TransactionInnerJson(
AccountId = accountId.value,
AccountId = accountIdSwagger.value,
TransactionId = "123",
TransactionReference = "Ref 1",
Amount = amountOfMoneyJsonV121,

View File

@ -94,36 +94,7 @@ object DirectLogin extends RestHelper with MdcLoggable {
//Handling get request for a token
case Req("my" :: "logins" :: "direct" :: Nil,_ , PostRequest) => {
//Extract the directLogin parameters from the header and test if the request is valid
var (httpCode, message, directLoginParameters) = validator("authorizationToken", getHttpMethod)
if (httpCode == 200) {
val userId:Long = (for {id <- getUserId(directLoginParameters)} yield id).getOrElse(0)
if (userId == 0) {
message = ErrorMessages.InvalidLoginCredentials
httpCode = 401
} else if (userId == AuthUser.usernameLockedStateCode) {
message = ErrorMessages.UsernameHasBeenLocked
httpCode = 401
} else {
val jwtPayloadAsJson =
"""{
"":""
}"""
val jwtClaims: JWTClaimsSet = JWTClaimsSet.parse(jwtPayloadAsJson)
val (token:String, secret:String) = generateTokenAndSecret(jwtClaims)
//Save the token that we have generated
if (saveAuthorizationToken(directLoginParameters, token, secret, userId)) {
message = token
} else {
httpCode = 500
message = "invalid"
}
}
}
val (httpCode: Int, message: String) = createToken(getAllParameters)
if (httpCode == 200)
successJsonResponse(Extraction.decompose(JSONFactory.createTokenJSON(message)), 201)
@ -132,6 +103,45 @@ object DirectLogin extends RestHelper with MdcLoggable {
}
}
/**
* according username, password, consumer_key to generate a DirectLogin token
* @param allParameters map {"username": "some_username", "password": "some_password", "consumer_key": "some_consumer_key"}
* @return httpCode and token value
*/
def createToken(allParameters: Map[String, String]) = {
//Extract the directLogin parameters from the header and test if the request is valid
var (httpCode, message, directLoginParameters) = validator("authorizationToken", allParameters)
if (httpCode == 200) {
val userId: Long = (for {id <- getUserId(directLoginParameters)} yield id).getOrElse(0)
if (userId == 0) {
message = ErrorMessages.InvalidLoginCredentials
httpCode = 401
} else if (userId == AuthUser.usernameLockedStateCode) {
message = ErrorMessages.UsernameHasBeenLocked
httpCode = 401
} else {
val jwtPayloadAsJson =
"""{
"":""
}"""
val jwtClaims: JWTClaimsSet = JWTClaimsSet.parse(jwtPayloadAsJson)
val (token: String, secret: String) = generateTokenAndSecret(jwtClaims)
//Save the token that we have generated
if (saveAuthorizationToken(directLoginParameters, token, secret, userId)) {
message = token
} else {
httpCode = 500
message = "invalid"
}
}
}
(httpCode, message)
}
def getHttpMethod = S.request match {
case Full(s) => s.post_? match {
case true => "POST"
@ -217,7 +227,7 @@ object DirectLogin extends RestHelper with MdcLoggable {
//Check if the request (access token or request token) is valid and return a tuple
def validator(requestType : String, httpMethod : String) : (Int, String, Map[String,String]) = {
def validator(requestType : String, allParameters: Map[String, String] = getAllParameters) : (Int, String, Map[String,String]) = {
def validAccessToken(tokenKey: String) = {
Tokens.tokens.vend.getTokenByKeyAndType(tokenKey, TokenType.Access) match {
@ -229,12 +239,10 @@ object DirectLogin extends RestHelper with MdcLoggable {
var message = ""
var httpCode: Int = 500
val parameters = getAllParameters
//are all the necessary directLogin parameters present?
val missingParams = missingDirectLoginParameters(parameters, requestType)
val missingParams = missingDirectLoginParameters(allParameters, requestType)
//guard maximum length and content of strings (a-z, 0-9 etc.) for parameters
val validParams = validDirectLoginParameters(parameters)
val validParams = validDirectLoginParameters(allParameters)
if (missingParams.nonEmpty) {
message = ErrorMessages.DirectLoginMissingParameters + missingParams.mkString(", ")
@ -246,18 +254,18 @@ object DirectLogin extends RestHelper with MdcLoggable {
}
else if (
requestType == "protectedResource" &&
! validAccessToken(parameters.getOrElse("token", ""))
! validAccessToken(allParameters.getOrElse("token", ""))
) {
message = ErrorMessages.DirectLoginInvalidToken + parameters.getOrElse("token", "")
message = ErrorMessages.DirectLoginInvalidToken + allParameters.getOrElse("token", "")
httpCode = 401
}
//check if the application is registered and active
else if (
requestType == "authorizationToken" &&
APIUtil.getPropsAsBoolValue("direct_login_consumer_key_mandatory", true) &&
! APIUtil.registeredApplication(parameters.getOrElse("consumer_key", ""))) {
! APIUtil.registeredApplication(allParameters.getOrElse("consumer_key", ""))) {
logger.error("application: " + parameters.getOrElse("consumer_key", "") + " not found")
logger.error("application: " + allParameters.getOrElse("consumer_key", "") + " not found")
message = ErrorMessages.InvalidConsumerKey
httpCode = 401
}
@ -265,7 +273,7 @@ object DirectLogin extends RestHelper with MdcLoggable {
httpCode = 200
if(message.nonEmpty)
logger.error("error message : " + message)
(httpCode, message, parameters)
(httpCode, message, allParameters)
}
@ -377,7 +385,7 @@ object DirectLogin extends RestHelper with MdcLoggable {
case Full(r) => r.request.method
case _ => "GET"
}
val (httpCode, message, directLoginParameters) = validator("protectedResource", httpMethod)
val (httpCode, message, directLoginParameters) = validator("protectedResource")
if (httpCode == 400 || httpCode == 401)
ParamFailure(message, Empty, Empty, APIFailure(message, httpCode))
@ -450,7 +458,7 @@ object DirectLogin extends RestHelper with MdcLoggable {
case _ => "GET"
}
val (httpCode, message, directLoginParameters) = validator("protectedResource", httpMethod)
val (httpCode, message, directLoginParameters) = validator("protectedResource")
val consumer: Option[Consumer] = for {
tokenId: String <- directLoginParameters.get("token")

View File

@ -381,17 +381,17 @@ object ApiRole {
case class CanAddKycStatus(requiresBankId: Boolean = true) extends ApiRole
lazy val canAddKycStatus = CanAddKycStatus()
case class CanGetKycChecks(requiresBankId: Boolean = true) extends ApiRole
lazy val canGetKycChecks = CanGetKycChecks()
case class CanGetAnyKycChecks(requiresBankId: Boolean = false) extends ApiRole
lazy val canGetAnyKycChecks = CanGetAnyKycChecks()
case class CanGetKycDocuments(requiresBankId: Boolean = true) extends ApiRole
lazy val canGetKycDocuments = CanGetKycDocuments()
case class CanGetAnyKycDocuments(requiresBankId: Boolean = false) extends ApiRole
lazy val canGetAnyKycDocuments = CanGetAnyKycDocuments()
case class CanGetKycMedia(requiresBankId: Boolean = true) extends ApiRole
lazy val canGetKycMedia = CanGetKycMedia()
case class CanGetAnyKycMedia(requiresBankId: Boolean = false) extends ApiRole
lazy val canGetAnyKycMedia = CanGetAnyKycMedia()
case class CanGetKycStatuses(requiresBankId: Boolean = true) extends ApiRole
lazy val canGetKycStatuses = CanGetKycStatuses()
case class CanGetAnyKycStatuses(requiresBankId: Boolean = false) extends ApiRole
lazy val canGetAnyKycStatuses = CanGetAnyKycStatuses()
private val roles =
canSearchAllTransactions ::
@ -512,10 +512,10 @@ object ApiRole {
canAddKycDocument ::
canAddKycMedia ::
canAddKycStatus ::
canGetKycChecks ::
canGetKycDocuments ::
canGetKycMedia ::
canGetKycStatuses ::
canGetAnyKycChecks ::
canGetAnyKycDocuments ::
canGetAnyKycMedia ::
canGetAnyKycStatuses ::
Nil
lazy val rolesMappedToClasses = roles.map(_.getClass)

View File

@ -36,9 +36,10 @@ object ErrorMessages {
// DynamicEntity Exceptions (OBP-09XXX)
val DynamicEntityNotFoundByDynamicEntityId = "OBP-09001: DynamicEntity not found. Please specify a valid value for dynamic_entity_id."
val DynamicEntityEntityNameAlreadyExists = "OBP-09002: DynamicEntity's entityName already exists. Please specify a different value for entityName."
val DynamicEntityEntityNotExists = "OBP-09003: DynamicEntity not exists. Please check entityName."
val DynamicEntityNameAlreadyExists = "OBP-09002: DynamicEntity's entityName already exists. Please specify a different value for entityName."
val DynamicEntityNotExists = "OBP-09003: DynamicEntity not exists. Please check entityName."
val DynamicEntityMissArgument = "OBP-09004: DynamicEntity process related argument is missing."
val EntityNotFoundByEntityId = "OBP-09005: Entity not found. Please specify a valid value for entityId."
// General messages (OBP-10XXX)
val InvalidJsonFormat = "OBP-10001: Incorrect json format."

View File

@ -32,7 +32,7 @@ import com.openbankproject.commons.model.enums.DynamicEntityOperation.{CREATE, U
import com.openbankproject.commons.model.enums.{AccountAttributeType, CardAttributeType, DynamicEntityOperation, ProductAttributeType}
import com.openbankproject.commons.model.{AccountApplication, Bank, Customer, CustomerAddress, Product, ProductCollection, ProductCollectionItem, TaxResidence, UserAuthContext, UserAuthContextUpdate, _}
import com.tesobe.CacheKeyFromArguments
import net.liftweb.common.{Box, Empty, Full}
import net.liftweb.common.{Box, Empty, Full, ParamFailure}
import net.liftweb.http.provider.HTTPParam
import net.liftweb.json
import net.liftweb.json.{JArray, JBool, JDouble, JInt, JObject, JString, JValue}
@ -1400,27 +1400,51 @@ object NewStyle {
}
}
def createOrUpdateDynamicEntity(dynamicEntity: DynamicEntityT, callContext: Option[CallContext]): Future[Box[DynamicEntityT]] = {
private def createDynamicEntity(dynamicEntity: DynamicEntityT, callContext: Option[CallContext]): Future[Box[DynamicEntityT]] = {
val existsDynamicEntity = DynamicEntityProvider.connectorMethodProvider.vend.getByEntityName(dynamicEntity.entityName)
val isEntityNameNotChange = existsDynamicEntity.isEmpty ||
existsDynamicEntity.filter(_.dynamicEntityId == dynamicEntity.dynamicEntityId).isDefined
if(existsDynamicEntity.isDefined) {
val errorMsg = s"$DynamicEntityNameAlreadyExists current entityName is '${dynamicEntity.entityName}'."
return Helper.booleanToFuture(errorMsg)(existsDynamicEntity.isEmpty).map(_.asInstanceOf[Box[DynamicEntityT]])
}
isEntityNameNotChange match {
case true => Future {
DynamicEntityProvider.connectorMethodProvider.vend.createOrUpdate(dynamicEntity)
}
case false => {
val entityNameExists = existsDynamicEntity.isDefined
// validate whether entityName is exists
val errorMsg = s"$DynamicEntityEntityNameAlreadyExists current entityName is '${dynamicEntity.entityName}'."
Helper.booleanToFuture(errorMsg)(!entityNameExists).map { _ =>
DynamicEntityProvider.connectorMethodProvider.vend.createOrUpdate(dynamicEntity)
}
}
Future {
DynamicEntityProvider.connectorMethodProvider.vend.createOrUpdate(dynamicEntity)
}
}
private def updateDynamicEntity(dynamicEntity: DynamicEntityT, dynamicEntityId: String , callContext: Option[CallContext]): Future[Box[DynamicEntityT]] = {
val originEntity = DynamicEntityProvider.connectorMethodProvider.vend.getById(dynamicEntityId)
// if can't find by id, return 404 error
val idNotExistsMsg = s"$DynamicEntityNotFoundByDynamicEntityId dynamicEntityId = ${dynamicEntity.dynamicEntityId.get}."
if (originEntity.isEmpty) {
return Helper.booleanToFuture(idNotExistsMsg, 404)(originEntity.isDefined).map(_.asInstanceOf[Box[DynamicEntityT]])
}
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)
if(existsDynamicEntity.isDefined) {
val errorMsg = s"$DynamicEntityNameAlreadyExists current entityName is '${dynamicEntity.entityName}'."
return Helper.booleanToFuture(errorMsg)(existsDynamicEntity.isEmpty).map(_.asInstanceOf[Box[DynamicEntityT]])
}
}
Future {
DynamicEntityProvider.connectorMethodProvider.vend.createOrUpdate(dynamicEntity)
}
}
def createOrUpdateDynamicEntity(dynamicEntity: DynamicEntityT, callContext: Option[CallContext]): Future[Box[DynamicEntityT]] =
dynamicEntity.dynamicEntityId match {
case Some(dynamicEntityId) => updateDynamicEntity(dynamicEntity, dynamicEntityId, callContext)
case None => createDynamicEntity(dynamicEntity, callContext)
}
def deleteDynamicEntity(dynamicEntityId: String): Future[Box[Boolean]] = Future {
DynamicEntityProvider.connectorMethodProvider.vend.delete(dynamicEntityId)
}

View File

@ -1398,7 +1398,7 @@ trait APIMethods121 {
BankAccountNotFound,
InvalidJsonFormat,
NoViewPermission,
"the view " + viewId + "does not allow adding more info",
"the view " + viewIdSwagger + "does not allow adding more info",
"More Info cannot be added",
UnknownError
),

View File

@ -350,7 +350,7 @@ trait APIMethods200 {
(Full(u), callContext) <- authorizedAccess(cc)
(bank, callContext) <- NewStyle.function.getBank(BankId(defaultBankId), callContext)
} yield {
val privateViewsUserCanAccessAtOneBank = Views.views.vend.privateViewsUserCanAccess(u).filter(_.bankId == bankId)
val privateViewsUserCanAccessAtOneBank = Views.views.vend.privateViewsUserCanAccess(u).filter(_.bankId == BankId(defaultBankId))
val privateAaccountsForOneBank = bank.privateAccounts(privateViewsUserCanAccessAtOneBank)
val result = corePrivateAccountsAtOneBankResult(CallerContext(corePrivateAccountsAtOneBank), codeContext, u, privateAaccountsForOneBank, privateViewsUserCanAccessAtOneBank)
(result, HttpCode.`200`(callContext))
@ -453,7 +453,7 @@ trait APIMethods200 {
List(UserNotLoggedIn, CustomerNotFoundByCustomerId, UnknownError),
Catalogs(notCore, notPSD2, notOBWG),
List(apiTagKyc, apiTagCustomer),
Some(List(canGetKycDocuments))
Some(List(canGetAnyKycDocuments))
)
// TODO Add Role
@ -463,7 +463,7 @@ trait APIMethods200 {
cc => {
for {
(Full(u), callContext) <- authorizedAccess(cc)
_ <- NewStyle.function.hasEntitlement(bankId.value, u.userId, ApiRole.canGetKycDocuments, callContext)
_ <- NewStyle.function.hasEntitlement("", u.userId, ApiRole.canGetAnyKycDocuments, callContext)
(customer, callContext) <- NewStyle.function.getCustomerByCustomerId(customerId, callContext)
(kycDocuments, callContxt) <- NewStyle.function.getKycDocuments(customerId, callContext)
} yield {
@ -490,14 +490,14 @@ trait APIMethods200 {
List(UserNotLoggedIn, CustomerNotFoundByCustomerId, UnknownError),
Catalogs(notCore, notPSD2, notOBWG),
List(apiTagKyc, apiTagCustomer),
Some(List(canGetKycMedia)))
Some(List(canGetAnyKycMedia)))
lazy val getKycMedia : OBPEndpoint = {
case "customers" :: customerId :: "kyc_media" :: Nil JsonGet _ => {
cc => {
for {
(Full(u), callContext) <- authorizedAccess(cc)
_ <- NewStyle.function.hasEntitlement(bankId.value, u.userId, ApiRole.canGetKycMedia, callContext)
_ <- NewStyle.function.hasEntitlement("", u.userId, ApiRole.canGetAnyKycMedia, callContext)
(customer, callContext) <- NewStyle.function.getCustomerByCustomerId(customerId, callContext)
(kycMedias, callContxt) <- NewStyle.function.getKycMedias(customerId, callContext)
} yield {
@ -523,17 +523,15 @@ trait APIMethods200 {
List(UserNotLoggedIn, CustomerNotFoundByCustomerId, UnknownError),
Catalogs(notCore, notPSD2, notOBWG),
List(apiTagKyc, apiTagCustomer),
Some(List(canGetKycChecks))
Some(List(canGetAnyKycChecks))
)
// TODO Add Role
lazy val getKycChecks : OBPEndpoint = {
case "customers" :: customerId :: "kyc_checks" :: Nil JsonGet _ => {
cc => {
for {
(Full(u), callContext) <- authorizedAccess(cc)
_ <- NewStyle.function.hasEntitlement(bankId.value, u.userId, ApiRole.canGetKycChecks, callContext)
_ <- NewStyle.function.hasEntitlement("", u.userId, ApiRole.canGetAnyKycChecks, callContext)
(customer, callContext) <- NewStyle.function.getCustomerByCustomerId(customerId, callContext)
(kycChecks, callContxt) <- NewStyle.function.getKycChecks(customerId, callContext)
} yield {
@ -558,7 +556,7 @@ trait APIMethods200 {
List(UserNotLoggedIn, CustomerNotFoundByCustomerId, UnknownError),
Catalogs(notCore, notPSD2, notOBWG),
List(apiTagKyc, apiTagCustomer),
Some(List(canGetKycStatuses))
Some(List(canGetAnyKycStatuses))
)
lazy val getKycStatuses : OBPEndpoint = {
@ -566,7 +564,7 @@ trait APIMethods200 {
cc => {
for {
(Full(u), callContext) <- authorizedAccess(cc)
_ <- NewStyle.function.hasEntitlement(bankId.value, u.userId, ApiRole.canGetKycStatuses, callContext)
_ <- NewStyle.function.hasEntitlement("", u.userId, ApiRole.canGetAnyKycStatuses, callContext)
(customer, callContext) <- NewStyle.function.getCustomerByCustomerId(customerId, callContext)
(kycStatuses, callContxt) <- NewStyle.function.getKycStatuses(customerId, callContext)
} yield {

View File

@ -483,7 +483,7 @@ trait APIMethods300 {
moderatedCoreAccountsJsonV300,
List(UserNotLoggedIn,UnknownError),
Catalogs(notCore, notPSD2, notOBWG),
List(apiTagAccountFirehose, apiTagAccount, apiTagFirehoseData, apiTagNewStyle),
List(apiTagAccount, apiTagAccountFirehose, apiTagFirehoseData, apiTagNewStyle),
Some(List(canUseFirehoseAtAnyBank))
)
@ -550,7 +550,7 @@ trait APIMethods300 {
transactionsJsonV300,
List(UserNotLoggedIn, FirehoseViewsNotAllowedOnThisInstance, UserHasMissingRoles, UnknownError),
Catalogs(notCore, notPSD2, notOBWG),
List(apiTagAccountFirehose, apiTagAccount, apiTagFirehoseData, apiTagNewStyle),
List(apiTagTransaction, apiTagAccountFirehose, apiTagTransactionFirehose, apiTagFirehoseData, apiTagNewStyle),
Some(List(canUseFirehoseAtAnyBank)))
lazy val getFirehoseTransactionsForBankAccount : OBPEndpoint = {
@ -1776,7 +1776,7 @@ trait APIMethods300 {
val msg = s"$InvalidJsonFormat The Json body should be the $CreateEntitlementRequestJSON "
x => unboxFullOrFail(x, callContext, msg)
}
_ <- Future { if (postedData.bank_id == "") Full() else NewStyle.function.getBank(bankId, callContext)}
_ <- Future { if (postedData.bank_id == "") Full() else NewStyle.function.getBank(BankId(postedData.bank_id), callContext)}
_ <- Helper.booleanToFuture(failMsg = IncorrectRoleName + postedData.role_name + ". Possible roles are " + ApiRole.availableRoles.sorted.mkString(", ")) {
availableRoles.exists(_ == postedData.role_name)

View File

@ -470,7 +470,7 @@ trait APIMethods310 {
transactionsJsonV300,
List(UserNotLoggedIn, FirehoseViewsNotAllowedOnThisInstance, UserHasMissingRoles, UnknownError),
Catalogs(notCore, notPSD2, notOBWG),
List(apiTagAccountFirehose, apiTagAccount, apiTagFirehoseData, apiTagNewStyle),
List(apiTagCustomer, apiTagFirehoseData, apiTagNewStyle),
Some(List(canUseFirehoseAtAnyBank)))
lazy val getFirehoseCustomers : OBPEndpoint = {

View File

@ -1,6 +1,5 @@
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._
@ -13,7 +12,8 @@ import code.api.v1_4_0.JSONFactory1_4_0.{ChallengeAnswerJSON, TransactionRequest
import code.api.v2_1_0._
import code.api.v3_0_0.JSONFactory300
import code.api.v3_1_0.ListResult
import code.dynamicEntity.{DynamicEntityCommons, DynamicEntityDefinition}
import code.api.{APIFailureNewStyle, ChargePolicy}
import code.dynamicEntity.DynamicEntityCommons
import code.model.dataAccess.AuthUser
import code.model.toUserExtended
import code.transactionrequests.TransactionRequests.TransactionChallengeTypes._
@ -25,9 +25,8 @@ import com.github.dwickern.macros.NameOf.nameOf
import com.openbankproject.commons.model._
import com.openbankproject.commons.model.enums.DynamicEntityFieldType
import com.openbankproject.commons.model.enums.DynamicEntityOperation._
import net.liftweb.common.{Box, Failure, Full}
import net.liftweb.common.{Box, Full, ParamFailure}
import net.liftweb.http.rest.RestHelper
import net.liftweb.json.JsonAST.JValue
import net.liftweb.json.Serialization.write
import net.liftweb.json._
import net.liftweb.util.StringHelpers
@ -720,13 +719,6 @@ trait APIMethods400 {
}
}
private def validateDynamicEntityJson(metadataJson: JValue) = {
val jFields = metadataJson.asInstanceOf[JObject].obj
require(jFields.size == 1, "json format for create or update DynamicEntity is not correct, it should have a single key value for structure definition")
val JField(_, definition) = jFields.head
definition.extract[DynamicEntityDefinition]
}
resourceDocs += ResourceDoc(
createDynamicEntity,
implementedInApiVersion,
@ -763,17 +755,12 @@ trait APIMethods400 {
for {
(Full(u), callContext) <- authorizedAccess(cc)
_ <- NewStyle.function.hasEntitlement("", u.userId, canCreateDynamicEntity, callContext)
failMsg = s"$InvalidJsonFormat The Json body should be the same structure as request body example."
postedData <- NewStyle.function.tryons(failMsg, 400, callContext) {
validateDynamicEntityJson(json)
val JField(entityName, _) = json.asInstanceOf[JObject].obj.head
DynamicEntityCommons(entityName, compactRender(json))
}
Full(dynamicEntity) <- NewStyle.function.createOrUpdateDynamicEntity(postedData, callContext)
jsonObject = json.asInstanceOf[JObject]
dynamicEntity = DynamicEntityCommons(jsonObject, None)
Full(result) <- NewStyle.function.createOrUpdateDynamicEntity(dynamicEntity, callContext)
} yield {
val commonsData: DynamicEntityCommons = dynamicEntity
val commonsData: DynamicEntityCommons = result
(commonsData.jValue, HttpCode.`201`(callContext))
}
}
@ -817,18 +804,11 @@ trait APIMethods400 {
(Full(u), callContext) <- authorizedAccess(cc)
_ <- NewStyle.function.hasEntitlement("", u.userId, canUpdateDynamicEntity, callContext)
failMsg = s"$InvalidJsonFormat The Json body should be the same structure as request body example."
putData <- NewStyle.function.tryons(failMsg, 400, callContext) {
validateDynamicEntityJson(json)
val JField(name, _) = json.asInstanceOf[JObject].obj.head
DynamicEntityCommons(name, compactRender(json), Some(dynamicEntityId))
}
(_, _) <- NewStyle.function.getDynamicEntityById(dynamicEntityId, callContext)
Full(dynamicEntity) <- NewStyle.function.createOrUpdateDynamicEntity(putData, callContext)
jsonObject = json.asInstanceOf[JObject]
dynamicEntity = DynamicEntityCommons(jsonObject, Some(dynamicEntityId))
Full(result) <- NewStyle.function.createOrUpdateDynamicEntity(dynamicEntity, callContext)
} yield {
val commonsData: DynamicEntityCommons = dynamicEntity
val commonsData: DynamicEntityCommons = result
(commonsData.jValue, HttpCode.`200`(callContext))
}
}
@ -870,9 +850,11 @@ trait APIMethods400 {
}
}
}
private def unboxResult[T](box: Box[T]): T = {
if(box.isInstanceOf[Failure]) {
throw new Exception(box.asInstanceOf[Failure].msg)
private def unboxResult[T: Manifest](box: Box[T]): T = {
if(box.isInstanceOf[ParamFailure[_]]) {
fullBoxOrException[T](box)
}
box.openOrThrowException("impossible error")
@ -881,9 +863,8 @@ trait APIMethods400 {
case EntityName(entityName) :: Nil JsonGet req => { cc =>
val listName = StringHelpers.snakify(English.plural(entityName))
for {
(box: Box[JArray], _) <- NewStyle.function.invokeDynamicConnector(GET_ALL, entityName, None, None, Some(cc))
// resultList = APIUtil.unboxFullOrFail(box, Some(cc))
resultList = unboxResult(box)
(box, _) <- NewStyle.function.invokeDynamicConnector(GET_ALL, entityName, None, None, Some(cc))
resultList: JArray = unboxResult(box.asInstanceOf[Box[JArray]])
} yield {
import net.liftweb.json.JsonDSL._
val jValue: JObject = listName -> resultList
@ -892,36 +873,32 @@ trait APIMethods400 {
}
case EntityName(entityName, id) JsonGet req => {cc =>
for {
(box: Box[JObject], _) <- NewStyle.function.invokeDynamicConnector(GET_ONE, entityName, None, Some(id), Some(cc))
// entity = APIUtil.unboxFullOrFail(box, Some(cc))
entity = unboxResult(box)
(box, _) <- NewStyle.function.invokeDynamicConnector(GET_ONE, entityName, None, Some(id), Some(cc))
entity: JValue = unboxResult(box.asInstanceOf[Box[JValue]])
} yield {
(entity, HttpCode.`200`(Some(cc)))
}
}
case EntityName(entityName) :: Nil JsonPost json -> _ => {cc =>
for {
(box: Box[JObject], _) <- NewStyle.function.invokeDynamicConnector(CREATE, entityName, Some(json.asInstanceOf[JObject]), None, Some(cc))
// entity = APIUtil.unboxFullOrFail(box, Some(cc))
entity = unboxResult(box)
(box, _) <- NewStyle.function.invokeDynamicConnector(CREATE, entityName, Some(json.asInstanceOf[JObject]), None, Some(cc))
entity: JValue = unboxResult(box.asInstanceOf[Box[JValue]])
} yield {
(entity, HttpCode.`201`(Some(cc)))
}
}
case EntityName(entityName, id) JsonPut json -> _ => { cc =>
for {
(box: Box[JObject], _) <- NewStyle.function.invokeDynamicConnector(UPDATE, entityName, Some(json.asInstanceOf[JObject]), Some(id), Some(cc))
// entity = APIUtil.unboxFullOrFail(box, Some(cc))
entity = unboxResult(box)
(box: Box[JValue], _) <- NewStyle.function.invokeDynamicConnector(UPDATE, entityName, Some(json.asInstanceOf[JObject]), Some(id), Some(cc))
entity: JValue = unboxResult(box.asInstanceOf[Box[JValue]])
} yield {
(entity, HttpCode.`200`(Some(cc)))
}
}
case EntityName(entityName, id) JsonDelete req => { cc =>
for {
(box: Box[JValue], _) <- NewStyle.function.invokeDynamicConnector(DELETE, entityName, None, Some(id), Some(cc))
// deleteResult = APIUtil.unboxFullOrFail(box, Some(cc))
deleteResult = unboxResult(box)
(box, _) <- NewStyle.function.invokeDynamicConnector(DELETE, entityName, None, Some(id), Some(cc))
deleteResult: JBool = unboxResult(box.asInstanceOf[Box[JBool]])
} yield {
(deleteResult, HttpCode.`200`(Some(cc)))
}

View File

@ -220,16 +220,8 @@ case class DynamicEntityInfo(definition: String, entityName: String) {
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 == idName)) match {
case (Some(idValue), false) => JObject(JField(idName, JString(idValue)) :: fields)
case _ => JObject(fields)

View File

@ -17,6 +17,7 @@ import net.liftweb.json.JValue
import net.liftweb.json.JsonAST.JNothing
import org.apache.commons.lang3.StringUtils
import scala.annotation.tailrec
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.Future
import scala.language.postfixOps
@ -55,16 +56,14 @@ object ConnectorEndpoints extends RestHelper{
val value = invokeMethod(methodSymbol, paramValues :_*)
// convert any to Future[(Box[_], Option[CallContext])] type
val futureValue: Future[(Box[_], Option[CallContext])] = toStandaredFuture(value)
val futureValue: Future[(Box[_], Option[CallContext])] = toStandardFuture(value)
for {
(Full(data), callContext) <- futureValue.map {it =>
APIUtil.fullBoxOrException(it._1 ~> APIFailureNewStyle("", 400, optionCC.map(_.toLight)))
it
}
(boxedData, _) <- futureValue
data = APIUtil.fullBoxOrException(boxedData ~> APIFailureNewStyle("", 400, optionCC.map(_.toLight)))
inboundAdapterCallContext = nameOf(InboundAdapterCallContext)
//convert first letter to small case
inboundAdapterCallContextKey = Character.toLowerCase(inboundAdapterCallContext.charAt(0)) + inboundAdapterCallContext.substring(1)
inboundAdapterCallContextKey = StringUtils.uncapitalize(inboundAdapterCallContext)
inboundAdapterCallContextValue = InboundAdapterCallContext(cc.correlationId)
} yield {
// NOTE: if any filed type is BigDecimal, it is can't be serialized by lift json
@ -184,7 +183,8 @@ object ConnectorEndpoints extends RestHelper{
mirrorObj.reflectMethod(method).apply(args :_*)
}
def toStandaredFuture(obj: Any): Future[(Box[_], Option[CallContext])] = {
@tailrec
def toStandardFuture(obj: Any): Future[(Box[_], Option[CallContext])] = {
obj match {
case null => Future((Empty, None))
case future: Future[_] => {
@ -202,10 +202,10 @@ object ConnectorEndpoints extends RestHelper{
}
case Full(data) => {
data match {
case _: (_, _) => toStandaredFuture(Future(obj))
case _: (_, _) => toStandardFuture(Future(obj))
case _ => {
val fillCallContext = obj.asInstanceOf[Box[_]].map((_, None))
toStandaredFuture(Future(fillCallContext))
toStandardFuture(Future(fillCallContext))
}
}
}

View File

@ -23,7 +23,7 @@ import code.cards.MappedPhysicalCard
import code.context.{UserAuthContextProvider, UserAuthContextUpdateProvider}
import code.customer._
import code.customeraddress.CustomerAddressX
import code.dynamicEntity.DynamicEntityProvider
import code.dynamicEntity.{DynamicEntityProvider, DynamicEntityT}
import code.fx.{FXRate, MappedFXRate, fx}
import code.kycchecks.KycChecks
import code.kycdocuments.KycDocuments
@ -60,8 +60,7 @@ import com.openbankproject.commons.model.{AccountApplication, AccountAttribute,
import com.tesobe.CacheKeyFromArguments
import com.tesobe.model.UpdateBankAccount
import net.liftweb.common._
import net.liftweb.json
import net.liftweb.json.{JArray, JBool, JDouble, JInt, JObject, JString, JValue}
import net.liftweb.json.{JArray, JBool, JObject, JValue}
import net.liftweb.mapper.{By, _}
import net.liftweb.util.Helpers.{tryo, _}
import net.liftweb.util.Mailer
@ -196,10 +195,8 @@ object LocalMappedConnector extends Connector with MdcLoggable {
)
} yield true
}
val errorMessage = sendingResult map {
case f: Failure => f.msg
case Empty => ""
}
val errorMessage = sendingResult.filter(_.isInstanceOf[Failure]).map(_.asInstanceOf[Failure].msg)
if(sendingResult.forall(_ == Full(true))) hashedPassword else (Failure(errorMessage.toSet.mkString(" <- ")), callContext)
case None => // All versions which precede v4.0.0 i.e. to keep backward compatibility
createHashedPassword("123")
@ -2801,50 +2798,30 @@ object LocalMappedConnector extends Connector with MdcLoggable {
val dynamicEntityBox = DynamicEntityProvider.connectorMethodProvider.vend.getByEntityName(entityName)
// do validate, any validate process fail will return immediately
if(dynamicEntityBox.isEmpty) {
return Helper.booleanToFuture(s"$DynamicEntityEntityNotExists entity's name is '$entityName'")(dynamicEntityBox.isDefined)
return Helper.booleanToFuture(s"$DynamicEntityNotExists entity's name is '$entityName'")(false)
.map(it => (it.map(_.asInstanceOf[JValue]), callContext))
} else if(entityId.isDefined && !persistedEntities.contains(entityId.get -> entityName)) {
val id = entityId.get
val idName = StringUtils.uncapitalize(entityName) + "Id"
}
return Helper.booleanToFuture(s"$InvalidUrl not exists $entityName of $idName = $id")(false)
.map(it => (it.map(_.asInstanceOf[JValue]), callContext))
} else if(requestBody.isDefined) {
val dynamicEntity = dynamicEntityBox.openOrThrowException(DynamicEntityEntityNotExists)
val jsonTypeMap = Map[String, Class[_]](
("boolean", classOf[JBool]),
("string", classOf[JString]),
("array", classOf[JArray]),
("integer", classOf[JInt]),
("number", classOf[JDouble]),
)
val definitionJson = json.parse(dynamicEntity.metadataJson).asInstanceOf[JObject]
val entity = (definitionJson \ entityName).asInstanceOf[JObject]
val requiredFieldNames: Set[String] = (entity \ "required").asInstanceOf[JArray].arr.map(_.asInstanceOf[JString].s).toSet
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 bodyJson = requestBody.getOrElse(throw new RuntimeException(s"$DynamicEntityMissArgument please supply the requestBody."))
val fields = bodyJson.obj.filter(it => fieldNameToType.keySet.contains(it.name))
// if there are field type are not match the definitions, there must be bug.
val invalidTypes = fields.filterNot(it => fieldNameToType(it.name).isInstance(it.value))
val invalidTypeNames = invalidTypes.map(_.name).mkString("[", ",", "]")
val missingRequiredFields = requiredFieldNames.filterNot(it => fields.exists(_.name == it))
val missingFieldNames = missingRequiredFields.mkString("[", ",", "]")
if(invalidTypes.nonEmpty) {
return Helper.booleanToFuture(s"$InvalidJsonFormat these field type not correct: $invalidTypeNames")(invalidTypes.isEmpty)
if(operation == CREATE || operation == UPDATE) {
if(requestBody.isEmpty) {
return Helper.booleanToFuture(s"$InvalidJsonFormat requestBody is required for $operation operation.")(false)
.map(it => (it.map(_.asInstanceOf[JValue]), callContext))
} else if(missingRequiredFields.nonEmpty) {
return Helper.booleanToFuture(s"$InvalidJsonFormat some required fields are missing: $missingFieldNames")(missingRequiredFields.isEmpty)
}
val dynamicEntity: DynamicEntityT = dynamicEntityBox.openOrThrowException(DynamicEntityNotExists)
val validateResult: Either[String, Unit] = dynamicEntity.validateEntityJson(requestBody.get)
if(validateResult.isLeft) {
return Helper.booleanToFuture(s"$InvalidJsonFormat details: ${validateResult.left.get}")(validateResult.isRight)
.map(it => (it.map(_.asInstanceOf[JValue]), callContext))
}
}
if(operation == GET_ONE || operation == UPDATE || operation == DELETE) {
if (entityId.isEmpty) {
return Helper.booleanToFuture(s"$InvalidJsonFormat entityId is required for $operation operation.")(entityId.isEmpty || StringUtils.isBlank(entityId.get))
.map(it => (it.map(_.asInstanceOf[JValue]), callContext))
}
if (!persistedEntities.contains(entityId.get -> entityName)) {
val id = entityId.get
return Helper.booleanToFuture(s"$EntityNotFoundByEntityId please check: entityId = $id", 404)(false)
.map(it => (it.map(_.asInstanceOf[JValue]), callContext))
}
}
@ -2860,7 +2837,8 @@ object LocalMappedConnector extends Connector with MdcLoggable {
}
case CREATE | UPDATE => {
val body = requestBody.getOrElse(throw new RuntimeException(s"$DynamicEntityMissArgument please supply the requestBody."))
val persistedEntity = MockerConnector.persist(entityName, body, entityId)
val id = if(operation == CREATE) None else entityId
val persistedEntity = MockerConnector.persist(entityName, body, id)
Full(persistedEntity)
}
case DELETE => {

View File

@ -9242,6 +9242,63 @@ trait RestConnector_vMar2019 extends Connector with KafkaHelper with MdcLoggable
}
//---------------- dynamic end ---------------------please don't modify this line
private val availableOperation = DynamicEntityOperation.values.map(it => s""""$it"""").mkString("[", ", ", "]")
messageDocs += dynamicEntityProcessDoc
def dynamicEntityProcessDoc = MessageDoc(
process = "obp.dynamicEntityProcess",
messageFormat = messageFormat,
description = s"operate committed dynamic entity data, the available value of 'operation' can be: ${availableOperation}",
outboundTopic = None,
inboundTopic = None,
exampleOutboundMessage = (
OutBoundDynamicEntityProcessDoc(outboundAdapterCallContext = OutboundAdapterCallContext(correlationId=correlationIdExample.value,
sessionId=Some(sessionIdExample.value),
consumerId=Some(consumerIdExample.value),
generalContext=Some(List( BasicGeneralContext(key=keyExample.value,
value=valueExample.value))),
outboundAdapterAuthInfo=Some( OutboundAdapterAuthInfo(userId=Some(userIdExample.value),
username=Some(usernameExample.value),
linkedCustomers=Some(List( BasicLinkedCustomer(customerId=customerIdExample.value,
customerNumber=customerNumberExample.value,
legalName=legalNameExample.value))),
userAuthContext=Some(List( BasicUserAuthContext(key=keyExample.value,
value=valueExample.value))),
authViews=Some(List( AuthView(view= ViewBasic(id=viewIdExample.value,
name=viewNameExample.value,
description=viewDescriptionExample.value),
account= AccountBasic(id=accountIdExample.value,
accountRoutings=List( AccountRouting(scheme=accountRoutingSchemeExample.value,
address=accountRoutingAddressExample.value)),
customerOwners=List( InternalBasicCustomer(bankId=bankIdExample.value,
customerId=customerIdExample.value,
customerNumber=customerNumberExample.value,
legalName=legalNameExample.value,
dateOfBirth=parseDate(dateOfBirthExample.value).getOrElse(sys.error("dateOfBirthExample.value is not validate date format.")))),
userOwners=List( InternalBasicUser(userId=userIdExample.value,
emailAddress=emailExample.value,
name=usernameExample.value))))))))),
operation = DynamicEntityOperation.UPDATE,
entityName = "FooBar",
requestBody = Some(FooBar(name = "James Brown", number = 1234567890)),
entityId = Some("foobar-id-value"))
),
exampleInboundMessage = (
InBoundDynamicEntityProcessDoc(inboundAdapterCallContext= InboundAdapterCallContext(correlationId=correlationIdExample.value,
sessionId=Some(sessionIdExample.value),
generalContext=Some(List( BasicGeneralContext(key=keyExample.value,
value=valueExample.value)))),
status= Status(errorCode=statusErrorCodeExample.value,
backendMessages=List( InboundStatusMessage(source=sourceExample.value,
status=inboundStatusMessageStatusExample.value,
errorCode=inboundStatusMessageErrorCodeExample.value,
text=inboundStatusMessageTextExample.value))),
data=FooBar(name = "James Brown", number = 1234567890, fooBarId = Some("foobar-id-value")))
),
adapterImplementation = Some(AdapterImplementation("- Core", 1))
)
override def dynamicEntityProcess(operation: DynamicEntityOperation,
entityName: String,
requestBody: Option[JObject],
@ -9365,7 +9422,7 @@ trait RestConnector_vMar2019 extends Connector with KafkaHelper with MdcLoggable
val future: Future[Box[Box[T]]] = extractBody(entity) map { msg =>
tryo {
val errorMsg = parse(msg).extract[ErrorMessage]
val failure: Box[T] = ParamFailure(errorMsg.message, "")
val failure: Box[T] = ParamFailure("", APIFailureNewStyle(errorMsg.message, status.intValue()))
failure
} ~> APIFailureNewStyle(msg, status.intValue())
}

View File

@ -1,11 +1,11 @@
package code.dynamicEntity
import code.api.util.ErrorMessages.InvalidJsonFormat
import com.openbankproject.commons.model.enums.DynamicEntityFieldType
import com.openbankproject.commons.model.{Converter, JsonFieldReName}
import net.liftweb.common.Box
import net.liftweb.json.JsonAST.JString
import net.liftweb.json.JsonDSL._
import net.liftweb.json.{JField, JObject, JsonAST}
import net.liftweb.json.{JArray, JBool, JDouble, JField, JInt, JNothing, JNull, JObject, JString, compactRender, parse}
import net.liftweb.util.SimpleInjector
object DynamicEntityProvider extends SimpleInjector {
@ -19,13 +19,11 @@ 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 {
private val definition: JObject = net.liftweb.json.parse(metadataJson).asInstanceOf[JObject]
//---------util methods
private lazy val definition: JObject = parse(metadataJson).asInstanceOf[JObject]
//convert metadataJson to JValue, so the final json field metadataJson have no escaped " to \", have good readable
lazy val jValue = dynamicEntityId match {
case Some(id) => {
@ -35,18 +33,165 @@ case class DynamicEntityCommons(entityName: String,
}
case None => definition
}
/**
* validate the commit json whether fulfil DynamicEntity schema
* @param entityJson commit json object to add new instance of given dynamic entity
* @return return Success[Unit], or return Left[String] error message
*/
def validateEntityJson(entityJson: JObject): Either[String, Unit] = {
val required: List[String] = (definition \ entityName \ "required").asInstanceOf[JArray].arr.map(_.asInstanceOf[JString].s)
val missingProperties = required diff entityJson.obj.map(_.name)
if(missingProperties.nonEmpty) {
return Left(s"$InvalidJsonFormat The 'required' field's not be fulfilled, missing properties: ${missingProperties.mkString(", ")}")
}
val invalidPropertyMsg = (definition \ entityName \ "properties").asInstanceOf[JObject].obj
.map(it => {
val JField(propertyName, propertyDef: JObject) = it
val propertyTypeName = (propertyDef \ "type").asInstanceOf[JString].s
(propertyName, propertyTypeName)
})
.map(it => {
val (propertyName, propertyType) = it
val propertyValue = entityJson \ propertyName
propertyType match {
case _ if propertyValue == JNothing || propertyValue == JNull => "" // required properties already checked.
case "string" if !propertyValue.isInstanceOf[JString] => s"$InvalidJsonFormat The type of '$propertyName' should be string"
case "number" if !propertyValue.isInstanceOf[JDouble] => s"$InvalidJsonFormat The type of '$propertyName' should be number"
case "integer" if !propertyValue.isInstanceOf[JInt] => s"$InvalidJsonFormat The type of '$propertyName' should be integer"
case "boolean" if !propertyValue.isInstanceOf[JBool] => s"$InvalidJsonFormat The type of '$propertyName' should be boolean"
case "array" if !propertyValue.isInstanceOf[JArray] => s"$InvalidJsonFormat The type of '$propertyName' should be array"
case "object" if !propertyValue.isInstanceOf[JObject] => s"$InvalidJsonFormat The type of '$propertyName' should be object"
case _ => ""
}
})
.filter(_.nonEmpty)
.mkString("; ")
if(invalidPropertyMsg.nonEmpty) {
Left(invalidPropertyMsg)
} else {
Right(Unit)
}
}
}
case class DynamicEntityCommons(entityName: String,
metadataJson: String,
dynamicEntityId: Option[String] = None
) extends DynamicEntityT with JsonFieldReName
object DynamicEntityCommons extends Converter[DynamicEntityT, DynamicEntityCommons] {
/**
* create DynamicEntityCommons object, and do validation
*
* @param jsonObject the follow schema json:
* {{{
* {
* "FooBar": {
* "required": [
* "name"
* ],
* "properties": {
* "name": {
* "type": "string",
* "example": "James Brown"
* },
* "number": {
* "type": "integer",
* "example": "698761728934"
* }
* }
* }
* }
* }}}
* @param dynamicEntityId
* @return object of DynamicEntityCommons
*/
def apply(jsonObject: JObject, dynamicEntityId: Option[String]): DynamicEntityCommons = {
def checkFormat(requirement: Boolean, message: String) = {
if (!requirement) throw new IllegalArgumentException(message)
}
val fields = jsonObject.obj
// validate whether json is object and have a single field, currently support one entity definition
checkFormat(fields.nonEmpty, s"$InvalidJsonFormat The Json root object should have a single entity, but current have none.")
checkFormat(fields.size == 1, s"$InvalidJsonFormat The Json root object should have a single entity, but current entityNames: ${fields.map(_.name).mkString(", ")}")
val JField(entityName, metadataJson) = fields.head
// validate entityName corresponding value is json object
val metadataStr = compactRender(metadataJson)
checkFormat(metadataJson.isInstanceOf[JObject], s"$InvalidJsonFormat The $entityName should have an object value, but current value is: $metadataStr")
val required = metadataJson \ "required"
// validate 'required' field exists and is a json array[string]
checkFormat(required != JNothing , s"$InvalidJsonFormat There must be 'required' field in $entityName, and type is json array[string]")
checkFormat(required.isInstanceOf[JArray] && required.asInstanceOf[JArray].arr.forall(_.isInstanceOf[JString]), s"$InvalidJsonFormat The 'required' field's type of $entityName should be array[string]")
val properties = metadataJson \ "properties"
// validate 'properties' field exists and is json object
checkFormat(properties != JNothing , s"$InvalidJsonFormat There must be 'required' field in $entityName, and type is array[string]")
checkFormat(properties.isInstanceOf[JObject], s"$InvalidJsonFormat The 'properties' field's type of $entityName should be json object")
val propertiesObj = properties.asInstanceOf[JObject]
val requiredFields = required.asInstanceOf[JArray].arr.map(_.asInstanceOf[JString].s)
val allFields = propertiesObj.obj
val missingRequiredFields = requiredFields diff allFields.map(_.name)
checkFormat(missingRequiredFields.isEmpty , s"$InvalidJsonFormat missing properties: ${missingRequiredFields.mkString(", ")}")
// validate there is no required field missing in properties
val notFoundRequiredField = requiredFields.diff(allFields.map(_.name))
checkFormat(metadataJson.isInstanceOf[JObject], s"$InvalidJsonFormat In the $entityName, all 'required' fields should be present, these are missing: ${notFoundRequiredField.mkString(", ")}")
// validate all properties have a type and example
allFields.foreach(field => {
val JField(fieldName, value) = field
checkFormat(value.isInstanceOf[JObject], s"$InvalidJsonFormat The property of $fieldName's type should be json object")
// 'type' exists and value should be one of allowed type
val fieldType = value \ "type"
checkFormat(fieldType.isInstanceOf[JString] && fieldType.asInstanceOf[JString].s.nonEmpty, s"$InvalidJsonFormat The property of $fieldName's 'type' field should be exists and type is json string")
checkFormat(allowedFieldType.contains(fieldType.asInstanceOf[JString].s), s"$InvalidJsonFormat The property of $fieldName's 'type' field should be json string and value should be one of: ${allowedFieldType.mkString(", ")}")
// example is exists
val fieldExample = value \ "example"
checkFormat(fieldExample != JNothing, s"$InvalidJsonFormat The property of $fieldName's 'example' field should be exists")
})
DynamicEntityCommons(entityName, compactRender(jsonObject), dynamicEntityId)
}
private val allowedFieldType: Set[String] = Set(
"string",
"number",
"integer",
"boolean",
"array",
// "object",
)
}
/**
* an example schema of DynamicEntity, this is as request body example usage
* example case classes, as an example schema of DynamicEntity, for request body example usage
* @param FooBar
*/
case class DynamicEntityFooBar(FooBar: DynamicEntityDefinition, dynamicEntityId: Option[String] = None)
case class DynamicEntityDefinition(required: List[String],properties: DynamicEntityFullBarFields)
case class DynamicEntityFullBarFields(name: DynamicEntityTypeExample, number: DynamicEntityTypeExample)
case class DynamicEntityTypeExample(`type`: DynamicEntityFieldType, example: String)
object DynamicEntityCommons extends Converter[DynamicEntityT, DynamicEntityCommons]
//-------------------example case class end
trait DynamicEntityProvider {

View File

@ -26,6 +26,7 @@ TESOBE (http://www.tesobe.com/)
*/
package code.snippet
import code.api.DirectLogin
import code.api.util.{APIUtil, ErrorMessages}
import code.consumer.Consumers
import code.model._
@ -37,6 +38,8 @@ import net.liftweb.util.Helpers._
import net.liftweb.util.{FieldError, Helpers}
import code.webuiprops.MappedWebUiPropsProvider.getWebUiPropsValue
import scala.collection.mutable.ListBuffer
class ConsumerRegistration extends MdcLoggable {
private object nameVar extends RequestVar("")
@ -89,6 +92,29 @@ class ConsumerRegistration extends MdcLoggable {
val urlOAuthEndpoint = APIUtil.getPropsValue("hostname", "") + "/oauth/initiate"
val urlDirectLoginEndpoint = APIUtil.getPropsValue("hostname", "") + "/my/logins/direct"
val createDirectLoginToken = getWebUiPropsValue("webui_create_directlogin_token_url", "")
val dummyCustomersInfo = getWebUiPropsValue("webui_dummy_customer_logins", "")
val isShowDummyCustomerTokens = getWebUiPropsValue("webui_show_dummy_customer_tokens", "false").toBoolean
val dummyUsersTokens: String = (isShowDummyCustomerTokens, dummyCustomersInfo) match {
case(true, v) if v.nonEmpty => {
val regex = """\{"user_name"\s*:\s*"(.+?)".+?"password"\s*:\s*"(.+?)".+?\}""".r
val matcher = regex.pattern.matcher(v)
val tokens = ListBuffer[String]()
while(matcher.find()) {
val userName = matcher.group(1)
val password = matcher.group(2)
val consumerKey = consumer.key.get
val (code, token) = DirectLogin.createToken(Map(("username", userName), ("password", password), ("consumer_key", consumerKey)))
val authHeader = code match {
case 200 => s"""$userName auth header --> Authorization: DirectLogin token="$token""""
case _ => s"""$userName - -> username or password is invalid, generate token fail"""
}
tokens += authHeader
}
tokens.mkString(""" | """)
}
case _ => ""
}
val registerConsumerSuccessMessageWebpage = getWebUiPropsValue(
"webui_register_consumer_success_message_webpage",
"Thanks for registering your consumer with the Open Bank Project API! Here is your developer information. Please save it in a secure location.")
@ -111,7 +137,13 @@ class ConsumerRegistration extends MdcLoggable {
"#directlogin-endpoint a [href]" #> urlDirectLoginEndpoint &
"#post-consumer-registration-more-info-link a *" #> registrationMoreInfoText &
"#post-consumer-registration-more-info-link a [href]" #> registrationMoreInfoUrl &
"#register-consumer-input" #> ""
"#register-consumer-input" #> "" & {
if(dummyUsersTokens.isEmpty) {
".preparedTokens" #> dummyUsersTokens
} else {
"#preparedTokens *" #> dummyUsersTokens
}
}
}
def showRegistrationResults(result : Consumer) = {

View File

@ -134,10 +134,14 @@ Berlin 13359, Germany
<div class="col-xs-12 col-sm-4">Direct Login Endpoint</div>
<div class="col-xs-12 col-sm-8"><span id="directlogin-endpoint"><a href="#">endpoint</a></span></div>
</div>
<div class="row preparedTokens">
<div class="col-xs-12 col-sm-4">Prepared user tokens</div>
<div class="col-xs-12 col-sm-8" id="preparedTokens"></div>
</div>
<div class="row">
<div class="col-xs-12 col-sm-4">Direct Login Documentation</div>
<div class="col-xs-12 col-sm-8"><a href="https://github.com/OpenBankProject/OBP-API/wiki/Direct-Login">How to use Direct Login</a>
</div>
</div>
</div>
<div class="row">
<div class="col-xs-12 col-sm-4"></div>

View File

@ -159,6 +159,13 @@ class DynamicEntityTest extends V400ServerSetup {
val response = makePostRequest(request, write(rightEntity))
Then("We should get a 201")
response.code should equal(201)
{ // create duplicated entityName FooBar, cause 400
val response400 = makePostRequest(request, write(rightEntity))
response400.code should equal(400)
response400.body.extract[ErrorMessage].message should startWith (DynamicEntityNameAlreadyExists)
}
val responseJson = response.body
val dynamicEntityId = (responseJson \ "dynamicEntityId").asInstanceOf[JString].s
val dynamicEntityIdJObject: JObject = "dynamicEntityId" -> dynamicEntityId
@ -193,11 +200,11 @@ class DynamicEntityTest extends V400ServerSetup {
{
// update a not exists DynamicEntity
val request400 = (v4_0_0_Request / "management" / "dynamic_entities" / "not-exists-id" ).PUT <@(user1)
val response400 = makePutRequest(request400, compactRender(updateRequest))
val request404 = (v4_0_0_Request / "management" / "dynamic_entities" / "not-exists-id" ).PUT <@(user1)
val response404 = makePutRequest(request404, compactRender(updateRequest))
Then("We should get a 400")
response400.code should equal(400)
response400.body.extract[ErrorMessage].message should startWith (DynamicEntityNotFoundByDynamicEntityId)
response404.code should equal(404)
response404.body.extract[ErrorMessage].message should startWith (DynamicEntityNotFoundByDynamicEntityId)
}
{

View File

@ -1165,4 +1165,13 @@ case class OutBoundDynamicEntityProcess (outboundAdapterCallContext: OutboundAda
entityName: String,
requestBody: Option[JObject],
entityId: Option[String]) extends TopicTrait
case class InBoundDynamicEntityProcess (inboundAdapterCallContext: InboundAdapterCallContext, status: Status, data: JValue) extends InBoundTrait[JValue]
case class InBoundDynamicEntityProcess (inboundAdapterCallContext: InboundAdapterCallContext, status: Status, data: JValue) extends InBoundTrait[JValue]
// because swagger generate not support JValue type, so here supply too xxxDoc TO generate correct request and response body example
case class FooBar(name: String, number: Int, fooBarId: Option[String] = None)
case class OutBoundDynamicEntityProcessDoc (outboundAdapterCallContext: OutboundAdapterCallContext,
operation: DynamicEntityOperation,
entityName: String,
requestBody: Option[FooBar],
entityId: Option[String]) extends TopicTrait
case class InBoundDynamicEntityProcessDoc (inboundAdapterCallContext: InboundAdapterCallContext, status: Status, data: FooBar) extends InBoundTrait[FooBar]

View File

@ -1,6 +1,6 @@
package com.openbankproject.commons.util
import net.liftweb.common.{Box, Empty, Full}
import net.liftweb.common.{Box, Empty, Failure, Full}
import scala.concurrent.ExecutionContext.Implicits.global
import scala.annotation.tailrec
@ -10,6 +10,7 @@ import scala.language.postfixOps
import scala.reflect.runtime.universe._
import scala.reflect.runtime.{universe => ru}
import scala.util.Success
import net.liftweb.json.JValue
object ReflectUtils {
private[this] val mirror: ru.Mirror = ru.runtimeMirror(getClass().getClassLoader)
@ -506,9 +507,18 @@ object ReflectUtils {
def toValueObject(t: Any): Any = {
t match {
case null => null
case v: JValue => v
case Some(v) => toValueObject(v)
case Full(v) => toValueObject(v)
case None|Empty => null
case v: Failure => v
case Left(v) => Left(toValueObject(v))
case v: Right[_, _] => v.map(toValueObject)
case v: Success[_]=> v.map(toValueObject)
case scala.util.Failure(v) => v
case it: Iterable[_] => it.map(toValueObject)
case array: Array[_] => array.map(toValueObject)
case v if(getType(v).typeSymbol.asClass.isCaseClass) => v
case v if getType(v).typeSymbol.asClass.isCaseClass => v
case other => {
val mirrorObj = mirror.reflect(other)
mirrorObj.symbol.info.decls