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

Add the ability to create and export reports as PDF #834

Closed
wants to merge 18 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
913d9a4
Merge branch 'master' of https://github.com/TheHive-Project/TheHive
axpatito May 29, 2018
9487e5e
Merge branch 'master' of https://github.com/TheHive-Project/TheHive
axpatito Jun 27, 2018
bc13906
[WIP] Added case report
axpatito Jun 27, 2018
ec24818
Added caseReport model
axpatito Jul 11, 2018
66d49b3
Added play2pdf dependency
axpatito Jul 11, 2018
0b714b6
Added case_report model and API call
axpatito Jul 12, 2018
b276ea3
[WIP] Added basic form to update case reports
axpatito Jul 12, 2018
7a1b2a6
Added pdf export capabilities from angular instead of Play
axpatito Jul 13, 2018
eee2eaa
Added patch endpoint to update template + other ui fixes
axpatito Jul 13, 2018
8148117
Modified javascript and template to create a semi-nice PDF
axpatito Jul 17, 2018
00293be
Small fixes to prevent warnings and 404's
axpatito Jul 18, 2018
8ccf0a2
Merge branch 'master' of https://github.com/TheHive-Project/TheHive
axpatito Aug 14, 2018
91244d6
Merge branch 'master' of https://github.com/TheHive-Project/TheHive
axpatito Oct 9, 2018
fad3a94
Merge branch 'master' of https://github.com/TheHive-Project/TheHive
axpatito Nov 28, 2018
5a9215f
Merge branch 'master' of https://github.com/TheHive-Project/TheHive
axpatito Dec 13, 2018
b832187
Merge of master
axpatito Dec 17, 2018
4594fc3
Added migration to create default template
axpatito Dec 17, 2018
d9e158d
Merge branch 'develop' into reports
axpatito Dec 20, 2018
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
42 changes: 42 additions & 0 deletions thehive-backend/app/controllers/CaseReportCtrl.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package controllers

import javax.inject.{ Inject, Singleton }
import org.elastic4play.Timed
import org.elastic4play.controllers.{ Authenticated, Fields, FieldsBodyParser, Renderer }
import org.elastic4play.services.{ AuxSrv, QueryDSL }
import play.api.http.Status
import play.api.mvc._
import models.Roles
import services.CaseReportSrv

import scala.concurrent.ExecutionContext

@Singleton
class CaseReportCtrl @Inject() (
auxSrv: AuxSrv,
authenticated: Authenticated,
renderer: Renderer,
components: ControllerComponents,
fieldsBodyParser: FieldsBodyParser,
caseReportSrv: CaseReportSrv,
implicit val ec: ExecutionContext) extends AbstractController(components) with Status {

def create(): Action[Fields] = authenticated(Roles.admin).async(fieldsBodyParser) { implicit request ⇒
caseReportSrv.create(request.body).map(caseReport ⇒ renderer.toOutput(OK, caseReport.toJson))
}

@Timed
def update(caseId: String): Action[Fields] = authenticated(Roles.admin).async(fieldsBodyParser) { implicit request ⇒
caseReportSrv.update(caseId, request.body).map(template ⇒ renderer.toOutput(OK, template.toJson))
}

@Timed
def find(): Action[Fields] = authenticated(Roles.read).async(fieldsBodyParser) { implicit request ⇒
val query = QueryDSL.any
val range = request.body.getString("range")
val sort = request.body.getStrings("sort").getOrElse(Nil)

val (reports, total) = caseReportSrv.find(query, range, sort)
renderer.toOutput(OK, reports.map(_.toJson), total)
}
}
16 changes: 16 additions & 0 deletions thehive-backend/app/models/CaseReport.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package models

import play.api.libs.json.JsObject

import org.elastic4play.models.{ AttributeDef, EntityDef, ModelDef, AttributeFormat ⇒ F }

trait CaseReportAttributes { _: AttributeDef ⇒
val templateName: A[String] = attribute("name", F.stringFmt, "Name of the template")
val content: A[String] = attribute("content", F.textFmt, "Content of the template")
}

class CaseReportModel extends ModelDef[CaseReportModel, CaseReport]("case_report", "case_report", "/case_report") with CaseReportAttributes

