Skip to content

Commit

Permalink
Apply scalafmt, enforce scalafmt in CI (#52)
Browse files Browse the repository at this point in the history
  • Loading branch information
ptrdom committed Oct 24, 2022
1 parent db732a9 commit e7a8e55
Show file tree
Hide file tree
Showing 11 changed files with 112 additions and 91 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/scala.yml
Expand Up @@ -16,4 +16,4 @@ jobs:
with:
java-version: 1.8
- name: Run tests
run: sbt +testOnly
run: sbt scalafmtSbtCheck scalafmtCheckAll +testOnly
17 changes: 9 additions & 8 deletions project/Scapegoat.scala
Expand Up @@ -24,26 +24,27 @@ object Scapegoat extends AutoPlugin {

// ---

import scala.xml.{ Elem => XmlElem, Node => XmlNode }
import scala.xml.{Elem => XmlElem, Node => XmlNode}

private def transformPomDependencies(tx: XmlElem => Option[XmlNode]): XmlNode => XmlNode = { node: XmlNode =>
import scala.xml.{ NodeSeq, XML }
import scala.xml.transform.{ RewriteRule, RuleTransformer }
import scala.xml.{NodeSeq, XML}
import scala.xml.transform.{RewriteRule, RuleTransformer}

val tr = new RuleTransformer(new RewriteRule {
override def transform(node: XmlNode): NodeSeq = node match {
case e: XmlElem if e.label == "dependency" => tx(e) match {
case Some(n) => n
case _ => NodeSeq.Empty
}
case e: XmlElem if e.label == "dependency" =>
tx(e) match {
case Some(n) => n
case _ => NodeSeq.Empty
}

case _ => node
}
})

tr.transform(node).headOption match {
case Some(transformed) => transformed
case _ => sys.error("Fails to transform the POM")
case _ => sys.error("Fails to transform the POM")
}
}
}
Expand Down
2 changes: 2 additions & 0 deletions project/plugins.sbt
Expand Up @@ -3,3 +3,5 @@ addSbtPlugin("com.github.gseitz" % "sbt-release" % "1.0.8")
addSbtPlugin("com.github.sbt" % "sbt-ci-release" % "1.5.10")

addSbtPlugin("com.sksamuel.scapegoat" %% "sbt-scapegoat" % "1.1.0")

addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.4.6")
98 changes: 51 additions & 47 deletions src/main/scala/com/stackstate/pac4j/AkkaHttpSecurity.scala
Expand Up @@ -47,12 +47,9 @@ object AkkaHttpSecurity {
* If the request proceeds, other ways (e.g. basic auth) are assumed to be configured in pac4j in order to pass
* credentials.
*/
private def getFormFields(
entity: HttpEntity,
enforceFormEncoding: Boolean,
)(implicit materializer: Materializer,
executionContext: ExecutionContext,
): Future[Seq[(String, String)]] = {
private def getFormFields(entity: HttpEntity, enforceFormEncoding: Boolean)(implicit materializer: Materializer,
executionContext: ExecutionContext,
): Future[Seq[(String, String)]] = {
Unmarshal(entity)
.to[StrictForm]
.fast
Expand Down Expand Up @@ -113,55 +110,64 @@ class AkkaHttpSecurity(config: Config, sessionStorage: SessionStorage, val sessi
* an AkkaHttpWebContext and any changes to this context are applied when the route returns (e.g. headers/cookies).
*/
def withContext(existingContext: Option[AkkaHttpWebContext] = None, formParams: Map[String, String] = Map.empty): Directive1[AkkaHttpWebContext] =
Directive[Tuple1[AkkaHttpWebContext]] { inner =>
ctx =>
val akkaWebContext = existingContext.getOrElse(new AkkaHttpWebContext(
Directive[Tuple1[AkkaHttpWebContext]] { inner => ctx =>
val akkaWebContext = existingContext.getOrElse(
new AkkaHttpWebContext(
request = ctx.request,
formFields = formParams.toSeq,
sessionStorage = sessionStorage,
sessionCookieName = sessionCookieName,
))
)
)

inner(Tuple1(akkaWebContext))(ctx).map[RouteResult] {
case Complete(response) => Complete(applyHeadersAndCookiesToResponse(akkaWebContext.getChanges)(response))
case rejection => rejection
}
inner(Tuple1(akkaWebContext))(ctx).map[RouteResult] {
case Complete(response) => Complete(applyHeadersAndCookiesToResponse(akkaWebContext.getChanges)(response))
case rejection => rejection
}
}

def withFormParameters(enforceFormEncoding: Boolean): Directive1[Map[String, String]] =
Directive[Tuple1[Map[String, String]]] { inner =>
ctx =>
import ctx.materializer
getFormFields(ctx.request.entity, enforceFormEncoding).flatMap { params =>
inner(Tuple1(params.toMap))(ctx)
}
Directive[Tuple1[Map[String, String]]] { inner => ctx =>
import ctx.materializer
getFormFields(ctx.request.entity, enforceFormEncoding).flatMap { params =>
inner(Tuple1(params.toMap))(ctx)
}
}

/**
* Authenticate using the provided pac4j configuration. Delivers an AuthenticationRequest which can be used for further authorization
* this does not apply any authorization ofr filtering.
*/
@SuppressWarnings(Array("NullAssignment"))
def withAuthentication(clients: String = null /* Default null, meaning all defined clients */ ,
def withAuthentication(clients: String = null /* Default null, meaning all defined clients */,
multiProfile: Boolean = true,
authorizers: String = ""): Directive1[AuthenticatedRequest] =
withContext().flatMap { akkaWebContext =>
Directive[Tuple1[AuthenticatedRequest]] { inner =>
ctx =>
// TODO This is a hack to ensure that any underlying Futures are scheduled (and handled in case of errors) from here
// TODO Fix this properly
Future.successful({}).flatMap { _ =>
val securityAccessAdapter: SecurityGrantedAccessAdapter[Future[RouteResult], AkkaHttpWebContext] =
(context: AkkaHttpWebContext, profiles: util.Collection[UserProfile], _: AnyRef) => {
val authenticatedRequest = AuthenticatedRequest(context, profiles.asScala.toList)
inner(Tuple1(authenticatedRequest))(ctx)
}

securityLogic.perform(akkaWebContext, config, securityAccessAdapter, actionAdapter, clients, authorizers, DefaultMatchers.SECURITYHEADERS, multiProfile).map {
result =>
result
Directive[Tuple1[AuthenticatedRequest]] { inner => ctx =>
// TODO This is a hack to ensure that any underlying Futures are scheduled (and handled in case of errors) from here
// TODO Fix this properly
Future.successful({}).flatMap { _ =>
val securityAccessAdapter: SecurityGrantedAccessAdapter[Future[RouteResult], AkkaHttpWebContext] =
(context: AkkaHttpWebContext, profiles: util.Collection[UserProfile], _: AnyRef) => {
val authenticatedRequest = AuthenticatedRequest(context, profiles.asScala.toList)
inner(Tuple1(authenticatedRequest))(ctx)
}
}

securityLogic
.perform(
akkaWebContext,
config,
securityAccessAdapter,
actionAdapter,
clients,
authorizers,
DefaultMatchers.SECURITYHEADERS,
multiProfile
)
.map { result =>
result
}
}
}
}

Expand All @@ -176,14 +182,13 @@ class AkkaHttpSecurity(config: Config, sessionStorage: SessionStorage, val sessi
existingContext: Option[AkkaHttpWebContext] = None,
setCsrfCookie: Boolean = true): Route =
withFormParameters(enforceFormEncoding) { formParams =>
withContext(existingContext, formParams) { akkaWebContext =>
_ =>
callbackLogic.perform(akkaWebContext, config, actionAdapter, defaultUrl, saveInSession, multiProfile, true, defaultClient.orNull).map {
result =>
if (setCsrfCookie) akkaWebContext.addResponseCsrfCookie()
akkaWebContext.addResponseSessionCookie()
result
}
withContext(existingContext, formParams) { akkaWebContext => _ =>
callbackLogic.perform(akkaWebContext, config, actionAdapter, defaultUrl, saveInSession, multiProfile, true, defaultClient.orNull).map {
result =>
if (setCsrfCookie) akkaWebContext.addResponseCsrfCookie()
akkaWebContext.addResponseSessionCookie()
result
}
}
}

Expand All @@ -192,9 +197,8 @@ class AkkaHttpSecurity(config: Config, sessionStorage: SessionStorage, val sessi
localLogout: Boolean = true,
destroySession: Boolean = true,
centralLogout: Boolean = false): Route = {
withContext() { akkaWebContext =>
_ =>
logoutLogic.perform(akkaWebContext, config, actionAdapter, defaultUrl, logoutPatternUrl, localLogout, destroySession, centralLogout)
withContext() { akkaWebContext => _ =>
logoutLogic.perform(akkaWebContext, config, actionAdapter, defaultUrl, logoutPatternUrl, localLogout, destroySession, centralLogout)
}
}

Expand Down
12 changes: 6 additions & 6 deletions src/main/scala/com/stackstate/pac4j/AkkaHttpWebContext.scala
Expand Up @@ -21,7 +21,7 @@ class AkkaHttpWebContext(val request: HttpRequest,
val formFields: Seq[(String, String)],
private[pac4j] val sessionStorage: SessionStorage,
val sessionCookieName: String)
extends WebContext {
extends WebContext {

import com.stackstate.pac4j.AkkaHttpWebContext._

Expand Down Expand Up @@ -190,11 +190,11 @@ object AkkaHttpWebContext {
new AkkaHttpWebContext(request, formFields, sessionStorage, sessionCookieName)

//This class is where all the HTTP response changes are stored so that they can later be applied to an HTTP Request
case class ResponseChanges private(headers: List[HttpHeader],
contentType: Option[ContentType],
content: String,
cookies: List[HttpCookie],
attributes: Map[String, AnyRef])
case class ResponseChanges private (headers: List[HttpHeader],
contentType: Option[ContentType],
content: String,
cookies: List[HttpCookie],
attributes: Map[String, AnyRef])

object ResponseChanges {
def empty: ResponseChanges = {
Expand Down
13 changes: 6 additions & 7 deletions src/main/scala/com/stackstate/pac4j/AuthenticatedRequest.scala
Expand Up @@ -6,13 +6,12 @@ import org.pac4j.core.profile.UserProfile
* Class which serves as a witness when authentication was successful. This class is private such that the user of this
* library cannot mess with it.
*/
case class AuthenticatedRequest private[pac4j] (
private[pac4j] val webContext: AkkaHttpWebContext,
/*
* Profiles can be accessed such that the user can inspect data
* from the authentication. Should not be empty after authentication
*/
val profiles: List[UserProfile]) {
case class AuthenticatedRequest private[pac4j] (private[pac4j] val webContext: AkkaHttpWebContext,
/*
* Profiles can be accessed such that the user can inspect data
* from the authentication. Should not be empty after authentication
*/
val profiles: List[UserProfile]) {

lazy val mainProfile: UserProfile = profiles.headOption match {
case Some(p) => p
Expand Down
Expand Up @@ -29,7 +29,6 @@ object CsrfCookieAuthorizer {
context
}


def createCookie(token: String, maxAge: Option[FiniteDuration]) = {
val cookie = new Cookie(Pac4jConstants.CSRF_TOKEN, token)
cookie.setPath(CookiePath)
Expand Down
Expand Up @@ -36,8 +36,8 @@ class InMemorySessionStorage(override val sessionLifetime: FiniteDuration) exten
sessionData.get(sessionKey) match {
case None =>
val sessionTime = getTime
expiryQueue = expiryQueue + ExpiryRecord (sessionTime, sessionKey)
sessionData = sessionData + (sessionKey -> DataRecord(sessionTime, Map.empty) )
expiryQueue = expiryQueue + ExpiryRecord(sessionTime, sessionKey)
sessionData = sessionData + (sessionKey -> DataRecord(sessionTime, Map.empty))
true
case Some(_) => false
}
Expand Down
Expand Up @@ -9,12 +9,22 @@ import akka.util.ByteString
import com.stackstate.pac4j.AkkaHttpActionAdapterTest.ActionInt
import com.stackstate.pac4j.http.AkkaHttpActionAdapter
import com.stackstate.pac4j.store.ForgetfulSessionStorage
import org.pac4j.core.exception.http.{BadRequestAction, ForbiddenAction, FoundAction, HttpAction, NoContentAction, OkAction, StatusAction, UnauthorizedAction}
import org.pac4j.core.exception.http.{
BadRequestAction,
ForbiddenAction,
FoundAction,
HttpAction,
NoContentAction,
OkAction,
StatusAction,
UnauthorizedAction
}
import org.scalatest.concurrent.ScalaFutures

class AkkaHttpActionAdapterTest extends AnyWordSpecLike with Matchers with ScalaFutures {

lazy val immediatelyExpireCookie: HttpCookie = HttpCookie.apply(name = AkkaHttpWebContext.DEFAULT_COOKIE_NAME, value = "", maxAge = Some(0), path = Some("/"), httpOnly = true)
lazy val immediatelyExpireCookie: HttpCookie =
HttpCookie.apply(name = AkkaHttpWebContext.DEFAULT_COOKIE_NAME, value = "", maxAge = Some(0), path = Some("/"), httpOnly = true)

"AkkaHttpActionAdapter" should {
"convert 200 to OK" in withContext { context =>
Expand Down
23 changes: 15 additions & 8 deletions src/test/scala/com/stackstate/pac4j/AkkaHttpSecurityTest.scala
Expand Up @@ -103,11 +103,12 @@ class AkkaHttpSecurityTest extends AnyWordSpecLike with Matchers with ScalatestR

val akkaHttpSecurity = new AkkaHttpSecurity(config, new ForgetfulSessionStorage)
val route =
akkaHttpSecurity.withAuthentication() { authenticated => {
authenticated.profiles.size shouldBe 1
authenticated.profiles.head shouldBe profile
complete("called!")
}
akkaHttpSecurity.withAuthentication() { authenticated =>
{
authenticated.profiles.size shouldBe 1
authenticated.profiles.head shouldBe profile
complete("called!")
}
}

Get("/") ~> route ~> check {
Expand Down Expand Up @@ -266,7 +267,6 @@ class AkkaHttpSecurityTest extends AnyWordSpecLike with Matchers with ScalatestR
}
}


"run the callbackLogic should not send back a sessionId if the set csrf cookie is false" in {
val config = new Config()
val existingContext = AkkaHttpWebContext(HttpRequest(), Seq.empty, new ForgetfulSessionStorage, AkkaHttpWebContext.DEFAULT_COOKIE_NAME)
Expand Down Expand Up @@ -300,7 +300,12 @@ class AkkaHttpSecurityTest extends AnyWordSpecLike with Matchers with ScalatestR

"run the callbackLogic should send back a sessionId if the csrf cookie is true" in {
val config = new Config()
val existingContext = AkkaHttpWebContext(HttpRequest(uri = "http://test.com"), Seq.empty, new InMemorySessionStorage(3.minutes), AkkaHttpWebContext.DEFAULT_COOKIE_NAME)
val existingContext = AkkaHttpWebContext(
HttpRequest(uri = "http://test.com"),
Seq.empty,
new InMemorySessionStorage(3.minutes),
AkkaHttpWebContext.DEFAULT_COOKIE_NAME
)

val actionAdapter = new HttpActionAdapter[HttpResponse, AkkaHttpWebContext] {
override def adapt(code: HttpAction, context: AkkaHttpWebContext): HttpResponse = ???
Expand Down Expand Up @@ -329,7 +334,9 @@ class AkkaHttpSecurityTest extends AnyWordSpecLike with Matchers with ScalatestR
localHeaders.find(_.value().contains("pac4jCsrfToken")).get.value().contains(s"Max-Age=$threeMinutesInSeconds;") shouldBe true
localHeaders.find(_.value().contains("AkkaHttpPac4jSession")).get.value().contains(s"Max-Age=$threeMinutesInSeconds;") shouldBe true

val csrfCookies: Seq[HttpCookie] = localHeaders.collect { case setCookie: `Set-Cookie` if setCookie.cookie.name() == "pac4jCsrfToken" => setCookie.cookie }
val csrfCookies: Seq[HttpCookie] = localHeaders.collect {
case setCookie: `Set-Cookie` if setCookie.cookie.name() == "pac4jCsrfToken" => setCookie.cookie
}
// Previous version always added the two cookies. Current version doesn't need domain.
// We add the two to keep it backwards compatible.
csrfCookies.filter(_.domain.nonEmpty).size shouldBe 1
Expand Down
17 changes: 8 additions & 9 deletions src/test/scala/com/stackstate/pac4j/AkkaHttpWebContextTest.scala
Expand Up @@ -132,8 +132,7 @@ class AkkaHttpWebContextTest extends AnyWordSpecLike with Matchers {
webContext.getChanges.cookies.find(_.name == AkkaHttpWebContext.DEFAULT_COOKIE_NAME) shouldBe None
}

"don't add a cookie when the session is empty" in withContext(sessionStorage = new ForgetfulSessionStorage {
}) { webContext =>
"don't add a cookie when the session is empty" in withContext(sessionStorage = new ForgetfulSessionStorage {}) { webContext =>
webContext.getChanges.cookies.find(_.name == AkkaHttpWebContext.DEFAULT_COOKIE_NAME) shouldBe None
}

Expand Down Expand Up @@ -242,12 +241,11 @@ class AkkaHttpWebContextTest extends AnyWordSpecLike with Matchers {
webContext.getOrCreateSessionId() shouldBe "validId"
}

"addResponseSessionCookie with empty session returns an expired cookie" in withContext(
sessionStorage = new ForgetfulSessionStorage {
override def sessionExists(sessionKey: SessionKey): Boolean = true
}
) { webContext =>
val immediatelyExpireCookie: HttpCookie = HttpCookie.apply(name = AkkaHttpWebContext.DEFAULT_COOKIE_NAME, value = "", maxAge = Some(0), path = Some("/"), httpOnly = true)
"addResponseSessionCookie with empty session returns an expired cookie" in withContext(sessionStorage = new ForgetfulSessionStorage {
override def sessionExists(sessionKey: SessionKey): Boolean = true
}) { webContext =>
val immediatelyExpireCookie: HttpCookie =
HttpCookie.apply(name = AkkaHttpWebContext.DEFAULT_COOKIE_NAME, value = "", maxAge = Some(0), path = Some("/"), httpOnly = true)
webContext.addResponseSessionCookie()

webContext.getChanges.cookies shouldBe List(immediatelyExpireCookie)
Expand All @@ -261,7 +259,8 @@ class AkkaHttpWebContextTest extends AnyWordSpecLike with Matchers {
override def sessionExists(sessionKey: SessionKey): Boolean = true
}
) { webContext =>
val validCookie: HttpCookie = HttpCookie.apply(name = AkkaHttpWebContext.DEFAULT_COOKIE_NAME, value = "validId", maxAge = Some(3), path = Some("/"), httpOnly = true)
val validCookie: HttpCookie =
HttpCookie.apply(name = AkkaHttpWebContext.DEFAULT_COOKIE_NAME, value = "validId", maxAge = Some(3), path = Some("/"), httpOnly = true)
webContext.addResponseSessionCookie()

webContext.getChanges.cookies shouldBe List(validCookie)
Expand Down

0 comments on commit e7a8e55

Please sign in to comment.