Skip to content

Commit

Permalink
upgrade http4s and other libraries
Browse files Browse the repository at this point in the history
  • Loading branch information
novakov-alexey committed Jun 16, 2024
1 parent bd7e80b commit 3b0b961
Show file tree
Hide file tree
Showing 13 changed files with 126 additions and 96 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/scala.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,5 @@ metals.sbt
out
.dotty-ide-disabled
settings.json
.ammonite
.ammonite
.bsp
1 change: 1 addition & 0 deletions .scalafix.conf
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,4 @@ DisableSyntax.noXml = true
DisableSyntax.noFinalVal = true
DisableSyntax.noFinalize = true
DisableSyntax.noValPatterns = true
OrganizeImports.targetDialect = Scala3
13 changes: 8 additions & 5 deletions .scalafmt.conf
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -14,9 +12,14 @@ rewrite.rules = [
SortImports,
RedundantParens
]
unindentTopLevelOperators = true
includeCurlyBraceInSelectChains = false
project.excludeFilters = [
".*~undo-tree~"
]
project.git = true
project.git = true
runner.dialect = scala212source3
fileOverride {
"glob:**/project/Dependencies.sc/**" {
runner.dialect = scala3
}
}
49 changes: 28 additions & 21 deletions build.sc
Original file line number Diff line number Diff line change
@@ -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") ++
Expand All @@ -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)
}
Expand All @@ -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
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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 =>
Expand All @@ -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)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 = ""
Expand All @@ -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,
Expand All @@ -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)] =
Expand Down Expand Up @@ -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(
Expand All @@ -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
Expand All @@ -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](
Expand Down Expand Up @@ -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]
}
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
Loading

0 comments on commit 3b0b961

Please sign in to comment.