diff --git a/.travis.yml b/.travis.yml index a89768b0..34a1f8d2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,6 @@ language: scala jdk: - - openjdk8 + - openjdk11 services: - docker env: diff --git a/CHANGELOG.md b/CHANGELOG.md index 1eb93f99..80cefd83 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +- Swap scalacache-guava to scalacache-caffeine +- Add an IO interface for ClientPublicKeyProvider + ## [7.0.1] - 2021-09-28 ### Changed Upgraded scala cats lib, and fixed resulting breakages. Also updated sbt plugins. diff --git a/build.sbt b/build.sbt index ccfb896d..16243007 100644 --- a/build.sbt +++ b/build.sbt @@ -107,9 +107,7 @@ lazy val `mauth-sender-sttp-akka-http` = scalaModuleProject("mauth-sender-sttp-a publishSettings, libraryDependencies ++= Dependencies.compile(catsEffect, akkaHttp, akkaStream, scalaLibCompat, sttp, scalaLogging).map(withExclusions) ++ - Dependencies.test(scalaMock, scalaTest, wiremock, sttpAkkaHttpBackend).map(withExclusions), - // TODO remove once published - mimaPreviousArtifacts := Set.empty + Dependencies.test(scalaMock, scalaTest, wiremock, sttpAkkaHttpBackend).map(withExclusions) ) lazy val `mauth-authenticator` = javaModuleProject("mauth-authenticator") @@ -123,7 +121,8 @@ lazy val `mauth-authenticator-scala` = scalaModuleProject("mauth-authenticator-s .settings( publishSettings, libraryDependencies ++= - Dependencies.test(logbackClassic, scalaMock, scalaTest, scalaLibCompat).map(withExclusions) + Dependencies.test(logbackClassic, scalaMock, scalaTest, scalaLibCompat).map(withExclusions) ++ + Dependencies.compile(catsEffect).map(withExclusions) ) lazy val `mauth-authenticator-apachehttp` = javaModuleProject("mauth-authenticator-apachehttp") diff --git a/modules/mauth-authenticator-akka-http/src/main/scala/com/mdsol/mauth/akka/http/MauthPublicKeyProvider.scala b/modules/mauth-authenticator-akka-http/src/main/scala/com/mdsol/mauth/akka/http/MauthPublicKeyProvider.scala index 78d09de2..183cf23c 100644 --- a/modules/mauth-authenticator-akka-http/src/main/scala/com/mdsol/mauth/akka/http/MauthPublicKeyProvider.scala +++ b/modules/mauth-authenticator-akka-http/src/main/scala/com/mdsol/mauth/akka/http/MauthPublicKeyProvider.scala @@ -1,27 +1,29 @@ package com.mdsol.mauth.akka.http -import java.net.URI -import java.security.PublicKey -import java.util.UUID - import akka.actor.ActorSystem import akka.http.scaladsl.model.{HttpResponse, StatusCodes} import akka.http.scaladsl.unmarshalling.Unmarshal import akka.stream.Materializer +import cats.effect.IO +import cats.effect.unsafe.implicits.global import com.fasterxml.jackson.databind.ObjectMapper +import com.github.benmanes.caffeine.cache.Caffeine import com.mdsol.mauth.http.HttpClient +import com.mdsol.mauth.http.Implicits._ import com.mdsol.mauth.models.UnsignedRequest import com.mdsol.mauth.scaladsl.utils.ClientPublicKeyProvider import com.mdsol.mauth.util.MAuthKeysHelper import com.mdsol.mauth.{AuthenticatorConfiguration, MAuthRequestSigner} import com.typesafe.scalalogging.StrictLogging -import scalacache.guava._ +import scalacache.caffeine.CaffeineCache import scalacache.memoization._ -import scalacache.modes.scalaFuture._ -import com.mdsol.mauth.http.Implicits._ +import scalacache.{Cache, Entry} +import java.net.URI +import java.security.PublicKey +import java.util.UUID import scala.concurrent.duration._ -import scala.concurrent.{ExecutionContext, Future, Promise} +import scala.concurrent.{ExecutionContext, Future} import scala.util.{Failure, Success, Try} class MauthPublicKeyProvider(configuration: AuthenticatorConfiguration, signer: MAuthRequestSigner)(implicit @@ -31,7 +33,8 @@ class MauthPublicKeyProvider(configuration: AuthenticatorConfiguration, signer: ) extends ClientPublicKeyProvider with StrictLogging { - implicit val guavaCache: GuavaCache[Option[PublicKey]] = GuavaCache[Option[PublicKey]] + private val cCache = Caffeine.newBuilder().build[String, Entry[Option[PublicKey]]]() + implicit val caffeineCache: Cache[IO, String, Option[PublicKey]] = CaffeineCache[IO, String, Option[PublicKey]](underlying = cCache) protected val mapper = new ObjectMapper /** Returns the associated public key for a given application UUID. @@ -39,40 +42,43 @@ class MauthPublicKeyProvider(configuration: AuthenticatorConfiguration, signer: * @param appUUID , UUID of the application for which we want to retrieve its public key. * @return { @link PublicKey} registered in MAuth for the application with given appUUID. */ - override def getPublicKey(appUUID: UUID): Future[Option[PublicKey]] = memoizeF(Some(configuration.getTimeToLive.seconds)) { + override def getPublicKey(appUUID: UUID): Future[Option[PublicKey]] = + getPublicKeyIO(appUUID).unsafeToFuture() + + override def getPublicKeyIO(appUUID: UUID): IO[Option[PublicKey]] = memoizeF(Some(configuration.getTimeToLive.seconds)) { val signedRequest = signer.signRequest(UnsignedRequest.noBody("GET", new URI(configuration.getBaseUrl + getRequestUrlPath(appUUID)), headers = Map.empty)) - retrievePublicKey()(HttpClient.call(signedRequest.toAkkaHttpRequest)) + retrievePublicKey()(IO.fromFuture(IO(HttpClient.call(signedRequest.toAkkaHttpRequest)))) } - protected def retrievePublicKey()(mauthPublicKeyFetcher: => Future[HttpResponse]): Future[Option[PublicKey]] = { - val promise = Promise[Option[PublicKey]]() + protected def retrievePublicKey()(mauthPublicKeyFetcher: => IO[HttpResponse]): IO[Option[PublicKey]] = { mauthPublicKeyFetcher .flatMap { response => - Unmarshal(response.entity) - .to[String] - .map { body => - if (response.status == StatusCodes.OK) { - Try(MAuthKeysHelper.getPublicKeyFromString(mapper.readTree(body).findValue("public_key_str").asText)) match { - case Success(publicKey) => promise.success(Some(publicKey)) - case Failure(error) => - logger.error("Converting string to Public Key failed", error) - promise.success(None) - } - } else { - logger.error(s"Unexpected response returned by server -- status: ${response.status} response: $body") - promise.success(None) + IO.fromFuture( + IO( + Unmarshal(response.entity) + .to[String] + ) + ).map { body => + if (response.status == StatusCodes.OK) { + Try(MAuthKeysHelper.getPublicKeyFromString(mapper.readTree(body).findValue("public_key_str").asText)) match { + case Success(publicKey) => Some(publicKey) + case Failure(error) => + logger.error("Converting string to Public Key failed", error) + None } + } else { + logger.error(s"Unexpected response returned by server -- status: ${response.status} response: $body") + None } - .recover { case error => - logger.error("Request to get MAuth public key couldn't be signed", error) - promise.success(None) - } + }.handleError { error: Throwable => + logger.error("Request to get MAuth public key couldn't be signed", error) + None + } } - .recover { case error => + .handleError { error: Throwable => logger.error("Request to get MAuth public key couldn't be completed", error) - promise.success(None) + None } - promise.future } protected def getRequestUrlPath(appUUID: UUID): String = diff --git a/modules/mauth-authenticator-scala/src/main/scala/com/mdsol/mauth/scaladsl/utils/ClientPublicKeyProvider.scala b/modules/mauth-authenticator-scala/src/main/scala/com/mdsol/mauth/scaladsl/utils/ClientPublicKeyProvider.scala index 87258849..4dcf836d 100644 --- a/modules/mauth-authenticator-scala/src/main/scala/com/mdsol/mauth/scaladsl/utils/ClientPublicKeyProvider.scala +++ b/modules/mauth-authenticator-scala/src/main/scala/com/mdsol/mauth/scaladsl/utils/ClientPublicKeyProvider.scala @@ -1,5 +1,7 @@ package com.mdsol.mauth.scaladsl.utils +import cats.effect.IO + import java.security.PublicKey import java.util.UUID @@ -13,4 +15,6 @@ trait ClientPublicKeyProvider { * @return Future of { @link PublicKey} registered in MAuth for the application with given appUUID. */ def getPublicKey(appUUID: UUID): Future[Option[PublicKey]] + + def getPublicKeyIO(appUUID: UUID): IO[Option[PublicKey]] } diff --git a/project/Dependencies.scala b/project/Dependencies.scala index e58af056..aa6a0625 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -21,7 +21,7 @@ object Dependencies extends DependencyUtils { val logbackCore: ModuleID = "ch.qos.logback" % "logback-core" % Version.logback val slf4jApi: ModuleID = "org.slf4j" % "slf4j-api" % "1.7.30" val typeSafeConfig: ModuleID = "com.typesafe" % "config" % "1.4.1" - val scalaCache: ModuleID = "com.github.cb372" %% "scalacache-guava" % "0.28.0" + val scalaCache: ModuleID = "com.github.cb372" %% "scalacache-caffeine" % "1.0.0-M6" val scalaLogging: ModuleID = "com.typesafe.scala-logging" %% "scala-logging" % "3.9.3" val catsEffect: ModuleID = "org.typelevel" %% "cats-effect" % "3.2.9" val sttp: ModuleID = "com.softwaremill.sttp.client3" %% "core" % Version.sttp