feature/add_dynamic_endpoints_with_swagger: process delete DynamicEndpoint and related roles.

This commit is contained in:
shuang 2020-03-30 09:06:04 +08:00
parent ec51a12053
commit 9ac32017c0
9 changed files with 159 additions and 18 deletions

View File

@ -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)

View File

@ -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 {

View File

@ -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)))
}
}
}

View File

@ -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:

View File

@ -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

View File

@ -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

View File

@ -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))
}
}

View File

@ -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]]
)
}

View File

@ -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)
}