Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for Scala 2.12 #4

Merged
merged 1 commit into from
Jan 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ Add the following line to your `build.sbt` file:
libraryDependencies += "@ORGANIZATION@" %% "@NAME@" % "@VERSION@"
```

The library is published for Scala versions: @SUPPORTED_SCALA_VERSIONS@.

## Usage

This library provides a class `TokenProvider` that is able to retrieve a
Expand Down
2 changes: 1 addition & 1 deletion build.sbt
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
ThisBuild / scalaVersion := "2.13.12"
ThisBuild / crossScalaVersions := Seq("2.13.12", "3.3.1")
ThisBuild / crossScalaVersions := Seq("2.12.18", "2.13.12", "3.3.1")
ThisBuild / organization := "com.permutive"
ThisBuild / versionPolicyIntention := Compatibility.BinaryAndSourceCompatible

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,6 @@

package com.permutive.gcp.auth.pureconfig

import scala.util.chaining._

import cats.effect.IO
import cats.effect.Resource

Expand Down Expand Up @@ -88,8 +86,10 @@ class TypeTokenSuite extends ClientSuite {

implicit val ConfigConfigReader: ConfigReader[Config] = ConfigReader.forProduct1("token-type")(Config.apply)

def fixture(resource: String) = Resource.make {
IO(sys.props("user.home")).flatTap(_ => IO(sys.props.put("user.home", getClass.getResource(resource).getPath())))
}(userHome => IO(sys.props.put("user.home", userHome)).void).pipe(ResourceFixture(_))
def fixture(resource: String) = ResourceFixture {
Resource.make {
IO(sys.props("user.home")).flatTap(_ => IO(sys.props.put("user.home", getClass.getResource(resource).getPath())))
}(userHome => IO(sys.props.put("user.home", userHome)).void)
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ private[auth] object Parser {
.compile
.onlyOrError
.map(line => RefreshToken(line.trim()))
.adaptError(_ => new EmptyRefreshTokenFile(path))
.adaptError { case _ => new EmptyRefreshTokenFile(path) }

final def googleClientSecrets[F[_]: Files: Concurrent](path: Path): F[(ClientId, ClientSecret)] =
Files[F]
Expand All @@ -60,7 +60,7 @@ private[auth] object Parser {
.flatMap { installed =>
(installed.get[ClientId]("client_id"), installed.get[ClientSecret]("client_secret")).tupled.liftTo[F]
}
.adaptError(new UnableToGetClientSecrets(path, _))
.adaptError { case t => new UnableToGetClientSecrets(path, t) }

final def googleServiceAccount[F[_]: Files: Sync](path: Path): F[(ClientEmail, RSAPrivateKey)] =
Files[F]
Expand All @@ -72,15 +72,15 @@ private[auth] object Parser {
(json.hcursor.get[ClientEmail]("client_email"), json.hcursor.get[String]("private_key")).tupled.liftTo[F]
}
.flatMap(_.traverse(loadPrivateKey[F]))
.adaptError(new UnableToGetClientData(path, _))
.adaptError { case t => new UnableToGetClientData(path, t) }

final def applicationDefaultCredentials[F[_]: Concurrent: Files]: F[(ClientId, ClientSecret, RefreshToken)] =
Files[F]
.readUtf8(defaultCredentialsFile)
.compile
.string
.flatMap(parser.decode[(ClientId, ClientSecret, RefreshToken)](_).liftTo[F])
.adaptError(new UnableToGetDefaultCredentials(defaultCredentialsFile, _))
.adaptError { case t => new UnableToGetDefaultCredentials(defaultCredentialsFile, t) }

implicit private val decodeCredentials: Decoder[(ClientId, ClientSecret, RefreshToken)] = c =>
(c.get[ClientId]("client_id"), c.get[ClientSecret]("client_secret"), c.get[RefreshToken]("refresh_token")).tupled
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,9 @@ import java.time.Duration
import java.time.Instant
import java.util.Date

import scala.annotation.nowarn
import scala.concurrent.duration._

import cats.Applicative
import cats.ApplicativeThrow
import cats.effect.Async
import cats.effect.Clock
import cats.effect.Concurrent
Expand All @@ -50,13 +48,12 @@ import com.permutive.refreshable.Refreshable
import fs2.io.file.Files
import fs2.io.file.Path
import org.http4s.Header
import org.http4s.Method
import org.http4s.Method.GET
import org.http4s.Method.POST
import org.http4s.Request
import org.http4s.Uri
import org.http4s.UrlForm
import org.http4s.client.Client
import org.http4s.client.dsl.MethodOps
import org.http4s.syntax.all._
import org.typelevel.ci._
import pdi.jwt.JwtCirce
Expand Down Expand Up @@ -166,11 +163,10 @@ object TokenProvider {
*/
def identity[F[_]: Concurrent: Clock](httpClient: Client[F], audience: Uri): TokenProvider[F] =
TokenProvider.create {
val request = GET(
uri"http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/identity" +?
("audience" -> audience),
Header.Raw(ci"Metadata-Flavor", "Google")
)
val uri = uri"http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/identity" +?
("audience" -> audience)

val request = Request[F](GET, uri).putHeaders(Header.Raw(ci"Metadata-Flavor", "Google"))

val jwtOptions = JwtOptions(signature = false)

Expand All @@ -183,7 +179,7 @@ object TokenProvider {
.map(ExpiresIn(_))
.map(AccessToken(Token(token), _))
}
.adaptError(new UnableToGetToken(_))
.adaptError { case t => new UnableToGetToken(t) }
}

/** Retrieves a workload service account token using Google's metadata server.
Expand All @@ -199,14 +195,13 @@ object TokenProvider {
*/
def serviceAccount[F[_]: Concurrent](httpClient: Client[F]): TokenProvider[F] =
TokenProvider.create {
val request =
Request[F](GET, uri"http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token")
.putHeaders(Header.Raw(ci"Metadata-Flavor", "Google"))

httpClient
.expect[AccessToken] {
GET(
uri"http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token",
Header.Raw(ci"Metadata-Flavor", "Google")
)
}
.adaptError(new UnableToGetToken(_))
.expect[AccessToken](request)
.adaptError { case t => new UnableToGetToken(t) }
}

/** Retrieves a service account token from Google's OAuth API.
Expand Down Expand Up @@ -250,9 +245,9 @@ object TokenProvider {
.sign(Algorithm.RSA256(privateKey))
}
.map(token => UrlForm("grant_type" -> "urn:ietf:params:oauth:grant-type:jwt-bearer", "assertion" -> token))
.map(POST(_, uri"https://oauth2.googleapis.com/token"))
.map(Request[F](POST, uri"https://oauth2.googleapis.com/token").withEntity(_))
.flatMap(httpClient.expect[AccessToken](_))
.adaptError(new UnableToGetToken(_))
.adaptError { case t => new UnableToGetToken(t) }
}

/** Retrieves a user account token from Google's OAuth API.
Expand Down Expand Up @@ -295,9 +290,11 @@ object TokenProvider {
"grant_type" -> "refresh_token"
)

val request = Request[F](POST, uri"https://oauth2.googleapis.com/token").withEntity(form)

httpClient
.expect[AccessToken](POST(form, uri"https://oauth2.googleapis.com/token"))
.adaptError(new UnableToGetToken(_))
.expect[AccessToken](request)
.adaptError { case t => new UnableToGetToken(t) }
}

/** Retrieves a user account token from Google's OAuth API using the "Application Default Credentials" file.
Expand All @@ -322,9 +319,4 @@ object TokenProvider {

def const[F[_]: Applicative](token: AccessToken): TokenProvider[F] = TokenProvider.create(token.pure)

@nowarn
@SuppressWarnings(Array("scalafix:DisableSyntax.implicitConversion"))
implicit private def http4sClientSyntaxMethod[F[_]: ApplicativeThrow](method: Method): MethodOps[F] =
new MethodOps[F](method)

}
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ package com.permutive.gcp.auth.models
import cats.effect.Concurrent
import cats.syntax.all._

import com.permutive.gcp.auth.models.ExpiresIn
import io.circe.Decoder
import org.http4s.AuthScheme
import org.http4s.Credentials
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,6 @@

package com.permutive.gcp.auth

import scala.util.chaining._

import cats.effect.IO
import cats.effect.kernel.Resource

Expand Down Expand Up @@ -173,9 +171,11 @@ class ParserSuite extends CatsEffectSuite {
// Parser.applicationDefaultCredentials //
//////////////////////////////////////////

def fixture(resource: String) = Resource.make {
IO(sys.props("user.home")).flatTap(_ => IO(sys.props.put("user.home", getClass.getResource(resource).getPath())))
}(userHome => IO(sys.props.put("user.home", userHome)).void).pipe(ResourceFixture(_))
def fixture(resource: String) = ResourceFixture {
Resource.make {
IO(sys.props("user.home")).flatTap(_ => IO(sys.props.put("user.home", getClass.getResource(resource).getPath())))
}(userHome => IO(sys.props.put("user.home", userHome)).void)
}

fixture("/default/valid").test("Parser.applicationDefaultCredentials should parse a valid file") { _ =>
val result = Parser.applicationDefaultCredentials[IO]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,6 @@ import java.security.KeyPairGenerator
import java.security.interfaces.RSAPrivateKey
import java.time.Instant

import scala.util.chaining._

import cats.effect.IO
import cats.effect.Resource
import cats.syntax.all._
Expand Down Expand Up @@ -232,9 +230,11 @@ class TokenProviderSuite extends ClientSuite {
// TokenProvider.userAccount(Client) //
///////////////////////////////////////

def fixture(resource: String) = Resource.make {
IO(sys.props("user.home")).flatTap(_ => IO(sys.props.put("user.home", getClass.getResource(resource).getPath())))
}(userHome => IO(sys.props.put("user.home", userHome)).void).pipe(ResourceFixture(_))
def fixture(resource: String) = ResourceFixture {
Resource.make {
IO(sys.props("user.home")).flatTap(_ => IO(sys.props.put("user.home", getClass.getResource(resource).getPath())))
}(userHome => IO(sys.props.put("user.home", userHome)).void)
}

fixture("/default/valid").test {
"TokenProvider.userAccount(Client) retrieves token successfully"
Expand Down
4 changes: 2 additions & 2 deletions project/plugins.sbt
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
addSbtPlugin("ch.epfl.scala" % "sbt-scalafix" % "0.11.1")
addSbtPlugin("com.alejandrohdezma" % "sbt-ci" % "2.14.0")
addSbtPlugin("com.alejandrohdezma" % "sbt-fix" % "0.7.1")
addSbtPlugin("com.alejandrohdezma" % "sbt-github-mdoc" % "0.11.12")
addSbtPlugin("com.alejandrohdezma" % "sbt-github-header" % "0.11.12")
addSbtPlugin("com.alejandrohdezma" % "sbt-github-mdoc" % "0.11.13")
addSbtPlugin("com.alejandrohdezma" % "sbt-github-header" % "0.11.13")
addSbtPlugin("com.alejandrohdezma" % "sbt-scalafix-defaults" % "0.11.0")
addSbtPlugin("com.alejandrohdezma" % "sbt-scalafmt-defaults" % "0.9.0")
addSbtPlugin("com.alejandrohdezma" % "sbt-mdoc-toc" % "0.4.1")
Expand Down