From c8c1e786e1a0efe219901bc9edba6e58628862aa Mon Sep 17 00:00:00 2001 From: hongwei Date: Thu, 26 Mar 2020 08:17:04 +0100 Subject: [PATCH 01/35] added the dynamicEndpoints endpoints #support Create, Get and GetAll now. --- .../main/scala/bootstrap/liftweb/Boot.scala | 2 + .../main/scala/code/api/util/ApiRole.scala | 15 ++ .../src/main/scala/code/api/util/ApiTag.scala | 1 + .../scala/code/api/util/ExampleValue.scala | 243 ++++++++++++++++++ .../main/scala/code/api/util/NewStyle.scala | 25 ++ .../scala/code/api/v4_0_0/APIMethods400.scala | 126 ++++++++- .../scala/code/bankconnectors/Connector.scala | 13 + .../bankconnectors/LocalMappedConnector.scala | 18 +- .../DynamicEndpointProvider.scala | 34 +++ .../MapppedDynamicEndpointProvider.scala | 40 +++ 10 files changed, 513 insertions(+), 4 deletions(-) create mode 100644 obp-api/src/main/scala/code/dynamicEndpoint/DynamicEndpointProvider.scala create mode 100644 obp-api/src/main/scala/code/dynamicEndpoint/MapppedDynamicEndpointProvider.scala diff --git a/obp-api/src/main/scala/bootstrap/liftweb/Boot.scala b/obp-api/src/main/scala/bootstrap/liftweb/Boot.scala index d9b8650fb..df6831dad 100644 --- a/obp-api/src/main/scala/bootstrap/liftweb/Boot.scala +++ b/obp-api/src/main/scala/bootstrap/liftweb/Boot.scala @@ -31,6 +31,7 @@ import java.util.{Locale, TimeZone} import code.CustomerDependants.MappedCustomerDependant import code.DynamicData.DynamicData +import code.DynamicEndpoint.DynamicEndpoint import code.accountapplication.MappedAccountApplication import code.accountattribute.MappedAccountAttribute import code.accountholders.MapperAccountHolders @@ -706,6 +707,7 @@ object ToSchemify { Authorisation, DynamicEntity, DynamicData, + DynamicEndpoint, AccountIdMapping, DirectDebit, StandingOrder diff --git a/obp-api/src/main/scala/code/api/util/ApiRole.scala b/obp-api/src/main/scala/code/api/util/ApiRole.scala index c8752b6f0..4522b52c1 100644 --- a/obp-api/src/main/scala/code/api/util/ApiRole.scala +++ b/obp-api/src/main/scala/code/api/util/ApiRole.scala @@ -406,6 +406,21 @@ object ApiRole { case class CanDeleteDynamicEntity(requiresBankId: Boolean = false) extends ApiRole lazy val canDeleteDynamicEntity = CanDeleteDynamicEntity() + + case class CanGetDynamicEndpoint(requiresBankId: Boolean = false) extends ApiRole + lazy val canGetDynamicEndpoint = CanGetDynamicEndpoint() + + case class CanGetDynamicEndpoints(requiresBankId: Boolean = false) extends ApiRole + lazy val canGetDynamicEndpoints = CanGetDynamicEndpoints() + + case class CanCreateDynamicEndpoint(requiresBankId: Boolean = false) extends ApiRole + lazy val canCreateDynamicEndpoint = CanCreateDynamicEndpoint() + + case class CanUpdateDynamicEndpoint(requiresBankId: Boolean = false) extends ApiRole + lazy val canUpdateDynamicEndpoint = CanUpdateDynamicEndpoint() + + case class CanDeleteDynamicEndpoint(requiresBankId: Boolean = false) extends ApiRole + lazy val canDeleteDynamicEndpoint = CanDeleteDynamicEndpoint() case class CanCreateResetPasswordUrl(requiresBankId: Boolean = false) extends ApiRole lazy val canCreateResetPasswordUrl = CanCreateResetPasswordUrl() diff --git a/obp-api/src/main/scala/code/api/util/ApiTag.scala b/obp-api/src/main/scala/code/api/util/ApiTag.scala index 4498a2105..38f8fca72 100644 --- a/obp-api/src/main/scala/code/api/util/ApiTag.scala +++ b/obp-api/src/main/scala/code/api/util/ApiTag.scala @@ -68,6 +68,7 @@ object ApiTag { val apiTagMethodRouting = ResourceDocTag("Method-Routing") val apiTagWebUiProps = ResourceDocTag("WebUi-Props") val apiTagDynamicEntity= ResourceDocTag("Dynamic-Entity") + val apiTagDynamicEndpoint= ResourceDocTag("Dynamic-Endpoint") // To mark the Berlin Group APIs suggested order of implementation val apiTagBerlinGroupM = ResourceDocTag("Berlin-Group-M") diff --git a/obp-api/src/main/scala/code/api/util/ExampleValue.scala b/obp-api/src/main/scala/code/api/util/ExampleValue.scala index d72689ae0..bcaf617e1 100644 --- a/obp-api/src/main/scala/code/api/util/ExampleValue.scala +++ b/obp-api/src/main/scala/code/api/util/ExampleValue.scala @@ -1,6 +1,7 @@ package code.api.util +import code.DynamicEndpoint.DynamicEndpointSwagger import code.api.util.Glossary.{glossaryItems, makeGlossaryItem} import code.dynamicEntity.{DynamicEntityDefinition, DynamicEntityFooBar, DynamicEntityFullBarFields, DynamicEntityIntTypeExample, DynamicEntityStringTypeExample} import com.openbankproject.commons.model.enums.DynamicEntityFieldType @@ -358,6 +359,248 @@ object ExampleValue { ) lazy val dynamicEntityResponseBodyExample = dynamicEntityRequestBodyExample.copy(dynamicEntityId = Some("dynamic-entity-id")) + + lazy val dynamicEndpointRequestBodyExample = DynamicEndpointSwagger(None, """{ + | "swagger": "2.0", + | "info": { + | "version": "0.0.1", + | "title": "Portus EVS sandbox demo API", + | "description": "Portus EVS sandbox demo API", + | "contact": { + | "name": "Digital & FinTech, Grant Thornton", + | "email": "peng.xu@ie.gt.com", + | "url": "https://www.tesobe.com/" + | } + | }, + | "host": "localhost:8080", + | "basePath": "/user", + | "schemes": [ + | "http" + | ], + | "consumes": [ + | "application/json" + | ], + | "produces": [ + | "application/json" + | ], + | "paths": { + | "/save": { + | "post": { + | "parameters": [ + | { + | "name": "body", + | "in": "body", + | "required": true, + | "schema": { + | "$ref": "#/definitions/user" + | } + | } + | ], + | "responses": { + | "201": { + | "description": "create user successful and return created user object", + | "schema": { + | "$ref": "#/definitions/user" + | } + | }, + | "500": { + | "description": "unexpected error", + | "schema": { + | "$ref": "#/responses/unexpectedError" + | } + | } + | } + | } + | }, + | "/getById/{userId}": { + | "get": { + | "description": "get reuested user by user ID", + | "parameters": [ + | { + | "$ref": "#/parameters/userId" + | } + | ], + | "consumes": [], + | "responses": { + | "200": { + | "description": "the successful get requested user by user ID", + | "schema": { + | "$ref": "#/definitions/user" + | } + | }, + | "400": { + | "description": "bad request", + | "schema": { + | "$ref": "#/responses/invalidRequest" + | } + | }, + | "404": { + | "description": "user not found", + | "schema": { + | "$ref": "#/definitions/APIError" + | } + | }, + | "500": { + | "description": "unexpected error", + | "schema": { + | "$ref": "#/responses/unexpectedError" + | } + | } + | } + | } + | }, + | "/listUsers": { + | "get": { + | "description": "get list of users", + | "consumes": [], + | "responses": { + | "200": { + | "description": "get all users", + | "schema": { + | "$ref": "#/definitions/users" + | } + | }, + | "404": { + | "description": "user not found", + | "schema": { + | "$ref": "#/definitions/APIError" + | } + | } + | } + | } + | }, + | "/updateUser": { + | "put": { + | "parameters": [ + | { + | "name": "body", + | "in": "body", + | "required": true, + | "schema": { + | "$ref": "#/definitions/user" + | } + | } + | ], + | "responses": { + | "200": { + | "description": "create user successful and return created user object", + | "schema": { + | "$ref": "#/definitions/user" + | } + | }, + | "500": { + | "description": "unexpected error", + | "schema": { + | "$ref": "#/responses/unexpectedError" + | } + | } + | } + | } + | }, + | "/delete/{userId}": { + | "delete": { + | "description": "delete user by user ID", + | "parameters": [ + | { + | "$ref": "#/parameters/userId" + | } + | ], + | "consumes": [], + | "responses": { + | "204": { + | "description": "the successful delete user by user ID" + | }, + | "400": { + | "description": "bad request", + | "schema": { + | "$ref": "#/responses/invalidRequest" + | } + | }, + | "500": { + | "description": "unexpected error", + | "schema": { + | "$ref": "#/responses/unexpectedError" + | } + | } + | } + | } + | } + | }, + | "definitions": { + | "user": { + | "type": "object", + | "properties": { + | "id": { + | "type": "integer", + | "description": "user ID" + | }, + | "first_name": { + | "type": "string" + | }, + | "last_name": { + | "type": "string" + | }, + | "age": { + | "type": "integer" + | }, + | "career": { + | "type": "string" + | } + | }, + | "required": [ + | "first_name", + | "last_name", + | "age" + | ] + | }, + | "users": { + | "description": "array of users", + | "type": "array", + | "items": { + | "$ref": "#/definitions/user" + | } + | }, + | "APIError": { + | "description": "content any error from API", + | "type": "object", + | "properties": { + | "errorCode": { + | "description": "content error code relate to API", + | "type": "string" + | }, + | "errorMessage": { + | "description": "content user-friendly error message", + | "type": "string" + | } + | } + | } + | }, + | "responses": { + | "unexpectedError": { + | "description": "unexpected error", + | "schema": { + | "$ref": "#/definitions/APIError" + | } + | }, + | "invalidRequest": { + | "description": "invalid request", + | "schema": { + | "$ref": "#/definitions/APIError" + | } + | } + | }, + | "parameters": { + | "userId": { + | "name": "userId", + | "in": "path", + | "required": true, + | "type": "string", + | "description": "user ID" + | } + | } + |} + |""".stripMargin) + lazy val dynamicEndpointResponseBodyExample = dynamicEndpointRequestBodyExample.copy(dynamicEndpointId = Some("dynamic-endpoint-id")) } diff --git a/obp-api/src/main/scala/code/api/util/NewStyle.scala b/obp-api/src/main/scala/code/api/util/NewStyle.scala index aabaecc47..cca431560 100644 --- a/obp-api/src/main/scala/code/api/util/NewStyle.scala +++ b/obp-api/src/main/scala/code/api/util/NewStyle.scala @@ -4,6 +4,7 @@ import java.util.Date import java.util.UUID.randomUUID import code.DynamicData.DynamicDataProvider +import code.DynamicEndpoint.DynamicEndpointT import code.api.APIFailureNewStyle import code.api.cache.Caching import code.api.util.APIUtil.{OBPReturnType, canGrantAccessToViewCommon, canRevokeAccessToViewCommon, connectorEmptyResponse, createHttpParamsByUrlFuture, createQueriesByHttpParamsFuture, fullBoxOrException, generateUUID, unboxFull, unboxFullOrFail} @@ -1996,5 +1997,29 @@ object NewStyle { getConnectorByName(connectorName).flatMap(_.implementedMethods.get(methodName)) } + def createDynamicEndpoint(swaggerString: String, callContext: Option[CallContext]): OBPReturnType[DynamicEndpointT] = { + Connector.connector.vend.createDynamicEndpoint( + swaggerString, + callContext + ) map { + i => (connectorEmptyResponse(i._1, callContext), i._2) + } + } + + def getDynamicEndpoint(dynamicEndpointId: String, callContext: Option[CallContext]): OBPReturnType[DynamicEndpointT] = { + Connector.connector.vend.getDynamicEndpoint( + dynamicEndpointId, + callContext + ) map { + i => (connectorEmptyResponse(i._1, callContext), i._2) + } + } + + def getDynamicEndpoints(callContext: Option[CallContext]): OBPReturnType[List[DynamicEndpointT]] = { + Connector.connector.vend.getDynamicEndpoints( + callContext + ) + } + } } diff --git a/obp-api/src/main/scala/code/api/v4_0_0/APIMethods400.scala b/obp-api/src/main/scala/code/api/v4_0_0/APIMethods400.scala index 48629ad0d..96890dff2 100644 --- a/obp-api/src/main/scala/code/api/v4_0_0/APIMethods400.scala +++ b/obp-api/src/main/scala/code/api/v4_0_0/APIMethods400.scala @@ -3,6 +3,7 @@ package code.api.v4_0_0 import java.util.Date import code.DynamicData.DynamicData +import code.DynamicEndpoint.{DynamicEndpointCommons, DynamicEndpointSwagger} import code.accountattribute.AccountAttributeX import code.api.ChargePolicy import code.api.ResourceDocs1_4_0.SwaggerDefinitionsJSON._ @@ -10,7 +11,7 @@ import code.api.util.APIUtil.{fullBoxOrException, _} import code.api.util.ApiRole._ import code.api.util.ApiTag._ import code.api.util.ErrorMessages._ -import code.api.util.ExampleValue.{dynamicEntityRequestBodyExample, dynamicEntityResponseBodyExample} +import code.api.util.ExampleValue.{dynamicEndpointRequestBodyExample, dynamicEndpointResponseBodyExample, dynamicEntityRequestBodyExample, dynamicEntityResponseBodyExample} import code.api.util.NewStyle.HttpCode import code.api.util._ import code.api.v1_2_1.{JSONFactory, PostTransactionTagJSON} @@ -2721,7 +2722,130 @@ trait APIMethods400 { } } } + + resourceDocs += ResourceDoc( + createDynamicEndpoint, + implementedInApiVersion, + nameOf(createDynamicEndpoint), + "POST", + "/management/dynamic_endpoints", + "Create DynamicEndpoint", + s"""Create a DynamicEndpoint. + | + | + |${authenticationRequiredMessage(true)} + | + |Create one DynamicEndpoint, + | + |""", + dynamicEndpointRequestBodyExample, + dynamicEndpointResponseBodyExample, + List( + $UserNotLoggedIn, + UserHasMissingRoles, + InvalidJsonFormat, + UnknownError + ), + Catalogs(notCore, notPSD2, notOBWG), + List(apiTagDynamicEndpoint, apiTagApi, apiTagNewStyle), + Some(List(canCreateDynamicEndpoint))) + + lazy val createDynamicEndpoint: OBPEndpoint = { + case "management" :: "dynamic_endpoints" :: Nil JsonPost json -> _ => { + cc => + for { + postedJson <- NewStyle.function.tryons(InvalidJsonFormat, 400, cc.callContext) { + json.extract[DynamicEndpointSwagger] + } + (dynamicEndpoint, callContext) <- NewStyle.function.createDynamicEndpoint(postedJson.swaggerString, cc.callContext) + } yield { + val commonsData: DynamicEndpointCommons = dynamicEndpoint + (commonsData, HttpCode.`201`(callContext)) + } + } + } + + + resourceDocs += ResourceDoc( + getDynamicEndpoint, + implementedInApiVersion, + nameOf(getDynamicEndpoint), + "GET", + "/management/dynamic_endpoints/DYNAMIC_ENDPOINT_ID", + "Get DynamicEndpoint", + s"""Get a DynamicEndpoint. + | + | + |${authenticationRequiredMessage(true)} + | + |Get one DynamicEndpoint, + | + |""", + emptyObjectJson, + dynamicEndpointResponseBodyExample, + List( + $UserNotLoggedIn, + UserHasMissingRoles, + InvalidJsonFormat, + UnknownError + ), + Catalogs(notCore, notPSD2, notOBWG), + List(apiTagDynamicEndpoint, apiTagApi, apiTagNewStyle), + Some(List(canGetDynamicEndpoint))) + + lazy val getDynamicEndpoint: OBPEndpoint = { + case "management" :: "dynamic_endpoints" :: dynamicEndpointId :: Nil JsonGet req => { + cc => + for { + (dynamicEndpoint, callContext) <- NewStyle.function.getDynamicEndpoint(dynamicEndpointId, cc.callContext) + } yield { + val commonsData: DynamicEndpointCommons = dynamicEndpoint + (commonsData, HttpCode.`201`(callContext)) + } + } + } + resourceDocs += ResourceDoc( + getDynamicEndpoints, + implementedInApiVersion, + nameOf(getDynamicEndpoints), + "GET", + "/management/dynamic_endpoints", + "Get DynamicEndpoints", + s"""Get DynamicEndpoints. + | + | + |${authenticationRequiredMessage(true)} + | + |Get DynamicEndpoints, + | + |""", + emptyObjectJson, + ListResult( + "dynamic_entities", + List(dynamicEndpointResponseBodyExample) + ), + List( + $UserNotLoggedIn, + UserHasMissingRoles, + InvalidJsonFormat, + UnknownError + ), + Catalogs(notCore, notPSD2, notOBWG), + List(apiTagDynamicEndpoint, apiTagApi, apiTagNewStyle), + Some(List(canGetDynamicEndpoints))) + + lazy val getDynamicEndpoints: OBPEndpoint = { + case "management" :: "dynamic_endpoints" :: Nil JsonGet req => { + cc => + for { + (dynamicEndpoints, callContext) <- NewStyle.function.getDynamicEndpoints(cc.callContext) + } yield { + val listCommons: List[DynamicEndpointCommons] = dynamicEndpoints + (ListResult("dynamic_entities", listCommons), HttpCode.`200`(cc.callContext)) + } + } + } } diff --git a/obp-api/src/main/scala/code/bankconnectors/Connector.scala b/obp-api/src/main/scala/code/bankconnectors/Connector.scala index 7a882b720..12a7ce871 100644 --- a/obp-api/src/main/scala/code/bankconnectors/Connector.scala +++ b/obp-api/src/main/scala/code/bankconnectors/Connector.scala @@ -3,6 +3,7 @@ package code.bankconnectors import java.util.Date import java.util.UUID.randomUUID +import code.DynamicEndpoint.DynamicEndpointT import code.accountholders.{AccountHolders, MapperAccountHolders} import code.api.{APIFailure, APIFailureNewStyle} import code.api.cache.Caching @@ -2177,4 +2178,16 @@ trait Connector extends MdcLoggable { callContext: Option[CallContext]): OBPReturnType[Box[StandingOrderTrait]] = Future { (Failure(setUnimplementedError), callContext) } + + def createDynamicEndpoint(swaggerString: String, callContext: Option[CallContext]): OBPReturnType[Box[DynamicEndpointT]] = Future { + (Failure(setUnimplementedError), callContext) + } + + def getDynamicEndpoint(dynamicEndpointId: String, callContext: Option[CallContext]): OBPReturnType[Box[DynamicEndpointT]] = Future { + (Failure(setUnimplementedError), callContext) + } + + def getDynamicEndpoints(callContext: Option[CallContext]): OBPReturnType[List[DynamicEndpointT]] = Future { + (List.empty[DynamicEndpointT], callContext) + } } diff --git a/obp-api/src/main/scala/code/bankconnectors/LocalMappedConnector.scala b/obp-api/src/main/scala/code/bankconnectors/LocalMappedConnector.scala index 569a8aef5..576bd181c 100644 --- a/obp-api/src/main/scala/code/bankconnectors/LocalMappedConnector.scala +++ b/obp-api/src/main/scala/code/bankconnectors/LocalMappedConnector.scala @@ -4,12 +4,13 @@ import java.util.Date import java.util.UUID.randomUUID import scala.concurrent.duration._ import code.DynamicData.DynamicDataProvider +import code.DynamicEndpoint.{DynamicEndpointProvider, DynamicEndpointT} import code.accountapplication.AccountApplicationX import code.accountattribute.AccountAttributeX import code.accountholders.{AccountHolders, MapperAccountHolders} import code.api.ResourceDocs1_4_0.SwaggerDefinitionsJSON import code.api.cache.Caching -import code.api.util.APIUtil.{OBPReturnType, DateWithMsFormat, generateUUID, hasEntitlement, isValidCurrencyISOCode, saveConnectorMetric, stringOrNull, unboxFullOrFail} +import code.api.util.APIUtil.{DateWithMsFormat, OBPReturnType, generateUUID, hasEntitlement, isValidCurrencyISOCode, saveConnectorMetric, stringOrNull, unboxFullOrFail} import code.api.util.ApiRole.canCreateAnyTransactionRequest import code.api.util.ErrorMessages._ import code.api.util._ @@ -73,7 +74,7 @@ import net.liftweb.common._ import net.liftweb.json import net.liftweb.json.{JArray, JBool, JObject, JValue} import net.liftweb.mapper.{By, _} -import net.liftweb.util.Helpers.{tryo,now, time, hours} +import net.liftweb.util.Helpers.{hours, now, time, tryo} import net.liftweb.util.Mailer import net.liftweb.util.Mailer.{From, PlainMailBodyType, Subject, To} import org.apache.commons.lang3.StringUtils @@ -84,7 +85,6 @@ import scala.collection.immutable.{List, Nil} import com.openbankproject.commons.ExecutionContext.Implicits.global import scala.concurrent._ - import scala.language.postfixOps import scala.math.{BigDecimal, BigInt} import scala.util.Random @@ -4001,6 +4001,18 @@ object LocalMappedConnector extends Connector with MdcLoggable { } yield { trtc } Full(res) } + + override def createDynamicEndpoint(swaggerString: String, callContext: Option[CallContext]): OBPReturnType[Box[DynamicEndpointT]] = Future { + (DynamicEndpointProvider.connectorMethodProvider.vend.create(swaggerString), callContext) + } + + override def getDynamicEndpoint(dynamicEndpointId: String, callContext: Option[CallContext]): OBPReturnType[Box[DynamicEndpointT]] = Future { + (DynamicEndpointProvider.connectorMethodProvider.vend.get(dynamicEndpointId), callContext) + } + + override def getDynamicEndpoints(callContext: Option[CallContext]): OBPReturnType[List[DynamicEndpointT]] = Future { + (DynamicEndpointProvider.connectorMethodProvider.vend.getAll(), callContext) + } } diff --git a/obp-api/src/main/scala/code/dynamicEndpoint/DynamicEndpointProvider.scala b/obp-api/src/main/scala/code/dynamicEndpoint/DynamicEndpointProvider.scala new file mode 100644 index 000000000..229686706 --- /dev/null +++ b/obp-api/src/main/scala/code/dynamicEndpoint/DynamicEndpointProvider.scala @@ -0,0 +1,34 @@ +package code.DynamicEndpoint + +import com.openbankproject.commons.model.{Converter, JsonFieldReName} +import net.liftweb.common.Box +import net.liftweb.util.SimpleInjector + +object DynamicEndpointProvider extends SimpleInjector { + + val connectorMethodProvider = new Inject(buildOne _) {} + + def buildOne: MappedDynamicEndpointProvider.type = MappedDynamicEndpointProvider +} + +trait DynamicEndpointT { + def dynamicEndpointId: Option[String] + def swaggerString: String +} + +case class DynamicEndpointCommons( + dynamicEndpointId: Option[String] = None, + swaggerString: String + ) extends DynamicEndpointT with JsonFieldReName + +object DynamicEndpointCommons extends Converter[DynamicEndpointT, DynamicEndpointCommons] + +case class DynamicEndpointSwagger(dynamicEndpointId: Option[String] = None, swaggerString: String) + +trait DynamicEndpointProvider { + def create(swaggerString: String): Box[DynamicEndpointT] + def update(dynamicEndpointId: String, swaggerString: String): Box[DynamicEndpointT] + def get(dynamicEndpointId: String): Box[DynamicEndpointT] + def getAll(): List[DynamicEndpointT] + def delete(dynamicEndpointId: String): Boolean +} \ No newline at end of file diff --git a/obp-api/src/main/scala/code/dynamicEndpoint/MapppedDynamicEndpointProvider.scala b/obp-api/src/main/scala/code/dynamicEndpoint/MapppedDynamicEndpointProvider.scala new file mode 100644 index 000000000..e7ccbd6e4 --- /dev/null +++ b/obp-api/src/main/scala/code/dynamicEndpoint/MapppedDynamicEndpointProvider.scala @@ -0,0 +1,40 @@ +package code.DynamicEndpoint + +import code.api.util.CustomJsonFormats +import code.util.MappedUUID +import net.liftweb.common.Box +import net.liftweb.mapper._ +import net.liftweb.util.Helpers.tryo + +object MappedDynamicEndpointProvider extends DynamicEndpointProvider with CustomJsonFormats{ + override def create(swaggerString: String): Box[DynamicEndpointT] = { + tryo{DynamicEndpoint.create.SwaggerString(swaggerString).saveMe()} + } + override def update(dynamicEndpointId: String, swaggerString: String): Box[DynamicEndpointT] = { + DynamicEndpoint.find(By(DynamicEndpoint.DynamicEndpointId, dynamicEndpointId)).map(_.SwaggerString(swaggerString).saveMe()) + } + + override def get(dynamicEndpointId: String): Box[DynamicEndpointT] = DynamicEndpoint.find(By(DynamicEndpoint.DynamicEndpointId, dynamicEndpointId)) + + override def getAll(): List[DynamicEndpointT] = DynamicEndpoint.findAll() + + override def delete(dynamicEndpointId: String): Boolean = DynamicEndpoint.bulkDelete_!!(By(DynamicEndpoint.DynamicEndpointId, dynamicEndpointId)) + +} + +class DynamicEndpoint extends DynamicEndpointT with LongKeyedMapper[DynamicEndpoint] with IdPK { + + override def getSingleton = DynamicEndpoint + + object DynamicEndpointId extends MappedUUID(this) + + object SwaggerString extends MappedText(this) + + override def dynamicEndpointId: Option[String] = Option(DynamicEndpointId.get) + override def swaggerString: String = SwaggerString.get +} + +object DynamicEndpoint extends DynamicEndpoint with LongKeyedMetaMapper[DynamicEndpoint] { + override def dbIndexes = UniqueIndex(DynamicEndpointId) :: super.dbIndexes +} + From cd255de3dad1c787f98e7fe2cadc094e3bfa322c Mon Sep 17 00:00:00 2001 From: hongwei Date: Thu, 26 Mar 2020 12:06:48 +0100 Subject: [PATCH 02/35] remove the account_attributes for the create account endpoint --- .../code/api/ResourceDocs1_4_0/SwaggerDefinitionsJSON.scala | 3 +-- obp-api/src/main/scala/code/api/v3_1_0/JSONFactory3.1.0.scala | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/obp-api/src/main/scala/code/api/ResourceDocs1_4_0/SwaggerDefinitionsJSON.scala b/obp-api/src/main/scala/code/api/ResourceDocs1_4_0/SwaggerDefinitionsJSON.scala index fbfe7143f..51fb7b727 100644 --- a/obp-api/src/main/scala/code/api/ResourceDocs1_4_0/SwaggerDefinitionsJSON.scala +++ b/obp-api/src/main/scala/code/api/ResourceDocs1_4_0/SwaggerDefinitionsJSON.scala @@ -3631,8 +3631,7 @@ object SwaggerDefinitionsJSON { product_code = accountTypeExample.value, balance = amountOfMoneyJsonV121, branch_id = branchIdExample.value, - account_routing = accountRoutingJsonV121, - account_attributes= List(accountAttributeResponseJson) + account_routing = accountRoutingJsonV121 ) val postAccountAccessJsonV400 = PostAccountAccessJsonV400(userIdExample.value, PostViewJsonV400(ExampleValue.viewIdExample.value, true)) diff --git a/obp-api/src/main/scala/code/api/v3_1_0/JSONFactory3.1.0.scala b/obp-api/src/main/scala/code/api/v3_1_0/JSONFactory3.1.0.scala index 0e4bb2058..fe33545cc 100644 --- a/obp-api/src/main/scala/code/api/v3_1_0/JSONFactory3.1.0.scala +++ b/obp-api/src/main/scala/code/api/v3_1_0/JSONFactory3.1.0.scala @@ -664,8 +664,7 @@ case class CreateAccountRequestJsonV310( product_code : String, balance : AmountOfMoneyJsonV121, branch_id : String, - account_routing: AccountRoutingJsonV121, - account_attributes: List[AccountAttributeResponseJson] + account_routing: AccountRoutingJsonV121 ) case class CreateAccountResponseJsonV310( From 93a346dee533f444b0c5b58a9bd428e4ea7a3372 Mon Sep 17 00:00:00 2001 From: hongwei Date: Thu, 26 Mar 2020 12:17:22 +0100 Subject: [PATCH 03/35] modify dynamic_entities --> dynamic-entities and dynamic_endpoints --> dynamic-endpoints in the URL --- .../scala/code/api/util/ErrorMessages.scala | 2 +- .../scala/code/api/v4_0_0/APIMethods400.scala | 30 +++++++++---------- .../code/api/v4_0_0/DynamicEntityTest.scala | 28 ++++++++--------- 3 files changed, 30 insertions(+), 30 deletions(-) diff --git a/obp-api/src/main/scala/code/api/util/ErrorMessages.scala b/obp-api/src/main/scala/code/api/util/ErrorMessages.scala index f41fc883f..9bfc85698 100644 --- a/obp-api/src/main/scala/code/api/util/ErrorMessages.scala +++ b/obp-api/src/main/scala/code/api/util/ErrorMessages.scala @@ -41,7 +41,7 @@ object ErrorMessages { val WebUiPropsNotFound = "OBP-08002: WebUi props not found. Please specify a valid value for WEB_UI_PROPS_ID." // DynamicEntity Exceptions (OBP-09XXX) - val DynamicEntityNotFoundByDynamicEntityId = "OBP-09001: DynamicEntity not found. Please specify a valid value for dynamic_entity_id." + val DynamicEntityNotFoundByDynamicEntityId = "OBP-09001: DynamicEntity not found. Please specify a valid value for DYNAMIC_ENTITY_ID." 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." diff --git a/obp-api/src/main/scala/code/api/v4_0_0/APIMethods400.scala b/obp-api/src/main/scala/code/api/v4_0_0/APIMethods400.scala index 96890dff2..379583fa4 100644 --- a/obp-api/src/main/scala/code/api/v4_0_0/APIMethods400.scala +++ b/obp-api/src/main/scala/code/api/v4_0_0/APIMethods400.scala @@ -812,7 +812,7 @@ trait APIMethods400 { implementedInApiVersion, nameOf(getDynamicEntities), "GET", - "/management/dynamic_entities", + "/management/dynamic-entities", "Get DynamicEntities", s"""Get the all DynamicEntities.""", emptyObjectJson, @@ -832,7 +832,7 @@ trait APIMethods400 { lazy val getDynamicEntities: OBPEndpoint = { - case "management" :: "dynamic_entities" :: Nil JsonGet req => { + case "management" :: "dynamic-entities" :: Nil JsonGet req => { cc => for { dynamicEntities <- Future(NewStyle.function.getDynamicEntities()) @@ -849,7 +849,7 @@ trait APIMethods400 { implementedInApiVersion, nameOf(createDynamicEntity), "POST", - "/management/dynamic_entities", + "/management/dynamic-entities", "Create DynamicEntity", s"""Create a DynamicEntity. | @@ -882,7 +882,7 @@ trait APIMethods400 { Some(List(canCreateDynamicEntity))) lazy val createDynamicEntity: OBPEndpoint = { - case "management" :: "dynamic_entities" :: Nil JsonPost json -> _ => { + case "management" :: "dynamic-entities" :: Nil JsonPost json -> _ => { cc => val dynamicEntity = DynamicEntityCommons(json.asInstanceOf[JObject], None) for { @@ -900,7 +900,7 @@ trait APIMethods400 { implementedInApiVersion, nameOf(updateDynamicEntity), "PUT", - "/management/dynamic_entities/DYNAMIC_ENTITY_ID", + "/management/dynamic-entities/DYNAMIC_ENTITY_ID", "Update DynamicEntity", s"""Update a DynamicEntity. | @@ -933,7 +933,7 @@ trait APIMethods400 { Some(List(canUpdateDynamicEntity))) lazy val updateDynamicEntity: OBPEndpoint = { - case "management" :: "dynamic_entities" :: dynamicEntityId :: Nil JsonPut json -> _ => { + case "management" :: "dynamic-entities" :: dynamicEntityId :: Nil JsonPut json -> _ => { cc => for { // Check whether there are uploaded data, only if no uploaded data allow to update DynamicEntity. @@ -958,7 +958,7 @@ trait APIMethods400 { implementedInApiVersion, nameOf(deleteDynamicEntity), "DELETE", - "/management/dynamic_entities/DYNAMIC_ENTITY_ID", + "/management/dynamic-entities/DYNAMIC_ENTITY_ID", "Delete DynamicEntity", s"""Delete a DynamicEntity specified by DYNAMIC_ENTITY_ID. | @@ -975,7 +975,7 @@ trait APIMethods400 { Some(List(canDeleteDynamicEntity))) lazy val deleteDynamicEntity: OBPEndpoint = { - case "management" :: "dynamic_entities" :: dynamicEntityId :: Nil JsonDelete _ => { + case "management" :: "dynamic-entities" :: dynamicEntityId :: Nil JsonDelete _ => { cc => for { // Check whether there are uploaded data, only if no uploaded data allow to delete DynamicEntity. @@ -2728,7 +2728,7 @@ trait APIMethods400 { implementedInApiVersion, nameOf(createDynamicEndpoint), "POST", - "/management/dynamic_endpoints", + "/management/dynamic-endpoints", "Create DynamicEndpoint", s"""Create a DynamicEndpoint. | @@ -2751,7 +2751,7 @@ trait APIMethods400 { Some(List(canCreateDynamicEndpoint))) lazy val createDynamicEndpoint: OBPEndpoint = { - case "management" :: "dynamic_endpoints" :: Nil JsonPost json -> _ => { + case "management" :: "dynamic-endpoints" :: Nil JsonPost json -> _ => { cc => for { postedJson <- NewStyle.function.tryons(InvalidJsonFormat, 400, cc.callContext) { @@ -2771,7 +2771,7 @@ trait APIMethods400 { implementedInApiVersion, nameOf(getDynamicEndpoint), "GET", - "/management/dynamic_endpoints/DYNAMIC_ENDPOINT_ID", + "/management/dynamic-endpoints/DYNAMIC_ENDPOINT_ID", "Get DynamicEndpoint", s"""Get a DynamicEndpoint. | @@ -2794,7 +2794,7 @@ trait APIMethods400 { Some(List(canGetDynamicEndpoint))) lazy val getDynamicEndpoint: OBPEndpoint = { - case "management" :: "dynamic_endpoints" :: dynamicEndpointId :: Nil JsonGet req => { + case "management" :: "dynamic-endpoints" :: dynamicEndpointId :: Nil JsonGet req => { cc => for { (dynamicEndpoint, callContext) <- NewStyle.function.getDynamicEndpoint(dynamicEndpointId, cc.callContext) @@ -2810,7 +2810,7 @@ trait APIMethods400 { implementedInApiVersion, nameOf(getDynamicEndpoints), "GET", - "/management/dynamic_endpoints", + "/management/dynamic-endpoints", "Get DynamicEndpoints", s"""Get DynamicEndpoints. | @@ -2822,7 +2822,7 @@ trait APIMethods400 { |""", emptyObjectJson, ListResult( - "dynamic_entities", + "dynamic-entities", List(dynamicEndpointResponseBodyExample) ), List( @@ -2836,7 +2836,7 @@ trait APIMethods400 { Some(List(canGetDynamicEndpoints))) lazy val getDynamicEndpoints: OBPEndpoint = { - case "management" :: "dynamic_endpoints" :: Nil JsonGet req => { + case "management" :: "dynamic-endpoints" :: Nil JsonGet req => { cc => for { (dynamicEndpoints, callContext) <- NewStyle.function.getDynamicEndpoints(cc.callContext) diff --git a/obp-api/src/test/scala/code/api/v4_0_0/DynamicEntityTest.scala b/obp-api/src/test/scala/code/api/v4_0_0/DynamicEntityTest.scala index 6cebabc96..a0c13de9f 100644 --- a/obp-api/src/test/scala/code/api/v4_0_0/DynamicEntityTest.scala +++ b/obp-api/src/test/scala/code/api/v4_0_0/DynamicEntityTest.scala @@ -152,7 +152,7 @@ class DynamicEntityTest extends V400ServerSetup { 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 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) @@ -163,7 +163,7 @@ class DynamicEntityTest extends V400ServerSetup { 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 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) @@ -174,7 +174,7 @@ class DynamicEntityTest extends V400ServerSetup { 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 request400 = (v4_0_0_Request / "management" / "dynamic-entities").GET val response400 = makeGetRequest(request400) Then("We should get a 400") response400.code should equal(400) @@ -185,7 +185,7 @@ class DynamicEntityTest extends V400ServerSetup { 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 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) @@ -198,7 +198,7 @@ class DynamicEntityTest extends V400ServerSetup { feature("Add a DynamicEntity v4.0.4- Unauthorized access - Authorized access") { scenario("We will call the endpoint without the proper Role " + canCreateDynamicEntity, ApiEndpoint1, VersionOfApi) { When("We make a request v4.0.0 without a Role " + canCreateDynamicEntity) - val request400 = (v4_0_0_Request / "management" / "dynamic_entities").POST <@(user1) + 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) @@ -209,7 +209,7 @@ class DynamicEntityTest extends V400ServerSetup { 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 request = (v4_0_0_Request / "management" / "dynamic_entities").POST <@(user1) + val request = (v4_0_0_Request / "management" / "dynamic-entities").POST <@(user1) val response = makePostRequest(request, write(rightEntity)) Then("We should get a 201") response.code should equal(201) @@ -244,7 +244,7 @@ class DynamicEntityTest extends V400ServerSetup { { // update success - val request400 = (v4_0_0_Request / "management" / "dynamic_entities" / dynamicEntityId ).PUT <@(user1) + val request400 = (v4_0_0_Request / "management" / "dynamic-entities" / dynamicEntityId ).PUT <@(user1) val response400 = makePutRequest(request400, compactRender(updateRequest)) Then("We should get a 200") response400.code should equal(200) @@ -254,7 +254,7 @@ class DynamicEntityTest extends V400ServerSetup { { // update a not exists DynamicEntity - val request404 = (v4_0_0_Request / "management" / "dynamic_entities" / "not-exists-id" ).PUT <@(user1) + 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 404") response404.code should equal(404) @@ -263,7 +263,7 @@ class DynamicEntityTest extends V400ServerSetup { { // update a DynamicEntity with wrong required field name - val request400 = (v4_0_0_Request / "management" / "dynamic_entities" / dynamicEntityId ).PUT <@(user1) + val request400 = (v4_0_0_Request / "management" / "dynamic-entities" / dynamicEntityId ).PUT <@(user1) val response400 = makePutRequest(request400, compactRender(wrongRequiredEntity)) Then("We should get a 400") @@ -273,7 +273,7 @@ class DynamicEntityTest extends V400ServerSetup { { // update a DynamicEntity with wrong type of description - val request400 = (v4_0_0_Request / "management" / "dynamic_entities" / dynamicEntityId ).PUT <@(user1) + val request400 = (v4_0_0_Request / "management" / "dynamic-entities" / dynamicEntityId ).PUT <@(user1) val response400 = makePutRequest(request400, compactRender(wrongDescriptionEntity)) Then("We should get a 400") @@ -283,7 +283,7 @@ class DynamicEntityTest extends V400ServerSetup { { // update a DynamicEntity with wrong type of property description - val request400 = (v4_0_0_Request / "management" / "dynamic_entities" / dynamicEntityId ).PUT <@(user1) + val request400 = (v4_0_0_Request / "management" / "dynamic-entities" / dynamicEntityId ).PUT <@(user1) val response400 = makePutRequest(request400, compactRender(wrongPropertyDescriptionEntity)) Then("We should get a 400") @@ -293,11 +293,11 @@ class DynamicEntityTest extends V400ServerSetup { Entitlement.entitlement.vend.addEntitlement("", resourceUser1.userId, CanGetDynamicEntities.toString) When("We make a request v4.0.0 with the Role " + canGetDynamicEntities) - val requestGet = (v4_0_0_Request / "management" / "dynamic_entities").GET <@(user1) + val requestGet = (v4_0_0_Request / "management" / "dynamic-entities").GET <@(user1) val responseGet = makeGetRequest(requestGet) Then("We should get a 200") responseGet.code should equal(200) - val json = responseGet.body \ "dynamic_entities" + val json = responseGet.body \ "dynamic-entities" val dynamicEntitiesGetJson = json.asInstanceOf[JArray] dynamicEntitiesGetJson.values should have size 1 @@ -308,7 +308,7 @@ class DynamicEntityTest extends V400ServerSetup { Entitlement.entitlement.vend.addEntitlement("", resourceUser1.userId, CanDeleteDynamicEntity.toString) When("We make a request v4.0.0 with the Role " + canDeleteDynamicEntity) - val requestDelete400 = (v4_0_0_Request / "management" / "dynamic_entities" / dynamicEntityId).DELETE <@(user1) + val requestDelete400 = (v4_0_0_Request / "management" / "dynamic-entities" / dynamicEntityId).DELETE <@(user1) val responseDelete400 = makeDeleteRequest(requestDelete400) Then("We should get a 200") responseDelete400.code should equal(200) From fe2cbb6c6579779c7a3a709049720470f70593d1 Mon Sep 17 00:00:00 2001 From: shuang Date: Thu, 26 Mar 2020 12:31:11 +0100 Subject: [PATCH 04/35] make upload swagger as string instead of json. --- .../scala/code/api/util/ExampleValue.scala | 2 +- .../scala/code/api/v4_0_0/APIMethods400.scala | 41 ++- .../api/v4_0_0/DynamicEndpointHelper.scala | 245 ++++++++++++++++++ .../DynamicEndpointProvider.scala | 2 +- 4 files changed, 276 insertions(+), 14 deletions(-) create mode 100644 obp-api/src/main/scala/code/api/v4_0_0/DynamicEndpointHelper.scala diff --git a/obp-api/src/main/scala/code/api/util/ExampleValue.scala b/obp-api/src/main/scala/code/api/util/ExampleValue.scala index bcaf617e1..657e26f57 100644 --- a/obp-api/src/main/scala/code/api/util/ExampleValue.scala +++ b/obp-api/src/main/scala/code/api/util/ExampleValue.scala @@ -360,7 +360,7 @@ object ExampleValue { lazy val dynamicEntityResponseBodyExample = dynamicEntityRequestBodyExample.copy(dynamicEntityId = Some("dynamic-entity-id")) - lazy val dynamicEndpointRequestBodyExample = DynamicEndpointSwagger(None, """{ + lazy val dynamicEndpointRequestBodyExample = DynamicEndpointSwagger("""{ | "swagger": "2.0", | "info": { | "version": "0.0.1", diff --git a/obp-api/src/main/scala/code/api/v4_0_0/APIMethods400.scala b/obp-api/src/main/scala/code/api/v4_0_0/APIMethods400.scala index 379583fa4..0f7004552 100644 --- a/obp-api/src/main/scala/code/api/v4_0_0/APIMethods400.scala +++ b/obp-api/src/main/scala/code/api/v4_0_0/APIMethods400.scala @@ -59,6 +59,7 @@ import scala.collection.mutable.ArrayBuffer import scala.concurrent.Future import code.model._ import org.apache.commons.lang3.StringUtils +import net.liftweb.json.compactRender trait APIMethods400 { self: RestHelper => @@ -2731,9 +2732,6 @@ trait APIMethods400 { "/management/dynamic-endpoints", "Create DynamicEndpoint", s"""Create a DynamicEndpoint. - | - | - |${authenticationRequiredMessage(true)} | |Create one DynamicEndpoint, | @@ -2750,12 +2748,20 @@ trait APIMethods400 { List(apiTagDynamicEndpoint, apiTagApi, apiTagNewStyle), Some(List(canCreateDynamicEndpoint))) + private val BodyPost = new TestPost[String] with JsonTest { + def body(r: Req): Box[String] = { + val value = r.body.map(it => new String(it)) + value + } + } + lazy val createDynamicEndpoint: OBPEndpoint = { - case "management" :: "dynamic-endpoints" :: Nil JsonPost json -> _ => { + case "management" :: "dynamic-endpoints" :: Nil BodyPost body -> req => { cc => for { postedJson <- NewStyle.function.tryons(InvalidJsonFormat, 400, cc.callContext) { - json.extract[DynamicEndpointSwagger] + DynamicEndpointHelper.parseSwaggerContent(body) + DynamicEndpointSwagger(body) } (dynamicEndpoint, callContext) <- NewStyle.function.createDynamicEndpoint(postedJson.swaggerString, cc.callContext) } yield { @@ -2776,8 +2782,6 @@ trait APIMethods400 { s"""Get a DynamicEndpoint. | | - |${authenticationRequiredMessage(true)} - | |Get one DynamicEndpoint, | |""", @@ -2813,16 +2817,13 @@ trait APIMethods400 { "/management/dynamic-endpoints", "Get DynamicEndpoints", s"""Get DynamicEndpoints. - | - | - |${authenticationRequiredMessage(true)} | |Get DynamicEndpoints, | |""", emptyObjectJson, ListResult( - "dynamic-entities", + "dynamic_entities", List(dynamicEndpointResponseBodyExample) ), List( @@ -2846,7 +2847,23 @@ trait APIMethods400 { } } } - + + lazy val dynamicEndpoint: OBPEndpoint = { + case EntityName(entityName) :: Nil JsonGet req => { cc => + val listName = StringHelpers.snakify(English.plural(entityName)) + for { + (Full(u), callContext) <- authenticatedAccess(cc) + _ <- NewStyle.function.hasEntitlement("", u.userId, DynamicEntityInfo.canGetRole(entityName), callContext) + (box, _) <- NewStyle.function.invokeDynamicConnector(GET_ALL, entityName, None, None, Some(cc)) + resultList: JArray = unboxResult(box.asInstanceOf[Box[JArray]], entityName) + } yield { + import net.liftweb.json.JsonDSL._ + + val jValue: JObject = listName -> filterDynamicObjects(resultList, req) + (jValue, HttpCode.`200`(Some(cc))) + } + } + } } diff --git a/obp-api/src/main/scala/code/api/v4_0_0/DynamicEndpointHelper.scala b/obp-api/src/main/scala/code/api/v4_0_0/DynamicEndpointHelper.scala new file mode 100644 index 000000000..7d16328e0 --- /dev/null +++ b/obp-api/src/main/scala/code/api/v4_0_0/DynamicEndpointHelper.scala @@ -0,0 +1,245 @@ +package code.api.v4_0_0 + +import java.io.File +import java.nio.charset.Charset +import java.util +import java.util.{Date, Objects} + +import code.DynamicEndpoint.{DynamicEndpointProvider, DynamicEndpointT} +import code.api.util.APIUtil.{Catalogs, OBPEndpoint, ResourceDoc, authenticationRequiredMessage, emptyObjectJson, generateUUID, notCore, notOBWG, notPSD2} +import code.api.util.ApiTag.{ResourceDocTag, apiTagApi, apiTagNewStyle} +import code.api.util.ErrorMessages.{InvalidJsonFormat, UnknownError, UserHasMissingRoles, UserNotLoggedIn} +import code.api.util.{APIUtil, ApiRole, ApiTag, CustomJsonFormats, NewStyle} +import code.api.util.ApiRole.getOrCreateDynamicApiRole +import com.openbankproject.commons.model.enums.DynamicEntityFieldType +import com.openbankproject.commons.util.{ApiVersion, Functions} +import io.swagger.v3.oas.models.{OpenAPI, Operation, PathItem} +import io.swagger.v3.oas.models.PathItem.HttpMethod +import io.swagger.v3.oas.models.media.{ArraySchema, BooleanSchema, Content, DateSchema, DateTimeSchema, IntegerSchema, NumberSchema, ObjectSchema, Schema, StringSchema} +import io.swagger.v3.oas.models.parameters.RequestBody +import io.swagger.v3.oas.models.responses.ApiResponses +import io.swagger.v3.parser.OpenAPIV3Parser +import net.liftweb.json.JsonAST.{JArray, JField, JObject} +import net.liftweb.json.JsonDSL._ +import net.liftweb.json +import net.liftweb.json.JValue +import net.liftweb.util.StringHelpers +import org.apache.commons.io.FileUtils +import org.apache.commons.lang3.StringUtils +import org.atteo.evo.inflector.English + +import scala.collection.immutable.{List, Nil} +import scala.collection.mutable +import scala.collection.mutable.ArrayBuffer +import scala.collection.JavaConverters._ + + +object DynamicEndpointHelper { + private implicit val formats = CustomJsonFormats.formats + /** + * dynamic endpoints url prefix + */ + val urlPrefix = APIUtil.getPropsValue("dynamic_endpoints_url_prefix", "dynamic") + + val dynamicEndpointsUrl: Set[(String, HttpMethod)] = Set() + + def swaggerToResourceDocs(content: String): mutable.Iterable[ResourceDoc] = { + val openAPI: OpenAPI = parseSwaggerContent(content) + + val tags: List[ResourceDocTag] = List(ApiTag.apiTagDynamicEndpoint, apiTagApi, apiTagNewStyle) + + val paths: mutable.Map[String, PathItem] = openAPI.getPaths.asScala + def entitlementSuffix(path: String) = Math.abs(path.hashCode).toString.substring(0, 3) // to avoid different swagger have same entitlement + val docs: mutable.Iterable[ResourceDoc] = for { + (path, pathItem) <- paths + (method: HttpMethod, op: Operation) <- pathItem.readOperationsMap.asScala + } yield { + val implementedInApiVersion = ApiVersion.v4_0_0 + val partialFunction: OBPEndpoint = APIMethods400.Implementations4_0_0.genericEndpoint // TODO create real endpoint + val partialFunctionName: String = s"$method-$path".replace('/', '_') + val requestVerb: String = method.name() + val requestUrl: String = s"/$urlPrefix/$path".replace("//", "/") + val summary: String = Option(pathItem.getSummary) + .filter(StringUtils.isNotBlank) + .getOrElse(buildSummary(method, op, path)) + val description: String = Option(pathItem.getDescription) + .filter(StringUtils.isNotBlank) + .orElse(Option(op.getDescription)) + .filter(StringUtils.isNotBlank) + .map(_.capitalize) + .getOrElse(summary) + val exampleRequestBody: Product = getRequestExample(openAPI, op.getRequestBody) + val successResponseBody: Product = getResponseExample(openAPI, op.getResponses) + val errorResponseBodies: List[String] = List( + UserNotLoggedIn, + UserHasMissingRoles, + UnknownError + ) + val catalogs: Catalogs = Catalogs(notCore, notPSD2, notOBWG) + + val roleName = s"Can$summary${entitlementSuffix(path)}" + .replaceFirst("Can(Create|Update|Get|Delete)", "Can$1Dynamic") + .replace(" ", "") + val roles: Option[List[ApiRole]] = Some(List( + ApiRole.getOrCreateDynamicApiRole(roleName) + )) + val connectorMethods = Some(List(s"""dynamicEntityProcess: parameters contains {"key": "entityName", "value": "$summary"}""")) //TODO temp + ResourceDoc( + partialFunction, + implementedInApiVersion, + partialFunctionName, + requestVerb, + requestUrl, + summary, + description, + exampleRequestBody, + successResponseBody, + errorResponseBodies, + catalogs, + tags, + roles, + connectorMethods = connectorMethods + ) + } + docs + } + + def parseSwaggerContent(content: String): OpenAPI = { + val tempSwaggerFile = File.createTempFile("temp", ".swagger") + FileUtils.write(tempSwaggerFile, content, Charset.forName("utf-8")) + val openAPI: OpenAPI = new OpenAPIV3Parser().read(tempSwaggerFile.getAbsolutePath) + // Delete temp file when program exits, only if delete fail. + if(!FileUtils.deleteQuietly(tempSwaggerFile)){ + tempSwaggerFile.deleteOnExit() + } + openAPI + } + + def doc: ArrayBuffer[ResourceDoc] = { + val dynamicEndpoints: List[DynamicEndpointT] = DynamicEndpointProvider.connectorMethodProvider.vend.getAll() + + val docs = dynamicEndpoints.flatMap(it => swaggerToResourceDocs(it.swaggerString)) + ArrayBuffer[ResourceDoc](docs:_*) + } + + private def buildSummary(method: HttpMethod, op: Operation, path: String): String = method match { + case _ if StringUtils.isNotBlank(op.getSummary) => op.getSummary + case HttpMethod.GET | HttpMethod.DELETE => + val opName = if(method == HttpMethod.GET) "Get" else "Delete" + op.getResponses.asScala + .find(_._1.startsWith("20")) + .flatMap(it => getRef(it._2.getContent, it._2.get$ref()) ) + .map(StringUtils.substringAfterLast(_, "/")) + .map(entityName => s"$opName $entityName") + .orElse(Option(op.getDescription)) + .filter(StringUtils.isNotBlank) + .orElse(Option(s"$opName $path")) + .map(_.replaceFirst("(?i)((get|delete)\\s+\\S+).*", "$1")) + .map(capitalize) + .get + + case m@(HttpMethod.POST | HttpMethod.PUT) => + val opName = if(m == HttpMethod.POST) "Create" else "Update" + + getRef(op.getRequestBody.getContent, op.getRequestBody.get$ref()) + .map(StringUtils.substringAfterLast(_, "/")) + .map(entityName => s"$opName $entityName") + .orElse(Option(op.getDescription)) + .filter(StringUtils.isNotBlank) + .orElse(Option(s"$method $path")) + .map(capitalize) + .get + case _ => throw new RuntimeException(s"Support HTTP METHOD: GET, POST, PUT, DELETE, current method is $method") + } + private def capitalize(str: String): String = + StringUtils.split(str, " ").map(_.capitalize).mkString(" ") + + private def getRequestExample(openAPI: OpenAPI, body: RequestBody): Product = { + if(body == null || body.getContent == null) { + "" + } else { + getExample(openAPI, getRef(body.getContent, body.get$ref()).orNull) + } + } + private def getResponseExample(openAPI: OpenAPI, apiResponses: ApiResponses): Product = { + if(apiResponses == null || apiResponses.isEmpty) { + JObject() + } else { + val ref: Option[String] = apiResponses.asScala + .find(_._1.startsWith("20")) + .flatMap(it => getRef(it._2.getContent, it._2.get$ref())) + getExample(openAPI, ref.orNull) + } + } + + private def getRef(content: Content, $ref: String): Option[String] = { + if(StringUtils.isNoneBlank($ref)) { + Option($ref) + } else { + val schemaRef: Option[String] = Option(content.get("application/json")) + .flatMap(it => Option[Schema[_]](it.getSchema)) + .map(_.get$ref()) + .filter(StringUtils.isNoneBlank(_)) + + if(schemaRef.isDefined) { + Option(schemaRef.get) + } else { + val supportMediaTypes = content.values().asScala + supportMediaTypes.collectFirst { + case mediaType if mediaType.getSchema != null && StringUtils.isNotBlank(mediaType.getSchema.get$ref()) => + mediaType.getSchema.get$ref() + } + } + } + + } + private val RegexDefinitions = """(?:#/components/schemas(?:/#definitions)?/)(.+)""".r + private val RegexResponse = """#/responses/(.+)""".r + + private def getExample(openAPI: OpenAPI, ref: String): Product = ref match { + case null => JObject() + + case RegexResponse(refName) => + val response = openAPI.getComponents.getResponses.get(refName) + val ref = getRef(response.getContent, response.get$ref()) + getExample(openAPI, ref.get) + + case RegexDefinitions(refName) => + openAPI.getComponents.getSchemas.get(refName) match { + case o: ObjectSchema => + val properties: util.Map[String, Schema[_]] = o.getProperties + + val jFields: mutable.Iterable[JField] = properties.asScala.map { kv => + val (name, value) = kv + val valueExample = if(value.getClass == classOf[Schema[_]]) getExample(openAPI, value.get$ref()) else getPropertyExample(value) + JField(name, json.Extraction.decompose(valueExample)) + } + JObject(jFields.toList) + + case a: ArraySchema => + Option(a.getExample) + .map(json.Extraction.decompose(_).asInstanceOf[JObject]) + .getOrElse { + val schema: Schema[_] = a.getItems + val singleItem: Any = if(schema.getClass == classOf[Schema[_]]) getExample(openAPI, schema.get$ref()) else getPropertyExample(schema) + val jItem = json.Extraction.decompose(singleItem) + jItem :: Nil + } + } + + } + + private def getPropertyExample(schema: Schema[_]) = schema match { + case b: BooleanSchema => Option(b.getExample).getOrElse(true) + case d: DateSchema => Option(d.getExample).getOrElse { + APIUtil.DateWithDayFormat.format(new Date()) + } + case t: DateTimeSchema => Option(t.getExample).getOrElse { + APIUtil.DateWithSecondsFormat.format(new Date()) + } + case i: IntegerSchema => Option(i.getExample).getOrElse(1) + case n: NumberSchema => Option(n.getExample).getOrElse(1.2) + case s: StringSchema => Option(s.getExample).getOrElse("string") + case _ => throw new RuntimeException(s"Not support type $schema, please support it if necessary.") + } +} \ No newline at end of file diff --git a/obp-api/src/main/scala/code/dynamicEndpoint/DynamicEndpointProvider.scala b/obp-api/src/main/scala/code/dynamicEndpoint/DynamicEndpointProvider.scala index 229686706..f38c239e2 100644 --- a/obp-api/src/main/scala/code/dynamicEndpoint/DynamicEndpointProvider.scala +++ b/obp-api/src/main/scala/code/dynamicEndpoint/DynamicEndpointProvider.scala @@ -23,7 +23,7 @@ case class DynamicEndpointCommons( object DynamicEndpointCommons extends Converter[DynamicEndpointT, DynamicEndpointCommons] -case class DynamicEndpointSwagger(dynamicEndpointId: Option[String] = None, swaggerString: String) +case class DynamicEndpointSwagger(swaggerString: String, dynamicEndpointId: Option[String] = None) trait DynamicEndpointProvider { def create(swaggerString: String): Box[DynamicEndpointT] From 538bfbd415949e2c84dcf931611d10eb220ae95f Mon Sep 17 00:00:00 2001 From: hongwei Date: Thu, 26 Mar 2020 13:31:56 +0100 Subject: [PATCH 05/35] fixed the failed test for Dynamic Entity --- obp-api/src/test/scala/code/api/v4_0_0/DynamicEntityTest.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/obp-api/src/test/scala/code/api/v4_0_0/DynamicEntityTest.scala b/obp-api/src/test/scala/code/api/v4_0_0/DynamicEntityTest.scala index a0c13de9f..4f4527e87 100644 --- a/obp-api/src/test/scala/code/api/v4_0_0/DynamicEntityTest.scala +++ b/obp-api/src/test/scala/code/api/v4_0_0/DynamicEntityTest.scala @@ -297,7 +297,7 @@ class DynamicEntityTest extends V400ServerSetup { val responseGet = makeGetRequest(requestGet) Then("We should get a 200") responseGet.code should equal(200) - val json = responseGet.body \ "dynamic-entities" + val json = responseGet.body \ "dynamic_entities" val dynamicEntitiesGetJson = json.asInstanceOf[JArray] dynamicEntitiesGetJson.values should have size 1 From 71cc7cd515816e2dd15c7fa48d55999c9876b0c5 Mon Sep 17 00:00:00 2001 From: hongwei Date: Thu, 26 Mar 2020 13:36:30 +0100 Subject: [PATCH 06/35] DynamicEntities -> Dynamic Entities DynamicEndpoints -> Dynamic Endpoints --- .../scala/code/api/v4_0_0/APIMethods400.scala | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/obp-api/src/main/scala/code/api/v4_0_0/APIMethods400.scala b/obp-api/src/main/scala/code/api/v4_0_0/APIMethods400.scala index 0f7004552..9184a8baa 100644 --- a/obp-api/src/main/scala/code/api/v4_0_0/APIMethods400.scala +++ b/obp-api/src/main/scala/code/api/v4_0_0/APIMethods400.scala @@ -814,8 +814,8 @@ trait APIMethods400 { nameOf(getDynamicEntities), "GET", "/management/dynamic-entities", - "Get DynamicEntities", - s"""Get the all DynamicEntities.""", + "Get Dynamic Entities", + s"""Get the all Dynamic Entities.""", emptyObjectJson, ListResult( "dynamic_entities", @@ -851,7 +851,7 @@ trait APIMethods400 { nameOf(createDynamicEntity), "POST", "/management/dynamic-entities", - "Create DynamicEntity", + "Create Dynamic Entity", s"""Create a DynamicEntity. | | @@ -902,7 +902,7 @@ trait APIMethods400 { nameOf(updateDynamicEntity), "PUT", "/management/dynamic-entities/DYNAMIC_ENTITY_ID", - "Update DynamicEntity", + "Update Dynamic Entity", s"""Update a DynamicEntity. | | @@ -960,7 +960,7 @@ trait APIMethods400 { nameOf(deleteDynamicEntity), "DELETE", "/management/dynamic-entities/DYNAMIC_ENTITY_ID", - "Delete DynamicEntity", + "Delete Dynamic Entity", s"""Delete a DynamicEntity specified by DYNAMIC_ENTITY_ID. | |""", @@ -2730,10 +2730,10 @@ trait APIMethods400 { nameOf(createDynamicEndpoint), "POST", "/management/dynamic-endpoints", - "Create DynamicEndpoint", - s"""Create a DynamicEndpoint. + "Create Dynamic Endpoint", + s"""Create a Dynamic Endpoint. | - |Create one DynamicEndpoint, + |Create one Dynamic Endpoint, | |""", dynamicEndpointRequestBodyExample, @@ -2778,11 +2778,11 @@ trait APIMethods400 { nameOf(getDynamicEndpoint), "GET", "/management/dynamic-endpoints/DYNAMIC_ENDPOINT_ID", - "Get DynamicEndpoint", - s"""Get a DynamicEndpoint. + "Get Dynamic Endpoint", + s"""Get a Dynamic Endpoint. | | - |Get one DynamicEndpoint, + |Get one Dynamic Endpoint, | |""", emptyObjectJson, @@ -2815,10 +2815,10 @@ trait APIMethods400 { nameOf(getDynamicEndpoints), "GET", "/management/dynamic-endpoints", - "Get DynamicEndpoints", - s"""Get DynamicEndpoints. + "Get Dynamic Endpoints", + s"""Get Dynamic Endpoints. | - |Get DynamicEndpoints, + |Get Dynamic Endpoints, | |""", emptyObjectJson, From 1ad13f307f5642bcc2d81339061a6b07d1e0ccce Mon Sep 17 00:00:00 2001 From: shuang Date: Thu, 26 Mar 2020 15:33:26 +0100 Subject: [PATCH 07/35] feature/add_dynamic_endpoints_with_swagger: request body, response content all not show special character escape --- .../scala/code/api/util/ExampleValue.scala | 489 +++++++++--------- .../scala/code/api/v4_0_0/APIMethods400.scala | 34 +- 2 files changed, 264 insertions(+), 259 deletions(-) diff --git a/obp-api/src/main/scala/code/api/util/ExampleValue.scala b/obp-api/src/main/scala/code/api/util/ExampleValue.scala index 657e26f57..b95acd9df 100644 --- a/obp-api/src/main/scala/code/api/util/ExampleValue.scala +++ b/obp-api/src/main/scala/code/api/util/ExampleValue.scala @@ -1,10 +1,12 @@ package code.api.util -import code.DynamicEndpoint.DynamicEndpointSwagger +import net.liftweb.json.JsonDSL._ import code.api.util.Glossary.{glossaryItems, makeGlossaryItem} import code.dynamicEntity.{DynamicEntityDefinition, DynamicEntityFooBar, DynamicEntityFullBarFields, DynamicEntityIntTypeExample, DynamicEntityStringTypeExample} import com.openbankproject.commons.model.enums.DynamicEntityFieldType +import net.liftweb.json +import net.liftweb.json.JObject case class ConnectorField(value: String, description: String) { @@ -360,247 +362,250 @@ object ExampleValue { lazy val dynamicEntityResponseBodyExample = dynamicEntityRequestBodyExample.copy(dynamicEntityId = Some("dynamic-entity-id")) - lazy val dynamicEndpointRequestBodyExample = DynamicEndpointSwagger("""{ - | "swagger": "2.0", - | "info": { - | "version": "0.0.1", - | "title": "Portus EVS sandbox demo API", - | "description": "Portus EVS sandbox demo API", - | "contact": { - | "name": "Digital & FinTech, Grant Thornton", - | "email": "peng.xu@ie.gt.com", - | "url": "https://www.tesobe.com/" - | } - | }, - | "host": "localhost:8080", - | "basePath": "/user", - | "schemes": [ - | "http" - | ], - | "consumes": [ - | "application/json" - | ], - | "produces": [ - | "application/json" - | ], - | "paths": { - | "/save": { - | "post": { - | "parameters": [ - | { - | "name": "body", - | "in": "body", - | "required": true, - | "schema": { - | "$ref": "#/definitions/user" - | } - | } - | ], - | "responses": { - | "201": { - | "description": "create user successful and return created user object", - | "schema": { - | "$ref": "#/definitions/user" - | } - | }, - | "500": { - | "description": "unexpected error", - | "schema": { - | "$ref": "#/responses/unexpectedError" - | } - | } - | } - | } - | }, - | "/getById/{userId}": { - | "get": { - | "description": "get reuested user by user ID", - | "parameters": [ - | { - | "$ref": "#/parameters/userId" - | } - | ], - | "consumes": [], - | "responses": { - | "200": { - | "description": "the successful get requested user by user ID", - | "schema": { - | "$ref": "#/definitions/user" - | } - | }, - | "400": { - | "description": "bad request", - | "schema": { - | "$ref": "#/responses/invalidRequest" - | } - | }, - | "404": { - | "description": "user not found", - | "schema": { - | "$ref": "#/definitions/APIError" - | } - | }, - | "500": { - | "description": "unexpected error", - | "schema": { - | "$ref": "#/responses/unexpectedError" - | } - | } - | } - | } - | }, - | "/listUsers": { - | "get": { - | "description": "get list of users", - | "consumes": [], - | "responses": { - | "200": { - | "description": "get all users", - | "schema": { - | "$ref": "#/definitions/users" - | } - | }, - | "404": { - | "description": "user not found", - | "schema": { - | "$ref": "#/definitions/APIError" - | } - | } - | } - | } - | }, - | "/updateUser": { - | "put": { - | "parameters": [ - | { - | "name": "body", - | "in": "body", - | "required": true, - | "schema": { - | "$ref": "#/definitions/user" - | } - | } - | ], - | "responses": { - | "200": { - | "description": "create user successful and return created user object", - | "schema": { - | "$ref": "#/definitions/user" - | } - | }, - | "500": { - | "description": "unexpected error", - | "schema": { - | "$ref": "#/responses/unexpectedError" - | } - | } - | } - | } - | }, - | "/delete/{userId}": { - | "delete": { - | "description": "delete user by user ID", - | "parameters": [ - | { - | "$ref": "#/parameters/userId" - | } - | ], - | "consumes": [], - | "responses": { - | "204": { - | "description": "the successful delete user by user ID" - | }, - | "400": { - | "description": "bad request", - | "schema": { - | "$ref": "#/responses/invalidRequest" - | } - | }, - | "500": { - | "description": "unexpected error", - | "schema": { - | "$ref": "#/responses/unexpectedError" - | } - | } - | } - | } - | } - | }, - | "definitions": { - | "user": { - | "type": "object", - | "properties": { - | "id": { - | "type": "integer", - | "description": "user ID" - | }, - | "first_name": { - | "type": "string" - | }, - | "last_name": { - | "type": "string" - | }, - | "age": { - | "type": "integer" - | }, - | "career": { - | "type": "string" - | } - | }, - | "required": [ - | "first_name", - | "last_name", - | "age" - | ] - | }, - | "users": { - | "description": "array of users", - | "type": "array", - | "items": { - | "$ref": "#/definitions/user" - | } - | }, - | "APIError": { - | "description": "content any error from API", - | "type": "object", - | "properties": { - | "errorCode": { - | "description": "content error code relate to API", - | "type": "string" - | }, - | "errorMessage": { - | "description": "content user-friendly error message", - | "type": "string" - | } - | } - | } - | }, - | "responses": { - | "unexpectedError": { - | "description": "unexpected error", - | "schema": { - | "$ref": "#/definitions/APIError" - | } - | }, - | "invalidRequest": { - | "description": "invalid request", - | "schema": { - | "$ref": "#/definitions/APIError" - | } - | } - | }, - | "parameters": { - | "userId": { - | "name": "userId", - | "in": "path", - | "required": true, - | "type": "string", - | "description": "user ID" - | } - | } - |} - |""".stripMargin) - lazy val dynamicEndpointResponseBodyExample = dynamicEndpointRequestBodyExample.copy(dynamicEndpointId = Some("dynamic-endpoint-id")) + private val dynamicEndpointSwagger = + """{ + | "swagger": "2.0", + | "info": { + | "version": "0.0.1", + | "title": "Portus EVS sandbox demo API", + | "description": "Portus EVS sandbox demo API", + | "contact": { + | "name": "Digital & FinTech, Grant Thornton", + | "email": "peng.xu@ie.gt.com", + | "url": "https://www.tesobe.com/" + | } + | }, + | "host": "localhost:8080", + | "basePath": "/user", + | "schemes": [ + | "http" + | ], + | "consumes": [ + | "application/json" + | ], + | "produces": [ + | "application/json" + | ], + | "paths": { + | "/save": { + | "post": { + | "parameters": [ + | { + | "name": "body", + | "in": "body", + | "required": true, + | "schema": { + | "$ref": "#/definitions/user" + | } + | } + | ], + | "responses": { + | "201": { + | "description": "create user successful and return created user object", + | "schema": { + | "$ref": "#/definitions/user" + | } + | }, + | "500": { + | "description": "unexpected error", + | "schema": { + | "$ref": "#/responses/unexpectedError" + | } + | } + | } + | } + | }, + | "/getById/{userId}": { + | "get": { + | "description": "get reuested user by user ID", + | "parameters": [ + | { + | "$ref": "#/parameters/userId" + | } + | ], + | "consumes": [], + | "responses": { + | "200": { + | "description": "the successful get requested user by user ID", + | "schema": { + | "$ref": "#/definitions/user" + | } + | }, + | "400": { + | "description": "bad request", + | "schema": { + | "$ref": "#/responses/invalidRequest" + | } + | }, + | "404": { + | "description": "user not found", + | "schema": { + | "$ref": "#/definitions/APIError" + | } + | }, + | "500": { + | "description": "unexpected error", + | "schema": { + | "$ref": "#/responses/unexpectedError" + | } + | } + | } + | } + | }, + | "/listUsers": { + | "get": { + | "description": "get list of users", + | "consumes": [], + | "responses": { + | "200": { + | "description": "get all users", + | "schema": { + | "$ref": "#/definitions/users" + | } + | }, + | "404": { + | "description": "user not found", + | "schema": { + | "$ref": "#/definitions/APIError" + | } + | } + | } + | } + | }, + | "/updateUser": { + | "put": { + | "parameters": [ + | { + | "name": "body", + | "in": "body", + | "required": true, + | "schema": { + | "$ref": "#/definitions/user" + | } + | } + | ], + | "responses": { + | "200": { + | "description": "create user successful and return created user object", + | "schema": { + | "$ref": "#/definitions/user" + | } + | }, + | "500": { + | "description": "unexpected error", + | "schema": { + | "$ref": "#/responses/unexpectedError" + | } + | } + | } + | } + | }, + | "/delete/{userId}": { + | "delete": { + | "description": "delete user by user ID", + | "parameters": [ + | { + | "$ref": "#/parameters/userId" + | } + | ], + | "consumes": [], + | "responses": { + | "204": { + | "description": "the successful delete user by user ID" + | }, + | "400": { + | "description": "bad request", + | "schema": { + | "$ref": "#/responses/invalidRequest" + | } + | }, + | "500": { + | "description": "unexpected error", + | "schema": { + | "$ref": "#/responses/unexpectedError" + | } + | } + | } + | } + | } + | }, + | "definitions": { + | "user": { + | "type": "object", + | "properties": { + | "id": { + | "type": "integer", + | "description": "user ID" + | }, + | "first_name": { + | "type": "string" + | }, + | "last_name": { + | "type": "string" + | }, + | "age": { + | "type": "integer" + | }, + | "career": { + | "type": "string" + | } + | }, + | "required": [ + | "first_name", + | "last_name", + | "age" + | ] + | }, + | "users": { + | "description": "array of users", + | "type": "array", + | "items": { + | "$ref": "#/definitions/user" + | } + | }, + | "APIError": { + | "description": "content any error from API", + | "type": "object", + | "properties": { + | "errorCode": { + | "description": "content error code relate to API", + | "type": "string" + | }, + | "errorMessage": { + | "description": "content user-friendly error message", + | "type": "string" + | } + | } + | } + | }, + | "responses": { + | "unexpectedError": { + | "description": "unexpected error", + | "schema": { + | "$ref": "#/definitions/APIError" + | } + | }, + | "invalidRequest": { + | "description": "invalid request", + | "schema": { + | "$ref": "#/definitions/APIError" + | } + | } + | }, + | "parameters": { + | "userId": { + | "name": "userId", + | "in": "path", + | "required": true, + | "type": "string", + | "description": "user ID" + | } + | } + |} + |""".stripMargin + lazy val dynamicEndpointRequestBodyExample = json.parse(dynamicEndpointSwagger).asInstanceOf[JObject] + lazy val dynamicEndpointResponseBodyExample = ("dynamicEndpointId", "dynamic-endpoint-id") ~ ("swaggerString", dynamicEndpointRequestBodyExample) + } diff --git a/obp-api/src/main/scala/code/api/v4_0_0/APIMethods400.scala b/obp-api/src/main/scala/code/api/v4_0_0/APIMethods400.scala index 9184a8baa..60ddff3cd 100644 --- a/obp-api/src/main/scala/code/api/v4_0_0/APIMethods400.scala +++ b/obp-api/src/main/scala/code/api/v4_0_0/APIMethods400.scala @@ -49,6 +49,7 @@ 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.json.JsonDSL._ import net.liftweb.mapper.By import net.liftweb.util.Helpers.now import net.liftweb.util.{Helpers, StringHelpers} @@ -2748,25 +2749,20 @@ trait APIMethods400 { List(apiTagDynamicEndpoint, apiTagApi, apiTagNewStyle), Some(List(canCreateDynamicEndpoint))) - private val BodyPost = new TestPost[String] with JsonTest { - def body(r: Req): Box[String] = { - val value = r.body.map(it => new String(it)) - value - } - } - lazy val createDynamicEndpoint: OBPEndpoint = { - case "management" :: "dynamic-endpoints" :: Nil BodyPost body -> req => { + case "management" :: "dynamic-endpoints" :: Nil JsonPost json -> _ => { cc => for { postedJson <- NewStyle.function.tryons(InvalidJsonFormat, 400, cc.callContext) { - DynamicEndpointHelper.parseSwaggerContent(body) - DynamicEndpointSwagger(body) + val swaggerContent = compactRender(json) + DynamicEndpointHelper.parseSwaggerContent(swaggerContent) + DynamicEndpointSwagger(swaggerContent) } (dynamicEndpoint, callContext) <- NewStyle.function.createDynamicEndpoint(postedJson.swaggerString, cc.callContext) } yield { - val commonsData: DynamicEndpointCommons = dynamicEndpoint - (commonsData, HttpCode.`201`(callContext)) + val swaggerJson = parse(dynamicEndpoint.swaggerString) + val responseJson: JObject = ("dynamicEndpointId", dynamicEndpoint.dynamicEndpointId) ~ ("swaggerString", swaggerJson) + (responseJson, HttpCode.`201`(callContext)) } } } @@ -2803,8 +2799,9 @@ trait APIMethods400 { for { (dynamicEndpoint, callContext) <- NewStyle.function.getDynamicEndpoint(dynamicEndpointId, cc.callContext) } yield { - val commonsData: DynamicEndpointCommons = dynamicEndpoint - (commonsData, HttpCode.`201`(callContext)) + val swaggerJson = parse(dynamicEndpoint.swaggerString) + val responseJson: JObject = ("dynamicEndpointId", dynamicEndpoint.dynamicEndpointId) ~ ("swaggerString", swaggerJson) + (responseJson, HttpCode.`201`(callContext)) } } } @@ -2840,10 +2837,13 @@ trait APIMethods400 { case "management" :: "dynamic-endpoints" :: Nil JsonGet req => { cc => for { - (dynamicEndpoints, callContext) <- NewStyle.function.getDynamicEndpoints(cc.callContext) + (dynamicEndpoints, _) <- NewStyle.function.getDynamicEndpoints(cc.callContext) } yield { - val listCommons: List[DynamicEndpointCommons] = dynamicEndpoints - (ListResult("dynamic_entities", listCommons), HttpCode.`200`(cc.callContext)) + val resultList = dynamicEndpoints.map[JObject, List[JObject]] { dynamicEndpoint=> + val swaggerJson = parse(dynamicEndpoint.swaggerString) + ("dynamicEndpointId", dynamicEndpoint.dynamicEndpointId) ~ ("swaggerString", swaggerJson) + } + (ListResult("dynamic_entities", resultList), HttpCode.`200`(cc.callContext)) } } } From afb7319c49971fa3311f788927ae61feec762fd3 Mon Sep 17 00:00:00 2001 From: hongwei Date: Fri, 27 Mar 2020 09:59:55 +0100 Subject: [PATCH 08/35] tweaked the camel case to snake case for the dynamicEndpoint response body --- obp-api/src/main/scala/code/api/v4_0_0/APIMethods400.scala | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/obp-api/src/main/scala/code/api/v4_0_0/APIMethods400.scala b/obp-api/src/main/scala/code/api/v4_0_0/APIMethods400.scala index 60ddff3cd..3f66f3898 100644 --- a/obp-api/src/main/scala/code/api/v4_0_0/APIMethods400.scala +++ b/obp-api/src/main/scala/code/api/v4_0_0/APIMethods400.scala @@ -2800,7 +2800,7 @@ trait APIMethods400 { (dynamicEndpoint, callContext) <- NewStyle.function.getDynamicEndpoint(dynamicEndpointId, cc.callContext) } yield { val swaggerJson = parse(dynamicEndpoint.swaggerString) - val responseJson: JObject = ("dynamicEndpointId", dynamicEndpoint.dynamicEndpointId) ~ ("swaggerString", swaggerJson) + val responseJson: JObject = ("dynamic_endpoint_id", dynamicEndpoint.dynamicEndpointId) ~ ("swagger_string", swaggerJson) (responseJson, HttpCode.`201`(callContext)) } } @@ -2820,7 +2820,7 @@ trait APIMethods400 { |""", emptyObjectJson, ListResult( - "dynamic_entities", + "dynamic_endpoints", List(dynamicEndpointResponseBodyExample) ), List( @@ -2841,7 +2841,7 @@ trait APIMethods400 { } yield { val resultList = dynamicEndpoints.map[JObject, List[JObject]] { dynamicEndpoint=> val swaggerJson = parse(dynamicEndpoint.swaggerString) - ("dynamicEndpointId", dynamicEndpoint.dynamicEndpointId) ~ ("swaggerString", swaggerJson) + ("dynamic_endpoint_id", dynamicEndpoint.dynamicEndpointId) ~ ("swagger_string", swaggerJson) } (ListResult("dynamic_entities", resultList), HttpCode.`200`(cc.callContext)) } From 97eefcc0527903faeb25ad1a72bfe856a984e27f Mon Sep 17 00:00:00 2001 From: hongwei Date: Thu, 26 Mar 2020 08:17:04 +0100 Subject: [PATCH 09/35] added the dynamicEndpoints endpoints #support Create, Get and GetAll now. --- .../main/scala/bootstrap/liftweb/Boot.scala | 2 + .../main/scala/code/api/util/ApiRole.scala | 15 ++ .../src/main/scala/code/api/util/ApiTag.scala | 1 + .../scala/code/api/util/ExampleValue.scala | 243 ++++++++++++++++++ .../main/scala/code/api/util/NewStyle.scala | 25 ++ .../scala/code/api/v4_0_0/APIMethods400.scala | 194 +++++++++++--- .../scala/code/bankconnectors/Connector.scala | 13 + .../bankconnectors/LocalMappedConnector.scala | 18 +- .../DynamicEndpointProvider.scala | 34 +++ .../MapppedDynamicEndpointProvider.scala | 40 +++ 10 files changed, 547 insertions(+), 38 deletions(-) create mode 100644 obp-api/src/main/scala/code/dynamicEndpoint/DynamicEndpointProvider.scala create mode 100644 obp-api/src/main/scala/code/dynamicEndpoint/MapppedDynamicEndpointProvider.scala diff --git a/obp-api/src/main/scala/bootstrap/liftweb/Boot.scala b/obp-api/src/main/scala/bootstrap/liftweb/Boot.scala index d9b8650fb..df6831dad 100644 --- a/obp-api/src/main/scala/bootstrap/liftweb/Boot.scala +++ b/obp-api/src/main/scala/bootstrap/liftweb/Boot.scala @@ -31,6 +31,7 @@ import java.util.{Locale, TimeZone} import code.CustomerDependants.MappedCustomerDependant import code.DynamicData.DynamicData +import code.DynamicEndpoint.DynamicEndpoint import code.accountapplication.MappedAccountApplication import code.accountattribute.MappedAccountAttribute import code.accountholders.MapperAccountHolders @@ -706,6 +707,7 @@ object ToSchemify { Authorisation, DynamicEntity, DynamicData, + DynamicEndpoint, AccountIdMapping, DirectDebit, StandingOrder diff --git a/obp-api/src/main/scala/code/api/util/ApiRole.scala b/obp-api/src/main/scala/code/api/util/ApiRole.scala index c8752b6f0..4522b52c1 100644 --- a/obp-api/src/main/scala/code/api/util/ApiRole.scala +++ b/obp-api/src/main/scala/code/api/util/ApiRole.scala @@ -406,6 +406,21 @@ object ApiRole { case class CanDeleteDynamicEntity(requiresBankId: Boolean = false) extends ApiRole lazy val canDeleteDynamicEntity = CanDeleteDynamicEntity() + + case class CanGetDynamicEndpoint(requiresBankId: Boolean = false) extends ApiRole + lazy val canGetDynamicEndpoint = CanGetDynamicEndpoint() + + case class CanGetDynamicEndpoints(requiresBankId: Boolean = false) extends ApiRole + lazy val canGetDynamicEndpoints = CanGetDynamicEndpoints() + + case class CanCreateDynamicEndpoint(requiresBankId: Boolean = false) extends ApiRole + lazy val canCreateDynamicEndpoint = CanCreateDynamicEndpoint() + + case class CanUpdateDynamicEndpoint(requiresBankId: Boolean = false) extends ApiRole + lazy val canUpdateDynamicEndpoint = CanUpdateDynamicEndpoint() + + case class CanDeleteDynamicEndpoint(requiresBankId: Boolean = false) extends ApiRole + lazy val canDeleteDynamicEndpoint = CanDeleteDynamicEndpoint() case class CanCreateResetPasswordUrl(requiresBankId: Boolean = false) extends ApiRole lazy val canCreateResetPasswordUrl = CanCreateResetPasswordUrl() diff --git a/obp-api/src/main/scala/code/api/util/ApiTag.scala b/obp-api/src/main/scala/code/api/util/ApiTag.scala index 4498a2105..38f8fca72 100644 --- a/obp-api/src/main/scala/code/api/util/ApiTag.scala +++ b/obp-api/src/main/scala/code/api/util/ApiTag.scala @@ -68,6 +68,7 @@ object ApiTag { val apiTagMethodRouting = ResourceDocTag("Method-Routing") val apiTagWebUiProps = ResourceDocTag("WebUi-Props") val apiTagDynamicEntity= ResourceDocTag("Dynamic-Entity") + val apiTagDynamicEndpoint= ResourceDocTag("Dynamic-Endpoint") // To mark the Berlin Group APIs suggested order of implementation val apiTagBerlinGroupM = ResourceDocTag("Berlin-Group-M") diff --git a/obp-api/src/main/scala/code/api/util/ExampleValue.scala b/obp-api/src/main/scala/code/api/util/ExampleValue.scala index d72689ae0..bcaf617e1 100644 --- a/obp-api/src/main/scala/code/api/util/ExampleValue.scala +++ b/obp-api/src/main/scala/code/api/util/ExampleValue.scala @@ -1,6 +1,7 @@ package code.api.util +import code.DynamicEndpoint.DynamicEndpointSwagger import code.api.util.Glossary.{glossaryItems, makeGlossaryItem} import code.dynamicEntity.{DynamicEntityDefinition, DynamicEntityFooBar, DynamicEntityFullBarFields, DynamicEntityIntTypeExample, DynamicEntityStringTypeExample} import com.openbankproject.commons.model.enums.DynamicEntityFieldType @@ -358,6 +359,248 @@ object ExampleValue { ) lazy val dynamicEntityResponseBodyExample = dynamicEntityRequestBodyExample.copy(dynamicEntityId = Some("dynamic-entity-id")) + + lazy val dynamicEndpointRequestBodyExample = DynamicEndpointSwagger(None, """{ + | "swagger": "2.0", + | "info": { + | "version": "0.0.1", + | "title": "Portus EVS sandbox demo API", + | "description": "Portus EVS sandbox demo API", + | "contact": { + | "name": "Digital & FinTech, Grant Thornton", + | "email": "peng.xu@ie.gt.com", + | "url": "https://www.tesobe.com/" + | } + | }, + | "host": "localhost:8080", + | "basePath": "/user", + | "schemes": [ + | "http" + | ], + | "consumes": [ + | "application/json" + | ], + | "produces": [ + | "application/json" + | ], + | "paths": { + | "/save": { + | "post": { + | "parameters": [ + | { + | "name": "body", + | "in": "body", + | "required": true, + | "schema": { + | "$ref": "#/definitions/user" + | } + | } + | ], + | "responses": { + | "201": { + | "description": "create user successful and return created user object", + | "schema": { + | "$ref": "#/definitions/user" + | } + | }, + | "500": { + | "description": "unexpected error", + | "schema": { + | "$ref": "#/responses/unexpectedError" + | } + | } + | } + | } + | }, + | "/getById/{userId}": { + | "get": { + | "description": "get reuested user by user ID", + | "parameters": [ + | { + | "$ref": "#/parameters/userId" + | } + | ], + | "consumes": [], + | "responses": { + | "200": { + | "description": "the successful get requested user by user ID", + | "schema": { + | "$ref": "#/definitions/user" + | } + | }, + | "400": { + | "description": "bad request", + | "schema": { + | "$ref": "#/responses/invalidRequest" + | } + | }, + | "404": { + | "description": "user not found", + | "schema": { + | "$ref": "#/definitions/APIError" + | } + | }, + | "500": { + | "description": "unexpected error", + | "schema": { + | "$ref": "#/responses/unexpectedError" + | } + | } + | } + | } + | }, + | "/listUsers": { + | "get": { + | "description": "get list of users", + | "consumes": [], + | "responses": { + | "200": { + | "description": "get all users", + | "schema": { + | "$ref": "#/definitions/users" + | } + | }, + | "404": { + | "description": "user not found", + | "schema": { + | "$ref": "#/definitions/APIError" + | } + | } + | } + | } + | }, + | "/updateUser": { + | "put": { + | "parameters": [ + | { + | "name": "body", + | "in": "body", + | "required": true, + | "schema": { + | "$ref": "#/definitions/user" + | } + | } + | ], + | "responses": { + | "200": { + | "description": "create user successful and return created user object", + | "schema": { + | "$ref": "#/definitions/user" + | } + | }, + | "500": { + | "description": "unexpected error", + | "schema": { + | "$ref": "#/responses/unexpectedError" + | } + | } + | } + | } + | }, + | "/delete/{userId}": { + | "delete": { + | "description": "delete user by user ID", + | "parameters": [ + | { + | "$ref": "#/parameters/userId" + | } + | ], + | "consumes": [], + | "responses": { + | "204": { + | "description": "the successful delete user by user ID" + | }, + | "400": { + | "description": "bad request", + | "schema": { + | "$ref": "#/responses/invalidRequest" + | } + | }, + | "500": { + | "description": "unexpected error", + | "schema": { + | "$ref": "#/responses/unexpectedError" + | } + | } + | } + | } + | } + | }, + | "definitions": { + | "user": { + | "type": "object", + | "properties": { + | "id": { + | "type": "integer", + | "description": "user ID" + | }, + | "first_name": { + | "type": "string" + | }, + | "last_name": { + | "type": "string" + | }, + | "age": { + | "type": "integer" + | }, + | "career": { + | "type": "string" + | } + | }, + | "required": [ + | "first_name", + | "last_name", + | "age" + | ] + | }, + | "users": { + | "description": "array of users", + | "type": "array", + | "items": { + | "$ref": "#/definitions/user" + | } + | }, + | "APIError": { + | "description": "content any error from API", + | "type": "object", + | "properties": { + | "errorCode": { + | "description": "content error code relate to API", + | "type": "string" + | }, + | "errorMessage": { + | "description": "content user-friendly error message", + | "type": "string" + | } + | } + | } + | }, + | "responses": { + | "unexpectedError": { + | "description": "unexpected error", + | "schema": { + | "$ref": "#/definitions/APIError" + | } + | }, + | "invalidRequest": { + | "description": "invalid request", + | "schema": { + | "$ref": "#/definitions/APIError" + | } + | } + | }, + | "parameters": { + | "userId": { + | "name": "userId", + | "in": "path", + | "required": true, + | "type": "string", + | "description": "user ID" + | } + | } + |} + |""".stripMargin) + lazy val dynamicEndpointResponseBodyExample = dynamicEndpointRequestBodyExample.copy(dynamicEndpointId = Some("dynamic-endpoint-id")) } diff --git a/obp-api/src/main/scala/code/api/util/NewStyle.scala b/obp-api/src/main/scala/code/api/util/NewStyle.scala index 6fa13664e..94f17a154 100644 --- a/obp-api/src/main/scala/code/api/util/NewStyle.scala +++ b/obp-api/src/main/scala/code/api/util/NewStyle.scala @@ -4,6 +4,7 @@ import java.util.Date import java.util.UUID.randomUUID import code.DynamicData.DynamicDataProvider +import code.DynamicEndpoint.DynamicEndpointT import code.api.APIFailureNewStyle import code.api.cache.Caching import code.api.util.APIUtil.{OBPReturnType, canGrantAccessToViewCommon, canRevokeAccessToViewCommon, connectorEmptyResponse, createHttpParamsByUrlFuture, createQueriesByHttpParamsFuture, fullBoxOrException, generateUUID, unboxFull, unboxFullOrFail} @@ -2002,5 +2003,29 @@ object NewStyle { } } + def createDynamicEndpoint(swaggerString: String, callContext: Option[CallContext]): OBPReturnType[DynamicEndpointT] = { + Connector.connector.vend.createDynamicEndpoint( + swaggerString, + callContext + ) map { + i => (connectorEmptyResponse(i._1, callContext), i._2) + } + } + + def getDynamicEndpoint(dynamicEndpointId: String, callContext: Option[CallContext]): OBPReturnType[DynamicEndpointT] = { + Connector.connector.vend.getDynamicEndpoint( + dynamicEndpointId, + callContext + ) map { + i => (connectorEmptyResponse(i._1, callContext), i._2) + } + } + + def getDynamicEndpoints(callContext: Option[CallContext]): OBPReturnType[List[DynamicEndpointT]] = { + Connector.connector.vend.getDynamicEndpoints( + callContext + ) + } + } } diff --git a/obp-api/src/main/scala/code/api/v4_0_0/APIMethods400.scala b/obp-api/src/main/scala/code/api/v4_0_0/APIMethods400.scala index 3e84c36d3..17eb1474b 100644 --- a/obp-api/src/main/scala/code/api/v4_0_0/APIMethods400.scala +++ b/obp-api/src/main/scala/code/api/v4_0_0/APIMethods400.scala @@ -3,6 +3,7 @@ package code.api.v4_0_0 import java.util.Date import code.DynamicData.DynamicData +import code.DynamicEndpoint.{DynamicEndpointCommons, DynamicEndpointSwagger} import code.accountattribute.AccountAttributeX import code.api.ChargePolicy import code.api.ResourceDocs1_4_0.SwaggerDefinitionsJSON._ @@ -10,7 +11,7 @@ import code.api.util.APIUtil.{fullBoxOrException, _} import code.api.util.ApiRole._ import code.api.util.ApiTag._ import code.api.util.ErrorMessages._ -import code.api.util.ExampleValue.{dynamicEntityRequestBodyExample, dynamicEntityResponseBodyExample} +import code.api.util.ExampleValue.{dynamicEndpointRequestBodyExample, dynamicEndpointResponseBodyExample, dynamicEntityRequestBodyExample, dynamicEntityResponseBodyExample} import code.api.util.NewStyle.HttpCode import code.api.util._ import code.api.v1_2_1.{JSONFactory, PostTransactionTagJSON} @@ -129,11 +130,11 @@ trait APIMethods400 { | INITIATED => COMPLETED |In case n persons needs to answer security challenge we have next flow of state of an `transaction request`: | INITIATED => NEXT_CHALLENGE_PENDING => ... => NEXT_CHALLENGE_PENDING => COMPLETED - | + | |The security challenge is bound to a user i.e. in case of right answer and the user is different than expected one the challenge will fail. | |Rule for calculating number of security challenges: - |If product Account attribute REQUIRED_CHALLENGE_ANSWERS=N then create N challenges + |If product Account attribute REQUIRED_CHALLENGE_ANSWERS=N then create N challenges |(one for every user that has a View where permission "can_add_transaction_request_to_any_account"=true) |In case REQUIRED_CHALLENGE_ANSWERS is not defined as an account attribute default value is 1. | @@ -361,7 +362,7 @@ trait APIMethods400 { ), Catalogs(Core, PSD2, OBWG), List(apiTagTransactionRequest, apiTagPSD2PIS, apiTagNewStyle)) - + // FREE_FORM. resourceDocs += ResourceDoc( createTransactionRequestFreeForm, @@ -422,7 +423,7 @@ trait APIMethods400 { account = BankIdAccountId(bankId, accountId) _ <- NewStyle.function.checkAuthorisationToCreateTransactionRequest(viewId, account, u, cc.callContext) - + _ <- Helper.booleanToFuture(InsufficientAuthorisationToCreateTransactionRequest) { u.hasOwnerViewAccess(BankIdAccountId(bankId, accountId)) || hasEntitlement(bankId.value, u.userId, ApiRole.canCreateAnyTransactionRequest) @@ -465,7 +466,7 @@ trait APIMethods400 { transactionRequestBodyRefundJson <- NewStyle.function.tryons(s"${InvalidJsonFormat}, it should be $ACCOUNT json format", 400, cc.callContext) { json.extract[TransactionRequestBodyRefundJsonV400] } - + transactionId = TransactionId(transactionRequestBodyRefundJson.refund.transaction_id) toBankId = BankId(transactionRequestBodyRefundJson.to.bank_id) toAccountId = AccountId(transactionRequestBodyRefundJson.to.account_id) @@ -474,28 +475,28 @@ trait APIMethods400 { transDetailsSerialized <- NewStyle.function.tryons(UnknownError, 400, callContext) { write(transactionRequestBodyRefundJson)(Serialization.formats(NoTypeHints)) } - + _ <- Helper.booleanToFuture(s"${RefundedTransaction} Current input amount is: '${transDetailsJson.value.amount}'. It can not be more than the original amount(${(transaction.amount).abs})") { (transaction.amount).abs >= transactionAmountNumber } - //TODO, we need additional field to guarantee the transaction is refunded... + //TODO, we need additional field to guarantee the transaction is refunded... // _ <- Helper.booleanToFuture(s"${RefundedTransaction}") { // !((transaction.description.toString contains(" Refund to ")) && (transaction.description.toString contains(" and transaction_id("))) // } - + //we add the extro info (counterparty name + transaction_id) for this special Refund endpoint. newDescription = s"${transactionRequestBodyRefundJson.description} - Refund for transaction_id: (${transactionId.value}) to ${transaction.otherAccount.counterpartyName}" - - //This is the refund endpoint, the original fromAccount is the `toAccount` which will receive money. + + //This is the refund endpoint, the original fromAccount is the `toAccount` which will receive money. refundToAccount = fromAccount - //This is the refund endpoint, the original toAccount is the `fromAccount` which will lose money. + //This is the refund endpoint, the original toAccount is the `fromAccount` which will lose money. refundFromAccount = toAccount - + (createdTransactionRequest, callContext) <- NewStyle.function.createTransactionRequestv400(u, viewId, refundFromAccount, refundToAccount, - transactionRequestType, + transactionRequestType, transactionRequestBodyRefundJson.copy(description = newDescription), transDetailsSerialized, sharedChargePolicy.toString, @@ -675,7 +676,7 @@ trait APIMethods400 { | |3) `id` : is `challenge.id` field in createTransactionRequest response body. | - |4) `answer` : must be `123` in case that Strong Customer Authentication method for OTP challenge is dummy. + |4) `answer` : must be `123` in case that Strong Customer Authentication method for OTP challenge is dummy. | For instance: SANDBOX_TAN_OTP_INSTRUCTION_TRANSPORT=dummy | Possible values are dummy,email and sms | In kafka mode, the answer can be got by phone message or other security ways. @@ -684,11 +685,11 @@ trait APIMethods400 { | INITIATED => COMPLETED |In case n persons needs to answer security challenge we have next flow of state of an `transaction request`: | INITIATED => NEXT_CHALLENGE_PENDING => ... => NEXT_CHALLENGE_PENDING => COMPLETED - | + | |The security challenge is bound to a user i.e. in case of right answer and the user is different than expected one the challenge will fail. | |Rule for calculating number of security challenges: - |If product Account attribute REQUIRED_CHALLENGE_ANSWERS=N then create N challenges + |If product Account attribute REQUIRED_CHALLENGE_ANSWERS=N then create N challenges |(one for every user that has a View where permission "can_add_transaction_request_to_any_account"=true) |In case REQUIRED_CHALLENGE_ANSWERS is not defined as an account attribute default value is 1. | @@ -731,14 +732,14 @@ trait APIMethods400 { account = BankIdAccountId(fromAccount.bankId, fromAccount.accountId) _ <- NewStyle.function.checkAuthorisationToCreateTransactionRequest(viewId, account, u, cc.callContext) - + // Check transReqId is valid (existingTransactionRequest, callContext) <- NewStyle.function.getTransactionRequestImpl(transReqId, cc.callContext) // Check the Transaction Request is still INITIATED _ <- Helper.booleanToFuture(TransactionRequestStatusNotInitiatedOrPending) { existingTransactionRequest.status.equals(TransactionRequestStatus.INITIATED.toString) || - existingTransactionRequest.status.equals(TransactionRequestStatus.NEXT_CHALLENGE_PENDING.toString) + existingTransactionRequest.status.equals(TransactionRequestStatus.NEXT_CHALLENGE_PENDING.toString) } // Check the input transactionRequestType is the same as when the user created the TransactionRequest @@ -780,7 +781,7 @@ trait APIMethods400 { .findAll(By(MappedExpectedChallengeAnswer.mTransactionRequestId, transReqId.value)) .count(_.successful == true) match { case number if number >= quorum => true - case _ => + case _ => MappedTransactionRequestProvider.saveTransactionRequestStatusImpl(transReqId, TransactionRequestStatus.NEXT_CHALLENGE_PENDING.toString) false } @@ -1112,7 +1113,7 @@ trait APIMethods400 { json.extract[PostResetPasswordUrlJsonV400] } } yield { - val resetLink = AuthUser.passwordResetUrl(postedData.username, postedData.email, postedData.user_id) + val resetLink = AuthUser.passwordResetUrl(postedData.username, postedData.email, postedData.user_id) (ResetPasswordUrlJsonV400(resetLink), HttpCode.`201`(cc.callContext)) } } @@ -1171,7 +1172,7 @@ trait APIMethods400 { hasEntitlement(bankId.value, loggedInUserId, canCreateAccount) || userIdAccountOwner == loggedInUserId } initialBalanceAsString = createAccountJson.balance.amount - //Note: here we map the product_code to account_type + //Note: here we map the product_code to account_type accountType = createAccountJson.product_code accountLabel = createAccountJson.label initialBalanceAsNumber <- NewStyle.function.tryons(InvalidAccountInitialBalance, 400, callContext) { @@ -1212,9 +1213,9 @@ trait APIMethods400 { } } } - - - + + + private def getApiInfoJSON() = { val (apiVersion, apiVersionStatus) = (implementedInApiVersion, OBPAPI4_0_0.versionStatus) val organisation = APIUtil.getPropsValue("hosted_by.organisation", "TESOBE") @@ -1756,9 +1757,9 @@ trait APIMethods400 { (_, callContext) <- NewStyle.function.getCounterpartyByCounterpartyId(CounterpartyId(postJson.counterparty_id), callContext) (directDebit, callContext) <- NewStyle.function.createDirectDebit( bankId.value, - accountId.value, - postJson.customer_id, - postJson.user_id, + accountId.value, + postJson.customer_id, + postJson.user_id, postJson.counterparty_id, if (postJson.date_signed.isDefined) postJson.date_signed.get else new Date(), postJson.date_starts, @@ -1826,7 +1827,7 @@ trait APIMethods400 { } } } - + resourceDocs += ResourceDoc( createStandingOrder, implementedInApiVersion, @@ -2027,8 +2028,8 @@ trait APIMethods400 { } } } - - + + resourceDocs += ResourceDoc( revokeUserAccessToView, implementedInApiVersion, @@ -2179,7 +2180,7 @@ trait APIMethods400 { CustomerAttributeType.withName(postedData.`type`) } (customer, callContext) <- NewStyle.function.getCustomerByCustomerId(customerId, cc.callContext) - _ <- Helper.booleanToFuture(InvalidCustomerBankId.replaceAll("Bank Id.",s"Bank Id ($bankId).").replaceAll("The Customer",s"The Customer($customerId)")){customer.bankId == bankId} + _ <- Helper.booleanToFuture(InvalidCustomerBankId.replaceAll("Bank Id.",s"Bank Id ($bankId).").replaceAll("The Customer",s"The Customer($customerId)")){customer.bankId == bankId} (accountAttribute, callContext) <- NewStyle.function.getCustomerAttributeById( customerAttributeId, callContext @@ -2335,8 +2336,8 @@ trait APIMethods400 { } } } - - + + resourceDocs += ResourceDoc( createTransactionAttribute, implementedInApiVersion, @@ -2648,7 +2649,7 @@ trait APIMethods400 { } } - + resourceDocs += ResourceDoc( createConsumer, implementedInApiVersion, @@ -2767,6 +2768,129 @@ trait APIMethods400 { } } + resourceDocs += ResourceDoc( + createDynamicEndpoint, + implementedInApiVersion, + nameOf(createDynamicEndpoint), + "POST", + "/management/dynamic_endpoints", + "Create DynamicEndpoint", + s"""Create a DynamicEndpoint. + | + | + |${authenticationRequiredMessage(true)} + | + |Create one DynamicEndpoint, + | + |""", + dynamicEndpointRequestBodyExample, + dynamicEndpointResponseBodyExample, + List( + $UserNotLoggedIn, + UserHasMissingRoles, + InvalidJsonFormat, + UnknownError + ), + Catalogs(notCore, notPSD2, notOBWG), + List(apiTagDynamicEndpoint, apiTagApi, apiTagNewStyle), + Some(List(canCreateDynamicEndpoint))) + + lazy val createDynamicEndpoint: OBPEndpoint = { + case "management" :: "dynamic_endpoints" :: Nil JsonPost json -> _ => { + cc => + for { + postedJson <- NewStyle.function.tryons(InvalidJsonFormat, 400, cc.callContext) { + json.extract[DynamicEndpointSwagger] + } + (dynamicEndpoint, callContext) <- NewStyle.function.createDynamicEndpoint(postedJson.swaggerString, cc.callContext) + } yield { + val commonsData: DynamicEndpointCommons = dynamicEndpoint + (commonsData, HttpCode.`201`(callContext)) + } + } + } + + + resourceDocs += ResourceDoc( + getDynamicEndpoint, + implementedInApiVersion, + nameOf(getDynamicEndpoint), + "GET", + "/management/dynamic_endpoints/DYNAMIC_ENDPOINT_ID", + "Get DynamicEndpoint", + s"""Get a DynamicEndpoint. + | + | + |${authenticationRequiredMessage(true)} + | + |Get one DynamicEndpoint, + | + |""", + emptyObjectJson, + dynamicEndpointResponseBodyExample, + List( + $UserNotLoggedIn, + UserHasMissingRoles, + InvalidJsonFormat, + UnknownError + ), + Catalogs(notCore, notPSD2, notOBWG), + List(apiTagDynamicEndpoint, apiTagApi, apiTagNewStyle), + Some(List(canGetDynamicEndpoint))) + + lazy val getDynamicEndpoint: OBPEndpoint = { + case "management" :: "dynamic_endpoints" :: dynamicEndpointId :: Nil JsonGet req => { + cc => + for { + (dynamicEndpoint, callContext) <- NewStyle.function.getDynamicEndpoint(dynamicEndpointId, cc.callContext) + } yield { + val commonsData: DynamicEndpointCommons = dynamicEndpoint + (commonsData, HttpCode.`201`(callContext)) + } + } + } + + resourceDocs += ResourceDoc( + getDynamicEndpoints, + implementedInApiVersion, + nameOf(getDynamicEndpoints), + "GET", + "/management/dynamic_endpoints", + "Get DynamicEndpoints", + s"""Get DynamicEndpoints. + | + | + |${authenticationRequiredMessage(true)} + | + |Get DynamicEndpoints, + | + |""", + emptyObjectJson, + ListResult( + "dynamic_entities", + List(dynamicEndpointResponseBodyExample) + ), + List( + $UserNotLoggedIn, + UserHasMissingRoles, + InvalidJsonFormat, + UnknownError + ), + Catalogs(notCore, notPSD2, notOBWG), + List(apiTagDynamicEndpoint, apiTagApi, apiTagNewStyle), + Some(List(canGetDynamicEndpoints))) + + lazy val getDynamicEndpoints: OBPEndpoint = { + case "management" :: "dynamic_endpoints" :: Nil JsonGet req => { + cc => + for { + (dynamicEndpoints, callContext) <- NewStyle.function.getDynamicEndpoints(cc.callContext) + } yield { + val listCommons: List[DynamicEndpointCommons] = dynamicEndpoints + (ListResult("dynamic_entities", listCommons), HttpCode.`200`(cc.callContext)) + } + } + } } diff --git a/obp-api/src/main/scala/code/bankconnectors/Connector.scala b/obp-api/src/main/scala/code/bankconnectors/Connector.scala index 010826d2d..4b5ae7051 100644 --- a/obp-api/src/main/scala/code/bankconnectors/Connector.scala +++ b/obp-api/src/main/scala/code/bankconnectors/Connector.scala @@ -3,6 +3,7 @@ package code.bankconnectors import java.util.Date import java.util.UUID.randomUUID +import code.DynamicEndpoint.DynamicEndpointT import code.accountholders.{AccountHolders, MapperAccountHolders} import code.api.{APIFailure, APIFailureNewStyle} import code.api.cache.Caching @@ -2180,4 +2181,16 @@ trait Connector extends MdcLoggable { def deleteCustomerAttribute(customerAttributeId: String, callContext: Option[CallContext]): OBPReturnType[Box[Boolean]] = Future{(Failure(setUnimplementedError), callContext)} + + def createDynamicEndpoint(swaggerString: String, callContext: Option[CallContext]): OBPReturnType[Box[DynamicEndpointT]] = Future { + (Failure(setUnimplementedError), callContext) + } + + def getDynamicEndpoint(dynamicEndpointId: String, callContext: Option[CallContext]): OBPReturnType[Box[DynamicEndpointT]] = Future { + (Failure(setUnimplementedError), callContext) + } + + def getDynamicEndpoints(callContext: Option[CallContext]): OBPReturnType[List[DynamicEndpointT]] = Future { + (List.empty[DynamicEndpointT], callContext) + } } diff --git a/obp-api/src/main/scala/code/bankconnectors/LocalMappedConnector.scala b/obp-api/src/main/scala/code/bankconnectors/LocalMappedConnector.scala index 88dbb4d40..b409d39fa 100644 --- a/obp-api/src/main/scala/code/bankconnectors/LocalMappedConnector.scala +++ b/obp-api/src/main/scala/code/bankconnectors/LocalMappedConnector.scala @@ -4,12 +4,13 @@ import java.util.Date import java.util.UUID.randomUUID import scala.concurrent.duration._ import code.DynamicData.DynamicDataProvider +import code.DynamicEndpoint.{DynamicEndpointProvider, DynamicEndpointT} import code.accountapplication.AccountApplicationX import code.accountattribute.AccountAttributeX import code.accountholders.{AccountHolders, MapperAccountHolders} import code.api.ResourceDocs1_4_0.SwaggerDefinitionsJSON import code.api.cache.Caching -import code.api.util.APIUtil.{OBPReturnType, DateWithMsFormat, generateUUID, hasEntitlement, isValidCurrencyISOCode, saveConnectorMetric, stringOrNull, unboxFullOrFail} +import code.api.util.APIUtil.{DateWithMsFormat, OBPReturnType, generateUUID, hasEntitlement, isValidCurrencyISOCode, saveConnectorMetric, stringOrNull, unboxFullOrFail} import code.api.util.ApiRole.canCreateAnyTransactionRequest import code.api.util.ErrorMessages._ import code.api.util._ @@ -73,7 +74,7 @@ import net.liftweb.common._ import net.liftweb.json import net.liftweb.json.{JArray, JBool, JObject, JValue} import net.liftweb.mapper.{By, _} -import net.liftweb.util.Helpers.{tryo,now, time, hours} +import net.liftweb.util.Helpers.{hours, now, time, tryo} import net.liftweb.util.Mailer import net.liftweb.util.Mailer.{From, PlainMailBodyType, Subject, To} import org.apache.commons.lang3.StringUtils @@ -84,7 +85,6 @@ import scala.collection.immutable.{List, Nil} import com.openbankproject.commons.ExecutionContext.Implicits.global import scala.concurrent._ - import scala.language.postfixOps import scala.math.{BigDecimal, BigInt} import scala.util.Random @@ -4001,6 +4001,18 @@ object LocalMappedConnector extends Connector with MdcLoggable { } yield { trtc } Full(res) } + + override def createDynamicEndpoint(swaggerString: String, callContext: Option[CallContext]): OBPReturnType[Box[DynamicEndpointT]] = Future { + (DynamicEndpointProvider.connectorMethodProvider.vend.create(swaggerString), callContext) + } + + override def getDynamicEndpoint(dynamicEndpointId: String, callContext: Option[CallContext]): OBPReturnType[Box[DynamicEndpointT]] = Future { + (DynamicEndpointProvider.connectorMethodProvider.vend.get(dynamicEndpointId), callContext) + } + + override def getDynamicEndpoints(callContext: Option[CallContext]): OBPReturnType[List[DynamicEndpointT]] = Future { + (DynamicEndpointProvider.connectorMethodProvider.vend.getAll(), callContext) + } override def deleteCustomerAttribute(customerAttributeId: String, callContext: Option[CallContext] ): OBPReturnType[Box[Boolean]] = { CustomerAttributeX.customerAttributeProvider.vend.deleteCustomerAttribute(customerAttributeId) map { ( _, callContext) } diff --git a/obp-api/src/main/scala/code/dynamicEndpoint/DynamicEndpointProvider.scala b/obp-api/src/main/scala/code/dynamicEndpoint/DynamicEndpointProvider.scala new file mode 100644 index 000000000..229686706 --- /dev/null +++ b/obp-api/src/main/scala/code/dynamicEndpoint/DynamicEndpointProvider.scala @@ -0,0 +1,34 @@ +package code.DynamicEndpoint + +import com.openbankproject.commons.model.{Converter, JsonFieldReName} +import net.liftweb.common.Box +import net.liftweb.util.SimpleInjector + +object DynamicEndpointProvider extends SimpleInjector { + + val connectorMethodProvider = new Inject(buildOne _) {} + + def buildOne: MappedDynamicEndpointProvider.type = MappedDynamicEndpointProvider +} + +trait DynamicEndpointT { + def dynamicEndpointId: Option[String] + def swaggerString: String +} + +case class DynamicEndpointCommons( + dynamicEndpointId: Option[String] = None, + swaggerString: String + ) extends DynamicEndpointT with JsonFieldReName + +object DynamicEndpointCommons extends Converter[DynamicEndpointT, DynamicEndpointCommons] + +case class DynamicEndpointSwagger(dynamicEndpointId: Option[String] = None, swaggerString: String) + +trait DynamicEndpointProvider { + def create(swaggerString: String): Box[DynamicEndpointT] + def update(dynamicEndpointId: String, swaggerString: String): Box[DynamicEndpointT] + def get(dynamicEndpointId: String): Box[DynamicEndpointT] + def getAll(): List[DynamicEndpointT] + def delete(dynamicEndpointId: String): Boolean +} \ No newline at end of file diff --git a/obp-api/src/main/scala/code/dynamicEndpoint/MapppedDynamicEndpointProvider.scala b/obp-api/src/main/scala/code/dynamicEndpoint/MapppedDynamicEndpointProvider.scala new file mode 100644 index 000000000..e7ccbd6e4 --- /dev/null +++ b/obp-api/src/main/scala/code/dynamicEndpoint/MapppedDynamicEndpointProvider.scala @@ -0,0 +1,40 @@ +package code.DynamicEndpoint + +import code.api.util.CustomJsonFormats +import code.util.MappedUUID +import net.liftweb.common.Box +import net.liftweb.mapper._ +import net.liftweb.util.Helpers.tryo + +object MappedDynamicEndpointProvider extends DynamicEndpointProvider with CustomJsonFormats{ + override def create(swaggerString: String): Box[DynamicEndpointT] = { + tryo{DynamicEndpoint.create.SwaggerString(swaggerString).saveMe()} + } + override def update(dynamicEndpointId: String, swaggerString: String): Box[DynamicEndpointT] = { + DynamicEndpoint.find(By(DynamicEndpoint.DynamicEndpointId, dynamicEndpointId)).map(_.SwaggerString(swaggerString).saveMe()) + } + + override def get(dynamicEndpointId: String): Box[DynamicEndpointT] = DynamicEndpoint.find(By(DynamicEndpoint.DynamicEndpointId, dynamicEndpointId)) + + override def getAll(): List[DynamicEndpointT] = DynamicEndpoint.findAll() + + override def delete(dynamicEndpointId: String): Boolean = DynamicEndpoint.bulkDelete_!!(By(DynamicEndpoint.DynamicEndpointId, dynamicEndpointId)) + +} + +class DynamicEndpoint extends DynamicEndpointT with LongKeyedMapper[DynamicEndpoint] with IdPK { + + override def getSingleton = DynamicEndpoint + + object DynamicEndpointId extends MappedUUID(this) + + object SwaggerString extends MappedText(this) + + override def dynamicEndpointId: Option[String] = Option(DynamicEndpointId.get) + override def swaggerString: String = SwaggerString.get +} + +object DynamicEndpoint extends DynamicEndpoint with LongKeyedMetaMapper[DynamicEndpoint] { + override def dbIndexes = UniqueIndex(DynamicEndpointId) :: super.dbIndexes +} + From 9a98be70c249fc7ffcc130daddd99b1fa9aaa654 Mon Sep 17 00:00:00 2001 From: shuang Date: Thu, 26 Mar 2020 16:26:02 +0800 Subject: [PATCH 10/35] feature/add_dynamic_endpoints_with_swagger: upload swagger content and show added endpoints, process not finished. --- .../resources/props/sample.props.template | 5 +- .../main/scala/code/api/util/APIUtil.scala | 4 +- .../scala/code/api/v3_1_0/APIMethods310.scala | 1 + .../scala/code/api/v4_0_0/APIMethods400.scala | 78 ++---- .../api/v4_0_0/DynamicEndpointHelper.scala | 239 ++++++++++++++++++ .../code/api/v4_0_0/DynamicEntityHelper.scala | 2 +- .../scala/code/api/v4_0_0/OBPAPI4_0_0.scala | 2 +- 7 files changed, 269 insertions(+), 62 deletions(-) create mode 100644 obp-api/src/main/scala/code/api/v4_0_0/DynamicEndpointHelper.scala diff --git a/obp-api/src/main/resources/props/sample.props.template b/obp-api/src/main/resources/props/sample.props.template index 4e59da766..eced776c6 100644 --- a/obp-api/src/main/resources/props/sample.props.template +++ b/obp-api/src/main/resources/props/sample.props.template @@ -766,4 +766,7 @@ stored_procedure_connector.poolFactoryName=commons-dbcp2 # ----------------------------------------------------------------------- # Set whether DynamicEntity display name starts with underscore, default is true -dynamic_entities_have_prefix=true \ No newline at end of file +dynamic_entities_have_prefix=true + +# Url prefix of dynamic endpoints, default is dynamic. e.g if set to foobar, one url can be /obp/v4.0.0/foobar/Address +dynamic_endpoints_url_prefix=dynamic \ No newline at end of file diff --git a/obp-api/src/main/scala/code/api/util/APIUtil.scala b/obp-api/src/main/scala/code/api/util/APIUtil.scala index fa6557bdf..5468d8843 100644 --- a/obp-api/src/main/scala/code/api/util/APIUtil.scala +++ b/obp-api/src/main/scala/code/api/util/APIUtil.scala @@ -81,7 +81,7 @@ import scala.collection.JavaConverters._ import scala.collection.immutable.{List, Nil} import scala.collection.mutable.ArrayBuffer import com.openbankproject.commons.ExecutionContext.Implicits.global -import com.openbankproject.commons.util.{ApiVersion, ReflectUtils, ScannedApiVersion} +import com.openbankproject.commons.util.{ApiVersion, JsonAble, ReflectUtils, ScannedApiVersion} import com.openbankproject.commons.util.Functions.Implicits._ import org.apache.commons.lang3.StringUtils @@ -1295,9 +1295,9 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{ for { (boxUser, callContext) <- checkAuth(cc) + // roles check _ <- checkRoles(bankId, boxUser) - // check bankId valid (bank, callContext) <- checkBank(bankId, callContext) // check accountId valid diff --git a/obp-api/src/main/scala/code/api/v3_1_0/APIMethods310.scala b/obp-api/src/main/scala/code/api/v3_1_0/APIMethods310.scala index 739a42fe4..e3b9cb64d 100644 --- a/obp-api/src/main/scala/code/api/v3_1_0/APIMethods310.scala +++ b/obp-api/src/main/scala/code/api/v3_1_0/APIMethods310.scala @@ -2227,6 +2227,7 @@ trait APIMethods310 { emptyObjectJson, List( UserHasMissingRoles, + BankNotFound, UnknownError ), Catalogs(notCore, notPSD2, notOBWG), diff --git a/obp-api/src/main/scala/code/api/v4_0_0/APIMethods400.scala b/obp-api/src/main/scala/code/api/v4_0_0/APIMethods400.scala index 17eb1474b..aa6d3d008 100644 --- a/obp-api/src/main/scala/code/api/v4_0_0/APIMethods400.scala +++ b/obp-api/src/main/scala/code/api/v4_0_0/APIMethods400.scala @@ -59,6 +59,7 @@ import scala.collection.mutable.ArrayBuffer import scala.concurrent.Future import code.model._ import org.apache.commons.lang3.StringUtils +import net.liftweb.json.compactRender trait APIMethods400 { self: RestHelper => @@ -2723,62 +2724,14 @@ trait APIMethods400 { } } - val customerAttributeGeneralInfo = - s""" - |CustomerAttributes are used to enhance the OBP Customer object with Bank specific entities. - | - """.stripMargin - - resourceDocs += ResourceDoc( - deleteCustomerAttribute, - implementedInApiVersion, - nameOf(deleteCustomerAttribute), - "DELETE", - "/banks/BANK_ID/CUSTOMER_ID/attributes/CUSTOMER_ATTRIBUTE_ID", - "Delete Customer Attribute", - s""" Delete Customer Attribute - | - |$customerAttributeGeneralInfo - | - |Delete a Customer Attribute by its id. - | - |${authenticationRequiredMessage(true)} - | - |""", - emptyObjectJson, - emptyObjectJson, - List( - UserHasMissingRoles, - UnknownError - ), - Catalogs(notCore, notPSD2, notOBWG), - List(apiTagCustomer, apiTagNewStyle), - Some(List(canDeleteCustomerAttributeAtOneBank))) - - lazy val deleteCustomerAttribute : OBPEndpoint = { - case "banks" :: bankId :: "customers" :: "attributes" :: customerAttributeId :: Nil JsonDelete _=> { - cc => - for { - (Full(u), callContext) <- authenticatedAccess(cc) - (_, callContext) <- NewStyle.function.getBank(BankId(bankId), callContext) - (customerAttribute, callContext) <- NewStyle.function.deleteCustomerAttribute(customerAttributeId, callContext) - } yield { - (Full(customerAttribute), HttpCode.`204`(callContext)) - } - } - } - resourceDocs += ResourceDoc( createDynamicEndpoint, implementedInApiVersion, nameOf(createDynamicEndpoint), "POST", "/management/dynamic_endpoints", - "Create DynamicEndpoint", + " Create DynamicEndpoint", s"""Create a DynamicEndpoint. - | - | - |${authenticationRequiredMessage(true)} | |Create one DynamicEndpoint, | @@ -2817,12 +2770,10 @@ trait APIMethods400 { nameOf(getDynamicEndpoint), "GET", "/management/dynamic_endpoints/DYNAMIC_ENDPOINT_ID", - "Get DynamicEndpoint", + " Get DynamicEndpoint", s"""Get a DynamicEndpoint. | | - |${authenticationRequiredMessage(true)} - | |Get one DynamicEndpoint, | |""", @@ -2849,18 +2800,15 @@ trait APIMethods400 { } } } - + resourceDocs += ResourceDoc( getDynamicEndpoints, implementedInApiVersion, nameOf(getDynamicEndpoints), "GET", "/management/dynamic_endpoints", - "Get DynamicEndpoints", + " Get DynamicEndpoints", s"""Get DynamicEndpoints. - | - | - |${authenticationRequiredMessage(true)} | |Get DynamicEndpoints, | @@ -2892,6 +2840,22 @@ trait APIMethods400 { } } + lazy val dynamicEndpoint: OBPEndpoint = { + case EntityName(entityName) :: Nil JsonGet req => { cc => + val listName = StringHelpers.snakify(English.plural(entityName)) + for { + (Full(u), callContext) <- authenticatedAccess(cc) + _ <- NewStyle.function.hasEntitlement("", u.userId, DynamicEntityInfo.canGetRole(entityName), callContext) + (box, _) <- NewStyle.function.invokeDynamicConnector(GET_ALL, entityName, None, None, Some(cc)) + resultList: JArray = unboxResult(box.asInstanceOf[Box[JArray]], entityName) + } yield { + import net.liftweb.json.JsonDSL._ + + val jValue: JObject = listName -> filterDynamicObjects(resultList, req) + (jValue, HttpCode.`200`(Some(cc))) + } + } + } } diff --git a/obp-api/src/main/scala/code/api/v4_0_0/DynamicEndpointHelper.scala b/obp-api/src/main/scala/code/api/v4_0_0/DynamicEndpointHelper.scala new file mode 100644 index 000000000..8cb34fd39 --- /dev/null +++ b/obp-api/src/main/scala/code/api/v4_0_0/DynamicEndpointHelper.scala @@ -0,0 +1,239 @@ +package code.api.v4_0_0 + +import java.io.File +import java.nio.charset.Charset +import java.util +import java.util.{Date, Objects} + +import code.DynamicEndpoint.{DynamicEndpointProvider, DynamicEndpointT} +import code.api.util.APIUtil.{Catalogs, OBPEndpoint, ResourceDoc, authenticationRequiredMessage, emptyObjectJson, generateUUID, notCore, notOBWG, notPSD2} +import code.api.util.ApiTag.{ResourceDocTag, apiTagApi, apiTagNewStyle} +import code.api.util.ErrorMessages.{InvalidJsonFormat, UnknownError, UserHasMissingRoles, UserNotLoggedIn} +import code.api.util.{APIUtil, ApiRole, ApiTag, CustomJsonFormats, NewStyle} +import code.api.util.ApiRole.getOrCreateDynamicApiRole +import com.openbankproject.commons.model.enums.DynamicEntityFieldType +import com.openbankproject.commons.util.{ApiVersion, Functions} +import io.swagger.v3.oas.models.{OpenAPI, Operation, PathItem} +import io.swagger.v3.oas.models.PathItem.HttpMethod +import io.swagger.v3.oas.models.media.{ArraySchema, BooleanSchema, Content, DateSchema, DateTimeSchema, IntegerSchema, NumberSchema, ObjectSchema, Schema, StringSchema} +import io.swagger.v3.oas.models.parameters.RequestBody +import io.swagger.v3.oas.models.responses.ApiResponses +import io.swagger.v3.parser.OpenAPIV3Parser +import net.liftweb.json.JsonAST.{JArray, JField, JObject} +import net.liftweb.json.JsonDSL._ +import net.liftweb.json +import net.liftweb.json.JValue +import net.liftweb.util.StringHelpers +import org.apache.commons.io.FileUtils +import org.apache.commons.lang3.StringUtils +import org.atteo.evo.inflector.English + +import scala.collection.immutable.{List, Nil} +import scala.collection.mutable +import scala.collection.mutable.ArrayBuffer +import scala.collection.JavaConverters._ + + +object DynamicEndpointHelper { + private implicit val formats = CustomJsonFormats.formats + /** + * dynamic endpoints url prefix + */ + val urlPrefix = APIUtil.getPropsValue("dynamic_endpoints_url_prefix", "dynamic") + + val dynamicEndpointsUrl: Set[(String, HttpMethod)] = Set() + + def swaggerToResourceDocs(content: String): mutable.Iterable[ResourceDoc] = { + val tempSwaggerFile = File.createTempFile("temp", ".swagger") + FileUtils.write(tempSwaggerFile, content, Charset.forName("utf-8")) + val openAPI: OpenAPI = new OpenAPIV3Parser().read(tempSwaggerFile.getAbsolutePath) + // Delete temp file when program exits, only if delete fail. + if(!FileUtils.deleteQuietly(tempSwaggerFile)){ + tempSwaggerFile.deleteOnExit() + } + val tags: List[ResourceDocTag] = List(ApiTag.apiTagDynamicEndpoint, apiTagApi, apiTagNewStyle) + + val paths: mutable.Map[String, PathItem] = openAPI.getPaths.asScala + def entitlementSuffix(path: String) = Math.abs(path.hashCode).toString.substring(0, 3) // to avoid different swagger have same entitlement + val docs: mutable.Iterable[ResourceDoc] = for { + (path, pathItem) <- paths + (method: HttpMethod, op: Operation) <- pathItem.readOperationsMap.asScala + } yield { + val implementedInApiVersion = ApiVersion.v4_0_0 + val partialFunction: OBPEndpoint = APIMethods400.Implementations4_0_0.genericEndpoint // TODO create real endpoint + val partialFunctionName: String = s"$method-$path".replace('/', '_') + val requestVerb: String = method.name() + val requestUrl: String = s"/$urlPrefix/$path".replace("//", "/") + val summary: String = Option(pathItem.getSummary) + .filter(StringUtils.isNotBlank) + .getOrElse(buildSummary(method, op, path)) + val description: String = Option(pathItem.getDescription) + .filter(StringUtils.isNotBlank) + .orElse(Option(op.getDescription)) + .filter(StringUtils.isNotBlank) + .map(_.capitalize) + .getOrElse(summary) + val exampleRequestBody: scala.Product = getRequestExample(openAPI, op.getRequestBody) + val successResponseBody: scala.Product = getResponseExample(openAPI, op.getResponses) + val errorResponseBodies: List[String] = List( + UserNotLoggedIn, + UserHasMissingRoles, + UnknownError + ) + val catalogs: Catalogs = Catalogs(notCore, notPSD2, notOBWG) + + val roleName = s"Can$summary${entitlementSuffix(path)}" + .replaceFirst("Can(Create|Update|Get|Delete)", "Can$1Dynamic") + .replace(" ", "") + val roles: Option[List[ApiRole]] = Some(List( + ApiRole.getOrCreateDynamicApiRole(roleName) + )) + val connectorMethods = Some(List(s"""dynamicEntityProcess: parameters contains {"key": "entityName", "value": "$summary"}""")) //TODO temp + ResourceDoc( + partialFunction, + implementedInApiVersion, + partialFunctionName, + requestVerb, + requestUrl, + summary, + description, + exampleRequestBody, + successResponseBody, + errorResponseBodies, + catalogs, + tags, + roles, + connectorMethods = connectorMethods + ) + } + docs + } + + def doc: ArrayBuffer[ResourceDoc] = { + val dynamicEndpoints: List[DynamicEndpointT] = DynamicEndpointProvider.connectorMethodProvider.vend.getAll() + + val docs = dynamicEndpoints.flatMap(it => swaggerToResourceDocs(it.swaggerString)) + ArrayBuffer[ResourceDoc](docs:_*) + } + + private def buildSummary(method: HttpMethod, op: Operation, path: String): String = method match { + case _ if StringUtils.isNotBlank(op.getSummary) => op.getSummary + case HttpMethod.GET | HttpMethod.DELETE => + val opName = if(method == HttpMethod.GET) "Get" else "Delete" + op.getResponses.asScala + .find(_._1.startsWith("20")) + .flatMap(it => getRef(it._2.getContent, it._2.get$ref()) ) + .map(StringUtils.substringAfterLast(_, "/")) + .map(entityName => s"$opName $entityName") + .orElse(Option(op.getDescription)) + .filter(StringUtils.isNotBlank) + .orElse(Option(s"$opName $path")) + .map(_.replaceFirst("(?i)((get|delete)\\s+\\S+).*", "$1")) + .map(capitalize) + .get + + case m@(HttpMethod.POST | HttpMethod.PUT) => + val opName = if(m == HttpMethod.POST) "Create" else "Update" + + getRef(op.getRequestBody.getContent, op.getRequestBody.get$ref()) + .map(StringUtils.substringAfterLast(_, "/")) + .map(entityName => s"$opName $entityName") + .orElse(Option(op.getDescription)) + .filter(StringUtils.isNotBlank) + .orElse(Option(s"$method $path")) + .map(capitalize) + .get + case _ => throw new RuntimeException(s"Support HTTP METHOD: GET, POST, PUT, DELETE, current method is $method") + } + private def capitalize(str: String): String = + StringUtils.split(str, " ").map(_.capitalize).mkString(" ") + + private def getRequestExample(openAPI: OpenAPI, body: RequestBody): Product = { + if(body == null || body.getContent == null) { + "" + } else { + getExample(openAPI, getRef(body.getContent, body.get$ref()).orNull) + } + } + private def getResponseExample(openAPI: OpenAPI, apiResponses: ApiResponses): Product = { + if(apiResponses == null || apiResponses.isEmpty) { + JObject() + } else { + val ref: Option[String] = apiResponses.asScala + .find(_._1.startsWith("20")) + .flatMap(it => getRef(it._2.getContent, it._2.get$ref())) + getExample(openAPI, ref.orNull) + } + } + + private def getRef(content: Content, $ref: String): Option[String] = { + if(StringUtils.isNoneBlank($ref)) { + Option($ref) + } else { + val schemaRef: Option[String] = Option(content.get("application/json")) + .flatMap(it => Option[Schema[_]](it.getSchema)) + .map(_.get$ref()) + .filter(StringUtils.isNoneBlank(_)) + + if(schemaRef.isDefined) { + Option(schemaRef.get) + } else { + val supportMediaTypes = content.values().asScala + supportMediaTypes.collectFirst { + case mediaType if mediaType.getSchema != null && StringUtils.isNotBlank(mediaType.getSchema.get$ref()) => + mediaType.getSchema.get$ref() + } + } + } + + } + private val RegexDefinitions = """(?:#/components/schemas(?:/#definitions)?/)(.+)""".r + private val RegexResponse = """#/responses/(.+)""".r + + private def getExample(openAPI: OpenAPI, ref: String): Product = ref match { + case null => JObject() + + case RegexResponse(refName) => + val response = openAPI.getComponents.getResponses.get(refName) + val ref = getRef(response.getContent, response.get$ref()) + getExample(openAPI, ref.get) + + case RegexDefinitions(refName) => + openAPI.getComponents.getSchemas.get(refName) match { + case o: ObjectSchema => + val properties: util.Map[String, Schema[_]] = o.getProperties + + val jFields: mutable.Iterable[JField] = properties.asScala.map { kv => + val (name, value) = kv + val valueExample = if(value.getClass == classOf[Schema[_]]) getExample(openAPI, value.get$ref()) else getPropertyExample(value) + JField(name, json.Extraction.decompose(valueExample)) + } + JObject(jFields.toList) + + case a: ArraySchema => + Option(a.getExample) + .map(json.Extraction.decompose(_).asInstanceOf[JObject]) + .getOrElse { + val schema: Schema[_] = a.getItems + val singleItem: Any = if(schema.getClass == classOf[Schema[_]]) getExample(openAPI, schema.get$ref()) else getPropertyExample(schema) + val jItem = json.Extraction.decompose(singleItem) + jItem :: Nil + } + } + + } + + private def getPropertyExample(schema: Schema[_]) = schema match { + case b: BooleanSchema => Option(b.getExample).getOrElse(true) + case d: DateSchema => Option(d.getExample).getOrElse { + APIUtil.DateWithDayFormat.format(new Date()) + } + case t: DateTimeSchema => Option(t.getExample).getOrElse { + APIUtil.DateWithSecondsFormat.format(new Date()) + } + case i: IntegerSchema => Option(i.getExample).getOrElse(1) + case n: NumberSchema => Option(n.getExample).getOrElse(1.2) + case s: StringSchema => Option(s.getExample).getOrElse("string") + case _ => throw new RuntimeException(s"Not support type $schema, please support it if necessary.") + } +} \ No newline at end of file diff --git a/obp-api/src/main/scala/code/api/v4_0_0/DynamicEntityHelper.scala b/obp-api/src/main/scala/code/api/v4_0_0/DynamicEntityHelper.scala index deee1304f..bf3fa7cf2 100644 --- a/obp-api/src/main/scala/code/api/v4_0_0/DynamicEntityHelper.scala +++ b/obp-api/src/main/scala/code/api/v4_0_0/DynamicEntityHelper.scala @@ -32,7 +32,7 @@ object MockerConnector { def definitionsMap = NewStyle.function.getDynamicEntities().map(it => (it.entityName, DynamicEntityInfo(it.metadataJson, it.entityName))).toMap - def doc = { + def doc: ArrayBuffer[ResourceDoc] = { val docs: Seq[ResourceDoc] = definitionsMap.values.flatMap(createDocs).toSeq collection.mutable.ArrayBuffer(docs:_*) } diff --git a/obp-api/src/main/scala/code/api/v4_0_0/OBPAPI4_0_0.scala b/obp-api/src/main/scala/code/api/v4_0_0/OBPAPI4_0_0.scala index 3e3983bfb..649c7125c 100644 --- a/obp-api/src/main/scala/code/api/v4_0_0/OBPAPI4_0_0.scala +++ b/obp-api/src/main/scala/code/api/v4_0_0/OBPAPI4_0_0.scala @@ -67,7 +67,7 @@ object OBPAPI4_0_0 extends OBPRestHelper with APIMethods130 with APIMethods140 w // if old version ResourceDoc objects have the same name endpoint with new version, omit old version ResourceDoc. def allResourceDocs = collectResourceDocs(OBPAPI3_1_0.allResourceDocs, Implementations4_0_0.resourceDocs, - MockerConnector.doc) + MockerConnector.doc, DynamicEndpointHelper.doc) .filterNot(it => it.partialFunctionName.matches(excludeEndpoints.mkString("|"))) //TODO exclude two endpoints, after training we need add logic to exclude endpoints From 1131367fd1cc87606841743a013da7b621e59cf1 Mon Sep 17 00:00:00 2001 From: shuang Date: Thu, 26 Mar 2020 19:31:11 +0800 Subject: [PATCH 11/35] feature/add_dynamic_endpoints_with_swagger: make upload swagger as string instead of json. --- .../scala/code/api/util/ExampleValue.scala | 2 +- .../scala/code/api/v4_0_0/APIMethods400.scala | 12 ++++++++-- .../api/v4_0_0/DynamicEndpointHelper.scala | 24 ++++++++++++------- .../DynamicEndpointProvider.scala | 2 +- 4 files changed, 27 insertions(+), 13 deletions(-) diff --git a/obp-api/src/main/scala/code/api/util/ExampleValue.scala b/obp-api/src/main/scala/code/api/util/ExampleValue.scala index bcaf617e1..657e26f57 100644 --- a/obp-api/src/main/scala/code/api/util/ExampleValue.scala +++ b/obp-api/src/main/scala/code/api/util/ExampleValue.scala @@ -360,7 +360,7 @@ object ExampleValue { lazy val dynamicEntityResponseBodyExample = dynamicEntityRequestBodyExample.copy(dynamicEntityId = Some("dynamic-entity-id")) - lazy val dynamicEndpointRequestBodyExample = DynamicEndpointSwagger(None, """{ + lazy val dynamicEndpointRequestBodyExample = DynamicEndpointSwagger("""{ | "swagger": "2.0", | "info": { | "version": "0.0.1", diff --git a/obp-api/src/main/scala/code/api/v4_0_0/APIMethods400.scala b/obp-api/src/main/scala/code/api/v4_0_0/APIMethods400.scala index aa6d3d008..742bd2650 100644 --- a/obp-api/src/main/scala/code/api/v4_0_0/APIMethods400.scala +++ b/obp-api/src/main/scala/code/api/v4_0_0/APIMethods400.scala @@ -2748,12 +2748,20 @@ trait APIMethods400 { List(apiTagDynamicEndpoint, apiTagApi, apiTagNewStyle), Some(List(canCreateDynamicEndpoint))) + private val BodyPost = new TestPost[String] with JsonTest { + def body(r: Req): Box[String] = { + val value = r.body.map(it => new String(it)) + value + } + } + lazy val createDynamicEndpoint: OBPEndpoint = { - case "management" :: "dynamic_endpoints" :: Nil JsonPost json -> _ => { + case "management" :: "dynamic_endpoints" :: Nil BodyPost body -> req => { cc => for { postedJson <- NewStyle.function.tryons(InvalidJsonFormat, 400, cc.callContext) { - json.extract[DynamicEndpointSwagger] + DynamicEndpointHelper.parseSwaggerContent(body) + DynamicEndpointSwagger(body) } (dynamicEndpoint, callContext) <- NewStyle.function.createDynamicEndpoint(postedJson.swaggerString, cc.callContext) } yield { diff --git a/obp-api/src/main/scala/code/api/v4_0_0/DynamicEndpointHelper.scala b/obp-api/src/main/scala/code/api/v4_0_0/DynamicEndpointHelper.scala index 8cb34fd39..7d16328e0 100644 --- a/obp-api/src/main/scala/code/api/v4_0_0/DynamicEndpointHelper.scala +++ b/obp-api/src/main/scala/code/api/v4_0_0/DynamicEndpointHelper.scala @@ -44,13 +44,8 @@ object DynamicEndpointHelper { val dynamicEndpointsUrl: Set[(String, HttpMethod)] = Set() def swaggerToResourceDocs(content: String): mutable.Iterable[ResourceDoc] = { - val tempSwaggerFile = File.createTempFile("temp", ".swagger") - FileUtils.write(tempSwaggerFile, content, Charset.forName("utf-8")) - val openAPI: OpenAPI = new OpenAPIV3Parser().read(tempSwaggerFile.getAbsolutePath) - // Delete temp file when program exits, only if delete fail. - if(!FileUtils.deleteQuietly(tempSwaggerFile)){ - tempSwaggerFile.deleteOnExit() - } + val openAPI: OpenAPI = parseSwaggerContent(content) + val tags: List[ResourceDocTag] = List(ApiTag.apiTagDynamicEndpoint, apiTagApi, apiTagNewStyle) val paths: mutable.Map[String, PathItem] = openAPI.getPaths.asScala @@ -73,8 +68,8 @@ object DynamicEndpointHelper { .filter(StringUtils.isNotBlank) .map(_.capitalize) .getOrElse(summary) - val exampleRequestBody: scala.Product = getRequestExample(openAPI, op.getRequestBody) - val successResponseBody: scala.Product = getResponseExample(openAPI, op.getResponses) + val exampleRequestBody: Product = getRequestExample(openAPI, op.getRequestBody) + val successResponseBody: Product = getResponseExample(openAPI, op.getResponses) val errorResponseBodies: List[String] = List( UserNotLoggedIn, UserHasMissingRoles, @@ -109,6 +104,17 @@ object DynamicEndpointHelper { docs } + def parseSwaggerContent(content: String): OpenAPI = { + val tempSwaggerFile = File.createTempFile("temp", ".swagger") + FileUtils.write(tempSwaggerFile, content, Charset.forName("utf-8")) + val openAPI: OpenAPI = new OpenAPIV3Parser().read(tempSwaggerFile.getAbsolutePath) + // Delete temp file when program exits, only if delete fail. + if(!FileUtils.deleteQuietly(tempSwaggerFile)){ + tempSwaggerFile.deleteOnExit() + } + openAPI + } + def doc: ArrayBuffer[ResourceDoc] = { val dynamicEndpoints: List[DynamicEndpointT] = DynamicEndpointProvider.connectorMethodProvider.vend.getAll() diff --git a/obp-api/src/main/scala/code/dynamicEndpoint/DynamicEndpointProvider.scala b/obp-api/src/main/scala/code/dynamicEndpoint/DynamicEndpointProvider.scala index 229686706..f38c239e2 100644 --- a/obp-api/src/main/scala/code/dynamicEndpoint/DynamicEndpointProvider.scala +++ b/obp-api/src/main/scala/code/dynamicEndpoint/DynamicEndpointProvider.scala @@ -23,7 +23,7 @@ case class DynamicEndpointCommons( object DynamicEndpointCommons extends Converter[DynamicEndpointT, DynamicEndpointCommons] -case class DynamicEndpointSwagger(dynamicEndpointId: Option[String] = None, swaggerString: String) +case class DynamicEndpointSwagger(swaggerString: String, dynamicEndpointId: Option[String] = None) trait DynamicEndpointProvider { def create(swaggerString: String): Box[DynamicEndpointT] From e56875e92a29684933b5cf82876b4199e428724c Mon Sep 17 00:00:00 2001 From: shuang Date: Thu, 26 Mar 2020 22:33:26 +0800 Subject: [PATCH 12/35] feature/add_dynamic_endpoints_with_swagger: request body, response content all not show special character escape --- .../main/scala/code/api/util/APIUtil.scala | 2 +- .../scala/code/api/util/ExampleValue.scala | 489 +++++++++--------- .../scala/code/api/v4_0_0/APIMethods400.scala | 34 +- 3 files changed, 265 insertions(+), 260 deletions(-) diff --git a/obp-api/src/main/scala/code/api/util/APIUtil.scala b/obp-api/src/main/scala/code/api/util/APIUtil.scala index 5468d8843..063e38166 100644 --- a/obp-api/src/main/scala/code/api/util/APIUtil.scala +++ b/obp-api/src/main/scala/code/api/util/APIUtil.scala @@ -1081,7 +1081,7 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{ case class EmptyClassJson(jsonString: String ="{}") // Used to document the API calls - case class ResourceDoc( + case class ResourceDoc ( partialFunction: OBPEndpoint, // PartialFunction[Req, Box[User] => Box[JsonResponse]], implementedInApiVersion: ScannedApiVersion, // TODO: Use ApiVersion enumeration instead of string partialFunctionName: String, // The string name of the partial function that implements this resource. Could use it to link to the source code that implements the call diff --git a/obp-api/src/main/scala/code/api/util/ExampleValue.scala b/obp-api/src/main/scala/code/api/util/ExampleValue.scala index 657e26f57..b95acd9df 100644 --- a/obp-api/src/main/scala/code/api/util/ExampleValue.scala +++ b/obp-api/src/main/scala/code/api/util/ExampleValue.scala @@ -1,10 +1,12 @@ package code.api.util -import code.DynamicEndpoint.DynamicEndpointSwagger +import net.liftweb.json.JsonDSL._ import code.api.util.Glossary.{glossaryItems, makeGlossaryItem} import code.dynamicEntity.{DynamicEntityDefinition, DynamicEntityFooBar, DynamicEntityFullBarFields, DynamicEntityIntTypeExample, DynamicEntityStringTypeExample} import com.openbankproject.commons.model.enums.DynamicEntityFieldType +import net.liftweb.json +import net.liftweb.json.JObject case class ConnectorField(value: String, description: String) { @@ -360,247 +362,250 @@ object ExampleValue { lazy val dynamicEntityResponseBodyExample = dynamicEntityRequestBodyExample.copy(dynamicEntityId = Some("dynamic-entity-id")) - lazy val dynamicEndpointRequestBodyExample = DynamicEndpointSwagger("""{ - | "swagger": "2.0", - | "info": { - | "version": "0.0.1", - | "title": "Portus EVS sandbox demo API", - | "description": "Portus EVS sandbox demo API", - | "contact": { - | "name": "Digital & FinTech, Grant Thornton", - | "email": "peng.xu@ie.gt.com", - | "url": "https://www.tesobe.com/" - | } - | }, - | "host": "localhost:8080", - | "basePath": "/user", - | "schemes": [ - | "http" - | ], - | "consumes": [ - | "application/json" - | ], - | "produces": [ - | "application/json" - | ], - | "paths": { - | "/save": { - | "post": { - | "parameters": [ - | { - | "name": "body", - | "in": "body", - | "required": true, - | "schema": { - | "$ref": "#/definitions/user" - | } - | } - | ], - | "responses": { - | "201": { - | "description": "create user successful and return created user object", - | "schema": { - | "$ref": "#/definitions/user" - | } - | }, - | "500": { - | "description": "unexpected error", - | "schema": { - | "$ref": "#/responses/unexpectedError" - | } - | } - | } - | } - | }, - | "/getById/{userId}": { - | "get": { - | "description": "get reuested user by user ID", - | "parameters": [ - | { - | "$ref": "#/parameters/userId" - | } - | ], - | "consumes": [], - | "responses": { - | "200": { - | "description": "the successful get requested user by user ID", - | "schema": { - | "$ref": "#/definitions/user" - | } - | }, - | "400": { - | "description": "bad request", - | "schema": { - | "$ref": "#/responses/invalidRequest" - | } - | }, - | "404": { - | "description": "user not found", - | "schema": { - | "$ref": "#/definitions/APIError" - | } - | }, - | "500": { - | "description": "unexpected error", - | "schema": { - | "$ref": "#/responses/unexpectedError" - | } - | } - | } - | } - | }, - | "/listUsers": { - | "get": { - | "description": "get list of users", - | "consumes": [], - | "responses": { - | "200": { - | "description": "get all users", - | "schema": { - | "$ref": "#/definitions/users" - | } - | }, - | "404": { - | "description": "user not found", - | "schema": { - | "$ref": "#/definitions/APIError" - | } - | } - | } - | } - | }, - | "/updateUser": { - | "put": { - | "parameters": [ - | { - | "name": "body", - | "in": "body", - | "required": true, - | "schema": { - | "$ref": "#/definitions/user" - | } - | } - | ], - | "responses": { - | "200": { - | "description": "create user successful and return created user object", - | "schema": { - | "$ref": "#/definitions/user" - | } - | }, - | "500": { - | "description": "unexpected error", - | "schema": { - | "$ref": "#/responses/unexpectedError" - | } - | } - | } - | } - | }, - | "/delete/{userId}": { - | "delete": { - | "description": "delete user by user ID", - | "parameters": [ - | { - | "$ref": "#/parameters/userId" - | } - | ], - | "consumes": [], - | "responses": { - | "204": { - | "description": "the successful delete user by user ID" - | }, - | "400": { - | "description": "bad request", - | "schema": { - | "$ref": "#/responses/invalidRequest" - | } - | }, - | "500": { - | "description": "unexpected error", - | "schema": { - | "$ref": "#/responses/unexpectedError" - | } - | } - | } - | } - | } - | }, - | "definitions": { - | "user": { - | "type": "object", - | "properties": { - | "id": { - | "type": "integer", - | "description": "user ID" - | }, - | "first_name": { - | "type": "string" - | }, - | "last_name": { - | "type": "string" - | }, - | "age": { - | "type": "integer" - | }, - | "career": { - | "type": "string" - | } - | }, - | "required": [ - | "first_name", - | "last_name", - | "age" - | ] - | }, - | "users": { - | "description": "array of users", - | "type": "array", - | "items": { - | "$ref": "#/definitions/user" - | } - | }, - | "APIError": { - | "description": "content any error from API", - | "type": "object", - | "properties": { - | "errorCode": { - | "description": "content error code relate to API", - | "type": "string" - | }, - | "errorMessage": { - | "description": "content user-friendly error message", - | "type": "string" - | } - | } - | } - | }, - | "responses": { - | "unexpectedError": { - | "description": "unexpected error", - | "schema": { - | "$ref": "#/definitions/APIError" - | } - | }, - | "invalidRequest": { - | "description": "invalid request", - | "schema": { - | "$ref": "#/definitions/APIError" - | } - | } - | }, - | "parameters": { - | "userId": { - | "name": "userId", - | "in": "path", - | "required": true, - | "type": "string", - | "description": "user ID" - | } - | } - |} - |""".stripMargin) - lazy val dynamicEndpointResponseBodyExample = dynamicEndpointRequestBodyExample.copy(dynamicEndpointId = Some("dynamic-endpoint-id")) + private val dynamicEndpointSwagger = + """{ + | "swagger": "2.0", + | "info": { + | "version": "0.0.1", + | "title": "Portus EVS sandbox demo API", + | "description": "Portus EVS sandbox demo API", + | "contact": { + | "name": "Digital & FinTech, Grant Thornton", + | "email": "peng.xu@ie.gt.com", + | "url": "https://www.tesobe.com/" + | } + | }, + | "host": "localhost:8080", + | "basePath": "/user", + | "schemes": [ + | "http" + | ], + | "consumes": [ + | "application/json" + | ], + | "produces": [ + | "application/json" + | ], + | "paths": { + | "/save": { + | "post": { + | "parameters": [ + | { + | "name": "body", + | "in": "body", + | "required": true, + | "schema": { + | "$ref": "#/definitions/user" + | } + | } + | ], + | "responses": { + | "201": { + | "description": "create user successful and return created user object", + | "schema": { + | "$ref": "#/definitions/user" + | } + | }, + | "500": { + | "description": "unexpected error", + | "schema": { + | "$ref": "#/responses/unexpectedError" + | } + | } + | } + | } + | }, + | "/getById/{userId}": { + | "get": { + | "description": "get reuested user by user ID", + | "parameters": [ + | { + | "$ref": "#/parameters/userId" + | } + | ], + | "consumes": [], + | "responses": { + | "200": { + | "description": "the successful get requested user by user ID", + | "schema": { + | "$ref": "#/definitions/user" + | } + | }, + | "400": { + | "description": "bad request", + | "schema": { + | "$ref": "#/responses/invalidRequest" + | } + | }, + | "404": { + | "description": "user not found", + | "schema": { + | "$ref": "#/definitions/APIError" + | } + | }, + | "500": { + | "description": "unexpected error", + | "schema": { + | "$ref": "#/responses/unexpectedError" + | } + | } + | } + | } + | }, + | "/listUsers": { + | "get": { + | "description": "get list of users", + | "consumes": [], + | "responses": { + | "200": { + | "description": "get all users", + | "schema": { + | "$ref": "#/definitions/users" + | } + | }, + | "404": { + | "description": "user not found", + | "schema": { + | "$ref": "#/definitions/APIError" + | } + | } + | } + | } + | }, + | "/updateUser": { + | "put": { + | "parameters": [ + | { + | "name": "body", + | "in": "body", + | "required": true, + | "schema": { + | "$ref": "#/definitions/user" + | } + | } + | ], + | "responses": { + | "200": { + | "description": "create user successful and return created user object", + | "schema": { + | "$ref": "#/definitions/user" + | } + | }, + | "500": { + | "description": "unexpected error", + | "schema": { + | "$ref": "#/responses/unexpectedError" + | } + | } + | } + | } + | }, + | "/delete/{userId}": { + | "delete": { + | "description": "delete user by user ID", + | "parameters": [ + | { + | "$ref": "#/parameters/userId" + | } + | ], + | "consumes": [], + | "responses": { + | "204": { + | "description": "the successful delete user by user ID" + | }, + | "400": { + | "description": "bad request", + | "schema": { + | "$ref": "#/responses/invalidRequest" + | } + | }, + | "500": { + | "description": "unexpected error", + | "schema": { + | "$ref": "#/responses/unexpectedError" + | } + | } + | } + | } + | } + | }, + | "definitions": { + | "user": { + | "type": "object", + | "properties": { + | "id": { + | "type": "integer", + | "description": "user ID" + | }, + | "first_name": { + | "type": "string" + | }, + | "last_name": { + | "type": "string" + | }, + | "age": { + | "type": "integer" + | }, + | "career": { + | "type": "string" + | } + | }, + | "required": [ + | "first_name", + | "last_name", + | "age" + | ] + | }, + | "users": { + | "description": "array of users", + | "type": "array", + | "items": { + | "$ref": "#/definitions/user" + | } + | }, + | "APIError": { + | "description": "content any error from API", + | "type": "object", + | "properties": { + | "errorCode": { + | "description": "content error code relate to API", + | "type": "string" + | }, + | "errorMessage": { + | "description": "content user-friendly error message", + | "type": "string" + | } + | } + | } + | }, + | "responses": { + | "unexpectedError": { + | "description": "unexpected error", + | "schema": { + | "$ref": "#/definitions/APIError" + | } + | }, + | "invalidRequest": { + | "description": "invalid request", + | "schema": { + | "$ref": "#/definitions/APIError" + | } + | } + | }, + | "parameters": { + | "userId": { + | "name": "userId", + | "in": "path", + | "required": true, + | "type": "string", + | "description": "user ID" + | } + | } + |} + |""".stripMargin + lazy val dynamicEndpointRequestBodyExample = json.parse(dynamicEndpointSwagger).asInstanceOf[JObject] + lazy val dynamicEndpointResponseBodyExample = ("dynamicEndpointId", "dynamic-endpoint-id") ~ ("swaggerString", dynamicEndpointRequestBodyExample) + } diff --git a/obp-api/src/main/scala/code/api/v4_0_0/APIMethods400.scala b/obp-api/src/main/scala/code/api/v4_0_0/APIMethods400.scala index 742bd2650..5133f2132 100644 --- a/obp-api/src/main/scala/code/api/v4_0_0/APIMethods400.scala +++ b/obp-api/src/main/scala/code/api/v4_0_0/APIMethods400.scala @@ -49,6 +49,7 @@ 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.json.JsonDSL._ import net.liftweb.mapper.By import net.liftweb.util.Helpers.now import net.liftweb.util.{Helpers, StringHelpers} @@ -2748,25 +2749,20 @@ trait APIMethods400 { List(apiTagDynamicEndpoint, apiTagApi, apiTagNewStyle), Some(List(canCreateDynamicEndpoint))) - private val BodyPost = new TestPost[String] with JsonTest { - def body(r: Req): Box[String] = { - val value = r.body.map(it => new String(it)) - value - } - } - lazy val createDynamicEndpoint: OBPEndpoint = { - case "management" :: "dynamic_endpoints" :: Nil BodyPost body -> req => { + case "management" :: "dynamic_endpoints" :: Nil JsonPost json -> _ => { cc => for { postedJson <- NewStyle.function.tryons(InvalidJsonFormat, 400, cc.callContext) { - DynamicEndpointHelper.parseSwaggerContent(body) - DynamicEndpointSwagger(body) + val swaggerContent = compactRender(json) + DynamicEndpointHelper.parseSwaggerContent(swaggerContent) + DynamicEndpointSwagger(swaggerContent) } (dynamicEndpoint, callContext) <- NewStyle.function.createDynamicEndpoint(postedJson.swaggerString, cc.callContext) } yield { - val commonsData: DynamicEndpointCommons = dynamicEndpoint - (commonsData, HttpCode.`201`(callContext)) + val swaggerJson = parse(dynamicEndpoint.swaggerString) + val responseJson: JObject = ("dynamicEndpointId", dynamicEndpoint.dynamicEndpointId) ~ ("swaggerString", swaggerJson) + (responseJson, HttpCode.`201`(callContext)) } } } @@ -2803,8 +2799,9 @@ trait APIMethods400 { for { (dynamicEndpoint, callContext) <- NewStyle.function.getDynamicEndpoint(dynamicEndpointId, cc.callContext) } yield { - val commonsData: DynamicEndpointCommons = dynamicEndpoint - (commonsData, HttpCode.`201`(callContext)) + val swaggerJson = parse(dynamicEndpoint.swaggerString) + val responseJson: JObject = ("dynamicEndpointId", dynamicEndpoint.dynamicEndpointId) ~ ("swaggerString", swaggerJson) + (responseJson, HttpCode.`201`(callContext)) } } } @@ -2840,10 +2837,13 @@ trait APIMethods400 { case "management" :: "dynamic_endpoints" :: Nil JsonGet req => { cc => for { - (dynamicEndpoints, callContext) <- NewStyle.function.getDynamicEndpoints(cc.callContext) + (dynamicEndpoints, _) <- NewStyle.function.getDynamicEndpoints(cc.callContext) } yield { - val listCommons: List[DynamicEndpointCommons] = dynamicEndpoints - (ListResult("dynamic_entities", listCommons), HttpCode.`200`(cc.callContext)) + val resultList = dynamicEndpoints.map[JObject, List[JObject]] { dynamicEndpoint=> + val swaggerJson = parse(dynamicEndpoint.swaggerString) + ("dynamicEndpointId", dynamicEndpoint.dynamicEndpointId) ~ ("swaggerString", swaggerJson) + } + (ListResult("dynamic_entities", resultList), HttpCode.`200`(cc.callContext)) } } } From 86988072c7cd7064734ee56726917e19daf29254 Mon Sep 17 00:00:00 2001 From: hongwei Date: Thu, 26 Mar 2020 12:06:48 +0100 Subject: [PATCH 13/35] remove the account_attributes for the create account endpoint --- .../code/api/ResourceDocs1_4_0/SwaggerDefinitionsJSON.scala | 3 +-- obp-api/src/main/scala/code/api/v3_1_0/JSONFactory3.1.0.scala | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/obp-api/src/main/scala/code/api/ResourceDocs1_4_0/SwaggerDefinitionsJSON.scala b/obp-api/src/main/scala/code/api/ResourceDocs1_4_0/SwaggerDefinitionsJSON.scala index fbfe7143f..51fb7b727 100644 --- a/obp-api/src/main/scala/code/api/ResourceDocs1_4_0/SwaggerDefinitionsJSON.scala +++ b/obp-api/src/main/scala/code/api/ResourceDocs1_4_0/SwaggerDefinitionsJSON.scala @@ -3631,8 +3631,7 @@ object SwaggerDefinitionsJSON { product_code = accountTypeExample.value, balance = amountOfMoneyJsonV121, branch_id = branchIdExample.value, - account_routing = accountRoutingJsonV121, - account_attributes= List(accountAttributeResponseJson) + account_routing = accountRoutingJsonV121 ) val postAccountAccessJsonV400 = PostAccountAccessJsonV400(userIdExample.value, PostViewJsonV400(ExampleValue.viewIdExample.value, true)) diff --git a/obp-api/src/main/scala/code/api/v3_1_0/JSONFactory3.1.0.scala b/obp-api/src/main/scala/code/api/v3_1_0/JSONFactory3.1.0.scala index 0e4bb2058..fe33545cc 100644 --- a/obp-api/src/main/scala/code/api/v3_1_0/JSONFactory3.1.0.scala +++ b/obp-api/src/main/scala/code/api/v3_1_0/JSONFactory3.1.0.scala @@ -664,8 +664,7 @@ case class CreateAccountRequestJsonV310( product_code : String, balance : AmountOfMoneyJsonV121, branch_id : String, - account_routing: AccountRoutingJsonV121, - account_attributes: List[AccountAttributeResponseJson] + account_routing: AccountRoutingJsonV121 ) case class CreateAccountResponseJsonV310( From ec84896825f384fd466a4ca7fd31b35cb48c5588 Mon Sep 17 00:00:00 2001 From: hongwei Date: Thu, 26 Mar 2020 19:17:22 +0800 Subject: [PATCH 14/35] modify dynamic_entities --> dynamic-entities and dynamic_endpoints --> dynamic-endpoints in the URL --- .../scala/code/api/util/ErrorMessages.scala | 2 +- .../scala/code/api/v4_0_0/APIMethods400.scala | 2 -- .../code/api/v4_0_0/DynamicEntityTest.scala | 28 +++++++++---------- 3 files changed, 15 insertions(+), 17 deletions(-) diff --git a/obp-api/src/main/scala/code/api/util/ErrorMessages.scala b/obp-api/src/main/scala/code/api/util/ErrorMessages.scala index f41fc883f..9bfc85698 100644 --- a/obp-api/src/main/scala/code/api/util/ErrorMessages.scala +++ b/obp-api/src/main/scala/code/api/util/ErrorMessages.scala @@ -41,7 +41,7 @@ object ErrorMessages { val WebUiPropsNotFound = "OBP-08002: WebUi props not found. Please specify a valid value for WEB_UI_PROPS_ID." // DynamicEntity Exceptions (OBP-09XXX) - val DynamicEntityNotFoundByDynamicEntityId = "OBP-09001: DynamicEntity not found. Please specify a valid value for dynamic_entity_id." + val DynamicEntityNotFoundByDynamicEntityId = "OBP-09001: DynamicEntity not found. Please specify a valid value for DYNAMIC_ENTITY_ID." 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." diff --git a/obp-api/src/main/scala/code/api/v4_0_0/APIMethods400.scala b/obp-api/src/main/scala/code/api/v4_0_0/APIMethods400.scala index 5133f2132..84a3de7a8 100644 --- a/obp-api/src/main/scala/code/api/v4_0_0/APIMethods400.scala +++ b/obp-api/src/main/scala/code/api/v4_0_0/APIMethods400.scala @@ -49,7 +49,6 @@ 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.json.JsonDSL._ import net.liftweb.mapper.By import net.liftweb.util.Helpers.now import net.liftweb.util.{Helpers, StringHelpers} @@ -60,7 +59,6 @@ import scala.collection.mutable.ArrayBuffer import scala.concurrent.Future import code.model._ import org.apache.commons.lang3.StringUtils -import net.liftweb.json.compactRender trait APIMethods400 { self: RestHelper => diff --git a/obp-api/src/test/scala/code/api/v4_0_0/DynamicEntityTest.scala b/obp-api/src/test/scala/code/api/v4_0_0/DynamicEntityTest.scala index 6cebabc96..a0c13de9f 100644 --- a/obp-api/src/test/scala/code/api/v4_0_0/DynamicEntityTest.scala +++ b/obp-api/src/test/scala/code/api/v4_0_0/DynamicEntityTest.scala @@ -152,7 +152,7 @@ class DynamicEntityTest extends V400ServerSetup { 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 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) @@ -163,7 +163,7 @@ class DynamicEntityTest extends V400ServerSetup { 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 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) @@ -174,7 +174,7 @@ class DynamicEntityTest extends V400ServerSetup { 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 request400 = (v4_0_0_Request / "management" / "dynamic-entities").GET val response400 = makeGetRequest(request400) Then("We should get a 400") response400.code should equal(400) @@ -185,7 +185,7 @@ class DynamicEntityTest extends V400ServerSetup { 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 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) @@ -198,7 +198,7 @@ class DynamicEntityTest extends V400ServerSetup { feature("Add a DynamicEntity v4.0.4- Unauthorized access - Authorized access") { scenario("We will call the endpoint without the proper Role " + canCreateDynamicEntity, ApiEndpoint1, VersionOfApi) { When("We make a request v4.0.0 without a Role " + canCreateDynamicEntity) - val request400 = (v4_0_0_Request / "management" / "dynamic_entities").POST <@(user1) + 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) @@ -209,7 +209,7 @@ class DynamicEntityTest extends V400ServerSetup { 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 request = (v4_0_0_Request / "management" / "dynamic_entities").POST <@(user1) + val request = (v4_0_0_Request / "management" / "dynamic-entities").POST <@(user1) val response = makePostRequest(request, write(rightEntity)) Then("We should get a 201") response.code should equal(201) @@ -244,7 +244,7 @@ class DynamicEntityTest extends V400ServerSetup { { // update success - val request400 = (v4_0_0_Request / "management" / "dynamic_entities" / dynamicEntityId ).PUT <@(user1) + val request400 = (v4_0_0_Request / "management" / "dynamic-entities" / dynamicEntityId ).PUT <@(user1) val response400 = makePutRequest(request400, compactRender(updateRequest)) Then("We should get a 200") response400.code should equal(200) @@ -254,7 +254,7 @@ class DynamicEntityTest extends V400ServerSetup { { // update a not exists DynamicEntity - val request404 = (v4_0_0_Request / "management" / "dynamic_entities" / "not-exists-id" ).PUT <@(user1) + 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 404") response404.code should equal(404) @@ -263,7 +263,7 @@ class DynamicEntityTest extends V400ServerSetup { { // update a DynamicEntity with wrong required field name - val request400 = (v4_0_0_Request / "management" / "dynamic_entities" / dynamicEntityId ).PUT <@(user1) + val request400 = (v4_0_0_Request / "management" / "dynamic-entities" / dynamicEntityId ).PUT <@(user1) val response400 = makePutRequest(request400, compactRender(wrongRequiredEntity)) Then("We should get a 400") @@ -273,7 +273,7 @@ class DynamicEntityTest extends V400ServerSetup { { // update a DynamicEntity with wrong type of description - val request400 = (v4_0_0_Request / "management" / "dynamic_entities" / dynamicEntityId ).PUT <@(user1) + val request400 = (v4_0_0_Request / "management" / "dynamic-entities" / dynamicEntityId ).PUT <@(user1) val response400 = makePutRequest(request400, compactRender(wrongDescriptionEntity)) Then("We should get a 400") @@ -283,7 +283,7 @@ class DynamicEntityTest extends V400ServerSetup { { // update a DynamicEntity with wrong type of property description - val request400 = (v4_0_0_Request / "management" / "dynamic_entities" / dynamicEntityId ).PUT <@(user1) + val request400 = (v4_0_0_Request / "management" / "dynamic-entities" / dynamicEntityId ).PUT <@(user1) val response400 = makePutRequest(request400, compactRender(wrongPropertyDescriptionEntity)) Then("We should get a 400") @@ -293,11 +293,11 @@ class DynamicEntityTest extends V400ServerSetup { Entitlement.entitlement.vend.addEntitlement("", resourceUser1.userId, CanGetDynamicEntities.toString) When("We make a request v4.0.0 with the Role " + canGetDynamicEntities) - val requestGet = (v4_0_0_Request / "management" / "dynamic_entities").GET <@(user1) + val requestGet = (v4_0_0_Request / "management" / "dynamic-entities").GET <@(user1) val responseGet = makeGetRequest(requestGet) Then("We should get a 200") responseGet.code should equal(200) - val json = responseGet.body \ "dynamic_entities" + val json = responseGet.body \ "dynamic-entities" val dynamicEntitiesGetJson = json.asInstanceOf[JArray] dynamicEntitiesGetJson.values should have size 1 @@ -308,7 +308,7 @@ class DynamicEntityTest extends V400ServerSetup { Entitlement.entitlement.vend.addEntitlement("", resourceUser1.userId, CanDeleteDynamicEntity.toString) When("We make a request v4.0.0 with the Role " + canDeleteDynamicEntity) - val requestDelete400 = (v4_0_0_Request / "management" / "dynamic_entities" / dynamicEntityId).DELETE <@(user1) + val requestDelete400 = (v4_0_0_Request / "management" / "dynamic-entities" / dynamicEntityId).DELETE <@(user1) val responseDelete400 = makeDeleteRequest(requestDelete400) Then("We should get a 200") responseDelete400.code should equal(200) From f0dc28c841b63c72ac2609aafa135242328e2580 Mon Sep 17 00:00:00 2001 From: hongwei Date: Thu, 26 Mar 2020 13:31:56 +0100 Subject: [PATCH 15/35] fixed the failed test for Dynamic Entity --- obp-api/src/test/scala/code/api/v4_0_0/DynamicEntityTest.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/obp-api/src/test/scala/code/api/v4_0_0/DynamicEntityTest.scala b/obp-api/src/test/scala/code/api/v4_0_0/DynamicEntityTest.scala index a0c13de9f..4f4527e87 100644 --- a/obp-api/src/test/scala/code/api/v4_0_0/DynamicEntityTest.scala +++ b/obp-api/src/test/scala/code/api/v4_0_0/DynamicEntityTest.scala @@ -297,7 +297,7 @@ class DynamicEntityTest extends V400ServerSetup { val responseGet = makeGetRequest(requestGet) Then("We should get a 200") responseGet.code should equal(200) - val json = responseGet.body \ "dynamic-entities" + val json = responseGet.body \ "dynamic_entities" val dynamicEntitiesGetJson = json.asInstanceOf[JArray] dynamicEntitiesGetJson.values should have size 1 From d840769e3efa73a24f854d343b0c29beb071cd34 Mon Sep 17 00:00:00 2001 From: hongwei Date: Thu, 26 Mar 2020 20:36:30 +0800 Subject: [PATCH 16/35] DynamicEntities -> Dynamic Entities DynamicEndpoints -> Dynamic Endpoints --- obp-api/src/main/scala/code/api/v4_0_0/APIMethods400.scala | 1 + 1 file changed, 1 insertion(+) diff --git a/obp-api/src/main/scala/code/api/v4_0_0/APIMethods400.scala b/obp-api/src/main/scala/code/api/v4_0_0/APIMethods400.scala index 84a3de7a8..eb256ff82 100644 --- a/obp-api/src/main/scala/code/api/v4_0_0/APIMethods400.scala +++ b/obp-api/src/main/scala/code/api/v4_0_0/APIMethods400.scala @@ -59,6 +59,7 @@ import scala.collection.mutable.ArrayBuffer import scala.concurrent.Future import code.model._ import org.apache.commons.lang3.StringUtils +import net.liftweb.json.compactRender trait APIMethods400 { self: RestHelper => From 961c43f3cfa7b97d985f77f10fb147da90226abb Mon Sep 17 00:00:00 2001 From: shuang Date: Fri, 27 Mar 2020 17:11:10 +0800 Subject: [PATCH 17/35] feature/add_dynamic_endpoints_with_swagger: prepare for url, role validation. --- .../scala/code/api/v4_0_0/APIMethods400.scala | 9 +++++---- .../api/v4_0_0/DynamicEndpointHelper.scala | 18 ++++++++++++------ 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/obp-api/src/main/scala/code/api/v4_0_0/APIMethods400.scala b/obp-api/src/main/scala/code/api/v4_0_0/APIMethods400.scala index eb256ff82..85b9810ff 100644 --- a/obp-api/src/main/scala/code/api/v4_0_0/APIMethods400.scala +++ b/obp-api/src/main/scala/code/api/v4_0_0/APIMethods400.scala @@ -3,7 +3,7 @@ package code.api.v4_0_0 import java.util.Date import code.DynamicData.DynamicData -import code.DynamicEndpoint.{DynamicEndpointCommons, DynamicEndpointSwagger} +import code.DynamicEndpoint.DynamicEndpointSwagger import code.accountattribute.AccountAttributeX import code.api.ChargePolicy import code.api.ResourceDocs1_4_0.SwaggerDefinitionsJSON._ @@ -49,6 +49,7 @@ 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.json.JsonDSL._ import net.liftweb.mapper.By import net.liftweb.util.Helpers.now import net.liftweb.util.{Helpers, StringHelpers} @@ -2819,7 +2820,7 @@ trait APIMethods400 { |""", emptyObjectJson, ListResult( - "dynamic_entities", + "dynamic_endpoints", List(dynamicEndpointResponseBodyExample) ), List( @@ -2833,7 +2834,7 @@ trait APIMethods400 { Some(List(canGetDynamicEndpoints))) lazy val getDynamicEndpoints: OBPEndpoint = { - case "management" :: "dynamic_endpoints" :: Nil JsonGet req => { + case "management" :: "dynamic_endpoints" :: Nil JsonGet _ => { cc => for { (dynamicEndpoints, _) <- NewStyle.function.getDynamicEndpoints(cc.callContext) @@ -2842,7 +2843,7 @@ trait APIMethods400 { val swaggerJson = parse(dynamicEndpoint.swaggerString) ("dynamicEndpointId", dynamicEndpoint.dynamicEndpointId) ~ ("swaggerString", swaggerJson) } - (ListResult("dynamic_entities", resultList), HttpCode.`200`(cc.callContext)) + (ListResult("dynamic_endpoints", resultList), HttpCode.`200`(cc.callContext)) } } } diff --git a/obp-api/src/main/scala/code/api/v4_0_0/DynamicEndpointHelper.scala b/obp-api/src/main/scala/code/api/v4_0_0/DynamicEndpointHelper.scala index 7d16328e0..5ccf36288 100644 --- a/obp-api/src/main/scala/code/api/v4_0_0/DynamicEndpointHelper.scala +++ b/obp-api/src/main/scala/code/api/v4_0_0/DynamicEndpointHelper.scala @@ -77,12 +77,14 @@ object DynamicEndpointHelper { ) val catalogs: Catalogs = Catalogs(notCore, notPSD2, notOBWG) - val roleName = s"Can$summary${entitlementSuffix(path)}" - .replaceFirst("Can(Create|Update|Get|Delete)", "Can$1Dynamic") - .replace(" ", "") - val roles: Option[List[ApiRole]] = Some(List( - ApiRole.getOrCreateDynamicApiRole(roleName) - )) + val roles: Option[List[ApiRole]] = { + val roleName = s"Can$summary${entitlementSuffix(path)}" + .replaceFirst("Can(Create|Update|Get|Delete)", "Can$1Dynamic") + .replace(" ", "") + Some(List( + ApiRole.getOrCreateDynamicApiRole(roleName) + )) + } val connectorMethods = Some(List(s"""dynamicEntityProcess: parameters contains {"key": "entityName", "value": "$summary"}""")) //TODO temp ResourceDoc( partialFunction, @@ -242,4 +244,8 @@ object DynamicEndpointHelper { case s: StringSchema => Option(s.getExample).getOrElse("string") case _ => throw new RuntimeException(s"Not support type $schema, please support it if necessary.") } +} + +case class DynamicEndpointInfo(id: String, url: String, method: HttpMethod, apiRole: ApiRole, serverUrl: Option[String]) { + } \ No newline at end of file From 3c8713c4525e1a2f318a7bd808aeac2faac50f01 Mon Sep 17 00:00:00 2001 From: shuang Date: Fri, 27 Mar 2020 19:07:29 +0800 Subject: [PATCH 18/35] feature/add_dynamic_endpoints_with_swagger: change some - to _ --- .../scala/code/api/v4_0_0/APIMethods400.scala | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/obp-api/src/main/scala/code/api/v4_0_0/APIMethods400.scala b/obp-api/src/main/scala/code/api/v4_0_0/APIMethods400.scala index 85b9810ff..239aee16f 100644 --- a/obp-api/src/main/scala/code/api/v4_0_0/APIMethods400.scala +++ b/obp-api/src/main/scala/code/api/v4_0_0/APIMethods400.scala @@ -814,12 +814,12 @@ trait APIMethods400 { implementedInApiVersion, nameOf(getDynamicEntities), "GET", - "/management/dynamic_entities", + "/management/dynamic-entities", "Get DynamicEntities", s"""Get the all DynamicEntities.""", emptyObjectJson, ListResult( - "dynamic_entities", + "dynamic-entities", List(dynamicEntityResponseBodyExample) ), List( @@ -834,7 +834,7 @@ trait APIMethods400 { lazy val getDynamicEntities: OBPEndpoint = { - case "management" :: "dynamic_entities" :: Nil JsonGet req => { + case "management" :: "dynamic-entities" :: Nil JsonGet req => { cc => for { dynamicEntities <- Future(NewStyle.function.getDynamicEntities()) @@ -851,7 +851,7 @@ trait APIMethods400 { implementedInApiVersion, nameOf(createDynamicEntity), "POST", - "/management/dynamic_entities", + "/management/dynamic-entities", "Create DynamicEntity", s"""Create a DynamicEntity. | @@ -884,7 +884,7 @@ trait APIMethods400 { Some(List(canCreateDynamicEntity))) lazy val createDynamicEntity: OBPEndpoint = { - case "management" :: "dynamic_entities" :: Nil JsonPost json -> _ => { + case "management" :: "dynamic-entities" :: Nil JsonPost json -> _ => { cc => val dynamicEntity = DynamicEntityCommons(json.asInstanceOf[JObject], None) for { @@ -902,7 +902,7 @@ trait APIMethods400 { implementedInApiVersion, nameOf(updateDynamicEntity), "PUT", - "/management/dynamic_entities/DYNAMIC_ENTITY_ID", + "/management/dynamic-entities/DYNAMIC_ENTITY_ID", "Update DynamicEntity", s"""Update a DynamicEntity. | @@ -935,7 +935,7 @@ trait APIMethods400 { Some(List(canUpdateDynamicEntity))) lazy val updateDynamicEntity: OBPEndpoint = { - case "management" :: "dynamic_entities" :: dynamicEntityId :: Nil JsonPut json -> _ => { + case "management" :: "dynamic-entities" :: dynamicEntityId :: Nil JsonPut json -> _ => { cc => for { // Check whether there are uploaded data, only if no uploaded data allow to update DynamicEntity. @@ -960,7 +960,7 @@ trait APIMethods400 { implementedInApiVersion, nameOf(deleteDynamicEntity), "DELETE", - "/management/dynamic_entities/DYNAMIC_ENTITY_ID", + "/management/dynamic-entities/DYNAMIC_ENTITY_ID", "Delete DynamicEntity", s"""Delete a DynamicEntity specified by DYNAMIC_ENTITY_ID. | @@ -977,7 +977,7 @@ trait APIMethods400 { Some(List(canDeleteDynamicEntity))) lazy val deleteDynamicEntity: OBPEndpoint = { - case "management" :: "dynamic_entities" :: dynamicEntityId :: Nil JsonDelete _ => { + case "management" :: "dynamic-entities" :: dynamicEntityId :: Nil JsonDelete _ => { cc => for { // Check whether there are uploaded data, only if no uploaded data allow to delete DynamicEntity. @@ -2730,7 +2730,7 @@ trait APIMethods400 { implementedInApiVersion, nameOf(createDynamicEndpoint), "POST", - "/management/dynamic_endpoints", + "/management/dynamic-endpoints", " Create DynamicEndpoint", s"""Create a DynamicEndpoint. | @@ -2750,7 +2750,7 @@ trait APIMethods400 { Some(List(canCreateDynamicEndpoint))) lazy val createDynamicEndpoint: OBPEndpoint = { - case "management" :: "dynamic_endpoints" :: Nil JsonPost json -> _ => { + case "management" :: "dynamic-endpoints" :: Nil JsonPost json -> _ => { cc => for { postedJson <- NewStyle.function.tryons(InvalidJsonFormat, 400, cc.callContext) { @@ -2773,7 +2773,7 @@ trait APIMethods400 { implementedInApiVersion, nameOf(getDynamicEndpoint), "GET", - "/management/dynamic_endpoints/DYNAMIC_ENDPOINT_ID", + "/management/dynamic-endpoints/DYNAMIC_ENDPOINT_ID", " Get DynamicEndpoint", s"""Get a DynamicEndpoint. | @@ -2794,7 +2794,7 @@ trait APIMethods400 { Some(List(canGetDynamicEndpoint))) lazy val getDynamicEndpoint: OBPEndpoint = { - case "management" :: "dynamic_endpoints" :: dynamicEndpointId :: Nil JsonGet req => { + case "management" :: "dynamic-endpoints" :: dynamicEndpointId :: Nil JsonGet req => { cc => for { (dynamicEndpoint, callContext) <- NewStyle.function.getDynamicEndpoint(dynamicEndpointId, cc.callContext) @@ -2811,7 +2811,7 @@ trait APIMethods400 { implementedInApiVersion, nameOf(getDynamicEndpoints), "GET", - "/management/dynamic_endpoints", + "/management/dynamic-endpoints", " Get DynamicEndpoints", s"""Get DynamicEndpoints. | @@ -2834,7 +2834,7 @@ trait APIMethods400 { Some(List(canGetDynamicEndpoints))) lazy val getDynamicEndpoints: OBPEndpoint = { - case "management" :: "dynamic_endpoints" :: Nil JsonGet _ => { + case "management" :: "dynamic-endpoints" :: Nil JsonGet _ => { cc => for { (dynamicEndpoints, _) <- NewStyle.function.getDynamicEndpoints(cc.callContext) From c2c2f25872ee8252ba82c38c3ce2a554a09ce33e Mon Sep 17 00:00:00 2001 From: shuang Date: Fri, 27 Mar 2020 19:50:57 +0800 Subject: [PATCH 19/35] feature/add_dynamic_endpoints_with_swagger: fix problem caused by merge with origin develop --- .../SwaggerDefinitionsJSON.scala | 54 +++++++++---------- .../scala/code/api/v4_0_0/APIMethods400.scala | 45 ++++++++++++++++ .../code/api/v4_0_0/JSONFactory4.0.0.scala | 4 +- .../bankconnectors/LocalMappedConnector.scala | 1 - .../scala/code/bankconnectors/package.scala | 2 +- .../src/main/scala/code/util/JsonUtils.scala | 1 + 6 files changed, 75 insertions(+), 32 deletions(-) diff --git a/obp-api/src/main/scala/code/api/ResourceDocs1_4_0/SwaggerDefinitionsJSON.scala b/obp-api/src/main/scala/code/api/ResourceDocs1_4_0/SwaggerDefinitionsJSON.scala index 51fb7b727..fe4205b7e 100644 --- a/obp-api/src/main/scala/code/api/ResourceDocs1_4_0/SwaggerDefinitionsJSON.scala +++ b/obp-api/src/main/scala/code/api/ResourceDocs1_4_0/SwaggerDefinitionsJSON.scala @@ -2019,34 +2019,8 @@ object SwaggerDefinitionsJSON { branch_id = ExampleValue.branchIdExample.value, name_suffix = ExampleValue.nameSuffixExample.value ) - - val customersJsonV300 = code.api.v3_0_0.CustomerJSONs(List(customerJsonV300)) - - val customerWithAttributesJsonV300 = CustomerWithAttributesJsonV300( - bank_id = bankIdExample.value, - customer_id = ExampleValue.customerIdExample.value, - customer_number = ExampleValue.customerNumberExample.value, - legal_name = ExampleValue.legalNameExample.value, - mobile_phone_number = ExampleValue.mobileNumberExample.value, - email = ExampleValue.emailExample.value, - face_image = customerFaceImageJson, - date_of_birth = "19900101", - relationship_status = ExampleValue.relationshipStatusExample.value, - dependants = ExampleValue.dependentsExample.value.toInt, - dob_of_dependants = List("19900101"), - credit_rating = Option(customerCreditRatingJSON), - credit_limit = Option(amountOfMoneyJsonV121), - highest_education_attained = ExampleValue.highestEducationAttainedExample.value, - employment_status = ExampleValue.employmentStatusExample.value, - kyc_status = ExampleValue.kycStatusExample.value.toBoolean, - last_ok_date = DateWithDayExampleObject, - title = ExampleValue.titleExample.value, - branch_id = ExampleValue.branchIdExample.value, - name_suffix = ExampleValue.nameSuffixExample.value, - customer_attributes = List(customerAttributeResponseJson) - ) - val customersWithAttributesJsonV300 = CustomersWithAttributesJsonV300(List(customerWithAttributesJsonV300)) + val customersJsonV300 = code.api.v3_0_0.CustomerJSONs(List(customerJsonV300)) val postCustomerJsonV310 = PostCustomerJsonV310( @@ -2123,6 +2097,32 @@ object SwaggerDefinitionsJSON { customer_attributes = List(customerAttributeResponseJson) ) + val customerWithAttributesJsonV300 = CustomerWithAttributesJsonV300( + bank_id = bankIdExample.value, + customer_id = ExampleValue.customerIdExample.value, + customer_number = ExampleValue.customerNumberExample.value, + legal_name = ExampleValue.legalNameExample.value, + mobile_phone_number = ExampleValue.mobileNumberExample.value, + email = ExampleValue.emailExample.value, + face_image = customerFaceImageJson, + date_of_birth = "19900101", + relationship_status = ExampleValue.relationshipStatusExample.value, + dependants = ExampleValue.dependentsExample.value.toInt, + dob_of_dependants = List("19900101"), + credit_rating = Option(customerCreditRatingJSON), + credit_limit = Option(amountOfMoneyJsonV121), + highest_education_attained = ExampleValue.highestEducationAttainedExample.value, + employment_status = ExampleValue.employmentStatusExample.value, + kyc_status = ExampleValue.kycStatusExample.value.toBoolean, + last_ok_date = DateWithDayExampleObject, + title = ExampleValue.titleExample.value, + branch_id = ExampleValue.branchIdExample.value, + name_suffix = ExampleValue.nameSuffixExample.value, + customer_attributes = List(customerAttributeResponseJson) + ) + + val customersWithAttributesJsonV300 = CustomersWithAttributesJsonV300(List(customerWithAttributesJsonV300)) + val putUpdateCustomerDataJsonV310 = PutUpdateCustomerDataJsonV310( face_image = customerFaceImageJson, relationship_status = ExampleValue.relationshipStatusExample.value, diff --git a/obp-api/src/main/scala/code/api/v4_0_0/APIMethods400.scala b/obp-api/src/main/scala/code/api/v4_0_0/APIMethods400.scala index 239aee16f..931ae56fc 100644 --- a/obp-api/src/main/scala/code/api/v4_0_0/APIMethods400.scala +++ b/obp-api/src/main/scala/code/api/v4_0_0/APIMethods400.scala @@ -2725,6 +2725,51 @@ trait APIMethods400 { } } + val customerAttributeGeneralInfo = + s""" + |CustomerAttributes are used to enhance the OBP Customer object with Bank specific entities. + | + """.stripMargin + + resourceDocs += ResourceDoc( + deleteCustomerAttribute, + implementedInApiVersion, + nameOf(deleteCustomerAttribute), + "DELETE", + "/banks/BANK_ID/CUSTOMER_ID/attributes/CUSTOMER_ATTRIBUTE_ID", + "Delete Customer Attribute", + s""" Delete Customer Attribute + | + |$customerAttributeGeneralInfo + | + |Delete a Customer Attribute by its id. + | + |${authenticationRequiredMessage(true)} + | + |""", + emptyObjectJson, + emptyObjectJson, + List( + UserHasMissingRoles, + UnknownError + ), + Catalogs(notCore, notPSD2, notOBWG), + List(apiTagCustomer, apiTagNewStyle), + Some(List(canDeleteCustomerAttributeAtOneBank))) + + lazy val deleteCustomerAttribute : OBPEndpoint = { + case "banks" :: bankId :: "customers" :: "attributes" :: customerAttributeId :: Nil JsonDelete _=> { + cc => + for { + (Full(u), callContext) <- authenticatedAccess(cc) + (_, callContext) <- NewStyle.function.getBank(BankId(bankId), callContext) + (customerAttribute, callContext) <- NewStyle.function.deleteCustomerAttribute(customerAttributeId, callContext) + } yield { + (Full(customerAttribute), HttpCode.`204`(callContext)) + } + } + } + resourceDocs += ResourceDoc( createDynamicEndpoint, implementedInApiVersion, diff --git a/obp-api/src/main/scala/code/api/v4_0_0/JSONFactory4.0.0.scala b/obp-api/src/main/scala/code/api/v4_0_0/JSONFactory4.0.0.scala index e27e48592..57897d916 100644 --- a/obp-api/src/main/scala/code/api/v4_0_0/JSONFactory4.0.0.scala +++ b/obp-api/src/main/scala/code/api/v4_0_0/JSONFactory4.0.0.scala @@ -35,18 +35,16 @@ import code.api.v1_2_1.{BankRoutingJsonV121, JSONFactory, UserJSONV121, ViewJSON import code.api.v1_4_0.JSONFactory1_4_0.TransactionRequestAccountJsonV140 import code.api.v2_0_0.TransactionRequestChargeJsonV200 import code.api.v2_1_0.ResourceUserJSON -import code.api.v2_2_0.ConsumerJson import code.api.v3_0_0.JSONFactory300.createAccountRoutingsJSON import code.api.v3_0_0.{CustomerAttributeResponseJsonV300, ViewBasicV300} import code.api.v3_1_0.AccountAttributeResponseJson import code.api.v3_1_0.JSONFactory310.createAccountAttributeJson import code.directdebit.DirectDebitTrait import code.entitlement.Entitlement -import code.model.{Consumer, ModeratedBankAccount, ModeratedBankAccountCore} +import code.model.{Consumer, ModeratedBankAccountCore} import code.standingorders.StandingOrderTrait import code.transactionChallenge.MappedExpectedChallengeAnswer import code.transactionrequests.TransactionRequests.TransactionChallengeTypes -import code.users.Users import com.openbankproject.commons.model._ import net.liftweb.common.{Box, Full} diff --git a/obp-api/src/main/scala/code/bankconnectors/LocalMappedConnector.scala b/obp-api/src/main/scala/code/bankconnectors/LocalMappedConnector.scala index b409d39fa..ab01ff068 100644 --- a/obp-api/src/main/scala/code/bankconnectors/LocalMappedConnector.scala +++ b/obp-api/src/main/scala/code/bankconnectors/LocalMappedConnector.scala @@ -77,7 +77,6 @@ import net.liftweb.mapper.{By, _} import net.liftweb.util.Helpers.{hours, now, time, tryo} import net.liftweb.util.Mailer import net.liftweb.util.Mailer.{From, PlainMailBodyType, Subject, To} -import org.apache.commons.lang3.StringUtils import org.mindrot.jbcrypt.BCrypt import scalacache.ScalaCache import scalacache.guava.GuavaCache diff --git a/obp-api/src/main/scala/code/bankconnectors/package.scala b/obp-api/src/main/scala/code/bankconnectors/package.scala index 2e6ef4e22..ead2c35b8 100644 --- a/obp-api/src/main/scala/code/bankconnectors/package.scala +++ b/obp-api/src/main/scala/code/bankconnectors/package.scala @@ -123,7 +123,7 @@ package object bankconnectors extends MdcLoggable { case name => Connector.getConnectorInstance(name) } val methodSymbol = connector.implementedMethods(methodName).alternatives match { - case m::Nil if m.isInstanceOf[MethodSymbol] => m.asMethod + case m::Nil if m.isMethod => m.asMethod case _ => findMethodByArgs(connector, methodName, args:_*) .getOrElse(sys.error(s"not found matched method, method name: ${methodName}, params: ${args.mkString(",")}")) diff --git a/obp-api/src/main/scala/code/util/JsonUtils.scala b/obp-api/src/main/scala/code/util/JsonUtils.scala index 8a73c93ad..a632ba58c 100644 --- a/obp-api/src/main/scala/code/util/JsonUtils.scala +++ b/obp-api/src/main/scala/code/util/JsonUtils.scala @@ -163,6 +163,7 @@ object JsonUtils { } else { JField(newName, jObj) } + case _ => throw new RuntimeException(s"Not support json value type, value is: $jValue") } } From a7884b43ce9131e92959dcc0a2734bbd320fcac5 Mon Sep 17 00:00:00 2001 From: hongwei Date: Fri, 27 Mar 2020 13:35:12 +0100 Subject: [PATCH 20/35] tweaked the camel case to snake case --- obp-api/src/main/scala/code/api/util/ExampleValue.scala | 2 +- obp-api/src/main/scala/code/api/v4_0_0/APIMethods400.scala | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/obp-api/src/main/scala/code/api/util/ExampleValue.scala b/obp-api/src/main/scala/code/api/util/ExampleValue.scala index b95acd9df..1698abecb 100644 --- a/obp-api/src/main/scala/code/api/util/ExampleValue.scala +++ b/obp-api/src/main/scala/code/api/util/ExampleValue.scala @@ -604,7 +604,7 @@ object ExampleValue { |} |""".stripMargin lazy val dynamicEndpointRequestBodyExample = json.parse(dynamicEndpointSwagger).asInstanceOf[JObject] - lazy val dynamicEndpointResponseBodyExample = ("dynamicEndpointId", "dynamic-endpoint-id") ~ ("swaggerString", dynamicEndpointRequestBodyExample) + lazy val dynamicEndpointResponseBodyExample = ("dynamic_endpoint_id", "dynamic-endpoint-id") ~ ("swagger_string", dynamicEndpointRequestBodyExample) } diff --git a/obp-api/src/main/scala/code/api/v4_0_0/APIMethods400.scala b/obp-api/src/main/scala/code/api/v4_0_0/APIMethods400.scala index 34055fa24..ce4feff2a 100644 --- a/obp-api/src/main/scala/code/api/v4_0_0/APIMethods400.scala +++ b/obp-api/src/main/scala/code/api/v4_0_0/APIMethods400.scala @@ -2761,7 +2761,7 @@ trait APIMethods400 { (dynamicEndpoint, callContext) <- NewStyle.function.createDynamicEndpoint(postedJson.swaggerString, cc.callContext) } yield { val swaggerJson = parse(dynamicEndpoint.swaggerString) - val responseJson: JObject = ("dynamicEndpointId", dynamicEndpoint.dynamicEndpointId) ~ ("swaggerString", swaggerJson) + val responseJson: JObject = ("dynamic_endpoint_id", dynamicEndpoint.dynamicEndpointId) ~ ("swagger_string", swaggerJson) (responseJson, HttpCode.`201`(callContext)) } } @@ -2843,7 +2843,7 @@ trait APIMethods400 { val swaggerJson = parse(dynamicEndpoint.swaggerString) ("dynamic_endpoint_id", dynamicEndpoint.dynamicEndpointId) ~ ("swagger_string", swaggerJson) } - (ListResult("dynamic_entities", resultList), HttpCode.`200`(cc.callContext)) + (ListResult("dynamic_endpoints", resultList), HttpCode.`200`(cc.callContext)) } } } From 0686517bd4a2809232a40ff06cf37390fb76f1af Mon Sep 17 00:00:00 2001 From: hongwei Date: Fri, 27 Mar 2020 13:41:28 +0100 Subject: [PATCH 21/35] added the tests for DynamicEndpoints --- .../api/v4_0_0/DynamicendPointsTest.scala | 185 ++++++++++++++++++ 1 file changed, 185 insertions(+) create mode 100644 obp-api/src/test/scala/code/api/v4_0_0/DynamicendPointsTest.scala diff --git a/obp-api/src/test/scala/code/api/v4_0_0/DynamicendPointsTest.scala b/obp-api/src/test/scala/code/api/v4_0_0/DynamicendPointsTest.scala new file mode 100644 index 000000000..f6ae29046 --- /dev/null +++ b/obp-api/src/test/scala/code/api/v4_0_0/DynamicendPointsTest.scala @@ -0,0 +1,185 @@ +package code.api.v4_0_0 + +import code.api.util.APIUtil.OAuth._ +import code.api.util.ApiRole._ +import code.api.util.ErrorMessages.{UserHasMissingRoles, UserNotLoggedIn} +import code.api.util.ExampleValue +import code.api.v4_0_0.OBPAPI4_0_0.Implementations4_0_0 +import code.entitlement.Entitlement +import com.github.dwickern.macros.NameOf.nameOf +import com.openbankproject.commons.model.ErrorMessage +import com.openbankproject.commons.util.ApiVersion +import net.liftweb.json.Serialization.write +import org.scalatest.Tag + +class DynamicEndpointsTest 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.createDynamicEndpoint)) + object ApiEndpoint2 extends Tag(nameOf(Implementations4_0_0.getDynamicEndpoints)) + object ApiEndpoint3 extends Tag(nameOf(Implementations4_0_0.getDynamicEndpoint)) + + + feature(s"test $ApiEndpoint1 version $VersionOfApi - Unauthorized access") { + scenario("We will call the endpoint without user credentials", ApiEndpoint1, VersionOfApi) { + val postDynamicEndpointRequestBodyExample = ExampleValue.dynamicEndpointRequestBodyExample + + When("We make a request v4.0.0") + val request400 = (v4_0_0_Request / "management" / "dynamic-endpoints").POST + val response400 = makePostRequest(request400, write(postDynamicEndpointRequestBodyExample)) + Then("We should get a 400") + response400.code should equal(400) + response400.body.extract[ErrorMessage].message should equal(UserNotLoggedIn) + } + } + + feature(s"test $ApiEndpoint1 version $VersionOfApi - authorized access- missing role") { + scenario("We will call the endpoint with user credentials", ApiEndpoint1, VersionOfApi) { + val postDynamicEndpointRequestBodyExample = ExampleValue.dynamicEndpointRequestBodyExample + + When("We make a request v4.0.0") + val request = (v4_0_0_Request / "management" / "dynamic-endpoints").POST<@ (user1) + val response = makePostRequest(request, write(postDynamicEndpointRequestBodyExample)) + Then("We should get a 403") + response.code should equal(403) + response.body.extract[ErrorMessage].message.toString contains (UserHasMissingRoles) should be (true) + } + } + + feature(s"test $ApiEndpoint1 version $VersionOfApi - authorized access - with role - should be success!") { + scenario("We will call the endpoint with user credentials", ApiEndpoint1, VersionOfApi) { + When("We make a request v4.0.0") + val postDynamicEndpointRequestBodyExample = ExampleValue.dynamicEndpointRequestBodyExample + + When("We make a request v4.0.0") + val request = (v4_0_0_Request / "management" / "dynamic-endpoints").POST<@ (user1) + val response = makePostRequest(request, write(postDynamicEndpointRequestBodyExample)) + Then("We should get a 403") + response.code should equal(403) + response.body.extract[ErrorMessage].message.toString contains (UserHasMissingRoles) should be (true) + + Then("We grant the role to the user1") + Entitlement.entitlement.vend.addEntitlement("", resourceUser1.userId, canCreateDynamicEndpoint.toString) + + val responseWithRole = makePostRequest(request, write(postDynamicEndpointRequestBodyExample)) + Then("We should get a 201") + responseWithRole.code should equal(201) + responseWithRole.body.toString contains("dynamic_endpoint_id") should be (true) + responseWithRole.body.toString contains("Portus EVS sandbox demo API") should be (true) + responseWithRole.body.toString contains("content user-friendly error message") should be (true) + responseWithRole.body.toString contains("create user successful and return created user object") should be (true) + } + } + + feature(s"test $ApiEndpoint2 version $VersionOfApi - 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-endpoints").GET + val response400 = makeGetRequest(request400) + Then("We should get a 400") + response400.code should equal(400) + response400.body.extract[ErrorMessage].message should equal(UserNotLoggedIn) + } + } + + feature(s"test $ApiEndpoint2 version $VersionOfApi - authorized access- missing role") { + scenario("We will call the endpoint with user credentials", ApiEndpoint1, VersionOfApi) { + When("We make a request v4.0.0") + val request = (v4_0_0_Request / "management" / "dynamic-endpoints").GET<@ (user1) + val response = makeGetRequest(request) + Then("We should get a 400") + response.code should equal(403) + response.body.extract[ErrorMessage].message.toString contains (UserHasMissingRoles) should be (true) + } + } + + feature(s"test $ApiEndpoint2 version $VersionOfApi - authorized access - with role - should be success!") { + scenario("We will call the endpoint with user credentials", ApiEndpoint1, VersionOfApi) { + When("We make a request v4.0.0") + val postDynamicEndpointRequestBodyExample = ExampleValue.dynamicEndpointRequestBodyExample + + When("We make a request v4.0.0") + val request = (v4_0_0_Request / "management" / "dynamic-endpoints").POST<@ (user1) + val response = makePostRequest(request, write(postDynamicEndpointRequestBodyExample)) + Then("We should get a 403") + response.code should equal(403) + response.body.extract[ErrorMessage].message.toString contains (UserHasMissingRoles) should be (true) + + Then("We grant the role to the user1") + Entitlement.entitlement.vend.addEntitlement("", resourceUser1.userId, CanGetDynamicEndpoints.toString) + Entitlement.entitlement.vend.addEntitlement("", resourceUser1.userId, CanCreateDynamicEndpoint.toString) + + val responseWithRole = makePostRequest(request, write(postDynamicEndpointRequestBodyExample)) + Then("We should get a 201") + responseWithRole.code should equal(201) + + val request400 = (v4_0_0_Request / "management" / "dynamic-endpoints").GET<@ (user1) + val response400 = makeGetRequest(request400) + response400.code should be (200) + response400.body.toString contains("Portus EVS sandbox demo API") should be (true) + response400.body.toString contains("content user-friendly error message") should be (true) + response400.body.toString contains("create user successful and return created user object") should be (true) + + } + } + + feature(s"test $ApiEndpoint3 version $VersionOfApi - 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-endpoints"/ "some-id").GET + val response400 = makeGetRequest(request400) + Then("We should get a 400") + response400.code should equal(400) + response400.body.extract[ErrorMessage].message should equal(UserNotLoggedIn) + } + } + + feature(s"test $ApiEndpoint3 version $VersionOfApi - authorized access- missing role") { + scenario("We will call the endpoint with user credentials", ApiEndpoint1, VersionOfApi) { + When("We make a request v4.0.0") + val request = (v4_0_0_Request / "management" / "dynamic-endpoints" /"some-id").GET<@ (user1) + val response = makeGetRequest(request) + Then("We should get a 400") + response.code should equal(403) + response.body.extract[ErrorMessage].message.toString contains (UserHasMissingRoles) should be (true) + } + } + + feature(s"test $ApiEndpoint3 version $VersionOfApi - authorized access - with role - should be success!") { + scenario("We will call the endpoint with user credentials", ApiEndpoint1, VersionOfApi) { + When("We make a request v4.0.0") + val postDynamicEndpointRequestBodyExample = ExampleValue.dynamicEndpointRequestBodyExample + + When("We make a request v4.0.0") + val request = (v4_0_0_Request / "management" / "dynamic-endpoints").POST<@ (user1) + val response = makePostRequest(request, write(postDynamicEndpointRequestBodyExample)) + Then("We should get a 403") + response.code should equal(403) + response.body.extract[ErrorMessage].message.toString contains (UserHasMissingRoles) should be (true) + + Then("We grant the role to the user1") + Entitlement.entitlement.vend.addEntitlement("", resourceUser1.userId, CanGetDynamicEndpoint.toString) + Entitlement.entitlement.vend.addEntitlement("", resourceUser1.userId, CanCreateDynamicEndpoint.toString) + + val responseWithRole = makePostRequest(request, write(postDynamicEndpointRequestBodyExample)) + Then("We should get a 201") + responseWithRole.code should equal(201) + + val id = responseWithRole.body.\\("dynamic_endpoint_id").values.get("dynamic_endpoint_id").head.toString + + val request400 = (v4_0_0_Request / "management" / "dynamic-endpoints" /id).GET<@ (user1) + val response400 = makeGetRequest(request400) + response400.code should be (200) + response400.body.toString contains("Portus EVS sandbox demo API") should be (true) + response400.body.toString contains("content user-friendly error message") should be (true) + response400.body.toString contains("create user successful and return created user object") should be (true) + + } + } +} From 01045f17eeb31a8db1182f4750e03804501adcfe Mon Sep 17 00:00:00 2001 From: hongwei Date: Fri, 27 Mar 2020 13:53:40 +0100 Subject: [PATCH 22/35] fixed the merge issue --- .../main/scala/code/api/util/NewStyle.scala | 24 ------------------- .../scala/code/bankconnectors/Connector.scala | 12 ---------- 2 files changed, 36 deletions(-) diff --git a/obp-api/src/main/scala/code/api/util/NewStyle.scala b/obp-api/src/main/scala/code/api/util/NewStyle.scala index 3267320b1..63d5aaa88 100644 --- a/obp-api/src/main/scala/code/api/util/NewStyle.scala +++ b/obp-api/src/main/scala/code/api/util/NewStyle.scala @@ -2027,29 +2027,5 @@ object NewStyle { } } - def createDynamicEndpoint(swaggerString: String, callContext: Option[CallContext]): OBPReturnType[DynamicEndpointT] = { - Connector.connector.vend.createDynamicEndpoint( - swaggerString, - callContext - ) map { - i => (connectorEmptyResponse(i._1, callContext), i._2) - } - } - - def getDynamicEndpoint(dynamicEndpointId: String, callContext: Option[CallContext]): OBPReturnType[DynamicEndpointT] = { - Connector.connector.vend.getDynamicEndpoint( - dynamicEndpointId, - callContext - ) map { - i => (connectorEmptyResponse(i._1, callContext), i._2) - } - } - - def getDynamicEndpoints(callContext: Option[CallContext]): OBPReturnType[List[DynamicEndpointT]] = { - Connector.connector.vend.getDynamicEndpoints( - callContext - ) - } - } } diff --git a/obp-api/src/main/scala/code/bankconnectors/Connector.scala b/obp-api/src/main/scala/code/bankconnectors/Connector.scala index 924f19be1..4b5ae7051 100644 --- a/obp-api/src/main/scala/code/bankconnectors/Connector.scala +++ b/obp-api/src/main/scala/code/bankconnectors/Connector.scala @@ -2179,18 +2179,6 @@ trait Connector extends MdcLoggable { (Failure(setUnimplementedError), callContext) } - def createDynamicEndpoint(swaggerString: String, callContext: Option[CallContext]): OBPReturnType[Box[DynamicEndpointT]] = Future { - (Failure(setUnimplementedError), callContext) - } - - def getDynamicEndpoint(dynamicEndpointId: String, callContext: Option[CallContext]): OBPReturnType[Box[DynamicEndpointT]] = Future { - (Failure(setUnimplementedError), callContext) - } - - def getDynamicEndpoints(callContext: Option[CallContext]): OBPReturnType[List[DynamicEndpointT]] = Future { - (List.empty[DynamicEndpointT], callContext) - } - def deleteCustomerAttribute(customerAttributeId: String, callContext: Option[CallContext]): OBPReturnType[Box[Boolean]] = Future{(Failure(setUnimplementedError), callContext)} From b805e47c9e3a9d230afb87bed1021308a7ad78f4 Mon Sep 17 00:00:00 2001 From: hongwei Date: Fri, 27 Mar 2020 14:00:26 +0100 Subject: [PATCH 23/35] tweaked the dynamicEndpointId -> dynamic_endpoint_id tweaked the swaggerString -> swagger_string --- obp-api/src/main/scala/code/api/v4_0_0/APIMethods400.scala | 2 +- .../src/test/scala/code/api/v4_0_0/DynamicendPointsTest.scala | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/obp-api/src/main/scala/code/api/v4_0_0/APIMethods400.scala b/obp-api/src/main/scala/code/api/v4_0_0/APIMethods400.scala index 58c63267c..0935a968e 100644 --- a/obp-api/src/main/scala/code/api/v4_0_0/APIMethods400.scala +++ b/obp-api/src/main/scala/code/api/v4_0_0/APIMethods400.scala @@ -2886,7 +2886,7 @@ trait APIMethods400 { } yield { val resultList = dynamicEndpoints.map[JObject, List[JObject]] { dynamicEndpoint=> val swaggerJson = parse(dynamicEndpoint.swaggerString) - ("dynamicEndpointId", dynamicEndpoint.dynamicEndpointId) ~ ("swaggerString", swaggerJson) + ("dynamic_endpoint_id", dynamicEndpoint.dynamicEndpointId) ~ ("swagger_string", swaggerJson) } (ListResult("dynamic_endpoints", resultList), HttpCode.`200`(cc.callContext)) } diff --git a/obp-api/src/test/scala/code/api/v4_0_0/DynamicendPointsTest.scala b/obp-api/src/test/scala/code/api/v4_0_0/DynamicendPointsTest.scala index f6ae29046..b9fca8dd1 100644 --- a/obp-api/src/test/scala/code/api/v4_0_0/DynamicendPointsTest.scala +++ b/obp-api/src/test/scala/code/api/v4_0_0/DynamicendPointsTest.scala @@ -71,6 +71,7 @@ class DynamicEndpointsTest extends V400ServerSetup { Then("We should get a 201") responseWithRole.code should equal(201) responseWithRole.body.toString contains("dynamic_endpoint_id") should be (true) + responseWithRole.body.toString contains("swagger_string") should be (true) responseWithRole.body.toString contains("Portus EVS sandbox demo API") should be (true) responseWithRole.body.toString contains("content user-friendly error message") should be (true) responseWithRole.body.toString contains("create user successful and return created user object") should be (true) @@ -176,6 +177,8 @@ class DynamicEndpointsTest extends V400ServerSetup { val request400 = (v4_0_0_Request / "management" / "dynamic-endpoints" /id).GET<@ (user1) val response400 = makeGetRequest(request400) response400.code should be (200) + response400.body.toString contains("dynamic_endpoint_id") should be (true) + response400.body.toString contains("swagger_string") should be (true) response400.body.toString contains("Portus EVS sandbox demo API") should be (true) response400.body.toString contains("content user-friendly error message") should be (true) response400.body.toString contains("create user successful and return created user object") should be (true) From 2b6c1a886d4b0608f37fcf002a53a01bb30d92ac Mon Sep 17 00:00:00 2001 From: shuang Date: Fri, 27 Mar 2020 21:03:31 +0800 Subject: [PATCH 24/35] feature/add_dynamic_endpoints_with_swagger: fix "dynamic-entities" to "dynamic_entities" in ListResult --- obp-api/src/main/scala/code/api/v4_0_0/APIMethods400.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/obp-api/src/main/scala/code/api/v4_0_0/APIMethods400.scala b/obp-api/src/main/scala/code/api/v4_0_0/APIMethods400.scala index 931ae56fc..4fa6ca960 100644 --- a/obp-api/src/main/scala/code/api/v4_0_0/APIMethods400.scala +++ b/obp-api/src/main/scala/code/api/v4_0_0/APIMethods400.scala @@ -819,7 +819,7 @@ trait APIMethods400 { s"""Get the all DynamicEntities.""", emptyObjectJson, ListResult( - "dynamic-entities", + "dynamic_entities", List(dynamicEntityResponseBodyExample) ), List( From 26b68d62027495747d83e2db8f28b96c2a3951fe Mon Sep 17 00:00:00 2001 From: shuang Date: Fri, 27 Mar 2020 21:03:31 +0800 Subject: [PATCH 25/35] feature/add_dynamic_endpoints_with_swagger: fix "dynamic-entities" to "dynamic_entities" in ListResult --- obp-api/src/main/scala/code/api/v4_0_0/APIMethods400.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/obp-api/src/main/scala/code/api/v4_0_0/APIMethods400.scala b/obp-api/src/main/scala/code/api/v4_0_0/APIMethods400.scala index 0935a968e..d57c3289d 100644 --- a/obp-api/src/main/scala/code/api/v4_0_0/APIMethods400.scala +++ b/obp-api/src/main/scala/code/api/v4_0_0/APIMethods400.scala @@ -819,7 +819,7 @@ trait APIMethods400 { s"""Get the all Dynamic Entities.""", emptyObjectJson, ListResult( - "dynamic-entities", + "dynamic_entities", List(dynamicEntityResponseBodyExample) ), List( From 4dbae0e1954d1088948472e075a5a4be781f4965 Mon Sep 17 00:00:00 2001 From: shuang Date: Fri, 27 Mar 2020 23:12:40 +0800 Subject: [PATCH 26/35] feature/add_dynamic_endpoints_with_swagger: do validation of path and method exists when upload swagger. --- .../scala/code/api/util/ErrorMessages.scala | 2 + .../scala/code/api/v4_0_0/APIMethods400.scala | 10 ++- .../api/v4_0_0/DynamicEndpointHelper.scala | 81 ++++++++++++++++--- 3 files changed, 81 insertions(+), 12 deletions(-) diff --git a/obp-api/src/main/scala/code/api/util/ErrorMessages.scala b/obp-api/src/main/scala/code/api/util/ErrorMessages.scala index 9bfc85698..0f3ddc696 100644 --- a/obp-api/src/main/scala/code/api/util/ErrorMessages.scala +++ b/obp-api/src/main/scala/code/api/util/ErrorMessages.scala @@ -49,6 +49,8 @@ object ErrorMessages { val DynamicEntityOperationNotAllowed = "OBP-09006: Operation is not allowed, because Current DynamicEntity have upload data, must to delete all the data before this operation." val DynamicEntityInstanceValidateFail = "OBP-09007: DynamicEntity data validation failure." + val DynamicEndpointExists = "OBP-09008: DynamicEndpoint already exists." + // General messages (OBP-10XXX) val InvalidJsonFormat = "OBP-10001: Incorrect json format." diff --git a/obp-api/src/main/scala/code/api/v4_0_0/APIMethods400.scala b/obp-api/src/main/scala/code/api/v4_0_0/APIMethods400.scala index d57c3289d..8084e9cd8 100644 --- a/obp-api/src/main/scala/code/api/v4_0_0/APIMethods400.scala +++ b/obp-api/src/main/scala/code/api/v4_0_0/APIMethods400.scala @@ -2798,10 +2798,14 @@ trait APIMethods400 { case "management" :: "dynamic-endpoints" :: Nil JsonPost json -> _ => { cc => for { - postedJson <- NewStyle.function.tryons(InvalidJsonFormat, 400, cc.callContext) { + (postedJson, openAPI) <- NewStyle.function.tryons(InvalidJsonFormat, 400, cc.callContext) { val swaggerContent = compactRender(json) - DynamicEndpointHelper.parseSwaggerContent(swaggerContent) - DynamicEndpointSwagger(swaggerContent) + + (DynamicEndpointSwagger(swaggerContent), DynamicEndpointHelper.parseSwaggerContent(swaggerContent)) + } + duplicatedUrl = DynamicEndpointHelper.findExistsEndpoints(openAPI).map(kv => s"${kv._2}:${kv._1}") + _ <- Helper.booleanToFuture(s"""$DynamicEndpointExists Current input is: ${duplicatedUrl.mkString("; ")}""") { + duplicatedUrl.isEmpty } (dynamicEndpoint, callContext) <- NewStyle.function.createDynamicEndpoint(postedJson.swaggerString, cc.callContext) } yield { diff --git a/obp-api/src/main/scala/code/api/v4_0_0/DynamicEndpointHelper.scala b/obp-api/src/main/scala/code/api/v4_0_0/DynamicEndpointHelper.scala index 5ccf36288..6a2456808 100644 --- a/obp-api/src/main/scala/code/api/v4_0_0/DynamicEndpointHelper.scala +++ b/obp-api/src/main/scala/code/api/v4_0_0/DynamicEndpointHelper.scala @@ -3,6 +3,7 @@ package code.api.v4_0_0 import java.io.File import java.nio.charset.Charset import java.util +import java.util.concurrent.CopyOnWriteArrayList import java.util.{Date, Objects} import code.DynamicEndpoint.{DynamicEndpointProvider, DynamicEndpointT} @@ -41,9 +42,56 @@ object DynamicEndpointHelper { */ val urlPrefix = APIUtil.getPropsValue("dynamic_endpoints_url_prefix", "dynamic") - val dynamicEndpointsUrl: Set[(String, HttpMethod)] = Set() + private lazy val dynamicEndpointInfos: CopyOnWriteArrayList[DynamicEndpointInfo] = { + val dynamicEndpoints: List[DynamicEndpointT] = DynamicEndpointProvider.connectorMethodProvider.vend.getAll() + val infos = dynamicEndpoints.map(it => swaggerToResourceDocs(it.swaggerString, it.dynamicEndpointId.get)) + new CopyOnWriteArrayList(infos.asJava) + } - def swaggerToResourceDocs(content: String): mutable.Iterable[ResourceDoc] = { + def findExistsEndpoints(openAPI: OpenAPI): List[(String, HttpMethod)] = { + val existsUrlToMethod: mutable.Buffer[(String, HttpMethod)] = dynamicEndpointInfos.asScala.flatMap(_.resourceDocs) + .map(doc => doc.requestUrl.replace(urlPrefix, "") -> HttpMethod.valueOf(doc.requestVerb)) + + def isExists(newUrl: String, newMethod: HttpMethod) = existsUrlToMethod.exists(kv => { + val (url, method) = kv + isSameUrl(newUrl, url) && newMethod == method + }) + + for { + (path, pathItem) <- openAPI.getPaths.asScala.toList + (method: HttpMethod, _) <- pathItem.readOperationsMap.asScala + if isExists(path, method) + } yield (path, method) + + } + + /** + * check whether two url is the same: + * isSameUrl("/abc/efg", "/abc/efg") == true + * isSameUrl("/abc/efg", "/abc/{id}") == true + * isSameUrl("/abc/{userId}", "/abc/{id}") == true + * isSameUrl("/abc/{userId}/", "/abc/{id}") == true + * isSameUrl("/def/abc/", "/abc/{id}") == false + * @param path1 + * @param path2 + * @return + */ + private def isSameUrl(path1: String, path2: String) = { + val path1Parts = StringUtils.split(path1, '/') + val path2Parts = StringUtils.split(path2, '/') + if(path1Parts.size != path2Parts.size) { + false + } else { + path1Parts.zip(path2Parts).forall {kv => + val (part1, part2) = kv + part1 == part2 || + (part1.startsWith("{") && part1.endsWith("}")) || + (part2.startsWith("{") && part2.endsWith("}")) + } + } + } + + def swaggerToResourceDocs(content: String, id: String): DynamicEndpointInfo = { val openAPI: OpenAPI = parseSwaggerContent(content) val tags: List[ResourceDocTag] = List(ApiTag.apiTagDynamicEndpoint, apiTagApi, apiTagNewStyle) @@ -58,7 +106,7 @@ object DynamicEndpointHelper { val partialFunction: OBPEndpoint = APIMethods400.Implementations4_0_0.genericEndpoint // TODO create real endpoint val partialFunctionName: String = s"$method-$path".replace('/', '_') val requestVerb: String = method.name() - val requestUrl: String = s"/$urlPrefix/$path".replace("//", "/") + val requestUrl: String = buildRequestUrl(path) val summary: String = Option(pathItem.getSummary) .filter(StringUtils.isNotBlank) .getOrElse(buildSummary(method, op, path)) @@ -103,9 +151,21 @@ object DynamicEndpointHelper { connectorMethods = connectorMethods ) } - docs + + val serverUrl = { + val servers = openAPI.getServers + if(servers.isEmpty) { + None + } else { + Some(servers.get(0).getUrl) + } + } + DynamicEndpointInfo(null, docs, serverUrl) } + private def buildRequestUrl(path: String): String = + s"/$urlPrefix/$path".replace("//", "/") + def parseSwaggerContent(content: String): OpenAPI = { val tempSwaggerFile = File.createTempFile("temp", ".swagger") FileUtils.write(tempSwaggerFile, content, Charset.forName("utf-8")) @@ -118,10 +178,13 @@ object DynamicEndpointHelper { } def doc: ArrayBuffer[ResourceDoc] = { - val dynamicEndpoints: List[DynamicEndpointT] = DynamicEndpointProvider.connectorMethodProvider.vend.getAll() - - val docs = dynamicEndpoints.flatMap(it => swaggerToResourceDocs(it.swaggerString)) - ArrayBuffer[ResourceDoc](docs:_*) + val docs = ArrayBuffer[ResourceDoc]() + dynamicEndpointInfos.forEach { info => + info.resourceDocs.foreach { doc => + docs += doc + } + } + docs } private def buildSummary(method: HttpMethod, op: Operation, path: String): String = method match { @@ -246,6 +309,6 @@ object DynamicEndpointHelper { } } -case class DynamicEndpointInfo(id: String, url: String, method: HttpMethod, apiRole: ApiRole, serverUrl: Option[String]) { +case class DynamicEndpointInfo(id: String, resourceDocs: mutable.Iterable[ResourceDoc], serverUrl: Option[String]) { } \ No newline at end of file From ec51a1205351670e205a0f3eb9fc5b1e4f7aae2d Mon Sep 17 00:00:00 2001 From: shuang Date: Sun, 29 Mar 2020 22:17:30 +0800 Subject: [PATCH 27/35] feature/add_dynamic_endpoints_with_swagger: dynamicEndpoint url match finish. --- .../scala/code/api/v4_0_0/APIMethods400.scala | 21 +- .../api/v4_0_0/DynamicEndpointHelper.scala | 240 ++++++++++++------ .../scala/code/api/v4_0_0/OBPAPI4_0_0.scala | 4 +- 3 files changed, 174 insertions(+), 91 deletions(-) diff --git a/obp-api/src/main/scala/code/api/v4_0_0/APIMethods400.scala b/obp-api/src/main/scala/code/api/v4_0_0/APIMethods400.scala index 8084e9cd8..4cda0317c 100644 --- a/obp-api/src/main/scala/code/api/v4_0_0/APIMethods400.scala +++ b/obp-api/src/main/scala/code/api/v4_0_0/APIMethods400.scala @@ -22,6 +22,7 @@ import code.api.v2_1_0._ import code.api.v2_2_0.{BankJSONV220, JSONFactory220} import code.api.v3_0_0.JSONFactory300 import code.api.v3_1_0.{CreateAccountRequestJsonV310, CustomerWithAttributesJsonV310, JSONFactory310, ListResult} +import code.api.v4_0_0.DynamicEndpointHelper.DynamicReq import code.api.v4_0_0.JSONFactory400.{createBankAccountJSON, createNewCoreBankAccountJson} import code.bankconnectors.Connector import code.dynamicEntity.{DynamicEntityCommons, ReferenceType} @@ -2803,11 +2804,13 @@ trait APIMethods400 { (DynamicEndpointSwagger(swaggerContent), DynamicEndpointHelper.parseSwaggerContent(swaggerContent)) } - duplicatedUrl = DynamicEndpointHelper.findExistsEndpoints(openAPI).map(kv => s"${kv._2}:${kv._1}") - _ <- Helper.booleanToFuture(s"""$DynamicEndpointExists Current input is: ${duplicatedUrl.mkString("; ")}""") { + duplicatedUrl = DynamicEndpointHelper.findExistsEndpoints(openAPI).map(kv => s"${kv._1}:${kv._2}") + errorMsg = s"""$DynamicEndpointExists Duplicated ${if(duplicatedUrl.size > 1) "endpoints" else "endpoint"}: ${duplicatedUrl.mkString("; ")}""" + _ <- Helper.booleanToFuture(errorMsg) { duplicatedUrl.isEmpty } (dynamicEndpoint, callContext) <- NewStyle.function.createDynamicEndpoint(postedJson.swaggerString, cc.callContext) + _ = DynamicEndpointHelper.addEndpoint(openAPI, dynamicEndpoint.dynamicEndpointId.get) } yield { val swaggerJson = parse(dynamicEndpoint.swaggerString) val responseJson: JObject = ("dynamicEndpointId", dynamicEndpoint.dynamicEndpointId) ~ ("swaggerString", swaggerJson) @@ -2897,18 +2900,16 @@ trait APIMethods400 { } } + lazy val dynamicEndpoint: OBPEndpoint = { - case EntityName(entityName) :: Nil JsonGet req => { cc => - val listName = StringHelpers.snakify(English.plural(entityName)) + case DynamicReq(url, json, method, params, role) => { cc => for { (Full(u), callContext) <- authenticatedAccess(cc) - _ <- NewStyle.function.hasEntitlement("", u.userId, DynamicEntityInfo.canGetRole(entityName), callContext) - (box, _) <- NewStyle.function.invokeDynamicConnector(GET_ALL, entityName, None, None, Some(cc)) - resultList: JArray = unboxResult(box.asInstanceOf[Box[JArray]], entityName) - } yield { - import net.liftweb.json.JsonDSL._ + _ <- NewStyle.function.hasEntitlement("", u.userId, role, callContext) + + jValue = JObject(JField("name", "hello")) + } yield { - val jValue: JObject = listName -> filterDynamicObjects(resultList, req) (jValue, HttpCode.`200`(Some(cc))) } } diff --git a/obp-api/src/main/scala/code/api/v4_0_0/DynamicEndpointHelper.scala b/obp-api/src/main/scala/code/api/v4_0_0/DynamicEndpointHelper.scala index 6a2456808..e1e17ec97 100644 --- a/obp-api/src/main/scala/code/api/v4_0_0/DynamicEndpointHelper.scala +++ b/obp-api/src/main/scala/code/api/v4_0_0/DynamicEndpointHelper.scala @@ -4,7 +4,8 @@ import java.io.File import java.nio.charset.Charset import java.util import java.util.concurrent.CopyOnWriteArrayList -import java.util.{Date, Objects} +import java.util.regex.Pattern +import java.util.{Date, Objects, Optional, stream} import code.DynamicEndpoint.{DynamicEndpointProvider, DynamicEndpointT} import code.api.util.APIUtil.{Catalogs, OBPEndpoint, ResourceDoc, authenticationRequiredMessage, emptyObjectJson, generateUUID, notCore, notOBWG, notPSD2} @@ -20,23 +21,25 @@ import io.swagger.v3.oas.models.media.{ArraySchema, BooleanSchema, Content, Date import io.swagger.v3.oas.models.parameters.RequestBody import io.swagger.v3.oas.models.responses.ApiResponses import io.swagger.v3.parser.OpenAPIV3Parser -import net.liftweb.json.JsonAST.{JArray, JField, JObject} +import net.liftweb.http.Req +import net.liftweb.http.rest.RestHelper +import net.liftweb.json.JsonAST.{JArray, JField, JNothing, JObject} import net.liftweb.json.JsonDSL._ import net.liftweb.json import net.liftweb.json.JValue import net.liftweb.util.StringHelpers import org.apache.commons.io.FileUtils import org.apache.commons.lang3.StringUtils -import org.atteo.evo.inflector.English import scala.collection.immutable.{List, Nil} import scala.collection.mutable import scala.collection.mutable.ArrayBuffer import scala.collection.JavaConverters._ +import scala.compat.java8.OptionConverters._ -object DynamicEndpointHelper { - private implicit val formats = CustomJsonFormats.formats +object DynamicEndpointHelper extends RestHelper { + /** * dynamic endpoints url prefix */ @@ -47,64 +50,80 @@ object DynamicEndpointHelper { val infos = dynamicEndpoints.map(it => swaggerToResourceDocs(it.swaggerString, it.dynamicEndpointId.get)) new CopyOnWriteArrayList(infos.asJava) } - - def findExistsEndpoints(openAPI: OpenAPI): List[(String, HttpMethod)] = { - val existsUrlToMethod: mutable.Buffer[(String, HttpMethod)] = dynamicEndpointInfos.asScala.flatMap(_.resourceDocs) - .map(doc => doc.requestUrl.replace(urlPrefix, "") -> HttpMethod.valueOf(doc.requestVerb)) - - def isExists(newUrl: String, newMethod: HttpMethod) = existsUrlToMethod.exists(kv => { - val (url, method) = kv - isSameUrl(newUrl, url) && newMethod == method - }) - - for { - (path, pathItem) <- openAPI.getPaths.asScala.toList - (method: HttpMethod, _) <- pathItem.readOperationsMap.asScala - if isExists(path, method) - } yield (path, method) - - } - /** - * check whether two url is the same: - * isSameUrl("/abc/efg", "/abc/efg") == true - * isSameUrl("/abc/efg", "/abc/{id}") == true - * isSameUrl("/abc/{userId}", "/abc/{id}") == true - * isSameUrl("/abc/{userId}/", "/abc/{id}") == true - * isSameUrl("/def/abc/", "/abc/{id}") == false - * @param path1 - * @param path2 - * @return + * extract request body, no matter GET, POST, PUT or DELETE method */ - private def isSameUrl(path1: String, path2: String) = { - val path1Parts = StringUtils.split(path1, '/') - val path2Parts = StringUtils.split(path2, '/') - if(path1Parts.size != path2Parts.size) { - false - } else { - path1Parts.zip(path2Parts).forall {kv => - val (part1, part2) = kv - part1 == part2 || - (part1.startsWith("{") && part1.endsWith("}")) || - (part2.startsWith("{") && part2.endsWith("}")) + object DynamicReq extends JsonTest with JsonBody { + /** + * unapply Request to (swagger url, json, http method, request parameters, role) + * @param r HttpRequest + * @return + */ + def unapply(r: Req): Option[(String, JValue, HttpMethod, Map[String, List[String]], ApiRole)] = { + val partPath = r.path.partPath + if (!testResponse_?(r) || partPath.headOption != Option(urlPrefix)) + None + else { + val method = HttpMethod.valueOf(r.requestType.method) + // url that match original swagger endpoint. + val url = partPath.tail.mkString("/", "/", "") + val foundDynamicEndpoint: Optional[(DynamicEndpointInfo, ResourceDoc)] = dynamicEndpointInfos.stream() + .map[Option[(DynamicEndpointInfo, ResourceDoc)]](_.findDynamicEndpoint(method, url)) + .filter(_.isDefined) + .findFirst() + .map(_.get) + + foundDynamicEndpoint.asScala + .flatMap[(String, JValue, HttpMethod, Map[String, List[String]], ApiRole)] { it => + val (dynamicEndpointInfo, doc) = it + val Some(role::_) = doc.roles + body(r).toOption + .orElse(Some(JNothing)) + .map(t => (dynamicEndpointInfo.targetUrl(url), t, method, r.params, role)) + } + } } } - def swaggerToResourceDocs(content: String, id: String): DynamicEndpointInfo = { - val openAPI: OpenAPI = parseSwaggerContent(content) + def addEndpoint(openAPI: OpenAPI, id: String): Boolean = { + val endpointInfo = swaggerToResourceDocs(openAPI, id) + dynamicEndpointInfos.add(endpointInfo) + } + def removeEndpoint(id: String): Boolean = { + dynamicEndpointInfos.asScala.find(_.id == id) match { + case Some(v) => dynamicEndpointInfos.remove(v) + case _ => false + } + } + + def findExistsEndpoints(openAPI: OpenAPI): List[(HttpMethod, String)] = { + for { + (path, pathItem) <- openAPI.getPaths.asScala.toList + (method: HttpMethod, _) <- pathItem.readOperationsMap.asScala + if dynamicEndpointInfos.stream().anyMatch(_.existsEndpoint(method, path)) + } yield (method, path) + + } + + private def swaggerToResourceDocs(content: String, id: String): DynamicEndpointInfo = { + val openAPI: OpenAPI = parseSwaggerContent(content) + swaggerToResourceDocs(openAPI, id) + } + + private def swaggerToResourceDocs(openAPI: OpenAPI, id: String): DynamicEndpointInfo = { val tags: List[ResourceDocTag] = List(ApiTag.apiTagDynamicEndpoint, apiTagApi, apiTagNewStyle) val paths: mutable.Map[String, PathItem] = openAPI.getPaths.asScala def entitlementSuffix(path: String) = Math.abs(path.hashCode).toString.substring(0, 3) // to avoid different swagger have same entitlement - val docs: mutable.Iterable[ResourceDoc] = for { + val docs: mutable.Iterable[(ResourceDoc, String)] = for { (path, pathItem) <- paths (method: HttpMethod, op: Operation) <- pathItem.readOperationsMap.asScala } yield { val implementedInApiVersion = ApiVersion.v4_0_0 - val partialFunction: OBPEndpoint = APIMethods400.Implementations4_0_0.genericEndpoint // TODO create real endpoint - val partialFunctionName: String = s"$method-$path".replace('/', '_') + val partialFunction: OBPEndpoint = APIMethods400.Implementations4_0_0.genericEndpoint // this function is just placeholder, not need a real value. + val partialFunctionName: String = s"$method-$path".replaceAll("\\W", "_") val requestVerb: String = method.name() val requestUrl: String = buildRequestUrl(path) val summary: String = Option(pathItem.getSummary) @@ -134,7 +153,7 @@ object DynamicEndpointHelper { )) } val connectorMethods = Some(List(s"""dynamicEntityProcess: parameters contains {"key": "entityName", "value": "$summary"}""")) //TODO temp - ResourceDoc( + val doc = ResourceDoc( partialFunction, implementedInApiVersion, partialFunctionName, @@ -150,6 +169,7 @@ object DynamicEndpointHelper { roles, connectorMethods = connectorMethods ) + (doc, path) } val serverUrl = { @@ -160,11 +180,19 @@ object DynamicEndpointHelper { Some(servers.get(0).getUrl) } } - DynamicEndpointInfo(null, docs, serverUrl) + DynamicEndpointInfo(id, docs, serverUrl) } - private def buildRequestUrl(path: String): String = - s"/$urlPrefix/$path".replace("//", "/") + private val PathParamRegx = """\{(.+)\}""".r + private val WordBoundPattern = Pattern.compile("(?<=[a-z])(?=[A-Z])|-") + + private def buildRequestUrl(path: String): String = { + val url = StringUtils.split(s"$urlPrefix/$path", "/") + url.map { + case PathParamRegx(param) => WordBoundPattern.matcher(param).replaceAll("_").toUpperCase() + case v => v + }.mkString("/", "/", "") + } def parseSwaggerContent(content: String): OpenAPI = { val tempSwaggerFile = File.createTempFile("temp", ".swagger") @@ -221,7 +249,7 @@ object DynamicEndpointHelper { private def getRequestExample(openAPI: OpenAPI, body: RequestBody): Product = { if(body == null || body.getContent == null) { - "" + JObject() } else { getExample(openAPI, getRef(body.getContent, body.get$ref()).orNull) } @@ -261,37 +289,40 @@ object DynamicEndpointHelper { private val RegexDefinitions = """(?:#/components/schemas(?:/#definitions)?/)(.+)""".r private val RegexResponse = """#/responses/(.+)""".r - private def getExample(openAPI: OpenAPI, ref: String): Product = ref match { - case null => JObject() + private def getExample(openAPI: OpenAPI, ref: String): Product = { + implicit val formats = CustomJsonFormats.formats + ref match { + case null => JObject() - case RegexResponse(refName) => - val response = openAPI.getComponents.getResponses.get(refName) - val ref = getRef(response.getContent, response.get$ref()) - getExample(openAPI, ref.get) + case RegexResponse(refName) => + val response = openAPI.getComponents.getResponses.get(refName) + val ref = getRef(response.getContent, response.get$ref()) + getExample(openAPI, ref.get) - case RegexDefinitions(refName) => - openAPI.getComponents.getSchemas.get(refName) match { - case o: ObjectSchema => - val properties: util.Map[String, Schema[_]] = o.getProperties + case RegexDefinitions(refName) => + openAPI.getComponents.getSchemas.get(refName) match { + case o: ObjectSchema => + val properties: util.Map[String, Schema[_]] = o.getProperties - val jFields: mutable.Iterable[JField] = properties.asScala.map { kv => - val (name, value) = kv - val valueExample = if(value.getClass == classOf[Schema[_]]) getExample(openAPI, value.get$ref()) else getPropertyExample(value) - JField(name, json.Extraction.decompose(valueExample)) - } - JObject(jFields.toList) - - case a: ArraySchema => - Option(a.getExample) - .map(json.Extraction.decompose(_).asInstanceOf[JObject]) - .getOrElse { - val schema: Schema[_] = a.getItems - val singleItem: Any = if(schema.getClass == classOf[Schema[_]]) getExample(openAPI, schema.get$ref()) else getPropertyExample(schema) - val jItem = json.Extraction.decompose(singleItem) - jItem :: Nil + val jFields: mutable.Iterable[JField] = properties.asScala.map { kv => + val (name, value) = kv + val valueExample = if(value.getClass == classOf[Schema[_]]) getExample(openAPI, value.get$ref()) else getPropertyExample(value) + JField(name, json.Extraction.decompose(valueExample)) } - } + JObject(jFields.toList) + case a: ArraySchema => + Option(a.getExample) + .map(json.Extraction.decompose(_).asInstanceOf[JObject]) + .getOrElse { + val schema: Schema[_] = a.getItems + val singleItem: Any = if(schema.getClass == classOf[Schema[_]]) getExample(openAPI, schema.get$ref()) else getPropertyExample(schema) + val jItem = json.Extraction.decompose(singleItem) + jItem :: Nil + } + } + + } } private def getPropertyExample(schema: Schema[_]) = schema match { @@ -309,6 +340,55 @@ object DynamicEndpointHelper { } } -case class DynamicEndpointInfo(id: String, resourceDocs: mutable.Iterable[ResourceDoc], serverUrl: Option[String]) { +/** + * + * @param id DynamicEntity id value + * @param docsToUrl ResourceDoc to url that defined in swagger content + * @param serverUrl base url that defined in swagger content + */ +case class DynamicEndpointInfo(id: String, docsToUrl: mutable.Iterable[(ResourceDoc, String)], serverUrl: Option[String]) { + val resourceDocs: mutable.Iterable[ResourceDoc] = docsToUrl.map(_._1) + + private val existsUrlToMethod: mutable.Iterable[(HttpMethod, String, ResourceDoc)] = + docsToUrl + .map(it => { + val (doc, path) = it + (HttpMethod.valueOf(doc.requestVerb), path, doc) + }) + + def findDynamicEndpoint(newMethod: HttpMethod, newUrl: String): Option[(DynamicEndpointInfo, ResourceDoc)] = existsUrlToMethod.find(it => { + val (method, url, _) = it + isSameUrl(newUrl, url) && newMethod == method + }).map(this -> _._3) + + def existsEndpoint(newMethod: HttpMethod, newUrl: String): Boolean = findDynamicEndpoint(newMethod, newUrl).isDefined + + def targetUrl(url: String): String = s"""${serverUrl.getOrElse("/")}/$url""".replaceAll("/{2,}", "/") + + /** + * check whether two url is the same: + * isSameUrl("/abc/efg", "/abc/efg") == true + * isSameUrl("/abc/efg", "/abc/{id}") == true + * isSameUrl("/abc/{userId}", "/abc/{id}") == true + * isSameUrl("/abc/{userId}/", "/abc/{id}") == true + * isSameUrl("/def/abc/", "/abc/{id}") == false + * @param pathX + * @param pathY + * @return + */ + private def isSameUrl(pathX: String, pathY: String) = { + val splitPathX = StringUtils.split(pathX, '/') + val splitPathY = StringUtils.split(pathY, '/') + if(splitPathX.size != splitPathY.size) { + false + } else { + splitPathX.zip(splitPathY).forall {kv => + val (partX, partY) = kv + partX == partY || + (partX.startsWith("{") && partX.endsWith("}")) || + (partY.startsWith("{") && partY.endsWith("}")) + } + } + } } \ No newline at end of file diff --git a/obp-api/src/main/scala/code/api/v4_0_0/OBPAPI4_0_0.scala b/obp-api/src/main/scala/code/api/v4_0_0/OBPAPI4_0_0.scala index 649c7125c..919c71345 100644 --- a/obp-api/src/main/scala/code/api/v4_0_0/OBPAPI4_0_0.scala +++ b/obp-api/src/main/scala/code/api/v4_0_0/OBPAPI4_0_0.scala @@ -55,7 +55,7 @@ object OBPAPI4_0_0 extends OBPRestHelper with APIMethods130 with APIMethods140 w // Possible Endpoints from 4.0.0, exclude one endpoint use - method,exclude multiple endpoints use -- method, // e.g getEndpoints(Implementations4_0_0) -- List(Implementations4_0_0.genericEndpoint, Implementations4_0_0.root) - val endpointsOf4_0_0 = getEndpoints(Implementations4_0_0) - Implementations4_0_0.genericEndpoint + val endpointsOf4_0_0 = getEndpoints(Implementations4_0_0) - Implementations4_0_0.genericEndpoint - Implementations4_0_0.dynamicEndpoint lazy val excludeEndpoints = nameOf(Implementations1_2_1.addPermissionForUserForBankAccountForMultipleViews) :: @@ -85,6 +85,8 @@ object OBPAPI4_0_0 extends OBPRestHelper with APIMethods130 with APIMethods140 w registerRoutes(routes, allResourceDocs, apiPrefix, true) oauthServe(apiPrefix{Implementations4_0_0.genericEndpoint}, None) + oauthServe(apiPrefix{Implementations4_0_0.dynamicEndpoint}, None) + logger.info(s"version $version has been run! There are ${routes.length} routes.") // specified response for OPTIONS request. From 9ac32017c00b2c8e285df6f5a95aaf8de7277c81 Mon Sep 17 00:00:00 2001 From: shuang Date: Mon, 30 Mar 2020 09:06:04 +0800 Subject: [PATCH 28/35] feature/add_dynamic_endpoints_with_swagger: process delete DynamicEndpoint and related roles. --- .../scala/code/api/util/ErrorMessages.scala | 1 + .../main/scala/code/api/util/NewStyle.scala | 44 ++++++++++++++++--- .../scala/code/api/v4_0_0/APIMethods400.scala | 42 +++++++++++++++++- .../api/v4_0_0/DynamicEndpointHelper.scala | 31 ++++++++++--- .../rest/RestConnector_vMar2019.scala | 43 +++++++++++++++++- .../scala/code/entitlement/Entilement.scala | 2 + .../code/entitlement/MappedEntitlements.scala | 6 ++- .../remotedata/RemotedataEntitlements.scala | 4 ++ .../RemotedataEntitlementsActor.scala | 4 ++ 9 files changed, 159 insertions(+), 18 deletions(-) diff --git a/obp-api/src/main/scala/code/api/util/ErrorMessages.scala b/obp-api/src/main/scala/code/api/util/ErrorMessages.scala index 0f3ddc696..4b5f8439b 100644 --- a/obp-api/src/main/scala/code/api/util/ErrorMessages.scala +++ b/obp-api/src/main/scala/code/api/util/ErrorMessages.scala @@ -50,6 +50,7 @@ object ErrorMessages { val DynamicEntityInstanceValidateFail = "OBP-09007: DynamicEntity data validation failure." val DynamicEndpointExists = "OBP-09008: DynamicEndpoint already exists." + val DynamicEndpointNotFoundByDynamicEndpointId = "OBP-09009: DynamicEndpoint not found. Please specify a valid value for DYNAMIC_ENDPOINT_ID." // General messages (OBP-10XXX) diff --git a/obp-api/src/main/scala/code/api/util/NewStyle.scala b/obp-api/src/main/scala/code/api/util/NewStyle.scala index 63d5aaa88..0a09123ea 100644 --- a/obp-api/src/main/scala/code/api/util/NewStyle.scala +++ b/obp-api/src/main/scala/code/api/util/NewStyle.scala @@ -3,8 +3,9 @@ package code.api.util import java.util.Date import java.util.UUID.randomUUID +import akka.http.scaladsl.model.HttpMethod import code.DynamicData.DynamicDataProvider -import code.DynamicEndpoint.DynamicEndpointT +import code.DynamicEndpoint.{DynamicEndpointProvider, DynamicEndpointT} import code.api.APIFailureNewStyle import code.api.cache.Caching import code.api.util.APIUtil.{OBPReturnType, canGrantAccessToViewCommon, canRevokeAccessToViewCommon, connectorEmptyResponse, createHttpParamsByUrlFuture, createQueriesByHttpParamsFuture, fullBoxOrException, generateUUID, unboxFull, unboxFullOrFail} @@ -14,8 +15,9 @@ import code.api.v1_4_0.OBPAPI1_4_0.Implementations1_4_0 import code.api.v2_0_0.OBPAPI2_0_0.Implementations2_0_0 import code.api.v2_1_0.OBPAPI2_1_0.Implementations2_1_0 import code.api.v2_2_0.OBPAPI2_2_0.Implementations2_2_0 -import code.api.v4_0_0.DynamicEntityInfo +import code.api.v4_0_0.{DynamicEndpointHelper, DynamicEntityInfo} import code.bankconnectors.Connector +import code.bankconnectors.rest.RestConnector_vMar2019 import code.branches.Branches.{Branch, DriveUpString, LobbyString} import code.consumer.Consumers import code.directdebit.DirectDebitTrait @@ -1920,6 +1922,10 @@ object NewStyle { } } } + def dynamicEndpointProcess(url: String, jValue: JValue, method: HttpMethod, params: Map[String, List[String]], + callContext: Option[CallContext]): OBPReturnType[Box[JValue]] = { + RestConnector_vMar2019.dynamicEndpointProcess(url, jValue, method, params, callContext) + } def createDirectDebit(bankId : String, @@ -2007,11 +2013,10 @@ object NewStyle { } def getDynamicEndpoint(dynamicEndpointId: String, callContext: Option[CallContext]): OBPReturnType[DynamicEndpointT] = { - Connector.connector.vend.getDynamicEndpoint( - dynamicEndpointId, - callContext - ) map { - i => (connectorEmptyResponse(i._1, callContext), i._2) + val dynamicEndpointBox: Box[DynamicEndpointT] = DynamicEndpointProvider.connectorMethodProvider.vend.get(dynamicEndpointId) + val dynamicEndpoint = unboxFullOrFail(dynamicEndpointBox, callContext, DynamicEndpointNotFoundByDynamicEndpointId, 404) + Future{ + (dynamicEndpoint, callContext) } } @@ -2020,6 +2025,31 @@ object NewStyle { callContext ) } + /** + * delete one DynamicEndpoint and corresponding entitlement and dynamic entitlement + * @param dynamicEndpointId + * @param callContext + * @return + */ + def deleteDynamicEndpoint(dynamicEndpointId: String, callContext: Option[CallContext]): Future[Box[Boolean]] = { + val dynamicEndpoint: OBPReturnType[DynamicEndpointT] = this.getDynamicEndpoint(dynamicEndpointId, callContext) + for { + (entity, _) <- dynamicEndpoint + deleteSuccess = DynamicEndpointProvider.connectorMethodProvider.vend.delete(dynamicEndpointId) + + deleteEndpointResult: Box[Boolean] = if(deleteSuccess) { + val roles = DynamicEndpointHelper.getRoles(dynamicEndpointId).map(_.toString()) + DynamicEndpointHelper.removeEndpoint(dynamicEndpointId) + val rolesDeleteResult: Box[Boolean] = Entitlement.entitlement.vend.deleteEntitlements(roles) + + Box !! (rolesDeleteResult == Full(true)) + } else { + Box !! false + } + } yield { + deleteEndpointResult + } + } def deleteCustomerAttribute(customerAttributeId : String, callContext: Option[CallContext]): OBPReturnType[Boolean] = { Connector.connector.vend.deleteCustomerAttribute(customerAttributeId, callContext) map { diff --git a/obp-api/src/main/scala/code/api/v4_0_0/APIMethods400.scala b/obp-api/src/main/scala/code/api/v4_0_0/APIMethods400.scala index 4cda0317c..95a20f51b 100644 --- a/obp-api/src/main/scala/code/api/v4_0_0/APIMethods400.scala +++ b/obp-api/src/main/scala/code/api/v4_0_0/APIMethods400.scala @@ -2838,6 +2838,7 @@ trait APIMethods400 { List( $UserNotLoggedIn, UserHasMissingRoles, + DynamicEndpointNotFoundByDynamicEndpointId, InvalidJsonFormat, UnknownError ), @@ -2900,6 +2901,39 @@ trait APIMethods400 { } } + resourceDocs += ResourceDoc( + deleteDynamicEndpoint, + implementedInApiVersion, + nameOf(deleteDynamicEndpoint), + "DELETE", + "/management/dynamic-endpoints/DYNAMIC_ENDPOINT_ID", + " Delete Dynamic Endpoint", + s"""Delete a DynamicEndpoint specified by DYNAMIC_ENDPOINT_ID. + | + |""", + emptyObjectJson, + emptyObjectJson, + List( + $UserNotLoggedIn, + UserHasMissingRoles, + DynamicEndpointNotFoundByDynamicEndpointId, + UnknownError + ), + Catalogs(notCore, notPSD2, notOBWG), + List(apiTagDynamicEndpoint, apiTagApi, apiTagNewStyle), + Some(List(canDeleteDynamicEndpoint))) + + lazy val deleteDynamicEndpoint : OBPEndpoint = { + case "management" :: "dynamic-endpoints" :: dynamicEndpointId :: Nil JsonDelete _ => { + cc => + for { + deleted <- NewStyle.function.deleteDynamicEndpoint(dynamicEndpointId, cc.callContext) + } yield { + (deleted, HttpCode.`200`(cc.callContext)) + } + } + } + lazy val dynamicEndpoint: OBPEndpoint = { case DynamicReq(url, json, method, params, role) => { cc => @@ -2907,10 +2941,14 @@ trait APIMethods400 { (Full(u), callContext) <- authenticatedAccess(cc) _ <- NewStyle.function.hasEntitlement("", u.userId, role, callContext) - jValue = JObject(JField("name", "hello")) + (box, _) <- NewStyle.function.dynamicEndpointProcess(url, json, method, params, callContext) } yield { + box match { + case Full(v) => (v, HttpCode.`200`(Some(cc))) + case e: Failure => (e.messageChain, HttpCode.`200`(Some(cc))) // TODO code need change + case _ => ("fail", HttpCode.`200`(Some(cc))) + } - (jValue, HttpCode.`200`(Some(cc))) } } } diff --git a/obp-api/src/main/scala/code/api/v4_0_0/DynamicEndpointHelper.scala b/obp-api/src/main/scala/code/api/v4_0_0/DynamicEndpointHelper.scala index e1e17ec97..0091bf1ef 100644 --- a/obp-api/src/main/scala/code/api/v4_0_0/DynamicEndpointHelper.scala +++ b/obp-api/src/main/scala/code/api/v4_0_0/DynamicEndpointHelper.scala @@ -5,8 +5,9 @@ import java.nio.charset.Charset import java.util import java.util.concurrent.CopyOnWriteArrayList import java.util.regex.Pattern -import java.util.{Date, Objects, Optional, stream} +import java.util.{Date, Optional} +import akka.http.scaladsl.model.HttpMethods import code.DynamicEndpoint.{DynamicEndpointProvider, DynamicEndpointT} import code.api.util.APIUtil.{Catalogs, OBPEndpoint, ResourceDoc, authenticationRequiredMessage, emptyObjectJson, generateUUID, notCore, notOBWG, notPSD2} import code.api.util.ApiTag.{ResourceDocTag, apiTagApi, apiTagNewStyle} @@ -17,6 +18,7 @@ import com.openbankproject.commons.model.enums.DynamicEntityFieldType import com.openbankproject.commons.util.{ApiVersion, Functions} import io.swagger.v3.oas.models.{OpenAPI, Operation, PathItem} import io.swagger.v3.oas.models.PathItem.HttpMethod +import akka.http.scaladsl.model.{HttpMethod => AkkaHttpMethod} import io.swagger.v3.oas.models.media.{ArraySchema, BooleanSchema, Content, DateSchema, DateTimeSchema, IntegerSchema, NumberSchema, ObjectSchema, Schema, StringSchema} import io.swagger.v3.oas.models.parameters.RequestBody import io.swagger.v3.oas.models.responses.ApiResponses @@ -50,6 +52,20 @@ object DynamicEndpointHelper extends RestHelper { val infos = dynamicEndpoints.map(it => swaggerToResourceDocs(it.swaggerString, it.dynamicEndpointId.get)) new CopyOnWriteArrayList(infos.asJava) } + + def getRoles(dynamicEndpointId: String): List[ApiRole] = { + val foundInfos: Option[DynamicEndpointInfo] = dynamicEndpointInfos.asScala + .find(_.id == dynamicEndpointId) + + val roles = foundInfos.toList + .flatMap(_.resourceDocs) + .map(_.roles) + .collect { + case Some(role :: _) => role + } + + roles + } /** * extract request body, no matter GET, POST, PUT or DELETE method */ @@ -59,27 +75,28 @@ object DynamicEndpointHelper extends RestHelper { * @param r HttpRequest * @return */ - def unapply(r: Req): Option[(String, JValue, HttpMethod, Map[String, List[String]], ApiRole)] = { + def unapply(r: Req): Option[(String, JValue, AkkaHttpMethod, Map[String, List[String]], ApiRole)] = { val partPath = r.path.partPath if (!testResponse_?(r) || partPath.headOption != Option(urlPrefix)) None else { - val method = HttpMethod.valueOf(r.requestType.method) + val akkaHttpMethod = HttpMethods.getForKeyCaseInsensitive(r.requestType.method).get + val httpMethod = HttpMethod.valueOf(r.requestType.method) // url that match original swagger endpoint. val url = partPath.tail.mkString("/", "/", "") val foundDynamicEndpoint: Optional[(DynamicEndpointInfo, ResourceDoc)] = dynamicEndpointInfos.stream() - .map[Option[(DynamicEndpointInfo, ResourceDoc)]](_.findDynamicEndpoint(method, url)) + .map[Option[(DynamicEndpointInfo, ResourceDoc)]](_.findDynamicEndpoint(httpMethod, url)) .filter(_.isDefined) .findFirst() .map(_.get) foundDynamicEndpoint.asScala - .flatMap[(String, JValue, HttpMethod, Map[String, List[String]], ApiRole)] { it => + .flatMap[(String, JValue, AkkaHttpMethod, Map[String, List[String]], ApiRole)] { it => val (dynamicEndpointInfo, doc) = it val Some(role::_) = doc.roles body(r).toOption .orElse(Some(JNothing)) - .map(t => (dynamicEndpointInfo.targetUrl(url), t, method, r.params, role)) + .map(t => (dynamicEndpointInfo.targetUrl(url), t, akkaHttpMethod, r.params, role)) } } @@ -363,7 +380,7 @@ case class DynamicEndpointInfo(id: String, docsToUrl: mutable.Iterable[(Resource def existsEndpoint(newMethod: HttpMethod, newUrl: String): Boolean = findDynamicEndpoint(newMethod, newUrl).isDefined - def targetUrl(url: String): String = s"""${serverUrl.getOrElse("/")}/$url""".replaceAll("/{2,}", "/") + def targetUrl(url: String): String = s"""${serverUrl.get}$url""" /** * check whether two url is the same: diff --git a/obp-api/src/main/scala/code/bankconnectors/rest/RestConnector_vMar2019.scala b/obp-api/src/main/scala/code/bankconnectors/rest/RestConnector_vMar2019.scala index 8a4c0e8e2..c85069cab 100644 --- a/obp-api/src/main/scala/code/bankconnectors/rest/RestConnector_vMar2019.scala +++ b/obp-api/src/main/scala/code/bankconnectors/rest/RestConnector_vMar2019.scala @@ -61,8 +61,10 @@ import code.model.dataAccess.internalMapping.MappedAccountIdMappingProvider import code.util.{Helper, JsonUtils} import com.openbankproject.commons.model.enums.{AccountAttributeType, CardAttributeType, DynamicEntityOperation, ProductAttributeType} import com.openbankproject.commons.util.{ReflectUtils, RequiredFieldValidation} -import net.liftweb.json._ +import net.liftweb.json +import net.liftweb.json.{JValue, _} import net.liftweb.json.Extraction.decompose +import org.apache.commons.lang3.StringUtils trait RestConnector_vMar2019 extends Connector with KafkaHelper with MdcLoggable { //this one import is for implicit convert, don't delete @@ -9324,6 +9326,45 @@ trait RestConnector_vMar2019 extends Connector with KafkaHelper with MdcLoggable val result: OBPReturnType[Box[JValue]] = sendRequest[InBound](url, HttpMethods.POST, req, callContext).map(convertToTuple(callContext)) result } + + //TODO params process + def dynamicEndpointProcess(url: String, jValue: JValue, method: HttpMethod, params: Map[String, List[String]], + callContext: Option[CallContext]): OBPReturnType[Box[JValue]] = { + val urlInMethodRouting: Option[String] = MethodRoutingHolder.methodRouting match { + case _: EmptyBox => None + case Full(routing) => routing.parameters.find(_.key == "url").map(_.value) + } + val targetUrl = urlInMethodRouting.getOrElse(url) + val jsonToSend = if(jValue == JNothing) "" else compactRender(jValue) + val request = prepareHttpRequest(url, method, HttpProtocol("HTTP/1.1"), jsonToSend).withHeaders(callContext) + logger.debug(s"RestConnector_vMar2019 request is : $request") + val responseFuture = makeHttpRequest(request) + + val result: Future[(Box[JValue], Option[CallContext])] = responseFuture.map { + case response@HttpResponse(status, _, entity@_, _) => (status, entity) + }.flatMap { + case (status, entity) if status.isSuccess() => + this.extractBody(entity) + .map{ + case v if StringUtils.isBlank(v) => (Empty, callContext) + case v => (Full(json.parse(v)), callContext) + } + case (status, entity) => { + val future: Future[Box[Box[JValue]]] = extractBody(entity) map { msg => + tryo { + val failure: Box[JValue] = ParamFailure(msg, APIFailureNewStyle(msg, status.intValue())) + failure + } ~> APIFailureNewStyle(msg, status.intValue()) + } + future.map{ + case Full(v) => (v, callContext) + case e: EmptyBox => (e, callContext) + } + } + } + + result + } //In RestConnector, we use the headers to propagate the parameters to Adapter. The parameters come from the CallContext.outboundAdapterAuthInfo.userAuthContext diff --git a/obp-api/src/main/scala/code/entitlement/Entilement.scala b/obp-api/src/main/scala/code/entitlement/Entilement.scala index 3fe8aa7dd..b67888ce7 100644 --- a/obp-api/src/main/scala/code/entitlement/Entilement.scala +++ b/obp-api/src/main/scala/code/entitlement/Entilement.scala @@ -32,6 +32,7 @@ trait EntitlementProvider { def getEntitlementsByRoleFuture(roleName: String) : Future[Box[List[Entitlement]]] def addEntitlement(bankId: String, userId: String, roleName: String) : Box[Entitlement] def deleteDynamicEntityEntitlement(entityName: String) : Box[Boolean] + def deleteEntitlements(entityNames: List[String]) : Box[Boolean] } trait Entitlement { @@ -54,6 +55,7 @@ class RemotedataEntitlementsCaseClasses { case class getEntitlementsByRoleFuture(roleName: String) case class addEntitlement(bankId: String, userId: String, roleName: String) case class deleteDynamicEntityEntitlement(entityName: String) + case class deleteEntitlements(entityNames: List[String]) } object RemotedataEntitlementsCaseClasses extends RemotedataEntitlementsCaseClasses \ No newline at end of file diff --git a/obp-api/src/main/scala/code/entitlement/MappedEntitlements.scala b/obp-api/src/main/scala/code/entitlement/MappedEntitlements.scala index abdd36726..c195ab7cd 100644 --- a/obp-api/src/main/scala/code/entitlement/MappedEntitlements.scala +++ b/obp-api/src/main/scala/code/entitlement/MappedEntitlements.scala @@ -94,8 +94,12 @@ object MappedEntitlementsProvider extends EntitlementProvider { override def deleteDynamicEntityEntitlement(entityName: String): Box[Boolean] = { val roleNames = DynamicEntityInfo.roleNames(entityName) + deleteEntitlements(roleNames) + } + + override def deleteEntitlements(entityNames: List[String]) : Box[Boolean] = { Box.tryo{ - MappedEntitlement.bulkDelete_!!(ByList(MappedEntitlement.mRoleName, roleNames)) + MappedEntitlement.bulkDelete_!!(ByList(MappedEntitlement.mRoleName, entityNames)) } } diff --git a/obp-api/src/main/scala/code/remotedata/RemotedataEntitlements.scala b/obp-api/src/main/scala/code/remotedata/RemotedataEntitlements.scala index a0508baf0..b1ad08809 100644 --- a/obp-api/src/main/scala/code/remotedata/RemotedataEntitlements.scala +++ b/obp-api/src/main/scala/code/remotedata/RemotedataEntitlements.scala @@ -56,4 +56,8 @@ object RemotedataEntitlements extends ObpActorInit with EntitlementProvider { (actor ? cc.deleteDynamicEntityEntitlement(entityName)).mapTo[Box[Boolean]] ) + override def deleteEntitlements(entityNames: List[String]) : Box[Boolean] = getValueFromFuture( + (actor ? cc.deleteEntitlements(entityNames)).mapTo[Box[Boolean]] + ) + } diff --git a/obp-api/src/main/scala/code/remotedata/RemotedataEntitlementsActor.scala b/obp-api/src/main/scala/code/remotedata/RemotedataEntitlementsActor.scala index e43e7ce14..97f8bb9bf 100644 --- a/obp-api/src/main/scala/code/remotedata/RemotedataEntitlementsActor.scala +++ b/obp-api/src/main/scala/code/remotedata/RemotedataEntitlementsActor.scala @@ -63,6 +63,10 @@ class RemotedataEntitlementsActor extends Actor with ObpActorHelper with MdcLogg logger.debug(s"deleteDynamicEntityEntitlement($entityName)") sender ! (mapper.deleteDynamicEntityEntitlement(entityName)) + case cc.deleteEntitlements(entityNames) => + logger.debug(s"deleteEntitlements($entityNames)") + sender ! (mapper.deleteEntitlements(entityNames)) + case message => logger.warn("[AKKA ACTOR ERROR - REQUEST NOT RECOGNIZED] " + message) } From 3b5e08c6ccf7ebc1863a37dc37720eb3f12d0eec Mon Sep 17 00:00:00 2001 From: hongwei Date: Mon, 30 Mar 2020 06:53:38 +0200 Subject: [PATCH 29/35] tweaked the dynamicEndpointId -> dynamic_endpoint_id tweaked the swaggerString -> swagger_string --- obp-api/src/main/scala/code/api/v4_0_0/APIMethods400.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/obp-api/src/main/scala/code/api/v4_0_0/APIMethods400.scala b/obp-api/src/main/scala/code/api/v4_0_0/APIMethods400.scala index 0935a968e..421ae74a4 100644 --- a/obp-api/src/main/scala/code/api/v4_0_0/APIMethods400.scala +++ b/obp-api/src/main/scala/code/api/v4_0_0/APIMethods400.scala @@ -2806,7 +2806,7 @@ trait APIMethods400 { (dynamicEndpoint, callContext) <- NewStyle.function.createDynamicEndpoint(postedJson.swaggerString, cc.callContext) } yield { val swaggerJson = parse(dynamicEndpoint.swaggerString) - val responseJson: JObject = ("dynamicEndpointId", dynamicEndpoint.dynamicEndpointId) ~ ("swaggerString", swaggerJson) + val responseJson: JObject = ("dynamic_endpoint_id", dynamicEndpoint.dynamicEndpointId) ~ ("swagger_string", swaggerJson) (responseJson, HttpCode.`201`(callContext)) } } @@ -2845,7 +2845,7 @@ trait APIMethods400 { (dynamicEndpoint, callContext) <- NewStyle.function.getDynamicEndpoint(dynamicEndpointId, cc.callContext) } yield { val swaggerJson = parse(dynamicEndpoint.swaggerString) - val responseJson: JObject = ("dynamicEndpointId", dynamicEndpoint.dynamicEndpointId) ~ ("swaggerString", swaggerJson) + val responseJson: JObject = ("dynamic_endpoint_id", dynamicEndpoint.dynamicEndpointId) ~ ("swagger_string", swaggerJson) (responseJson, HttpCode.`201`(callContext)) } } From e4a41dbf2d0c2df8b4357f86e06bf41602965764 Mon Sep 17 00:00:00 2001 From: hongwei Date: Mon, 30 Mar 2020 06:56:06 +0200 Subject: [PATCH 30/35] tweaked 201 ->200 for getDynamicEndpoint --- obp-api/src/main/scala/code/api/v4_0_0/APIMethods400.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/obp-api/src/main/scala/code/api/v4_0_0/APIMethods400.scala b/obp-api/src/main/scala/code/api/v4_0_0/APIMethods400.scala index 421ae74a4..512e20f70 100644 --- a/obp-api/src/main/scala/code/api/v4_0_0/APIMethods400.scala +++ b/obp-api/src/main/scala/code/api/v4_0_0/APIMethods400.scala @@ -2846,7 +2846,7 @@ trait APIMethods400 { } yield { val swaggerJson = parse(dynamicEndpoint.swaggerString) val responseJson: JObject = ("dynamic_endpoint_id", dynamicEndpoint.dynamicEndpointId) ~ ("swagger_string", swaggerJson) - (responseJson, HttpCode.`201`(callContext)) + (responseJson, HttpCode.`200`(callContext)) } } } From fd089a939643f2e768ebdc9264dc2eb6aa0fb573 Mon Sep 17 00:00:00 2001 From: shuang Date: Mon, 30 Mar 2020 12:46:55 +0800 Subject: [PATCH 31/35] feature/add_dynamic_endpoints_with_swagger: methodRouting can change request url, and url can have expression --- .../main/scala/code/api/util/NewStyle.scala | 4 +- .../scala/code/api/v4_0_0/APIMethods400.scala | 8 +- .../api/v4_0_0/DynamicEndpointHelper.scala | 87 ++++++++++++++----- .../scala/code/bankconnectors/Connector.scala | 4 + .../bankconnectors/LocalMappedConnector.scala | 10 +++ .../scala/code/bankconnectors/package.scala | 14 +++ .../rest/RestConnector_vMar2019.scala | 44 +++++++++- .../src/main/scala/code/util/JsonUtils.scala | 12 ++- 8 files changed, 150 insertions(+), 33 deletions(-) diff --git a/obp-api/src/main/scala/code/api/util/NewStyle.scala b/obp-api/src/main/scala/code/api/util/NewStyle.scala index 0a09123ea..9bfa4b9ca 100644 --- a/obp-api/src/main/scala/code/api/util/NewStyle.scala +++ b/obp-api/src/main/scala/code/api/util/NewStyle.scala @@ -1922,9 +1922,9 @@ object NewStyle { } } } - def dynamicEndpointProcess(url: String, jValue: JValue, method: HttpMethod, params: Map[String, List[String]], + def dynamicEndpointProcess(url: String, jValue: JValue, method: HttpMethod, params: Map[String, List[String]], pathParams: Map[String, String], callContext: Option[CallContext]): OBPReturnType[Box[JValue]] = { - RestConnector_vMar2019.dynamicEndpointProcess(url, jValue, method, params, callContext) + Connector.connector.vend.dynamicEndpointProcess(url, jValue, method, params, pathParams, callContext) } diff --git a/obp-api/src/main/scala/code/api/v4_0_0/APIMethods400.scala b/obp-api/src/main/scala/code/api/v4_0_0/APIMethods400.scala index 95a20f51b..578819b01 100644 --- a/obp-api/src/main/scala/code/api/v4_0_0/APIMethods400.scala +++ b/obp-api/src/main/scala/code/api/v4_0_0/APIMethods400.scala @@ -2866,9 +2866,9 @@ trait APIMethods400 { "GET", "/management/dynamic-endpoints", " Get DynamicEndpoints", - s"""Get DynamicEndpoints. + s""" | - |Get DynamicEndpoints, + |Get DynamicEndpoints. | |""", emptyObjectJson, @@ -2936,12 +2936,12 @@ trait APIMethods400 { lazy val dynamicEndpoint: OBPEndpoint = { - case DynamicReq(url, json, method, params, role) => { cc => + case DynamicReq(url, json, method, params, pathParams, role) => { cc => for { (Full(u), callContext) <- authenticatedAccess(cc) _ <- NewStyle.function.hasEntitlement("", u.userId, role, callContext) - (box, _) <- NewStyle.function.dynamicEndpointProcess(url, json, method, params, callContext) + (box, _) <- NewStyle.function.dynamicEndpointProcess(url, json, method, params, pathParams, callContext) } yield { box match { case Full(v) => (v, HttpCode.`200`(Some(cc))) diff --git a/obp-api/src/main/scala/code/api/v4_0_0/DynamicEndpointHelper.scala b/obp-api/src/main/scala/code/api/v4_0_0/DynamicEndpointHelper.scala index 0091bf1ef..dd43c7249 100644 --- a/obp-api/src/main/scala/code/api/v4_0_0/DynamicEndpointHelper.scala +++ b/obp-api/src/main/scala/code/api/v4_0_0/DynamicEndpointHelper.scala @@ -70,12 +70,20 @@ object DynamicEndpointHelper extends RestHelper { * extract request body, no matter GET, POST, PUT or DELETE method */ object DynamicReq extends JsonTest with JsonBody { + + private val ExpressionRegx = """\{(.+?)\}""".r /** - * unapply Request to (swagger url, json, http method, request parameters, role) + * unapply Request to (request url, json, http method, request parameters, path parameters, role) + * request url is current request url + * json is request body + * http method is request http method + * request parameters is http request parameters + * path parameters: /banks/{bankId}/users/{userId} bankId and userId corresponding key to value + * role is current endpoint required entitlement * @param r HttpRequest * @return */ - def unapply(r: Req): Option[(String, JValue, AkkaHttpMethod, Map[String, List[String]], ApiRole)] = { + def unapply(r: Req): Option[(String, JValue, AkkaHttpMethod, Map[String, List[String]], Map[String, String], ApiRole)] = { val partPath = r.path.partPath if (!testResponse_?(r) || partPath.headOption != Option(urlPrefix)) None @@ -84,19 +92,29 @@ object DynamicEndpointHelper extends RestHelper { val httpMethod = HttpMethod.valueOf(r.requestType.method) // url that match original swagger endpoint. val url = partPath.tail.mkString("/", "/", "") - val foundDynamicEndpoint: Optional[(DynamicEndpointInfo, ResourceDoc)] = dynamicEndpointInfos.stream() - .map[Option[(DynamicEndpointInfo, ResourceDoc)]](_.findDynamicEndpoint(httpMethod, url)) + val foundDynamicEndpoint: Optional[(DynamicEndpointInfo, ResourceDoc, String)] = dynamicEndpointInfos.stream() + .map[Option[(DynamicEndpointInfo, ResourceDoc, String)]](_.findDynamicEndpoint(httpMethod, url)) .filter(_.isDefined) .findFirst() .map(_.get) foundDynamicEndpoint.asScala - .flatMap[(String, JValue, AkkaHttpMethod, Map[String, List[String]], ApiRole)] { it => - val (dynamicEndpointInfo, doc) = it + .flatMap[(String, JValue, AkkaHttpMethod, Map[String, List[String]], Map[String, String], ApiRole)] { it => + val (dynamicEndpointInfo, doc, originalUrl) = it + + val pathParams: Map[String, String] = if(originalUrl == url) { + Map.empty[String, String] + } else { + val tuples: Array[(String, String)] = StringUtils.split(originalUrl, "/").zip(partPath.tail) + tuples.collect { + case (ExpressionRegx(name), value) => name->value + }.toMap + } + val Some(role::_) = doc.roles body(r).toOption .orElse(Some(JNothing)) - .map(t => (dynamicEndpointInfo.targetUrl(url), t, akkaHttpMethod, r.params, role)) + .map(t => (dynamicEndpointInfo.targetUrl(url), t, akkaHttpMethod, r.params, pathParams, role)) } } @@ -132,6 +150,12 @@ object DynamicEndpointHelper extends RestHelper { private def swaggerToResourceDocs(openAPI: OpenAPI, id: String): DynamicEndpointInfo = { val tags: List[ResourceDocTag] = List(ApiTag.apiTagDynamicEndpoint, apiTagApi, apiTagNewStyle) + val serverUrl = { + val servers = openAPI.getServers + assert(!servers.isEmpty, s"swagger host is mandatory, but current swagger host is empty, id=$id") + servers.get(0).getUrl + } + val paths: mutable.Map[String, PathItem] = openAPI.getPaths.asScala def entitlementSuffix(path: String) = Math.abs(path.hashCode).toString.substring(0, 3) // to avoid different swagger have same entitlement val docs: mutable.Iterable[(ResourceDoc, String)] = for { @@ -151,7 +175,34 @@ object DynamicEndpointHelper extends RestHelper { .orElse(Option(op.getDescription)) .filter(StringUtils.isNotBlank) .map(_.capitalize) - .getOrElse(summary) + .getOrElse(summary) + + s""" + | + |MethodRouting settings example: + |``` + |{ + | "is_bank_id_exact_match":false, + | "method_name":"dynamicEndpointProcess", + | "connector_name":"rest_vMar2019", + | "bank_id_pattern":".*", + | "parameters":[ + | { + | "key":"url_pattern", + | "value":"$serverUrl$path" + | }, + | { + | "key":"http_method", + | "value":"$requestVerb" + | } + | { + | "key":"url", + | "value":"http://mydomain.com/xxx" + | } + | ] + |} + |``` + | + |""".stripMargin val exampleRequestBody: Product = getRequestExample(openAPI, op.getRequestBody) val successResponseBody: Product = getResponseExample(openAPI, op.getResponses) val errorResponseBodies: List[String] = List( @@ -169,7 +220,7 @@ object DynamicEndpointHelper extends RestHelper { ApiRole.getOrCreateDynamicApiRole(roleName) )) } - val connectorMethods = Some(List(s"""dynamicEntityProcess: parameters contains {"key": "entityName", "value": "$summary"}""")) //TODO temp + val connectorMethods = Some(List("dynamicEndpointProcess")) val doc = ResourceDoc( partialFunction, implementedInApiVersion, @@ -189,18 +240,10 @@ object DynamicEndpointHelper extends RestHelper { (doc, path) } - val serverUrl = { - val servers = openAPI.getServers - if(servers.isEmpty) { - None - } else { - Some(servers.get(0).getUrl) - } - } DynamicEndpointInfo(id, docs, serverUrl) } - private val PathParamRegx = """\{(.+)\}""".r + private val PathParamRegx = """\{(.+?)\}""".r private val WordBoundPattern = Pattern.compile("(?<=[a-z])(?=[A-Z])|-") private def buildRequestUrl(path: String): String = { @@ -363,7 +406,7 @@ object DynamicEndpointHelper extends RestHelper { * @param docsToUrl ResourceDoc to url that defined in swagger content * @param serverUrl base url that defined in swagger content */ -case class DynamicEndpointInfo(id: String, docsToUrl: mutable.Iterable[(ResourceDoc, String)], serverUrl: Option[String]) { +case class DynamicEndpointInfo(id: String, docsToUrl: mutable.Iterable[(ResourceDoc, String)], serverUrl: String) { val resourceDocs: mutable.Iterable[ResourceDoc] = docsToUrl.map(_._1) private val existsUrlToMethod: mutable.Iterable[(HttpMethod, String, ResourceDoc)] = @@ -373,14 +416,14 @@ case class DynamicEndpointInfo(id: String, docsToUrl: mutable.Iterable[(Resource (HttpMethod.valueOf(doc.requestVerb), path, doc) }) - def findDynamicEndpoint(newMethod: HttpMethod, newUrl: String): Option[(DynamicEndpointInfo, ResourceDoc)] = existsUrlToMethod.find(it => { + def findDynamicEndpoint(newMethod: HttpMethod, newUrl: String): Option[(DynamicEndpointInfo, ResourceDoc, String)] = existsUrlToMethod.find(it => { val (method, url, _) = it isSameUrl(newUrl, url) && newMethod == method - }).map(this -> _._3) + }).map(it => (this, it._3, it._2)) def existsEndpoint(newMethod: HttpMethod, newUrl: String): Boolean = findDynamicEndpoint(newMethod, newUrl).isDefined - def targetUrl(url: String): String = s"""${serverUrl.get}$url""" + def targetUrl(url: String): String = s"""$serverUrl$url""" /** * check whether two url is the same: diff --git a/obp-api/src/main/scala/code/bankconnectors/Connector.scala b/obp-api/src/main/scala/code/bankconnectors/Connector.scala index 4b5ae7051..fe4faccd5 100644 --- a/obp-api/src/main/scala/code/bankconnectors/Connector.scala +++ b/obp-api/src/main/scala/code/bankconnectors/Connector.scala @@ -59,6 +59,7 @@ import scala.concurrent.duration._ import scala.math.{BigDecimal, BigInt} import scala.util.Random import scala.reflect.runtime.universe.{MethodSymbol, typeOf} +import _root_.akka.http.scaladsl.model.HttpMethod /* So we can switch between different sources of resources e.g. @@ -2152,6 +2153,9 @@ trait Connector extends MdcLoggable { requestBody: Option[JObject], entityId: Option[String], callContext: Option[CallContext]): OBPReturnType[Box[JValue]] = Future{(Failure(setUnimplementedError), callContext)} + + def dynamicEndpointProcess(url: String, jValue: JValue, method: HttpMethod, params: Map[String, List[String]], pathParams: Map[String, String], + callContext: Option[CallContext]): OBPReturnType[Box[JValue]] = Future{(Failure(setUnimplementedError), callContext)} def createDirectDebit(bankId: String, accountId: String, diff --git a/obp-api/src/main/scala/code/bankconnectors/LocalMappedConnector.scala b/obp-api/src/main/scala/code/bankconnectors/LocalMappedConnector.scala index ab01ff068..fa3ad1a46 100644 --- a/obp-api/src/main/scala/code/bankconnectors/LocalMappedConnector.scala +++ b/obp-api/src/main/scala/code/bankconnectors/LocalMappedConnector.scala @@ -2,6 +2,7 @@ package code.bankconnectors import java.util.Date import java.util.UUID.randomUUID + import scala.concurrent.duration._ import code.DynamicData.DynamicDataProvider import code.DynamicEndpoint.{DynamicEndpointProvider, DynamicEndpointT} @@ -80,6 +81,7 @@ import net.liftweb.util.Mailer.{From, PlainMailBodyType, Subject, To} import org.mindrot.jbcrypt.BCrypt import scalacache.ScalaCache import scalacache.guava.GuavaCache + import scala.collection.immutable.{List, Nil} import com.openbankproject.commons.ExecutionContext.Implicits.global @@ -88,6 +90,7 @@ import scala.language.postfixOps import scala.math.{BigDecimal, BigInt} import scala.util.Random +import _root_.akka.http.scaladsl.model.HttpMethod object LocalMappedConnector extends Connector with MdcLoggable { @@ -3041,6 +3044,13 @@ object LocalMappedConnector extends Connector with MdcLoggable { } } + /* delegate to rest connector + */ + override def dynamicEndpointProcess(url: String, jValue: JValue, method: HttpMethod, params: Map[String, List[String]], pathParams: Map[String, String], + callContext: Option[CallContext]): OBPReturnType[Box[JValue]] = { + Connector.getConnectorInstance("rest_vMar2019").dynamicEndpointProcess(url,jValue, method, params, pathParams, callContext) + } + override def createDirectDebit(bankId: String, accountId: String, customerId: String, diff --git a/obp-api/src/main/scala/code/bankconnectors/package.scala b/obp-api/src/main/scala/code/bankconnectors/package.scala index ead2c35b8..44120f248 100644 --- a/obp-api/src/main/scala/code/bankconnectors/package.scala +++ b/obp-api/src/main/scala/code/bankconnectors/package.scala @@ -1,7 +1,9 @@ package code import java.lang.reflect.Method +import java.util.regex.Pattern +import akka.http.scaladsl.model.HttpMethod import code.api.{APIFailureNewStyle, ApiVersionHolder} import code.api.util.{CallContext, NewStyle} import code.methodrouting.{MethodRouting, MethodRoutingT} @@ -97,6 +99,18 @@ package object bankconnectors extends MdcLoggable { NewStyle.function.getMethodRoutings(Some(methodName)) .find(_.parameters.exists(it => it.key == "entityName" && it.value == entityName)) } + case _ if methodName == "dynamicEndpointProcess" => { + val Array(url: String, _, method: HttpMethod, _*) = args + NewStyle.function.getMethodRoutings(Some(methodName)) + .find(routing => { + routing.parameters.exists(it => it.key == "http_method" && it.value.equalsIgnoreCase(method.value)) && + routing.parameters.exists(it => it.key == "url")&& + routing.parameters.exists( + it => it.key == "url_pattern" && + (it.value == url || Pattern.compile(it.value).matcher(url).matches()) + ) + }) + } case None => NewStyle.function.getMethodRoutings(Some(methodName), Some(false)) .find {routing => val bankIdPattern = routing.bankIdPattern diff --git a/obp-api/src/main/scala/code/bankconnectors/rest/RestConnector_vMar2019.scala b/obp-api/src/main/scala/code/bankconnectors/rest/RestConnector_vMar2019.scala index c85069cab..e909bc495 100644 --- a/obp-api/src/main/scala/code/bankconnectors/rest/RestConnector_vMar2019.scala +++ b/obp-api/src/main/scala/code/bankconnectors/rest/RestConnector_vMar2019.scala @@ -9327,16 +9327,52 @@ trait RestConnector_vMar2019 extends Connector with KafkaHelper with MdcLoggable result } - //TODO params process - def dynamicEndpointProcess(url: String, jValue: JValue, method: HttpMethod, params: Map[String, List[String]], + + override def dynamicEndpointProcess(url: String, jValue: JValue, method: HttpMethod, params: Map[String, List[String]], pathParams: Map[String, String], callContext: Option[CallContext]): OBPReturnType[Box[JValue]] = { val urlInMethodRouting: Option[String] = MethodRoutingHolder.methodRouting match { case _: EmptyBox => None case Full(routing) => routing.parameters.find(_.key == "url").map(_.value) } - val targetUrl = urlInMethodRouting.getOrElse(url) + val pathVariableRex = """\{:(.+?)\}""".r + val targetUrl = urlInMethodRouting.map { urlInRouting => + val tuples: Iterator[(String, String)] = pathVariableRex.findAllMatchIn(urlInRouting).map{ regex => + val expression = regex.group(0) + val paramName = regex.group(1) + val paramValue = + if(paramName.startsWith("body.")) { + val path = StringUtils.substringAfter(paramName, "body.") + val value = JsonUtils.getValueByPath(jValue, path) + JsonUtils.toString(value) + } else { + pathParams.get(paramName) + .orElse(params.get(paramName).flatMap(_.headOption)).getOrElse(throw new RuntimeException(s"param $paramName not exists.")) + } + expression -> paramValue + } + + (urlInRouting /: tuples) {(pre, kv)=> + pre.replace(kv._1, kv._2) + } + }.getOrElse(url) + + val paramNameToValue = for { + (name, values) <- params + value <- values + param = s"$name=$value" + } yield param + + val paramUrl: String = + if(params.isEmpty){ + targetUrl + } else if(targetUrl.contains("?")) { + targetUrl + "&" + paramNameToValue.mkString("&") + } else { + targetUrl + "?" + paramNameToValue.mkString("&") + } + val jsonToSend = if(jValue == JNothing) "" else compactRender(jValue) - val request = prepareHttpRequest(url, method, HttpProtocol("HTTP/1.1"), jsonToSend).withHeaders(callContext) + val request = prepareHttpRequest(paramUrl, method, HttpProtocol("HTTP/1.1"), jsonToSend).withHeaders(callContext) logger.debug(s"RestConnector_vMar2019 request is : $request") val responseFuture = makeHttpRequest(request) diff --git a/obp-api/src/main/scala/code/util/JsonUtils.scala b/obp-api/src/main/scala/code/util/JsonUtils.scala index a632ba58c..f865bbba8 100644 --- a/obp-api/src/main/scala/code/util/JsonUtils.scala +++ b/obp-api/src/main/scala/code/util/JsonUtils.scala @@ -421,7 +421,7 @@ object JsonUtils { * @param pathExpress path, can be prefix by - or !, e.g: "-result.count" "!value.isDeleted" * @return given nested field value */ - private def getValueByPath(jValue: JValue, pathExpress: String): JValue = { + def getValueByPath(jValue: JValue, pathExpress: String): JValue = { pathExpress match { case str if str.trim == "$root" || str.trim.isEmpty => jValue // if path is "$root" or "", return whole original json case RegexBoolean(b) => JBool(b.toBoolean) @@ -480,4 +480,14 @@ object JsonUtils { case v => v.values.toString == expectValue } } + + def toString(jValue: JValue) = jValue match{ + case JString(s) => s + case JInt(num) => num.toString() + case JDouble(num) => num.toString() + case JBool(b) => b.toString() + case JNothing => "" + case JNull => "null" + case v => json.compactRender(v) + } } From 8435a8634844f359fcea2bf030b4c15421dd7a43 Mon Sep 17 00:00:00 2001 From: hongwei Date: Mon, 30 Mar 2020 07:09:44 +0200 Subject: [PATCH 32/35] removed personal / company details in the example swagger --- .../scala/code/api/util/ExampleValue.scala | 8 ++++---- .../code/api/v4_0_0/DynamicendPointsTest.scala | 18 +++++++++--------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/obp-api/src/main/scala/code/api/util/ExampleValue.scala b/obp-api/src/main/scala/code/api/util/ExampleValue.scala index 1698abecb..9021803ee 100644 --- a/obp-api/src/main/scala/code/api/util/ExampleValue.scala +++ b/obp-api/src/main/scala/code/api/util/ExampleValue.scala @@ -367,11 +367,11 @@ object ExampleValue { | "swagger": "2.0", | "info": { | "version": "0.0.1", - | "title": "Portus EVS sandbox demo API", - | "description": "Portus EVS sandbox demo API", + | "title": "Example Title", + | "description": "Example Description", | "contact": { - | "name": "Digital & FinTech, Grant Thornton", - | "email": "peng.xu@ie.gt.com", + | "name": "Example Company", + | "email": " simon@example.com", | "url": "https://www.tesobe.com/" | } | }, diff --git a/obp-api/src/test/scala/code/api/v4_0_0/DynamicendPointsTest.scala b/obp-api/src/test/scala/code/api/v4_0_0/DynamicendPointsTest.scala index b9fca8dd1..97bbc6e08 100644 --- a/obp-api/src/test/scala/code/api/v4_0_0/DynamicendPointsTest.scala +++ b/obp-api/src/test/scala/code/api/v4_0_0/DynamicendPointsTest.scala @@ -72,9 +72,9 @@ class DynamicEndpointsTest extends V400ServerSetup { responseWithRole.code should equal(201) responseWithRole.body.toString contains("dynamic_endpoint_id") should be (true) responseWithRole.body.toString contains("swagger_string") should be (true) - responseWithRole.body.toString contains("Portus EVS sandbox demo API") should be (true) - responseWithRole.body.toString contains("content user-friendly error message") should be (true) - responseWithRole.body.toString contains("create user successful and return created user object") should be (true) + responseWithRole.body.toString contains("Example Title") should be (true) + responseWithRole.body.toString contains("Example Description") should be (true) + responseWithRole.body.toString contains("Example Company") should be (true) } } @@ -123,9 +123,9 @@ class DynamicEndpointsTest extends V400ServerSetup { val request400 = (v4_0_0_Request / "management" / "dynamic-endpoints").GET<@ (user1) val response400 = makeGetRequest(request400) response400.code should be (200) - response400.body.toString contains("Portus EVS sandbox demo API") should be (true) - response400.body.toString contains("content user-friendly error message") should be (true) - response400.body.toString contains("create user successful and return created user object") should be (true) + response400.body.toString contains("Example Title") should be (true) + response400.body.toString contains("Example Description") should be (true) + response400.body.toString contains("Example Company") should be (true) } } @@ -179,9 +179,9 @@ class DynamicEndpointsTest extends V400ServerSetup { response400.code should be (200) response400.body.toString contains("dynamic_endpoint_id") should be (true) response400.body.toString contains("swagger_string") should be (true) - response400.body.toString contains("Portus EVS sandbox demo API") should be (true) - response400.body.toString contains("content user-friendly error message") should be (true) - response400.body.toString contains("create user successful and return created user object") should be (true) + response400.body.toString contains("Example Title") should be (true) + response400.body.toString contains("Example Description") should be (true) + response400.body.toString contains("Example Company") should be (true) } } From 757506f3dd438e744234046e693f10fac8bf5302 Mon Sep 17 00:00:00 2001 From: shuang Date: Mon, 30 Mar 2020 14:51:00 +0800 Subject: [PATCH 33/35] feature/add_dynamic_endpoints_with_swagger: fix fail unit test. --- .../src/main/scala/code/bankconnectors/package.scala | 7 +++++-- .../RestConnector_vMar2019_FrozenTest.scala | 12 +++++++++--- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/obp-api/src/main/scala/code/bankconnectors/package.scala b/obp-api/src/main/scala/code/bankconnectors/package.scala index 44120f248..20020c7fb 100644 --- a/obp-api/src/main/scala/code/bankconnectors/package.scala +++ b/obp-api/src/main/scala/code/bankconnectors/package.scala @@ -19,7 +19,7 @@ import scala.collection.mutable.ArrayBuffer import scala.reflect.runtime.universe.{MethodSymbol, Type, typeOf} import code.api.util.ErrorMessages.InvalidConnectorResponseForMissingRequiredValues import code.api.util.APIUtil.fullBoxOrException -import com.openbankproject.commons.util.ApiVersion +import com.openbankproject.commons.util.{ApiVersion, ReflectUtils} import com.openbankproject.commons.util.ReflectUtils._ import com.openbankproject.commons.util.Functions.Implicits._ import net.liftweb.util.ThreadGlobal @@ -193,7 +193,8 @@ package object bankconnectors extends MdcLoggable { processObj match { case None => None - case Some(value) => { + + case Some(value) if ReflectUtils.isObpObject(value) => { val argNameToValues: Map[String, Any] = getConstructorArgs(value) //find from current object constructor args // orElse: if current object constructor args not found value, recursive search args @@ -206,6 +207,8 @@ package object bankconnectors extends MdcLoggable { .find(it => it.isDefined) } } + + case _ => None } } diff --git a/obp-api/src/test/scala/code/connector/RestConnector_vMar2019_FrozenTest.scala b/obp-api/src/test/scala/code/connector/RestConnector_vMar2019_FrozenTest.scala index c8939ad98..e52490a45 100644 --- a/obp-api/src/test/scala/code/connector/RestConnector_vMar2019_FrozenTest.scala +++ b/obp-api/src/test/scala/code/connector/RestConnector_vMar2019_FrozenTest.scala @@ -6,6 +6,7 @@ import java.net.URI import code.bankconnectors.rest.RestConnector_vMar2019 import code.connector.RestConnector_vMar2019_FrozenUtil.{connectorMethodNames, persistFilePath, typeNameToFieldsInfo} import com.openbankproject.commons.util.ReflectUtils +import net.liftweb.common.Logger import org.apache.commons.io.IOUtils import org.scalatest.matchers.{MatchResult, Matcher} import org.scalatest.{BeforeAndAfter, FlatSpec, Matchers, Tag} @@ -19,14 +20,19 @@ import scala.reflect.runtime.universe._ class RestConnector_vMar2019_FrozenTest extends FlatSpec with Matchers with BeforeAndAfter { private var connectorMethodNamesPersisted: List[String] = _ private var typeNameToFieldsInfoPersisted: Map[String, Map[String, String]] = _ + private val logger = Logger(classOf[RestConnector_vMar2019_FrozenTest]) before { - val in = new ObjectInputStream(new FileInputStream(persistFilePath)) + var in: ObjectInputStream = null try { + in = new ObjectInputStream(new FileInputStream(persistFilePath)) in.readUTF() connectorMethodNamesPersisted = in.readObject().asInstanceOf[List[String]] typeNameToFieldsInfoPersisted = in.readObject().asInstanceOf[Map[String, Map[String, String]]] - } finally { + } catch { + case e: Throwable => + logger.error("read frozen file fail.", e) + }finally { IOUtils.closeQuietly(in) } } @@ -85,7 +91,7 @@ object RestConnector_vMar2019_FrozenUtil { .filter(_.overrides.nonEmpty) .filter(_.paramLists.flatten.nonEmpty) .map(_.name.toString) - .toList + .toList.filterNot(_ == "dynamicEndpointProcess") // typeNameToFieldsInfo sturcture is: (typeFullName, Map(fieldName->fieldTypeName)) val typeNameToFieldsInfo: Map[String, Map[String, String]] = { From 1beb594b7ccad471985c82427ef373621b221192 Mon Sep 17 00:00:00 2001 From: shuang Date: Mon, 30 Mar 2020 15:34:05 +0800 Subject: [PATCH 34/35] feature/add_dynamic_endpoints_with_swagger: fix fail unit test of DynamicEndpointsTest. --- .../scala/code/api/v4_0_0/APIMethods400.scala | 1 + .../api/v4_0_0/DynamicendPointsTest.scala | 32 ++++++++++++++++--- 2 files changed, 29 insertions(+), 4 deletions(-) diff --git a/obp-api/src/main/scala/code/api/v4_0_0/APIMethods400.scala b/obp-api/src/main/scala/code/api/v4_0_0/APIMethods400.scala index 37119b0a7..55d2320a4 100644 --- a/obp-api/src/main/scala/code/api/v4_0_0/APIMethods400.scala +++ b/obp-api/src/main/scala/code/api/v4_0_0/APIMethods400.scala @@ -2789,6 +2789,7 @@ trait APIMethods400 { List( $UserNotLoggedIn, UserHasMissingRoles, + DynamicEndpointExists, InvalidJsonFormat, UnknownError ), diff --git a/obp-api/src/test/scala/code/api/v4_0_0/DynamicendPointsTest.scala b/obp-api/src/test/scala/code/api/v4_0_0/DynamicendPointsTest.scala index 97bbc6e08..5af5cdb82 100644 --- a/obp-api/src/test/scala/code/api/v4_0_0/DynamicendPointsTest.scala +++ b/obp-api/src/test/scala/code/api/v4_0_0/DynamicendPointsTest.scala @@ -2,13 +2,14 @@ package code.api.v4_0_0 import code.api.util.APIUtil.OAuth._ import code.api.util.ApiRole._ -import code.api.util.ErrorMessages.{UserHasMissingRoles, UserNotLoggedIn} +import code.api.util.ErrorMessages.{DynamicEndpointExists, UserHasMissingRoles, UserNotLoggedIn} import code.api.util.ExampleValue import code.api.v4_0_0.OBPAPI4_0_0.Implementations4_0_0 import code.entitlement.Entitlement import com.github.dwickern.macros.NameOf.nameOf import com.openbankproject.commons.model.ErrorMessage import com.openbankproject.commons.util.ApiVersion +import net.liftweb.json.JsonAST.JField import net.liftweb.json.Serialization.write import org.scalatest.Tag @@ -116,10 +117,22 @@ class DynamicEndpointsTest extends V400ServerSetup { Entitlement.entitlement.vend.addEntitlement("", resourceUser1.userId, CanGetDynamicEndpoints.toString) Entitlement.entitlement.vend.addEntitlement("", resourceUser1.userId, CanCreateDynamicEndpoint.toString) - val responseWithRole = makePostRequest(request, write(postDynamicEndpointRequestBodyExample)) + + val newSwagger = postDynamicEndpointRequestBodyExample.transformField { + case JField(name, value) if name.startsWith("/") => JField(s"$name/abc", value) + } + + val responseWithRole = makePostRequest(request, write(newSwagger)) Then("We should get a 201") responseWithRole.code should equal(201) + + val duplicatedRequest = makePostRequest(request, write(newSwagger)) + Then("We should get a 400") + duplicatedRequest.code should equal(400) + duplicatedRequest.body.extract[ErrorMessage].message.toString contains (DynamicEndpointExists) should be (true) + + val request400 = (v4_0_0_Request / "management" / "dynamic-endpoints").GET<@ (user1) val response400 = makeGetRequest(request400) response400.code should be (200) @@ -167,11 +180,22 @@ class DynamicEndpointsTest extends V400ServerSetup { Then("We grant the role to the user1") Entitlement.entitlement.vend.addEntitlement("", resourceUser1.userId, CanGetDynamicEndpoint.toString) Entitlement.entitlement.vend.addEntitlement("", resourceUser1.userId, CanCreateDynamicEndpoint.toString) - - val responseWithRole = makePostRequest(request, write(postDynamicEndpointRequestBodyExample)) + + val newSwagger = postDynamicEndpointRequestBodyExample.transformField { + case JField(name, value) if name.startsWith("/") => JField(s"$name/def", value) + } + + val responseWithRole = makePostRequest(request, write(newSwagger)) Then("We should get a 201") responseWithRole.code should equal(201) + + val duplicatedRequest = makePostRequest(request, write(newSwagger)) + Then("We should get a 400") + duplicatedRequest.code should equal(400) + duplicatedRequest.body.extract[ErrorMessage].message.toString contains (DynamicEndpointExists) should be (true) + + val id = responseWithRole.body.\\("dynamic_endpoint_id").values.get("dynamic_endpoint_id").head.toString val request400 = (v4_0_0_Request / "management" / "dynamic-endpoints" /id).GET<@ (user1) From 7dc4d31aedb0f6d8ff5a60a5f2aac5c190615998 Mon Sep 17 00:00:00 2001 From: hongwei Date: Mon, 30 Mar 2020 09:57:51 +0200 Subject: [PATCH 35/35] added the test for deleteDynamicEndpoint --- .../scala/code/api/v4_0_0/APIMethods400.scala | 2 +- .../api/v4_0_0/DynamicendPointsTest.scala | 86 +++++++++++++++++-- 2 files changed, 80 insertions(+), 8 deletions(-) diff --git a/obp-api/src/main/scala/code/api/v4_0_0/APIMethods400.scala b/obp-api/src/main/scala/code/api/v4_0_0/APIMethods400.scala index 55d2320a4..a077eb3f9 100644 --- a/obp-api/src/main/scala/code/api/v4_0_0/APIMethods400.scala +++ b/obp-api/src/main/scala/code/api/v4_0_0/APIMethods400.scala @@ -2931,7 +2931,7 @@ trait APIMethods400 { for { deleted <- NewStyle.function.deleteDynamicEndpoint(dynamicEndpointId, cc.callContext) } yield { - (deleted, HttpCode.`200`(cc.callContext)) + (deleted, HttpCode.`204`(cc.callContext)) } } } diff --git a/obp-api/src/test/scala/code/api/v4_0_0/DynamicendPointsTest.scala b/obp-api/src/test/scala/code/api/v4_0_0/DynamicendPointsTest.scala index 5af5cdb82..a6d9cc83f 100644 --- a/obp-api/src/test/scala/code/api/v4_0_0/DynamicendPointsTest.scala +++ b/obp-api/src/test/scala/code/api/v4_0_0/DynamicendPointsTest.scala @@ -25,6 +25,7 @@ class DynamicEndpointsTest extends V400ServerSetup { object ApiEndpoint1 extends Tag(nameOf(Implementations4_0_0.createDynamicEndpoint)) object ApiEndpoint2 extends Tag(nameOf(Implementations4_0_0.getDynamicEndpoints)) object ApiEndpoint3 extends Tag(nameOf(Implementations4_0_0.getDynamicEndpoint)) + object ApiEndpoint4 extends Tag(nameOf(Implementations4_0_0.deleteDynamicEndpoint)) feature(s"test $ApiEndpoint1 version $VersionOfApi - Unauthorized access") { @@ -80,7 +81,7 @@ class DynamicEndpointsTest extends V400ServerSetup { } feature(s"test $ApiEndpoint2 version $VersionOfApi - Unauthorized access") { - scenario("We will call the endpoint without user credentials", ApiEndpoint1, VersionOfApi) { + 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-endpoints").GET val response400 = makeGetRequest(request400) @@ -91,7 +92,7 @@ class DynamicEndpointsTest extends V400ServerSetup { } feature(s"test $ApiEndpoint2 version $VersionOfApi - authorized access- missing role") { - scenario("We will call the endpoint with user credentials", ApiEndpoint1, VersionOfApi) { + scenario("We will call the endpoint with user credentials", ApiEndpoint2, VersionOfApi) { When("We make a request v4.0.0") val request = (v4_0_0_Request / "management" / "dynamic-endpoints").GET<@ (user1) val response = makeGetRequest(request) @@ -102,7 +103,7 @@ class DynamicEndpointsTest extends V400ServerSetup { } feature(s"test $ApiEndpoint2 version $VersionOfApi - authorized access - with role - should be success!") { - scenario("We will call the endpoint with user credentials", ApiEndpoint1, VersionOfApi) { + scenario("We will call the endpoint with user credentials", ApiEndpoint2, VersionOfApi) { When("We make a request v4.0.0") val postDynamicEndpointRequestBodyExample = ExampleValue.dynamicEndpointRequestBodyExample @@ -144,7 +145,7 @@ class DynamicEndpointsTest extends V400ServerSetup { } feature(s"test $ApiEndpoint3 version $VersionOfApi - Unauthorized access") { - scenario("We will call the endpoint without user credentials", ApiEndpoint1, VersionOfApi) { + 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-endpoints"/ "some-id").GET val response400 = makeGetRequest(request400) @@ -155,7 +156,7 @@ class DynamicEndpointsTest extends V400ServerSetup { } feature(s"test $ApiEndpoint3 version $VersionOfApi - authorized access- missing role") { - scenario("We will call the endpoint with user credentials", ApiEndpoint1, VersionOfApi) { + scenario("We will call the endpoint with user credentials", ApiEndpoint3, VersionOfApi) { When("We make a request v4.0.0") val request = (v4_0_0_Request / "management" / "dynamic-endpoints" /"some-id").GET<@ (user1) val response = makeGetRequest(request) @@ -166,7 +167,7 @@ class DynamicEndpointsTest extends V400ServerSetup { } feature(s"test $ApiEndpoint3 version $VersionOfApi - authorized access - with role - should be success!") { - scenario("We will call the endpoint with user credentials", ApiEndpoint1, VersionOfApi) { + scenario("We will call the endpoint with user credentials", ApiEndpoint3, VersionOfApi) { When("We make a request v4.0.0") val postDynamicEndpointRequestBodyExample = ExampleValue.dynamicEndpointRequestBodyExample @@ -208,5 +209,76 @@ class DynamicEndpointsTest extends V400ServerSetup { response400.body.toString contains("Example Company") should be (true) } - } + } + + feature(s"test $ApiEndpoint4 version $VersionOfApi - Unauthorized access") { + scenario("We will call the endpoint without user credentials", ApiEndpoint4, VersionOfApi) { + When("We make a request v4.0.0") + val request400 = (v4_0_0_Request / "management" / "dynamic-endpoints"/ "some-id").DELETE + val response400 = makeDeleteRequest(request400) + Then("We should get a 400") + response400.code should equal(400) + response400.body.extract[ErrorMessage].message should equal(UserNotLoggedIn) + } + } + + feature(s"test $ApiEndpoint4 version $VersionOfApi - authorized access- missing role") { + scenario("We will call the endpoint with user credentials", ApiEndpoint4, VersionOfApi) { + When("We make a request v4.0.0") + val request = (v4_0_0_Request / "management" / "dynamic-endpoints" /"some-id").DELETE<@ (user1) + val response = makeDeleteRequest(request) + Then("We should get a 400") + response.code should equal(403) + response.body.extract[ErrorMessage].message.toString contains (UserHasMissingRoles) should be (true) + } + } + + feature(s"test $ApiEndpoint4 version $VersionOfApi - authorized access - with role - should be success!") { + scenario("We will call the endpoint with user credentials", ApiEndpoint4, VersionOfApi) { + When("We make a request v4.0.0") + val postDynamicEndpointRequestBodyExample = ExampleValue.dynamicEndpointRequestBodyExample + + When("We make a request v4.0.0") + val request = (v4_0_0_Request / "management" / "dynamic-endpoints").POST<@ (user1) + val response = makePostRequest(request, write(postDynamicEndpointRequestBodyExample)) + Then("We should get a 403") + response.code should equal(403) + response.body.extract[ErrorMessage].message.toString contains (UserHasMissingRoles) should be (true) + + Then("We grant the role to the user1") + Entitlement.entitlement.vend.addEntitlement("", resourceUser1.userId, CanGetDynamicEndpoint.toString) + Entitlement.entitlement.vend.addEntitlement("", resourceUser1.userId, CanCreateDynamicEndpoint.toString) + Entitlement.entitlement.vend.addEntitlement("", resourceUser1.userId, CanDeleteDynamicEndpoint.toString) + + val newSwagger = postDynamicEndpointRequestBodyExample.transformField { + case JField(name, value) if name.startsWith("/") => JField(s"$name/def2", value) + } + + val responseWithRole = makePostRequest(request, write(newSwagger)) + Then("We should get a 201") + responseWithRole.code should equal(201) + + + val id = responseWithRole.body.\\("dynamic_endpoint_id").values.get("dynamic_endpoint_id").head.toString + + val request400 = (v4_0_0_Request / "management" / "dynamic-endpoints" /id).GET<@ (user1) + val response400 = makeGetRequest(request400) + response400.code should be (200) + response400.body.toString contains("dynamic_endpoint_id") should be (true) + response400.body.toString contains("swagger_string") should be (true) + response400.body.toString contains("Example Title") should be (true) + response400.body.toString contains("Example Description") should be (true) + response400.body.toString contains("Example Company") should be (true) + + + val requestDelete = (v4_0_0_Request / "management" / "dynamic-endpoints" /id).DELETE<@ (user1) + val responseDelete = makeDeleteRequest(requestDelete) + responseDelete.code should be (204) + + val responseGetAgain = makeGetRequest(request400) + responseGetAgain.code should be (404) + + + } + } }