From bc6fa2e5772c7686219363ca5243d2671a9b0b8f Mon Sep 17 00:00:00 2001 From: Nabil Adouani Date: Mon, 9 Jul 2018 17:45:41 +0200 Subject: [PATCH] #110 Add responders admin page --- .../responder-config-form.controller.js | 28 +++ .../responders/responder-config-form.html | 89 +++++++++ .../responders/responder.edit.controller.js | 84 ++++++++ .../responders/responder.edit.modal.html | 15 ++ .../responders/responders-list.controller.js | 181 ++++++++++++++++++ .../responders/responders-list.html | 97 ++++++++++ .../details/organization.page.controller.js | 6 +- .../details/organization.page.html | 13 +- .../organizations/organizations.module.js | 56 +++++- .../organizations/organizations.service.js | 35 +++- 10 files changed, 592 insertions(+), 12 deletions(-) create mode 100644 www/src/app/pages/admin/organizations/components/responders/responder-config-form.controller.js create mode 100644 www/src/app/pages/admin/organizations/components/responders/responder-config-form.html create mode 100644 www/src/app/pages/admin/organizations/components/responders/responder.edit.controller.js create mode 100644 www/src/app/pages/admin/organizations/components/responders/responder.edit.modal.html create mode 100644 www/src/app/pages/admin/organizations/components/responders/responders-list.controller.js create mode 100644 www/src/app/pages/admin/organizations/components/responders/responders-list.html diff --git a/www/src/app/pages/admin/organizations/components/responders/responder-config-form.controller.js b/www/src/app/pages/admin/organizations/components/responders/responder-config-form.controller.js new file mode 100644 index 000000000..e1d8b04b2 --- /dev/null +++ b/www/src/app/pages/admin/organizations/components/responders/responder-config-form.controller.js @@ -0,0 +1,28 @@ +'use strict'; + +import _ from 'lodash/core'; + +export default class ResponderConfigFormController { + constructor($log, Tlps, ResponderService) { + 'ngInject'; + + this.ResponderService = ResponderService; + this.Tlps = Tlps; + this.rateUnits = ['Day', 'Month']; + } + + applyConfig(config) { + _.forEach( + _.keys(config), + k => (this.responder.configuration[k] = config[k]) + ); + } + + applyGlobalConfig() { + this.applyConfig(this.globalConfig.config); + } + + applyBaseConfig() { + this.applyConfig(this.baseConfig.config); + } +} \ No newline at end of file diff --git a/www/src/app/pages/admin/organizations/components/responders/responder-config-form.html b/www/src/app/pages/admin/organizations/components/responders/responder-config-form.html new file mode 100644 index 000000000..0b04facc4 --- /dev/null +++ b/www/src/app/pages/admin/organizations/components/responders/responder-config-form.html @@ -0,0 +1,89 @@ +
+

Base details

+ +
+ +
+ +
+
+
+ +
+

+ Configuration + +

+ +
+ +
+

+ Options + +

+
+ +
+
+ + +
+
+ + +
+
+
+
+ +
+
+ + +
+
+ + +
+
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+ +
+
+

Define the maximum number of requests and the associated unit if applicable.

