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

Implementing case merging feature (Issue #1264) #1798

Merged
merged 20 commits into from
Mar 11, 2021
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 19 additions & 14 deletions frontend/app/scripts/controllers/case/CaseMainCtrl.js
Original file line number Diff line number Diff line change
Expand Up @@ -253,20 +253,25 @@
});

caseModal.result.then(function(selectedCase) {
CaseSrv.merge({}, {
caseId: $scope.caze.id,
mergedCaseId: selectedCase.id
}, function (merged) {

$state.go('app.case.details', {
caseId: merged.id
});

NotificationSrv.log('The cases have been successfully merged into a new case #' + merged.caseId, 'success');
}, function (response) {
//this.pendingAsync = false;
NotificationSrv.error('Case Merge', response.data, response.status);
});
CaseSrv.merge([$scope.caze._id, selectedCase._id])
.then(function (response) {
var merged = response.data;

$state.go('app.case.details', {
caseId: merged._id
});

NotificationSrv.log('The cases have been successfully merged into a new case #' + merged.number, 'success');
})
.catch(function (response) {
//this.pendingAsync = false;
NotificationSrv.error('Case Merge', response.data, response.status);
})

// CaseSrv.merge({}, {
// caseId: $scope.caze.id,
// mergedCaseId: selectedCase.id
// }, , );
}).catch(function(err) {
if(err && !_.isString(err)) {
NotificationSrv.error('Case Merge', err.data, err.status);
Expand Down
20 changes: 12 additions & 8 deletions frontend/app/scripts/services/api/CaseSrv.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,14 @@
url: './api/case/:caseId/links',
isArray: true
},
merge: {
method: 'POST',
url: './api/case/:caseId/_merge/:mergedCaseId',
params: {
caseId: '@caseId',
mergedCaseId: '@mergedCaseId',
}
},
// merge: {
// method: 'POST',
// url: './api/case/:caseId/_merge/:mergedCaseId',
// params: {
// caseId: '@caseId',
// mergedCaseId: '@mergedCaseId',
// }
// },
forceRemove: {
method: 'DELETE',
url: './api/case/:caseId/force',
Expand Down Expand Up @@ -94,6 +94,10 @@
return defer.promise;
};

this.merge = function(ids) {
return $http.post('./api/v1/case/_merge/' + ids.join(','));
};

this.bulkUpdate = function(ids, update) {
return $http.patch('./api/case/_bulk', _.extend({ids: ids}, update));
};
Expand Down
2 changes: 1 addition & 1 deletion frontend/app/views/partials/case/case.merge.html
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ <h4>{{c.title}}</h4>
<div>
<span>
<i class="glyphicon glyphicon-user"></i>
<user-info value="c.owner" field="name"></user-info>
<user-info value="c.assignee" field="name"></user-info>
</span>
<span class="ml-xxs">
<i class="glyphicon glyphicon-calendar"></i>
Expand Down
4 changes: 2 additions & 2 deletions frontend/app/views/partials/case/case.panelinfo.html
Original file line number Diff line number Diff line change
Expand Up @@ -109,12 +109,12 @@ <h3 class="box-title text-primary">
</a>
</span>

<!-- <span class="ml-xxs pull-right">
<span class="ml-xxs pull-right">
<a href ng-click="mergeCase()" class="text-primary noline" uib-tooltip="Merge case">
<i class="text-primary fa fa-compress"></i>
Merge
</a>
</span> -->
</span>

<span class="ml-xxs pull-right" ng-if="!caze.flag || caze.flag == undefined">
<a href ng-click="switchFlag()" class="text-muted noline" uib-tooltip="Flag case">
Expand Down
21 changes: 6 additions & 15 deletions thehive/app/org/thp/thehive/controllers/v0/CaseCtrl.scala
Original file line number Diff line number Diff line change
Expand Up @@ -144,22 +144,14 @@ class CaseCtrl @Inject() (
} yield Results.NoContent
}

def merge(caseIdsOrNumbers: String): Action[AnyContent] =
def merge(caseId: String, caseToMerge: String): Action[AnyContent] =
entrypoint("merge cases")
.authTransaction(db) { implicit request => implicit graph =>
caseIdsOrNumbers
.split(',')
.toSeq
.toTry(c =>
caseSrv
.get(EntityIdOrName(c))
.visible(organisationSrv)
.getOrFail("Case")
)
.map { cases =>
val mergedCase = caseSrv.merge(cases)
Results.Ok(mergedCase.toJson)
}
for {
caze <- caseSrv.get(EntityIdOrName(caseId)).visible(organisationSrv).getOrFail("Case")
toMerge <- caseSrv.get(EntityIdOrName(caseToMerge)).visible(organisationSrv).getOrFail("Case")
merged <- caseSrv.merge(Seq(caze, toMerge))
} yield Results.Created(merged.toJson)
}

def linkedCases(caseIdOrNumber: String): Action[AnyContent] =
Expand All @@ -185,7 +177,6 @@ class PublicCase @Inject() (
caseSrv: CaseSrv,
organisationSrv: OrganisationSrv,
observableSrv: ObservableSrv,
taskSrv: TaskSrv,
userSrv: UserSrv,
customFieldSrv: CustomFieldSrv,
implicit val db: Database
Expand Down
22 changes: 11 additions & 11 deletions thehive/app/org/thp/thehive/controllers/v0/Router.scala
Original file line number Diff line number Diff line change
Expand Up @@ -119,17 +119,17 @@ class Router @Inject() (
case GET(p"/case/artifact/$observableId/similar") => observableCtrl.findSimilar(observableId)
// case POST(p"/case/:caseId/artifact/_search") => observableCtrl.findInCase(caseId)

case GET(p"/case") => caseCtrl.search
case POST(p"/case/_search") => caseCtrl.search
case POST(p"/case/_stats") => caseCtrl.stats
case POST(p"/case") => caseCtrl.create
case GET(p"/case/$caseId") => caseCtrl.get(caseId)
case PATCH(p"/case/_bulk") => caseCtrl.bulkUpdate // Not used by the frontend
case PATCH(p"/case/$caseId") => caseCtrl.update(caseId)
case DELETE(p"/case/$caseId") => caseCtrl.delete(caseId) // Not used by the frontend
case DELETE(p"/case/$caseId/force") => caseCtrl.delete(caseId)
case POST(p"/case/_merge/$caseIds") => caseCtrl.merge(caseIds) // Not implemented in backend and not used by frontend
case GET(p"/case/$caseId/links") => caseCtrl.linkedCases(caseId)
case GET(p"/case") => caseCtrl.search
case POST(p"/case") => caseCtrl.create // Audit ok
case GET(p"/case/$caseId") => caseCtrl.get(caseId)
case PATCH(p"/case/_bulk") => caseCtrl.bulkUpdate // Not used by the frontend
case PATCH(p"/case/$caseId") => caseCtrl.update(caseId) // Audit ok
case POST(p"/case/$caseId/_merge/$toMerge") => caseCtrl.merge(caseId, toMerge)
case POST(p"/case/_search") => caseCtrl.search
case POST(p"/case/_stats") => caseCtrl.stats
case DELETE(p"/case/$caseId") => caseCtrl.delete(caseId) // Not used by the frontend
case DELETE(p"/case/$caseId/force") => caseCtrl.delete(caseId) // Audit ok
case GET(p"/case/$caseId/links") => caseCtrl.linkedCases(caseId)

case GET(p"/config/user") => configCtrl.userList
case GET(p"/config/user/$path") => configCtrl.userGet(path)
Expand Down
40 changes: 27 additions & 13 deletions thehive/app/org/thp/thehive/controllers/v1/CaseCtrl.scala
Original file line number Diff line number Diff line change
Expand Up @@ -133,21 +133,35 @@ class CaseCtrl @Inject() (
} yield Results.NoContent
}

def deleteCustomField(cfId: String): Action[AnyContent] =
entrypoint("delete a custom field")
.authPermittedTransaction(db, Permissions.manageCase) { implicit request => implicit graph =>
for {
_ <-
caseSrv
.caseCustomFieldSrv
.get(EntityIdOrName(cfId))
.filter(_.outV.v[Case].can(Permissions.manageCase))
.existsOrFail
_ <- caseSrv.deleteCustomField(EntityIdOrName(cfId))
} yield Results.NoContent
}

def merge(caseIdsOrNumbers: String): Action[AnyContent] =
entrypoint("merge cases")
.authTransaction(db) { implicit request => implicit graph =>
caseIdsOrNumbers
.split(',')
.toSeq
.toTry(c =>
caseSrv
.get(EntityIdOrName(c))
.visible(organisationSrv)
.getOrFail("Case")
)
.map { cases =>
val mergedCase = caseSrv.merge(cases)
Results.Ok(mergedCase.toJson)
}
for {
cases <-
caseIdsOrNumbers
.split(',')
.toSeq
.toTry(c =>
caseSrv
.get(EntityIdOrName(c))
.visible(organisationSrv)
.getOrFail("Case")
)
mergedCase <- caseSrv.merge(cases)
} yield Results.Created(mergedCase.toJson)
}
}
Original file line number Diff line number Diff line change
@@ -1,20 +1,21 @@
package org.thp.thehive.controllers.v1

import org.thp.scalligraph.traversal.TraversalOps.TraversalOpsDefs
import org.thp.scalligraph.traversal.{Converter, Traversal}
import org.thp.thehive.controllers.v1.Conversion._
import org.thp.thehive.models.Procedure
import org.thp.thehive.services.PatternOps._
import org.thp.thehive.services.ProcedureOps._
import play.api.libs.json.JsValue
import play.api.libs.json.{JsNull, JsValue}

import java.util.{Map => JMap}
import java.util.{List => JList, Map => JMap}

trait ProcedureRenderer extends BaseRenderer[Procedure] {
def patternStats: Traversal.V[Procedure] => Traversal[JsValue, JMap[String, Any], Converter[JsValue, JMap[String, Any]]] =
_.pattern.richPattern.domainMap(_.toJson)

def patternParentStats: Traversal.V[Procedure] => Traversal[JsValue, JMap[String, Any], Converter[JsValue, JMap[String, Any]]] =
_.pattern.parent.richPattern.domainMap(_.toJson)
def patternParentStats: Traversal.V[Procedure] => Traversal[JsValue, JList[JMap[String, Any]], Converter[JsValue, JList[JMap[String, Any]]]] =
_.pattern.parent.richPattern.fold.domainMap(_.headOption.fold[JsValue](JsNull)(_.toJson))

def procedureStatsRenderer(extraData: Set[String]): Traversal.V[Procedure] => JsTraversal = { implicit traversal =>
baseRenderer(
Expand Down
13 changes: 7 additions & 6 deletions thehive/app/org/thp/thehive/controllers/v1/Router.scala
Original file line number Diff line number Diff line change
Expand Up @@ -55,12 +55,13 @@ class Router @Inject() (
case POST(p"/auth/totp/unset") => authenticationCtrl.totpUnsetSecret(None)
case POST(p"/auth/totp/unset/$user") => authenticationCtrl.totpUnsetSecret(Some(user))

case POST(p"/case") => caseCtrl.create
case GET(p"/case/$caseId") => caseCtrl.get(caseId)
case PATCH(p"/case/$caseId") => caseCtrl.update(caseId)
case POST(p"/case/_merge/$caseIds") => caseCtrl.merge(caseIds)
case DELETE(p"/case/$caseId") => caseCtrl.delete(caseId)
// case PATCH(p"api/case/_bulk") => caseCtrl.bulkUpdate()
case POST(p"/case") => caseCtrl.create
case GET(p"/case/$caseId") => caseCtrl.get(caseId)
case PATCH(p"/case/$caseId") => caseCtrl.update(caseId)
case POST(p"/case/_merge/$caseIds") => caseCtrl.merge(caseIds)
case DELETE(p"/case/$caseId") => caseCtrl.delete(caseId)
case DELETE(p"/case/customField/$cfId") => caseCtrl.deleteCustomField(cfId)
// case PATCH(p"api/case/_bulk") => caseCtrl.bulkUpdate()
// case POST(p"/case/_stats") => caseCtrl.stats()
// case GET(p"/case/$caseId/links") => caseCtrl.linkedCases(caseId)

Expand Down
Loading