From 839a7e137909aca3d1d69a2612b3738fb041f84b Mon Sep 17 00:00:00 2001 From: Nabil Adouani Date: Fri, 29 Jan 2021 17:03:54 +0100 Subject: [PATCH] #1766 WIP: Add a section to manage attack pattern catalogs --- frontend/app/index.html | 2 + frontend/app/scripts/app.js | 15 ++ .../admin/attack/AttackPatternListCtrl.js | 159 ++++++++++++++++++ .../scripts/services/api/AttackPatternSrv.js | 52 ++++++ .../views/components/header.component.html | 19 ++- .../views/partials/admin/attack/import.html | 22 +++ .../app/views/partials/admin/attack/list.html | 96 +++++++++++ .../partials/admin/attack/list/filters.html | 38 +++++ .../partials/admin/attack/list/toolbar.html | 21 +++ .../app/views/partials/admin/attack/view.html | 107 ++++++++++++ 10 files changed, 525 insertions(+), 6 deletions(-) create mode 100644 frontend/app/scripts/controllers/admin/attack/AttackPatternListCtrl.js create mode 100644 frontend/app/scripts/services/api/AttackPatternSrv.js create mode 100644 frontend/app/views/partials/admin/attack/import.html create mode 100644 frontend/app/views/partials/admin/attack/list.html create mode 100644 frontend/app/views/partials/admin/attack/list/filters.html create mode 100644 frontend/app/views/partials/admin/attack/list/toolbar.html create mode 100644 frontend/app/views/partials/admin/attack/view.html diff --git a/frontend/app/index.html b/frontend/app/index.html index 8e4642c44c..dfdb2865af 100644 --- a/frontend/app/index.html +++ b/frontend/app/index.html @@ -159,6 +159,7 @@ + @@ -284,6 +285,7 @@ + diff --git a/frontend/app/scripts/app.js b/frontend/app/scripts/app.js index 80d8e764a0..9676fea0b4 100644 --- a/frontend/app/scripts/app.js +++ b/frontend/app/scripts/app.js @@ -232,6 +232,21 @@ angular.module('thehive', [ permissions: ['manageTaxonomy'] } }) + .state('app.administration.attackPatterns', { + url: '/attack-patterns', + templateUrl: 'views/partials/admin/attack/list.html', + controller: 'AttackPatternListCtrl', + controllerAs: '$vm', + title: 'ATT&CK patterns administration', + resolve: { + appConfig: function(VersionSrv) { + return VersionSrv.get(); + } + } + // guard: { + // permissions: ['manageTaxonomy'] + // } + }) .state('app.administration.organisations', { url: '/organisations', templateUrl: 'views/partials/admin/organisation/list.html', diff --git a/frontend/app/scripts/controllers/admin/attack/AttackPatternListCtrl.js b/frontend/app/scripts/controllers/admin/attack/AttackPatternListCtrl.js new file mode 100644 index 0000000000..2c91a1bdd3 --- /dev/null +++ b/frontend/app/scripts/controllers/admin/attack/AttackPatternListCtrl.js @@ -0,0 +1,159 @@ +(function() { + 'use strict'; + + angular.module('theHiveControllers') + .controller('AttackPatternListCtrl', AttackPatternListCtrl) + .controller('AttackPatternDialogCtrl', AttackPatternDialogCtrl) + .controller('AttackPatternImportCtrl', AttackPatternImportCtrl); + + function AttackPatternListCtrl($scope, $uibModal, PaginatedQuerySrv, FilteringSrv, AttackPatternSrv, NotificationSrv, ModalSrv, appConfig) { + var self = this; + + this.appConfig = appConfig; + + self.load = function() { + this.loading = true; + + this.list = new PaginatedQuerySrv({ + name: 'attack-patterns', + root: undefined, + objectType: 'pattern', + version: 'v1', + scope: $scope, + sort: self.filtering.context.sort, + loadAll: false, + pageSize: self.filtering.context.pageSize, + filter: this.filtering.buildQuery(), + baseFilter: { + _field: 'patternType', + _value:'attack-pattern' + }, + operations: [ + {'_name': 'listPattern'} + ], + extraData: ['enabled'], + onUpdate: function() { + self.loading = false; + } + }); + }; + + self.show = function(patternId) { + $uibModal.open({ + animation: true, + templateUrl: 'views/partials/admin/attack/view.html', + controller: 'AttackPatternDialogCtrl', + controllerAs: '$modal', + size: 'max', + resolve: { + pattern: function() { + return AttackPatternSrv.get(patternId); + } + } + }); + }; + + + self.import = function () { + var modalInstance = $uibModal.open({ + animation: true, + templateUrl: 'views/partials/admin/attack/import.html', + controller: 'AttackPatternImportCtrl', + controllerAs: '$vm', + size: 'lg', + resolve: { + appConfig: self.appConfig + } + }); + + modalInstance.result + .then(function() { + self.load(); + }) + .catch(function(err){ + if(err && !_.isString(err)) { + NotificationSrv.error('Pattern import', err.data, err.status); + } + }); + }; + + this.toggleFilters = function () { + this.filtering.toggleFilters(); + }; + + this.filter = function () { + self.filtering.filter().then(this.applyFilters); + }; + + this.clearFilters = function () { + this.filtering.clearFilters() + .then(self.search); + }; + + this.removeFilter = function (index) { + self.filtering.removeFilter(index) + .then(self.search); + }; + + this.search = function () { + self.load(); + self.filtering.storeContext(); + }; + this.addFilterValue = function (field, value) { + this.filtering.addFilterValue(field, value); + this.search(); + }; + + self.$onInit = function() { + self.filtering = new FilteringSrv('pattern', 'attack-pattern.list', { + version: 'v1', + defaults: { + showFilters: true, + showStats: false, + pageSize: 15, + sort: ['+name'] + }, + defaultFilter: [] + }); + + self.filtering.initContext('list') + .then(function() { + self.load(); + + $scope.$watch('$vm.list.pageSize', function (newValue) { + self.filtering.setPageSize(newValue); + }); + }); + }; + } + + function AttackPatternDialogCtrl($uibModalInstance, AttackPatternSrv, NotificationSrv, pattern) { + this.pattern = pattern; + + this.ok = function () { + $uibModalInstance.close(); + }; + + this.cancel = function () { + $uibModalInstance.dismiss('cancel'); + }; + } + + function AttackPatternImportCtrl($uibModalInstance, AttackPatternSrv, NotificationSrv, appConfig) { + this.appConfig = appConfig; + this.formData = {}; + + this.ok = function () { + AttackPatternSrv.import(this.formData) + .then(function() { + $uibModalInstance.close(); + }, function(response) { + NotificationSrv.error('AttackPatternImportCtrl', response.data, response.status); + }); + }; + + this.cancel = function () { + $uibModalInstance.dismiss('cancel'); + }; + } +})(); diff --git a/frontend/app/scripts/services/api/AttackPatternSrv.js b/frontend/app/scripts/services/api/AttackPatternSrv.js new file mode 100644 index 0000000000..1979cfde9c --- /dev/null +++ b/frontend/app/scripts/services/api/AttackPatternSrv.js @@ -0,0 +1,52 @@ + +(function() { + 'use strict'; + angular.module('theHiveServices') + .service('AttackPatternSrv', function($http, QuerySrv) { + var baseUrl = './api/v1/pattern'; + + this.list = function() { + return QuerySrv.call('v1', [ + { _name: 'listPattern' } + ], { + name:'list-attack-patterns' + }); + }; + + this.get = function(id) { + return $http.get(baseUrl + '/' + id) + .then(function(response){ + return response.data; + }); + }; + + this.import = function(post) { + var postData = { + file: post.attachment + }; + + return $http({ + method: 'POST', + url: baseUrl + '/import/attack', + headers: { + 'Content-Type': undefined + }, + transformRequest: function (data) { + var formData = new FormData(), + copy = angular.copy(data, {}); + + angular.forEach(data, function (value, key) { + if (Object.getPrototypeOf(value) instanceof Blob || Object.getPrototypeOf(value) instanceof File) { + formData.append(key, value); + delete copy[key]; + } + }); + + return formData; + }, + data: postData + }); + }; + }); + +})(); diff --git a/frontend/app/views/components/header.component.html b/frontend/app/views/components/header.component.html index ca85b74689..fed26cd57f 100644 --- a/frontend/app/views/components/header.component.html +++ b/frontend/app/views/components/header.component.html @@ -98,12 +98,6 @@ Case custom fields -
  • - - - Taxonomies - -
  • @@ -116,6 +110,19 @@ Analyzer templates
  • +
  • +
  • + + + Taxonomies + +
  • +
  • + + + ATT&CK Patterns + +
  • +
    + + +
    + + + + + + + + + + + + + + + + + + + + + +
    IDNameSub-Technique ofTacticsDescription
    + {{::pattern.patternId}} + {{::pattern.name}} + - + {{::pattern.parent}} + +
    + {{tactic}} +
    +
    + {{::pattern.description | limitTo:250}}... + +
    + + + + + + + + + + + +
    + +
    +
    + + +
    + + + + diff --git a/frontend/app/views/partials/admin/attack/list/filters.html b/frontend/app/views/partials/admin/attack/list/filters.html new file mode 100644 index 0000000000..431c826ca5 --- /dev/null +++ b/frontend/app/views/partials/admin/attack/list/filters.html @@ -0,0 +1,38 @@ +
    +
    +

    Filters

    +
    +
    +
    +
    + + + + +
    +
    +
    + +
    +
    +
    + +
    +
    + +
    +
    diff --git a/frontend/app/views/partials/admin/attack/list/toolbar.html b/frontend/app/views/partials/admin/attack/list/toolbar.html new file mode 100644 index 0000000000..96afe4ba9e --- /dev/null +++ b/frontend/app/views/partials/admin/attack/list/toolbar.html @@ -0,0 +1,21 @@ +
    +
    + +
    +
    diff --git a/frontend/app/views/partials/admin/attack/view.html b/frontend/app/views/partials/admin/attack/view.html new file mode 100644 index 0000000000..7a8250dd18 --- /dev/null +++ b/frontend/app/views/partials/admin/attack/view.html @@ -0,0 +1,107 @@ +
    + + + + + +