From 9b8a0be705240bfe4b983262dff5eb28feeaa725 Mon Sep 17 00:00:00 2001 From: Daniel Slapman Date: Sun, 11 Dec 2022 20:03:13 +0100 Subject: [PATCH] Replace pseudofunction mechanism with JS sandboxing --- .../tinkoff/tcb/mockingbird/Mockingbird.scala | 7 ++- .../mockingbird/api/PublicApiHandler.scala | 7 ++- .../mockingbird/config/Configuration.scala | 4 +- .../mockingbird/grpc/GrpcRequestHandler.scala | 7 +-- .../mockingbird/scenario/ScenarioEngine.scala | 7 ++- .../utils/sandboxing/ClassAccessRule.scala | 13 ---- .../tcb/utils/sandboxing/GraalJsSandbox.scala | 56 +++++++++-------- .../transformation/json/js_eval/package.scala | 13 +++- .../utils/transformation/json/package.scala | 33 +--------- .../tcb/utils/transformation/package.scala | 37 ----------- .../utils/sandboxing/GraalJsSandboxSpec.scala | 3 +- .../transformation/json/JsEvalSpec.scala | 12 +--- .../json/JsonTransformationsSpec.scala | 63 +++---------------- readme.md | 2 +- 14 files changed, 76 insertions(+), 188 deletions(-) delete mode 100644 backend/mockingbird/src/main/scala/ru/tinkoff/tcb/utils/sandboxing/ClassAccessRule.scala diff --git a/backend/mockingbird-api/src/main/scala/ru/tinkoff/tcb/mockingbird/Mockingbird.scala b/backend/mockingbird-api/src/main/scala/ru/tinkoff/tcb/mockingbird/Mockingbird.scala index cfe56178..f42b6acd 100644 --- a/backend/mockingbird-api/src/main/scala/ru/tinkoff/tcb/mockingbird/Mockingbird.scala +++ b/backend/mockingbird-api/src/main/scala/ru/tinkoff/tcb/mockingbird/Mockingbird.scala @@ -5,7 +5,6 @@ import scalapb.zio_grpc.Server import scalapb.zio_grpc.ServerLayer import scalapb.zio_grpc.ServiceList import scalapb.zio_grpc.server.ZServerCallHandler - import com.mongodb.ConnectionString import io.grpc.ServerBuilder import io.grpc.Status @@ -18,7 +17,6 @@ import sttp.client3.asynchttpclient.zio.AsyncHttpClientZioBackend import tofu.logging.Logging import tofu.logging.impl.ZUniversalLogging import zio.managed.* - import ru.tinkoff.tcb.mockingbird.api.AdminApiHandler import ru.tinkoff.tcb.mockingbird.api.AdminHttp import ru.tinkoff.tcb.mockingbird.api.MetricsHttp @@ -48,6 +46,8 @@ import ru.tinkoff.tcb.mockingbird.stream.EphemeralCleaner import ru.tinkoff.tcb.mockingbird.stream.EventSpawner import ru.tinkoff.tcb.mockingbird.stream.SDFetcher import ru.tinkoff.tcb.utils.metrics.makeRegistry +import ru.tinkoff.tcb.utils.resource.readStr +import ru.tinkoff.tcb.utils.sandboxing.GraalJsSandbox object Mockingbird extends scala.App { type FL = WLD & ServerConfig & PublicHttp & EventSpawner & ResourceManager & EphemeralCleaner & GrpcRequestHandler @@ -122,6 +122,7 @@ object Mockingbird extends scala.App { ) } yield scopedBackend }, + (ZLayer.service[ServerConfig].project(_.sandbox) ++ ZLayer.fromZIO(ZIO.attempt(readStr("prelude.js")).map(Option(_)))) >>> GraalJsSandbox.live, mongoLayer, aesEncoder, collection(_.stub) >>> HttpStubDAOImpl.live, @@ -166,10 +167,12 @@ object Mockingbird extends scala.App { .exec(bytes) .provideSome[RequestContext]( Tracing.live, + MockingbirdConfiguration.server, MockingbirdConfiguration.mongo, mongoLayer, collection(_.state) >>> PersistentStateDAOImpl.live, collection(_.grpcStub) >>> GrpcStubDAOImpl.live, + (ZLayer.service[ServerConfig].project(_.sandbox) ++ ZLayer.fromZIO(ZIO.attempt(readStr("prelude.js")).map(Option(_)))) >>> GraalJsSandbox.live, GrpcStubResolverImpl.live, GrpcRequestHandlerImpl.live ) diff --git a/backend/mockingbird/src/main/scala/ru/tinkoff/tcb/mockingbird/api/PublicApiHandler.scala b/backend/mockingbird/src/main/scala/ru/tinkoff/tcb/mockingbird/api/PublicApiHandler.scala index 17a8f516..57a1e8b8 100644 --- a/backend/mockingbird/src/main/scala/ru/tinkoff/tcb/mockingbird/api/PublicApiHandler.scala +++ b/backend/mockingbird/src/main/scala/ru/tinkoff/tcb/mockingbird/api/PublicApiHandler.scala @@ -3,7 +3,6 @@ package ru.tinkoff.tcb.mockingbird.api import scala.concurrent.duration.FiniteDuration import scala.util.control.NonFatal import scala.xml.Node - import io.circe.Json import io.circe.parser.parse import io.circe.syntax.* @@ -13,7 +12,6 @@ import sttp.client3.* import sttp.client3.circe.* import sttp.model.Method import zio.interop.catz.core.* - import ru.tinkoff.tcb.criteria.* import ru.tinkoff.tcb.criteria.Typed.* import ru.tinkoff.tcb.logging.MDCLogging @@ -37,6 +35,7 @@ import ru.tinkoff.tcb.mockingbird.scenario.CallbackEngine import ru.tinkoff.tcb.mockingbird.scenario.ScenarioEngine import ru.tinkoff.tcb.utils.circe.optics.JsonOptic import ru.tinkoff.tcb.utils.regex.* +import ru.tinkoff.tcb.utils.sandboxing.GraalJsSandbox import ru.tinkoff.tcb.utils.transformation.json.* import ru.tinkoff.tcb.utils.transformation.string.* import ru.tinkoff.tcb.utils.transformation.xml.* @@ -50,6 +49,7 @@ final class PublicApiHandler( stateDAO: PersistentStateDAO[Task], resolver: StubResolver, engine: CallbackEngine, + implicit val jsSandbox: GraalJsSandbox, private val httpBackend: SttpBackend[Task, ?], proxyConfig: ProxyConfig ) { @@ -243,8 +243,9 @@ object PublicApiHandler { ssd <- ZIO.service[PersistentStateDAO[Task]] resolver <- ZIO.service[StubResolver] engine <- ZIO.service[ScenarioEngine] + jsSandbox <- ZIO.service[GraalJsSandbox] sttpClient <- ZIO.service[SttpBackend[Task, Any]] proxyCfg <- ZIO.service[ProxyConfig] - } yield new PublicApiHandler(hsd, ssd, resolver, engine, sttpClient, proxyCfg) + } yield new PublicApiHandler(hsd, ssd, resolver, engine, jsSandbox, sttpClient, proxyCfg) } } diff --git a/backend/mockingbird/src/main/scala/ru/tinkoff/tcb/mockingbird/config/Configuration.scala b/backend/mockingbird/src/main/scala/ru/tinkoff/tcb/mockingbird/config/Configuration.scala index 784e2145..ede368ac 100644 --- a/backend/mockingbird/src/main/scala/ru/tinkoff/tcb/mockingbird/config/Configuration.scala +++ b/backend/mockingbird/src/main/scala/ru/tinkoff/tcb/mockingbird/config/Configuration.scala @@ -8,7 +8,9 @@ import net.ceedubs.ficus.Ficus.* import net.ceedubs.ficus.readers.ArbitraryTypeReader.* import net.ceedubs.ficus.readers.EnumerationReader.* -case class ServerConfig(interface: String, port: Int, allowedOrigins: Seq[String]) +case class JsSandboxConfig(allowedClasses: Set[String] = Set()) + +case class ServerConfig(interface: String, port: Int, allowedOrigins: Seq[String], sandbox: JsSandboxConfig) case class SecurityConfig(secret: String) diff --git a/backend/mockingbird/src/main/scala/ru/tinkoff/tcb/mockingbird/grpc/GrpcRequestHandler.scala b/backend/mockingbird/src/main/scala/ru/tinkoff/tcb/mockingbird/grpc/GrpcRequestHandler.scala index b6b262f1..76ff1f23 100644 --- a/backend/mockingbird/src/main/scala/ru/tinkoff/tcb/mockingbird/grpc/GrpcRequestHandler.scala +++ b/backend/mockingbird/src/main/scala/ru/tinkoff/tcb/mockingbird/grpc/GrpcRequestHandler.scala @@ -3,13 +3,11 @@ package ru.tinkoff.tcb.mockingbird.grpc import scalapb.zio_grpc.RequestContext import scalapb.zio_grpc.ZManagedChannel import scalapb.zio_grpc.client.ClientCalls - import io.circe.Json import io.circe.syntax.KeyOps import io.grpc.CallOptions import io.grpc.ManagedChannelBuilder import zio.Duration - import ru.tinkoff.tcb.mockingbird.api.Tracing import ru.tinkoff.tcb.mockingbird.api.WLD import ru.tinkoff.tcb.mockingbird.error.StubSearchError @@ -18,13 +16,14 @@ import ru.tinkoff.tcb.mockingbird.model.FillResponse import ru.tinkoff.tcb.mockingbird.model.GProxyResponse import ru.tinkoff.tcb.mockingbird.model.PersistentState import ru.tinkoff.tcb.mockingbird.model.Scope +import ru.tinkoff.tcb.utils.sandboxing.GraalJsSandbox import ru.tinkoff.tcb.utils.transformation.json.* trait GrpcRequestHandler { def exec(bytes: Array[Byte]): RIO[WLD & RequestContext, Array[Byte]] } -class GrpcRequestHandlerImpl(stubResolver: GrpcStubResolver) extends GrpcRequestHandler { +class GrpcRequestHandlerImpl(stubResolver: GrpcStubResolver, implicit val jsSandbox: GraalJsSandbox) extends GrpcRequestHandler { override def exec(bytes: Array[Byte]): RIO[WLD & RequestContext, Array[Byte]] = for { context <- ZIO.service[RequestContext] @@ -86,7 +85,7 @@ class GrpcRequestHandlerImpl(stubResolver: GrpcStubResolver) extends GrpcRequest } object GrpcRequestHandlerImpl { - val live: URLayer[GrpcStubResolver, GrpcRequestHandler] = ZLayer.fromFunction(new GrpcRequestHandlerImpl(_)) + val live: URLayer[GrpcStubResolver & GraalJsSandbox, GrpcRequestHandlerImpl] = ZLayer.fromFunction(new GrpcRequestHandlerImpl(_, _)) } object GrpcRequestHandler { diff --git a/backend/mockingbird/src/main/scala/ru/tinkoff/tcb/mockingbird/scenario/ScenarioEngine.scala b/backend/mockingbird/src/main/scala/ru/tinkoff/tcb/mockingbird/scenario/ScenarioEngine.scala index ea7d454c..4fc040bc 100644 --- a/backend/mockingbird/src/main/scala/ru/tinkoff/tcb/mockingbird/scenario/ScenarioEngine.scala +++ b/backend/mockingbird/src/main/scala/ru/tinkoff/tcb/mockingbird/scenario/ScenarioEngine.scala @@ -2,7 +2,6 @@ package ru.tinkoff.tcb.mockingbird.scenario import java.nio.charset.Charset import java.util.Base64 - import io.circe.Json import io.circe.syntax.* import kantan.xpath.Node as KNode @@ -13,7 +12,6 @@ import sttp.client3.* import sttp.client3.circe.* import sttp.model.Method import zio.interop.catz.core.* - import ru.tinkoff.tcb.criteria.* import ru.tinkoff.tcb.criteria.Typed.* import ru.tinkoff.tcb.logging.MDCLogging @@ -42,6 +40,7 @@ import ru.tinkoff.tcb.mockingbird.model.XMLCallbackRequest import ru.tinkoff.tcb.mockingbird.model.XmlOutput import ru.tinkoff.tcb.mockingbird.stream.SDFetcher import ru.tinkoff.tcb.utils.id.SID +import ru.tinkoff.tcb.utils.sandboxing.GraalJsSandbox import ru.tinkoff.tcb.utils.transformation.json.* import ru.tinkoff.tcb.utils.transformation.string.* import ru.tinkoff.tcb.utils.transformation.xml.* @@ -62,6 +61,7 @@ final class ScenarioEngine( stateDAO: PersistentStateDAO[Task], resolver: ScenarioResolver, fetcher: SDFetcher, + implicit val jsSandbox: GraalJsSandbox, private val httpBackend: SttpBackend[Task, ?] ) extends CallbackEngine { private val log = MDCLogging.`for`[WLD](this) @@ -217,7 +217,8 @@ object ScenarioEngine { psd <- ZIO.service[PersistentStateDAO[Task]] resolver <- ZIO.service[ScenarioResolver] fetcher <- ZIO.service[SDFetcher] + jsSandbox <- ZIO.service[GraalJsSandbox] sttpClient <- ZIO.service[SttpBackend[Task, Any]] - } yield new ScenarioEngine(sd, psd, resolver, fetcher, sttpClient) + } yield new ScenarioEngine(sd, psd, resolver, fetcher, jsSandbox, sttpClient) } } diff --git a/backend/mockingbird/src/main/scala/ru/tinkoff/tcb/utils/sandboxing/ClassAccessRule.scala b/backend/mockingbird/src/main/scala/ru/tinkoff/tcb/utils/sandboxing/ClassAccessRule.scala deleted file mode 100644 index 8cef1aa4..00000000 --- a/backend/mockingbird/src/main/scala/ru/tinkoff/tcb/utils/sandboxing/ClassAccessRule.scala +++ /dev/null @@ -1,13 +0,0 @@ -package ru.tinkoff.tcb.utils.sandboxing - -sealed trait ClassAccessRule extends (String => Boolean) - -object ClassAccessRule { - case class Exact(className: String) extends ClassAccessRule { - override def apply(arg: String): Boolean = arg == className - } - - case class StartsWith(prefix: String) extends ClassAccessRule { - override def apply(arg: String): Boolean = arg.startsWith(prefix) - } -} diff --git a/backend/mockingbird/src/main/scala/ru/tinkoff/tcb/utils/sandboxing/GraalJsSandbox.scala b/backend/mockingbird/src/main/scala/ru/tinkoff/tcb/utils/sandboxing/GraalJsSandbox.scala index cedb9d3a..f8c087ff 100644 --- a/backend/mockingbird/src/main/scala/ru/tinkoff/tcb/utils/sandboxing/GraalJsSandbox.scala +++ b/backend/mockingbird/src/main/scala/ru/tinkoff/tcb/utils/sandboxing/GraalJsSandbox.scala @@ -4,17 +4,14 @@ import scala.reflect.ClassTag import scala.reflect.classTag import scala.util.Try import scala.util.Using - import org.graalvm.polyglot.* - -import ru.tinkoff.tcb.utils.instances.predicate.or.* +import ru.tinkoff.tcb.mockingbird.config.JsSandboxConfig class GraalJsSandbox( - classAccessRules: List[ClassAccessRule] = GraalJsSandbox.DefaultAccess, + jsSandboxConfig: JsSandboxConfig, prelude: Option[String] = None ) { - private val accessRule = classAccessRules.asInstanceOf[List[String => Boolean]].combineAll - + private val allowedClasses = GraalJsSandbox.DefaultAccess ++ jsSandboxConfig.allowedClasses private val preludeSource = prelude.map(Source.create("js", _)) def eval[T: ClassTag](code: String, environment: Map[String, Any] = Map.empty): Try[T] = @@ -22,7 +19,7 @@ class GraalJsSandbox( Context .newBuilder("js") .allowHostAccess(HostAccess.ALL) - .allowHostClassLookup((t: String) => accessRule(t)) + .allowHostClassLookup((t: String) => allowedClasses(t)) .option("engine.WarnInterpreterOnly", "false") .build() ) { context => @@ -36,24 +33,31 @@ class GraalJsSandbox( } object GraalJsSandbox { - val DefaultAccess: List[ClassAccessRule] = List( - ClassAccessRule.Exact("java.lang.Byte"), - ClassAccessRule.Exact("java.lang.Boolean"), - ClassAccessRule.Exact("java.lang.Double"), - ClassAccessRule.Exact("java.lang.Float"), - ClassAccessRule.Exact("java.lang.Integer"), - ClassAccessRule.Exact("java.lang.Long"), - ClassAccessRule.Exact("java.lang.Math"), - ClassAccessRule.Exact("java.lang.Short"), - ClassAccessRule.Exact("java.lang.String"), - ClassAccessRule.Exact("java.math.BigDecimal"), - ClassAccessRule.Exact("java.math.BigInteger"), - ClassAccessRule.Exact("java.time.LocalDate"), - ClassAccessRule.Exact("java.time.LocalDateTime"), - ClassAccessRule.Exact("java.time.format.DateTimeFormatter"), - ClassAccessRule.Exact("java.util.List"), - ClassAccessRule.Exact("java.util.Map"), - ClassAccessRule.Exact("java.util.Random"), - ClassAccessRule.Exact("java.util.Set") + val live: URLayer[Option[String] & JsSandboxConfig, GraalJsSandbox] = ZLayer { + for { + sandboxConfig <- ZIO.service[JsSandboxConfig] + prelude <- ZIO.service[Option[String]] + } yield new GraalJsSandbox(sandboxConfig, prelude) + } + + val DefaultAccess: Set[String] = Set( + "java.lang.Byte", + "java.lang.Boolean", + "java.lang.Double", + "java.lang.Float", + "java.lang.Integer", + "java.lang.Long", + "java.lang.Math", + "java.lang.Short", + "java.lang.String", + "java.math.BigDecimal", + "java.math.BigInteger", + "java.time.LocalDate", + "java.time.LocalDateTime", + "java.time.format.DateTimeFormatter", + "java.util.List", + "java.util.Map", + "java.util.Random", + "java.util.Set" ) } diff --git a/backend/mockingbird/src/main/scala/ru/tinkoff/tcb/utils/transformation/json/js_eval/package.scala b/backend/mockingbird/src/main/scala/ru/tinkoff/tcb/utils/transformation/json/js_eval/package.scala index 0a51c3db..7df7f110 100644 --- a/backend/mockingbird/src/main/scala/ru/tinkoff/tcb/utils/transformation/json/js_eval/package.scala +++ b/backend/mockingbird/src/main/scala/ru/tinkoff/tcb/utils/transformation/json/js_eval/package.scala @@ -1,6 +1,7 @@ package ru.tinkoff.tcb.utils.transformation.json -import java.lang.Boolean as JBoolean +import java.math as jm +import java.lang as jl import scala.jdk.CollectionConverters.* import io.circe.Json @@ -11,7 +12,7 @@ package object js_eval { val circe2js: Json.Folder[AnyRef] = new Json.Folder[AnyRef] { override def onNull: AnyRef = null - override def onBoolean(value: Boolean): AnyRef = JBoolean.valueOf(value) + override def onBoolean(value: Boolean): AnyRef = jl.Boolean.valueOf(value) override def onNumber(value: JsonNumber): AnyRef = value.toBigDecimal.map(_.bigDecimal).orNull @@ -25,4 +26,12 @@ package object js_eval { .toMap .asJava } + + val fold2Json: PartialFunction[AnyRef, Json] = { + case b: jl.Boolean => Json.fromBoolean(b) + case s: String => Json.fromString(s) + case bd: jm.BigDecimal => Json.fromBigDecimal(bd) + case i: jl.Integer => Json.fromInt(i.intValue()) + case l: jl.Long => Json.fromLong(l.longValue()) + } } diff --git a/backend/mockingbird/src/main/scala/ru/tinkoff/tcb/utils/transformation/json/package.scala b/backend/mockingbird/src/main/scala/ru/tinkoff/tcb/utils/transformation/json/package.scala index de4af261..e4d88e6b 100644 --- a/backend/mockingbird/src/main/scala/ru/tinkoff/tcb/utils/transformation/json/package.scala +++ b/backend/mockingbird/src/main/scala/ru/tinkoff/tcb/utils/transformation/json/package.scala @@ -1,7 +1,5 @@ package ru.tinkoff.tcb.utils.transformation -import java.lang as jl -import java.math as jm import scala.util.Failure import scala.util.Success import scala.util.control.TailCalls @@ -10,7 +8,6 @@ import scala.util.control.TailCalls.TailRec import io.circe.Json import io.circe.JsonNumber as JNumber import kantan.xpath.* -import mouse.boolean.* import ru.tinkoff.tcb.utils.circe.* import ru.tinkoff.tcb.utils.circe.optics.JsonOptic @@ -18,6 +15,7 @@ import ru.tinkoff.tcb.utils.json.json2StringFolder import ru.tinkoff.tcb.utils.regex.OneOrMore import ru.tinkoff.tcb.utils.sandboxing.GraalJsSandbox import ru.tinkoff.tcb.utils.transformation.xml.nodeTemplater +import ru.tinkoff.tcb.utils.transformation.json.js_eval.fold2Json package object json { private val JORx = """\$([\:~])?\{([\p{L}\d\.\[\]\-_]+)\}""".r @@ -81,39 +79,14 @@ package object json { }.result } - def eval: Json = - transformValues { case js @ JsonString(str) => - str - .foldTemplate( - Json.fromString, - Json.fromInt, - Json.fromLong - ) - .orElse { - FunRx - .findFirstIn(str) - .isDefined - .option( - FunRx - .replaceSomeIn(str, m => m.matched.foldTemplate(identity, _.toString(), _.toString())) - ) - .map(Json.fromString) - } - .getOrElse(js) - }.result - - def eval2(implicit sandbox: GraalJsSandbox): Json = + def eval(implicit sandbox: GraalJsSandbox): Json = transformValues { case js @ JsonString(CodeRx(code)) => (sandbox.eval[AnyRef](code) match { - case Success(str: String) => Option(Json.fromString(str)) - case Success(bd: jm.BigDecimal) => Option(Json.fromBigDecimal(bd)) - case Success(i: jl.Integer) => Option(Json.fromInt(i.intValue())) - case Success(l: jl.Long) => Option(Json.fromLong(l.longValue())) + case Success(value) if fold2Json.isDefinedAt(value) => Option(fold2Json(value)) case Success(other) => throw new Exception(s"${other.getClass.getCanonicalName}: $other") case Failure(exception) => throw exception }).getOrElse(js) - case JsonString(other) => throw new Exception(other) }.result def patch(values: Json, schema: Map[JsonOptic, String]): Json = diff --git a/backend/mockingbird/src/main/scala/ru/tinkoff/tcb/utils/transformation/package.scala b/backend/mockingbird/src/main/scala/ru/tinkoff/tcb/utils/transformation/package.scala index db16b13f..1ef76a13 100644 --- a/backend/mockingbird/src/main/scala/ru/tinkoff/tcb/utils/transformation/package.scala +++ b/backend/mockingbird/src/main/scala/ru/tinkoff/tcb/utils/transformation/package.scala @@ -1,13 +1,7 @@ package ru.tinkoff.tcb.utils -import java.time.LocalDate -import java.time.LocalDateTime -import java.util.UUID -import scala.util.Random import scala.util.matching.Regex -import ru.tinkoff.tcb.utils.time.* - package object transformation { val SubstRx: Regex = """\$\{(.*?)\}""".r val FunRx: Regex = """%\{.*?\}""".r @@ -24,35 +18,4 @@ package object transformation { val Today: Regex = """%\{today\((.*?)\)\}""".r val Now: Regex = """%\{now\((.*?)\)\}""".r - - implicit final class TemplateTransformations(private val template: String) extends AnyVal { - def foldTemplate[T](foldString: String => T, foldInt: Int => T, foldLong: Long => T): Option[T] = - template match { - case RandStr(len) => - Some(foldString(Random.alphanumeric.take(len.toInt).mkString)) - case RandAlphabetStr(alphabet, minLen, maxLen) => - Some( - foldString( - List.fill(Random.between(minLen.toInt, maxLen.toInt))(alphabet(Random.nextInt(alphabet.length))).mkString - ) - ) - case RandNumStr(len) => - Some(foldString(Seq.fill(len.toInt)(Random.nextInt(10)).mkString)) - case RandInt(max) => - Some(foldInt(Random.nextInt(max.toInt))) - case RandIntInterval(min, max) => - Some(foldInt(Random.between(min.toInt, max.toInt))) - case RandLong(max) => - Some(foldLong(Random.nextLong(max.toLong))) - case RandLongInterval(min, max) => - Some(foldLong(Random.between(min.toLong, max.toLong))) - case RandUUID() => - Some(foldString(UUID.randomUUID().toString)) - case Today(Formatter(fmt)) => - Some(foldString(LocalDate.now().format(fmt))) - case Now(Formatter(fmt)) => - Some(foldString(LocalDateTime.now().format(fmt))) - case _ => None - } - } } diff --git a/backend/mockingbird/src/test/scala/ru/tinkoff/tcb/utils/sandboxing/GraalJsSandboxSpec.scala b/backend/mockingbird/src/test/scala/ru/tinkoff/tcb/utils/sandboxing/GraalJsSandboxSpec.scala index 1314cc13..27a22092 100644 --- a/backend/mockingbird/src/test/scala/ru/tinkoff/tcb/utils/sandboxing/GraalJsSandboxSpec.scala +++ b/backend/mockingbird/src/test/scala/ru/tinkoff/tcb/utils/sandboxing/GraalJsSandboxSpec.scala @@ -4,9 +4,10 @@ import org.graalvm.polyglot.PolyglotException import org.scalatest.TryValues import org.scalatest.funsuite.AnyFunSuite import org.scalatest.matchers.should.Matchers +import ru.tinkoff.tcb.mockingbird.config.JsSandboxConfig class GraalJsSandboxSpec extends AnyFunSuite with Matchers with TryValues { - private val sandbox = new GraalJsSandbox + private val sandbox = new GraalJsSandbox(new JsSandboxConfig()) test("Eval simple arithmetics") { sandbox.eval[Int]("1 + 2").success.value shouldBe 3 diff --git a/backend/mockingbird/src/test/scala/ru/tinkoff/tcb/utils/transformation/json/JsEvalSpec.scala b/backend/mockingbird/src/test/scala/ru/tinkoff/tcb/utils/transformation/json/JsEvalSpec.scala index 55769367..4c2cd56e 100644 --- a/backend/mockingbird/src/test/scala/ru/tinkoff/tcb/utils/transformation/json/JsEvalSpec.scala +++ b/backend/mockingbird/src/test/scala/ru/tinkoff/tcb/utils/transformation/json/JsEvalSpec.scala @@ -4,19 +4,17 @@ import java.math.BigDecimal as JBD import java.security.MessageDigest import java.util.List as JList import scala.jdk.CollectionConverters.* - import io.circe.Json import io.circe.syntax.* import org.scalatest.TryValues import org.scalatest.funsuite.AnyFunSuite import org.scalatest.matchers.should.Matchers - -import ru.tinkoff.tcb.utils.sandboxing.ClassAccessRule +import ru.tinkoff.tcb.mockingbird.config.JsSandboxConfig import ru.tinkoff.tcb.utils.sandboxing.GraalJsSandbox import ru.tinkoff.tcb.utils.transformation.json.js_eval.circe2js class JsEvalSpec extends AnyFunSuite with Matchers with TryValues { - private val sandbox = new GraalJsSandbox + private val sandbox = new GraalJsSandbox(JsSandboxConfig()) test("Simple expressions") { val data = Json.obj("a" := Json.obj("b" := 42, "c" := "test", "d" := 1 :: 2 :: Nil)) @@ -39,11 +37,7 @@ class JsEvalSpec extends AnyFunSuite with Matchers with TryValues { } test("JS functions") { - val aesSandbox = new GraalJsSandbox( - classAccessRules = List( - ClassAccessRule.Exact("java.security.MessageDigest") - ) ::: GraalJsSandbox.DefaultAccess - ) + val aesSandbox = new GraalJsSandbox(JsSandboxConfig(Set("java.security.MessageDigest"))) val etalon = MessageDigest.getInstance("SHA-1").digest("abc".getBytes) diff --git a/backend/mockingbird/src/test/scala/ru/tinkoff/tcb/utils/transformation/json/JsonTransformationsSpec.scala b/backend/mockingbird/src/test/scala/ru/tinkoff/tcb/utils/transformation/json/JsonTransformationsSpec.scala index f560d9f7..6ccbd69d 100644 --- a/backend/mockingbird/src/test/scala/ru/tinkoff/tcb/utils/transformation/json/JsonTransformationsSpec.scala +++ b/backend/mockingbird/src/test/scala/ru/tinkoff/tcb/utils/transformation/json/JsonTransformationsSpec.scala @@ -2,14 +2,13 @@ package ru.tinkoff.tcb.utils.transformation.json import java.time.format.DateTimeFormatter import java.util.UUID - import io.circe.Json import io.circe.syntax.* import kantan.xpath.XmlSource import org.scalatest.OptionValues import org.scalatest.funsuite.AnyFunSuite import org.scalatest.matchers.should.Matchers - +import ru.tinkoff.tcb.mockingbird.config.JsSandboxConfig import ru.tinkoff.tcb.utils.circe.* import ru.tinkoff.tcb.utils.circe.optics.JLens import ru.tinkoff.tcb.utils.circe.optics.JsonOptic @@ -137,57 +136,6 @@ class JsonTransformationsSpec extends AnyFunSuite with Matchers with OptionValue Json.obj().substitute(Json.obj()) } - test("Simple eval") { - val datePattern = "yyyy-MM-dd" - val dFormatter = DateTimeFormatter.ofPattern(datePattern) - val pattern = "yyyy-MM-dd'T'HH:mm:ss" - val formatter = DateTimeFormatter.ofPattern(pattern) - - val template = Json.obj( - "a" := "%{randomString(10)}", - "ai" := "%{randomString(\"ABCDEF1234567890\", 4, 6)}", - "b" := "%{randomInt(5)}", - "bi" := "%{randomInt(3, 8)}", - "c" := "%{randomLong(5)}", - "ci" := "%{randomLong(3, 8)}", - "d" := "%{UUID}", - "e" := s"%{now($pattern)}", - "f" := s"%{today($datePattern)}" - ) - - val res = template.eval - - (res \\ "a").headOption.flatMap(_.asString).value should have length 10 - - info((res \\ "ai").headOption.flatMap(_.asString).value) - (res \\ "ai").headOption.flatMap(_.asString).value should fullyMatch regex """[ABCDEF1234567890]{4,5}""" - - val b = (res \\ "b").headOption.flatMap(_.asNumber).flatMap(_.toInt).value - b should be >= 0 - b should be < 5 - - val bi = (res \\ "bi").headOption.flatMap(_.asNumber).flatMap(_.toInt).value - bi should be >= 3 - bi should be < 8 - - val c = (res \\ "c").headOption.flatMap(_.asNumber).flatMap(_.toLong).value - c should be >= 0L - c should be < 5L - - val ci = (res \\ "ci").headOption.flatMap(_.asNumber).flatMap(_.toLong).value - ci should be >= 3L - ci should be < 8L - - val d = (res \\ "d").headOption.flatMap(_.asString).value - noException should be thrownBy UUID.fromString(d) - - val e = (res \\ "e").headOption.flatMap(_.asString).value - noException should be thrownBy formatter.parse(e) - - val f = (res \\ "f").headOption.flatMap(_.asString).value - noException should be thrownBy dFormatter.parse(f) - } - test("JavaScript eval") { val datePattern = "yyyy-MM-dd" val dFormatter = DateTimeFormatter.ofPattern(datePattern) @@ -195,7 +143,7 @@ class JsonTransformationsSpec extends AnyFunSuite with Matchers with OptionValue val formatter = DateTimeFormatter.ofPattern(pattern) val prelude = readStr("prelude.js") - implicit val sandbox: GraalJsSandbox = new GraalJsSandbox(prelude = Option(prelude)) + implicit val sandbox: GraalJsSandbox = new GraalJsSandbox(JsSandboxConfig(), prelude = Option(prelude)) val template = Json.obj( "a" := "%{randomString(10)}", @@ -209,7 +157,7 @@ class JsonTransformationsSpec extends AnyFunSuite with Matchers with OptionValue "f" := s"%{today('$datePattern')}" ) - val res = template.eval2 + val res = template.eval (res \\ "a").headOption.flatMap(_.asString).value should have length 10 @@ -243,8 +191,11 @@ class JsonTransformationsSpec extends AnyFunSuite with Matchers with OptionValue } test("Formatted eval") { + val prelude = readStr("prelude.js") + implicit val sandbox: GraalJsSandbox = new GraalJsSandbox(JsSandboxConfig(), prelude = Option(prelude)) + val template = Json.obj( - "fmt" := "%{randomInt(10)}: %{randomLong(10)} | %{randomString(12)}" + "fmt" := "%{randomInt(10) + ': ' + randomLong(10) + ' | ' + randomString(12)}" ) val res = template.eval diff --git a/readme.md b/readme.md index 697f28d6..be9bc0d3 100644 --- a/readme.md +++ b/readme.md @@ -149,7 +149,7 @@ State аккумулятивно дописывается. Разрешено п * `%{now("yyyy-MM-dd'T'HH:mm:ss")}` - текущее время в заданном [формате](https://docs.oracle.com/javase/8/docs/api/java/time/format/DateTimeFormatter.html) * `%{today("yyyy-MM-dd")}` - текущая дата в заданном [формате](https://docs.oracle.com/javase/8/docs/api/java/time/format/DateTimeFormatter.html) -Можно определять строки со сложным форматом: `%{randomInt(10)}: %{randomLong(10)} | %{randomString(12)}`, поддерживаются все псевдофункции из списка выше +Можно определять строки со сложным форматом: `%{randomInt(10) +': ' + randomLong(10) + ' | ' + {randomString(12)}`, поддерживаются все псевдофункции из списка выше ## Резолвинг заглушек/сценариев