From 3b0b961f899029a8a1deccd6426c8131d303b086 Mon Sep 17 00:00:00 2001 From: Alexey Novakov Date: Sun, 16 Jun 2024 22:20:50 +0200 Subject: [PATCH] upgrade http4s and other libraries --- .github/workflows/scala.yml | 2 +- .gitignore | 3 +- .scalafix.conf | 1 + .scalafmt.conf | 13 ++-- build.sc | 49 +++++++------ .../novakovalexey/http4s/spnego/Spnego.scala | 11 +-- .../http4s/spnego/SpnegoAuthenticator.scala | 69 +++++++++++-------- .../http4s/spnego/rejection.scala | 2 +- .../http4s/spnego/SpnegoAuthTest.scala | 55 ++++++++------- mill | 2 +- project/Dependencies.sc | 10 ++- project/build.properties | 1 + .../novakovalexey/http4s/spnego/Main.scala | 4 +- 13 files changed, 126 insertions(+), 96 deletions(-) create mode 100644 project/build.properties diff --git a/.github/workflows/scala.yml b/.github/workflows/scala.yml index 0451608..ae76373 100644 --- a/.github/workflows/scala.yml +++ b/.github/workflows/scala.yml @@ -19,7 +19,7 @@ jobs: with: java-version: 11.0.x - name: Style checks - run: ./mill all __.checkFormat __.docJar + run: ./mill __.checkFormat + __.docJar tests: runs-on: ubuntu-latest diff --git a/.gitignore b/.gitignore index a51dc87..0cd9f21 100644 --- a/.gitignore +++ b/.gitignore @@ -7,4 +7,5 @@ metals.sbt out .dotty-ide-disabled settings.json -.ammonite \ No newline at end of file +.ammonite +.bsp \ No newline at end of file diff --git a/.scalafix.conf b/.scalafix.conf index 28ff426..57972db 100644 --- a/.scalafix.conf +++ b/.scalafix.conf @@ -16,3 +16,4 @@ DisableSyntax.noXml = true DisableSyntax.noFinalVal = true DisableSyntax.noFinalize = true DisableSyntax.noValPatterns = true +OrganizeImports.targetDialect = Scala3 \ No newline at end of file diff --git a/.scalafmt.conf b/.scalafmt.conf index 9c0fedd..361e090 100644 --- a/.scalafmt.conf +++ b/.scalafmt.conf @@ -1,9 +1,7 @@ -version = 2.7.5 -align = none +version = "3.7.12" style = IntelliJ continuationIndent.defnSite = 2 danglingParentheses.preset = true -docstrings = JavaDoc importSelectors = singleLine maxColumn = 120 rewrite.redundantBraces.stringInterpolation = true @@ -14,9 +12,14 @@ rewrite.rules = [ SortImports, RedundantParens ] -unindentTopLevelOperators = true includeCurlyBraceInSelectChains = false project.excludeFilters = [ ".*~undo-tree~" ] -project.git = true \ No newline at end of file +project.git = true +runner.dialect = scala212source3 +fileOverride { + "glob:**/project/Dependencies.sc/**" { + runner.dialect = scala3 + } +} diff --git a/build.sc b/build.sc index 597ec2d..6373766 100644 --- a/build.sc +++ b/build.sc @@ -1,36 +1,39 @@ -import $ivy.`com.goyeau::mill-git:9977203` -import $ivy.`com.lihaoyi::mill-contrib-bsp:$MILL_VERSION` +import $ivy.`com.goyeau::mill-git::0.2.5` +import $ivy.`com.lihaoyi::mill-contrib-bsp:0.8.0-15-080141` import $ivy.`io.github.davidgregory084::mill-tpolecat:0.1.4` import $file.project.Dependencies, Dependencies.Dependencies._ import com.goyeau.mill.git.GitVersionedPublishModule import io.github.davidgregory084.TpolecatModule import mill._ +import scalalib._ import mill.scalalib._ import mill.scalalib.publish.{Developer, License, PomSettings, VersionControl} import mill.scalalib.scalafmt.ScalafmtModule import mill.modules.Jvm -import ammonite.ops._ +import ScalaVersion._ object ScalaVersion { + val ver3 = "3.3.1" val ver213 = "2.13.5" - val ver212 = "2.12.13" } -object `http4s-spnego` extends Cross[Http4sSpnegoModule](ScalaVersion.ver213, ScalaVersion.ver212) -class Http4sSpnegoModule(val crossScalaVersion: String) - extends CrossScalaModule - with TpolecatModule +object `http4s-spnego` extends Cross[Http4sSpnegoModule](Seq(ver3, ver213)) + +trait Http4sSpnegoModule + extends CrossScalaModule with ScalafmtModule with GitVersionedPublishModule { override def scalacOptions = super.scalacOptions().filter(_ != "-Wunused:imports").filter(_ != "-Wunused:explicits") ++ (if (scalaVersion().startsWith("2.12")) Seq("-Ypartial-unification") else Seq.empty) + override def ivyDeps = super.ivyDeps() ++ http4sBase ++ logging ++ kindProjector - override def scalacPluginIvyDeps = super.scalacPluginIvyDeps() ++ betterMonadicFor - object test extends Tests { - def testFrameworks = Seq("org.scalatest.tools.Framework") + override def scalacPluginIvyDeps = super.scalacPluginIvyDeps() //++ betterMonadicFor + + object test extends ScalaTests { + def testFramework = "org.scalatest.tools.Framework" override def ivyDeps = super.ivyDeps() ++ tests override def scalacOptions = super.scalacOptions().filter(_ != "-Wunused:params").filter(_ != "-Xfatal-warnings") ++ @@ -51,32 +54,36 @@ class Http4sSpnegoModule(val crossScalaVersion: String) developers = Seq(Developer("novakov-alexey", "Alexey Novakov", "https://github.com/novakov-alexey")) ) } + object `test-server` extends ScalaModule { def scalaVersion = ScalaVersion.ver213 + override def ivyDeps = super.ivyDeps() ++ http4sBase ++ http4sDsl + override def moduleDeps = super.moduleDeps ++ Seq(`http4s-spnego`(ScalaVersion.ver213)) + def packageIt = T { val dest = T.ctx().dest - val libDir = dest / 'lib - val binDir = dest / 'bin + val libDir = dest / "lib" + val binDir = dest / "bin" - mkdir(libDir) - mkdir(binDir) + os.makeDir(libDir) + os.makeDir(binDir) val allJars = packageSelfModules() ++ runClasspath() .map(_.path) - .filter(path => exists(path) && !path.isDir) + .filter(path => os.exists(path) && !os.isDir(path)) .toSeq allJars.foreach { file => - cp.into(file, libDir) + os.copy.into(file, libDir) } - val runnerFile = Jvm.createLauncher(finalMainClass(), Agg.from(ls(libDir)), forkArgs()) + val runnerFile = util.Jvm.createLauncher(finalMainClass(), Agg.from(os.list(libDir)), forkArgs()) - mv.into(runnerFile.path, binDir) + os.move.into(runnerFile.path, binDir) PathRef(dest) } @@ -88,8 +95,8 @@ object `test-server` extends ScalaModule { .zip(module.artifactName) .zip(module.artifactId) .map { case ((jar, name), suffix) => - val namedJar = jar.path / up / s"$name$suffix.jar" - cp(jar.path, namedJar) + val namedJar = jar.path / os.up / s"$name$suffix.jar" + os.copy(jar.path, namedJar) namedJar } diff --git a/http4s-spnego/src/io/github/novakovalexey/http4s/spnego/Spnego.scala b/http4s-spnego/src/io/github/novakovalexey/http4s/spnego/Spnego.scala index abee1a7..2ba00a8 100644 --- a/http4s-spnego/src/io/github/novakovalexey/http4s/spnego/Spnego.scala +++ b/http4s-spnego/src/io/github/novakovalexey/http4s/spnego/Spnego.scala @@ -3,8 +3,8 @@ package io.github.novakovalexey.http4s.spnego import cats.data.{Kleisli, OptionT} import cats.effect.Sync import cats.implicits._ -import io.chrisdavenport.log4cats.Logger -import io.chrisdavenport.log4cats.slf4j.Slf4jLogger +import org.typelevel.log4cats.Logger +import org.typelevel.log4cats.slf4j.Slf4jLogger import io.github.novakovalexey.http4s.spnego.SpnegoAuthenticator._ import org.http4s._ import org.http4s.server.AuthMiddleware @@ -29,7 +29,7 @@ class Spnego[F[_]: Sync](cfg: SpnegoConfig, tokens: Tokens, authenticator: Spneg logger: Logger[F] ) { val authToken: Kleisli[F, Request[F], Either[Rejection, AuthToken]] = - Kleisli(request => authenticator.apply(request.headers)) + Kleisli(request => authenticator.apply(request.cookies, request.headers)) private val onFailure: AuthedRoutes[Rejection, F] = Kleisli { req => @@ -40,12 +40,13 @@ class Spnego[F[_]: Sync](cfg: SpnegoConfig, tokens: Tokens, authenticator: Spneg s"Failed to parse '$name' value, because of $msg", Nil ).pure[F] - case ServerErrorRejection(e) => (s"server error: ${e.getMessage}", Nil).pure[F] + case ServerErrorRejection(e) => (s"server error: ${e.getMessage}", Nil).pure[F] case UnexpectedErrorRejection(e) => (s"unexpected error: ${e.getMessage}", Nil).pure[F] } OptionT.liftF(for { - (msg, headers) <- rejection + response <- rejection + (msg, headers) = response res = Response[F](Status.Unauthorized).putHeaders(headers: _*).withEntity(msg) } yield res) } diff --git a/http4s-spnego/src/io/github/novakovalexey/http4s/spnego/SpnegoAuthenticator.scala b/http4s-spnego/src/io/github/novakovalexey/http4s/spnego/SpnegoAuthenticator.scala index 8cda16a..8d3c94d 100644 --- a/http4s-spnego/src/io/github/novakovalexey/http4s/spnego/SpnegoAuthenticator.scala +++ b/http4s-spnego/src/io/github/novakovalexey/http4s/spnego/SpnegoAuthenticator.scala @@ -7,26 +7,37 @@ import java.util.Collections import cats.data.OptionT import cats.effect.Sync import cats.implicits._ -import io.chrisdavenport.log4cats.Logger -import io.chrisdavenport.log4cats.slf4j.Slf4jLogger +import org.typelevel.log4cats.Logger +import org.typelevel.log4cats.slf4j.Slf4jLogger import io.github.novakovalexey.http4s.spnego.SpnegoAuthenticator._ import javax.security.auth.Subject import javax.security.auth.kerberos.KerberosPrincipal import javax.security.auth.login.LoginContext import org.http4s._ import org.ietf.jgss.{GSSCredential, GSSManager} +import org.typelevel.ci._ private[spnego] object SpnegoAuthenticator { val Negotiate = "Negotiate" - val Authenticate = "WWW-Authenticate" + val Authenticate = ci"WWW-Authenticate" + + case class AuthenticateHeader(v: String) + object AuthenticateHeader { + implicit def headerAuthentication: Header[AuthenticateHeader, Header.Single] = + new Header[AuthenticateHeader, Header.Single] { + def name = Authenticate + def value(f: AuthenticateHeader) = f.v + def parse(s: String) = AuthenticateHeader(s).asRight + } + } def reasonToString: RejectionReason => String = { case CredentialsRejected => "Credentials rejected" - case CredentialsMissing => "Credentials are missing" + case CredentialsMissing => "Credentials are missing" } private[spnego] def loginContext[F[_]](cfg: SpnegoConfig)(implicit F: Sync[F]): F[LoginContext] = for { - (entryName, kerberosConfiguration) <- F.delay { + configs <- F.delay { cfg.jaasConfig match { case Some(c) => val noEntryNeeded = "" @@ -37,6 +48,8 @@ private[spnego] object SpnegoAuthenticator { F.raiseError(new RuntimeException("Spnego Configuration creation has been failed", e)) } + (entryName, kerberosConfiguration) = configs + lc <- F.delay { val subject = new Subject( false, @@ -57,11 +70,12 @@ private[spnego] object SpnegoAuthenticator { } yield lc /* - Creates LoginContext, logins and created GSSManager based on SpnegoConfig + Creates LoginContext, logins and created GSSManager based on SpnegoConfig */ private[spnego] def apply[F[_]](cfg: SpnegoConfig, tokens: Tokens)(implicit F: Sync[F]): F[SpnegoAuthenticator[F]] = for { - (lc, manager) <- login[F](cfg) + response <- login[F](cfg) + (lc, manager) = response } yield new SpnegoAuthenticator[F](cfg, tokens, lc, manager) private[spnego] def login[F[_]](cfg: SpnegoConfig)(implicit F: Sync[F]): F[(LoginContext, GSSManager)] = @@ -94,18 +108,16 @@ private[spnego] class SpnegoAuthenticator[F[_]]( )(implicit F: Sync[F]) { implicit lazy val logger: Logger[F] = Slf4jLogger.getLogger[F] - private[spnego] def apply(hs: Headers): F[Either[Rejection, AuthToken]] = - cookieToken(hs).orElse(kerberosNegotiate(hs)).getOrElseF(initiateNegotiations) + private[spnego] def apply(cookies: List[RequestCookie], hs: Headers): F[Either[Rejection, AuthToken]] = + cookieToken(cookies).orElse(kerberosNegotiate(hs)).getOrElseF(initiateNegotiations) - private def cookieToken(hs: Headers) = - for { - c <- - headers.Cookie - .from(hs) - .collect { case h => h.values.find(_.name == cfg.cookieName) } - .flatten - .toOptionT[F] + private def initiateNegotiations: F[Either[Rejection, AuthToken]] = + logger.debug("no negotiation header found, initiating negotiations") *> + Either.left[Rejection, AuthToken](AuthenticationFailedRejection(CredentialsMissing, challengeHeader())).pure[F] + private def cookieToken(hs: List[RequestCookie]) = + for { + c <- hs.find(h => h.name == cfg.cookieName).toOptionT[F] _ <- OptionT(logger.debug("cookie found").map(_.some)) t <- Some( @@ -126,13 +138,12 @@ private[spnego] class SpnegoAuthenticator[F[_]]( private def clientToken(hs: Headers): F[Option[Array[Byte]]] = for { - authHeader <- headers.Authorization.from(hs).filter(_.value.startsWith(Negotiate)).pure[F] + authHeader <- hs.headers.find(_.value.toString.startsWith(Negotiate)).pure[F] token <- authHeader match { case Some(header) => - logger.debug("authorization header found") *> Base64Util - .decode(header.value.substring(Negotiate.length).trim) - .some - .pure[F] + logger.debug("authorization header found") *> Sync[F] + .delay(Base64Util.decode(header.value.substring(Negotiate.length).trim)) + .map(Some(_)) case _ => Option.empty[Array[Byte]].pure[F] } } yield token @@ -143,18 +154,20 @@ private[spnego] class SpnegoAuthenticator[F[_]]( result <- OptionT(kerberosCore(token).map(Option(_))) } yield result - private def challengeHeader(maybeServerToken: Option[Array[Byte]] = None): Header = { + private def challengeHeader(maybeServerToken: Option[Array[Byte]] = None) = { val scheme = Negotiate + maybeServerToken.map(" " + Base64Util.encode(_)).getOrElse("") - Header(Authenticate, scheme) + AuthenticateHeader(scheme) } private def kerberosCore(clientToken: Array[Byte]): F[Either[Rejection, AuthToken]] = F.defer { for { - (maybeServerToken, maybeToken) <- kerberosAcceptToken(clientToken) + response <- kerberosAcceptToken(clientToken) + (maybeServerToken, maybeToken) = response _ <- logger.debug(s"serverToken '${maybeServerToken.map(Base64Util.encode)}' token '$maybeToken'") token <- maybeToken match { - case Some(t) => logger.debug("received new token") *> t.asRight[Rejection].pure[F] + case Some(t) => + logger.debug("received new token") *> t.asRight[Rejection].pure[F] case _ => logger.debug("no token received, but if there is a serverToken, then negotiations are ongoing") *> Either .left[Rejection, AuthToken]( @@ -193,8 +206,4 @@ private[spnego] class SpnegoAuthenticator[F[_]]( } ) } - - private def initiateNegotiations: F[Either[Rejection, AuthToken]] = - logger.debug("no negotiation header found, initiating negotiations") *> - Either.left[Rejection, AuthToken](AuthenticationFailedRejection(CredentialsMissing, challengeHeader())).pure[F] } diff --git a/http4s-spnego/src/io/github/novakovalexey/http4s/spnego/rejection.scala b/http4s-spnego/src/io/github/novakovalexey/http4s/spnego/rejection.scala index 407d85c..bf6aa92 100644 --- a/http4s-spnego/src/io/github/novakovalexey/http4s/spnego/rejection.scala +++ b/http4s-spnego/src/io/github/novakovalexey/http4s/spnego/rejection.scala @@ -6,7 +6,7 @@ import org.http4s.Header sealed trait Rejection -final case class AuthenticationFailedRejection(reason: RejectionReason, challenge: Header) extends Rejection +final case class AuthenticationFailedRejection(reason: RejectionReason, challenge: Header.ToRaw) extends Rejection sealed trait RejectionReason diff --git a/http4s-spnego/test/src/io/github/novakovalexey/http4s/spnego/SpnegoAuthTest.scala b/http4s-spnego/test/src/io/github/novakovalexey/http4s/spnego/SpnegoAuthTest.scala index 98eb4b3..b9ccaa1 100644 --- a/http4s-spnego/test/src/io/github/novakovalexey/http4s/spnego/SpnegoAuthTest.scala +++ b/http4s-spnego/test/src/io/github/novakovalexey/http4s/spnego/SpnegoAuthTest.scala @@ -1,31 +1,33 @@ package io.github.novakovalexey.http4s.spnego import cats.data.{Kleisli, OptionT} -import cats.effect.{ContextShift, IO} +import cats.effect.IO +import cats.effect.unsafe.implicits.global import cats.implicits._ -import io.chrisdavenport.log4cats.SelfAwareStructuredLogger -import io.chrisdavenport.log4cats.slf4j.Slf4jLogger + +import org.typelevel.log4cats._ +import org.typelevel.log4cats.slf4j._ + import io.github.novakovalexey.http4s.spnego.SpnegoAuthenticator.login + import org.http4s._ import org.http4s.headers.Authorization import org.http4s.implicits._ import org.scalatest.flatspec.AnyFlatSpec import org.scalatest.matchers.should.Matchers -import scala.concurrent.ExecutionContext.Implicits.global import scala.concurrent.duration._ import scala.io.Codec +import org.typelevel.ci.CIString class SpnegoAuthTest extends AnyFlatSpec with Matchers { - implicit val cs: ContextShift[IO] = IO.contextShift(global) - val realm = "EXAMPLE.ORG" val principal = s"HTTP/myservice@$realm" val keytab = "/etc/krb5.keytab" val debug = true val domain = Some("myservice") val path: Option[String] = None - val tokenValidity: FiniteDuration = 3600.seconds + val tokenValidity = 3600.seconds val cookieName = "http4s.spnego" val cfg = SpnegoConfig( @@ -40,7 +42,7 @@ class SpnegoAuthTest extends AnyFlatSpec with Matchers { ) val testTokens = new Tokens(cfg.tokenValidity.toMillis, Codec.toUTF8(cfg.signatureSecret)) val authenticator = SpnegoAuthenticator[IO](cfg, testTokens).unsafeRunSync() - implicit lazy val logger: SelfAwareStructuredLogger[IO] = Slf4jLogger.getLogger[IO] + implicit lazy val logger: SelfAwareStructuredLogger[IO] = Slf4jFactory.create[IO].getLogger val spnego = new Spnego[IO](cfg, testTokens, authenticator) val loginEndpoint = new LoginEndpoint[IO](spnego) val authorizationHeader = "Authorization" @@ -48,7 +50,9 @@ class SpnegoAuthTest extends AnyFlatSpec with Matchers { val userPrincipal = "myprincipal" it should "reject invalid authorization token" in { - val req = Request[IO]().putHeaders(Authorization(Credentials.Token(SpnegoAuthenticator.Negotiate.ci, "test"))) + val req = Request[IO]().putHeaders( + Authorization(Credentials.Token(CIString(SpnegoAuthenticator.Negotiate), Base64Util.encode("test".getBytes()))) + ) val route = loginEndpoint.routes.orNotFound val res = route.run(req) @@ -59,7 +63,9 @@ class SpnegoAuthTest extends AnyFlatSpec with Matchers { } it should "reject invalid cookie" in { - val req = Request[IO]().addCookie(cookieName, "myprincipal&1566120533815&0zjbRRVXDFlDYfRurlxaySKWhgE=") + val req = Request[IO]().addCookie(cookieName, + Base64Util.encode("myprincipal&1566120533815&0zjbRRVXDFlDYfRurlxaySKWhgE=".getBytes()) + ) val route = loginEndpoint.routes.orNotFound val res = route.run(req) @@ -71,7 +77,8 @@ class SpnegoAuthTest extends AnyFlatSpec with Matchers { def mockKerberos(token: Option[AuthToken], mockTokens: Tokens = testTokens): Spnego[IO] = { val authenticator = for { - (lc, manager) <- login[IO](cfg) + response <- login[IO](cfg) + (lc, manager) = response } yield new SpnegoAuthenticator[IO](cfg, mockTokens, lc, manager) { override private[spnego] def kerberosAcceptToken( clientToken: Array[Byte] @@ -85,11 +92,11 @@ class SpnegoAuthTest extends AnyFlatSpec with Matchers { it should "reject token if Kerberos failed" in { //given val route = loginRoute(None) - val clientToken = "test" - val req2 = Request[IO]().putHeaders(Header(authorizationHeader, s"${SpnegoAuthenticator.Negotiate} $clientToken")) + val clientToken = Base64Util.encode("test".getBytes()) + val req2 = Request[IO]().putHeaders(Header.Raw(CIString(authorizationHeader), s"${SpnegoAuthenticator.Negotiate} $clientToken")) //when val res2 = route.run(req2) - val actualResp = res2.unsafeRunSync + val actualResp = res2.unsafeRunSync() //then actualResp.status should ===(Status.Unauthorized) actualResp.as[String].unsafeRunSync() should ===(SpnegoAuthenticator.reasonToString(CredentialsMissing)) @@ -132,10 +139,12 @@ class SpnegoAuthTest extends AnyFlatSpec with Matchers { res1.unsafeRunSync().status should ===(Status.Unauthorized) //given - val clientToken = "test" - val req2 = Request[IO]().putHeaders(Header(authorizationHeader, s"${SpnegoAuthenticator.Negotiate} $clientToken")) + val clientToken = Base64Util.encode("test".getBytes()) + val req2 = Request[IO]().putHeaders( + Header.Raw(CIString(authorizationHeader), s"${SpnegoAuthenticator.Negotiate} $clientToken") + ) //when - val okResponse = route.run(req2).unsafeRunSync + val okResponse = route.run(req2).unsafeRunSync() //then okResponse.status should ===(Status.Ok) @@ -162,11 +171,11 @@ class SpnegoAuthTest extends AnyFlatSpec with Matchers { //given val noTokenValidity = new Tokens(0.millisecond.toMillis, Codec.toUTF8(cfg.signatureSecret)) val routes = loginRoute(Some(noTokenValidity.create(userPrincipal)), noTokenValidity) - val signature = "test" - val req = Request[IO]().putHeaders(Header(authorizationHeader, s"${SpnegoAuthenticator.Negotiate} $signature")) + val signature = Base64Util.encode("test".getBytes()) + val req = Request[IO]().putHeaders(Header.Raw(CIString(authorizationHeader), s"${SpnegoAuthenticator.Negotiate} $signature")) //when val io = routes.run(req) - val resp = io.unsafeRunSync + val resp = io.unsafeRunSync() //then resp.status should ===(Status.Ok) val cookie = resp.cookies.headOption.map(_.content).getOrElse(fail()) @@ -190,7 +199,7 @@ class SpnegoAuthTest extends AnyFlatSpec with Matchers { it should "allow custom onFailure handler" in { val login = new LoginEndpoint[IO](spnego) val onFailure: AuthedRoutes[Rejection, IO] = Kleisli { _ => - val res = Response[IO](Status.BadRequest).putHeaders(Header("test 1", "test 2")).withEntity("test entity") + val res = Response[IO](Status.BadRequest).putHeaders(Header.Raw(CIString("test 1"), "test 2")).withEntity("test entity") OptionT.liftF(res.pure[IO]) } val routes = login.routes(onFailure).orNotFound @@ -202,9 +211,9 @@ class SpnegoAuthTest extends AnyFlatSpec with Matchers { //then val actualResp = res1.unsafeRunSync() actualResp.status should ===(Status.BadRequest) - val maybeHeader = actualResp.headers.get("test 1".ci) + val maybeHeader = actualResp.headers.get(CIString("test 1")) maybeHeader.isDefined should ===(true) - maybeHeader.map(_.value should ===("test 2")).getOrElse(fail()) + maybeHeader.map(_.head.value should ===("test 2")).getOrElse(fail()) actualResp.as[String].unsafeRunSync() should ===("test entity") } } diff --git a/mill b/mill index 23a3bff..cd2dee7 100755 --- a/mill +++ b/mill @@ -3,7 +3,7 @@ # This is a wrapper script, that automatically download mill from GitHub release pages # You can give the required mill version with MILL_VERSION env variable # If no version is given, it falls back to the value of DEFAULT_MILL_VERSION -DEFAULT_MILL_VERSION=0.8.0-15-080141 +DEFAULT_MILL_VERSION=0.11.7 set -e diff --git a/project/Dependencies.sc b/project/Dependencies.sc index d537ba6..ba92991 100644 --- a/project/Dependencies.sc +++ b/project/Dependencies.sc @@ -2,17 +2,15 @@ import mill._ import mill.scalalib._ object Dependencies { - lazy val http4sVersion = "0.21.19" + lazy val http4sVersion = "0.23.27" lazy val http4sBase = { - Agg(ivy"org.http4s::http4s-core:$http4sVersion", ivy"org.http4s::http4s-blaze-server:$http4sVersion") + Agg(ivy"org.http4s::blaze-core:0.23.16", ivy"org.http4s::http4s-blaze-server:0.23.16") } lazy val http4sDsl = Agg(ivy"org.http4s::http4s-dsl:$http4sVersion") - lazy val logging = Agg(ivy"io.chrisdavenport::log4cats-slf4j:1.1.1", ivy"ch.qos.logback:logback-classic:1.2.3") + lazy val logging = Agg(ivy"org.typelevel::log4cats-slf4j:2.7.0", ivy"ch.qos.logback:logback-classic:1.2.3") lazy val kindProjector = Agg(ivy"org.typelevel:kind-projector_2.13.5:0.11.3") // had to put explicit Scala version, otherwise it resolves some old/wrong Scala version :-( - lazy val betterMonadicFor = Agg(ivy"com.olegpy::better-monadic-for:0.3.1") - - lazy val tests = Agg(ivy"org.scalatest::scalatest:3.2.5") ++ http4sDsl + lazy val tests = Agg(ivy"org.scalatest::scalatest:3.2.18") ++ http4sDsl } diff --git a/project/build.properties b/project/build.properties new file mode 100644 index 0000000..3040987 --- /dev/null +++ b/project/build.properties @@ -0,0 +1 @@ +sbt.version=1.9.4 diff --git a/test-server/src/io/github/novakovalexey/http4s/spnego/Main.scala b/test-server/src/io/github/novakovalexey/http4s/spnego/Main.scala index 77cefe0..0688709 100644 --- a/test-server/src/io/github/novakovalexey/http4s/spnego/Main.scala +++ b/test-server/src/io/github/novakovalexey/http4s/spnego/Main.scala @@ -15,7 +15,7 @@ object Main extends IOApp { override def run(args: List[String]): IO[ExitCode] = stream[IO].compile.drain.as(ExitCode.Success) - def stream[F[_]: ConcurrentEffect: ContextShift: Timer]: Stream[F, ExitCode] = for { + def stream[F[_]: Async]: Stream[F, ExitCode] = for { spnego <- Stream.eval(makeSpnego) httpApp = Router("/auth" -> new LoginEndpoint[F](spnego).routes).orNotFound @@ -27,7 +27,7 @@ object Main extends IOApp { .serve } yield stream - private def makeSpnego[F[_]: ConcurrentEffect: ContextShift: Timer] = { + private def makeSpnego[F[_]: Async] = { val realm = sys.env.getOrElse("REALM", "EXAMPLE.ORG") val principal = sys.env.getOrElse("PRINCIPAL", s"HTTP/testserver@$realm") val keytab = sys.env.getOrElse("KEYTAB", "/tmp/krb5.keytab")