Skip to content

Commit

Permalink
Merge pull request #89 from pjfanning/softwaremill-pekko
Browse files Browse the repository at this point in the history
Pekko HTTP Session
  • Loading branch information
adamw committed Aug 29, 2023
2 parents 8ff8681 + 426c24f commit 12e10c6
Show file tree
Hide file tree
Showing 68 changed files with 6,712 additions and 18 deletions.
96 changes: 90 additions & 6 deletions build.sbt
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import com.softwaremill.SbtSoftwareMillCommon.commonSmlBuildSettings
import com.softwaremill.Publish.ossPublishSettings

val scala2_12 = "2.12.15"
val scala2_13 = "2.13.8"
val scala2_12 = "2.12.18"
val scala2_13 = "2.13.11"
val scala3 = "3.3.0"
val scala2 = List(scala2_12, scala2_13)
val scala2And3 = List(scala2_12, scala2_13, scala3)

lazy val commonSettings = commonSmlBuildSettings ++ ossPublishSettings ++ Seq(
organization := "com.softwaremill.akka-http-session",
Expand All @@ -16,12 +18,12 @@ val json4sVersion = "4.0.4"
val akkaStreamsProvided = "com.typesafe.akka" %% "akka-stream" % akkaStreamsVersion % "provided"
val akkaStreamsTestkit = "com.typesafe.akka" %% "akka-stream-testkit" % akkaStreamsVersion % "test"

val scalaTest = "org.scalatest" %% "scalatest" % "3.2.11" % "test"
val scalaTest = "org.scalatest" %% "scalatest" % "3.2.16" % "test"

lazy val rootProject = (project in file("."))
.settings(commonSettings: _*)
.settings(publish / skip := true, name := "akka-http-session", scalaVersion := scala2_13)
.aggregate(core.projectRefs ++ jwt.projectRefs ++ example.projectRefs ++ javaTests.projectRefs: _*)
.settings(publish / skip := true, name := "akka-http-session-root", scalaVersion := scala2_13)
.aggregate(core.projectRefs ++ jwt.projectRefs ++ example.projectRefs ++ javaTests.projectRefs ++
pekkoCore.projectRefs ++ pekkoJwt.projectRefs ++ pekkoExample.projectRefs ++ pekkoJavaTests.projectRefs: _*)

lazy val core = (projectMatrix in file("core"))
.settings(commonSettings: _*)
Expand Down Expand Up @@ -88,3 +90,85 @@ lazy val javaTests = (projectMatrix in file("javaTests"))
)
.jvmPlatform(scalaVersions = scala2)
.dependsOn(core, jwt)

// Pekko build

lazy val pekkoCommonSettings = commonSmlBuildSettings ++ ossPublishSettings ++ Seq(
organization := "com.softwaremill.pekko-http-session",
versionScheme := Some("early-semver")
)

val pekkoHttpVersion = "1.0.0"
val pekkoStreamsVersion = "1.0.1"

val pekkoStreamsProvided = "org.apache.pekko" %% "pekko-stream" % pekkoStreamsVersion % "provided"
val pekkoStreamsTestkit = "org.apache.pekko" %% "pekko-stream-testkit" % pekkoStreamsVersion % "test"
val scalaJava8CompatVersion = "1.0.2"

lazy val pekkoCore = (projectMatrix in file("pekko-http-session/core"))
.settings(pekkoCommonSettings: _*)
.settings(
name := "core",
libraryDependencies ++= Seq(
"org.apache.pekko" %% "pekko-http" % pekkoHttpVersion,
"org.scala-lang.modules" %% "scala-java8-compat" % scalaJava8CompatVersion,
pekkoStreamsProvided,
"org.apache.pekko" %% "pekko-http-testkit" % pekkoHttpVersion % "test",
pekkoStreamsTestkit,
"org.scalacheck" %% "scalacheck" % "1.15.4" % "test",
scalaTest
)
)
.jvmPlatform(scalaVersions = scala2And3)

