Merge branch 'feature/dynamic_code_sandbox' into develop-Simon

# Conflicts:
#	obp-api/src/main/resources/props/sample.props.template
#	obp-api/src/main/scala/code/api/util/APIUtil.scala
#	obp-api/src/main/scala/code/api/util/ErrorMessages.scala
#	obp-api/src/main/scala/code/api/v4_0_0/APIMethods400.scala
This commit is contained in:
hongwei 2022-01-18 10:12:07 +01:00
commit 3407a22a62
34 changed files with 1438 additions and 241 deletions

View File

@ -8,7 +8,7 @@
<groupId>com.tesobe</groupId>
<artifactId>obp-parent</artifactId>
<relativePath>../pom.xml</relativePath>
<version>1.10.0</version>
<version>1.10.1</version>
</parent>
<artifactId>obp-api</artifactId>
<packaging>war</packaging>
@ -479,7 +479,7 @@
<!--macro cache-->
<dependency>
<groupId>com.github.oldbig</groupId>
<groupId>com.github.OpenBankProject</groupId>
<artifactId>macmemo</artifactId>
<version>0.6-OBP-SNAPSHOT</version>
</dependency>

View File

@ -1113,4 +1113,11 @@ personal_data_collection_consent_country_waiver_list = Austria, Belgium, Bulgari
# Local identity provider url
# it defaults to the hostname props value
# local_identity_provider=this is the hostname of the local obp server including scheme
# local_identity_provider=this is the hostname of the local obp server including scheme
# enable dynamic code sandbox, default is false, this will make sandbox works for code running in Future, will make performance lower than disable
dynamic_code_sandbox_enable=false
# enable dynamic code compile validation, default is false, if set it to true, it will validate all the dynamic method body when you create/update any
# dynamic scala method. Note, it only check all the obp code dependents for all the method in scala code.
dynamic_code_compile_validate_enable=false

View File

