http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/1ef8cd8f/metron-interface/metron-config/src/app/sensors/sensor-threat-triage/sensor-threat-triage.component.html ---------------------------------------------------------------------- diff --git a/metron-interface/metron-config/src/app/sensors/sensor-threat-triage/sensor-threat-triage.component.html b/metron-interface/metron-config/src/app/sensors/sensor-threat-triage/sensor-threat-triage.component.html new file mode 100644 index 0000000..9d67df6 --- /dev/null +++ b/metron-interface/metron-config/src/app/sensors/sensor-threat-triage/sensor-threat-triage.component.html @@ -0,0 +1,88 @@ +<!-- + Licensed to the Apache Software + Foundation (ASF) under one or more contributor license agreements. See the + NOTICE file distributed with this work for additional information regarding + copyright ownership. The ASF licenses this file to You under the Apache License, + Version 2.0 (the "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software distributed + under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES + OR CONDITIONS OF ANY KIND, either express or implied. See the License for + the specific language governing permissions and limitations under the License. + --> +<metron-config-sensor-rule-editor *ngIf="showTextEditor" [riskLevelRule]="currentRiskLevelRule" + (onCancelTextEditor)="onCancelTextEditor()" + (onSubmitTextEditor)="onSubmitTextEditor($event)"></metron-config-sensor-rule-editor> + +<div class="metron-slider-pane-edit fill load-left-to-right dialog1x"> + + <div class="form-title">Threat Triage Rules</div> + <i class="fa fa-times pull-right close-button" aria-hidden="true" (click)="onClose()"></i> + + + <form role="form" class="threat-intel-form"> + <div class="form-group threat-triage-aggregator"> + <label attr.for="aggregator">AGGREGATOR</label> + <select class="form-control" name="aggregator" + [(ngModel)]="sensorEnrichmentConfig.threatIntel.triageConfig.aggregator"> + <option *ngFor="let aggregator of availableAggregators" [value]="aggregator">{{aggregator}}</option> + </select> + </div> + <div class="threat-triage-summary"> + <div class="form-group"> + <div class="rules-summary-title">Rules</div> + <div class="row mx-0"> + <div class="btn" (click)="onFilterChange(threatTriageFilter.HIGH)" [ngClass]="{'filter-button': filter != threatTriageFilter.HIGH, 'filter-button-selected': filter == threatTriageFilter.HIGH}"> + <i aria-hidden="true" class="fa fa-circle" style="color: red"></i> {{highAlerts}} + </div> + <div class="btn" (click)="onFilterChange(threatTriageFilter.MEDIUM)" [ngClass]="{'filter-button': filter != threatTriageFilter.MEDIUM, 'filter-button-selected': filter == threatTriageFilter.MEDIUM}"> + <i aria-hidden="true" class="fa fa-circle" style="color: orange"></i> {{mediumAlerts}} + </div> + <div class="btn" (click)="onFilterChange(threatTriageFilter.LOW)" [ngClass]="{'filter-button': filter != threatTriageFilter.LOW, 'filter-button-selected': filter == threatTriageFilter.LOW}"> + <i aria-hidden="true" class="fa fa-circle" style="color: khaki"></i> {{lowAlerts}} + </div> + </div> + <div class="row mx-0 threat-triage-rules-sort"> + <span class="label">Sort by </span> + <li class="nav-item dropdown"> + <span class="nav-link dropdown-toggle" data-toggle="dropdown" href="#" role="button" aria-haspopup="true" aria-expanded="false">{{sortOrderOption[sortOrder].replace('_', ' ')}}</span> + <div class="dropdown-menu bg-inverse"> + <span class="dropdown-item" (click)="onSortOrderChange(sortOrderOption.Highest_Score)">Highest Score</span> + <span class="dropdown-item" (click)="onSortOrderChange(sortOrderOption.Lowest_Score)">Lowest Score</span> + <span class="dropdown-item" (click)="onSortOrderChange(sortOrderOption.Highest_Name)">Highest Name</span> + <span class="dropdown-item" (click)="onSortOrderChange(sortOrderOption.Lowest_Name)">Lowest Name</span> + </div> + </li> + </div> + </div> + </div> + <div class="form-group threat-triage-rules-list"> + <div *ngFor="let riskLevelRule of this.visibleRules"> + <div class="row mx-0 py-0"> + <div class="threat-triage-rule-row" style="color: khaki; font-size: 30px; margin-top: -7px" [style.color]="getRuleColor(riskLevelRule)"> + <b>I</b> + </div> + <div class="threat-triage-rule-row" style="font-size: small; width: 8%"> + {{ riskLevelRule.score }} + </div> + <div class="threat-triage-rule-row threat-triage-rule-str"> + {{ getDisplayName(riskLevelRule) }} + </div> + <div class="threat-triage-rule-row" style=""><i class="fa fa-i-cursor" aria-hidden="true" + style="cursor: pointer;" (click)="onEditRule(riskLevelRule)"></i></div> + + <div class="threat-triage-rule-row" style=""><i class="fa fa-trash-o" aria-hidden="true" (click)="onDeleteRule(riskLevelRule)" style="cursor: pointer"></i></div> + </div> + <div class="form-seperator-edit"></div> + </div> + </div> + <div class="form-group mx-0 py-0"> + <button class="btn form-enable-disable-button add-button" (click)="onNewRule()"><i + aria-hidden="true" class="fa fa-plus fa-4"></i></button> + </div> + </form> + +</div>
http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/1ef8cd8f/metron-interface/metron-config/src/app/sensors/sensor-threat-triage/sensor-threat-triage.component.scss ---------------------------------------------------------------------- diff --git a/metron-interface/metron-config/src/app/sensors/sensor-threat-triage/sensor-threat-triage.component.scss b/metron-interface/metron-config/src/app/sensors/sensor-threat-triage/sensor-threat-triage.component.scss new file mode 100644 index 0000000..bbc17a0 --- /dev/null +++ b/metron-interface/metron-config/src/app/sensors/sensor-threat-triage/sensor-threat-triage.component.scss @@ -0,0 +1,137 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +@import "../../_variables.scss"; +@import "../../../styles.scss"; + +textarea +{ + height: auto; +} + +.form-title +{ + padding-left: 25px; +} + +.form-group +{ + padding-left: 25px; + padding-right: 20px; +} + +.close-button +{ + padding-right: 20px; +} + +.threat-triage-summary +{ + background-color: $edit-child-highlight; + padding-top: 5px; + padding-bottom: 5px; +} + +.filter-button +{ + width: 32%; + background: $field-background; + border: 1px solid $form-button-border; + border-radius: .25em; + cursor: default; + color: $nav-active-color; + i { + font-size: smaller; + } +} + +.filter-button-selected +{ + @extend .filter-button; + background-color: #006ea0; + color: #bdbdbd; +} + +.threat-triage-aggregator +{ + padding-bottom: 10px; +} + +.threat-triage-rules-sort +{ + padding-top: 5px; + font-size: 12px; + font-family: Roboto-Regular; + .label + { + display: inline-block; + padding-right: 8px; + } + .dropdown + { + list-style: none; + display: inline-block; + color: $field-button-color; + } +} + +.rules-summary-title +{ + font-size: 15px; +} + +.threat-triage-rules-list +{ + padding-top: 10px; +} + +.threat-triage-rule-row +{ + display: inline-block; + position: relative; + vertical-align: top; + + .fa { + color: $nav-active-color; + font-size: large; + } +} + +.add-button +{ + font-size: 20px; + width: 100%; + padding: 2px; + + i { + color: $field-button-color; + } +} + +.threat-triage-rule-str +{ + width: 64%; + padding-right: 10px; + font-size: small; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} +metron-config-sensor-rule-editor +{ + @extend .flexbox-row-reverse; +} http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/1ef8cd8f/metron-interface/metron-config/src/app/sensors/sensor-threat-triage/sensor-threat-triage.component.spec.ts ---------------------------------------------------------------------- diff --git a/metron-interface/metron-config/src/app/sensors/sensor-threat-triage/sensor-threat-triage.component.spec.ts b/metron-interface/metron-config/src/app/sensors/sensor-threat-triage/sensor-threat-triage.component.spec.ts new file mode 100644 index 0000000..b9e595e --- /dev/null +++ b/metron-interface/metron-config/src/app/sensors/sensor-threat-triage/sensor-threat-triage.component.spec.ts @@ -0,0 +1,211 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import {SimpleChange, SimpleChanges} from '@angular/core'; +import {Http} from '@angular/http'; +import {async, TestBed, ComponentFixture} from '@angular/core/testing'; +import {SensorThreatTriageComponent, SortOrderOption, ThreatTriageFilter} from './sensor-threat-triage.component'; +import {SensorEnrichmentConfig, ThreatIntelConfig} from '../../model/sensor-enrichment-config'; +import {RiskLevelRule} from '../../model/risk-level-rule'; +import {SensorEnrichmentConfigService} from '../../service/sensor-enrichment-config.service'; +import {Observable} from 'rxjs/Observable'; +import '../../rxjs-operators'; +import {SensorThreatTriageModule} from './sensor-threat-triage.module'; + +class MockSensorEnrichmentConfigService { + public getAvailableThreatTriageAggregators(): Observable<string[]> { + return Observable.create(observer => { + observer.next(['MAX', 'MIN', 'SUM', 'MEAN', 'POSITIVE_MEAN']); + observer.complete(); + }); + } +} + +describe('Component: SensorThreatTriageComponent', () => { + + let component: SensorThreatTriageComponent; + let fixture: ComponentFixture<SensorThreatTriageComponent>; + let sensorEnrichmentConfigService: SensorEnrichmentConfigService; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [SensorThreatTriageModule], + providers: [ + {provide: Http}, + {provide: SensorEnrichmentConfigService, useClass: MockSensorEnrichmentConfigService}, + ] + }).compileComponents() + .then(() => { + fixture = TestBed.createComponent(SensorThreatTriageComponent); + component = fixture.componentInstance; + sensorEnrichmentConfigService = fixture.debugElement.injector.get(SensorEnrichmentConfigService); + }); + })); + + it('should create an instance', () => { + expect(component).toBeDefined(); + fixture.destroy(); + }); + + it('should create an instance', async(() => { + spyOn(component, 'init'); + let changes: SimpleChanges = {'showThreatTriage': new SimpleChange(false, true)}; + + component.ngOnChanges(changes); + expect(component.init).toHaveBeenCalled(); + + changes = {'showStellar': new SimpleChange(true, false)}; + component.ngOnChanges(changes); + expect(component.init['calls'].count()).toEqual(1); + + fixture.destroy(); + })); + + it('should close panel', async(() => { + let numClosed = 0; + component.hideThreatTriage.subscribe((closed: boolean) => { + numClosed++; + }); + + component.onClose(); + expect(numClosed).toEqual(1); + + fixture.destroy(); + })); + + it('should get color', async(() => { + let sensorEnrichmentConfig = new SensorEnrichmentConfig(); + sensorEnrichmentConfig.threatIntel = Object.assign(new ThreatIntelConfig(), { + 'triageConfig': { + 'riskLevelRules': { + 'ruleA': 15, + 'ruleB': 95, + 'ruleC': 50 + }, + 'aggregator': 'MAX', + 'aggregationConfig': {} + } + }); + component.sensorEnrichmentConfig = sensorEnrichmentConfig; + + let ruleA = {name: 'ruleA', rule: 'rule A', score: 15, comment: ''}; + let ruleB = {name: 'ruleB', rule: 'rule B', score: 95, comment: ''}; + let ruleC = {name: 'ruleC', rule: 'rule C', score: 50, comment: ''}; + + expect(component.getRuleColor(ruleA)).toEqual('khaki'); + expect(component.getRuleColor(ruleB)).toEqual('red'); + expect(component.getRuleColor(ruleC)).toEqual('orange'); + + fixture.destroy(); + })); + + it('should edit rules', async(() => { + let ruleA = {name: 'ruleA', rule: 'rule A', score: 15, comment: ''}; + let ruleB = {name: 'ruleB', rule: 'rule B', score: 95, comment: ''}; + let ruleC = {name: 'ruleC', rule: 'rule C', score: 50, comment: ''}; + let ruleD = {name: 'ruleD', rule: 'rule D', score: 85, comment: ''}; + let ruleE = {name: 'ruleE', rule: 'rule E', score: 5, comment: ''}; + let ruleF = {name: 'ruleF', rule: 'rule F', score: 21, comment: ''}; + let ruleG = {name: 'ruleG', rule: 'rule G', score: 100, comment: ''}; + + let sensorEnrichmentConfig = new SensorEnrichmentConfig(); + sensorEnrichmentConfig.threatIntel = Object.assign(new ThreatIntelConfig(), { + 'triageConfig': { + 'riskLevelRules': [ruleA, ruleB, ruleC, ruleD, ruleE], + 'aggregator': 'MAX', + 'aggregationConfig': {} + } + }); + component.sensorEnrichmentConfig = sensorEnrichmentConfig; + + + let changes: SimpleChanges = {'showThreatTriage': new SimpleChange(false, true)}; + component.ngOnChanges(changes); + + // sorted by score high to low + expect(component.visibleRules).toEqual([ruleB, ruleD, ruleC, ruleA, ruleE]); + expect(component.lowAlerts).toEqual(2); + expect(component.mediumAlerts).toEqual(1); + expect(component.highAlerts).toEqual(2); + + // sorted by name high to low + component.onSortOrderChange(SortOrderOption.Highest_Name); + expect(component.visibleRules).toEqual([ruleE, ruleD, ruleC, ruleB, ruleA]); + + // sorted by score low to high + component.onSortOrderChange(SortOrderOption.Lowest_Score); + expect(component.visibleRules).toEqual([ruleE, ruleA, ruleC, ruleD, ruleB]); + + // sorted by name low to high + component.onSortOrderChange(SortOrderOption.Lowest_Name); + expect(component.visibleRules).toEqual([ruleA, ruleB, ruleC, ruleD, ruleE]); + + component.onNewRule(); + expect(component.currentRiskLevelRule.name).toEqual(''); + expect(component.currentRiskLevelRule.rule).toEqual(''); + expect(component.currentRiskLevelRule.score).toEqual(0); + expect(component.showTextEditor).toEqual(true); + + component.currentRiskLevelRule = new RiskLevelRule(); + component.onCancelTextEditor(); + expect(component.showTextEditor).toEqual(false); + expect(component.visibleRules).toEqual([ruleA, ruleB, ruleC, ruleD, ruleE]); + + component.sortOrder = SortOrderOption.Lowest_Score; + component.onNewRule(); + component.currentRiskLevelRule = ruleF; + expect(component.showTextEditor).toEqual(true); + component.onSubmitTextEditor(ruleF); + expect(component.visibleRules).toEqual([ruleE, ruleA, ruleF, ruleC, ruleD, ruleB]); + expect(component.lowAlerts).toEqual(2); + expect(component.mediumAlerts).toEqual(2); + expect(component.highAlerts).toEqual(2); + expect(component.showTextEditor).toEqual(false); + + component.onDeleteRule(ruleE); + expect(component.visibleRules).toEqual([ruleA, ruleF, ruleC, ruleD, ruleB]); + expect(component.lowAlerts).toEqual(1); + expect(component.mediumAlerts).toEqual(2); + expect(component.highAlerts).toEqual(2); + + component.onFilterChange(ThreatTriageFilter.LOW); + expect(component.visibleRules).toEqual([ruleA]); + + component.onFilterChange(ThreatTriageFilter.MEDIUM); + expect(component.visibleRules).toEqual([ruleF, ruleC]); + + component.onFilterChange(ThreatTriageFilter.HIGH); + expect(component.visibleRules).toEqual([ruleD, ruleB]); + + component.onFilterChange(ThreatTriageFilter.HIGH); + expect(component.visibleRules).toEqual([ruleA, ruleF, ruleC, ruleD, ruleB]); + + component.onEditRule(ruleC); + expect(component.currentRiskLevelRule).toEqual(ruleC); + expect(component.showTextEditor).toEqual(true); + component.onSubmitTextEditor(ruleG); + expect(component.visibleRules).toEqual([ruleA, ruleF, ruleD, ruleB, ruleG]); + expect(component.lowAlerts).toEqual(1); + expect(component.mediumAlerts).toEqual(1); + expect(component.highAlerts).toEqual(3); + expect(component.showTextEditor).toEqual(false); + + fixture.destroy(); + })); + + +}); http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/1ef8cd8f/metron-interface/metron-config/src/app/sensors/sensor-threat-triage/sensor-threat-triage.component.ts ---------------------------------------------------------------------- diff --git a/metron-interface/metron-config/src/app/sensors/sensor-threat-triage/sensor-threat-triage.component.ts b/metron-interface/metron-config/src/app/sensors/sensor-threat-triage/sensor-threat-triage.component.ts new file mode 100644 index 0000000..db32b04 --- /dev/null +++ b/metron-interface/metron-config/src/app/sensors/sensor-threat-triage/sensor-threat-triage.component.ts @@ -0,0 +1,208 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/* tslint:disable:triple-equals */ +import {Component, Input, EventEmitter, Output, OnChanges, SimpleChanges} from '@angular/core'; +import {SensorEnrichmentConfig } from '../../model/sensor-enrichment-config'; +import {RiskLevelRule} from '../../model/risk-level-rule'; +import {SensorEnrichmentConfigService} from '../../service/sensor-enrichment-config.service'; + +export enum SortOrderOption { + Lowest_Score, Highest_Score, Lowest_Name, Highest_Name +} + +export enum ThreatTriageFilter { + NONE, LOW, MEDIUM, HIGH +} + +@Component({ + selector: 'metron-config-sensor-threat-triage', + templateUrl: './sensor-threat-triage.component.html', + styleUrls: ['./sensor-threat-triage.component.scss'] +}) + +export class SensorThreatTriageComponent implements OnChanges { + + @Input() showThreatTriage: boolean; + @Input() sensorEnrichmentConfig: SensorEnrichmentConfig; + + @Output() hideThreatTriage: EventEmitter<boolean> = new EventEmitter<boolean>(); + availableAggregators = []; + visibleRules: RiskLevelRule[] = []; + + showTextEditor = false; + currentRiskLevelRule: RiskLevelRule; + + lowAlerts = 0; + mediumAlerts = 0; + highAlerts = 0; + + sortOrderOption = SortOrderOption; + sortOrder = SortOrderOption.Highest_Score; + threatTriageFilter = ThreatTriageFilter; + filter: ThreatTriageFilter = ThreatTriageFilter.NONE; + + constructor(private sensorEnrichmentConfigService: SensorEnrichmentConfigService) { } + + ngOnChanges(changes: SimpleChanges) { + if (changes['showThreatTriage'] && changes['showThreatTriage'].currentValue) { + this.init(); + } + } + + init(): void { + this.visibleRules = this.sensorEnrichmentConfig.threatIntel.triageConfig.riskLevelRules; + this.sensorEnrichmentConfigService.getAvailableThreatTriageAggregators().subscribe(results => { + this.availableAggregators = results; + }); + this.updateBuckets(); + this.onSortOrderChange(null); + } + + onClose(): void { + this.hideThreatTriage.emit(true); + } + + + onSubmitTextEditor(riskLevelRule: RiskLevelRule): void { + this.deleteRule(this.currentRiskLevelRule); + this.sensorEnrichmentConfig.threatIntel.triageConfig.riskLevelRules.push(riskLevelRule); + this.showTextEditor = false; + this.init(); + } + + onCancelTextEditor(): void { + this.showTextEditor = false; + } + + onEditRule(riskLevelRule: RiskLevelRule) { + this.currentRiskLevelRule = riskLevelRule; + this.showTextEditor = true; + } + + onDeleteRule(riskLevelRule: RiskLevelRule) { + this.deleteRule(riskLevelRule); + this.init(); + } + + onNewRule(): void { + this.currentRiskLevelRule = new RiskLevelRule(); + this.showTextEditor = true; + } + + deleteRule(riskLevelRule: RiskLevelRule) { + let index = this.sensorEnrichmentConfig.threatIntel.triageConfig.riskLevelRules.indexOf(riskLevelRule); + if (index != -1) { + this.sensorEnrichmentConfig.threatIntel.triageConfig.riskLevelRules.splice(index, 1); + } + } + + updateBuckets() { + this.lowAlerts = 0; + this.mediumAlerts = 0; + this.highAlerts = 0; + for (let riskLevelRule of this.visibleRules) { + if (riskLevelRule.score <= 20) { + this.lowAlerts++; + } else if (riskLevelRule.score >= 80) { + this.highAlerts++; + } else { + this.mediumAlerts++; + } + } + } + + getRuleColor(riskLevelRule: RiskLevelRule): string { + let color: string; + if (riskLevelRule.score <= 20) { + color = 'khaki'; + } else if (riskLevelRule.score >= 80) { + color = 'red'; + } else { + color = 'orange'; + } + return color; + } + + onSortOrderChange(sortOrder: any) { + if (sortOrder !== null) { + this.sortOrder = sortOrder; + } + + // all comparisons with enums must be == and not === + if (this.sortOrder == this.sortOrderOption.Highest_Score) { + this.visibleRules.sort((a, b) => { + return b.score - a.score; + }); + } else if (this.sortOrder == SortOrderOption.Lowest_Score) { + this.visibleRules.sort((a, b) => { + return a.score - b.score; + }); + } else if (this.sortOrder == SortOrderOption.Lowest_Name) { + this.visibleRules.sort((a, b) => { + let aName = a.name ? a.name : ''; + let bName = b.name ? b.name : ''; + if (aName.toLowerCase() >= bName.toLowerCase()) { + return 1; + } else if (aName.toLowerCase() < bName.toLowerCase()) { + return -1; + } + }); + } else { + this.visibleRules.sort((a, b) => { + let aName = a.name ? a.name : ''; + let bName = b.name ? b.name : ''; + if (aName.toLowerCase() >= bName.toLowerCase()) { + return -1; + } else if (aName.toLowerCase() < bName.toLowerCase()) { + return 1; + } + }); + } + } + + onFilterChange(filter: ThreatTriageFilter) { + if (filter === this.filter) { + this.filter = ThreatTriageFilter.NONE; + } else { + this.filter = filter; + } + this.visibleRules = this.sensorEnrichmentConfig.threatIntel.triageConfig.riskLevelRules.filter(riskLevelRule => { + if (this.filter === ThreatTriageFilter.NONE) { + return true; + } else { + if (this.filter === ThreatTriageFilter.HIGH) { + return riskLevelRule.score >= 80; + } else if (this.filter === ThreatTriageFilter.LOW) { + return riskLevelRule.score <= 20; + } else { + return riskLevelRule.score < 80 && riskLevelRule.score > 20; + } + } + }); + this.onSortOrderChange(null); + } + + getDisplayName(riskLevelRule: RiskLevelRule): string { + if (riskLevelRule.name) { + return riskLevelRule.name; + } else { + return riskLevelRule.rule ? riskLevelRule.rule : ''; + } + } + +} http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/1ef8cd8f/metron-interface/metron-config/src/app/sensors/sensor-threat-triage/sensor-threat-triage.module.ts ---------------------------------------------------------------------- diff --git a/metron-interface/metron-config/src/app/sensors/sensor-threat-triage/sensor-threat-triage.module.ts b/metron-interface/metron-config/src/app/sensors/sensor-threat-triage/sensor-threat-triage.module.ts new file mode 100644 index 0000000..66838d9 --- /dev/null +++ b/metron-interface/metron-config/src/app/sensors/sensor-threat-triage/sensor-threat-triage.module.ts @@ -0,0 +1,29 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { NgModule } from '@angular/core'; +import {SharedModule} from '../../shared/shared.module'; +import {SensorThreatTriageComponent} from './sensor-threat-triage.component'; +import {SensorRuleEditorModule} from './rule-editor/sensor-rule-editor.module'; + + +@NgModule ({ + imports: [ SharedModule, SensorRuleEditorModule ], + declarations: [ SensorThreatTriageComponent ], + exports: [ SensorThreatTriageComponent ] +}) +export class SensorThreatTriageModule {} http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/1ef8cd8f/metron-interface/metron-config/src/app/service/authentication.service.spec.ts ---------------------------------------------------------------------- diff --git a/metron-interface/metron-config/src/app/service/authentication.service.spec.ts b/metron-interface/metron-config/src/app/service/authentication.service.spec.ts new file mode 100644 index 0000000..7f9b296 --- /dev/null +++ b/metron-interface/metron-config/src/app/service/authentication.service.spec.ts @@ -0,0 +1,190 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import {Router} from '@angular/router'; +import {async, inject, TestBed} from '@angular/core/testing'; +import {MockBackend, MockConnection} from '@angular/http/testing'; +import {HttpModule, XHRBackend, Response, ResponseOptions, Http} from '@angular/http'; +import '../rxjs-operators'; +import {Observable} from 'rxjs/Observable'; +import {AuthenticationService} from './authentication.service'; +import {APP_CONFIG, METRON_REST_CONFIG} from '../app.config'; +import {IAppConfig} from '../app.config.interface'; + +class MockRouter { + + navigateByUrl(url: string) { + + } +} + +describe('AuthenticationService', () => { + + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [HttpModule], + providers: [ + AuthenticationService, + {provide: XHRBackend, useClass: MockBackend}, + {provide: Router, useClass: MockRouter}, + {provide: APP_CONFIG, useValue: METRON_REST_CONFIG} + ] + }) + .compileComponents(); + })); + + describe('when service functions', () => { + it('can instantiate service when inject service', + inject([AuthenticationService], (service: AuthenticationService) => { + expect(service instanceof AuthenticationService).toBe(true); + })); + + }); + + describe('when service functions', () => { + let authenticationService: AuthenticationService; + let mockBackend: MockBackend; + let userResponse: Response; + let userName = 'test'; + let router: MockRouter; + + beforeEach(inject([Http, XHRBackend, Router, AuthenticationService, APP_CONFIG], + (http: Http, be: MockBackend, mRouter: MockRouter, service: AuthenticationService, config: IAppConfig) => { + mockBackend = be; + router = mRouter; + authenticationService = service; + userResponse = new Response(new ResponseOptions({status: 200, body: userName})); + })); + + it('init', async(inject([], () => { + let userResponsesuccess = true; + spyOn(authenticationService.onLoginEvent, 'emit'); + spyOn(authenticationService, 'getCurrentUser').and.callFake(function() { + if (userResponsesuccess) { + return Observable.create(observer => { + observer.next(userResponse); + observer.complete(); + }); + } + + return Observable.throw('Error'); + }); + + authenticationService.init(); + expect(authenticationService.onLoginEvent.emit).toHaveBeenCalledWith(true); + + userResponsesuccess = false; + authenticationService.init(); + expect(authenticationService.onLoginEvent.emit['calls'].count()).toEqual(2); + + }))); + + it('login', async(inject([], () => { + let responseMessageSuccess = true; + mockBackend.connections.subscribe((c: MockConnection) => { + if (responseMessageSuccess) { + c.mockRespond(userResponse); + } else { + c.mockError(new Error('login failed')); + } + }); + + spyOn(router, 'navigateByUrl'); + spyOn(authenticationService.onLoginEvent, 'emit'); + authenticationService.login('test', 'test', error => { + }); + + expect(router.navigateByUrl).toHaveBeenCalledWith('/sensors'); + expect(authenticationService.onLoginEvent.emit).toHaveBeenCalled(); + + responseMessageSuccess = false; + let errorSpy = jasmine.createSpy('error'); + authenticationService.login('test', 'test', errorSpy); + expect(errorSpy).toHaveBeenCalledWith(new Error('login failed')); + + }))); + + it('logout', async(inject([], () => { + let responseMessageSuccess = true; + mockBackend.connections.subscribe((c: MockConnection) => { + if (responseMessageSuccess) { + c.mockRespond(userResponse); + } else { + c.mockError(new Error('login failed')); + } + }); + + spyOn(router, 'navigateByUrl'); + spyOn(authenticationService.onLoginEvent, 'emit'); + authenticationService.logout(); + + expect(router.navigateByUrl).toHaveBeenCalledWith('/login'); + expect(authenticationService.onLoginEvent.emit).toHaveBeenCalled(); + + responseMessageSuccess = false; + spyOn(console, 'log'); + authenticationService.logout(); + expect(console.log).toHaveBeenCalled(); + + }))); + + it('checkAuthentication', async(inject([], () => { + let isAuthenticated = false; + spyOn(router, 'navigateByUrl'); + spyOn(authenticationService, 'isAuthenticated').and.callFake(function() { + return isAuthenticated; + }); + + authenticationService.checkAuthentication(); + expect(router.navigateByUrl).toHaveBeenCalledWith('/login'); + + isAuthenticated = true; + authenticationService.checkAuthentication(); + expect(router.navigateByUrl['calls'].count()).toEqual(1); + }))); + + it('getCurrentUser', async(inject([], () => { + mockBackend.connections.subscribe((c: MockConnection) => userResponse); + authenticationService.getCurrentUser(null).subscribe( + result => { + expect(result).toEqual(''); + }, error => console.log(error)); + }))); + + it('isAuthenticationChecked', async(inject([], () => { + mockBackend.connections.subscribe((c: MockConnection) => c.mockRespond(userResponse)); + + expect(authenticationService.isAuthenticationChecked()).toEqual(false); + + authenticationService.login('test', 'test', null); + expect(authenticationService.isAuthenticationChecked()).toEqual(true); + + }))); + + it('isAuthenticated', async(inject([], () => { + mockBackend.connections.subscribe((c: MockConnection) => c.mockRespond(userResponse)); + + expect(authenticationService.isAuthenticated()).toEqual(false); + + authenticationService.login('test', 'test', null); + expect(authenticationService.isAuthenticated()).toEqual(true); + + }))); + }); + + +}); http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/1ef8cd8f/metron-interface/metron-config/src/app/service/authentication.service.ts ---------------------------------------------------------------------- diff --git a/metron-interface/metron-config/src/app/service/authentication.service.ts b/metron-interface/metron-config/src/app/service/authentication.service.ts new file mode 100644 index 0000000..5fd50f3 --- /dev/null +++ b/metron-interface/metron-config/src/app/service/authentication.service.ts @@ -0,0 +1,92 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import {Injectable, EventEmitter, Inject} from '@angular/core'; +import {Http, Headers, RequestOptions, Response} from '@angular/http'; +import {Router} from '@angular/router'; +import {Observable} from 'rxjs/Observable'; +import {IAppConfig} from '../app.config.interface'; +import {APP_CONFIG} from '../app.config'; + +@Injectable() +export class AuthenticationService { + + private static USER_NOT_VERIFIED: string = 'USER-NOT-VERIFIED'; + private currentUser: string = AuthenticationService.USER_NOT_VERIFIED; + loginUrl: string = this.config.apiEndpoint + '/user'; + logoutUrl: string = '/logout'; + defaultHeaders = {'Content-Type': 'application/json', 'X-Requested-With': 'XMLHttpRequest'}; + onLoginEvent: EventEmitter<boolean> = new EventEmitter<boolean>(); + + constructor(private http: Http, private router: Router, @Inject(APP_CONFIG) private config: IAppConfig) { + this.init(); + } + + public init() { + this.getCurrentUser(new RequestOptions({headers: new Headers(this.defaultHeaders)})).subscribe((response: Response) => { + this.currentUser = response.text(); + if (this.currentUser) { + this.onLoginEvent.emit(true); + } + }, error => { + this.onLoginEvent.emit(false); + }); + } + + public login(username: string, password: string, onError): void { + let loginHeaders: Headers = new Headers(this.defaultHeaders); + loginHeaders.append('authorization', 'Basic ' + btoa(username + ':' + password)); + let loginOptions: RequestOptions = new RequestOptions({headers: loginHeaders}); + this.getCurrentUser(loginOptions).subscribe((response: Response) => { + this.currentUser = response.text(); + this.router.navigateByUrl('/sensors'); + this.onLoginEvent.emit(true); + }, + error => { + onError(error); + }); + } + + public logout(): void { + this.http.post(this.logoutUrl, {}, new RequestOptions({headers: new Headers(this.defaultHeaders)})).subscribe(response => { + this.currentUser = AuthenticationService.USER_NOT_VERIFIED; + this.onLoginEvent.emit(false); + this.router.navigateByUrl('/login'); + }, + error => { + console.log(error); + }); + } + + public checkAuthentication() { + if (!this.isAuthenticated()) { + this.router.navigateByUrl('/login'); + } + } + + public getCurrentUser(options: RequestOptions): Observable<Response> { + return this.http.get(this.loginUrl, options); + } + + public isAuthenticationChecked(): boolean { + return this.currentUser !== AuthenticationService.USER_NOT_VERIFIED; + } + + public isAuthenticated(): boolean { + return this.currentUser !== AuthenticationService.USER_NOT_VERIFIED && this.currentUser != null; + } +} http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/1ef8cd8f/metron-interface/metron-config/src/app/service/global-config.service.spec.ts ---------------------------------------------------------------------- diff --git a/metron-interface/metron-config/src/app/service/global-config.service.spec.ts b/metron-interface/metron-config/src/app/service/global-config.service.spec.ts new file mode 100644 index 0000000..f53c3f3 --- /dev/null +++ b/metron-interface/metron-config/src/app/service/global-config.service.spec.ts @@ -0,0 +1,99 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import {async, inject, TestBed} from '@angular/core/testing'; +import {MockBackend, MockConnection} from '@angular/http/testing'; +import {HttpModule, XHRBackend, Response, ResponseOptions, Http} from '@angular/http'; +import '../rxjs-operators'; +import {APP_CONFIG, METRON_REST_CONFIG} from '../app.config'; +import {IAppConfig} from '../app.config.interface'; +import {GlobalConfigService} from './global-config.service'; + +describe('GlobalConfigService', () => { + + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [HttpModule], + providers: [ + GlobalConfigService, + {provide: XHRBackend, useClass: MockBackend}, + {provide: APP_CONFIG, useValue: METRON_REST_CONFIG} + ] + }) + .compileComponents(); + })); + + it('can instantiate service when inject service', + inject([GlobalConfigService], (service: GlobalConfigService) => { + expect(service instanceof GlobalConfigService).toBe(true); + })); + + it('can instantiate service with "new"', inject([Http, APP_CONFIG], (http: Http, config: IAppConfig) => { + expect(http).not.toBeNull('http should be provided'); + let service = new GlobalConfigService(http, config); + expect(service instanceof GlobalConfigService).toBe(true, 'new service should be ok'); + })); + + + it('can provide the mockBackend as XHRBackend', + inject([XHRBackend], (backend: MockBackend) => { + expect(backend).not.toBeNull('backend should be provided'); + })); + + describe('when service functions', () => { + let globalConfigService: GlobalConfigService; + let mockBackend: MockBackend; + let globalConfig = {'field': 'value'}; + let globalConfigResponse: Response; + let deleteResponse: Response; + + beforeEach(inject([Http, XHRBackend, APP_CONFIG], (http: Http, be: MockBackend, config: IAppConfig) => { + mockBackend = be; + globalConfigService = new GlobalConfigService(http, config); + globalConfigResponse = new Response(new ResponseOptions({status: 200, body: globalConfig})); + })); + + it('post', async(inject([], () => { + mockBackend.connections.subscribe((c: MockConnection) => c.mockRespond(globalConfigResponse)); + + globalConfigService.post(globalConfig).subscribe( + result => { + expect(result).toEqual(globalConfig); + }, error => console.log(error)); + }))); + + it('get', async(inject([], () => { + mockBackend.connections.subscribe((c: MockConnection) => c.mockRespond(globalConfigResponse)); + + globalConfigService.get().subscribe( + result => { + expect(result).toEqual(globalConfig); + }, error => console.log(error)); + }))); + + it('deleteSensorParserConfigs', async(inject([], () => { + mockBackend.connections.subscribe((c: MockConnection) => c.mockRespond(deleteResponse)); + + globalConfigService.delete().subscribe(result => { + expect(result.status).toEqual(200); + }); + }))); + }); + +}); + + http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/1ef8cd8f/metron-interface/metron-config/src/app/service/global-config.service.ts ---------------------------------------------------------------------- diff --git a/metron-interface/metron-config/src/app/service/global-config.service.ts b/metron-interface/metron-config/src/app/service/global-config.service.ts new file mode 100644 index 0000000..1ed4325 --- /dev/null +++ b/metron-interface/metron-config/src/app/service/global-config.service.ts @@ -0,0 +1,75 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import {Injectable, Inject} from '@angular/core'; +import {Http, Headers, RequestOptions, Response, ResponseOptions} from '@angular/http'; +import {Observable} from 'rxjs/Observable'; +import {HttpUtil} from '../util/httpUtil'; +import {IAppConfig} from '../app.config.interface'; +import {APP_CONFIG} from '../app.config'; + +@Injectable() +export class GlobalConfigService { + url = this.config.apiEndpoint + '/global/config'; + defaultHeaders = {'Content-Type': 'application/json', 'X-Requested-With': 'XMLHttpRequest'}; + + private globalConfig = { + + }; + + constructor(private http: Http, @Inject(APP_CONFIG) private config: IAppConfig) { + this.globalConfig['solr.collection'] = 'metron'; + this.globalConfig['storm.indexingWorkers'] = 1; + this.globalConfig['storm.indexingExecutors'] = 2; + this.globalConfig['hdfs.boltBatchSize'] = 5000; + this.globalConfig['hdfs.boltFieldDelimiter'] = '|'; + this.globalConfig['hdfs.boltFileRotationSize'] = 5; + this.globalConfig['hdfs.boltCompressionCodecClass'] = 'org.apache.hadoop.io.compress.SnappyCodec'; + this.globalConfig['hdfs.indexOutput'] = '/tmp/metron/enriched'; + this.globalConfig['kafkaWriter.topic'] = 'outputTopic'; + this.globalConfig['kafkaWriter.keySerializer'] = 'org.apache.kafka.common.serialization.StringSerializer'; + this.globalConfig['kafkaWriter.valueSerializer'] = 'org.apache.kafka.common.serialization.StringSerializer'; + this.globalConfig['kafkaWriter.requestRequiredAcks'] = 1; + this.globalConfig['solrWriter.indexName'] = 'alfaalfa'; + this.globalConfig['solrWriter.shards'] = 1; + this.globalConfig['solrWriter.replicationFactor'] = 1; + this.globalConfig['solrWriter.batchSize'] = 50; + } + + public post(globalConfig: {}): Observable<{}> { + return this.http.post(this.url, globalConfig, new RequestOptions({headers: new Headers(this.defaultHeaders)})) + .map(HttpUtil.extractData) + .catch(HttpUtil.handleError); + } + + public get(): Observable<{}> { + return this.http.get(this.url , new RequestOptions({headers: new Headers(this.defaultHeaders)})) + .map(HttpUtil.extractData) + .catch(HttpUtil.handleError); + } + + public delete(): Observable<Response> { + let responseOptions = new ResponseOptions(); + responseOptions.status = 200; + let response = new Response(responseOptions); + return Observable.create(observer => { + observer.next(response); + observer.complete(); + }); + } + +} http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/1ef8cd8f/metron-interface/metron-config/src/app/service/grok-validation.service.spec.ts ---------------------------------------------------------------------- diff --git a/metron-interface/metron-config/src/app/service/grok-validation.service.spec.ts b/metron-interface/metron-config/src/app/service/grok-validation.service.spec.ts new file mode 100644 index 0000000..da45a80 --- /dev/null +++ b/metron-interface/metron-config/src/app/service/grok-validation.service.spec.ts @@ -0,0 +1,106 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import {async, inject, TestBed} from '@angular/core/testing'; +import {MockBackend, MockConnection} from '@angular/http/testing'; +import {GrokValidationService} from './grok-validation.service'; +import {GrokValidation} from '../model/grok-validation'; +import {HttpModule, XHRBackend, Response, ResponseOptions, Http} from '@angular/http'; +import '../rxjs-operators'; +import {APP_CONFIG, METRON_REST_CONFIG} from '../app.config'; +import {IAppConfig} from '../app.config.interface'; + +describe('GrokValidationService', () => { + + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [HttpModule], + providers: [ + GrokValidationService, + {provide: XHRBackend, useClass: MockBackend}, + {provide: APP_CONFIG, useValue: METRON_REST_CONFIG} + ] + }) + .compileComponents(); + })); + + it('can instantiate service when inject service', + inject([GrokValidationService], (service: GrokValidationService) => { + expect(service instanceof GrokValidationService).toBe(true); + })); + + it('can instantiate service with "new"', inject([Http, APP_CONFIG], (http: Http, config: IAppConfig) => { + expect(http).not.toBeNull('http should be provided'); + let service = new GrokValidationService(http, config); + expect(service instanceof GrokValidationService).toBe(true, 'new service should be ok'); + })); + + + it('can provide the mockBackend as XHRBackend', + inject([XHRBackend], (backend: MockBackend) => { + expect(backend).not.toBeNull('backend should be provided'); + })); + + describe('when service functions', () => { + let grokValidationService: GrokValidationService; + let mockBackend: MockBackend; + let grokValidation = new GrokValidation(); + grokValidation.statement = 'statement'; + grokValidation.sampleData = 'sampleData'; + grokValidation.results = {'results': 'results'}; + let grokList = ['pattern']; + let grokStatement = 'grok statement'; + let grokValidationResponse: Response; + let grokListResponse: Response; + let grokGetStatementResponse: Response; + + beforeEach(inject([Http, XHRBackend, APP_CONFIG], (http: Http, be: MockBackend, config: IAppConfig) => { + mockBackend = be; + grokValidationService = new GrokValidationService(http, config); + grokValidationResponse = new Response(new ResponseOptions({status: 200, body: grokValidation})); + grokListResponse = new Response(new ResponseOptions({status: 200, body: grokList})); + grokGetStatementResponse = new Response(new ResponseOptions({status: 200, body: grokStatement})); + })); + + it('validate', async(inject([], () => { + mockBackend.connections.subscribe((c: MockConnection) => c.mockRespond(grokValidationResponse)); + + grokValidationService.validate(grokValidation).subscribe( + result => { + expect(result).toEqual(grokValidation); + }, error => console.log(error)); + }))); + + it('list', async(inject([], () => { + mockBackend.connections.subscribe((c: MockConnection) => c.mockRespond(grokListResponse)); + grokValidationService.list().subscribe( + results => { + expect(results).toEqual(grokList); + }, error => console.log(error)); + }))); + + it('getStatement', async(inject([], () => { + mockBackend.connections.subscribe((c: MockConnection) => c.mockRespond(grokGetStatementResponse)); + grokValidationService.getStatement('/path').subscribe( + results => { + expect(results).toEqual(grokStatement); + }, error => console.log(error)); + }))); + }); + + +}); http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/1ef8cd8f/metron-interface/metron-config/src/app/service/grok-validation.service.ts ---------------------------------------------------------------------- diff --git a/metron-interface/metron-config/src/app/service/grok-validation.service.ts b/metron-interface/metron-config/src/app/service/grok-validation.service.ts new file mode 100644 index 0000000..bcdce82 --- /dev/null +++ b/metron-interface/metron-config/src/app/service/grok-validation.service.ts @@ -0,0 +1,56 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import {Injectable, Inject} from '@angular/core'; +import {Http, Headers, RequestOptions, URLSearchParams} from '@angular/http'; +import {Observable} from 'rxjs/Observable'; +import {GrokValidation} from '../model/grok-validation'; +import {HttpUtil} from '../util/httpUtil'; +import {IAppConfig} from '../app.config.interface'; +import {APP_CONFIG} from '../app.config'; + +@Injectable() +export class GrokValidationService { + url = this.config.apiEndpoint + '/grok'; + defaultHeaders = {'Content-Type': 'application/json', 'X-Requested-With': 'XMLHttpRequest'}; + + constructor(private http: Http, @Inject(APP_CONFIG) private config: IAppConfig) { + + } + + public validate(grokValidation: GrokValidation): Observable<GrokValidation> { + return this.http.post(this.url + '/validate', JSON.stringify(grokValidation), + new RequestOptions({headers: new Headers(this.defaultHeaders)})) + .map(HttpUtil.extractData) + .catch(HttpUtil.handleError); + } + + public list(): Observable<string[]> { + return this.http.get(this.url + '/list', new RequestOptions({headers: new Headers(this.defaultHeaders)})) + .map(HttpUtil.extractData) + .catch(HttpUtil.handleError); + } + + public getStatement(path: string): Observable<string> { + let params: URLSearchParams = new URLSearchParams(); + params.set('path', path); + return this.http.get(this.url + '/get/statement', new RequestOptions({headers: new Headers(this.defaultHeaders), search: params})) + .map(HttpUtil.extractString) + .catch(HttpUtil.handleError); + } + +} http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/1ef8cd8f/metron-interface/metron-config/src/app/service/hdfs.service.spec.ts ---------------------------------------------------------------------- diff --git a/metron-interface/metron-config/src/app/service/hdfs.service.spec.ts b/metron-interface/metron-config/src/app/service/hdfs.service.spec.ts new file mode 100644 index 0000000..16196ab --- /dev/null +++ b/metron-interface/metron-config/src/app/service/hdfs.service.spec.ts @@ -0,0 +1,110 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import {async, inject, TestBed} from '@angular/core/testing'; +import {MockBackend, MockConnection} from '@angular/http/testing'; +import {HttpModule, XHRBackend, Response, ResponseOptions, Http} from '@angular/http'; +import '../rxjs-operators'; +import {APP_CONFIG, METRON_REST_CONFIG} from '../app.config'; +import {IAppConfig} from '../app.config.interface'; +import {HdfsService} from './hdfs.service'; + +describe('HdfsService', () => { + + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [HttpModule], + providers: [ + HdfsService, + {provide: XHRBackend, useClass: MockBackend}, + {provide: APP_CONFIG, useValue: METRON_REST_CONFIG} + ] + }) + .compileComponents(); + })); + + it('can instantiate service when inject service', + inject([HdfsService], (service: HdfsService) => { + expect(service instanceof HdfsService).toBe(true); + })); + + it('can instantiate service with "new"', inject([Http, APP_CONFIG], (http: Http, config: IAppConfig) => { + expect(http).not.toBeNull('http should be provided'); + let service = new HdfsService(http, config); + expect(service instanceof HdfsService).toBe(true, 'new service should be ok'); + })); + + + it('can provide the mockBackend as XHRBackend', + inject([XHRBackend], (backend: MockBackend) => { + expect(backend).not.toBeNull('backend should be provided'); + })); + + describe('when service functions', () => { + let hdfsService: HdfsService; + let mockBackend: MockBackend; + let fileList = ['file1', 'file2']; + let contents = 'file contents'; + let listResponse: Response; + let readResponse: Response; + let postResponse: Response; + let deleteResponse: Response; + + beforeEach(inject([Http, XHRBackend, APP_CONFIG], (http: Http, be: MockBackend, config: IAppConfig) => { + mockBackend = be; + hdfsService = new HdfsService(http, config); + listResponse = new Response(new ResponseOptions({status: 200, body: fileList})); + readResponse = new Response(new ResponseOptions({status: 200, body: contents})); + postResponse = new Response(new ResponseOptions({status: 200})); + deleteResponse = new Response(new ResponseOptions({status: 200})); + })); + + it('list', async(inject([], () => { + mockBackend.connections.subscribe((c: MockConnection) => c.mockRespond(listResponse)); + hdfsService.list('/path').subscribe( + result => { + expect(result).toEqual(fileList); + }, error => console.log(error)); + }))); + + it('read', async(inject([], () => { + mockBackend.connections.subscribe((c: MockConnection) => c.mockRespond(readResponse)); + hdfsService.read('/path').subscribe( + result => { + expect(result).toEqual(contents); + }, error => console.log(error)); + }))); + + it('post', async(inject([], () => { + mockBackend.connections.subscribe((c: MockConnection) => c.mockRespond(postResponse)); + hdfsService.post('/path', contents).subscribe( + result => { + expect(result.status).toEqual(200); + }, error => console.log(error)); + }))); + + it('deleteFile', async(inject([], () => { + mockBackend.connections.subscribe((c: MockConnection) => c.mockRespond(deleteResponse)); + hdfsService.deleteFile('/path').subscribe( + result => { + expect(result.status).toEqual(200); + }, error => console.log(error)); + }))); + }); + + +}); http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/1ef8cd8f/metron-interface/metron-config/src/app/service/hdfs.service.ts ---------------------------------------------------------------------- diff --git a/metron-interface/metron-config/src/app/service/hdfs.service.ts b/metron-interface/metron-config/src/app/service/hdfs.service.ts new file mode 100644 index 0000000..4e4b808 --- /dev/null +++ b/metron-interface/metron-config/src/app/service/hdfs.service.ts @@ -0,0 +1,63 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import {Injectable, Inject} from '@angular/core'; +import {Http, Headers, RequestOptions, Response, URLSearchParams} from '@angular/http'; +import {Observable} from 'rxjs/Observable'; +import {HttpUtil} from '../util/httpUtil'; +import {IAppConfig} from '../app.config.interface'; +import {APP_CONFIG} from '../app.config'; + +@Injectable() +export class HdfsService { + url = this.config.apiEndpoint + '/hdfs'; + defaultHeaders = {'Content-Type': 'application/json', 'X-Requested-With': 'XMLHttpRequest'}; + + constructor(private http: Http, @Inject(APP_CONFIG) private config: IAppConfig) { + } + + public list(path: string): Observable<string[]> { + let params: URLSearchParams = new URLSearchParams(); + params.set('path', path); + return this.http.get(this.url + '/list', new RequestOptions({headers: new Headers(this.defaultHeaders), search: params})) + .map(HttpUtil.extractData) + .catch(HttpUtil.handleError); + } + + public read(path: string): Observable<string> { + let params: URLSearchParams = new URLSearchParams(); + params.set('path', path); + return this.http.get(this.url , new RequestOptions({headers: new Headers(this.defaultHeaders), search: params})) + .map(HttpUtil.extractString) + .catch(HttpUtil.handleError); + } + + public post(path: string, contents: string): Observable<Response> { + let params: URLSearchParams = new URLSearchParams(); + params.set('path', path); + return this.http.post(this.url, contents, new RequestOptions({headers: new Headers(this.defaultHeaders), search: params})) + .catch(HttpUtil.handleError); + } + + public deleteFile(path: string): Observable<Response> { + let params: URLSearchParams = new URLSearchParams(); + params.set('path', path); + return this.http.delete(this.url, new RequestOptions({headers: new Headers(this.defaultHeaders), search: params})) + .catch(HttpUtil.handleError); + } + +} http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/1ef8cd8f/metron-interface/metron-config/src/app/service/kafka.service.spec.ts ---------------------------------------------------------------------- diff --git a/metron-interface/metron-config/src/app/service/kafka.service.spec.ts b/metron-interface/metron-config/src/app/service/kafka.service.spec.ts new file mode 100644 index 0000000..e6f1d7f --- /dev/null +++ b/metron-interface/metron-config/src/app/service/kafka.service.spec.ts @@ -0,0 +1,114 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import {async, inject, TestBed} from '@angular/core/testing'; +import {MockBackend, MockConnection} from '@angular/http/testing'; +import {KafkaService} from './kafka.service'; +import {KafkaTopic} from '../model/kafka-topic'; +import {HttpModule, XHRBackend, Response, ResponseOptions, Http} from '@angular/http'; +import '../rxjs-operators'; +import {APP_CONFIG, METRON_REST_CONFIG} from '../app.config'; +import {IAppConfig} from '../app.config.interface'; + +describe('KafkaService', () => { + + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [HttpModule], + providers: [ + KafkaService, + {provide: XHRBackend, useClass: MockBackend}, + {provide: APP_CONFIG, useValue: METRON_REST_CONFIG} + ] + }) + .compileComponents(); + })); + + it('can instantiate service when inject service', + inject([KafkaService], (service: KafkaService) => { + expect(service instanceof KafkaService).toBe(true); + })); + + it('can instantiate service with "new"', inject([Http, APP_CONFIG], (http: Http, config: IAppConfig) => { + expect(http).not.toBeNull('http should be provided'); + let service = new KafkaService(http, config); + expect(service instanceof KafkaService).toBe(true, 'new service should be ok'); + })); + + it('can provide the mockBackend as XHRBackend', + inject([XHRBackend], (backend: MockBackend) => { + expect(backend).not.toBeNull('backend should be provided'); + })); + + describe('when service functions', () => { + let kafkaService: KafkaService; + let mockBackend: MockBackend; + let kafkaTopic = new KafkaTopic(); + kafkaTopic.name = 'bro'; + kafkaTopic.numPartitions = 1; + kafkaTopic.replicationFactor = 1; + let sampleMessage = 'sample message'; + let kafkaResponse: Response; + let kafkaListResponse: Response; + let sampleMessageResponse: Response; + + beforeEach(inject([Http, XHRBackend, APP_CONFIG], (http: Http, be: MockBackend, config: IAppConfig) => { + mockBackend = be; + kafkaService = new KafkaService(http, config); + kafkaResponse = new Response(new ResponseOptions({status: 200, body: kafkaTopic})); + kafkaListResponse = new Response(new ResponseOptions({status: 200, body: [kafkaTopic]})); + sampleMessageResponse = new Response(new ResponseOptions({status: 200, body: sampleMessage})); + })); + + it('post', async(inject([], () => { + mockBackend.connections.subscribe((c: MockConnection) => c.mockRespond(kafkaResponse)); + + kafkaService.post(kafkaTopic).subscribe( + result => { + expect(result).toEqual(kafkaTopic); + }, error => console.log(error)); + }))); + + it('get', async(inject([], () => { + mockBackend.connections.subscribe((c: MockConnection) => c.mockRespond(kafkaResponse)); + + kafkaService.get('bro').subscribe( + result => { + expect(result).toEqual(kafkaTopic); + }, error => console.log(error)); + }))); + + it('list', async(inject([], () => { + mockBackend.connections.subscribe((c: MockConnection) => c.mockRespond(kafkaListResponse)); + + kafkaService.list().subscribe( + result => { + expect(result).toEqual([kafkaTopic]); + }, error => console.log(error)); + }))); + + it('sample', async(inject([], () => { + mockBackend.connections.subscribe((c: MockConnection) => c.mockRespond(sampleMessageResponse)); + kafkaService.sample('bro').subscribe( + result => { + expect(result).toEqual(sampleMessage); + }, error => console.log(error)); + }))); + }); + +}); + http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/1ef8cd8f/metron-interface/metron-config/src/app/service/kafka.service.ts ---------------------------------------------------------------------- diff --git a/metron-interface/metron-config/src/app/service/kafka.service.ts b/metron-interface/metron-config/src/app/service/kafka.service.ts new file mode 100644 index 0000000..ac02366 --- /dev/null +++ b/metron-interface/metron-config/src/app/service/kafka.service.ts @@ -0,0 +1,59 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import {Injectable, Inject} from '@angular/core'; +import {Http, Headers, RequestOptions} from '@angular/http'; +import {Observable} from 'rxjs/Observable'; +import {KafkaTopic} from '../model/kafka-topic'; +import {HttpUtil} from '../util/httpUtil'; +import {IAppConfig} from '../app.config.interface'; +import {APP_CONFIG} from '../app.config'; + +@Injectable() +export class KafkaService { + url = this.config.apiEndpoint + '/kafka/topic'; + defaultHeaders = {'Content-Type': 'application/json', 'X-Requested-With': 'XMLHttpRequest'}; + + constructor(private http: Http, @Inject(APP_CONFIG) private config: IAppConfig) { + + } + + public post(kafkaTopic: KafkaTopic): Observable<KafkaTopic> { + return this.http.post(this.url, JSON.stringify(kafkaTopic), new RequestOptions({headers: new Headers(this.defaultHeaders)})) + .map(HttpUtil.extractData) + .catch(HttpUtil.handleError); + } + + public get(name: string): Observable<KafkaTopic> { + return this.http.get(this.url + '/' + name, new RequestOptions({headers: new Headers(this.defaultHeaders)})) + .map(HttpUtil.extractData) + .catch(HttpUtil.handleError); + } + + public list(): Observable<string[]> { + return this.http.get(this.url, new RequestOptions({headers: new Headers(this.defaultHeaders)})) + .map(HttpUtil.extractData) + .catch(HttpUtil.handleError); + } + + public sample(name: string): Observable<string> { + return this.http.get(this.url + '/' + name + '/sample', new RequestOptions({headers: new Headers(this.defaultHeaders)})) + .map(HttpUtil.extractString) + .catch(HttpUtil.handleError); + } + +} http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/1ef8cd8f/metron-interface/metron-config/src/app/service/sensor-enrichment-config.service.spec.ts ---------------------------------------------------------------------- diff --git a/metron-interface/metron-config/src/app/service/sensor-enrichment-config.service.spec.ts b/metron-interface/metron-config/src/app/service/sensor-enrichment-config.service.spec.ts new file mode 100644 index 0000000..89863ee --- /dev/null +++ b/metron-interface/metron-config/src/app/service/sensor-enrichment-config.service.spec.ts @@ -0,0 +1,144 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import {async, inject, TestBed} from '@angular/core/testing'; +import {MockBackend, MockConnection} from '@angular/http/testing'; +import {SensorEnrichmentConfigService} from './sensor-enrichment-config.service'; +import {SensorEnrichmentConfig, EnrichmentConfig} from '../model/sensor-enrichment-config'; +import {HttpModule, XHRBackend, Response, ResponseOptions, Http} from '@angular/http'; +import '../rxjs-operators'; +import {METRON_REST_CONFIG, APP_CONFIG} from '../app.config'; +import {IAppConfig} from '../app.config.interface'; + +describe('SensorEnrichmentConfigService', () => { + + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [HttpModule], + providers: [ + SensorEnrichmentConfigService, + {provide: XHRBackend, useClass: MockBackend}, + {provide: APP_CONFIG, useValue: METRON_REST_CONFIG} + ] + }) + .compileComponents(); + })); + + it('can instantiate service when inject service', + inject([SensorEnrichmentConfigService], (service: SensorEnrichmentConfigService) => { + expect(service instanceof SensorEnrichmentConfigService).toBe(true); + })); + + it('can instantiate service with "new"', inject([Http, APP_CONFIG], (http: Http, config: IAppConfig) => { + expect(http).not.toBeNull('http should be provided'); + let service = new SensorEnrichmentConfigService(http, config); + expect(service instanceof SensorEnrichmentConfigService).toBe(true, 'new service should be ok'); + })); + + + it('can provide the mockBackend as XHRBackend', + inject([XHRBackend], (backend: MockBackend) => { + expect(backend).not.toBeNull('backend should be provided'); + })); + + describe('when service functions', () => { + let sensorEnrichmentConfigService: SensorEnrichmentConfigService; + let mockBackend: MockBackend; + let sensorEnrichmentConfig1 = new SensorEnrichmentConfig(); + let enrichmentConfig1 = new EnrichmentConfig(); + enrichmentConfig1.fieldMap = {'geo': ['ip_dst_addr'], 'host': ['ip_dst_addr']}; + sensorEnrichmentConfig1.enrichment.fieldMap = enrichmentConfig1; + let sensorEnrichmentConfig2 = new SensorEnrichmentConfig(); + let enrichmentConfig2 = new EnrichmentConfig(); + enrichmentConfig1.fieldMap = {'whois': ['ip_dst_addr'], 'host': ['ip_src_addr']}; + sensorEnrichmentConfig2.enrichment = enrichmentConfig2; + let availableEnrichments: string[] = ['geo', 'host', 'whois']; + let availableThreatTriageAggregators: string[] = ['MAX', 'MIN', 'SUM', 'MEAN', 'POSITIVE_MEAN']; + let sensorEnrichmentConfigResponse: Response; + let sensorEnrichmentConfigsResponse: Response; + let availableEnrichmentsResponse: Response; + let availableThreatTriageAggregatorsResponse: Response; + let deleteResponse: Response; + + beforeEach(inject([Http, XHRBackend, APP_CONFIG], (http: Http, be: MockBackend, config: IAppConfig) => { + mockBackend = be; + sensorEnrichmentConfigService = new SensorEnrichmentConfigService(http, config); + sensorEnrichmentConfigResponse = new Response(new ResponseOptions({status: 200, body: sensorEnrichmentConfig1})); + sensorEnrichmentConfigsResponse = new Response(new ResponseOptions({status: 200, body: [sensorEnrichmentConfig1, + sensorEnrichmentConfig2]})); + availableEnrichmentsResponse = new Response(new ResponseOptions({status: 200, body: availableEnrichments})); + availableThreatTriageAggregatorsResponse = new Response(new ResponseOptions({status: 200, body: availableThreatTriageAggregators})); + deleteResponse = new Response(new ResponseOptions({status: 200})); + })); + + it('post', async(inject([], () => { + mockBackend.connections.subscribe((c: MockConnection) => c.mockRespond(sensorEnrichmentConfigResponse)); + + sensorEnrichmentConfigService.post('bro', sensorEnrichmentConfig1).subscribe( + result => { + expect(result).toEqual(sensorEnrichmentConfig1); + }, error => console.log(error)); + }))); + + it('get', async(inject([], () => { + mockBackend.connections.subscribe((c: MockConnection) => c.mockRespond(sensorEnrichmentConfigResponse)); + + sensorEnrichmentConfigService.get('bro').subscribe( + result => { + expect(result).toEqual(sensorEnrichmentConfig1); + }, error => console.log(error)); + }))); + + it('getAll', async(inject([], () => { + mockBackend.connections.subscribe((c: MockConnection) => c.mockRespond(sensorEnrichmentConfigsResponse)); + + sensorEnrichmentConfigService.getAll().subscribe( + results => { + expect(results).toEqual([sensorEnrichmentConfig1, sensorEnrichmentConfig2]); + }, error => console.log(error)); + }))); + + it('getAvailableEnrichments', async(inject([], () => { + mockBackend.connections.subscribe((c: MockConnection) => c.mockRespond(availableEnrichmentsResponse)); + + sensorEnrichmentConfigService.getAvailableEnrichments().subscribe( + results => { + expect(results).toEqual(availableEnrichments); + }, error => console.log(error)); + }))); + + it('getAvailableThreatTriageAggregators', async(inject([], () => { + mockBackend.connections.subscribe((c: MockConnection) => c.mockRespond(availableThreatTriageAggregatorsResponse)); + + sensorEnrichmentConfigService.getAvailableThreatTriageAggregators().subscribe( + results => { + expect(results).toEqual(availableThreatTriageAggregators); + }, error => console.log(error)); + }))); + + it('deleteSensorEnrichments', async(inject([], () => { + mockBackend.connections.subscribe((c: MockConnection) => c.mockRespond(deleteResponse)); + + sensorEnrichmentConfigService.deleteSensorEnrichments('bro').subscribe(result => { + expect(result.status).toEqual(200); + }); + }))); + }); + +}); + + http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/1ef8cd8f/metron-interface/metron-config/src/app/service/sensor-enrichment-config.service.ts ---------------------------------------------------------------------- diff --git a/metron-interface/metron-config/src/app/service/sensor-enrichment-config.service.ts b/metron-interface/metron-config/src/app/service/sensor-enrichment-config.service.ts new file mode 100644 index 0000000..90c314b --- /dev/null +++ b/metron-interface/metron-config/src/app/service/sensor-enrichment-config.service.ts @@ -0,0 +1,71 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import {Injectable, Inject} from '@angular/core'; +import {Http, Headers, RequestOptions, Response} from '@angular/http'; +import {Observable} from 'rxjs/Observable'; +import {SensorEnrichmentConfig} from '../model/sensor-enrichment-config'; +import {HttpUtil} from '../util/httpUtil'; +import {IAppConfig} from '../app.config.interface'; +import {APP_CONFIG} from '../app.config'; + +@Injectable() +export class SensorEnrichmentConfigService { + url = this.config.apiEndpoint + '/sensor/enrichment/config'; + defaultHeaders = {'Content-Type': 'application/json', 'X-Requested-With': 'XMLHttpRequest'}; + + constructor(private http: Http, @Inject(APP_CONFIG) private config: IAppConfig) { + } + + public post(name: string, sensorEnrichmentConfig: SensorEnrichmentConfig): Observable<SensorEnrichmentConfig> { + return this.http.post(this.url + '/' + name, JSON.stringify(sensorEnrichmentConfig), + new RequestOptions({headers: new Headers(this.defaultHeaders)})) + .map(HttpUtil.extractData) + .catch(HttpUtil.handleError); + } + + public get(name: string): Observable<SensorEnrichmentConfig> { + return this.http.get(this.url + '/' + name, new RequestOptions({headers: new Headers(this.defaultHeaders)})) + .map(HttpUtil.extractData) + .catch(HttpUtil.handleError); + } + + public getAll(): Observable<SensorEnrichmentConfig[]> { + return this.http.get(this.url, new RequestOptions({headers: new Headers(this.defaultHeaders)})) + .map(HttpUtil.extractData) + .catch(HttpUtil.handleError); + } + + public deleteSensorEnrichments(name: string): Observable<Response> { + return this.http.delete(this.url + '/' + name, new RequestOptions({headers: new Headers(this.defaultHeaders)})) + .catch(HttpUtil.handleError); + } + + public getAvailableEnrichments(): Observable<string[]> { + return this.http.get(this.url + '/list/available/enrichments', new RequestOptions({headers: new Headers(this.defaultHeaders)})) + .map(HttpUtil.extractData) + .catch(HttpUtil.handleError); + } + + public getAvailableThreatTriageAggregators(): Observable<string[]> { + return this.http.get(this.url + '/list/available/threat/triage/aggregators', + new RequestOptions({headers: new Headers(this.defaultHeaders)})) + .map(HttpUtil.extractData) + .catch(HttpUtil.handleError); + } + +} http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/1ef8cd8f/metron-interface/metron-config/src/app/service/sensor-indexing-config.service.spec.ts ---------------------------------------------------------------------- diff --git a/metron-interface/metron-config/src/app/service/sensor-indexing-config.service.spec.ts b/metron-interface/metron-config/src/app/service/sensor-indexing-config.service.spec.ts new file mode 100644 index 0000000..3640162 --- /dev/null +++ b/metron-interface/metron-config/src/app/service/sensor-indexing-config.service.spec.ts @@ -0,0 +1,118 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import {async, inject, TestBed} from '@angular/core/testing'; +import {MockBackend, MockConnection} from '@angular/http/testing'; +import {HttpModule, XHRBackend, Response, ResponseOptions, Http} from '@angular/http'; +import '../rxjs-operators'; +import {METRON_REST_CONFIG, APP_CONFIG} from '../app.config'; +import {IAppConfig} from '../app.config.interface'; +import {SensorIndexingConfigService} from './sensor-indexing-config.service'; +import {IndexingConfigurations} from '../model/sensor-indexing-config'; + +describe('SensorIndexingConfigService', () => { + + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [HttpModule], + providers: [ + SensorIndexingConfigService, + {provide: XHRBackend, useClass: MockBackend}, + {provide: APP_CONFIG, useValue: METRON_REST_CONFIG} + ] + }) + .compileComponents(); + })); + + it('can instantiate service when inject service', + inject([SensorIndexingConfigService], (service: SensorIndexingConfigService) => { + expect(service instanceof SensorIndexingConfigService).toBe(true); + })); + + it('can instantiate service with "new"', inject([Http, APP_CONFIG], (http: Http, config: IAppConfig) => { + expect(http).not.toBeNull('http should be provided'); + let service = new SensorIndexingConfigService(http, config); + expect(service instanceof SensorIndexingConfigService).toBe(true, 'new service should be ok'); + })); + + + it('can provide the mockBackend as XHRBackend', + inject([XHRBackend], (backend: MockBackend) => { + expect(backend).not.toBeNull('backend should be provided'); + })); + + describe('when service functions', () => { + let sensorIndexingConfigService: SensorIndexingConfigService; + let mockBackend: MockBackend; + let sensorIndexingConfig1 = new IndexingConfigurations(); + sensorIndexingConfig1.hdfs.index = 'squid'; + sensorIndexingConfig1.hdfs.batchSize = 1; + let sensorIndexingConfig2 = new IndexingConfigurations(); + sensorIndexingConfig2.hdfs.index = 'yaf'; + sensorIndexingConfig2.hdfs.batchSize = 2; + let sensorIndexingConfigResponse: Response; + let sensorIndexingConfigsResponse: Response; + let deleteResponse: Response; + + beforeEach(inject([Http, XHRBackend, APP_CONFIG], (http: Http, be: MockBackend, config: IAppConfig) => { + mockBackend = be; + sensorIndexingConfigService = new SensorIndexingConfigService(http, config); + sensorIndexingConfigResponse = new Response(new ResponseOptions({status: 200, body: sensorIndexingConfig1})); + sensorIndexingConfigsResponse = new Response(new ResponseOptions({status: 200, body: [sensorIndexingConfig1, + sensorIndexingConfig2]})); + deleteResponse = new Response(new ResponseOptions({status: 200})); + })); + + it('post', async(inject([], () => { + mockBackend.connections.subscribe((c: MockConnection) => c.mockRespond(sensorIndexingConfigResponse)); + + sensorIndexingConfigService.post('squid', sensorIndexingConfig1).subscribe( + result => { + expect(result).toEqual(sensorIndexingConfig1); + }, error => console.log(error)); + }))); + + it('get', async(inject([], () => { + mockBackend.connections.subscribe((c: MockConnection) => c.mockRespond(sensorIndexingConfigResponse)); + + sensorIndexingConfigService.get('squid').subscribe( + result => { + expect(result).toEqual(sensorIndexingConfig1); + }, error => console.log(error)); + }))); + + it('getAll', async(inject([], () => { + mockBackend.connections.subscribe((c: MockConnection) => c.mockRespond(sensorIndexingConfigsResponse)); + + sensorIndexingConfigService.getAll().subscribe( + results => { + expect(results).toEqual([sensorIndexingConfig1, sensorIndexingConfig2]); + }, error => console.log(error)); + }))); + + it('deleteSensorEnrichments', async(inject([], () => { + mockBackend.connections.subscribe((c: MockConnection) => c.mockRespond(deleteResponse)); + + sensorIndexingConfigService.deleteSensorIndexingConfig('squid').subscribe(result => { + expect(result.status).toEqual(200); + }); + }))); + }); + +}); + +