From 917822f04e485c83345734df37fff5091eb562b4 Mon Sep 17 00:00:00 2001 From: Enno Runne <458526+ennru@users.noreply.github.com> Date: Tue, 24 Oct 2023 13:53:54 +0200 Subject: [PATCH] Replace Spray JWT dependency (#3030) --- build.sbt | 3 +- .../alpakka/google/auth/GoogleOAuth2.scala | 2 +- .../google/auth/JwtSprayJsonParser.scala | 79 +++++++++++++++++++ project/Dependencies.scala | 7 +- 4 files changed, 86 insertions(+), 5 deletions(-) create mode 100644 google-common/src/main/scala/akka/stream/alpakka/google/auth/JwtSprayJsonParser.scala diff --git a/build.sbt b/build.sbt index 0449d24d48..4f0fb94267 100644 --- a/build.sbt +++ b/build.sbt @@ -186,7 +186,8 @@ lazy val googleCommon = alpakkaProject( "google-common", "google.common", Dependencies.GoogleCommon, - Test / fork := true + Test / fork := true, + headerSources / excludeFilter := HiddenFileFilter || "JwtSprayJsonParser.scala" ) lazy val googleCloudBigQuery = alpakkaProject( diff --git a/google-common/src/main/scala/akka/stream/alpakka/google/auth/GoogleOAuth2.scala b/google-common/src/main/scala/akka/stream/alpakka/google/auth/GoogleOAuth2.scala index 61bebde794..e09b8c9351 100644 --- a/google-common/src/main/scala/akka/stream/alpakka/google/auth/GoogleOAuth2.scala +++ b/google-common/src/main/scala/akka/stream/alpakka/google/auth/GoogleOAuth2.scala @@ -13,7 +13,7 @@ import akka.stream.Materializer import akka.stream.alpakka.google.http.GoogleHttp import akka.stream.alpakka.google.{implicits, RequestSettings} import pdi.jwt.JwtAlgorithm.RS256 -import pdi.jwt.{JwtClaim, JwtSprayJson} +import pdi.jwt.JwtClaim import spray.json.DefaultJsonProtocol._ import spray.json.JsonFormat diff --git a/google-common/src/main/scala/akka/stream/alpakka/google/auth/JwtSprayJsonParser.scala b/google-common/src/main/scala/akka/stream/alpakka/google/auth/JwtSprayJsonParser.scala new file mode 100644 index 0000000000..ef4121b44e --- /dev/null +++ b/google-common/src/main/scala/akka/stream/alpakka/google/auth/JwtSprayJsonParser.scala @@ -0,0 +1,79 @@ +/* + * This software is licensed under the Apache 2 license. + * Copyright 2021 JWT-Scala Contributors. + * + * Copyright (C) since 2023 Lightbend Inc. + */ + +package akka.stream.alpakka.google.auth + +import akka.annotation.InternalApi +import pdi.jwt.{JwtAlgorithm, JwtClaim, JwtHeader, JwtJsonCommon} + +import java.time.Clock +import pdi.jwt.exceptions.JwtNonStringException +import spray.json._ + +/** + * Implementation of `JwtCore` using `JsObject` from spray-json. + * + * This class originally came from jwt-spray-json, + * but was removed in https://github.com/jwt-scala/jwt-scala/commit/bf1131ce02480103c0b953b97da001105a3ee038 + */ +@InternalApi +private[auth] trait JwtSprayJsonParser[H, C] extends JwtJsonCommon[JsObject, H, C] { + protected def parse(value: String): JsObject = value.parseJson.asJsObject + + protected def stringify(value: JsObject): String = value.compactPrint + + protected def getAlgorithm(header: JsObject): Option[JwtAlgorithm] = + header.fields.get("alg").flatMap { + case JsString("none") => None + case JsString(algo) => Option(JwtAlgorithm.fromString(algo)) + case JsNull => None + case _ => throw new JwtNonStringException("alg") + } + +} + +@InternalApi +private[auth] object JwtSprayJson extends JwtSprayJson(Clock.systemUTC) { + def apply(clock: Clock): JwtSprayJson = new JwtSprayJson(clock) +} + +@InternalApi +private[auth] class JwtSprayJson(override val clock: Clock) extends JwtSprayJsonParser[JwtHeader, JwtClaim] { + + import DefaultJsonProtocol._ + override def parseHeader(header: String): JwtHeader = { + val jsObj = parse(header) + JwtHeader( + algorithm = getAlgorithm(jsObj), + typ = safeGetField[String](jsObj, "typ"), + contentType = safeGetField[String](jsObj, "cty"), + keyId = safeGetField[String](jsObj, "kid") + ) + } + + override def parseClaim(claim: String): JwtClaim = { + val jsObj = parse(claim) + val content = JsObject(jsObj.fields - "iss" - "sub" - "aud" - "exp" - "nbf" - "iat" - "jti") + JwtClaim( + content = stringify(content), + issuer = safeGetField[String](jsObj, "iss"), + subject = safeGetField[String](jsObj, "sub"), + audience = safeGetField[Set[String]](jsObj, "aud") + .orElse(safeGetField[String](jsObj, "aud").map(s => Set(s))), + expiration = safeGetField[Long](jsObj, "exp"), + notBefore = safeGetField[Long](jsObj, "nbf"), + issuedAt = safeGetField[Long](jsObj, "iat"), + jwtId = safeGetField[String](jsObj, "jti") + ) + } + + private[this] def safeRead[A: JsonReader](js: JsValue) = + safeReader[A].read(js).fold(_ => None, a => Option(a)) + + private[this] def safeGetField[A: JsonReader](js: JsObject, name: String) = + js.fields.get(name).flatMap(safeRead[A]) +} diff --git a/project/Dependencies.scala b/project/Dependencies.scala index ecec888645..4074d28904 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -31,7 +31,8 @@ object Dependencies { val CouchbaseVersion = "2.7.16" val CouchbaseVersionForDocs = "2.7" - val JwtCoreVersion = "3.0.1" + // https://github.com/jwt-scala/jwt-scala/releases + val JwtScalaVersion = "9.4.4" val log4jOverSlf4jVersion = "1.7.36" val jclOverSlf4jVersion = "1.7.36" @@ -204,7 +205,7 @@ object Dependencies { libraryDependencies ++= Seq( "com.typesafe.akka" %% "akka-http" % AkkaHttpVersion, "com.typesafe.akka" %% "akka-http-spray-json" % AkkaHttpVersion, - "com.github.jwt-scala" %% "jwt-spray-json" % "9.0.2", // ApacheV2 + "com.github.jwt-scala" %% "jwt-json-common" % JwtScalaVersion, // https://github.com/googleapis/google-auth-library-java "com.google.auth" % "google-auth-library-credentials" % GoogleAuthVersion, "io.specto" % "hoverfly-java" % hoverflyVersion % Test // ApacheV2 @@ -304,7 +305,7 @@ object Dependencies { libraryDependencies ++= Seq( "com.typesafe.akka" %% "akka-http" % AkkaHttpVersion, "com.typesafe.akka" %% "akka-http-spray-json" % AkkaHttpVersion, - "com.github.jwt-scala" %% "jwt-spray-json" % "7.1.4" // ApacheV2 + "com.github.jwt-scala" %% "jwt-json-common" % JwtScalaVersion ) ++ Mockito )