This is an automated email from the ASF dual-hosted git repository.
pjfanning pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/pekko-connectors.git
The following commit(s) were added to refs/heads/main by this push:
new ae7c53315 Google: close the credentials file when parsing it fails
(#1928)
ae7c53315 is described below
commit ae7c53315a8b2faf9b4908cff836b24573813847
Author: PJ Fanning <[email protected]>
AuthorDate: Sun Sep 6 22:51:20 2026 +0100
Google: close the credentials file when parsing it fails (#1928)
* Google: close the credentials file when parsing it fails
Neither ServiceAccountCredentials nor UserAccessCredentials closed the file
in
a finally, so a parse failure leaked the descriptor. That is the routine
path:
the application-default provider tries the service account parser first,
and on
a machine set up with `gcloud auth application-default login` the well-known
file is an authorized user file, so the first parse always throws before
falling through to the parser that can read it.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
* Update
google-common/src/test/scala/org/apache/pekko/stream/connectors/google/auth/CredentialsSpec.scala
Co-authored-by: Philippus Baalman <[email protected]>
* Update
google-common/src/test/scala/org/apache/pekko/stream/connectors/google/auth/CredentialsSpec.scala
Co-authored-by: Philippus Baalman <[email protected]>
---------
Co-authored-by: Claude Opus 5 (1M context) <[email protected]>
Co-authored-by: Philippus Baalman <[email protected]>
---
.../google/auth/ServiceAccountCredentials.scala | 5 +-
.../google/auth/UserAccessCredentials.scala | 5 +-
.../connectors/google/auth/CredentialsSpec.scala | 75 ++++++++++++++++++++++
3 files changed, 81 insertions(+), 4 deletions(-)
diff --git
a/google-common/src/main/scala/org/apache/pekko/stream/connectors/google/auth/ServiceAccountCredentials.scala
b/google-common/src/main/scala/org/apache/pekko/stream/connectors/google/auth/ServiceAccountCredentials.scala
index b96c8bced..8792f4620 100644
---
a/google-common/src/main/scala/org/apache/pekko/stream/connectors/google/auth/ServiceAccountCredentials.scala
+++
b/google-common/src/main/scala/org/apache/pekko/stream/connectors/google/auth/ServiceAccountCredentials.scala
@@ -42,8 +42,9 @@ private[connectors] object ServiceAccountCredentials {
c.getString("private-key"))
} else {
val src = Source.fromFile(c.getString("path"))
- val credentials =
JsonParser(src.mkString).convertTo[ServiceAccountCredentialsFile]
- src.close()
+ val credentials =
+ try JsonParser(src.mkString).convertTo[ServiceAccountCredentialsFile]
+ finally src.close()
(credentials.project_id, credentials.client_email,
credentials.private_key)
}
}
diff --git
a/google-common/src/main/scala/org/apache/pekko/stream/connectors/google/auth/UserAccessCredentials.scala
b/google-common/src/main/scala/org/apache/pekko/stream/connectors/google/auth/UserAccessCredentials.scala
index 102690cc7..939b0bbd6 100644
---
a/google-common/src/main/scala/org/apache/pekko/stream/connectors/google/auth/UserAccessCredentials.scala
+++
b/google-common/src/main/scala/org/apache/pekko/stream/connectors/google/auth/UserAccessCredentials.scala
@@ -46,8 +46,9 @@ private[connectors] object UserAccessCredentials {
projectId = c.getString("project-id"))
} else {
val src = Source.fromFile(c.getString("path"))
- val credentials =
JsonParser(src.mkString).convertTo[UserAccessCredentialsFile]
- src.close()
+ val credentials =
+ try JsonParser(src.mkString).convertTo[UserAccessCredentialsFile]
+ finally src.close()
apply(
clientId = credentials.client_id,
clientSecret = credentials.client_secret,
diff --git
a/google-common/src/test/scala/org/apache/pekko/stream/connectors/google/auth/CredentialsSpec.scala
b/google-common/src/test/scala/org/apache/pekko/stream/connectors/google/auth/CredentialsSpec.scala
index c6bda3783..23c7ec408 100644
---
a/google-common/src/test/scala/org/apache/pekko/stream/connectors/google/auth/CredentialsSpec.scala
+++
b/google-common/src/test/scala/org/apache/pekko/stream/connectors/google/auth/CredentialsSpec.scala
@@ -25,6 +25,9 @@ import org.scalatest.BeforeAndAfterAll
import org.scalatest.matchers.should.Matchers
import org.scalatest.wordspec.AnyWordSpecLike
+import java.nio.charset.StandardCharsets.UTF_8
+import java.nio.file.{ Files, Path }
+
class CredentialsSpec
extends TestKit(ActorSystem("CredentialsSpec"))
with AnyWordSpecLike
@@ -36,6 +39,78 @@ class CredentialsSpec
super.afterAll()
}
+ private def withJsonFile(contents: String)(test: String => Unit): Unit = {
+ val file = Files.createTempFile("pekko-connectors-credentials", ".json")
+ Files.write(file, contents.getBytes(UTF_8))
+ try test(hoconPath(file))
+ finally Files.deleteIfExists(file): Unit
+ }
+
+ // backslashes are escapes in a HOCON quoted string, and Java accepts
forward slashes on Windows too
+ private def hoconPath(file: Path): String =
file.toAbsolutePath.toString.replace('\\', '/')
+
+ private val serviceAccountJson =
+
"""{"project_id":"a-project","client_email":"[email protected]","private_key":"a-key"}"""
+
+ private val userAccessJson =
+
"""{"client_id":"an-id","client_secret":"a-secret","refresh_token":"a-token","quota_project_id":"a-project"}"""
+
+ "ServiceAccountCredentials" should {
+
+ "read a service account file" in withJsonFile(serviceAccountJson) { path =>
+ val config = ConfigFactory.parseString(s"""
+ |project-id = ""
+ |client-email = ""
+ |private-key = ""
+ |path = "$path"
+ """.stripMargin)
+
+ ServiceAccountCredentials(config, Set("a-scope")).projectId shouldBe
"a-project"
+ }
+
+ "propagate the failure, having closed the file, when the file is not a
service account" in
+ withJsonFile(userAccessJson) { path =>
+ val config = ConfigFactory.parseString(s"""
+ |project-id = ""
+ |client-email = ""
+ |private-key = ""
+ |path = "$path"
+ """.stripMargin)
+
+ // this is the routine case: `gcloud auth application-default login`
writes a user access file,
+ // and the application-default provider tries the service account parser
against it first
+ an[Exception] should be thrownBy ServiceAccountCredentials(config,
Set("a-scope"))
+ }
+ }
+
+ "UserAccessCredentials" should {
+
+ "read a user access file" in withJsonFile(userAccessJson) { path =>
+ val config = ConfigFactory.parseString(s"""
+ |project-id = ""
+ |client-id = ""
+ |client-secret = ""
+ |refresh-token = ""
+ |path = "$path"
+ """.stripMargin)
+
+ UserAccessCredentials(config).projectId shouldBe "a-project"
+ }
+
+ "propagate the failure, having closed the file, when the file is not a
user access file" in
+ withJsonFile(serviceAccountJson) { path =>
+ val config = ConfigFactory.parseString(s"""
+ |project-id = ""
+ |client-id = ""
+ |client-secret = ""
+ |refresh-token = ""
+ |path = "$path"
+ """.stripMargin)
+
+ an[Exception] should be thrownBy UserAccessCredentials(config)
+ }
+ }
+
"Credentials" should {
"parse 'none' provider" in {
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]