11package org .thp .cortex .controllers
22
3- import javax .inject .{Inject , Singleton }
4-
5- import scala .collection .immutable
6- import scala .concurrent .{ExecutionContext , Future }
7- import scala .concurrent .duration .{DurationLong , FiniteDuration }
8- import scala .util .Random
9-
10- import play .api .{Configuration , Logger }
11- import play .api .http .Status
12- import play .api .libs .json .Json
13- import play .api .mvc .{AbstractController , Action , AnyContent , ControllerComponents }
14-
153import org .apache .pekko .actor .{ActorSystem , Props }
16- import org .apache .pekko .util .Timeout
174import org .apache .pekko .pattern .ask
5+ import org .apache .pekko .util .Timeout
6+ import org .elastic4play .Timed
7+ import org .elastic4play .controllers ._
8+ import org .elastic4play .services .{AuxSrv , EventSrv , MigrationSrv }
189import org .thp .cortex .models .Roles
19- import org .thp .cortex .services .StreamActor
2010import org .thp .cortex .services .StreamActor .StreamMessages
11+ import org .thp .cortex .services .{StreamActor , UserSrv }
12+ import play .api .http .Status
13+ import play .api .libs .json .Json
14+ import play .api .mvc .{AbstractController , Action , AnyContent , ControllerComponents }
15+ import play .api .{Configuration , Logger }
2116
22- import org .elastic4play .Timed
23- import org .elastic4play .controllers ._
24- import org .elastic4play .services .{AuxSrv , EventSrv , MigrationSrv , UserSrv }
17+ import java .security .SecureRandom
18+ import javax .inject .{Inject , Singleton }
19+ import scala .collection .immutable
20+ import scala .concurrent .duration .{DurationLong , FiniteDuration }
21+ import scala .concurrent .{ExecutionContext , Future }
2522
2623@ Singleton
2724class StreamCtrl (
@@ -70,17 +67,32 @@ class StreamCtrl(
7067 )
7168 private [StreamCtrl ] lazy val logger = Logger (getClass)
7269
70+ // The bootstrap user has no User document in ES yet (initial setup / migration), so there is no
71+ // organization to look up. Such a stream is left unbound (None) instead of being tied to an org.
72+ private val initialUserId = " init"
73+
74+ private def organizationId (userId : String ): Future [Option [String ]] =
75+ if (userId == initialUserId) Future .successful(None )
76+ else userSrv.getOrganizationId(userId).map(Some (_))
77+
7378 /** Create a new stream entry with the event head
7479 */
7580 @ Timed (" controllers.StreamCtrl.create" )
76- def create : Action [AnyContent ] = authenticated(Roles .read) {
77- val id = generateStreamId()
78- system.actorOf(Props (classOf [StreamActor ], cacheExpiration, refresh, nextItemMaxWait, globalMaxWait, eventSrv, auxSrv), s " stream- $id" )
79- Ok (id)
81+ def create : Action [AnyContent ] = authenticated(Roles .read).async { request =>
82+ // the stream is bound to the creator's organization
83+ organizationId(request.userId).map { organizationId =>
84+ val id = generateStreamId()
85+ system.actorOf(
86+ Props (classOf [StreamActor ], cacheExpiration, refresh, nextItemMaxWait, globalMaxWait, eventSrv, auxSrv, userSrv, organizationId),
87+ s " stream- $id"
88+ )
89+ Ok (id)
90+ }
8091 }
8192
8293 val alphanumeric : immutable.IndexedSeq [Char ] = ('a' to 'z' ) ++ ('A' to 'Z' ) ++ ('0' to '9' )
83- private [controllers] def generateStreamId () = Seq .fill(10 )(alphanumeric(Random .nextInt(alphanumeric.size))).mkString
94+ private val random = new SecureRandom ()
95+ private [controllers] def generateStreamId () = Seq .fill(10 )(alphanumeric(random.nextInt(alphanumeric.size))).mkString
8496 private [controllers] def isValidStreamId (streamId : String ): Boolean =
8597 streamId.length == 10 && streamId.forall(alphanumeric.contains)
8698
@@ -94,18 +106,28 @@ class StreamCtrl(
94106 if (! isValidStreamId(id)) {
95107 Future .successful(BadRequest (" Invalid stream id" ))
96108 } else {
97- val futureStatus = authenticated.expirationStatus(request) match {
98- case ExpirationError if ! migrationSrv.isMigrating =>
99- userSrv.getInitialUser(request).recoverWith { case _ => authenticated.getFromApiKey(request) }.map(_ => OK )
100- case _ : ExpirationWarning => Future .successful(220 )
101- case _ => Future .successful(OK )
102- }
109+ val futureOrganizationAndStatus : Future [(Option [String ], Status )] =
110+ if (migrationSrv.isMigrating)
111+ Future .successful((None , Ok ))
112+ else
113+ authenticated.expirationStatus(request) match {
114+ case ExpirationError =>
115+ userSrv
116+ .getInitialUser(request)
117+ .recoverWith { case _ => authenticated.getFromApiKey(request) }
118+ .flatMap(authContext => organizationId(authContext.userId).map(_ -> Ok ))
119+ case _ : ExpirationWarning =>
120+ authenticated.getContext(request).flatMap(authContext => organizationId(authContext.userId).map(_ -> new Status (220 )))
121+ case _ =>
122+ authenticated.getContext(request).flatMap(authContext => organizationId(authContext.userId).map(_ -> Ok ))
123+ }
103124
104- futureStatus.flatMap { status =>
105- (system.actorSelection(s " /user/stream- $id" ) ? StreamActor .GetOperations ) map {
106- case StreamMessages (operations) => renderer.toOutput(status, operations)
107- case m => InternalServerError (s " Unexpected message : $m ( ${m.getClass}) " )
108- }
125+ futureOrganizationAndStatus.flatMap {
126+ case (organizationId, status) =>
127+ (system.actorSelection(s " /user/stream- $id" ) ? StreamActor .GetOperations (organizationId)) map {
128+ case StreamMessages (operations) => renderer.toOutput(status.header.status, operations)
129+ case m => InternalServerError (s " Unexpected message : $m ( ${m.getClass}) " )
130+ }
109131 }
110132 }
111133 }
@@ -114,7 +136,7 @@ class StreamCtrl(
114136 def status : Action [AnyContent ] = Action { implicit request =>
115137 val status = authenticated.expirationStatus(request) match {
116138 case ExpirationWarning (duration) => Json .obj(" remaining" -> duration.toSeconds, " warning" -> true )
117- case ExpirationError => Json .obj(" remaining" -> 0 , " warning" -> true )
139+ case ExpirationError => Json .obj(" remaining" -> 0 , " warning" -> true )
118140 case ExpirationOk (duration) => Json .obj(" remaining" -> duration.toSeconds, " warning" -> false )
119141 }
120142 Ok (status)
0 commit comments