lazy val pekkoJwt = (projectMatrix in file("pekko-http-session/jwt"))
.settings(pekkoCommonSettings: _*)
.settings(
name := "jwt",
libraryDependencies ++= Seq(
"org.json4s" %% "json4s-jackson" % json4sVersion,
"org.json4s" %% "json4s-ast" % json4sVersion,
"org.json4s" %% "json4s-core" % json4sVersion,
pekkoStreamsProvided,
scalaTest
),
// generating docs for 2.13 causes an error: "not found: type DefaultFormats$"
Compile / doc / sources := Seq.empty
)
.jvmPlatform(scalaVersions = scala2And3)
.dependsOn(pekkoCore)

lazy val pekkoExample = (projectMatrix in file("pekko-http-session/example"))
.settings(pekkoCommonSettings: _*)
.settings(
publishArtifact := false,
libraryDependencies ++= Seq(
pekkoStreamsProvided,
"com.typesafe.scala-logging" %% "scala-logging" % "3.9.4",
"ch.qos.logback" % "logback-classic" % "1.2.12",
"org.json4s" %% "json4s-ext" % json4sVersion
)
)
.jvmPlatform(scalaVersions = scala2And3)
.dependsOn(pekkoCore, pekkoJwt)

lazy val pekkoJavaTests = (projectMatrix in file("pekko-http-session/javaTests"))
.settings(pekkoCommonSettings: _*)
.settings(
name := "javaTests",
Test / testOptions := Seq(Tests.Argument(TestFrameworks.JUnit, "-a")), // required for javadsl JUnit tests
crossPaths := false, // https://github.com/sbt/junit-interface/issues/35
publishArtifact := false,
libraryDependencies ++= Seq(
pekkoStreamsProvided,
"org.apache.pekko" %% "pekko-http" % pekkoHttpVersion,
"org.scala-lang.modules" %% "scala-java8-compat" % scalaJava8CompatVersion,
"org.apache.pekko" %% "pekko-http-testkit" % pekkoHttpVersion % "test",
pekkoStreamsTestkit,
"junit" % "junit" % "4.13.2" % "test",
"com.github.sbt" % "junit-interface" % "0.13.3" % "test",
scalaTest
)
)
.jvmPlatform(scalaVersions = scala2And3)
.dependsOn(pekkoCore, pekkoJwt)
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ class CsrfDirectivesTest extends AnyFlatSpec with ScalatestRouteTest with Matche
responseAs[String] should be("ok")