@ -217,9 +217,15 @@ class Boot extends MdcLoggable {
logger.info("external props folder: " + propsPath)
TimeZone.setDefault(TimeZone.getTimeZone("UTC"))
logger.info("Current Project TimeZone: " + TimeZone.getDefault)
// set dynamic_code_sandbox_enable to System.properties, so com.openbankproject.commons.ExecutionContext can read this value
APIUtil.getPropsValue("dynamic_code_sandbox_enable")
.foreach(it => System.setProperty("dynamic_code_sandbox_enable", it))
}
def boot {
// set up the way to connect to the relational DB we're using (ok if other connector than relational)
if (!DB.jndiJdbcConnAvailable_?) {

View File

@ -113,15 +113,18 @@ object ApiVersionHolder {
* This is helpful if you want send back given error message and status code
* @param jsonResponse
*/
case class JsonResponseException(jsonResponse: JsonResponse) extends RuntimeException with NoStackTrace {
case class JsonResponseException(jsonResponse: JsonResponse) extends RuntimeException with NoStackTrace
object JsonResponseException {
/**
*
* @param errorMsg error message
* @param errorCode response error code and status code
* @param correlationId this value can be got from callContext
*/
def this(errorMsg: String, errorCode: Int, correlationId: String) =
this(createErrorJsonResponse(errorMsg: String, errorCode: Int, correlationId: String))
def apply(errorMsg: String, errorCode: Int, correlationId: String):JsonResponseException = {
JsonResponseException(createErrorJsonResponse(errorMsg: String, errorCode: Int, correlationId: String))
}
}
trait OBPRestHelper extends RestHelper with MdcLoggable {

View File

@ -4281,6 +4281,7 @@ object SwaggerDefinitionsJSON {
val jsonConnectorMethodMethodBody = JsonConnectorMethodMethodBody(connectorMethodBodyExample.value)
val jsonDynamicResourceDoc = JsonDynamicResourceDoc(
bankId = Some(bankIdExample.value),
dynamicResourceDocId = Some(dynamicResourceDocIdExample.value),
methodBody = dynamicResourceDocMethodBodyExample.value,
partialFunctionName = dynamicResourceDocPartialFunctionNameExample.value,
@ -4296,6 +4297,7 @@ object SwaggerDefinitionsJSON {
)
val jsonDynamicMessageDoc = JsonDynamicMessageDoc(
bankId = Some(bankIdExample.value),
dynamicMessageDocId = Some(dynamicMessageDocIdExample.value),
process = processExample.value,
messageFormat = messageFormatExample.value,
@ -4494,6 +4496,8 @@ object SwaggerDefinitionsJSON {
description = descriptionExample.value,
meta = metaJson,
)
val requestRootJsonClass = dynamic.practise.PractiseEndpoint.RequestRootJsonClass(name = nameExample.value, age=ageExample.value.toLong, Nil)
val entitlementJsonV400 = EntitlementJsonV400(
entitlement_id = entitlementIdExample.value,

View File

@ -33,7 +33,6 @@ import java.nio.charset.Charset
import java.text.{ParsePosition, SimpleDateFormat}
import java.util.concurrent.ConcurrentHashMap
import java.util.{Calendar, Date, UUID}
import code.UserRefreshes.UserRefreshes
import code.accountholders.AccountHolders
import code.api.Constant._
@ -45,6 +44,7 @@ import code.api.util.APIUtil.ResourceDoc.{findPathVariableNames, isPathVariable}
import code.api.util.ApiRole.{canCreateProduct, canCreateProductAtAnyBank}
import code.api.util.ApiTag.{ResourceDocTag, apiTagBank, apiTagNewStyle}
import code.api.util.Glossary.GlossaryItem
import code.api.util.RateLimitingJson.CallLimit
import code.api.v1_2.ErrorMessage
import code.api.v2_0_0.CreateEntitlementJSON
import code.api.v4_0_0.dynamic.{DynamicEndpointHelper, DynamicEndpoints, DynamicEntityHelper}
@ -95,6 +95,17 @@ import scala.collection.JavaConverters._
import scala.collection.immutable.{List, Nil}
import scala.collection.mutable
import scala.collection.mutable.{ArrayBuffer, ListBuffer}
import com.openbankproject.commons.ExecutionContext.Implicits.global
import com.openbankproject.commons.util.{ApiVersion, Functions, JsonAble, ReflectUtils, ScannedApiVersion}
import com.openbankproject.commons.util.Functions.Implicits._
import com.openbankproject.commons.util.Functions.Memo
import javassist.{ClassPool, LoaderClassPath}
import javassist.expr.{ExprEditor, MethodCall}
import org.apache.commons.io.IOUtils
import org.apache.commons.lang3.StringUtils
import java.security.AccessControlException
import scala.collection.mutable
import scala.concurrent.Future
import scala.io.BufferedSource
import scala.util.Either
@ -2478,7 +2489,11 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{
case _ => laf.abort
}
scf.onFailure {
case e: Throwable => laf.fail(Failure(e.getMessage(), Full(e), Empty))
case e: AccessControlException =>
laf.fail(Failure(s"$DynamicResourceDocMethodPermission No permission of: ${e.getPermission.toString}", Full(e), Empty))
case e: Throwable =>
laf.fail(Failure(e.getMessage(), Full(e), Empty))
}
laf
}
@ -3751,40 +3766,61 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{
pool.appendClassPath(new LoaderClassPath(Thread.currentThread.getContextClassLoader))
pool
}
private val memoClassPool = new Memo[ClassLoader, ClassPool]
private def getClassPool(classLoader: ClassLoader) = memoClassPool.memoize(classLoader){
val cp = ClassPool.getDefault
cp.appendClassPath(new LoaderClassPath(classLoader))
cp
}
/**
* according class name, method name and method's signature to get all dependent methods
*/
def getDependentMethods(className: String, methodName:String, signature: String): List[(String, String, String)] = {
val methods = ListBuffer[(String, String, String)]()
val method = cp.get(className).getMethod(methodName, signature)
method.instrument(new ExprEditor() {
@throws[CannotCompileException]
override def edit(m: MethodCall): Unit = {
val tuple = (m.getClassName, m.getMethodName, m.getSignature)
methods += tuple
}
})
methods.toList
}
/**
* get all dependent connector method names for an object
* @param endpoint can be OBPEndpoint or other PartialFunction
* @return a list of connector method name
*/
def getDependentConnectorMethods(endpoint: PartialFunction[_, _]) = {
def getDependentConnectorMethods(endpoint: PartialFunction[_, _]): List[String] = {
val connectorTypeName = classOf[Connector].getName
val endpointClassName = endpoint.getClass.getName
// not analyze dynamic code
// if(endpointClassName.startsWith("__wrapper$")) {
// return Nil
// }
val classPool = this.getClassPool(endpoint.getClass.getClassLoader)
def getObpTrace(className: String, methodName: String, signature: String, exclude: List[(String, String, String)] = Nil): List[(String, String, String)] =
memo.memoize((className, methodName, signature)) {
val methods = ListBuffer[(String, String, String)]()
val method = cp.get(className).getMethod(methodName, signature)
method.instrument(new ExprEditor() {
@throws[CannotCompileException]
override def edit(m: MethodCall): Unit = {
val tuple = (m.getClassName, m.getMethodName, m.getSignature)
if (ReflectUtils.isObpClass(m.getClassName)) {
methods += tuple
}
}
})
// List:: className->methodName->signature
val methods = getDependentMethods(className, methodName, signature)
val list = methods.distinct.toList.filterNot(exclude.contains)
val list = methods.distinct.filter(it => ReflectUtils.isObpClass(it._1)).filterNot(exclude.contains)
list.collect {
case x@(clazzName, _, _) if clazzName == connectorTypeName => x :: Nil
case (clazzName, mName, mSignature) =>
case (clazzName, mName, mSignature) if !clazzName.startsWith("__wrapper$") =>
getObpTrace(clazzName, mName, mSignature, list ::: exclude)
}.flatten.distinct
}
val endpointClassName = endpoint.getClass.getName
// list of connector method name
val connectorMethods: Array[String] = for {
method <- cp.get(endpoint.getClass.getName).getDeclaredMethods
method <- classPool.get(endpointClassName).getDeclaredMethods
(clazzName, methodName, _) <- getObpTrace(endpointClassName, method.getName, method.getSignature)
if clazzName == connectorTypeName && !methodName.contains("$default$")
} yield methodName

View File

@ -752,9 +752,27 @@ object ApiRole {
case class CanDeleteDynamicResourceDoc(requiresBankId: Boolean = false) extends ApiRole
lazy val canDeleteDynamicResourceDoc = CanDeleteDynamicResourceDoc()
case class CanCreateBankLevelDynamicResourceDoc(requiresBankId: Boolean = true) extends ApiRole
lazy val canCreateBankLevelDynamicResourceDoc = CanCreateBankLevelDynamicResourceDoc()
case class CanUpdateBankLevelDynamicResourceDoc(requiresBankId: Boolean = true) extends ApiRole
lazy val canUpdateBankLevelDynamicResourceDoc = CanUpdateBankLevelDynamicResourceDoc()
case class CanGetBankLevelDynamicResourceDoc(requiresBankId: Boolean = true) extends ApiRole
lazy val canGetBankLevelDynamicResourceDoc = CanGetBankLevelDynamicResourceDoc()
case class CanGetAllBankLevelDynamicResourceDocs(requiresBankId: Boolean = true) extends ApiRole
lazy val canGetAllBankLevelDynamicResourceDocs = CanGetAllBankLevelDynamicResourceDocs()
case class CanDeleteBankLevelDynamicResourceDoc(requiresBankId: Boolean = true) extends ApiRole
lazy val canDeleteBankLevelDynamicResourceDoc = CanDeleteBankLevelDynamicResourceDoc()
case class CanCreateDynamicMessageDoc(requiresBankId: Boolean = false) extends ApiRole
lazy val canCreateDynamicMessageDoc = CanCreateDynamicMessageDoc()
case class CanCreateBankLevelDynamicMessageDoc(requiresBankId: Boolean = true) extends ApiRole
lazy val canCreateBankLevelDynamicMessageDoc = CanCreateBankLevelDynamicMessageDoc()
case class CanUpdateDynamicMessageDoc(requiresBankId: Boolean = false) extends ApiRole
lazy val canUpdateDynamicMessageDoc = CanUpdateDynamicMessageDoc()
@ -762,12 +780,18 @@ object ApiRole {
case class CanGetDynamicMessageDoc(requiresBankId: Boolean = false) extends ApiRole
lazy val canGetDynamicMessageDoc = CanGetDynamicMessageDoc()
case class CanGetBankLevelDynamicMessageDoc(requiresBankId: Boolean = true) extends ApiRole
lazy val canGetBankLevelDynamicMessageDoc = CanGetBankLevelDynamicMessageDoc()
case class CanGetAllDynamicMessageDocs(requiresBankId: Boolean = false) extends ApiRole
lazy val canGetAllDynamicMessageDocs = CanGetAllDynamicMessageDocs()
case class CanDeleteDynamicMessageDoc(requiresBankId: Boolean = false) extends ApiRole
lazy val canDeleteDynamicMessageDoc = CanDeleteDynamicMessageDoc()
case class CanDeleteBankLevelDynamicMessageDoc(requiresBankId: Boolean = true) extends ApiRole
lazy val canDeleteBankLevelDynamicMessageDoc = CanDeleteBankLevelDynamicMessageDoc()
case class CanCreateEndpointMapping(requiresBankId: Boolean = false) extends ApiRole
lazy val canCreateEndpointMapping = CanCreateEndpointMapping()

View File

@ -23,6 +23,7 @@ object CustomJsonFormats {
val formats: Formats = JsonSerializers.commonFormats + PrimaryDataBodySerializer + ToStringDeSerializer + TupleSerializer
val losslessFormats: Formats = net.liftweb.json.DefaultFormats.lossless ++ JsonSerializers.serializers
val emptyHintFormats = DefaultFormats.withHints(ShortTypeHints(List())) ++ JsonSerializers.serializers

View File

@ -1,16 +1,43 @@
package code.api.util
import com.openbankproject.commons.util.JsonUtils
import code.api.JsonResponseException
import code.api.util.APIUtil.ResourceDoc
import code.api.util.ErrorMessages.DynamicResourceDocMethodDependency
import code.api.util.NewStyle.HttpCode
import code.api.v4_0_0.JSONFactory400
import code.api.v4_0_0.dynamic.{CompiledObjects, DynamicCompileEndpoint}
import code.api.v4_0_0.dynamic.practise.PractiseEndpoint
import com.openbankproject.commons.ExecutionContext
import com.openbankproject.commons.model.BankId
import com.openbankproject.commons.util.Functions.Memo
import com.openbankproject.commons.util.{JsonUtils, ReflectUtils}
import javassist.{ClassPool, LoaderClassPath}
import net.liftweb.common.{Box, Failure, Full}
import net.liftweb.json.{JObject, JValue, prettyRender}
import net.liftweb.http.JsonResponse
import net.liftweb.json.{ JValue, prettyRender}
import org.apache.commons.lang3.StringUtils
import java.lang.reflect.ReflectPermission
import java.net.NetPermission
import java.security.{AccessControlContext, AccessController, CodeSource, Permission, PermissionCollection, Permissions, Policy, PrivilegedAction, ProtectionDomain}
import java.util.PropertyPermission
import java.util.concurrent.ConcurrentHashMap
import scala.collection.immutable.List
import scala.collection.mutable.ListBuffer
import scala.reflect.runtime.universe
import scala.reflect.runtime.universe.runtimeMirror
import scala.runtime.NonLocalReturnControl
import scala.tools.reflect.{ToolBox, ToolBoxError}
object DynamicUtil {
val toolBox: ToolBox[universe.type] = runtimeMirror(getClass.getClassLoader).mkToolBox()
private val memoClassPool = new Memo[ClassLoader, ClassPool]
private def getClassPool(classLoader: ClassLoader) = memoClassPool.memoize(classLoader){
val cp = ClassPool.getDefault
cp.appendClassPath(new LoaderClassPath(classLoader))
cp
}
// code -> dynamic method function
// the same code should always be compiled once, so here cache them
@ -115,6 +142,81 @@ object DynamicUtil {
}
}
def getDynamicCodeDependentMethods(clazz: Class[_], predicate: String => Boolean = _ => true): List[(String, String, String)] = {
val className = clazz.getTypeName
val listBuffer = new ListBuffer[(String, String, String)]()
for {
method <- getClassPool(clazz.getClassLoader).get(className).getDeclaredMethods.toList
if predicate(method.getName)
ternary @ (typeName, methodName, signature) <- APIUtil.getDependentMethods(className, method.getName, method.getSignature)
} yield {
// if method is also dynamic compile code, extract it's dependent method
if(className.startsWith(typeName) && methodName.startsWith(clazz.getPackage.getName+ "$")) {
listBuffer.appendAll(APIUtil.getDependentMethods(typeName, methodName, signature))
} else {
listBuffer.append(ternary)
}
}
listBuffer.distinct.toList
}
trait Sandbox {
@throws[Exception]
def runInSandbox[R](action: => R): R
}
object Sandbox {
// initialize SecurityManager if not initialized
if (System.getSecurityManager == null) {
Policy.setPolicy(new Policy() {
override def getPermissions(codeSource: CodeSource): PermissionCollection = {
for (element <- Thread.currentThread.getStackTrace) {
if ("sun.rmi.server.LoaderHandler" == element.getClassName && "loadClass" == element.getMethodName)
return new Permissions
}
super.getPermissions(codeSource)
}
override def implies(domain: ProtectionDomain, permission: Permission) = true
})
System.setSecurityManager(new SecurityManager)
}
def createSandbox(permissionList: List[Permission]): Sandbox = {
val accessControlContext: AccessControlContext = {
val permissions = new Permissions()
permissionList.foreach(permissions.add)
val protectionDomain = new ProtectionDomain(null, permissions)
new AccessControlContext(Array(protectionDomain))
}
new Sandbox {
@throws[Exception]
def runInSandbox[R](action: => R): R = try {
val privilegedAction: PrivilegedAction[R] = () => action
AccessController.doPrivileged(privilegedAction, accessControlContext)
} catch {
case e: NonLocalReturnControl[Full[JsonResponse]] if e.value.isInstanceOf[Full[JsonResponse]] =>
throw JsonResponseException(e.value.orNull)
case e: NonLocalReturnControl[JsonResponse] if e.value.isInstanceOf[JsonResponse] =>
throw JsonResponseException(e.value)
}
}
}
private val memoSandbox = new Memo[String, Sandbox]
/**
* this method will call create Sandbox underneath, but will have default permissions and bankId permission and cache.
*/
def sandbox(bankId: String): Sandbox = memoSandbox.memoize(bankId) {
Sandbox.createSandbox(BankId.permission(bankId) :: Validation.allowedRuntimePermissions)
}
}
/**
* common import statements those are used by compiler
*/
@ -163,5 +265,99 @@ object DynamicUtil {
|import scala.concurrent.duration._
|import scala.concurrent.{Await, Future}
|import com.openbankproject.commons.dto._
|import code.api.util.APIUtil.ResourceDoc
|import code.api.util.DynamicUtil.Sandbox
|import code.api.util.NewStyle.HttpCode
|import code.api.util._
|import code.api.v4_0_0.JSONFactory400
|import code.api.v4_0_0.dynamic.{CompiledObjects, DynamicCompileEndpoint}
|import code.api.v4_0_0.dynamic.practise.PractiseEndpoint
|import com.openbankproject.commons.ExecutionContext
|import com.openbankproject.commons.model.BankId
|import com.openbankproject.commons.util.{JsonUtils, ReflectUtils}
|import net.liftweb.common.{Box, Full}
|import org.apache.commons.lang3.StringUtils
|
|import java.io.File
|import java.security.{AccessControlException, Permission}
|import java.util.PropertyPermission
|import scala.collection.immutable.List
|import scala.io.Source
|""".stripMargin
object Validation {
val dynamicCodeSandboxPermissions = APIUtil.getPropsValue("dynamic_code_sandbox_permissions", "[]").trim
val scalaCodePermissioins = "List[java.security.Permission]"+dynamicCodeSandboxPermissions.replaceFirst("\\[","(").dropRight(1)+")"
val permissions:Box[List[java.security.Permission]] = DynamicUtil.compileScalaCode(scalaCodePermissioins)
// all Permissions put at here
// Here is the Java Permission document, please extend these permissions carefully.
// https://docs.oracle.com/javase/8/docs/technotes/guides/security/spec/security-spec.doc3.html#17001
// If you are not familiar with the permissions, we provide the clear error messages for the missing permissions in the log.
// eg1 scala test level : and have a look at the scala test for `createSandbox` method, you can see how to add permissions there too.
// eg2 api level: "OBP-40047: DynamicResourceDoc method have no enough permissions. No permission of: (\"java.io.FilePermission\" \"stop-words-en.txt\" \"write\")"
// --> you can extends following permission: new java.net.SocketPermission("ir.dcs.gla.ac.uk:80", "connect,resolve"),
// NOTE: These permissions are only checked during runtime, not the compilation period.
val allowedRuntimePermissions = permissions.openOrThrowException("Can not compile the props `dynamic_code_sandbox_permissions` to permissions")
val dependenciesString = APIUtil.getPropsValue("dynamic_code_compile_validate_dependencies", "[]").trim
val scalaCodeDependencies = s"${DynamicUtil.importStatements}"+dependenciesString.replaceFirst("\\[","Map(").dropRight(1) +").mapValues(v => StringUtils.split(v, ',').map(_.trim).toSet)"
val dependenciesBox: Box[Map[String, Set[String]]] = DynamicUtil.compileScalaCode(scalaCodeDependencies)
/**
* Compilation OBP Dependencies Guard, only checked the OBP methods, not scala/Java libraies(are checked during the runtime.).
*
* allowedCompilationMethods -->
* The following methods will be checked when you call the `Create Dynamic ResourceDoc/MessageDoc` endpoints.
* You can control all the OBP methods here.
*/
// all allowed methods put at here, typeName -> methods
val allowedCompilationMethods: Map[String, Set[String]] = dependenciesBox.openOrThrowException("Can not compile the props `dynamic_code_compile_validate_dependencies` to Map")
//Do not touch this Set, try to use the `allowedPermissions` and `allowedMethods` to control the sandbox
val restrictedTypes = Set(
"scala.reflect.runtime.",
"java.lang.reflect.",
"scala.concurrent.ExecutionContext"
)
private def isRestrictedType(typeName: String) = ReflectUtils.isObpClass(typeName) || restrictedTypes.exists(typeName.startsWith)
/**
* validate dependencies, (className, methodName, signature)
*
* Here only validate the restricted types(isObpClass + val restrictedTypes), not all scala/java types.
*/
private def validateDependency(dependentMethods: List[(String, String, String)]) = {
val notAllowedDependentMethods = dependentMethods collect {
case (typeName, method, _)
if isRestrictedType(typeName) &&
!allowedCompilationMethods.get(typeName).exists(set => set.contains(method) || set.contains("*")) &&
!allowedCompilationMethods.exists { it =>
val (tpName, allowedMethods) = it
tpName.endsWith("*") &&
typeName.startsWith(StringUtils.substringBeforeLast(tpName, "*")) &&
(allowedMethods.contains(method) || allowedMethods.contains("*"))
}
=>
s"$typeName.$method"
}
// change to JsonResponseException
if(notAllowedDependentMethods.nonEmpty) {
val illegalDependency = notAllowedDependentMethods.mkString("[", ", ", "]")
throw JsonResponseException(s"$DynamicResourceDocMethodDependency $illegalDependency", 400, "none")
}
}
def validateDependency(obj: AnyRef): Unit = {
if(APIUtil.getPropsAsBoolValue("dynamic_code_compile_validate_enable",false)){
val dependentMethods: List[(String, String, String)] = DynamicUtil.getDynamicCodeDependentMethods(obj.getClass)
validateDependency(dependentMethods)
} else{ // If false, nothing to do here.
;
}
}
}
}

View File

@ -544,7 +544,11 @@ object ErrorMessages {
val DynamicMessageDocNotFound = "OBP-40043: DynamicMessageDoc not found, please specify valid DYNAMIC_MESSAGE_DOC_ID. "
val DynamicMessageDocDeleteError = "OBP-40044: DynamicMessageDoc can not be deleted. "
val DynamicCodeCompileFail = "OBP-40045: The code to do compile is illegal scala code, compilation failed. "
val InvalidOperationId = "OBP-40046: Invalid operation_id, please specify valid operation_id."
val DynamicResourceDocMethodDependency = "OBP-40046: DynamicResourceDoc method call forbidden methods. "
val DynamicResourceDocMethodPermission = "OBP-40047: DynamicResourceDoc method have no enough permissions. "
val InvalidOperationId = "OBP-40048: Invalid operation_id, please specify valid operation_id."
// Exceptions (OBP-50XXX)
val UnknownError = "OBP-50000: Unknown Error."
val FutureTimeoutException = "OBP-50001: Future Timeout Exception."

View File

@ -397,21 +397,23 @@ object ExampleValue {
lazy val connectorMethodIdExample = ConnectorField("ace0352a-9a0f-4bfa-b30b-9003aa467f51", "A string that MUST uniquely identify the connector method on this OBP instance, can be used in all cache. ")
glossaryItems += makeGlossaryItem("ConnectorMethod.connectorMethodId", connectorMethodIdExample)
lazy val dynamicResourceDocMethodBodyExample = ConnectorField("%20%20%20%20val%20Some(resourceDoc)%20%3D%20callContext.resourceDocument%0A%20%20%20%20" +
"val%20hasRequestBody%20%3D%20request.body.isDefined%0A%0A%20%20%20%20%2F%2F%20get%20Path%20Parameters%2C%20example%3A%0A%20%20%20" +
"%20%2F%2F%20if%20the%20requestUrl%20of%20resourceDoc%20is%20%2Fhello%2Fbanks%2FBANK_ID%2Fworld%0A%20%20%20%20%2F%2F%20the%20reque" +
"st%20path%20is%20%2Fhello%2Fbanks%2Fbank_x%2Fworld%0A%20%20%20%20%2F%2FpathParams.get(%22BANK_ID%22)%20will%20get%20Option(%22bank" +
"_x%22)%20value%0A%20%20%20%20val%20pathParams%20%3D%20getPathParams(callContext%2C%20request)%0A%20%20%20%20val%20myUserId%20%3D%2" +
"0pathParams(%22MY_USER_ID%22)%0A%0A%0A%20%20%20%20val%20requestEntity%20%3D%20request.json%20match%20%7B%0A%20%20%20%20%20%20case%" +
"20Full(zson)%20%3D%3E%0A%20%20%20%20%20%20%20%20try%20%7B%0A%20%20%20%20%20%20%20%20%20%20zson.extract%5BRequestRootJsonClass%5D%0" +
"A%20%20%20%20%20%20%20%20%7D%20catch%20%7B%0A%20%20%20%20%20%20%20%20%20%20case%20e%3A%20MappingException%20%3D%3E%0A%20%20%20%20%" +
"20%20%20%20%20%20%20%20return%20Full(errorJsonResponse(s%22%24InvalidJsonFormat%20%24%7Be.msg%7D%22))%0A%20%20%20%20%20%20%20%20%7" +
"D%0A%20%20%20%20%20%20case%20_%3A%20EmptyBox%20%3D%3E%0A%20%20%20%20%20%20%20%20return%20Full(errorJsonResponse(s%22%24InvalidRequ" +
"estPayload%20Current%20request%20has%20no%20payload%22))%0A%20%20%20%20%7D%0A%0A%0A%20%20%20%20%2F%2F%20please%20add%20business%20" +
"logic%20here%0A%20%20%20%20val%20responseBody%3AResponseRootJsonClass%20%3D%20ResponseRootJsonClass(s%22%24%7BmyUserId%7D_from_pat" +
"h%22%2C%20requestEntity.name%2C%20requestEntity.age%2C%20requestEntity.hobby)%0A%20%20%20%20Future.successful%20%7B%0A%20%20%20%20" +
"%20%20(responseBody%2C%20HttpCode.%60200%60(callContext.callContext))%0A%20%20%20%20%7D",
lazy val dynamicResourceDocMethodBodyExample = ConnectorField("%20%20%20%20val%20Some(resourceDoc)%20%3D%20callContext." +
"resourceDocument%0A%20%20%20%20val%20hasRequestBody%20%3D%20request.body.isDefined%0A%0A%20%20%20%20%2F%2F%20get%20" +
"Path%20Parameters%2C%20example%3A%0A%20%20%20%20%2F%2F%20if%20the%20requestUrl%20of%20resourceDoc%20is%20%2Fhello%2" +
"Fbanks%2FBANK_ID%2Fworld%0A%20%20%20%20%2F%2F%20the%20request%20path%20is%20%2Fhello%2Fbanks%2Fbank_x%2Fworld%0A%20" +
"%20%20%20%2F%2FpathParams.get(%22BANK_ID%22)%20will%20get%20Option(%22bank_x%22)%20value%0A%0A%20%20%20%20val%20my" +
"UserId%20%3D%20pathParams(%22MY_USER_ID%22)%0A%0A%0A%20%20%20%20val%20requestEntity%20%3D%20request.json%20match%20" +
"%7B%0A%20%20%20%20%20%20case%20Full(zson)%20%3D%3E%0A%20%20%20%20%20%20%20%20try%20%7B%0A%20%20%20%20%20%20%20%20%" +
"20%20zson.extract%5BRequestRootJsonClass%5D%0A%20%20%20%20%20%20%20%20%7D%20catch%20%7B%0A%20%20%20%20%20%20%20%20%" +
"20%20case%20e%3A%20MappingException%20%3D%3E%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20Full(errorJsonResponse(" +
"s%22%24InvalidJsonFormat%20%24%7Be.msg%7D%22))%0A%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20case%20_%3A%20Emp" +
"tyBox%20%3D%3E%0A%20%20%20%20%20%20%20%20return%20Full(errorJsonResponse(s%22%24InvalidRequestPayload%20Current%20" +
"request%20has%20no%20payload%22))%0A%20%20%20%20%7D%0A%0A%0A%20%20%20%20%2F%2F%20please%20add%20business%20logic%20" +
"here%0A%20%20%20%20val%20responseBody%3AResponseRootJsonClass%20%3D%20ResponseRootJsonClass(s%22%24%7BmyUserId%7D_" +
"from_path%22%2C%20requestEntity.name%2C%20requestEntity.age%2C%20requestEntity.hobby)%0A%20%20%20%20Future.successf" +
"ul%20%7B%0A%20%20%20%20%20%20(responseBody%2C%20HttpCode.%60200%60(callContext.callContext))%0A%20%20%20%20%7D",
"the URL-encoded format String, the original code is the OBP connector method body.")
glossaryItems += makeGlossaryItem("DynamicResourceDoc.methodBody", dynamicResourceDocMethodBodyExample)
lazy val connectorMethodBodyExample = ConnectorField("%20%20%20%20%20%20Future.successful%28%0A%20%20%20%20%20%20%20%20Full%28%" +
@ -1612,6 +1614,9 @@ object ExampleValue {
lazy val nameExample = ConnectorField("ACCOUNT_MANAGEMENT_FEE",NoDescriptionProvided)
glossaryItems += makeGlossaryItem("name", nameExample)
lazy val ageExample = ConnectorField("18", "The user age.")
glossaryItems += makeGlossaryItem("age", nameExample)
lazy val productFeeIdExample = ConnectorField(NoExampleProvided,NoDescriptionProvided)
glossaryItems += makeGlossaryItem("product_fee_id", nameExample)

View File

@ -616,6 +616,13 @@ object Glossary extends MdcLoggable {
|"""
)
glossaryItems += GlossaryItem(
title =
"Age",
description =
"""The user Age"""
)
glossaryItems += GlossaryItem(
title = "Account.account_id",
description =

View File

@ -1,6 +1,6 @@
package code.api.util
import java.io
import java.util.Date
import java.util.UUID.randomUUID
@ -9,7 +9,7 @@ import code.DynamicEndpoint.{DynamicEndpointProvider, DynamicEndpointT}
import code.api.APIFailureNewStyle
import code.api.Constant.SYSTEM_READ_ACCOUNTS_BERLIN_GROUP_VIEW_ID
import code.api.cache.Caching
import code.api.util.APIUtil.{EntitlementAndScopeStatus, JsonResponseExtractor, OBPReturnType, afterAuthenticateInterceptResult, canGrantAccessToViewCommon, canRevokeAccessToViewCommon, connectorEmptyResponse, createHttpParamsByUrlFuture, createQueriesByHttpParamsFuture, fullBoxOrException, generateUUID, unboxFull, unboxFullOrFail}
import code.api.util.APIUtil.{EntitlementAndScopeStatus, OBPReturnType, afterAuthenticateInterceptResult, canGrantAccessToViewCommon, canRevokeAccessToViewCommon, connectorEmptyResponse, createHttpParamsByUrlFuture, createQueriesByHttpParamsFuture, fullBoxOrException, generateUUID, unboxFull, unboxFullOrFail}
import code.api.util.ApiRole.canCreateAnyTransactionRequest
import code.api.util.ErrorMessages.{InsufficientAuthorisationToCreateTransactionRequest, _}
import code.api.ResourceDocs1_4_0.ResourceDocs140.ImplementationsResourceDocs
@ -54,6 +54,7 @@ import net.liftweb.json.JsonDSL._
import net.liftweb.json.{JField, JInt, JNothing, JNull, JObject, JString, JValue, _}
import net.liftweb.util.Helpers.tryo
import org.apache.commons.lang3.StringUtils
import java.security.AccessControlException
import scala.collection.immutable.List
import scala.concurrent.Future
@ -63,7 +64,6 @@ import code.validation.{JsonSchemaValidationProvider, JsonValidation}
import net.liftweb.http.JsonResponse
import net.liftweb.util.Props
import code.api.JsonResponseException
import code.api.v4_0_0.ProductFeeJsonV400
import code.api.v4_0_0.dynamic.{DynamicEndpointHelper, DynamicEntityInfo}
import code.bankattribute.BankAttribute
import code.connectormethod.{ConnectorMethodProvider, JsonConnectorMethod}
@ -71,9 +71,6 @@ import code.dynamicMessageDoc.{DynamicMessageDocProvider, JsonDynamicMessageDoc}
import code.dynamicResourceDoc.{DynamicResourceDocProvider, JsonDynamicResourceDoc}
import code.endpointMapping.{EndpointMappingProvider, EndpointMappingT}
import code.endpointTag.EndpointTagT
import net.liftweb.json
import scala.util.Success
object NewStyle {
lazy val endpoints: List[(String, String)] = List(
@ -191,6 +188,18 @@ object NewStyle {
import com.openbankproject.commons.ExecutionContext.Implicits.global
private def validateBankId(bankId: Option[String], callContext: Option[CallContext]): Unit = {
bankId.foreach(validateBankId(_, callContext))
}
private def validateBankId(bankId: String, callContext: Option[CallContext]): Unit = try {
BankId.checkPermission(bankId)
} catch {
case _: AccessControlException =>
val correlationId = callContext.map(_.correlationId).getOrElse("none")
throw JsonResponseException(s"$DynamicResourceDocMethodPermission No permission of operate bank $bankId", 400, correlationId)
}
def getBranch(bankId : BankId, branchId : BranchId, callContext: Option[CallContext]): OBPReturnType[BranchT] = {
Connector.connector.vend.getBranch(bankId, branchId, callContext) map {
x => fullBoxOrException(x ~> APIFailureNewStyle(BranchNotFoundByBranchId, 400, callContext.map(_.toLight)))
@ -292,13 +301,17 @@ object NewStyle {
}
def createBankLevelEndpointTag(bankId:String, operationId:String, tagName:String, callContext: Option[CallContext]): OBPReturnType[EndpointTagT] = {
validateBankId(bankId, callContext)
Connector.connector.vend.createBankLevelEndpointTag(bankId, operationId, tagName, callContext) map {
i => (unboxFullOrFail(i._1, callContext, s"$CreateEndpointTagError", 400), i._2)
}
}
def updateBankLevelEndpointTag(bankId:String, endpointTagId: String, operationId:String, tagName:String, callContext: Option[CallContext]): OBPReturnType[EndpointTagT] = {
Connector.connector.vend.updateBankLevelEndpointTag(bankId, endpointTagId, operationId, tagName, callContext) map {
validateBankId(bankId, callContext)
Connector.connector.vend.updateBankLevelEndpointTag(bankId, endpointTagId, operationId, tagName, callContext) map {
i => (unboxFullOrFail(i._1, callContext, s"$UpdateEndpointTagError", 400), i._2)
}
}
@ -310,6 +323,8 @@ object NewStyle {
}
def checkBankLevelEndpointTagExists(bankId: String, operationId: String, tagName:String, callContext: Option[CallContext]): OBPReturnType[Boolean] = {
validateBankId(bankId, callContext)
Connector.connector.vend.getBankLevelEndpointTag(bankId: String, operationId: String, tagName: String, callContext) map {
i => (i._1.isDefined, i._2)
}
@ -334,6 +349,8 @@ object NewStyle {
}
def getBankLevelEndpointTags(bankId:String, operationId : String, callContext: Option[CallContext]) : OBPReturnType[List[EndpointTagT]] = {
validateBankId(bankId, callContext)
Connector.connector.vend.getBankLevelEndpointTags(bankId, operationId, callContext) map {
i => (unboxFullOrFail(i._1, callContext, s"$InvalidConnectorResponseForGetEndpointTags Current OPERATION_ID is $operationId", 404), i._2)
}
@ -364,6 +381,7 @@ object NewStyle {
bankRoutingScheme: String,
bankRoutingAddress: String,
callContext: Option[CallContext]): OBPReturnType[Bank] = {
validateBankId(bankId, callContext)
Future {
Connector.connector.vend.createOrUpdateBank(
bankId,
@ -936,7 +954,7 @@ object NewStyle {
}
def hasEntitlement(bankId: String, userId: String, role: ApiRole, callContext: Option[CallContext], errorMsg: String = ""): Future[Box[Unit]] = {
val errorInfo =
val errorInfo =
if(StringUtils.isBlank(errorMsg)&& !bankId.isEmpty) UserHasMissingRoles + role.toString() + s" at Bank($bankId)"
else if(StringUtils.isBlank(errorMsg)&& bankId.isEmpty) UserHasMissingRoles + role.toString()
else errorMsg
@ -1903,6 +1921,8 @@ object NewStyle {
def getProductCollectionItemsTree(collectionCode: String,
bankId: String,
callContext: Option[CallContext]): OBPReturnType[List[(ProductCollectionItem, Product, List[ProductAttribute])]] = {
validateBankId(bankId, callContext)
Connector.connector.vend.getProductCollectionItemsTree(collectionCode, bankId, callContext) map {
i => {
val data: Box[List[ProductCollectionItemsTree]] = i._1
@ -2032,6 +2052,8 @@ object NewStyle {
mSatisfied: Boolean,
comments: String,
callContext: Option[CallContext]): OBPReturnType[KycCheck] = {
validateBankId(bankId, callContext)
Connector.connector.vend.createOrUpdateKycCheck(bankId, customerId, id, customerNumber, date, how, staffUserId, mStaffName, mSatisfied, comments, callContext)
.map {
i => (unboxFullOrFail(i._1, callContext, s"$InvalidConnectorResponse Can not create or update KycCheck in the backend. ", 400), i._2)
@ -2048,6 +2070,8 @@ object NewStyle {
issuePlace: String,
expiryDate: Date,
callContext: Option[CallContext]): OBPReturnType[KycDocument] = {
validateBankId(bankId, callContext)
Connector.connector.vend.createOrUpdateKycDocument(
bankId,
customerId,
@ -2074,6 +2098,8 @@ object NewStyle {
relatesToKycDocumentId: String,
relatesToKycCheckId: String,
callContext: Option[CallContext]): OBPReturnType[KycMedia] = {
validateBankId(bankId, callContext)
Connector.connector.vend.createOrUpdateKycMedia(
bankId,
customerId,
@ -2096,6 +2122,8 @@ object NewStyle {
ok: Boolean,
date: Date,
callContext: Option[CallContext]): OBPReturnType[KycStatus] = {
validateBankId(bankId, callContext)
Connector.connector.vend.createOrUpdateKycStatus(
bankId,
customerId,
@ -2299,7 +2327,9 @@ object NewStyle {
posted: Option[CardPostedInfo],
customerId: String,
callContext: Option[CallContext]
): OBPReturnType[PhysicalCard] =
): OBPReturnType[PhysicalCard] = {
validateBankId(bankId, callContext)
Connector.connector.vend.createPhysicalCard(
bankCardNumber: String,
nameOnCard: String,
@ -2325,6 +2355,7 @@ object NewStyle {
) map {
i => (unboxFullOrFail(i._1, callContext, s"$CreateCardError"), i._2)
}
}
def updatePhysicalCard(
cardId: String,
@ -2349,7 +2380,9 @@ object NewStyle {
posted: Option[CardPostedInfo],
customerId: String,
callContext: Option[CallContext]
): OBPReturnType[PhysicalCardTrait] =
): OBPReturnType[PhysicalCardTrait] = {
validateBankId(bankId, callContext)
Connector.connector.vend.updatePhysicalCard(
cardId: String,
bankCardNumber: String,
@ -2376,7 +2409,8 @@ object NewStyle {
) map {
i => (unboxFullOrFail(i._1, callContext, s"$UpdateCardError"), i._2)
}
}
def getPhysicalCardsForBank(bank: Bank, user : User, queryParams: List[OBPQueryParam], callContext:Option[CallContext]) : OBPReturnType[List[PhysicalCard]] =
Connector.connector.vend.getPhysicalCardsForBank(bank: Bank, user : User, queryParams: List[OBPQueryParam], callContext:Option[CallContext]) map {
i => (unboxFullOrFail(i._1, callContext, CardNotFound), i._2)
@ -2583,19 +2617,27 @@ object NewStyle {
}
}
def createOrUpdateEndpointMapping(bankId: Option[String], endpointMapping: EndpointMappingT, callContext: Option[CallContext]) = Future {
(EndpointMappingProvider.endpointMappingProvider.vend.createOrUpdate(bankId, endpointMapping), callContext)
} map {
i => (connectorEmptyResponse(i._1, callContext), i._2)
def createOrUpdateEndpointMapping(bankId: Option[String], endpointMapping: EndpointMappingT, callContext: Option[CallContext]) = {
validateBankId(bankId, callContext)
Future {
(EndpointMappingProvider.endpointMappingProvider.vend.createOrUpdate(bankId, endpointMapping), callContext)
} map {
i => (connectorEmptyResponse(i._1, callContext), i._2)
}
}
def deleteEndpointMapping(bankId: Option[String], endpointMappingId: String, callContext: Option[CallContext]) = Future {
(EndpointMappingProvider.endpointMappingProvider.vend.delete(bankId, endpointMappingId), callContext)
} map {
i => (connectorEmptyResponse(i._1, callContext), i._2)
def deleteEndpointMapping(bankId: Option[String], endpointMappingId: String, callContext: Option[CallContext]) = {
validateBankId(bankId, callContext)
Future {
(EndpointMappingProvider.endpointMappingProvider.vend.delete(bankId, endpointMappingId), callContext)
} map {
i => (connectorEmptyResponse(i._1, callContext), i._2)
}
}
def getEndpointMappingById(bankId: Option[String], endpointMappingId : String, callContext: Option[CallContext]): OBPReturnType[EndpointMappingT] = {
validateBankId(bankId, callContext)
val endpointMappingBox: Box[EndpointMappingT] = EndpointMappingProvider.endpointMappingProvider.vend.getById(bankId, endpointMappingId)
Future{
val endpointMapping = unboxFullOrFail(endpointMappingBox, callContext, s"$EndpointMappingNotFoundByEndpointMappingId Current ENDPOINT_MAPPING_ID is $endpointMappingId", 404)
@ -2604,6 +2646,8 @@ object NewStyle {
}
def getEndpointMappingByOperationId(bankId: Option[String], operationId : String, callContext: Option[CallContext]): OBPReturnType[EndpointMappingT] = {
validateBankId(bankId, callContext)
val endpointMappingBox: Box[EndpointMappingT] = EndpointMappingProvider.endpointMappingProvider.vend.getByOperationId(bankId, operationId)
Future{
val endpointMapping = unboxFullOrFail(endpointMappingBox, callContext, s"$EndpointMappingNotFoundByOperationId Current OPERATION_ID is $operationId",404)
@ -2616,6 +2660,8 @@ object NewStyle {
def getEndpointMappings(bankId: Option[String], callContext: Option[CallContext]): OBPReturnType[List[EndpointMappingT]] = {
import scala.concurrent.duration._
validateBankId(bankId, callContext)
var cacheKey = (randomUUID().toString, randomUUID().toString, randomUUID().toString)
CacheKeyFromArguments.buildCacheKey {
Caching.memoizeSyncWithProvider(Some(cacheKey.toString()))(endpointMappingTTL second) {
@ -2674,24 +2720,29 @@ object NewStyle {
* @param dynamicEntityId
* @return
*/
def deleteDynamicEntity(bankId: Option[String], dynamicEntityId: String): Future[Box[Boolean]] = Future {
for {
entity <- DynamicEntityProvider.connectorMethodProvider.vend.getById(bankId, dynamicEntityId)
deleteEntityResult <- DynamicEntityProvider.connectorMethodProvider.vend.delete(entity)
deleteEntitleMentResult <- if(deleteEntityResult) {
Entitlement.entitlement.vend.deleteDynamicEntityEntitlement(entity.entityName, entity.bankId)
} else {
Box !! false
def deleteDynamicEntity(bankId: Option[String], dynamicEntityId: String): Future[Box[Boolean]] = {
validateBankId(bankId, None)
Future {
for {
entity <- DynamicEntityProvider.connectorMethodProvider.vend.getById(bankId, dynamicEntityId)
deleteEntityResult <- DynamicEntityProvider.connectorMethodProvider.vend.delete(entity)
deleteEntitleMentResult <- if (deleteEntityResult) {
Entitlement.entitlement.vend.deleteDynamicEntityEntitlement(entity.entityName, entity.bankId)
} else {
Box !! false
}
} yield {
if (deleteEntitleMentResult) {
DynamicEntityInfo.roleNames(entity.entityName, entity.bankId).foreach(ApiRole.removeDynamicApiRole(_))
}
deleteEntitleMentResult
}
} yield {
if(deleteEntitleMentResult) {
DynamicEntityInfo.roleNames(entity.entityName, entity.bankId).foreach(ApiRole.removeDynamicApiRole(_))
}
deleteEntitleMentResult
}
}
def getDynamicEntityById(bankId: Option[String], dynamicEntityId : String, callContext: Option[CallContext]): OBPReturnType[DynamicEntityT] = {
validateBankId(bankId, callContext)
val dynamicEntityBox: Box[DynamicEntityT] = DynamicEntityProvider.connectorMethodProvider.vend.getById(bankId, dynamicEntityId)
val dynamicEntity = unboxFullOrFail(dynamicEntityBox, callContext, DynamicEntityNotFoundByDynamicEntityId, 404)
Future{
@ -2699,9 +2750,12 @@ object NewStyle {
}
}
def getDynamicEntityByEntityName(bankId: Option[String], entityName : String, callContext: Option[CallContext]): OBPReturnType[Box[DynamicEntityT]] = Future {
val boxedDynamicEntity = DynamicEntityProvider.connectorMethodProvider.vend.getByEntityName(entityName)
(boxedDynamicEntity, callContext)
def getDynamicEntityByEntityName(bankId: Option[String], entityName : String, callContext: Option[CallContext]): OBPReturnType[Box[DynamicEntityT]] = {
validateBankId(bankId, callContext)
Future {
val boxedDynamicEntity = DynamicEntityProvider.connectorMethodProvider.vend.getByEntityName(entityName)
(boxedDynamicEntity, callContext)
}
}
private[this] val dynamicEntityTTL = {
@ -2712,6 +2766,8 @@ object NewStyle {
def getDynamicEntities(bankId: Option[String]): List[DynamicEntityT] = {
import scala.concurrent.duration._
validateBankId(bankId, None)
var cacheKey = (randomUUID().toString, randomUUID().toString, randomUUID().toString)
CacheKeyFromArguments.buildCacheKey {
Caching.memoizeSyncWithProvider(Some(cacheKey.toString()))(dynamicEntityTTL second) {
@ -2766,6 +2822,8 @@ object NewStyle {
queryParameters: Option[Map[String, List[String]]],
callContext: Option[CallContext]): OBPReturnType[Box[JValue]] = {
import DynamicEntityOperation._
validateBankId(bankId, callContext)
val dynamicEntityBox = DynamicEntityProvider.connectorMethodProvider.vend.getByEntityName(entityName)
// do validate, any validate process fail will return immediately
if(dynamicEntityBox.isEmpty) {
@ -2848,6 +2906,8 @@ object NewStyle {
dateStarts: Date,
dateExpires: Option[Date],
callContext: Option[CallContext]): OBPReturnType[DirectDebitTrait] = {
validateBankId(bankId, callContext)
Connector.connector.vend.createDirectDebit(
bankId,
accountId,
@ -2875,6 +2935,8 @@ object NewStyle {
dateStarts: Date,
dateExpires: Option[Date],
callContext: Option[CallContext]): OBPReturnType[StandingOrderTrait] = {
validateBankId(bankId, callContext)
Connector.connector.vend.createStandingOrder(
bankId,
accountId,
@ -2920,19 +2982,27 @@ object NewStyle {
getConnectorByName(connectorName).flatMap(_.callableMethods.get(methodName))
}
def createDynamicEndpoint(bankId:Option[String], userId: String, swaggerString: String, callContext: Option[CallContext]): OBPReturnType[DynamicEndpointT] = Future {
(DynamicEndpointProvider.connectorMethodProvider.vend.create(bankId:Option[String], userId, swaggerString), callContext)
} map {
i => (connectorEmptyResponse(i._1, callContext), i._2)
def createDynamicEndpoint(bankId:Option[String], userId: String, swaggerString: String, callContext: Option[CallContext]): OBPReturnType[DynamicEndpointT] = {
validateBankId(bankId, callContext)
Future {
(DynamicEndpointProvider.connectorMethodProvider.vend.create(bankId: Option[String], userId, swaggerString), callContext)
} map {
i => (connectorEmptyResponse(i._1, callContext), i._2)
}
}
def updateDynamicEndpointHost(bankId: Option[String], userId: String, swaggerString: String, callContext: Option[CallContext]): OBPReturnType[DynamicEndpointT] = Future {
(DynamicEndpointProvider.connectorMethodProvider.vend.updateHost(bankId, userId, swaggerString), callContext)
} map {
i => (connectorEmptyResponse(i._1, callContext), i._2)
def updateDynamicEndpointHost(bankId: Option[String], userId: String, swaggerString: String, callContext: Option[CallContext]): OBPReturnType[DynamicEndpointT] = {
validateBankId(bankId, callContext)
Future {
(DynamicEndpointProvider.connectorMethodProvider.vend.updateHost(bankId, userId, swaggerString), callContext)
} map {
i => (connectorEmptyResponse(i._1, callContext), i._2)
}
}
def getDynamicEndpoint(bankId: Option[String], dynamicEndpointId: String, callContext: Option[CallContext]): OBPReturnType[DynamicEndpointT] = {
validateBankId(bankId, callContext)
val dynamicEndpointBox: Box[DynamicEndpointT] = DynamicEndpointProvider.connectorMethodProvider.vend.get(bankId, dynamicEndpointId)
val errorMessage =
if(bankId.isEmpty)
@ -2945,8 +3015,11 @@ object NewStyle {
}
}
def getDynamicEndpoints(bankId: Option[String], callContext: Option[CallContext]): OBPReturnType[List[DynamicEndpointT]] = Future {
(DynamicEndpointProvider.connectorMethodProvider.vend.getAll(bankId), callContext)
def getDynamicEndpoints(bankId: Option[String], callContext: Option[CallContext]): OBPReturnType[List[DynamicEndpointT]] = {
validateBankId(bankId, callContext)
Future {
(DynamicEndpointProvider.connectorMethodProvider.vend.getAll(bankId), callContext)
}
}
def getDynamicEndpointsByUserId(userId: String, callContext: Option[CallContext]): OBPReturnType[List[DynamicEndpointT]] = Future {
@ -2959,6 +3032,8 @@ object NewStyle {
* @return
*/
def deleteDynamicEndpoint(bankId: Option[String], dynamicEndpointId: String, callContext: Option[CallContext]): Future[Box[Boolean]] = {
validateBankId(bankId, callContext)
val dynamicEndpoint: OBPReturnType[DynamicEndpointT] = this.getDynamicEndpoint(bankId, dynamicEndpointId, callContext)
for {
(entity, _) <- dynamicEndpoint
@ -3256,89 +3331,91 @@ object NewStyle {
(unboxFullOrFail(connectorMethod, callContext, s"$ConnectorMethodNotFound Current CONNECTOR_METHOD_ID(${connectorMethodId})", 400), callContext)
}
def isJsonDynamicResourceDocExists(requestVerb : String, requestUrl : String, callContext: Option[CallContext]): OBPReturnType[Boolean] =
def isJsonDynamicResourceDocExists(bankId: Option[String], requestVerb : String, requestUrl : String, callContext: Option[CallContext]): OBPReturnType[Boolean] =
Future {
val result = DynamicResourceDocProvider.provider.vend.getByVerbAndUrl(requestVerb, requestUrl)
val result = DynamicResourceDocProvider.provider.vend.getByVerbAndUrl(bankId, requestVerb, requestUrl)
(result.isDefined, callContext)
}
def createJsonDynamicResourceDoc(dynamicResourceDoc: JsonDynamicResourceDoc, callContext: Option[CallContext]): OBPReturnType[JsonDynamicResourceDoc] =
def createJsonDynamicResourceDoc(bankId: Option[String], dynamicResourceDoc: JsonDynamicResourceDoc, callContext: Option[CallContext]): OBPReturnType[JsonDynamicResourceDoc] =
Future {
val newInternalConnector = DynamicResourceDocProvider.provider.vend.create(dynamicResourceDoc)
val newInternalConnector = DynamicResourceDocProvider.provider.vend.create(bankId, dynamicResourceDoc)
val errorMsg = s"$UnknownError Can not create Dynamic Resource Doc in the backend. "
(unboxFullOrFail(newInternalConnector, callContext, errorMsg, 400), callContext)
}
def updateJsonDynamicResourceDoc(entity: JsonDynamicResourceDoc, callContext: Option[CallContext]): OBPReturnType[JsonDynamicResourceDoc] =
def updateJsonDynamicResourceDoc(bankId: Option[String], entity: JsonDynamicResourceDoc, callContext: Option[CallContext]): OBPReturnType[JsonDynamicResourceDoc] =
Future {
val updatedConnectorMethod = DynamicResourceDocProvider.provider.vend.update(entity: JsonDynamicResourceDoc)
val updatedConnectorMethod = DynamicResourceDocProvider.provider.vend.update(bankId, entity: JsonDynamicResourceDoc)
val errorMsg = s"$UnknownError Can not update Dynamic Resource Doc in the backend. "
(unboxFullOrFail(updatedConnectorMethod, callContext, errorMsg, 400), callContext)
}
def isJsonDynamicResourceDocExists(dynamicResourceDocId: String, callContext: Option[CallContext]): OBPReturnType[Boolean] =
def isJsonDynamicResourceDocExists(bankId: Option[String], dynamicResourceDocId: String, callContext: Option[CallContext]): OBPReturnType[Boolean] =
Future {
val result = DynamicResourceDocProvider.provider.vend.getById(dynamicResourceDocId)
val result = DynamicResourceDocProvider.provider.vend.getById(bankId, dynamicResourceDocId)
(result.isDefined, callContext)
}
def getJsonDynamicResourceDocs(callContext: Option[CallContext]): OBPReturnType[List[JsonDynamicResourceDoc]] =
def getJsonDynamicResourceDocs(bankId: Option[String], callContext: Option[CallContext]): OBPReturnType[List[JsonDynamicResourceDoc]] =
Future {
val dynamicResourceDocs: List[JsonDynamicResourceDoc] = DynamicResourceDocProvider.provider.vend.getAll()
val dynamicResourceDocs: List[JsonDynamicResourceDoc] = DynamicResourceDocProvider.provider.vend.getAll(bankId)
dynamicResourceDocs -> callContext
}
def getJsonDynamicResourceDocById(dynamicResourceDocId: String, callContext: Option[CallContext]): OBPReturnType[JsonDynamicResourceDoc] =
def getJsonDynamicResourceDocById(bankId: Option[String], dynamicResourceDocId: String, callContext: Option[CallContext]): OBPReturnType[JsonDynamicResourceDoc] =
Future {
val dynamicResourceDoc = DynamicResourceDocProvider.provider.vend.getById(dynamicResourceDocId)
val dynamicResourceDoc = DynamicResourceDocProvider.provider.vend.getById(bankId, dynamicResourceDocId)
(unboxFullOrFail(dynamicResourceDoc, callContext, s"$DynamicResourceDocNotFound Current DYNAMIC_RESOURCE_DOC_ID(${dynamicResourceDocId})", 400), callContext)
}
def deleteJsonDynamicResourceDocById(dynamicResourceDocId: String, callContext: Option[CallContext]): OBPReturnType[Boolean] =
def deleteJsonDynamicResourceDocById(bankId: Option[String], dynamicResourceDocId: String, callContext: Option[CallContext]): OBPReturnType[Boolean] =
Future {
val dynamicResourceDoc = DynamicResourceDocProvider.provider.vend.deleteById(dynamicResourceDocId)
val dynamicResourceDoc = DynamicResourceDocProvider.provider.vend.deleteById(bankId, dynamicResourceDocId)
(unboxFullOrFail(dynamicResourceDoc, callContext, s"$DynamicResourceDocDeleteError Current DYNAMIC_RESOURCE_DOC_ID(${dynamicResourceDocId})", 400), callContext)
}
def createJsonDynamicMessageDoc(dynamicMessageDoc: JsonDynamicMessageDoc, callContext: Option[CallContext]): OBPReturnType[JsonDynamicMessageDoc] =
def createJsonDynamicMessageDoc(bankId: Option[String], dynamicMessageDoc: JsonDynamicMessageDoc, callContext: Option[CallContext]): OBPReturnType[JsonDynamicMessageDoc] =
Future {
val newInternalConnector = DynamicMessageDocProvider.provider.vend.create(dynamicMessageDoc)
val newInternalConnector = DynamicMessageDocProvider.provider.vend.create(bankId, dynamicMessageDoc)
val errorMsg = s"$UnknownError Can not create Dynamic Message Doc in the backend. "
(unboxFullOrFail(newInternalConnector, callContext, errorMsg, 400), callContext)
}
def updateJsonDynamicMessageDoc(entity: JsonDynamicMessageDoc, callContext: Option[CallContext]): OBPReturnType[JsonDynamicMessageDoc] =
def updateJsonDynamicMessageDoc(bankId: Option[String], entity: JsonDynamicMessageDoc, callContext: Option[CallContext]): OBPReturnType[JsonDynamicMessageDoc] =
Future {
val updatedConnectorMethod = DynamicMessageDocProvider.provider.vend.update(entity: JsonDynamicMessageDoc)
val updatedConnectorMethod = DynamicMessageDocProvider.provider.vend.update(bankId: Option[String], entity: JsonDynamicMessageDoc)
val errorMsg = s"$UnknownError Can not update Dynamic Message Doc in the backend. "
(unboxFullOrFail(updatedConnectorMethod, callContext, errorMsg, 400), callContext)
}
def isJsonDynamicMessageDocExists(process: String, callContext: Option[CallContext]): OBPReturnType[Boolean] =
def isJsonDynamicMessageDocExists(bankId: Option[String], process: String, callContext: Option[CallContext]): OBPReturnType[Boolean] =
Future {
val result = DynamicMessageDocProvider.provider.vend.getByProcess(process)
val result = DynamicMessageDocProvider.provider.vend.getByProcess(bankId, process)
(result.isDefined, callContext)
}
def getJsonDynamicMessageDocs(callContext: Option[CallContext]): OBPReturnType[List[JsonDynamicMessageDoc]] =
def getJsonDynamicMessageDocs(bankId: Option[String], callContext: Option[CallContext]): OBPReturnType[List[JsonDynamicMessageDoc]] =
Future {
val dynamicMessageDocs: List[JsonDynamicMessageDoc] = DynamicMessageDocProvider.provider.vend.getAll()
val dynamicMessageDocs: List[JsonDynamicMessageDoc] = DynamicMessageDocProvider.provider.vend.getAll(bankId)
dynamicMessageDocs -> callContext
}
def getJsonDynamicMessageDocById(dynamicMessageDocId: String, callContext: Option[CallContext]): OBPReturnType[JsonDynamicMessageDoc] =
def getJsonDynamicMessageDocById(bankId: Option[String], dynamicMessageDocId: String, callContext: Option[CallContext]): OBPReturnType[JsonDynamicMessageDoc] =
Future {
val dynamicMessageDoc = DynamicMessageDocProvider.provider.vend.getById(dynamicMessageDocId)
val dynamicMessageDoc = DynamicMessageDocProvider.provider.vend.getById(bankId, dynamicMessageDocId)
(unboxFullOrFail(dynamicMessageDoc, callContext, s"$DynamicMessageDocNotFound Current DYNAMIC_RESOURCE_DOC_ID(${dynamicMessageDocId})", 400), callContext)
}
def deleteJsonDynamicMessageDocById(dynamicMessageDocId: String, callContext: Option[CallContext]): OBPReturnType[Boolean] =
def deleteJsonDynamicMessageDocById(bankId: Option[String], dynamicMessageDocId: String, callContext: Option[CallContext]): OBPReturnType[Boolean] =
Future {
val dynamicMessageDoc = DynamicMessageDocProvider.provider.vend.deleteById(dynamicMessageDocId)
val dynamicMessageDoc = DynamicMessageDocProvider.provider.vend.deleteById(bankId, dynamicMessageDocId)
(unboxFullOrFail(dynamicMessageDoc, callContext, s"$DynamicMessageDocDeleteError", 400), callContext)
}
def validateUserAuthContextUpdateRequest(bankId: String, userId: String, key: String, value: String, scaMethod: String, callContext: Option[CallContext]): OBPReturnType[UserAuthContextUpdate] = {
validateBankId(bankId, callContext)
Connector.connector.vend.validateUserAuthContextUpdateRequest(bankId, userId, key, value, scaMethod, callContext) map {
i => (connectorEmptyResponse(i._1, callContext), i._2)
}

View File

@ -7,10 +7,11 @@ import java.util.{Calendar, Date}
import code.DynamicData.{DynamicData, DynamicDataProvider}
import code.DynamicEndpoint.DynamicEndpointSwagger
import code.accountattribute.AccountAttributeX
import code.api.ResourceDocs1_4_0.SwaggerDefinitionsJSON._
import code.api.ResourceDocs1_4_0.SwaggerDefinitionsJSON.{jsonDynamicResourceDoc, _}
import code.api.util.APIUtil.{fullBoxOrException, _}
import code.api.util.ApiRole.{canCreateEntitlementAtAnyBank, _}
import code.api.util.ApiTag._
import code.api.util.DynamicUtil.Validation
import code.api.util.ErrorMessages._
import code.api.util.ExampleValue._
import code.api.util.Glossary.getGlossaryItem
@ -9238,7 +9239,7 @@ trait APIMethods400 {
_ <- Helper.booleanToFuture(failMsg = errorMsg, cc=callContext) {
connectorMethod.isDefined
}
_ = Validation.validateDependency(connectorMethod.orNull)
(connectorMethod, callContext) <- NewStyle.function.createJsonConnectorMethod(jsonConnectorMethod, callContext)
} yield {
(connectorMethod, HttpCode.`201`(callContext))
@ -9283,6 +9284,7 @@ trait APIMethods400 {
_ <- Helper.booleanToFuture(failMsg = errorMsg, cc=callContext) {
connectorMethod.isDefined
}
_ = Validation.validateDependency(connectorMethod.orNull)
(connectorMethod, callContext) <- NewStyle.function.updateJsonConnectorMethod(connectorMethodId, connectorMethodBody.methodBody, callContext)
} yield {
(connectorMethod, HttpCode.`200`(callContext))
@ -9395,18 +9397,21 @@ trait APIMethods400 {
}
_ = try {
CompiledObjects(jsonDynamicResourceDoc.exampleRequestBody, jsonDynamicResourceDoc.successResponseBody, jsonDynamicResourceDoc.methodBody)
.validateDependency()
} catch {
case e: JsonResponseException =>
throw e
case e: Exception =>
val jsonResponse = createErrorJsonResponse(s"$DynamicCodeCompileFail ${e.getMessage}", 400, cc.correlationId)
throw JsonResponseException(jsonResponse)
}
(isExists, callContext) <- NewStyle.function.isJsonDynamicResourceDocExists(jsonDynamicResourceDoc.requestVerb, jsonDynamicResourceDoc.requestUrl, Some(cc))
(isExists, callContext) <- NewStyle.function.isJsonDynamicResourceDocExists(None, jsonDynamicResourceDoc.requestVerb, jsonDynamicResourceDoc.requestUrl, Some(cc))
_ <- Helper.booleanToFuture(failMsg = s"$DynamicResourceDocAlreadyExists The combination of request_url(${jsonDynamicResourceDoc.requestUrl}) and request_verb(${jsonDynamicResourceDoc.requestVerb}) must be unique", cc=callContext) {
(!isExists)
}
(dynamicResourceDoc, callContext) <- NewStyle.function.createJsonDynamicResourceDoc(jsonDynamicResourceDoc, callContext)
(dynamicResourceDoc, callContext) <- NewStyle.function.createJsonDynamicResourceDoc(None, jsonDynamicResourceDoc, callContext)
} yield {
(dynamicResourceDoc, HttpCode.`201`(callContext))
}
@ -9459,15 +9464,18 @@ trait APIMethods400 {
_ = try {
CompiledObjects(jsonDynamicResourceDoc.exampleRequestBody, jsonDynamicResourceDoc.successResponseBody, jsonDynamicResourceDoc.methodBody)
.validateDependency()
} catch {
case e: JsonResponseException =>
throw e
case e: Exception =>
val jsonResponse = createErrorJsonResponse(s"$DynamicCodeCompileFail ${e.getMessage}", 400, cc.correlationId)
throw JsonResponseException(jsonResponse)
}
(_, callContext) <- NewStyle.function.getJsonDynamicResourceDocById(dynamicResourceDocId, cc.callContext)
(_, callContext) <- NewStyle.function.getJsonDynamicResourceDocById(None, dynamicResourceDocId, cc.callContext)
(dynamicResourceDoc, callContext) <- NewStyle.function.updateJsonDynamicResourceDoc(dynamicResourceDocBody.copy(dynamicResourceDocId = Some(dynamicResourceDocId)), callContext)
(dynamicResourceDoc, callContext) <- NewStyle.function.updateJsonDynamicResourceDoc(None, dynamicResourceDocBody.copy(dynamicResourceDocId = Some(dynamicResourceDocId)), callContext)
} yield {
(dynamicResourceDoc, HttpCode.`200`(callContext))
}
@ -9498,8 +9506,8 @@ trait APIMethods400 {
case "management" :: "dynamic-resource-docs" :: dynamicResourceDocId :: Nil JsonDelete _ => {
cc =>
for {
(_, callContext) <- NewStyle.function.getJsonDynamicResourceDocById(dynamicResourceDocId, cc.callContext)
(dynamicResourceDoc, callContext) <- NewStyle.function.deleteJsonDynamicResourceDocById(dynamicResourceDocId, callContext)
(_, callContext) <- NewStyle.function.getJsonDynamicResourceDocById(None, dynamicResourceDocId, cc.callContext)
(dynamicResourceDoc, callContext) <- NewStyle.function.deleteJsonDynamicResourceDocById(None, dynamicResourceDocId, callContext)
} yield {
(dynamicResourceDoc, HttpCode.`204`(callContext))
}
@ -9530,7 +9538,7 @@ trait APIMethods400 {
case "management" :: "dynamic-resource-docs" :: dynamicResourceDocId :: Nil JsonGet _ => {
cc =>
for {
(dynamicResourceDoc, callContext) <- NewStyle.function.getJsonDynamicResourceDocById(dynamicResourceDocId, cc.callContext)
(dynamicResourceDoc, callContext) <- NewStyle.function.getJsonDynamicResourceDocById(None, dynamicResourceDocId, cc.callContext)
} yield {
(dynamicResourceDoc, HttpCode.`200`(callContext))
}
@ -9561,13 +9569,253 @@ trait APIMethods400 {
case "management" :: "dynamic-resource-docs" :: Nil JsonGet _ => {
cc =>
for {
(dynamicResourceDocs, callContext) <- NewStyle.function.getJsonDynamicResourceDocs(cc.callContext)
(dynamicResourceDocs, callContext) <- NewStyle.function.getJsonDynamicResourceDocs(None, cc.callContext)
} yield {
(ListResult("dynamic-resource-docs", dynamicResourceDocs), HttpCode.`200`(callContext))
}
}
}
staticResourceDocs += ResourceDoc(
createBankLevelDynamicResourceDoc,
implementedInApiVersion,
nameOf(createBankLevelDynamicResourceDoc),
"POST",
"/management/banks/BANK_ID/dynamic-resource-docs",
"Create Bank Level Dynamic Resource Doc",
s"""Create a Bank Level Dynamic Resource Doc.
|
|The connector_method_body is URL-encoded format String
|""",
jsonDynamicResourceDoc.copy(dynamicResourceDocId=None),
jsonDynamicResourceDoc,
List(
$BankNotFound,
$UserNotLoggedIn,
UserHasMissingRoles,
InvalidJsonFormat,
UnknownError
),
List(apiTagDynamicResourceDoc, apiTagNewStyle),
Some(List(canCreateBankLevelDynamicResourceDoc)))
lazy val createBankLevelDynamicResourceDoc: OBPEndpoint = {
case "management" :: "banks" :: bankId :: "dynamic-resource-docs" :: Nil JsonPost json -> _ => {
cc =>
for {
jsonDynamicResourceDoc <- NewStyle.function.tryons(s"$InvalidJsonFormat The Json body should be the $JsonDynamicResourceDoc", 400, cc.callContext) {
json.extract[JsonDynamicResourceDoc]
}
_ <- Helper.booleanToFuture(failMsg = s"""$InvalidJsonFormat The request_verb must be one of ["POST", "PUT", "GET", "DELETE"]""", cc=cc.callContext) {
Set("POST", "PUT", "GET", "DELETE").contains(jsonDynamicResourceDoc.requestVerb)
}
_ <- Helper.booleanToFuture(failMsg = s"""$InvalidJsonFormat When request_verb is "GET" or "DELETE", the example_request_body must be a blank String "" or just totally omit the field""", cc=cc.callContext) {
(jsonDynamicResourceDoc.requestVerb, jsonDynamicResourceDoc.exampleRequestBody) match {
case ("GET" | "DELETE", Some(JString(s))) => //we support the empty string "" here
StringUtils.isBlank(s)
case ("GET" | "DELETE", Some(requestBody)) => //we add the guard, we forbid any json objects in GET/DELETE request body.
requestBody == JNothing
case _ => true
}
}
_ = try {
CompiledObjects(jsonDynamicResourceDoc.exampleRequestBody, jsonDynamicResourceDoc.successResponseBody, jsonDynamicResourceDoc.methodBody)
.validateDependency()
} catch {
case e: JsonResponseException =>
throw e
case e: Exception =>
val jsonResponse = createErrorJsonResponse(s"$DynamicCodeCompileFail ${e.getMessage}", 400, cc.correlationId)
throw JsonResponseException(jsonResponse)
}
_ = try {
CompiledObjects(jsonDynamicResourceDoc.exampleRequestBody, jsonDynamicResourceDoc.successResponseBody, jsonDynamicResourceDoc.methodBody)
} catch {
case e: Exception =>
val jsonResponse = createErrorJsonResponse(s"$DynamicCodeCompileFail ${e.getMessage}", 400, cc.correlationId)
throw JsonResponseException(jsonResponse)
}
(isExists, callContext) <- NewStyle.function.isJsonDynamicResourceDocExists(Some(bankId), jsonDynamicResourceDoc.requestVerb, jsonDynamicResourceDoc.requestUrl, Some(cc))
_ <- Helper.booleanToFuture(failMsg = s"$DynamicResourceDocAlreadyExists The combination of request_url(${jsonDynamicResourceDoc.requestUrl}) and request_verb(${jsonDynamicResourceDoc.requestVerb}) must be unique", cc=callContext) {
(!isExists)
}
(dynamicResourceDoc, callContext) <- NewStyle.function.createJsonDynamicResourceDoc(Some(bankId), jsonDynamicResourceDoc, callContext)
} yield {
(dynamicResourceDoc, HttpCode.`201`(callContext))
}
}
}
staticResourceDocs += ResourceDoc(
updateBankLevelDynamicResourceDoc,
implementedInApiVersion,
nameOf(updateBankLevelDynamicResourceDoc),
"PUT",
"/management/banks/BANK_ID/dynamic-resource-docs/DYNAMIC-RESOURCE-DOC-ID",
"Update Bank Level Dynamic Resource Doc",
s"""Update a Bank Level Dynamic Resource Doc.
|
|The connector_method_body is URL-encoded format String
|""",
jsonDynamicResourceDoc.copy(dynamicResourceDocId = None),
jsonDynamicResourceDoc,
List(
$BankNotFound,
$UserNotLoggedIn,
UserHasMissingRoles,
InvalidJsonFormat,
UnknownError
),
List(apiTagDynamicResourceDoc, apiTagNewStyle),
Some(List(canUpdateBankLevelDynamicResourceDoc)))
lazy val updateBankLevelDynamicResourceDoc: OBPEndpoint = {
case "management" :: "banks" :: bankId :: "dynamic-resource-docs" :: dynamicResourceDocId :: Nil JsonPut json -> _ => {
cc =>
for {
dynamicResourceDocBody <- NewStyle.function.tryons(s"$InvalidJsonFormat The Json body should be the $JsonDynamicResourceDoc", 400, cc.callContext) {
json.extract[JsonDynamicResourceDoc]
}
_ <- Helper.booleanToFuture(failMsg = s"""$InvalidJsonFormat The request_verb must be one of ["POST", "PUT", "GET", "DELETE"]""", cc=cc.callContext) {
Set("POST", "PUT", "GET", "DELETE").contains(dynamicResourceDocBody.requestVerb)
}
_ <- Helper.booleanToFuture(failMsg = s"""$InvalidJsonFormat When request_verb is "GET" or "DELETE", the example_request_body must be a blank String""", cc=cc.callContext) {
(dynamicResourceDocBody.requestVerb, dynamicResourceDocBody.exampleRequestBody) match {
case ("GET" | "DELETE", Some(JString(s))) =>
StringUtils.isBlank(s)
case ("GET" | "DELETE", Some(requestBody)) =>
requestBody == JNothing
case _ => true
}
}
_ = try {
CompiledObjects(dynamicResourceDocBody.exampleRequestBody, dynamicResourceDocBody.successResponseBody, dynamicResourceDocBody.methodBody)
.validateDependency()
} catch {
case e: JsonResponseException =>
throw e
case e: Exception =>
val jsonResponse = createErrorJsonResponse(s"$DynamicCodeCompileFail ${e.getMessage}", 400, cc.correlationId)
throw JsonResponseException(jsonResponse)
}
_ = try {
CompiledObjects(dynamicResourceDocBody.exampleRequestBody, dynamicResourceDocBody.successResponseBody, jsonDynamicResourceDoc.methodBody)
} catch {
case e: Exception =>
val jsonResponse = createErrorJsonResponse(s"$DynamicCodeCompileFail ${e.getMessage}", 400, cc.correlationId)
throw JsonResponseException(jsonResponse)
}
(_, callContext) <- NewStyle.function.getJsonDynamicResourceDocById(Some(bankId), dynamicResourceDocId, cc.callContext)
(dynamicResourceDoc, callContext) <- NewStyle.function.updateJsonDynamicResourceDoc(Some(bankId), dynamicResourceDocBody.copy(dynamicResourceDocId = Some(dynamicResourceDocId)), callContext)
} yield {
(dynamicResourceDoc, HttpCode.`200`(callContext))
}
}
}
staticResourceDocs += ResourceDoc(
deleteBankLevelDynamicResourceDoc,
implementedInApiVersion,
nameOf(deleteBankLevelDynamicResourceDoc),
"DELETE",
"/management/banks/BANK_ID/dynamic-resource-docs/DYNAMIC-RESOURCE-DOC-ID",
"Delete Bank Level Dynamic Resource Doc",
s"""Delete a Bank Level Dynamic Resource Doc.
|""",
EmptyBody,
BooleanBody(true),
List(
$BankNotFound,
$UserNotLoggedIn,
UserHasMissingRoles,
InvalidJsonFormat,
UnknownError
),
List(apiTagDynamicResourceDoc, apiTagNewStyle),
Some(List(canDeleteBankLevelDynamicResourceDoc)))
lazy val deleteBankLevelDynamicResourceDoc: OBPEndpoint = {
case "management" :: "banks" :: bankId :: "dynamic-resource-docs" :: dynamicResourceDocId :: Nil JsonDelete _ => {
cc =>
for {
(_, callContext) <- NewStyle.function.getJsonDynamicResourceDocById(Some(bankId), dynamicResourceDocId, cc.callContext)
(dynamicResourceDoc, callContext) <- NewStyle.function.deleteJsonDynamicResourceDocById(Some(bankId), dynamicResourceDocId, callContext)
} yield {
(dynamicResourceDoc, HttpCode.`204`(callContext))
}
}
}
staticResourceDocs += ResourceDoc(
getBankLevelDynamicResourceDoc,
implementedInApiVersion,
nameOf(getBankLevelDynamicResourceDoc),
"GET",
"/management/banks/BANK_ID/dynamic-resource-docs/DYNAMIC-RESOURCE-DOC-ID",
"Get Bank Level Dynamic Resource Doc by Id",
s"""Get a Bank Level Dynamic Resource Doc by DYNAMIC-RESOURCE-DOC-ID.
|
|""",
EmptyBody,
jsonDynamicResourceDoc,
List(
$BankNotFound,
$UserNotLoggedIn,
UserHasMissingRoles,
UnknownError
),
List(apiTagDynamicResourceDoc, apiTagNewStyle),
Some(List(canGetBankLevelDynamicResourceDoc)))
lazy val getBankLevelDynamicResourceDoc: OBPEndpoint = {
case "management" :: "banks" :: bankId :: "dynamic-resource-docs" :: dynamicResourceDocId :: Nil JsonGet _ => {
cc =>
for {
(dynamicResourceDoc, callContext) <- NewStyle.function.getJsonDynamicResourceDocById(Some(bankId), dynamicResourceDocId, cc.callContext)
} yield {
(dynamicResourceDoc, HttpCode.`200`(callContext))
}
}
}
staticResourceDocs += ResourceDoc(
getAllBankLevelDynamicResourceDocs,
implementedInApiVersion,
nameOf(getAllBankLevelDynamicResourceDocs),
"GET",
"/management/banks/BANK_ID/dynamic-resource-docs",
"Get all Bank Level Dynamic Resource Docs",
s"""Get all Bank Level Dynamic Resource Docs.
|
|""",
EmptyBody,
ListResult("dynamic-resource-docs", jsonDynamicResourceDoc::Nil),
List(
$BankNotFound,
$UserNotLoggedIn,
UserHasMissingRoles,
UnknownError
),
List(apiTagDynamicResourceDoc, apiTagNewStyle),
Some(List(canGetAllBankLevelDynamicResourceDocs)))
lazy val getAllBankLevelDynamicResourceDocs: OBPEndpoint = {
case "management" :: "banks" :: bankId :: "dynamic-resource-docs" :: Nil JsonGet _ => {
cc =>
for {
(dynamicResourceDocs, callContext) <- NewStyle.function.getJsonDynamicResourceDocs(Some(bankId), cc.callContext)
} yield {
(ListResult("dynamic-resource-docs", dynamicResourceDocs), HttpCode.`200`(callContext))
}
}
}
staticResourceDocs += ResourceDoc(
buildDynamicEndpointTemplate,
@ -9647,7 +9895,7 @@ trait APIMethods400 {
dynamicMessageDoc <- NewStyle.function.tryons(s"$InvalidJsonFormat The Json body should be the $JsonDynamicMessageDoc", 400, cc.callContext) {
json.extract[JsonDynamicMessageDoc]
}
(dynamicMessageDocExisted, callContext) <- NewStyle.function.isJsonDynamicMessageDocExists(dynamicMessageDoc.process, cc.callContext)
(dynamicMessageDocExisted, callContext) <- NewStyle.function.isJsonDynamicMessageDocExists(None, dynamicMessageDoc.process, cc.callContext)
_ <- Helper.booleanToFuture(failMsg = s"$DynamicMessageDocAlreadyExists The json body process(${dynamicMessageDoc.process}) already exists", cc=callContext) {
(!dynamicMessageDocExisted)
}
@ -9656,7 +9904,53 @@ trait APIMethods400 {
_ <- Helper.booleanToFuture(failMsg = errorMsg, cc=callContext) {
connectorMethod.isDefined
}
(dynamicMessageDoc, callContext) <- NewStyle.function.createJsonDynamicMessageDoc(dynamicMessageDoc, callContext)
_ = Validation.validateDependency(connectorMethod.orNull)
(dynamicMessageDoc, callContext) <- NewStyle.function.createJsonDynamicMessageDoc(None, dynamicMessageDoc, callContext)
} yield {
(dynamicMessageDoc, HttpCode.`201`(callContext))
}
}
}
staticResourceDocs += ResourceDoc(
createBankLevelDynamicMessageDoc,
implementedInApiVersion,
nameOf(createBankLevelDynamicMessageDoc),
"POST",
"/management/banks/BANK_ID/dynamic-message-docs",
"Create Bank Level Dynamic Message Doc",
s"""Create a Bank Level Dynamic Message Doc.
|""",
jsonDynamicMessageDoc.copy(dynamicMessageDocId=None),
jsonDynamicMessageDoc,
List(
$BankNotFound,
$UserNotLoggedIn,
UserHasMissingRoles,
InvalidJsonFormat,
UnknownError
),
List(apiTagDynamicMessageDoc, apiTagNewStyle),
Some(List(canCreateBankLevelDynamicMessageDoc)))
lazy val createBankLevelDynamicMessageDoc: OBPEndpoint = {
case "management" :: "banks" :: bankId ::"dynamic-message-docs" :: Nil JsonPost json -> _ => {
cc =>
for {
dynamicMessageDoc <- NewStyle.function.tryons(s"$InvalidJsonFormat The Json body should be the $JsonDynamicMessageDoc", 400, cc.callContext) {
json.extract[JsonDynamicMessageDoc]
}
(dynamicMessageDocExisted, callContext) <- NewStyle.function.isJsonDynamicMessageDocExists(Some(bankId), dynamicMessageDoc.process, cc.callContext)
_ <- Helper.booleanToFuture(failMsg = s"$DynamicMessageDocAlreadyExists The json body process(${dynamicMessageDoc.process}) already exists", cc=callContext) {
(!dynamicMessageDocExisted)
}
connectorMethod = DynamicConnector.createFunction(dynamicMessageDoc.process, dynamicMessageDoc.decodedMethodBody)
errorMsg = if(connectorMethod.isEmpty) s"$ConnectorMethodBodyCompileFail ${connectorMethod.asInstanceOf[Failure].msg}" else ""
_ <- Helper.booleanToFuture(failMsg = errorMsg, cc=callContext) {
connectorMethod.isDefined
}
_ = Validation.validateDependency(connectorMethod.orNull)
(dynamicMessageDoc, callContext) <- NewStyle.function.createJsonDynamicMessageDoc(Some(bankId), dynamicMessageDoc, callContext)
} yield {
(dynamicMessageDoc, HttpCode.`201`(callContext))
}
@ -9695,8 +9989,9 @@ trait APIMethods400 {
_ <- Helper.booleanToFuture(failMsg = errorMsg, cc=cc.callContext) {
connectorMethod.isDefined
}
(_, callContext) <- NewStyle.function.getJsonDynamicMessageDocById(dynamicMessageDocId, cc.callContext)
(dynamicMessageDoc, callContext) <- NewStyle.function.updateJsonDynamicMessageDoc(dynamicMessageDocBody.copy(dynamicMessageDocId=Some(dynamicMessageDocId)), callContext)
_ = Validation.validateDependency(connectorMethod.orNull)
(_, callContext) <- NewStyle.function.getJsonDynamicMessageDocById(None, dynamicMessageDocId, cc.callContext)
(dynamicMessageDoc, callContext) <- NewStyle.function.updateJsonDynamicMessageDoc(None, dynamicMessageDocBody.copy(dynamicMessageDocId=Some(dynamicMessageDocId)), callContext)
} yield {
(dynamicMessageDoc, HttpCode.`200`(callContext))
}
@ -9727,7 +10022,7 @@ trait APIMethods400 {
case "management" :: "dynamic-message-docs" :: dynamicMessageDocId :: Nil JsonGet _ => {
cc =>
for {
(dynamicMessageDoc, callContext) <- NewStyle.function.getJsonDynamicMessageDocById(dynamicMessageDocId, cc.callContext)
(dynamicMessageDoc, callContext) <- NewStyle.function.getJsonDynamicMessageDocById(None, dynamicMessageDocId, cc.callContext)
} yield {
(dynamicMessageDoc, HttpCode.`200`(callContext))
}
@ -9758,7 +10053,7 @@ trait APIMethods400 {
case "management" :: "dynamic-message-docs" :: Nil JsonGet _ => {
cc =>
for {
(dynamicMessageDocs, callContext) <- NewStyle.function.getJsonDynamicMessageDocs(cc.callContext)
(dynamicMessageDocs, callContext) <- NewStyle.function.getJsonDynamicMessageDocs(None, cc.callContext)
} yield {
(ListResult("dynamic-message-docs", dynamicMessageDocs), HttpCode.`200`(callContext))
}
@ -9789,8 +10084,147 @@ trait APIMethods400 {
case "management" :: "dynamic-message-docs" :: dynamicMessageDocId :: Nil JsonDelete _ => {
cc =>
for {
(_, callContext) <- NewStyle.function.getJsonDynamicMessageDocById(dynamicMessageDocId, cc.callContext)
(dynamicResourceDoc, callContext) <- NewStyle.function.deleteJsonDynamicMessageDocById(dynamicMessageDocId, callContext)
(_, callContext) <- NewStyle.function.getJsonDynamicMessageDocById(None, dynamicMessageDocId, cc.callContext)
(dynamicResourceDoc, callContext) <- NewStyle.function.deleteJsonDynamicMessageDocById(None, dynamicMessageDocId, callContext)
} yield {
(dynamicResourceDoc, HttpCode.`204`(callContext))
}
}
}
staticResourceDocs += ResourceDoc(
updateBankLevelDynamicMessageDoc,
implementedInApiVersion,
nameOf(updateBankLevelDynamicMessageDoc),
"PUT",
"/management/banks/BANK_ID/dynamic-message-docs/DYNAMIC_MESSAGE_DOC_ID",
"Update Bank Level Dynamic Message Doc",
s"""Update a Bank Level Dynamic Message Doc.
|""",
jsonDynamicMessageDoc.copy(dynamicMessageDocId=None),
jsonDynamicMessageDoc,
List(
$BankNotFound,
$UserNotLoggedIn,
UserHasMissingRoles,
InvalidJsonFormat,
UnknownError
),
List(apiTagDynamicMessageDoc, apiTagNewStyle),
Some(List(canUpdateDynamicMessageDoc)))
lazy val updateBankLevelDynamicMessageDoc: OBPEndpoint = {
case "management" :: "banks" :: bankId::"dynamic-message-docs" :: dynamicMessageDocId :: Nil JsonPut json -> _ => {
cc =>
for {
dynamicMessageDocBody <- NewStyle.function.tryons(s"$InvalidJsonFormat The Json body should be the $JsonDynamicMessageDoc", 400, cc.callContext) {
json.extract[JsonDynamicMessageDoc]
}
connectorMethod = DynamicConnector.createFunction(dynamicMessageDocBody.process, dynamicMessageDocBody.decodedMethodBody)
errorMsg = if(connectorMethod.isEmpty) s"$ConnectorMethodBodyCompileFail ${connectorMethod.asInstanceOf[Failure].msg}" else ""
_ <- Helper.booleanToFuture(failMsg = errorMsg, cc=cc.callContext) {
connectorMethod.isDefined
}
_ = Validation.validateDependency(connectorMethod.orNull)
(_, callContext) <- NewStyle.function.getJsonDynamicMessageDocById(Some(bankId), dynamicMessageDocId, cc.callContext)
(dynamicMessageDoc, callContext) <- NewStyle.function.updateJsonDynamicMessageDoc(Some(bankId), dynamicMessageDocBody.copy(dynamicMessageDocId=Some(dynamicMessageDocId)), callContext)
} yield {
(dynamicMessageDoc, HttpCode.`200`(callContext))
}
}
}
staticResourceDocs += ResourceDoc(
getBankLevelDynamicMessageDoc,
implementedInApiVersion,
nameOf(getBankLevelDynamicMessageDoc),
"GET",
"/management/banks/BANK_ID/dynamic-message-docs/DYNAMIC_MESSAGE_DOC_ID",
"Get Bank Level Dynamic Message Doc",
s"""Get a Bank Level Dynamic Message Doc by DYNAMIC_MESSAGE_DOC_ID.
|
|""",
EmptyBody,
jsonDynamicMessageDoc,
List(
$BankNotFound,
$UserNotLoggedIn,
UserHasMissingRoles,
UnknownError
),
List(apiTagDynamicMessageDoc, apiTagNewStyle),
Some(List(canGetBankLevelDynamicMessageDoc)))
lazy val getBankLevelDynamicMessageDoc: OBPEndpoint = {
case "management" :: "banks" :: bankId :: "dynamic-message-docs" :: dynamicMessageDocId :: Nil JsonGet _ => {
cc =>
for {
(dynamicMessageDoc, callContext) <- NewStyle.function.getJsonDynamicMessageDocById(None, dynamicMessageDocId, cc.callContext)
} yield {
(dynamicMessageDoc, HttpCode.`200`(callContext))
}
}
}
staticResourceDocs += ResourceDoc(
getAllBankLevelDynamicMessageDocs,
implementedInApiVersion,
nameOf(getAllBankLevelDynamicMessageDocs),
"GET",
"/management/banks/BANK_ID/dynamic-message-docs",
"Get all Bank Level Dynamic Message Docs",
s"""Get all Bank Level Dynamic Message Docs.
|
|""",
EmptyBody,
ListResult("dynamic-message-docs", jsonDynamicMessageDoc::Nil),
List(
$BankNotFound,
$UserNotLoggedIn,
UserHasMissingRoles,
UnknownError
),
List(apiTagDynamicMessageDoc, apiTagNewStyle),
Some(List(canGetAllDynamicMessageDocs)))
lazy val getAllBankLevelDynamicMessageDocs: OBPEndpoint = {
case "management" :: "banks" :: bankId :: "dynamic-message-docs" :: Nil JsonGet _ => {
cc =>
for {
(dynamicMessageDocs, callContext) <- NewStyle.function.getJsonDynamicMessageDocs(Some(bankId), cc.callContext)
} yield {
(ListResult("dynamic-message-docs", dynamicMessageDocs), HttpCode.`200`(callContext))
}
}
}
staticResourceDocs += ResourceDoc(
deleteBankLevelDynamicMessageDoc,
implementedInApiVersion,
nameOf(deleteBankLevelDynamicMessageDoc),
"DELETE",
"/management/banks/BANK_ID/dynamic-message-docs/DYNAMIC_MESSAGE_DOC_ID",
"Delete Bank Level Dynamic Message Doc",
s"""Delete a Bank Level Dynamic Message Doc.
|""",
EmptyBody,
BooleanBody(true),
List(
$BankNotFound,
$UserNotLoggedIn,
UserHasMissingRoles,
InvalidJsonFormat,
UnknownError
),
List(apiTagDynamicMessageDoc, apiTagNewStyle),
Some(List(canDeleteBankLevelDynamicMessageDoc)))
lazy val deleteBankLevelDynamicMessageDoc: OBPEndpoint = {
case "management" :: "banks" :: bankId :: "dynamic-message-docs" :: dynamicMessageDocId :: Nil JsonDelete _ => {
cc =>
for {
(_, callContext) <- NewStyle.function.getJsonDynamicMessageDocById(Some(bankId), dynamicMessageDocId, cc.callContext)
(dynamicResourceDoc, callContext) <- NewStyle.function.deleteJsonDynamicMessageDocById(Some(bankId), dynamicMessageDocId, callContext)
} yield {
(dynamicResourceDoc, HttpCode.`204`(callContext))
}

View File

@ -1,7 +1,8 @@
package code.api.v4_0_0.dynamic
import code.api.util.APIUtil.{OBPEndpoint, OBPReturnType, futureToBoxedResponse, scalaFutureToLaFuture}
import code.api.util.{CallContext, CustomJsonFormats}
import code.api.util.DynamicUtil.{Sandbox, Validation}
import code.api.util.{CallContext, CustomJsonFormats, DynamicUtil}
import net.liftweb.common.Box
import net.liftweb.http.{JsonResponse, Req}
@ -12,19 +13,34 @@ import net.liftweb.http.{JsonResponse, Req}
trait DynamicCompileEndpoint {
implicit val formats = CustomJsonFormats.formats
protected def process(callContext: CallContext, request: Req): Box[JsonResponse]
// * is any bankId
val boundBankId: String
protected def process(callContext: CallContext, request: Req, pathParams: Map[String, String]): Box[JsonResponse]
val endpoint: OBPEndpoint = new OBPEndpoint {
override def isDefinedAt(x: Req): Boolean = true
override def apply(request: Req): CallContext => Box[JsonResponse] = process(_, request)
override def apply(request: Req): CallContext => Box[JsonResponse] = { cc =>
val Some(pathParams) = cc.resourceDocument.map(_.getPathParams(request.path.partPath))
validateDependencies()
Sandbox.sandbox(boundBankId).runInSandbox {
process(cc, request, pathParams)
}
}
}
protected implicit def scalaFutureToBoxedJsonResponse[T](scf: OBPReturnType[T])(implicit m: Manifest[T]): Box[JsonResponse] = {
futureToBoxedResponse(scalaFutureToLaFuture(scf))
}
protected def getPathParams(callContext: CallContext, request: Req): Map[String, String] = {
val Some(resourceDoc) = callContext.resourceDocument
resourceDoc.getPathParams(request.path.partPath)
private def validateDependencies() = {
val dependencies = DynamicUtil.getDynamicCodeDependentMethods(this.getClass, "process" == )
Validation.validateDependency(dependencies)
}
}
object DynamicCompileEndpoint {
implicit def scalaFutureToBoxedJsonResponse[T](scf: OBPReturnType[T])(implicit m: Manifest[T]): Box[JsonResponse] = {
futureToBoxedResponse(scalaFutureToLaFuture(scf))
}
}

View File

@ -1,5 +1,5 @@
package code.api.v4_0_0.dynamic
import code.api.util.DynamicUtil.{Sandbox, Validation}
import code.api.util.APIUtil.{BooleanBody, DoubleBody, EmptyBody, LongBody, OBPEndpoint, PrimaryDataBody, ResourceDoc, StringBody, getDisabledEndpointOperationIds}
import code.api.util.{CallContext, DynamicUtil}
import code.api.v4_0_0.dynamic.practise.{DynamicEndpointCodeGenerator, PractiseEndpointGroup}
@ -125,7 +125,7 @@ case class CompiledObjects(exampleRequestBody: Option[JValue], successResponseBo
}
val successResponse: Product = toCaseObject(successResponseBody)
val partialFunction: OBPEndpoint = {
private val partialFunction: OBPEndpoint = {
//If the requestBody is PrimaryDataBody, return None. otherwise, return the exampleRequestBody:Option[JValue]
// In side OBP resourceDoc, requestBody and successResponse must be Product type
@ -148,26 +148,36 @@ case class CompiledObjects(exampleRequestBody: Option[JValue], successResponseBo
val code =
s"""
|import code.api.util.APIUtil.errorJsonResponse
|import code.api.util.CallContext
|import code.api.util.ErrorMessages.{InvalidJsonFormat, InvalidRequestPayload}
|import code.api.util.NewStyle.HttpCode
|import code.api.v4_0_0.dynamic.DynamicCompileEndpoint
|import code.api.util.APIUtil.{OBPReturnType, futureToBoxedResponse, scalaFutureToLaFuture, errorJsonResponse}
|
|import net.liftweb.common.{Box, EmptyBox, Full}
|import net.liftweb.http.{JsonResponse, Req}
|import net.liftweb.json.MappingException
|
|import scala.concurrent.Future
|import com.openbankproject.commons.ExecutionContext.Implicits.global
|
|implicit def scalaFutureToBoxedJsonResponse[T](scf: OBPReturnType[T])(implicit m: Manifest[T]): Box[JsonResponse] = {
| futureToBoxedResponse(scalaFutureToLaFuture(scf))
|}
|
|implicit val formats = code.api.util.CustomJsonFormats.formats
|
|$requestBodyCaseClasses
|
|$responseBodyCaseClasses
|
|(new DynamicCompileEndpoint {
| override protected def process(callContext: CallContext, request: Req): Box[JsonResponse] = {
| $decodedMethodBody
| }
|}).endpoint
|val endpoint: code.api.util.APIUtil.OBPEndpoint = {
| case request => { callContext =>
| val Some(pathParams) = callContext.resourceDocument.map(_.getPathParams(request.path.partPath))
| $decodedMethodBody
| }
|}
|
|endpoint
|
|""".stripMargin
val endpointMethod = DynamicUtil.compileScalaCode[OBPEndpoint](code)
@ -180,6 +190,37 @@ case class CompiledObjects(exampleRequestBody: Option[JValue], successResponseBo
}
}
/**
* this will check all the dynamic scala code dependencies at compile time.
*
*Search for the usage, you can see how to use it in OBP code.
*/
def validateDependency() = Validation.validateDependency(this.partialFunction)
/**
* This is used to check the security permission at the run time.
* all the obp partialFunctions will be wrapped into the sandbox which under the permission control.
*
*/
def sandboxEndpoint(bankId: Option[String]) : OBPEndpoint = {
val sandbox = bankId match {
case Some(v) if StringUtils.isNotBlank(v) =>
Sandbox.sandbox(v)
case _ => Sandbox.sandbox("*")
}
new OBPEndpoint {
override def isDefinedAt(req: Req): Boolean = partialFunction.isDefinedAt(req)
// run dynamic code in sandbox
override def apply(req: Req): CallContext => Box[JsonResponse] = {cc =>
val fn = partialFunction.apply(req)
sandbox.runInSandbox(fn(cc))
}
}
}
private def toCaseObject(jValue: Option[JValue]): Product = {
if (jValue.isEmpty || jValue.exists(JNothing ==)) {
EmptyBody
@ -195,4 +236,3 @@ case class CompiledObjects(exampleRequestBody: Option[JValue], successResponseBo
}
}

View File

@ -13,7 +13,7 @@ object DynamicResourceDocsEndpointGroup extends EndpointGroup {
override protected def resourceDocs: List[APIUtil.ResourceDoc] =
DynamicResourceDocProvider.provider.vend.getAllAndConvert(toResourceDoc)
DynamicResourceDocProvider.provider.vend.getAllAndConvert(None, toResourceDoc) //TODO need to check if this can be `NONE`
private val apiVersion : ScannedApiVersion = ApiVersion.v4_0_0
@ -37,7 +37,7 @@ object DynamicResourceDocsEndpointGroup extends EndpointGroup {
private val toResourceDoc: JsonDynamicResourceDoc => ResourceDoc = { dynamicDoc =>
val compiledObjects = CompiledObjects(dynamicDoc.exampleRequestBody, dynamicDoc.successResponseBody, dynamicDoc.methodBody)
ResourceDoc(
partialFunction = compiledObjects.partialFunction, //connectorMethodBody
partialFunction = compiledObjects.sandboxEndpoint(dynamicDoc.bankId),
implementedInApiVersion = apiVersion,
partialFunctionName = dynamicDoc.partialFunctionName + "_" + (dynamicDoc.requestVerb + dynamicDoc.requestUrl).hashCode,
requestVerb = dynamicDoc.requestVerb,

View File

@ -1,35 +1,55 @@
package code.api.v4_0_0.dynamic.practise
import code.api.util.APIUtil.errorJsonResponse
import code.api.util.CallContext
import code.api.util.ErrorMessages.{InvalidJsonFormat, InvalidRequestPayload}
import code.api.util.NewStyle.HttpCode
import code.api.v4_0_0.dynamic.DynamicCompileEndpoint
import net.liftweb.common.{Box, EmptyBox, Full}
import net.liftweb.http.{JsonResponse, Req}
import net.liftweb.json.MappingException
import scala.concurrent.Future
// any import statement here need be moved into the process method body
/**
* practise new endpoint at this object, don't commit you practise code to git
*
* This endpoint is only for testing new dynamic resource/messages method body.
* eg: when you try the create dynamic resource doc endpoint, you need to prepare the method body properly.
* you can prepare the obp scala code just under the method:
* `process(callContext: CallContext, request: Req, pathParams: Map[String, String])`,
*
*
*
*/
object PractiseEndpoint extends DynamicCompileEndpoint {
// all request case classes
case class RequestRootJsonClass(name: String, age: Long, hobby: Option[List[String]])
object PractiseEndpoint extends code.api.v4_0_0.dynamic.DynamicCompileEndpoint {
// don't modify these import statement
import code.api.util.CallContext
import code.api.util.ErrorMessages.{InvalidJsonFormat, InvalidRequestPayload}
import code.api.util.NewStyle.HttpCode
import code.api.util.APIUtil.{OBPReturnType, futureToBoxedResponse, scalaFutureToLaFuture, errorJsonResponse}
import net.liftweb.common.{Box, EmptyBox, Full}
import net.liftweb.http.{JsonResponse, Req}
import net.liftweb.json.MappingException
// all response case classes
case class ResponseRootJsonClass(my_user_id: String, name: String, age: Long, hobby: Option[List[String]])
import scala.concurrent.Future
import com.openbankproject.commons.ExecutionContext.Implicits.global
import code.api.v4_0_0.dynamic.DynamicCompileEndpoint._
// request method
val requestMethod = "POST"
val requestUrl = "/my_user/MY_USER_ID"
// all request case classes
case class RequestRootJsonClass(name: String, age: Long, hobby: List[String])
// all response case classes
case class ResponseRootJsonClass(my_user_id: String, name: String, age: Long, hobby: List[String])
// * is any bankId, if bound to other bankId, just modify this value to correct one
override val boundBankId = "abc"
// copy the whole method body as "dynamicResourceDoc" method body
override protected def process(callContext: CallContext, request: Req): Box[JsonResponse] = {
override protected def
process(callContext: CallContext, request: Req, pathParams: Map[String, String]) : Box[JsonResponse] = {
// please add import sentences here, those used by this method
import code.api.util.NewStyle
import code.api.v4_0_0.JSONFactory400
val Some(resourceDoc) = callContext.resourceDocument
val hasRequestBody = request.body.isDefined
@ -38,14 +58,12 @@ object PractiseEndpoint extends DynamicCompileEndpoint {
// if the requestUrl of resourceDoc is /hello/banks/BANK_ID/world
// the request path is /hello/banks/bank_x/world
//pathParams.get("BANK_ID") will get Option("bank_x") value
val pathParams = getPathParams(callContext, request)
val myUserId = pathParams("MY_USER_ID")
val requestEntity = request.json match {
case Full(zson) =>
try {
zson.extract[RequestRootJsonClass]
zson.extract[RequestRootJsonClass]
} catch {
case e: MappingException =>
return Full(errorJsonResponse(s"$InvalidJsonFormat ${e.msg}"))
@ -53,12 +71,13 @@ object PractiseEndpoint extends DynamicCompileEndpoint {
case _: EmptyBox =>
return Full(errorJsonResponse(s"$InvalidRequestPayload Current request has no payload"))
}
// please add business logic here
val responseBody:ResponseRootJsonClass = ResponseRootJsonClass(s"${myUserId}_from_path", requestEntity.name, requestEntity.age, requestEntity.hobby)
Future.successful {
(responseBody, HttpCode.`200`(callContext.callContext))
val responseBody:ResponseRootJsonClass = ResponseRootJsonClass(s"${myUserId}_from_path", requestEntity.name, requestEntity.age, requestEntity.hobby)
for {
(banks, callContext) <- NewStyle.function.getBanks(Some(callContext))
} yield {
(JSONFactory400.createBanksJson(banks), HttpCode.`200`(callContext))
}
}

View File

@ -1,5 +1,6 @@
package code.api.v4_0_0.dynamic.practise
import code.api.ResourceDocs1_4_0.SwaggerDefinitionsJSON.requestRootJsonClass
import code.api.util.APIUtil
import code.api.util.APIUtil.{ResourceDoc, StringBody}
import code.api.util.ApiTag.{apiTagDynamicResourceDoc, apiTagNewStyle}
@ -24,10 +25,18 @@ object PractiseEndpointGroup extends EndpointGroup{
PractiseEndpoint.requestUrl,
"A test endpoint",
s"""A test endpoint.
|Just for debug method body of dynamic resource doc
|
|Just for debug method body of dynamic resource doc.
|better watch the following introduction video first
|* [Dynamic resourceDoc version1](https://vimeo.com/623381607)
|
|The endpoint return the response from PractiseEndpoint code.
|Here, code.api.v4_0_0.dynamic.practise.PractiseEndpoint.process
|You can test the method body grammar, and try the business logic, but need to restart the OBP-API code .
|
|""",
StringBody("Any request body"),
StringBody("Any response body"),
requestRootJsonClass,
requestRootJsonClass,
List(
UnknownError
),

View File

@ -4,9 +4,8 @@ import code.api.util.DynamicUtil.compileScalaCode
import code.api.util.{CallContext, DynamicUtil}
import code.dynamicMessageDoc.{DynamicMessageDocProvider, JsonDynamicMessageDoc}
import net.liftweb.common.Box
import com.openbankproject.commons.ExecutionContext.Implicits.global
import scala.concurrent.Future
import code.api.util.APIUtil.{EntitlementAndScopeStatus, JsonResponseExtractor, OBPReturnType}
object DynamicConnector {
@ -23,14 +22,13 @@ object DynamicConnector {
def updateSingletonObject(key:String, value: Any) = singletonObjectMap.update(key, value)
def removeSingletonObject(key:String) = singletonObjectMap.remove(key)
def invoke(process: String, args: Array[AnyRef], callContext: Option[CallContext]) = {
val function: Box[(Array[AnyRef], Option[CallContext]) => Future[Box[(AnyRef, Option[CallContext])]]] =
getFunction(process).asInstanceOf[Box[(Array[AnyRef], Option[CallContext]) => Future[Box[(AnyRef, Option[CallContext])]]]]
def invoke(bankId: Option[String], process: String, args: Array[AnyRef], callContext: Option[CallContext]) = {
val function = getFunction(bankId, process)
function.map(f =>f(args: Array[AnyRef], callContext: Option[CallContext])).openOrThrowException(s"There is no process $process, it should not be called here")
}
}
private def getFunction(process: String) = {
DynamicMessageDocProvider.provider.vend.getByProcess(process) map {
private def getFunction(bankId: Option[String], process: String) = {
DynamicMessageDocProvider.provider.vend.getByProcess(bankId, process) map {
case v :JsonDynamicMessageDoc =>
createFunction(process, v.decodedMethodBody).openOrThrowException(s"InternalConnector method compile fail")
}
@ -44,17 +42,20 @@ object DynamicConnector {
* @param methodBody method body of connector method
* @return function of connector method that is dynamic created, can be Function0, Function1, Function2...
*/
def createFunction(process: String, methodBody:String): Box[Any] =
def createFunction(process: String, methodBody:String): Box[(Array[AnyRef], Option[CallContext]) => Future[Box[(AnyRef, Option[CallContext])]]] =
{
//messageDoc.process is a bit different with the methodName, we need tweak the format of it:
//eg: process("obp.getBank") ==> methodName("getBank")
val method = s"""
|${DynamicUtil.importStatements}
|def func(args: Array[AnyRef], callContext: Option[CallContext]): Future[Box[(AnyRef, Option[CallContext])]] = {
| $methodBody
|
|val fn = new ((Array[AnyRef], Option[CallContext]) => Future[Box[(AnyRef, Option[CallContext])]]) (){
| override def apply(args: Array[AnyRef], callContext: Option[CallContext]): Future[Box[(AnyRef, Option[CallContext])]] = {
| $methodBody
| }
|}
|
|func _
|fn
|""".stripMargin
compileScalaCode(method)
}

View File

@ -50,7 +50,7 @@ object InternalConnector {
* @param methodBody method body of connector method
* @return function of connector method that is dynamic created, can be Function0, Function1, Function2...
*/
def createFunction(methodName: String, methodBody:String): Box[Any] =
def createFunction(methodName: String, methodBody:String)=
methodNameToSignature.get(methodName) match {
case Some(signature) =>
val method = s"""

View File

@ -9,6 +9,7 @@ class DynamicMessageDoc extends LongKeyedMapper[DynamicMessageDoc] with IdPK {
override def getSingleton = DynamicMessageDoc
object BankId extends MappedString(this, 255)
object DynamicMessageDocId extends UUIDString(this)
object Process extends MappedString(this, 255)
object MessageFormat extends MappedString(this, 255)
@ -27,6 +28,7 @@ class DynamicMessageDoc extends LongKeyedMapper[DynamicMessageDoc] with IdPK {
object DynamicMessageDoc extends DynamicMessageDoc with LongKeyedMetaMapper[DynamicMessageDoc] {
override def dbIndexes: List[BaseIndex[DynamicMessageDoc]] = UniqueIndex(DynamicMessageDocId) :: UniqueIndex(Process) :: super.dbIndexes
def getJsonDynamicMessageDoc(dynamicMessageDoc: DynamicMessageDoc) = JsonDynamicMessageDoc(
bankId = Some(dynamicMessageDoc.BankId.get),
dynamicMessageDocId = Some(dynamicMessageDoc.DynamicMessageDocId.get),
process = dynamicMessageDoc.Process.get,
messageFormat = dynamicMessageDoc.MessageFormat.get,

View File

@ -16,6 +16,7 @@ object DynamicMessageDocProvider extends SimpleInjector {
}
case class JsonDynamicMessageDoc(
bankId: Option[String],
dynamicMessageDocId: Option[String],
process: String,
messageFormat: String,
@ -34,12 +35,12 @@ case class JsonDynamicMessageDoc(
trait DynamicMessageDocProvider {
def getById(dynamicMessageDocId: String): Box[JsonDynamicMessageDoc]
def getByProcess(process: String): Box[JsonDynamicMessageDoc]
def getAll(): List[JsonDynamicMessageDoc]
def getById(bankId: Option[String], dynamicMessageDocId: String): Box[JsonDynamicMessageDoc]
def getByProcess(bankId: Option[String], process: String): Box[JsonDynamicMessageDoc]
def getAll(bankId: Option[String]): List[JsonDynamicMessageDoc]
def create(entity: JsonDynamicMessageDoc): Box[JsonDynamicMessageDoc]
def update(entity: JsonDynamicMessageDoc): Box[JsonDynamicMessageDoc]
def deleteById(dynamicMessageDocId: String): Box[Boolean]
def create(bankId: Option[String], entity: JsonDynamicMessageDoc): Box[JsonDynamicMessageDoc]
def update(bankId: Option[String], entity: JsonDynamicMessageDoc): Box[JsonDynamicMessageDoc]
def deleteById(bankId: Option[String], dynamicMessageDocId: String): Box[Boolean]
}

View File

@ -7,6 +7,7 @@ import net.liftweb.common.{Box, Empty, Full}
import net.liftweb.mapper._
import net.liftweb.util.Helpers.tryo
import net.liftweb.util.Props
import java.util.UUID.randomUUID
import code.util.Helper
@ -19,27 +20,43 @@ object MappedDynamicMessageDocProvider extends DynamicMessageDocProvider {
else APIUtil.getPropsValue(s"dynamicMessageDoc.cache.ttl.seconds", "40").toInt
}
override def getById(dynamicMessageDocId: String): Box[JsonDynamicMessageDoc] =
DynamicMessageDoc.find(By(DynamicMessageDoc.DynamicMessageDocId, dynamicMessageDocId))
.map(DynamicMessageDoc.getJsonDynamicMessageDoc)
override def getById(bankId: Option[String], dynamicMessageDocId: String): Box[JsonDynamicMessageDoc] =
if(bankId.isEmpty) {
DynamicMessageDoc.find(By(DynamicMessageDoc.DynamicMessageDocId, dynamicMessageDocId)).map(DynamicMessageDoc.getJsonDynamicMessageDoc)
}else{
DynamicMessageDoc.find(
By(DynamicMessageDoc.DynamicMessageDocId, dynamicMessageDocId),
By(DynamicMessageDoc.BankId, bankId.getOrElse("")
)).map(DynamicMessageDoc.getJsonDynamicMessageDoc)
}
override def getByProcess(process: String): Box[JsonDynamicMessageDoc] =
DynamicMessageDoc.find(By(DynamicMessageDoc.Process, process))
.map(DynamicMessageDoc.getJsonDynamicMessageDoc)
override def getByProcess(bankId: Option[String], process: String): Box[JsonDynamicMessageDoc] =
if(bankId.isEmpty) {
DynamicMessageDoc.find(By(DynamicMessageDoc.Process, process)).map(DynamicMessageDoc.getJsonDynamicMessageDoc)
}else{
DynamicMessageDoc.find(
By(DynamicMessageDoc.Process, process),
By(DynamicMessageDoc.BankId, bankId.getOrElse("")
)).map(DynamicMessageDoc.getJsonDynamicMessageDoc)
}
override def getAll(): List[JsonDynamicMessageDoc] = {
override def getAll(bankId: Option[String]): List[JsonDynamicMessageDoc] = {
var cacheKey = (randomUUID().toString, randomUUID().toString, randomUUID().toString)
CacheKeyFromArguments.buildCacheKey {
Caching.memoizeSyncWithProvider (Some(cacheKey.toString())) (getDynamicMessageDocTTL second) {
DynamicMessageDoc.findAll()
.map(DynamicMessageDoc.getJsonDynamicMessageDoc)
if(bankId.isEmpty){
DynamicMessageDoc.findAll().map(DynamicMessageDoc.getJsonDynamicMessageDoc)
} else {
DynamicMessageDoc.findAll(By(DynamicMessageDoc.BankId, bankId.getOrElse(""))).map(DynamicMessageDoc.getJsonDynamicMessageDoc)
}
}}
}
override def create(entity: JsonDynamicMessageDoc): Box[JsonDynamicMessageDoc]=
override def create(bankId: Option[String], entity: JsonDynamicMessageDoc): Box[JsonDynamicMessageDoc]= {
tryo {
DynamicMessageDoc.create
.BankId(bankId.getOrElse(null))
.DynamicMessageDocId(APIUtil.generateUUID())
.Process(entity.process)
.MessageFormat(entity.messageFormat)
@ -54,10 +71,20 @@ object MappedDynamicMessageDocProvider extends DynamicMessageDocProvider {
.MethodBody(entity.methodBody)
.saveMe()
}.map(DynamicMessageDoc.getJsonDynamicMessageDoc)
}
override def update(entity: JsonDynamicMessageDoc): Box[JsonDynamicMessageDoc] = {
DynamicMessageDoc.find(By(DynamicMessageDoc.DynamicMessageDocId, entity.dynamicMessageDocId.getOrElse(""))) match {
override def update(bankId: Option[String], entity: JsonDynamicMessageDoc): Box[JsonDynamicMessageDoc] = {
val dynamicMessageDocBox = if(bankId.isDefined){
DynamicMessageDoc.find(
By(DynamicMessageDoc.DynamicMessageDocId, entity.dynamicMessageDocId.getOrElse(""))
)
} else {
DynamicMessageDoc.find(
By(DynamicMessageDoc.DynamicMessageDocId, entity.dynamicMessageDocId.getOrElse(""))
)
}
dynamicMessageDocBox match {
case Full(v) =>
tryo {
v.DynamicMessageDocId(entity.dynamicMessageDocId.getOrElse(""))
@ -78,7 +105,14 @@ object MappedDynamicMessageDocProvider extends DynamicMessageDocProvider {
}
}
override def deleteById(id: String): Box[Boolean] = tryo {
DynamicMessageDoc.bulkDelete_!!(By(DynamicMessageDoc.DynamicMessageDocId, id))
override def deleteById(bankId: Option[String], id: String): Box[Boolean] = tryo {
if(bankId.isEmpty) {
DynamicMessageDoc.bulkDelete_!!(By(DynamicMessageDoc.DynamicMessageDocId, id))
}else{
DynamicMessageDoc.bulkDelete_!!(
By(DynamicMessageDoc.BankId, bankId.getOrElse("")),
By(DynamicMessageDoc.DynamicMessageDocId, id)
)
}
}
}

View File

@ -10,7 +10,8 @@ import scala.collection.immutable.List
class DynamicResourceDoc extends LongKeyedMapper[DynamicResourceDoc] with IdPK {
override def getSingleton = DynamicResourceDoc
object BankId extends MappedString(this, 255)
object DynamicResourceDocId extends UUIDString(this)
object PartialFunctionName extends MappedString(this, 255)
object RequestVerb extends MappedString(this, 255)
@ -30,6 +31,7 @@ class DynamicResourceDoc extends LongKeyedMapper[DynamicResourceDoc] with IdPK {
object DynamicResourceDoc extends DynamicResourceDoc with LongKeyedMetaMapper[DynamicResourceDoc] {
override def dbIndexes: List[BaseIndex[DynamicResourceDoc]] = UniqueIndex(DynamicResourceDocId) :: UniqueIndex(RequestUrl,RequestVerb) :: super.dbIndexes
def getJsonDynamicResourceDoc(dynamicResourceDoc: DynamicResourceDoc) = JsonDynamicResourceDoc(
bankId = Some(dynamicResourceDoc.BankId.get),
dynamicResourceDocId = Some(dynamicResourceDoc.DynamicResourceDocId.get),
methodBody = dynamicResourceDoc.MethodBody.get,
partialFunctionName = dynamicResourceDoc.PartialFunctionName.get,

View File

@ -20,6 +20,7 @@ object DynamicResourceDocProvider extends SimpleInjector {
}
case class JsonDynamicResourceDoc(
bankId: Option[String],
dynamicResourceDocId: Option[String],
methodBody: String,
partialFunctionName: String,
@ -38,15 +39,15 @@ case class JsonDynamicResourceDoc(
trait DynamicResourceDocProvider {
def getById(dynamicResourceDocId: String): Box[JsonDynamicResourceDoc]
def getByVerbAndUrl(requestVerb: String, requestUrl: String): Box[JsonDynamicResourceDoc]
def getById(bankId: Option[String], dynamicResourceDocId: String): Box[JsonDynamicResourceDoc]
def getByVerbAndUrl(bankId: Option[String], requestVerb: String, requestUrl: String): Box[JsonDynamicResourceDoc]
def getAll(): List[JsonDynamicResourceDoc] = getAllAndConvert(identity)
def getAll(bankId: Option[String]): List[JsonDynamicResourceDoc] = getAllAndConvert(bankId, identity)
def getAllAndConvert[T: Manifest](transform: JsonDynamicResourceDoc => T): List[T]
def getAllAndConvert[T: Manifest](bankId: Option[String], transform: JsonDynamicResourceDoc => T): List[T]
def create(entity: JsonDynamicResourceDoc): Box[JsonDynamicResourceDoc]
def update(entity: JsonDynamicResourceDoc): Box[JsonDynamicResourceDoc]
def deleteById(dynamicResourceDocId: String): Box[Boolean]
def create(bankId: Option[String], entity: JsonDynamicResourceDoc): Box[JsonDynamicResourceDoc]
def update(bankId: Option[String], entity: JsonDynamicResourceDoc): Box[JsonDynamicResourceDoc]
def deleteById(bankId: Option[String], dynamicResourceDocId: String): Box[Boolean]
}

View File

@ -20,29 +20,58 @@ object MappedDynamicResourceDocProvider extends DynamicResourceDocProvider {
else APIUtil.getPropsValue(s"dynamicResourceDoc.cache.ttl.seconds", "40").toInt
}
override def getById(dynamicResourceDocId: String): Box[JsonDynamicResourceDoc] = DynamicResourceDoc
.find(By(DynamicResourceDoc.DynamicResourceDocId, dynamicResourceDocId))
.map(DynamicResourceDoc.getJsonDynamicResourceDoc)
override def getById(bankId: Option[String], dynamicResourceDocId: String): Box[JsonDynamicResourceDoc] = {
if(bankId.isEmpty){
DynamicResourceDoc
.find(By(DynamicResourceDoc.DynamicResourceDocId, dynamicResourceDocId))
.map(DynamicResourceDoc.getJsonDynamicResourceDoc)
} else{
DynamicResourceDoc
.find(
By(DynamicResourceDoc.DynamicResourceDocId, dynamicResourceDocId),
By(DynamicResourceDoc.BankId, bankId.getOrElse("")),
)
.map(DynamicResourceDoc.getJsonDynamicResourceDoc)
}
}
override def getByVerbAndUrl(requestVerb: String, requestUrl: String): Box[JsonDynamicResourceDoc] = DynamicResourceDoc
.find(By(DynamicResourceDoc.RequestVerb, requestVerb), By(DynamicResourceDoc.RequestUrl, requestUrl))
.map(DynamicResourceDoc.getJsonDynamicResourceDoc)
override def getByVerbAndUrl(bankId: Option[String], requestVerb: String, requestUrl: String): Box[JsonDynamicResourceDoc] =
if(bankId.isEmpty){
DynamicResourceDoc
.find(By(DynamicResourceDoc.RequestVerb, requestVerb), By(DynamicResourceDoc.RequestUrl, requestUrl))
.map(DynamicResourceDoc.getJsonDynamicResourceDoc)
} else{
DynamicResourceDoc
.find(
By(DynamicResourceDoc.BankId, bankId.getOrElse("")),
By(DynamicResourceDoc.RequestVerb, requestVerb),
By(DynamicResourceDoc.RequestUrl, requestUrl))
.map(DynamicResourceDoc.getJsonDynamicResourceDoc)
}
override def getAllAndConvert[T: Manifest](transform: JsonDynamicResourceDoc => T): List[T] = {
override def getAllAndConvert[T: Manifest](bankId: Option[String], transform: JsonDynamicResourceDoc => T): List[T] = {
var cacheKey = (randomUUID().toString, randomUUID().toString, randomUUID().toString)
CacheKeyFromArguments.buildCacheKey {
Caching.memoizeSyncWithProvider (Some(cacheKey.toString())) (getDynamicResourceDocTTL second) {
DynamicResourceDoc.findAll()
.map(doc => transform(DynamicResourceDoc.getJsonDynamicResourceDoc(doc)))
}}
if(bankId.isEmpty){
DynamicResourceDoc.findAll()
.map(doc => transform(DynamicResourceDoc.getJsonDynamicResourceDoc(doc)))
} else {
DynamicResourceDoc.findAll(
By(DynamicResourceDoc.BankId, bankId.getOrElse("")))
.map(doc => transform(DynamicResourceDoc.getJsonDynamicResourceDoc(doc)))
}
}
}
}
override def create(entity: JsonDynamicResourceDoc): Box[JsonDynamicResourceDoc]=
override def create(bankId: Option[String], entity: JsonDynamicResourceDoc): Box[JsonDynamicResourceDoc]=
tryo {
val requestBody = entity.exampleRequestBody.map(json.compactRender(_)).orNull
val responseBody = entity.successResponseBody.map(json.compactRender(_)).orNull
DynamicResourceDoc.create
.BankId(bankId.getOrElse(null))
.DynamicResourceDocId(APIUtil.generateUUID())
.PartialFunctionName(entity.partialFunctionName)
.RequestVerb(entity.requestVerb)
@ -59,13 +88,14 @@ object MappedDynamicResourceDocProvider extends DynamicResourceDocProvider {
}.map(DynamicResourceDoc.getJsonDynamicResourceDoc)
override def update(entity: JsonDynamicResourceDoc): Box[JsonDynamicResourceDoc] = {
override def update(bankId: Option[String], entity: JsonDynamicResourceDoc): Box[JsonDynamicResourceDoc] = {
DynamicResourceDoc.find(By(DynamicResourceDoc.DynamicResourceDocId, entity.dynamicResourceDocId.getOrElse(""))) match {
case Full(v) =>
tryo {
val requestBody = entity.exampleRequestBody.map(json.compactRender(_)).orNull
val responseBody = entity.successResponseBody.map(json.compactRender(_)).orNull
v.PartialFunctionName(entity.partialFunctionName)
.BankId(bankId.getOrElse(null))
.RequestVerb(entity.requestVerb)
.RequestUrl(entity.requestUrl)
.Summary(entity.summary)
@ -82,8 +112,15 @@ object MappedDynamicResourceDocProvider extends DynamicResourceDocProvider {
}
}
override def deleteById(id: String): Box[Boolean] = tryo {
DynamicResourceDoc.bulkDelete_!!(By(DynamicResourceDoc.DynamicResourceDocId, id))
override def deleteById(bankId: Option[String], id: String): Box[Boolean] = tryo {
if(bankId.isEmpty) {
DynamicResourceDoc.bulkDelete_!!(By(DynamicResourceDoc.DynamicResourceDocId, id))
}else{
DynamicResourceDoc.bulkDelete_!!(
By(DynamicResourceDoc.BankId, bankId.getOrElse("")),
By(DynamicResourceDoc.DynamicResourceDocId, id)
)
}
}
}

View File

@ -0,0 +1,68 @@
/**
Open Bank Project - API
Copyright (C) 2011-2022, TESOBE GmbH.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Email: contact@tesobe.com
TESOBE GmbH.
Osloer Strasse 16/17
Berlin 13359, Germany
This product includes software developed at
TESOBE (http://www.tesobe.com/)
*/
package code.util
import code.api.ResourceDocs1_4_0.SwaggerDefinitionsJSON
import code.api.v4_0_0.V400ServerSetup
import code.api.v4_0_0.dynamic.CompiledObjects
import code.setup.PropsReset
import org.scalatest.Tag
class DynamicEndpointsTest extends V400ServerSetup with PropsReset {
object DynamicUtilsTag extends Tag("DynamicEndpoints")
feature("test DynamicEndpoints.CompiledObjects.validateDependency method") {
scenario("validateDependency should work well "){
//This mean, we are only disabled the v4.0.0, all other versions should be enabled
setPropsValues(
"dynamic_code_compile_validate_enable" -> "true",
"dynamic_code_compile_validate_dependencies" -> """[
| NewStyle.function.getClass.getTypeName -> "*",
| CompiledObjects.getClass.getTypeName -> "sandbox",
| HttpCode.getClass.getTypeName -> "200",
| DynamicCompileEndpoint.getClass.getTypeName -> "getPathParams, scalaFutureToBoxedJsonResponse",
| APIUtil.getClass.getTypeName -> "errorJsonResponse, errorJsonResponse$default$1, errorJsonResponse$default$2, errorJsonResponse$default$3, errorJsonResponse$default$4, scalaFutureToLaFuture, futureToBoxedResponse",
| ErrorMessages.getClass.getTypeName -> "*",
| ExecutionContext.Implicits.getClass.getTypeName -> "global",
| JSONFactory400.getClass.getTypeName -> "createBanksJson",
| classOf[Sandbox].getTypeName -> "runInSandbox",
| classOf[CallContext].getTypeName -> "*",
| classOf[ResourceDoc].getTypeName -> "getPathParams",
| "scala.reflect.runtime.package$" -> "universe",
| PractiseEndpoint.getClass.getTypeName + "*" -> "*"
|]""".stripMargin
)
val jsonDynamicResourceDoc = SwaggerDefinitionsJSON.jsonDynamicResourceDoc
CompiledObjects(jsonDynamicResourceDoc.exampleRequestBody, jsonDynamicResourceDoc.successResponseBody, jsonDynamicResourceDoc.methodBody)
.validateDependency()
}
}
}

View File

@ -27,13 +27,20 @@ TESOBE (http://www.tesobe.com/)
package code.util
import code.api.util.DynamicUtil.Sandbox
import code.api.util._
import code.setup.PropsReset
import com.openbankproject.commons.model.BankId
import com.openbankproject.commons.util.{JsonUtils, ReflectUtils}
import net.liftweb.common.Box
import net.liftweb.common.{Box}
import net.liftweb.json
import org.scalatest.{FeatureSpec, FlatSpec, GivenWhenThen, Matchers, Tag}
import java.io.File
import java.security.{AccessControlException}
import scala.collection.immutable.List
import scala.io.Source
class DynamicUtilTest extends FlatSpec with Matchers {
object DynamicUtilsTag extends Tag("DynamicUtil")
@ -48,6 +55,118 @@ class DynamicUtilTest extends FlatSpec with Matchers {
getBankResponse should be (125)
}
"DynamicUtil.compileScalaCode method" should "compile the permissions and dependences" taggedAs DynamicUtilsTag in {
val permissions:Box[List[java.security.Permission]] = DynamicUtil.compileScalaCode("""
|List[java.security.Permission](
| new java.net.NetPermission("specifyStreamHandler"),
| new java.lang.reflect.ReflectPermission("suppressAccessChecks"),
| new java.lang.RuntimePermission("getenv.*")
| ) """.stripMargin)
val permissionList = permissions.openOrThrowException("Can not compile the string to permissions")
Sandbox.createSandbox(permissionList)
permissionList.toString contains ("""java.net.NetPermission""") shouldBe (true)
val permissionString =
"""[new java.net.NetPermission("specifyStreamHandler"),
|new java.lang.reflect.ReflectPermission("suppressAccessChecks"),
|new java.lang.RuntimePermission("getenv.*"),
|new java.util.PropertyPermission("cglib.useCache", "read"),
|new java.util.PropertyPermission("net.sf.cglib.test.stressHashCodes", "read"),
|new java.util.PropertyPermission("cglib.debugLocation", "read"),
|new java.lang.RuntimePermission("accessDeclaredMembers"),
|new java.lang.RuntimePermission("getClassLoader")]""".stripMargin
val scalaCode = "List[java.security.Permission]"+permissionString.replaceFirst("\\[","(").dropRight(1)+")"
val permissions2:Box[List[java.security.Permission]] = DynamicUtil.compileScalaCode(scalaCode)
val permissionList2 = permissions2.openOrThrowException("Can not compile the string to permissions")
Sandbox.createSandbox(permissionList2)
permissionList2.toString contains ("""java.net.NetPermission""") shouldBe (true)
val dependenciesBox: Box[Map[String, Set[String]]] = DynamicUtil.compileScalaCode(s"${DynamicUtil.importStatements}"+"""
|
|Map(
| // companion objects methods
| NewStyle.function.getClass.getTypeName -> "*",
| CompiledObjects.getClass.getTypeName -> "sandbox",
| HttpCode.getClass.getTypeName -> "200",
| DynamicCompileEndpoint.getClass.getTypeName -> "getPathParams, scalaFutureToBoxedJsonResponse",
| APIUtil.getClass.getTypeName -> "errorJsonResponse, errorJsonResponse$default$1, errorJsonResponse$default$2, errorJsonResponse$default$3, errorJsonResponse$default$4, scalaFutureToLaFuture, futureToBoxedResponse",
| ErrorMessages.getClass.getTypeName -> "*",
| ExecutionContext.Implicits.getClass.getTypeName -> "global",
| JSONFactory400.getClass.getTypeName -> "createBanksJson",
|
| // class methods
| classOf[Sandbox].getTypeName -> "runInSandbox",
| classOf[CallContext].getTypeName -> "*",
| classOf[ResourceDoc].getTypeName -> "getPathParams",
| "scala.reflect.runtime.package$" -> "universe",
|
| // allow any method of PractiseEndpoint for test
| PractiseEndpoint.getClass.getTypeName + "*" -> "*",
|
| ).mapValues(v => StringUtils.split(v, ',').map(_.trim).toSet)""".stripMargin)
val dependencies = dependenciesBox.openOrThrowException("Can not compile the string to Map")
dependencies.toString contains ("code.api.util.NewStyle") shouldBe (true)
val dependenciesString = """[NewStyle.function.getClass.getTypeName -> "*",CompiledObjects.getClass.getTypeName -> "sandbox",HttpCode.getClass.getTypeName -> "200",DynamicCompileEndpoint.getClass.getTypeName -> "getPathParams, scalaFutureToBoxedJsonResponse",APIUtil.getClass.getTypeName -> "errorJsonResponse, errorJsonResponse$default$1, errorJsonResponse$default$2, errorJsonResponse$default$3, errorJsonResponse$default$4, scalaFutureToLaFuture, futureToBoxedResponse",ErrorMessages.getClass.getTypeName -> "*",ExecutionContext.Implicits.getClass.getTypeName -> "global",JSONFactory400.getClass.getTypeName -> "createBanksJson",classOf[Sandbox].getTypeName -> "runInSandbox",classOf[CallContext].getTypeName -> "*",classOf[ResourceDoc].getTypeName -> "getPathParams","scala.reflect.runtime.package$" -> "universe",PractiseEndpoint.getClass.getTypeName + "*" -> "*"]""".stripMargin
val scalaCode2 = s"${DynamicUtil.importStatements}"+dependenciesString.replaceFirst("\\[","Map(").dropRight(1) +").mapValues(v => StringUtils.split(v, ',').map(_.trim).toSet)"
val dependenciesBox2: Box[Map[String, Set[String]]] = DynamicUtil.compileScalaCode(scalaCode2)
val dependencies2 = dependenciesBox2.openOrThrowException("Can not compile the string to Map")
dependencies2.toString contains ("code.api.util.NewStyle") shouldBe (true)
}
"Sandbox.createSandbox method" should "should throw exception" taggedAs DynamicUtilsTag in {
val permissionList = List(
// new java.net.SocketPermission("ir.dcs.gla.ac.uk:80","connect,resolve"),
)
intercept[AccessControlException] {
Sandbox.createSandbox(permissionList).runInSandbox {
scala.io.Source.fromURL("https://apisandbox.openbankproject.com/")
}
}
}
"Sandbox.createSandbox method" should "should work well" taggedAs DynamicUtilsTag in {
val permissionList = List(
new java.net.SocketPermission("apisandbox.openbankproject.com:443","connect,resolve"),
new java.util.PropertyPermission("user.dir","read"),
new java.io.FilePermission("README.md","read"),
)
Sandbox.createSandbox(permissionList).runInSandbox {
scala.io.Source.fromURL("https://apisandbox.openbankproject.com/")
Source.fromFile("README.md").getLines
new File(".").getCanonicalPath
}
}
"Sandbox.sandbox method test bankId" should "should throw exception" taggedAs DynamicUtilsTag in {
intercept[AccessControlException] {
Sandbox.sandbox(bankId= "abc").runInSandbox {
BankId("123" )
}
}
}
"Sandbox.sandbox method test bankId" should "should work well" taggedAs DynamicUtilsTag in {
Sandbox.sandbox(bankId= "abc").runInSandbox {
BankId("abc" )
}
}
"Sandbox.sandbox method test default permission" should "should throw exception" taggedAs DynamicUtilsTag in {
// intercept[AccessControlException] {
Sandbox.sandbox(bankId= "abc").runInSandbox {
scala.io.Source.fromURL("https://apisandbox.openbankproject.com/")
}
// }
}
val zson = {
"""

View File

@ -7,7 +7,7 @@
<groupId>com.tesobe</groupId>
<artifactId>obp-parent</artifactId>
<relativePath>../pom.xml</relativePath>
<version>1.10.0</version>
<version>1.10.1</version>
</parent>
<artifactId>obp-commons</artifactId>
<packaging>jar</packaging>

View File

@ -2,9 +2,12 @@ package com.openbankproject.commons
import com.alibaba.ttl.TtlRunnable
import scala.concurrent.{ ExecutionContext => ScalaExecutionContext}
import java.security.{AccessController, PrivilegedAction}
import scala.concurrent.{ExecutionContext => ScalaExecutionContext}
object ExecutionContext {
val enableSandbox = System.getProperty("dynamic_code_sandbox_enable", "false").toBoolean
object Implicits {
/**
* The implicit global `ExecutionContext`. Import `global` when you want to provide the global
@ -24,8 +27,17 @@ object ExecutionContext {
*/
def wrapExecutionContext(executionContext: ScalaExecutionContext): ScalaExecutionContext = {
new ScalaExecutionContext{
override def execute(runnable: Runnable): Unit = executionContext.execute(TtlRunnable.get(runnable, true, true))
override def execute(runnable: Runnable): Unit = {
val privilegedRunnable = if(enableSandbox) PrivilegedRunnable(runnable) else runnable
executionContext.execute(TtlRunnable.get(privilegedRunnable, true, true))
}
override def reportFailure(cause: Throwable): Unit = executionContext.reportFailure(cause)
}
}
def PrivilegedRunnable(runnable: Runnable): Runnable = {
val acc = AccessController.getContext
val privilegedAction: PrivilegedAction[Unit] = () => runnable.run()
() => AccessController.doPrivileged(privilegedAction, acc)
}
}

View File

@ -27,9 +27,10 @@ TESOBE (http://www.tesobe.com/)
package com.openbankproject.commons.model
import java.util.Date
import com.openbankproject.commons.util.{OBPRequired, optional}
import java.security.AccessControlContext
import javax.security.auth.AuthPermission
import scala.collection.immutable.List
import scala.math.BigDecimal
@ -138,11 +139,25 @@ object AccountId {
}
case class BankId(value : String) {
BankId.checkPermission(value)
override def toString = value
}
object BankId {
def unapply(id : String) = Some(BankId(id))
def checkPermission(bankId: String): Unit = {
val sm = System.getSecurityManager
// use AuthPermission to control bankId value, it is a hack way.
if (sm != null) { //only check the permission when we using the security manager.
val securityContext = sm.getSecurityContext.asInstanceOf[AccessControlContext]
sm.checkPermission(permission(bankId), securityContext)
}
}
def checkPermission(bankId: Option[String]): Unit = bankId.foreach(checkPermission)
def permission(bankId: String) = new AuthPermission(s"You do not have the permission for the BANK_ID($bankId)")
}
case class AccountRoutingAddress(val value: String) {

View File

@ -1,7 +1,6 @@
package com.openbankproject.commons.util
import java.lang.reflect.{Constructor, Modifier, Parameter}
import com.openbankproject.commons.model.{JsonFieldReName, ListResult}
import com.openbankproject.commons.model.enums.{SimpleEnum, SimpleEnumCollection}
import com.openbankproject.commons.util.Functions.Implicits._
@ -20,6 +19,24 @@ import scala.reflect.runtime.{universe => ru}
object JsonSerializers {
object CustomFormats extends DefaultFormats {
private val defaultFormats = net.liftweb.json.DefaultFormats
val losslessDate = defaultFormats.losslessDate
val UTC = defaultFormats.UTC
/**
* DefaultFormats#parameterNameReader has bug, when execute fail, cause return Nil, this is not reasonable,
* Here override it to: when execute fail, try to call constructor.getParamters
*/
override val parameterNameReader: ParameterNameReader = new ParameterNameReader {
override def lookupParameterNames(constructor: Constructor[_]): Traversable[String] = try {
defaultFormats.parameterNameReader.lookupParameterNames(constructor)
} catch {
case _ => constructor.getParameters.map(_.getName)
}
}
}
val serializers: List[Serializer[_]] =
AbstractTypeDeserializer :: SimpleEnumDeserializer ::
BigDecimalSerializer :: StringDeserializer ::
@ -27,7 +44,7 @@ object JsonSerializers {
JsonAbleSerializer :: ListResultSerializer.asInstanceOf[Serializer[_]] :: // here must do class cast, or it cause compile error, looks like a bug of scala.
MapperSerializer :: Nil
implicit val commonFormats = net.liftweb.json.DefaultFormats ++ serializers
implicit val commonFormats = CustomFormats ++ serializers
val nullTolerateFormats = commonFormats + JNothingSerializer

View File

@ -5,7 +5,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>com.tesobe</groupId>
<artifactId>obp-parent</artifactId>
<version>1.10.0</version>
<version>1.10.1</version>
<packaging>pom</packaging>
<name>Open Bank Project API Parent</name>
<inceptionYear>2011</inceptionYear>