class CaseReport(model: CaseReportModel, attributes: JsObject)
extends EntityDef[CaseReportModel, CaseReport](model, attributes)
with CaseReportAttributes
16 changes: 15 additions & 1 deletion thehive-backend/app/models/Migration.scala
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import play.api.{ Configuration, Environment, Logger }
import akka.NotUsed
import akka.stream.Materializer
import akka.stream.scaladsl.Source
import services.{ AlertSrv, DashboardSrv }
import services.{ AlertSrv, DashboardSrv, CaseReportSrv }

import org.elastic4play.ConflictError
import org.elastic4play.controllers.Fields
Expand All @@ -35,6 +35,7 @@ class Migration(
dblists: DBLists,
eventSrv: EventSrv,
dashboardSrv: DashboardSrv,
caseReportSrv: CaseReportSrv,
userSrv: UserSrv,
environment: Environment,
implicit val ec: ExecutionContext,
Expand All @@ -44,6 +45,7 @@ class Migration(
dblists: DBLists,
eventSrv: EventSrv,
dashboardSrv: DashboardSrv,
caseReportSrv: CaseReportSrv,
userSrv: UserSrv,
environment: Environment,
ec: ExecutionContext,
Expand All @@ -56,6 +58,7 @@ class Migration(
dblists,
eventSrv,
dashboardSrv,
caseReportSrv,
userSrv,
environment,
ec, materializer)
Expand Down Expand Up @@ -110,11 +113,19 @@ class Migration(
}
}

private def createDefaultReport(): Future[CaseReport] = {
userSrv.inInitAuthContext { implicit authContext ⇒
caseReportSrv.create(Fields(Json.obj("name" -> "Default", "content" -> "")))
}
}

override def endMigration(version: Int): Future[Unit] = {
if (requireUpdateMispAlertArtifact) {
logger.info("Retrieve MISP attribute to update alerts")
eventSrv.publish(UpdateMispAlertArtifact())
}
logger.info("Creating the base case report")
createDefaultReport()
logger.info("Updating observable data type list")
addDataTypes(Seq("filename", "fqdn", "url", "user-agent", "domain", "ip", "mail_subject", "hash", "mail",
"registry", "uri_path", "regexp", "other", "file", "autonomous-system"))
Expand Down Expand Up @@ -341,6 +352,9 @@ class Migration(
addAttribute("alert", "customFields" → JsObject.empty),
addAttribute("case_task", "group" → JsString("default")),
addAttribute("case", "pap" → JsNumber(2)))

case DatabaseState(14) ⇒
Seq()
}

private def generateAlertId(alert: JsObject): String = {
Expand Down
2 changes: 1 addition & 1 deletion thehive-backend/app/models/package.scala
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@


package object models {
val modelVersion = 14
val modelVersion = 15
}
43 changes: 43 additions & 0 deletions thehive-backend/app/services/CaseReportSrv.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package services

import akka.NotUsed
import akka.stream.Materializer
import akka.stream.scaladsl.Source
import javax.inject.{ Inject, Singleton }
import models.{ CaseReportModel, CaseReport }
import org.elastic4play.controllers.Fields
import org.elastic4play.database.ModifyConfig
import org.elastic4play.services._

import scala.concurrent.{ ExecutionContext, Future }

@Singleton
class CaseReportSrv @Inject() (
caseReportModel: CaseReportModel,
createSrv: CreateSrv,
getSrv: GetSrv,
updateSrv: UpdateSrv,
deleteSrv: DeleteSrv,
findSrv: FindSrv,
implicit val ec: ExecutionContext,
implicit val mat: Materializer) {

def create(fields: Fields)(implicit authContext: AuthContext): Future[CaseReport] =
createSrv[CaseReportModel, CaseReport](caseReportModel, fields)

def get(id: String): Future[CaseReport] =
getSrv[CaseReportModel, CaseReport](caseReportModel, id)

def update(id: String, fields: Fields)(implicit authContext: AuthContext): Future[CaseReport] =
update(id, fields, ModifyConfig.default)

def update(id: String, fields: Fields, modifyConfig: ModifyConfig)(implicit authContext: AuthContext): Future[CaseReport] =
updateSrv[CaseReportModel, CaseReport](caseReportModel, id, fields, modifyConfig)

def delete(id: String)(implicit authContext: AuthContext): Future[Unit] =
deleteSrv.realDelete[CaseReportModel, CaseReport](caseReportModel, id)

def find(queryDef: QueryDef, range: Option[String], sortBy: Seq[String]): (Source[CaseReport, NotUsed], Future[Long]) = {
findSrv[CaseReportModel, CaseReport](caseReportModel, queryDef, range, sortBy)
}
}
3 changes: 3 additions & 0 deletions thehive-backend/conf/routes
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ DELETE /api/case/:caseId/force controllers.CaseCtrl.realDelet
GET /api/case/:caseId/links controllers.CaseCtrl.linkedCases(caseId)
POST /api/case/:caseId1/_merge/:caseId2 controllers.CaseCtrl.merge(caseId1, caseId2)

GET /api/case_report controllers.CaseReportCtrl.find()
PATCH /api/case_report/:templateId controllers.CaseReportCtrl.update(templateId)

POST /api/case/template/_search controllers.CaseTemplateCtrl.find()
POST /api/case/template controllers.CaseTemplateCtrl.create()
GET /api/case/template/:caseTemplateId controllers.CaseTemplateCtrl.get(caseTemplateId)
Expand Down
4 changes: 4 additions & 0 deletions ui/app/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@
<script src="bower_components/angular-drag-and-drop-lists/angular-drag-and-drop-lists.js"></script>
<script src="bower_components/angular-bootstrap-colorpicker/js/bootstrap-colorpicker-module.js"></script>
<script src="bower_components/file-saver/FileSaver.js"></script>
<script src="bower_components/jspdf/dist/jspdf.debug.js"></script>
<script src="bower_components/js-url/url.js"></script>
<!-- endbower -->

Expand Down Expand Up @@ -139,6 +140,7 @@
<script src="scripts/controllers/SearchCtrl.js"></script>
<script src="scripts/controllers/SettingsCtrl.js"></script>
<script src="scripts/controllers/admin/AdminCaseTemplatesCtrl.js"></script>
<script src="scripts/controllers/admin/AdminCaseReportCtrl.js"></script>
<script src="scripts/controllers/admin/AdminCustomFieldDialogCtrl.js"></script>
<script src="scripts/controllers/admin/AdminCustomFieldsCtrl.js"></script>
<script src="scripts/controllers/admin/AdminMetricsCtrl.js"></script>
Expand All @@ -161,6 +163,7 @@
<script src="scripts/controllers/case/CaseObservablesCtrl.js"></script>
<script src="scripts/controllers/case/CaseObservablesExportCtrl.js"></script>
<script src="scripts/controllers/case/CaseObservablesItemCtrl.js"></script>
<script src="scripts/controllers/case/CaseReportModalCtrl.js"></script>
<script src="scripts/controllers/case/CaseReopenModalCtrl.js"></script>
<script src="scripts/controllers/case/CaseStatsCtrl.js"></script>
<script src="scripts/controllers/case/CaseTasksCtrl.js"></script>
Expand Down Expand Up @@ -235,6 +238,7 @@
<script src="scripts/services/AuditSrv.js"></script>
<script src="scripts/services/AuthenticationSrv.js"></script>
<script src="scripts/services/CaseArtifactSrv.js"></script>
<script src="scripts/services/CaseReportSrv.js"></script>
<script src="scripts/services/CaseSrv.js"></script>
<script src="scripts/services/CaseTabsSrv.js"></script>
<script src="scripts/services/CaseTaskSrv.js"></script>
Expand Down
7 changes: 7 additions & 0 deletions ui/app/scripts/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,13 @@ angular.module('thehive', ['ngAnimate', 'ngMessages', 'ngSanitize', 'ui.bootstra
controller: 'AdminObservablesCtrl',
title: 'Observable administration'
})
.state('app.administration.case-report-templates',{
url: '/case-report-templates',
templateUrl: 'views/partials/admin/case-report-templates.html',
controller: 'AdminCaseReportCtrl',
controllerAs: 'cr',
title: 'Case report template administration'
})
.state('app.case', {
abstract: true,
url: 'case/{caseId}',
Expand Down
48 changes: 48 additions & 0 deletions ui/app/scripts/controllers/admin/AdminCaseReportCtrl.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
(function () {
'use strict';

angular.module('theHiveControllers').controller('AdminCaseReportCtrl',
function AdminCaseReportCtrl($scope, $q, CaseReportSrv, NotificationSrv){
var self = this;
self.templates = []
self.formData = {}
self.editorOptions = {
useWrapMode: true,
showGutter: true
};

this.load = function() {
$q.all([
CaseReportSrv.list(),
]).then(function (response) {
self.templates = response[0];
self.formData.content = self.templates[0].content
self.formData.name = self.templates[0].name
self.formData.template_id = self.templates[0].id
return $q.resolve(self.templates);
}, function(rejection) {
NotificationSrv.error('CaseReport', rejection.data, rejection.status);
})
};
this.load();

$scope.updateReport = function(){
$q.all([
CaseReportSrv.update(self.formData.template_id, {
'name': self.formData.name,
'content': self.formData.content
})
]).then(function (response){
var res = response[0]
if (res.status === 200){
NotificationSrv.log('Template updated successfully', 'success');
}
else{
NotificationSrv.error('CaseReport', res.data, res.status)
}
}, function(rejection){
NotificationSrv.error('CaseReport', rejection.data, rejection.status)
})
};
});
})();
16 changes: 15 additions & 1 deletion ui/app/scripts/controllers/case/CaseMainCtrl.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
(function() {
'use strict';
angular.module('theHiveControllers').controller('CaseMainCtrl',
function($scope, $rootScope, $state, $stateParams, $q, $uibModal, CaseTabsSrv, CaseSrv, MetricsCacheSrv, UserInfoSrv, MispSrv, StreamSrv, StreamStatSrv, NotificationSrv, UtilsSrv, CaseResolutionStatus, CaseImpactStatus, CortexSrv, caze) {
function($scope, $rootScope, $state, $stateParams, $q, $uibModal, CaseTabsSrv, CaseSrv, MetricsCacheSrv, UserInfoSrv, MispSrv, StreamSrv, StreamStatSrv, NotificationSrv, UtilsSrv, CaseResolutionStatus, CaseImpactStatus, CortexSrv, CaseReportSrv, caze) {
$scope.CaseResolutionStatus = CaseResolutionStatus;
$scope.CaseImpactStatus = CaseImpactStatus;
$scope.caseResponders = null;
Expand Down Expand Up @@ -343,6 +343,20 @@
});
};

$scope.caseReport = function() {
$uibModal.open({
scope: $scope,
controller: 'CaseReportModalCtrl',
templateUrl: 'views/partials/case/case.report.html',
size: 'lg',
resolve: {
caze: function() {
return $scope.caze;
},
}
});
};

/**
* A workaround filter to make sure the ngRepeat doesn't order the
* object keys
Expand Down
53 changes: 53 additions & 0 deletions ui/app/scripts/controllers/case/CaseReportModalCtrl.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
(function () {
'use strict';

angular.module('theHiveControllers')
.controller('CaseReportModalCtrl', CaseReportModalCtrl);

function CaseReportModalCtrl($scope, $state, $uibModalInstance, $q, $compile, PSearchSrv,SearchSrv, CaseSrv, UserInfoSrv, NotificationSrv, caze, $http, CaseReportSrv) {
var self = this;
self.templates = [];
self.artifacts = [];

$q.all([
CaseReportSrv.list(),
PSearchSrv($scope.caseId, 'case_artifact', {
scope: $scope,
baseFilter: {
'_and': [{
'_parent': {
"_type": "case",
"_query": {
"_id": $scope.caseId
}
}
},   {
'status': 'Ok'
}]
},
loadAll: true,
sort: '-startDate',
nstats: true
}),
]).then(function (response) {
self.templates = response[0];
caze.artifacts = response[1];
console.log(caze.artifacts);
var template = self.templates[0].content;
$('#case-report-content').html($compile(template)($scope));
return $q.resolve(self.template);
}, function(rejection) {
NotificationSrv.error('CaseReport', rejection.data, rejection.status);
});

$scope.createPDF = function(){
var pdf = new jsPDF('p', 'pt', 'letter');
pdf.fromHTML($('#case-report-content').get(0), 5, 5);
pdf.save(caze.title + '.pdf');
}

$scope.cancel = function () {
$uibModalInstance.dismiss();
};
}
})();
23 changes: 23 additions & 0 deletions ui/app/scripts/services/CaseReportSrv.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
(function() {
'use strict';
angular.module('theHiveServices').service('CaseReportSrv', function($q, $http) {
this.list = function() {
var defer = $q.defer();
$http.get('./api/case_report')
.then(function(response) {
defer.resolve(response.data);
}, function(err) {
defer.reject(err);
});
return defer.promise;
};

this.create = function(template) {
return $http.post('./api/case_report/create', template);
}

this.update = function(id, template) {
return $http.patch('./api/case_report/' + id, template);
}
});
})();
Loading