val csrfCookieOption = header[`Set-Cookie`]
csrfCookieOption should be('defined)
csrfCookieOption shouldBe defined
val Some(csrfCookie) = csrfCookieOption

csrfCookie.cookie.name should be(cookieName)
Expand Down
8 changes: 4 additions & 4 deletions core/src/test/scala/com/softwaremill/session/OneOffTest.scala
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ class OneOffTest extends AnyFlatSpec with ScalatestRouteTest with Matchers with
"Using cookies" should "set the correct session cookie name" in {
Get("/set") ~> createRoutes(TestUsingCookies) ~> check {
val sessionCookieOption = header[`Set-Cookie`]
sessionCookieOption should be('defined)
sessionCookieOption shouldBe defined
val Some(sessionCookie) = sessionCookieOption

sessionCookie.cookie.name should be(TestUsingCookies.sessionCookieName)
Expand All @@ -71,7 +71,7 @@ class OneOffTest extends AnyFlatSpec with ScalatestRouteTest with Matchers with
responseAs[String] should be("ok")

val sessionOption = using.getSession
sessionOption should be('defined)
sessionOption shouldBe defined

using.isSessionExpired should be(false)
}
Expand Down Expand Up @@ -188,7 +188,7 @@ class OneOffTest extends AnyFlatSpec with ScalatestRouteTest with Matchers with
val legacySession = Legacy.encodeV0_5_1(data, now, sessionConfig)

Get("/getReq") ~> addHeader(using.setSessionHeader(legacySession)) ~> routes(manager_tokenMigrationFromV0_5_1) ~> check {
using.getSession should be('defined)
using.getSession shouldBe defined
responseAs[String] should be(data.toString)
}
}
Expand All @@ -213,7 +213,7 @@ class OneOffTest extends AnyFlatSpec with ScalatestRouteTest with Matchers with
val legacySession = Legacy.encodeV0_5_2(data, now, sessionConfig)

Get("/getReq") ~> addHeader(using.setSessionHeader(legacySession)) ~> routes(manager_tokenMigrationFromV0_5_2) ~> check {
using.getSession should be('defined)
using.getSession shouldBe defined
responseAs[String] should be(data.toString)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,9 +64,9 @@ class RefreshableTest extends AnyFlatSpec with ScalatestRouteTest with Matchers
Get("/set") ~> routes ~> check {
responseAs[String] should be("ok")

using.getSession should be('defined)
using.getSession shouldBe defined
using.countSessionHeaders should be(1)
using.getRefreshToken should be('defined)
using.getRefreshToken shouldBe defined
using.countRefreshTokenHeaders should be(1)
}
}
Expand Down Expand Up @@ -120,7 +120,7 @@ class RefreshableTest extends AnyFlatSpec with ScalatestRouteTest with Matchers
Get("/set") ~> routes ~> check {
val Some(token1) = using.getRefreshToken
val session1 = using.getSession
session1 should be('defined)
session1 shouldBe defined

Get("/getOpt") ~>
addHeader(using.setRefreshTokenHeader(token1)) ~>
Expand All @@ -129,7 +129,7 @@ class RefreshableTest extends AnyFlatSpec with ScalatestRouteTest with Matchers
using.countSessionHeaders should be(1)
using.countRefreshTokenHeaders should be(1)
val session2 = using.getSession
session2 should be('defined)
session2 shouldBe defined
session2 should not be (session1)
}
}
Expand Down Expand Up @@ -275,7 +275,7 @@ class RefreshableTest extends AnyFlatSpec with ScalatestRouteTest with Matchers

// new token should be generated
session1 should not be (session3)
token3Opt should be('defined)
token3Opt shouldBe defined
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package com.softwaremill.pekkohttpsession.javadsl

import com.softwaremill.pekkohttpsession
import com.softwaremill.pekkohttpsession.CsrfCheckMode

import java.util.function.Supplier
import org.apache.pekko.http.javadsl.server.Route
import org.apache.pekko.http.javadsl.server.directives.RouteAdapter

/**
* Java alternative for com.softwaremill.pekkohttpsession.CsrfDirectives
*/
trait CsrfDirectives {

def hmacTokenCsrfProtection[T](checkMode: CsrfCheckMode[T], inner: Supplier[Route]): Route = RouteAdapter {
pekkohttpsession.CsrfDirectives.hmacTokenCsrfProtection(checkMode) {
inner.get.asInstanceOf[RouteAdapter].delegate
}
}

/**
* @deprecated as of release 0.6.1, replaced by {@link #hmacTokensCsrfProtection()}
*/
def randomTokenCsrfProtection[T](checkMode: CsrfCheckMode[T], inner: Supplier[Route]): Route =
hmacTokenCsrfProtection(checkMode, inner)

def setNewCsrfToken[T](checkMode: CsrfCheckMode[T], inner: Supplier[Route]): Route = RouteAdapter {
pekkohttpsession.CsrfDirectives.setNewCsrfToken(checkMode) {
inner.get.asInstanceOf[RouteAdapter].delegate
}
}

}

object CsrfDirectives extends CsrfDirectives
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package com.softwaremill.pekkohttpsession.javadsl;

import org.apache.pekko.http.javadsl.server.AllDirectives;
import org.apache.pekko.http.javadsl.server.Route;
import com.softwaremill.pekkohttpsession.CsrfCheckMode;
import com.softwaremill.pekkohttpsession.GetSessionTransport;
import com.softwaremill.pekkohttpsession.SessionContinuity;
import com.softwaremill.pekkohttpsession.SessionManager;
import com.softwaremill.pekkohttpsession.SessionResult;
import com.softwaremill.pekkohttpsession.SetSessionTransport;

import java.util.Optional;
import java.util.function.Function;
import java.util.function.Supplier;

public class HttpSessionAwareDirectives<T> extends AllDirectives {

private final SessionManager<T> sessionManager;

public HttpSessionAwareDirectives(SessionManager<T> sessionManager) {
this.sessionManager = sessionManager;
}

public Route session(SessionContinuity sc, GetSessionTransport st, Function<SessionResult<T>, Route> continuity) {
return SessionDirectives$.MODULE$.session(sc, st, continuity);
}

public Route setSession(SessionContinuity sc, SetSessionTransport st, T session, Supplier<Route> continuity) {
return SessionDirectives$.MODULE$.setSession(sc, st, session, continuity);
}

public Route optionalSession(SessionContinuity sc, SetSessionTransport st, Function<Optional<T>, Route> continuity) {
return SessionDirectives$.MODULE$.optionalSession(sc, st, continuity);
}

public Route requiredSession(SessionContinuity<T> sc, SetSessionTransport st, Function<T, Route> continuity) {
return SessionDirectives$.MODULE$.requiredSession(sc, st, continuity);
}

public Route touchRequiredSession(SessionContinuity<T> sc, SetSessionTransport st, Function<T, Route> continuity) {
return SessionDirectives$.MODULE$.touchRequiredSession(sc, st, continuity);
}

public Route invalidateSession(SessionContinuity<T> sc, SetSessionTransport st, Supplier<Route> continuity) {
return SessionDirectives$.MODULE$.invalidateSession(sc, st, continuity);
}

public Route setNewCsrfToken(CsrfCheckMode<T> checkHeader, Supplier<Route> continuity) {
return CsrfDirectives$.MODULE$.setNewCsrfToken(checkHeader, continuity);
}

public Route randomTokenCsrfProtection(CsrfCheckMode<T> checkHeader, Supplier<Route> continuity) {
return CsrfDirectives$.MODULE$.randomTokenCsrfProtection(checkHeader, continuity);
}

public SessionManager<T> getSessionManager() {
return sessionManager;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.softwaremill.pekkohttpsession.javadsl

/**
* Can't use the trait com.softwaremill.pekkohttpsession.InMemoryRefreshTokenStorage in Java code, hence this wrapper
* http://stackoverflow.com/questions/7637752/using-scala-traits-with-implemented-methods-in-java
*/
abstract class InMemoryRefreshTokenStorage[T]() extends com.softwaremill.pekkohttpsession.InMemoryRefreshTokenStorage[T]
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package com.softwaremill.pekkohttpsession.javadsl

import com.softwaremill.pekkohttpsession
import com.softwaremill.pekkohttpsession.{GetSessionTransport, OneOffSessionDirectives, RefreshableSessionDirectives, SessionContinuity, SessionResult, SetSessionTransport}

import java.util.Optional
import java.util.function.Supplier
import org.apache.pekko.http.javadsl.server.Route
import org.apache.pekko.http.javadsl.server.directives.RouteAdapter

import scala.compat.java8.OptionConverters._

/**
* Java alternative for com.softwaremill.pekkohttpsession.SessionDirectives
*/
trait SessionDirectives extends OneOffSessionDirectives with RefreshableSessionDirectives {

def session[T](sc: SessionContinuity[T], st: GetSessionTransport, inner: java.util.function.Function[SessionResult[T], Route]): Route = RouteAdapter {
pekkohttpsession.SessionDirectives.session(sc, st) { sessionResult =>
inner.apply(sessionResult).asInstanceOf[RouteAdapter].delegate
}
}

def setSession[T](sc: SessionContinuity[T], st: SetSessionTransport, v: T, inner: Supplier[Route]): Route = RouteAdapter {
pekkohttpsession.SessionDirectives.setSession(sc, st, v) {
inner.get.asInstanceOf[RouteAdapter].delegate
}
}

def invalidateSession[T](sc: SessionContinuity[T], st: GetSessionTransport, inner: Supplier[Route]): Route = RouteAdapter {
pekkohttpsession.SessionDirectives.invalidateSession(sc, st) {
inner.get.asInstanceOf[RouteAdapter].delegate
}
}

def optionalSession[T](sc: SessionContinuity[T], st: GetSessionTransport, inner: java.util.function.Function[Optional[T], Route]): Route = RouteAdapter {
pekkohttpsession.SessionDirectives.optionalSession(sc, st) { session =>
inner.apply(session.asJava).asInstanceOf[RouteAdapter].delegate
}
}

def requiredSession[T](sc: SessionContinuity[T], st: GetSessionTransport, inner: java.util.function.Function[T, Route]): Route = RouteAdapter {
pekkohttpsession.SessionDirectives.requiredSession(sc, st) { session =>
inner.apply(session).asInstanceOf[RouteAdapter].delegate
}
}

def touchRequiredSession[T](sc: SessionContinuity[T], st: GetSessionTransport, inner: java.util.function.Function[T, Route]): Route = RouteAdapter {
pekkohttpsession.SessionDirectives.touchRequiredSession(sc, st) { session =>
inner.apply(session).asInstanceOf[RouteAdapter].delegate
}
}

}

object SessionDirectives extends SessionDirectives

Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package com.softwaremill.pekkohttpsession.javadsl;

import com.softwaremill.pekkohttpsession.MultiValueSessionSerializer;
import com.softwaremill.pekkohttpsession.SessionSerializer;
import com.softwaremill.pekkohttpsession.SessionSerializer$;
import com.softwaremill.pekkohttpsession.converters.MapConverters;
import scala.collection.JavaConverters;
import scala.compat.java8.JFunction0;
import scala.compat.java8.JFunction1;
import scala.util.Try;

import java.util.Map;

/**
* Wrapper for session serializers in com.softwaremill.pekkohttpsession.SessionSerializer
*/
public final class SessionSerializers {

public static final SessionSerializer<String, String> StringToStringSessionSerializer = SessionSerializer$.MODULE$.stringToStringSessionSerializer();
public static final SessionSerializer<Integer, String> IntToStringSessionSerializer = (SessionSerializer<Integer, String>) (SessionSerializer) SessionSerializer$.MODULE$.intToStringSessionSerializer();
public static final SessionSerializer<Long, String> LongToStringSessionSerializer = (SessionSerializer<Long, String>) (SessionSerializer) SessionSerializer$.MODULE$.longToStringSessionSerializer();
public static final SessionSerializer<Float, String> FloatToStringSessionSerializer = (SessionSerializer<Float, String>) (SessionSerializer) SessionSerializer$.MODULE$.floatToStringSessionSerializer();
public static final SessionSerializer<Double, String> DoubleToStringSessionSerializer = (SessionSerializer<Double, String>) (SessionSerializer) SessionSerializer$.MODULE$.doubleToStringSessionSerializer();

public static final SessionSerializer<Map<String, String>, String> MapToStringSessionSerializer = new MultiValueSessionSerializer<>(
(JFunction1<Map<String, String>, scala.collection.immutable.Map<String, String>>) m -> MapConverters.toImmutableMap(m),
(JFunction1<scala.collection.immutable.Map<String, String>, Try<Map<String, String>>>) v1 ->
Try.apply((JFunction0<Map<String, String>>) () -> JavaConverters.mapAsJavaMapConverter(v1).asJava())
);

private SessionSerializers() {
}

}

0 comments on commit 12e10c6

Please sign in to comment.