+
+
+
\ No newline at end of file diff --git a/www/src/app/pages/admin/organizations/components/responders/responder.edit.controller.js b/www/src/app/pages/admin/organizations/components/responders/responder.edit.controller.js new file mode 100644 index 000000000..5d2f17bca --- /dev/null +++ b/www/src/app/pages/admin/organizations/components/responders/responder.edit.controller.js @@ -0,0 +1,84 @@ +'use strict'; + +import _ from 'lodash/core'; + +export default class ResponderEditController { + constructor( + $log, + $uibModalInstance, + definition, + globalConfig, + baseConfig, + configuration, + responder, + mode + ) { + 'ngInject'; + + this.$log = $log; + this.$uibModalInstance = $uibModalInstance; + this.mode = mode; + this.definition = definition; + this.globalConfig = globalConfig; + this.baseConfig = baseConfig; + this.configuration = configuration; + this.responder = responder; + } + + $onInit() { + if (_.isEmpty(this.responder)) { + let responder = { + name: this.definition.id, + configuration: {}, + rate: undefined, + rateUnit: undefined + }; + + _.forEach(this.definition.configurationItems, item => { + const property = item.name, + configValue = (this.configuration.config || {})[property]; + + responder.configuration[property] = + configValue || + item.defaultValue || + (item.multi ? [undefined] : undefined); + }); + + // Handle TLP default config + const globalConfig = [ + 'proxy_http', + 'proxy_https' + ]; + _.forEach(globalConfig, cnf => { + if (responder.configuration[cnf] === undefined) { + responder.configuration[cnf] = + this.configuration.config[cnf] !== undefined ? + this.configuration.config[cnf] : + undefined; + } + }); + + if (responder.configuration.check_tlp === undefined) { + responder.configuration.check_tlp = true; + } + if (responder.configuration.max_tlp === undefined) { + responder.configuration.max_tlp = 2; + } + if (responder.configuration.check_pap === undefined) { + responder.configuration.check_pap = true; + } + if (responder.configuration.max_pap === undefined) { + responder.configuration.max_pap = 2; + } + + this.responder = responder; + } + } + + save() { + this.$uibModalInstance.close(this.responder); + } + cancel() { + this.$uibModalInstance.dismiss('cancel'); + } +} \ No newline at end of file diff --git a/www/src/app/pages/admin/organizations/components/responders/responder.edit.modal.html b/www/src/app/pages/admin/organizations/components/responders/responder.edit.modal.html new file mode 100644 index 000000000..d6be8d337 --- /dev/null +++ b/www/src/app/pages/admin/organizations/components/responders/responder.edit.modal.html @@ -0,0 +1,15 @@ +
+ + + +
\ No newline at end of file diff --git a/www/src/app/pages/admin/organizations/components/responders/responders-list.controller.js b/www/src/app/pages/admin/organizations/components/responders/responders-list.controller.js new file mode 100644 index 000000000..01b62188b --- /dev/null +++ b/www/src/app/pages/admin/organizations/components/responders/responders-list.controller.js @@ -0,0 +1,181 @@ +'use strict'; + +import _ from 'lodash'; + +import ResponderEditController from './responder.edit.controller'; +import editModalTpl from './responder.edit.modal.html'; + +export default class OrganizationRespondersController { + constructor( + $log, + $q, + $uibModal, + ResponderService, + OrganizationService, + ModalService, + NotificationService + ) { + 'ngInject'; + + this.$log = $log; + this.$q = $q; + this.$uibModal = $uibModal; + this.ResponderService = ResponderService; + this.OrganizationService = OrganizationService; + this.ModalService = ModalService; + this.NotificationService = NotificationService; + + this.state = { + filterAvailable: '' + }; + } + + $onInit() { + this.activeResponders = _.keyBy(this.responders, 'workerDefinitionId'); + this.definitionsIds = _.keys(this.responderDefinitions).sort(); + this.invalidResponders = _.filter(this.responders, a => + _.isEmpty(a.dataTypeList) + ); + } + + openModal(mode, definition, responder) { + let baseConfigName = definition ? definition.baseConfig : undefined; + return this.ResponderService.getBaseConfig(baseConfigName) + .then(baseConfig => { + let configs = { + globalConfig: {}, + baseConfig: baseConfig, + responderConfig: { + config: {} + } + }; + + return this.ResponderService.getConfiguration('global').then( + globalConfig => { + configs.globalConfig = globalConfig; + + if (!baseConfig.config) { + baseConfig.config = {}; + } + + _.merge( + configs.responderConfig.config, + configs.baseConfig.config, + configs.globalConfig.config + ); + + return configs; + } + ); + }) + .then(configs => { + let modal = this.$uibModal.open({ + animation: true, + controller: ResponderEditController, + controllerAs: '$modal', + templateUrl: editModalTpl, + size: 'lg', + resolve: { + definition: () => definition, + globalConfig: () => configs.globalConfig, + baseConfig: () => configs.baseConfig, + configuration: () => configs.responderConfig, + responder: () => angular.copy(responder), + mode: () => mode + } + }); + + return modal.result; + }) + .then(response => { + if (mode === 'create') { + return this.OrganizationService.enableResponder( + definition.id, + response + ); + } else { + return this.OrganizationService.updateResponder( + responder.id, + _.pick( + response, + 'configuration', + 'rate', + 'rateUnit', + 'name' + ) + ); + } + }) + .then(() => this.reload()); + } + + edit(mode, definition, responder) { + this.openModal(mode, definition, responder) + .then(() => { + this.NotificationService.success('Responder updated successfully'); + }) + .catch(err => { + if (!_.isString(err)) { + this.NotificationService.error('Failed to edit the responder.'); + } + }); + } + + enable(responderId) { + let definition = this.responderDefinitions[responderId]; + + this.openModal('create', definition, {}) + .then(() => { + this.NotificationService.success('Responder enabled successfully'); + }) + .catch(err => { + if (!_.isString(err)) { + this.NotificationService.error('Failed to enable the responder.'); + } + }); + } + + disable(responderId) { + let modalInstance = this.ModalService.confirm( + 'Disable responder', + 'Are you sure you want to disable this responder? The corresponding configuration will be lost.', { + flavor: 'danger', + okText: 'Yes, disable it' + } + ); + + modalInstance.result + .then(() => this.OrganizationService.disableResponder(responderId)) + .then(() => { + this.reload(); + this.NotificationService.success('Responder disabled successfully'); + }) + .catch(err => { + if (!_.isString(err)) { + this.NotificationService.error('Unable to delete the responder.'); + } + }); + } + + reload() { + this.OrganizationService.responders(this.organization.id).then(responders => { + this.responders = responders; + this.$onInit(); + }); + } + + refreshResponders() { + this.ResponderService.scan() + .then(() => this.ResponderService.definitions(true)) + .then(defintions => { + this.responderDefinitions = defintions; + this.reload(); + this.NotificationService.success('Responder definitions refreshed.'); + }) + .catch(() => + this.NotificationService.error( + 'Failed to refresh responder definitions.' + ) + ); + } +} \ No newline at end of file diff --git a/www/src/app/pages/admin/organizations/components/responders/responders-list.html b/www/src/app/pages/admin/organizations/components/responders/responders-list.html new file mode 100644 index 000000000..9d5207dbb --- /dev/null +++ b/www/src/app/pages/admin/organizations/components/responders/responders-list.html @@ -0,0 +1,97 @@ +
+ +
+
+

You have {{$ctrl.invalidReponders.length}} invalid + +

+

Invalid responders have no definition and cannot be run on any observable. You have to remove them.

+
+
+
+
+
+

+ {{r.name}} +

+
+ +
+
+
+
+ +

Available responders ({{$ctrl.definitionsIds.length || 0}}) + + Refresh responders +

+ +
+
+
No responders available.
+
+
+ +
+
+
+ + + + +
+
+
+
+
Responders
+
Max TLP
+
Rate Limit
+
+
+
+
+ +
+

+ {{definition.id}} +

+

+ + Version: {{definition.version}} + + Author: {{definition.author}} + + License: {{definition.license}} +

+
{{definition.description}}
+
+
+ +
+
+ + {{$ctrl.activeResponders[def].rate}} per {{$ctrl.activeResponders[def].rateUnit}} + + None +
+
+ + Edit +
+ + +
+
+
+
\ No newline at end of file diff --git a/www/src/app/pages/admin/organizations/details/organization.page.controller.js b/www/src/app/pages/admin/organizations/details/organization.page.controller.js index bfca1e0ed..f66ad1969 100644 --- a/www/src/app/pages/admin/organizations/details/organization.page.controller.js +++ b/www/src/app/pages/admin/organizations/details/organization.page.controller.js @@ -1,12 +1,11 @@ 'use strict'; -import _ from 'lodash'; - export default class OrganizationPageController { constructor( $log, $stateParams, AnalyzerService, + ResponderService, OrganizationService, AuthService ) { @@ -15,7 +14,8 @@ export default class OrganizationPageController { this.$log = $log; this.orgId = $stateParams.id; this.AnalyzerService = AnalyzerService; + this.ResponderService = ResponderService; this.OrganizationService = OrganizationService; this.AuthService = AuthService; } -} +} \ No newline at end of file diff --git a/www/src/app/pages/admin/organizations/details/organization.page.html b/www/src/app/pages/admin/organizations/details/organization.page.html index 1927403a9..4477b6141 100644 --- a/www/src/app/pages/admin/organizations/details/organization.page.html +++ b/www/src/app/pages/admin/organizations/details/organization.page.html @@ -14,7 +14,7 @@

Organization: - Configurations + Analyzers Config @@ -22,6 +22,17 @@

Organization: Analyzers + + + + Responders Config + + + + + Responders + + diff --git a/www/src/app/pages/admin/organizations/organizations.module.js b/www/src/app/pages/admin/organizations/organizations.module.js index 6b551b3e9..6a2567cbc 100644 --- a/www/src/app/pages/admin/organizations/organizations.module.js +++ b/www/src/app/pages/admin/organizations/organizations.module.js @@ -8,21 +8,31 @@ import organizationsPageTpl from './list/organizations.page.html'; import OrganizationPageController from './details/organization.page.controller'; import organizationPageTpl from './details/organization.page.html'; +// Users +import OrganizationUsersController from './components/users-list.controller'; +import organizationUsersTpl from './components/users-list.html'; + +// Analyzers import AnalyzerConfigFormController from './components/analyzer-config-form.controller'; import analyzerConfigFormTpl from './components/analyzer-config-form.html'; import OrganizationAnalyzersController from './components/analyzers-list.controller'; import organizationAnalyzersTpl from './components/analyzers-list.html'; -import OrganizationUsersController from './components/users-list.controller'; -import organizationUsersTpl from './components/users-list.html'; - import OrganizationConfigsController from './components/config-list.controller'; import organizationConfigsTpl from './components/config-list.html'; import ConfigurationForm from './components/config-form.controller'; import configurationFormTpl from './components/config-form.html'; +// Responders +import ResponderConfigFormController from './components/responders/responder-config-form.controller'; +import responderConfigFormTpl from './components/responders/responder-config-form.html'; + +import OrganizationRespondersController from './components/responders/responders-list.controller'; +import organizationRespondersTpl from './components/responders/responders-list.html'; + + import organizationService from './organizations.service.js'; import './organizations.scss'; @@ -67,6 +77,20 @@ const organizationsModule = angular } else { return $q.resolve([]); } + }, + responderDefinitions: (AuthService, ResponderService, $q) => { + if (AuthService.hasRole([Roles.ORGADMIN])) { + return ResponderService.definitions(); + } else { + return $q.resolve({}); + } + }, + responders: (AuthService, OrganizationService, $q) => { + if (AuthService.hasRole([Roles.ORGADMIN])) { + return OrganizationService.responders(); + } else { + return $q.resolve([]); + } } }, data: { @@ -86,7 +110,9 @@ const organizationsModule = angular users: '<', analyzerDefinitions: '<', analyzers: '<', - configurations: '<' + configurations: '<', + responderDefinitions: '<', + responders: '<' } }) .component('organizationAnalyzersList', { @@ -98,6 +124,15 @@ const organizationsModule = angular analyzers: '<' } }) + .component('organizationRespondersList', { + controller: OrganizationRespondersController, + templateUrl: organizationRespondersTpl, + bindings: { + organization: '<', + responderDefinitions: '<', + responders: '<' + } + }) .component('organizationUsersList', { controller: OrganizationUsersController, templateUrl: organizationUsersTpl, @@ -132,6 +167,17 @@ const organizationsModule = angular baseConfig: '<' } }) + .component('responderConfigForm', { + controller: ResponderConfigFormController, + templateUrl: responderConfigFormTpl, + bindings: { + responder: '=', + definition: '=', + configuration: '<', + globalConfig: '<', + baseConfig: '<' + } + }) .service('OrganizationService', organizationService); -export default organizationsModule; +export default organizationsModule; \ No newline at end of file diff --git a/www/src/app/pages/admin/organizations/organizations.service.js b/www/src/app/pages/admin/organizations/organizations.service.js index 255024450..dc229b377 100644 --- a/www/src/app/pages/admin/organizations/organizations.service.js +++ b/www/src/app/pages/admin/organizations/organizations.service.js @@ -60,20 +60,37 @@ export default class OrganizationService { } enable(id) { - return this.$http.patch(`./api/organization/${id}`, { status: 'Active' }); + return this.$http.patch(`./api/organization/${id}`, { + status: 'Active' + }); } analyzers() { let defer = this.$q.defer(); this.$http - .get(`./api/organization/analyzer`, { params: { range: 'all' } }) + .get(`./api/organization/analyzer`, { + params: { + range: 'all' + } + }) .then(response => defer.resolve(response.data)) .catch(err => defer.reject(err)); return defer.promise; } + responders() { + return this.$http + .get(`./api/organization/responder`, { + params: { + range: 'all' + } + }) + .then(response => this.$q.resolve(response.data)) + .catch(err => this.$q.reject(err)); + } + users(id) { let defer = this.$q.defer(); @@ -96,4 +113,16 @@ export default class OrganizationService { disableAnalyzer(analyzerId) { return this.$http.delete(`./api/analyzer/${analyzerId}`); } -} + + enableResponder(responderId, config) { + return this.$http.post(`./api/organization/responder/${responderId}`, config); + } + + updateResponder(responderId, config) { + return this.$http.patch(`./api/responder/${responderId}`, config); + } + + disableResponder(responderId) { + return this.$http.delete(`./api/responder/${responderId}`); + } +} \ No newline at end of file