Skip to content

Commit 62e07ec

Browse files
committed
Merge branch 'release/3.0.0-RC1'
2 parents 0b2928f + 17131df commit 62e07ec

File tree

72 files changed

+1551
-583
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

72 files changed

+1551
-583
lines changed

CHANGELOG.md

+22-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,28 @@
11
# Change Log
22

3-
## [2.1.3](https://github.com/TheHive-Project/Cortex/tree/2.1.3)
3+
## [3.0.0-RC1](https://github.com/TheHive-Project/Cortex/tree/3.0.0-RC1) (2019-04-05)
44

5+
[Full Changelog](https://github.com/TheHive-Project/Cortex/compare/2.1.3...3.0.0-RC1)
6+
7+
**Implemented enhancements:**
8+
9+
- Remove size limitations [\#178](https://github.com/TheHive-Project/Cortex/issues/178)
10+
- Collapse job error messages by default in job history [\#171](https://github.com/TheHive-Project/Cortex/issues/171)
11+
- Update Copyright with year 2019 [\#168](https://github.com/TheHive-Project/Cortex/issues/168)
12+
13+
**Fixed bugs:**
14+
15+
- SSO: Authentication module not found [\#181](https://github.com/TheHive-Project/Cortex/issues/181)
16+
- Akka Dispatcher Blocked [\#170](https://github.com/TheHive-Project/Cortex/issues/170)
17+
18+
**Closed issues:**
19+
20+
- Use files to communicate with analyzer/responder [\#176](https://github.com/TheHive-Project/Cortex/issues/176)
21+
- Provide analyzers and responders packaged with docker [\#175](https://github.com/TheHive-Project/Cortex/issues/175)
22+
- Single sign-on support for Cortex [\#165](https://github.com/TheHive-Project/Cortex/issues/165)
23+
- File extraction [\#120](https://github.com/TheHive-Project/Cortex/issues/120)
24+
25+
## [2.1.3](https://github.com/TheHive-Project/Cortex/tree/2.1.3) (2018-12-20)
526
[Full Changelog](https://github.com/TheHive-Project/Cortex/compare/2.1.2...2.1.3)
627

728
**Implemented enhancements:**

app/org/thp/cortex/Module.scala

+15-2
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,23 @@
11
package org.thp.cortex
2+
23
import com.google.inject.AbstractModule
34
import net.codingwell.scalaguice.{ ScalaModule, ScalaMultibinder }
45
import play.api.libs.concurrent.AkkaGuiceSupport
56
import play.api.{ Configuration, Environment, Logger, Mode }
6-
77
import scala.collection.JavaConverters._
8+
89
import com.google.inject.name.Names
910
import org.reflections.Reflections
1011
import org.reflections.scanners.SubTypesScanner
1112
import org.reflections.util.ConfigurationBuilder
1213
import org.thp.cortex.models.{ AuditedModel, Migration }
13-
import org.thp.cortex.services.{ AuditActor, CortexAuthSrv, UserSrv }
14+
import org.thp.cortex.services._
15+
1416
import org.elastic4play.models.BaseModelDef
1517
import org.elastic4play.services.auth.MultiAuthSrv
1618
import org.elastic4play.services.{ AuthSrv, MigrationOperations }
1719
import org.thp.cortex.controllers.{ AssetCtrl, AssetCtrlDev, AssetCtrlProd }
20+
import services.mappers.{ MultiUserMapperSrv, UserMapper }
1821

1922
class Module(environment: Environment, configuration: Configuration) extends AbstractModule with ScalaModule with AkkaGuiceSupport {
2023

@@ -50,16 +53,26 @@ class Module(environment: Environment, configuration: Configuration) extends Abs
5053
.filterNot(c java.lang.reflect.Modifier.isAbstract(c.getModifiers) || c.isMemberClass)
5154
.filterNot(c c == classOf[MultiAuthSrv] || c == classOf[CortexAuthSrv])
5255
.foreach { authSrvClass
56+
logger.info(s"Loading authentication module $authSrvClass")
5357
authBindings.addBinding.to(authSrvClass)
5458
}
5559

60+
val ssoMapperBindings = ScalaMultibinder.newSetBinder[UserMapper](binder)
61+
reflectionClasses
62+
.getSubTypesOf(classOf[UserMapper])
63+
.asScala
64+
.filterNot(c java.lang.reflect.Modifier.isAbstract(c.getModifiers) || c.isMemberClass)
65+
.filterNot(c c == classOf[MultiUserMapperSrv])
66+
.foreach(mapperCls ssoMapperBindings.addBinding.to(mapperCls))
67+
5668
if (environment.mode == Mode.Prod)
5769
bind[AssetCtrl].to[AssetCtrlProd]
5870
else
5971
bind[AssetCtrl].to[AssetCtrlDev]
6072

6173
bind[org.elastic4play.services.UserSrv].to[UserSrv]
6274
bind[Int].annotatedWith(Names.named("databaseVersion")).toInstance(models.modelVersion)
75+
bind[UserMapper].to[MultiUserMapperSrv]
6376

6477
bind[AuthSrv].to[CortexAuthSrv]
6578
bind[MigrationOperations].to[Migration]
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,16 @@
11
package org.thp.cortex.controllers
22

3-
import javax.inject.{ Inject, Singleton }
43
import scala.concurrent.{ ExecutionContext, Future }
54

6-
import play.api.libs.json.{ JsNumber, JsObject, JsString, Json }
5+
import play.api.libs.json.{ JsObject, JsString, Json }
76
import play.api.mvc.{ AbstractController, Action, AnyContent, ControllerComponents }
87

98
import akka.stream.Materializer
10-
import akka.stream.scaladsl.Sink
11-
import org.thp.cortex.models.{ Roles, Worker, WorkerDefinition }
9+
import javax.inject.{ Inject, Singleton }
10+
import org.thp.cortex.models.{ Roles, Worker }
1211
import org.thp.cortex.services.{ UserSrv, WorkerSrv }
1312

1413
import org.elastic4play.controllers.{ Authenticated, Fields, FieldsBodyParser, Renderer }
15-
import org.elastic4play.models.JsonFormat.baseModelEntityWrites
1614
import org.elastic4play.services.JsonFormat.queryReads
1715
import org.elastic4play.services.{ QueryDSL, QueryDef }
1816

@@ -33,75 +31,42 @@ class AnalyzerCtrl @Inject() (
3331
val sort = request.body.getStrings("sort").getOrElse(Nil)
3432
val isAdmin = request.roles.contains(Roles.orgAdmin)
3533
val (analyzers, analyzerTotal) = workerSrv.findAnalyzersForUser(request.userId, query, range, sort)
36-
val enrichedAnalyzers = analyzers.mapAsync(2)(analyzerJson(isAdmin))
37-
renderer.toOutput(OK, enrichedAnalyzers, analyzerTotal)
34+
renderer.toOutput(OK, analyzers.map(analyzerJson(isAdmin)), analyzerTotal)
3835
}
3936

4037
def get(analyzerId: String): Action[AnyContent] = authenticated(Roles.read).async { request
4138
val isAdmin = request.roles.contains(Roles.orgAdmin)
4239
workerSrv.getForUser(request.userId, analyzerId)
43-
.flatMap(analyzerJson(isAdmin))
44-
.map(renderer.toOutput(OK, _))
45-
}
46-
47-
private val emptyAnalyzerDefinitionJson = Json.obj(
48-
"version" "0.0",
49-
"description" "unknown",
50-
"dataTypeList" Nil,
51-
"author" "unknown",
52-
"url" "unknown",
53-
"license" "unknown")
54-
55-
private def analyzerJson(analyzer: Worker, analyzerDefinition: Option[WorkerDefinition]) = {
56-
analyzer.toJson ++ analyzerDefinition.fold(emptyAnalyzerDefinitionJson) { ad
57-
Json.obj(
58-
"maxTlp" (analyzer.config \ "max_tlp").asOpt[JsNumber],
59-
"maxPap" (analyzer.config \ "max_pap").asOpt[JsNumber],
60-
"version" ad.version,
61-
"description" ad.description,
62-
"author" ad.author,
63-
"url" ad.url,
64-
"license" ad.license,
65-
"baseConfig" ad.baseConfiguration)
66-
} + ("analyzerDefinitionId" JsString(analyzer.workerDefinitionId())) // For compatibility reason
40+
.map(a renderer.toOutput(OK, analyzerJson(isAdmin)(a)))
6741
}
6842

69-
private def analyzerJson(isAdmin: Boolean)(analyzer: Worker): Future[JsObject] = {
70-
workerSrv.getDefinition(analyzer.workerDefinitionId())
71-
.map(analyzerDefinition analyzerJson(analyzer, Some(analyzerDefinition)))
72-
.recover { case _ analyzerJson(analyzer, None) }
73-
.map {
74-
case a if isAdmin a + ("configuration" Json.parse(analyzer.configuration()))
75-
case a a
76-
}
43+
private def analyzerJson(isAdmin: Boolean)(analyzer: Worker): JsObject = {
44+
if (isAdmin)
45+
analyzer.toJson + ("configuration" Json.parse(analyzer.configuration())) + ("analyzerDefinitionId" JsString(analyzer.workerDefinitionId()))
46+
else
47+
analyzer.toJson + ("analyzerDefinitionId" JsString(analyzer.workerDefinitionId()))
7748
}
7849

7950
def listForType(dataType: String): Action[AnyContent] = authenticated(Roles.read).async { request
8051
import org.elastic4play.services.QueryDSL._
81-
workerSrv.findAnalyzersForUser(request.userId, "dataTypeList" ~= dataType, Some("all"), Nil)
82-
._1
83-
.mapAsyncUnordered(2) { analyzer
84-
workerSrv.getDefinition(analyzer.workerDefinitionId())
85-
.map(ad analyzerJson(analyzer, Some(ad)))
86-
}
87-
.runWith(Sink.seq)
88-
.map(analyzers renderer.toOutput(OK, analyzers))
52+
val (responderList, responderCount) = workerSrv.findAnalyzersForUser(request.userId, "dataTypeList" ~= dataType, Some("all"), Nil)
53+
renderer.toOutput(OK, responderList.map(analyzerJson(isAdmin = false)), responderCount)
8954
}
9055

9156
def create(analyzerDefinitionId: String): Action[Fields] = authenticated(Roles.orgAdmin).async(fieldsBodyParser) { implicit request
9257
for {
9358
organizationId userSrv.getOrganizationId(request.userId)
94-
workerDefinition workerSrv.getDefinition(analyzerDefinitionId)
59+
workerDefinition Future.fromTry(workerSrv.getDefinition(analyzerDefinitionId))
9560
analyzer workerSrv.create(organizationId, workerDefinition, request.body)
96-
} yield renderer.toOutput(CREATED, analyzerJson(analyzer, Some(workerDefinition)))
61+
} yield renderer.toOutput(CREATED, analyzerJson(isAdmin = false)(analyzer))
9762
}
9863

99-
def listDefinitions: Action[AnyContent] = authenticated(Roles.orgAdmin, Roles.superAdmin).async { implicit request
64+
def listDefinitions: Action[AnyContent] = authenticated(Roles.orgAdmin, Roles.superAdmin).async { _
10065
val (analyzers, analyzerTotal) = workerSrv.listAnalyzerDefinitions
10166
renderer.toOutput(OK, analyzers, analyzerTotal)
10267
}
10368

104-
def scan: Action[AnyContent] = authenticated(Roles.orgAdmin, Roles.superAdmin) { implicit request
69+
def scan: Action[AnyContent] = authenticated(Roles.orgAdmin, Roles.superAdmin) { _
10570
workerSrv.rescan()
10671
NoContent
10772
}
@@ -117,7 +82,6 @@ class AnalyzerCtrl @Inject() (
11782
for {
11883
analyzer workerSrv.getForUser(request.userId, analyzerId)
11984
updatedAnalyzer workerSrv.update(analyzer, request.body)
120-
updatedAnalyzerJson analyzerJson(isAdmin = true)(updatedAnalyzer)
121-
} yield renderer.toOutput(OK, updatedAnalyzerJson)
85+
} yield renderer.toOutput(OK, analyzerJson(isAdmin = true)(updatedAnalyzer))
12286
}
12387
}

app/org/thp/cortex/controllers/AuthenticationCtrl.scala

+24-3
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,18 @@
11
package org.thp.cortex.controllers
22

3-
import javax.inject.{ Inject, Singleton }
4-
53
import scala.concurrent.{ ExecutionContext, Future }
64

75
import play.api.mvc._
86

7+
import javax.inject.{ Inject, Singleton }
8+
import org.thp.cortex.models.UserStatus
99
import org.thp.cortex.services.UserSrv
1010

1111
import org.elastic4play.controllers.{ Authenticated, Fields, FieldsBodyParser, Renderer }
1212
import org.elastic4play.database.DBIndex
1313
import org.elastic4play.services.AuthSrv
14-
import org.elastic4play.{ MissingAttributeError, Timed }
1514
import org.elastic4play.services.JsonFormat.authContextWrites
15+
import org.elastic4play.{ AuthorizationError, MissingAttributeError, OAuth2Redirect, Timed }
1616

1717
@Singleton
1818
class AuthenticationCtrl @Inject() (
@@ -38,6 +38,27 @@ class AuthenticationCtrl @Inject() (
3838
}
3939
}
4040

41+
@Timed
42+
def ssoLogin: Action[AnyContent] = Action.async { implicit request
43+
dbIndex.getIndexStatus.flatMap {
44+
case false Future.successful(Results.Status(520))
45+
case _
46+
(for {
47+
authContext authSrv.authenticate()
48+
user userSrv.get(authContext.userId)
49+
} yield {
50+
if (user.status() == UserStatus.Ok)
51+
authenticated.setSessingUser(Ok, authContext)
52+
else
53+
throw AuthorizationError("Your account is locked")
54+
}) recover {
55+
// A bit of a hack with the status code, so that Angular doesn't reject the origin
56+
case OAuth2Redirect(redirectUrl, qp) Redirect(redirectUrl, qp, status = OK)
57+
case e throw e
58+
}
59+
}
60+
}
61+
4162
@Timed
4263
def logout = Action {
4364
Ok.withNewSession

app/org/thp/cortex/controllers/JobCtrl.scala

+11-11
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,31 @@
11
package org.thp.cortex.controllers
22

3-
import javax.inject.{ Inject, Named, Singleton }
3+
import scala.concurrent.duration.{ Duration, FiniteDuration }
4+
import scala.concurrent.{ ExecutionContext, Future }
5+
6+
import play.api.http.Status
7+
import play.api.libs.json.{ JsObject, JsString, JsValue, Json }
8+
import play.api.mvc.{ AbstractController, Action, AnyContent, ControllerComponents }
49

510
import akka.actor.{ ActorRef, ActorSystem }
611
import akka.pattern.ask
712
import akka.stream.Materializer
813
import akka.stream.scaladsl.Sink
914
import akka.util.Timeout
15+
import javax.inject.{ Inject, Named, Singleton }
16+
import org.thp.cortex.models.{ Job, JobStatus, Roles }
17+
import org.thp.cortex.services.AuditActor.{ JobEnded, Register }
18+
import org.thp.cortex.services.JobSrv
19+
1020
import org.elastic4play.controllers.{ Authenticated, Fields, FieldsBodyParser, Renderer }
1121
import org.elastic4play.models.JsonFormat.baseModelEntityWrites
1222
import org.elastic4play.services.JsonFormat.queryReads
1323
import org.elastic4play.services.{ QueryDSL, QueryDef }
1424
import org.elastic4play.utils.RichFuture
15-
import org.thp.cortex.models.{ Job, JobStatus, Roles }
16-
import org.thp.cortex.services.AuditActor.{ JobEnded, Register }
17-
import org.thp.cortex.services.{ JobSrv, UserSrv }
18-
import play.api.http.Status
19-
import play.api.libs.json.{ JsObject, JsString, JsValue, Json }
20-
import play.api.mvc.{ AbstractController, Action, AnyContent, ControllerComponents }
21-
22-
import scala.concurrent.duration.{ Duration, FiniteDuration }
23-
import scala.concurrent.{ ExecutionContext, Future }
2425

2526
@Singleton
2627
class JobCtrl @Inject() (
2728
jobSrv: JobSrv,
28-
userSrv: UserSrv,
2929
@Named("audit") auditActor: ActorRef,
3030
fieldsBodyParser: FieldsBodyParser,
3131
authenticated: Authenticated,

app/org/thp/cortex/controllers/ResponderCtrl.scala

+13-26
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import play.api.libs.json.{ JsNumber, JsObject, Json }
66
import play.api.mvc.{ AbstractController, Action, AnyContent, ControllerComponents }
77

88
import akka.stream.Materializer
9-
import akka.stream.scaladsl.Sink
109
import javax.inject.{ Inject, Singleton }
1110
import org.thp.cortex.models.{ Roles, Worker, WorkerDefinition }
1211
import org.thp.cortex.services.{ UserSrv, WorkerSrv }
@@ -33,15 +32,13 @@ class ResponderCtrl @Inject() (
3332
val sort = request.body.getStrings("sort").getOrElse(Nil)
3433
val isAdmin = request.roles.contains(Roles.orgAdmin)
3534
val (responders, responderTotal) = workerSrv.findRespondersForUser(request.userId, query, range, sort)
36-
val enrichedResponders = responders.mapAsync(2)(responderJson(isAdmin))
37-
renderer.toOutput(OK, enrichedResponders, responderTotal)
35+
renderer.toOutput(OK, responders.map(responderJson(isAdmin)), responderTotal)
3836
}
3937

4038
def get(responderId: String): Action[AnyContent] = authenticated(Roles.read).async { request
4139
val isAdmin = request.roles.contains(Roles.orgAdmin)
4240
workerSrv.getForUser(request.userId, responderId)
43-
.flatMap(responderJson(isAdmin))
44-
.map(renderer.toOutput(OK, _))
41+
.map(responder renderer.toOutput(OK, responderJson(isAdmin)(responder)))
4542
}
4643

4744
private val emptyResponderDefinitionJson = Json.obj(
@@ -66,42 +63,33 @@ class ResponderCtrl @Inject() (
6663
}
6764
}
6865

69-
private def responderJson(isAdmin: Boolean)(responder: Worker): Future[JsObject] = {
70-
workerSrv.getDefinition(responder.workerDefinitionId())
71-
.map(responderDefinition responderJson(responder, Some(responderDefinition)))
72-
.recover { case _ responderJson(responder, None) }
73-
.map {
74-
case a if isAdmin a + ("configuration" Json.parse(responder.configuration()))
75-
case a a
76-
}
66+
private def responderJson(isAdmin: Boolean)(responder: Worker): JsObject = {
67+
if (isAdmin)
68+
responder.toJson + ("configuration" Json.parse(responder.configuration()))
69+
else
70+
responder.toJson
7771
}
7872

7973
def listForType(dataType: String): Action[AnyContent] = authenticated(Roles.read).async { request
8074
import org.elastic4play.services.QueryDSL._
81-
workerSrv.findRespondersForUser(request.userId, "dataTypeList" ~= dataType, Some("all"), Nil)
82-
._1
83-
.mapAsyncUnordered(2) { responder
84-
workerSrv.getDefinition(responder.workerDefinitionId())
85-
.map(ad responderJson(responder, Some(ad)))
86-
}
87-
.runWith(Sink.seq)
88-
.map(responders renderer.toOutput(OK, responders))
75+
val (responderList, responderCount) = workerSrv.findRespondersForUser(request.userId, "dataTypeList" ~= dataType, Some("all"), Nil)
76+
renderer.toOutput(OK, responderList.map(responderJson(false)), responderCount)
8977
}
9078

9179
def create(responderDefinitionId: String): Action[Fields] = authenticated(Roles.orgAdmin).async(fieldsBodyParser) { implicit request
9280
for {
9381
organizationId userSrv.getOrganizationId(request.userId)
94-
workerDefinition workerSrv.getDefinition(responderDefinitionId)
82+
workerDefinition Future.fromTry(workerSrv.getDefinition(responderDefinitionId))
9583
responder workerSrv.create(organizationId, workerDefinition, request.body)
9684
} yield renderer.toOutput(CREATED, responderJson(responder, Some(workerDefinition)))
9785
}
9886

99-
def listDefinitions: Action[AnyContent] = authenticated(Roles.orgAdmin, Roles.superAdmin).async { implicit request
87+
def listDefinitions: Action[AnyContent] = authenticated(Roles.orgAdmin, Roles.superAdmin).async { _
10088
val (responders, responderTotal) = workerSrv.listResponderDefinitions
10189
renderer.toOutput(OK, responders, responderTotal)
10290
}
10391

104-
def scan: Action[AnyContent] = authenticated(Roles.orgAdmin, Roles.superAdmin) { implicit request
92+
def scan: Action[AnyContent] = authenticated(Roles.orgAdmin, Roles.superAdmin) { _
10593
workerSrv.rescan()
10694
NoContent
10795
}
@@ -117,7 +105,6 @@ class ResponderCtrl @Inject() (
117105
for {
118106
responder workerSrv.getForUser(request.userId, responderId)
119107
updatedResponder workerSrv.update(responder, request.body)
120-
updatedResponderJson responderJson(isAdmin = true)(updatedResponder)
121-
} yield renderer.toOutput(OK, updatedResponderJson)
108+
} yield renderer.toOutput(OK, responderJson(isAdmin = true)(updatedResponder))
122109
}
123110
}

0 commit comments

Comments
 (0)