From 6cdd69dc566356be371457348f33f53c3b9f9433 Mon Sep 17 00:00:00 2001 From: Nick Date: Mon, 24 Jun 2024 17:18:56 +0100 Subject: [PATCH] Pass request headers to the JS enrichment (close #901) --- .../enrichments/EnrichmentManager.scala | 13 ++++-- .../registry/JavascriptScriptEnrichment.scala | 10 ++-- .../JavascriptScriptEnrichmentSpec.scala | 46 ++++++++++++++----- 3 files changed, 49 insertions(+), 20 deletions(-) diff --git a/modules/common/src/main/scala/com.snowplowanalytics.snowplow.enrich/common/enrichments/EnrichmentManager.scala b/modules/common/src/main/scala/com.snowplowanalytics.snowplow.enrich/common/enrichments/EnrichmentManager.scala index 1bacdc4e3..8218c6f32 100644 --- a/modules/common/src/main/scala/com.snowplowanalytics.snowplow.enrich/common/enrichments/EnrichmentManager.scala +++ b/modules/common/src/main/scala/com.snowplowanalytics.snowplow.enrich/common/enrichments/EnrichmentManager.scala @@ -316,7 +316,9 @@ object EnrichmentManager { _ <- getHttpHeaderContexts // Execute header extractor enrichment _ <- getYauaaContext[F](registry.yauaa, raw.context.headers) // Runs YAUAA enrichment (gets info thanks to user agent) _ <- extractSchemaFields[F](unstructEvent) // Extract the event vendor/name/format/version - _ <- registry.javascriptScript.traverse(getJsScript[F](_)) // Execute the JavaScript scripting enrichment + _ <- registry.javascriptScript.traverse( // Execute the JavaScript scripting enrichment + getJsScript[F](_, raw.context.headers) + ) _ <- getCurrency[F](raw.context.timestamp, registry.currencyConversion) // Finalize the currency conversion _ <- getWeatherContext[F](registry.weather) // Fetch weather context _ <- geoLocation[F](registry.ipLookups) // Execute IP lookup enrichment @@ -347,7 +349,9 @@ object EnrichmentManager { _ <- getYauaaContext[F](registry.yauaa, raw.context.headers) // Runs YAUAA enrichment (gets info thanks to user agent) _ <- extractSchemaFields[F](unstructEvent) // Extract the event vendor/name/format/version _ <- geoLocation[F](registry.ipLookups) // Execute IP lookup enrichment - _ <- registry.javascriptScript.traverse(getJsScript[F](_)) // Execute the JavaScript scripting enrichment + _ <- registry.javascriptScript.traverse( // Execute the JavaScript scripting enrichment + getJsScript[F](_, raw.context.headers) + ) _ <- sqlContexts // Derive some contexts with custom SQL Query enrichment _ <- apiContexts // Derive some contexts with custom API Request enrichment _ <- anonIp[F](registry.anonIp) // Anonymize the IP @@ -788,12 +792,13 @@ object EnrichmentManager { // Execute the JavaScript scripting enrichment def getJsScript[F[_]: Applicative]( - javascriptScript: JavascriptScriptEnrichment + javascriptScript: JavascriptScriptEnrichment, + headers: List[String] ): EStateT[F, Unit] = EStateT.fromEither { case (event, derivedContexts) => ME.formatContexts(derivedContexts).foreach(c => event.derived_contexts = c) - javascriptScript.process(event).leftMap(NonEmptyList.one) + javascriptScript.process(event, headers).leftMap(NonEmptyList.one) } def headerContexts[F[_]: Applicative, A]( diff --git a/modules/common/src/main/scala/com.snowplowanalytics.snowplow.enrich/common/enrichments/registry/JavascriptScriptEnrichment.scala b/modules/common/src/main/scala/com.snowplowanalytics.snowplow.enrich/common/enrichments/registry/JavascriptScriptEnrichment.scala index 4578b047a..ea18c38a7 100644 --- a/modules/common/src/main/scala/com.snowplowanalytics.snowplow.enrich/common/enrichments/registry/JavascriptScriptEnrichment.scala +++ b/modules/common/src/main/scala/com.snowplowanalytics.snowplow.enrich/common/enrichments/registry/JavascriptScriptEnrichment.scala @@ -10,6 +10,8 @@ */ package com.snowplowanalytics.snowplow.enrich.common.enrichments.registry +import scala.collection.JavaConverters._ + import cats.data.{NonEmptyList, ValidatedNel} import cats.implicits._ @@ -73,8 +75,8 @@ final case class JavascriptScriptEnrichment( private val stringified = rawFunction + s""" var getJavascriptContexts = function() { const params = ${params.asJson.noSpaces}; - return function(event) { - const result = process(event, params); + return function(event, headers) { + const result = process(event, params, headers); if (result == null) { return "[]" } else { @@ -95,11 +97,11 @@ final case class JavascriptScriptEnrichment( * The event can be updated in-place by the JS function. * @return either a JSON array of contexts on Success, or an error String on Failure */ - def process(event: EnrichedEvent): Either[FailureDetails.EnrichmentFailure, List[SelfDescribingData[Json]]] = + def process(event: EnrichedEvent, headers: List[String]): Either[FailureDetails.EnrichmentFailure, List[SelfDescribingData[Json]]] = invocable .flatMap(_ => Either - .catchNonFatal(engine.invokeFunction("getJavascriptContexts", event).asInstanceOf[String]) + .catchNonFatal(engine.invokeFunction("getJavascriptContexts", event, headers.asJava).asInstanceOf[String]) .leftMap(e => s"Error during execution of JavaScript function: [${e.getMessage}]") ) .flatMap(contexts => diff --git a/modules/common/src/test/scala/com.snowplowanalytics.snowplow.enrich.common/enrichments/registry/JavascriptScriptEnrichmentSpec.scala b/modules/common/src/test/scala/com.snowplowanalytics.snowplow.enrich.common/enrichments/registry/JavascriptScriptEnrichmentSpec.scala index d596b8c86..64e3a8a80 100644 --- a/modules/common/src/test/scala/com.snowplowanalytics.snowplow.enrich.common/enrichments/registry/JavascriptScriptEnrichmentSpec.scala +++ b/modules/common/src/test/scala/com.snowplowanalytics.snowplow.enrich.common/enrichments/registry/JavascriptScriptEnrichmentSpec.scala @@ -36,13 +36,14 @@ class JavascriptScriptEnrichmentSpec extends Specification { Javascript enrichment should be able to proceed with return null $e10 Javascript enrichment should be able to update the fields without return statement $e11 Javascript enrichment should be able to utilize the passed parameters $e12 + Javascript enrichment should be able to utilize the headers $e13 """ val schemaKey = SchemaKey("com.snowplowanalytics.snowplow", "javascript_script_config", "jsonschema", SchemaVer.Full(1, 0, 0)) def e1 = - JavascriptScriptEnrichment(schemaKey, "[").process(buildEnriched()) must beLeft( + JavascriptScriptEnrichment(schemaKey, "[").process(buildEnriched(), List.empty) must beLeft( failureContains(_: FailureDetails.EnrichmentFailure, "Error compiling") ) @@ -51,7 +52,7 @@ class JavascriptScriptEnrichmentSpec extends Specification { function process(event) { return { foo: "bar" } }""" - JavascriptScriptEnrichment(schemaKey, function).process(buildEnriched()) must beLeft( + JavascriptScriptEnrichment(schemaKey, function).process(buildEnriched(), List.empty) must beLeft( failureContains(_: FailureDetails.EnrichmentFailure, "not read as an array") ) } @@ -61,7 +62,7 @@ class JavascriptScriptEnrichmentSpec extends Specification { function process(event) { return [ { foo: "bar" } ] }""" - JavascriptScriptEnrichment(schemaKey, function).process(buildEnriched()) must beLeft( + JavascriptScriptEnrichment(schemaKey, function).process(buildEnriched(), List.empty) must beLeft( failureContains(_: FailureDetails.EnrichmentFailure, "not self-desribing") ) } @@ -74,7 +75,7 @@ class JavascriptScriptEnrichmentSpec extends Specification { data: { appId: event.getApp_id() } } ]; }""" - JavascriptScriptEnrichment(schemaKey, function).process(buildEnriched(appId)) must beRight.like { + JavascriptScriptEnrichment(schemaKey, function).process(buildEnriched(appId), List.empty) must beRight.like { case List(sdj) if sdj.data.noSpaces.contains(appId) => true case _ => false } @@ -91,7 +92,7 @@ class JavascriptScriptEnrichmentSpec extends Specification { data: { foo: "bar" } } ]; }""" - JavascriptScriptEnrichment(schemaKey, function).process(enriched) + JavascriptScriptEnrichment(schemaKey, function).process(enriched, List.empty) enriched.app_id must beEqualTo(newAppId) } @@ -100,7 +101,7 @@ class JavascriptScriptEnrichmentSpec extends Specification { function process(event) { throw "Error" }""" - JavascriptScriptEnrichment(schemaKey, function).process(buildEnriched()) must beLeft( + JavascriptScriptEnrichment(schemaKey, function).process(buildEnriched(), List.empty) must beLeft( failureContains(_: FailureDetails.EnrichmentFailure, "Error during execution") ) } @@ -110,7 +111,7 @@ class JavascriptScriptEnrichmentSpec extends Specification { function process(event) { return [ ]; }""" - JavascriptScriptEnrichment(schemaKey, function).process(buildEnriched()) must beRight + JavascriptScriptEnrichment(schemaKey, function).process(buildEnriched(), List.empty) must beRight } def e8 = { @@ -133,7 +134,7 @@ class JavascriptScriptEnrichmentSpec extends Specification { SchemaKey("com.acme", "bar", "jsonschema", SchemaVer.Full(1, 0, 0)), json"""{"hello":"world"}""" ) - JavascriptScriptEnrichment(schemaKey, function).process(buildEnriched()) must beRight.like { + JavascriptScriptEnrichment(schemaKey, function).process(buildEnriched(), List.empty) must beRight.like { case List(c1, c2) if c1 == context1 && c2 == context2 => true case _ => false } @@ -145,7 +146,7 @@ class JavascriptScriptEnrichmentSpec extends Specification { var a = 42 // no-op }""" - JavascriptScriptEnrichment(schemaKey, function).process(buildEnriched()) must beRight(Nil) + JavascriptScriptEnrichment(schemaKey, function).process(buildEnriched(), List.empty) must beRight(Nil) } def e10 = { @@ -154,7 +155,7 @@ class JavascriptScriptEnrichmentSpec extends Specification { return null }""" - JavascriptScriptEnrichment(schemaKey, function).process(buildEnriched()) must beRight(Nil) + JavascriptScriptEnrichment(schemaKey, function).process(buildEnriched(), List.empty) must beRight(Nil) } def e11 = { @@ -165,7 +166,7 @@ class JavascriptScriptEnrichmentSpec extends Specification { function process(event) { event.setApp_id("$newAppId") }""" - JavascriptScriptEnrichment(schemaKey, function).process(enriched) + JavascriptScriptEnrichment(schemaKey, function).process(enriched, List.empty) enriched.app_id must beEqualTo(newAppId) } @@ -178,7 +179,28 @@ class JavascriptScriptEnrichmentSpec extends Specification { function process(event, params) { event.setApp_id(params.nested.foo) }""" - JavascriptScriptEnrichment(schemaKey, function, params).process(enriched) + JavascriptScriptEnrichment(schemaKey, function, params).process(enriched, List.empty) + enriched.app_id must beEqualTo("newId") + } + + def e13 = { + val appId = "greatApp" + val enriched = buildEnriched(appId) + val function = + s""" + function process(event, params, headers) { + for (header of headers) { + const jwt = header.match(/X-JWT:(.+)/i) + if (jwt) { + event.setApp_id(jwt[1].trim()) + } + } + }""" + + JavascriptScriptEnrichment(schemaKey, function).process(enriched, List.empty) + enriched.app_id must beEqualTo("greatApp") + + JavascriptScriptEnrichment(schemaKey, function).process(enriched, List("x-jwt: newId")) enriched.app_id must beEqualTo("newId") }