11package app .softnetwork .session .model
22
33import java .util .UUID
4-
5- import com .softwaremill .session .{SessionConfig , SessionManager , SessionSerializer }
4+ import com .softwaremill .session .{
5+ JValueSessionSerializer ,
6+ JwtSessionEncoder ,
7+ SessionConfig ,
8+ SessionEncoder ,
9+ SessionManager ,
10+ SessionSerializer
11+ }
612import app .softnetwork .persistence .model .ProtobufDomainObject
713import app .softnetwork .session .config .Settings .Session ._
814import app .softnetwork .session .security .Crypto
9-
15+ import org . json4s .{ Formats , JValue }
1016import org .softnetwork .session .model .Session
1117
12- import scala .collection . mutable
18+ import scala .language . implicitConversions
1319import scala .util .Try
1420
1521/** Created by smanciot on 29/04/2021.
1622 */
1723trait SessionData extends ProtobufDomainObject
1824
19- trait SessionDecorator { _ : Session =>
25+ trait SessionDecorator { self : Session =>
2026
2127 import Session ._
2228
2329 private var dirty : Boolean = false
2430
2531 def clear (): Session = {
2632 val theId = id
27- data.clear()
28- this += CookieName -> theId
33+ withKvs(Map .empty[String , String ] + (SessionName -> theId))
2934 }
3035
3136 def isDirty : Boolean = dirty
3237
33- def get (key : String ): Option [String ] = data .get(key)
38+ def get (key : String ): Option [String ] = kvs .get(key)
3439
35- def isEmpty : Boolean = data .isEmpty
40+ def isEmpty : Boolean = kvs .isEmpty
3641
37- def contains (key : String ): Boolean = data .contains(key)
42+ def contains (key : String ): Boolean = kvs .contains(key)
3843
39- def -= (key : String ): Session = synchronized {
40- dirty = true
41- data -= key
42- this
43- }
44+ def - (key : String ): Session =
45+ synchronized {
46+ dirty = true
47+ withKvs(kvs - key)
48+ }
4449
45- def += (kv : (String , String )): Session = synchronized {
46- dirty = true
47- data += kv
48- this
49- }
50+ def + (kv : (String , String )): Session =
51+ synchronized {
52+ dirty = true
53+ withKvs(kvs + kv)
54+ }
5055
51- def apply (key : String ): Any = data(key)
56+ def ++ (kvs : Seq [(String , String )]): Session =
57+ synchronized {
58+ dirty = true
59+ withKvs(this .kvs ++ kvs)
60+ }
5261
53- lazy val id : String = data(CookieName )
62+ def apply (key : String ): Any = kvs(key)
63+
64+ lazy val id : String = kvs(SessionName )
5465
5566 lazy val admin : Boolean = get(adminKey).exists(_.toBoolean)
5667
@@ -66,67 +77,95 @@ trait SessionCompanion {
6677
6778 val anonymousKey = " anonymous"
6879
69- val sessionConfig : SessionConfig = {
70- SessionConfig .default(CookieSecret )
71- }
80+ val SessionName : String = DefaultSessionConfig .sessionCookieConfig.name
7281
73- implicit val sessionManager : SessionManager [Session ] = new SessionManager [Session ](sessionConfig)
82+ val _refreshable : Boolean = Continuity match {
83+ case " refreshable" => true
84+ case _ => false
85+ }
7486
7587 def apply (): Session =
7688 Session .defaultInstance
77- .withData(mutable. Map (CookieName -> UUID .randomUUID.toString))
78- .withRefreshable(false )
89+ .withKvs( Map (SessionName -> UUID .randomUUID.toString))
90+ .withRefreshable(_refreshable )
7991
8092 def apply (uuid : String ): Session =
8193 Session .defaultInstance
82- .withData(mutable.Map (CookieName -> uuid))
83- .withRefreshable(false )
84-
85- implicit def sessionSerializer : SessionSerializer [Session , String ] =
86- new SessionSerializer [Session , String ] {
87- override def serialize (session : Session ): String = {
88- val encoded = java.net.URLEncoder
89- .encode(
90- session.data
91- .filterNot(_._1.contains(" :" ))
92- .map(d => d._1 + " :" + d._2)
93- .mkString(" \u0000 " ),
94- " UTF-8"
95- )
96- Crypto .sign(encoded, CookieSecret ) + " -" + encoded
97- }
94+ .withKvs(Map (SessionName -> uuid))
95+ .withRefreshable(_refreshable)
9896
99- override def deserialize (data : String ): Try [Session ] = {
100- def urldecode (data : String ) =
101- mutable.Map [String , String ](
102- java.net.URLDecoder
103- .decode(data, " UTF-8" )
104- .split(" \u0000 " )
105- .map(_.split(" :" ))
106- .map(p => p(0 ) -> p.drop(1 ).mkString(" :" )): _*
107- )
108- // Do not change this unless you understand the security issues behind timing attacks.
109- // This method intentionally runs in constant time if the two strings have the same length.
110- // If it didn't, it would be vulnerable to a timing attack.
111- def safeEquals (a : String , b : String ) = {
112- if (a.length != b.length) false
113- else {
114- var equal = 0
115- for (i <- Array .range(0 , a.length)) {
116- equal |= a(i) ^ b(i)
117- }
118- equal == 0
119- }
120- }
97+ }
12198
122- Try {
123- val splitted = data.split(" -" )
124- val message = splitted.tail.mkString(" -" )
125- if (safeEquals(splitted(0 ), Crypto .sign(message, CookieSecret )))
126- Session .defaultInstance.withData(urldecode(message))
127- else
128- throw new Exception (" corrupted encrypted data" )
99+ class BasicSessionSerializer (val serverSecret : String ) extends SessionSerializer [Session , String ] {
100+
101+ override def serialize (session : Session ): String = {
102+ val encoded = java.net.URLEncoder
103+ .encode(
104+ session.kvs
105+ .filterNot(_._1.contains(" :" ))
106+ .map(d => d._1 + " :" + d._2)
107+ .mkString(" \u0000 " ),
108+ " UTF-8"
109+ )
110+ Crypto .sign(encoded, serverSecret) + " -" + encoded
111+ }
112+
113+ override def deserialize (kvs : String ): Try [Session ] = {
114+ def urldecode (kvs : String ) =
115+ Map [String , String ](
116+ java.net.URLDecoder
117+ .decode(kvs, " UTF-8" )
118+ .split(" \u0000 " )
119+ .map(_.split(" :" ))
120+ .map(p => p(0 ) -> p.drop(1 ).mkString(" :" )): _*
121+ )
122+
123+ // Do not change this unless you understand the security issues behind timing attacks.
124+ // This method intentionally runs in constant time if the two strings have the same length.
125+ // If it didn't, it would be vulnerable to a timing attack.
126+ def safeEquals (a : String , b : String ) = {
127+ if (a.length != b.length) false
128+ else {
129+ var equal = 0
130+ for (i <- Array .range(0 , a.length)) {
131+ equal |= a(i) ^ b(i)
129132 }
133+ equal == 0
130134 }
131135 }
136+
137+ Try {
138+ val split = kvs.split(" -" )
139+ val message = split.tail.mkString(" -" )
140+ if (safeEquals(split(0 ), Crypto .sign(message, serverSecret)))
141+ Session .defaultInstance.withKvs(urldecode(message))
142+ else
143+ throw new Exception (" corrupted encrypted kvs" )
144+ }
145+ }
146+ }
147+
148+ object SessionSerializers {
149+
150+ def basic (secret : String ): SessionSerializer [Session , String ] =
151+ new BasicSessionSerializer (secret)
152+
153+ def jwt (implicit formats : Formats ): SessionSerializer [Session , JValue ] =
154+ JValueSessionSerializer .caseClass[Session ]
155+ }
156+
157+ object SessionManagers {
158+
159+ def basic (implicit sessionConfig : SessionConfig ): SessionManager [Session ] = {
160+ import SessionEncoder ._
161+ implicit val serializer : SessionSerializer [Session , String ] =
162+ SessionSerializers .basic(sessionConfig.serverSecret)
163+ new SessionManager [Session ](sessionConfig)
164+ }
165+
166+ def jwt (implicit sessionConfig : SessionConfig , formats : Formats ): SessionManager [Session ] = {
167+ implicit val serializer : SessionSerializer [Session , JValue ] = SessionSerializers .jwt(formats)
168+ implicit val encoder : JwtSessionEncoder [Session ] = new JwtSessionEncoder [Session ]
169+ new SessionManager [Session ](sessionConfig)(encoder)
170+ }
132171}
0 commit comments