Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Pekko HTTP Session #89

Merged
merged 3 commits into from
Aug 29, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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() {
}

}