METRON-1712 PCAP UI - Input validation (tiborm via merrimanr) closes apache/metron#1142
Project: http://git-wip-us.apache.org/repos/asf/metron/repo Commit: http://git-wip-us.apache.org/repos/asf/metron/commit/52de126f Tree: http://git-wip-us.apache.org/repos/asf/metron/tree/52de126f Diff: http://git-wip-us.apache.org/repos/asf/metron/diff/52de126f Branch: refs/heads/master Commit: 52de126fc746181d6aac8ce002d6b814fd1e6cb5 Parents: afb9607 Author: tiborm <tibor.mel...@gmail.com> Authored: Wed Aug 8 16:36:35 2018 -0500 Committer: rmerriman <merrim...@gmail.com> Committed: Wed Aug 8 16:36:35 2018 -0500 ---------------------------------------------------------------------- .../src/app/pcap/model/pcap.mock.ts | 4 +- .../src/app/pcap/model/pcap.request.ts | 4 +- .../pcap-filters/pcap-filters.component.html | 12 +- .../pcap-filters/pcap-filters.component.spec.ts | 228 ++++++++++++++++++- .../pcap/pcap-filters/pcap-filters.component.ts | 5 + 5 files changed, 232 insertions(+), 21 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/metron/blob/52de126f/metron-interface/metron-alerts/src/app/pcap/model/pcap.mock.ts ---------------------------------------------------------------------- diff --git a/metron-interface/metron-alerts/src/app/pcap/model/pcap.mock.ts b/metron-interface/metron-alerts/src/app/pcap/model/pcap.mock.ts index c867fe9..bf02da8 100644 --- a/metron-interface/metron-alerts/src/app/pcap/model/pcap.mock.ts +++ b/metron-interface/metron-alerts/src/app/pcap/model/pcap.mock.ts @@ -22,9 +22,9 @@ export const fakePcapRequest = { startTimeMs: 0, endTimeMs: 0, ipSrcAddr: '0.0.0.0', - ipSrcPort: '80', + ipSrcPort: 80, ipDstAddr: '0.0.0.0', - ipDstPort: '80', + ipDstPort: 80, protocol: '*', packetFilter: '*', includeReverse: false http://git-wip-us.apache.org/repos/asf/metron/blob/52de126f/metron-interface/metron-alerts/src/app/pcap/model/pcap.request.ts ---------------------------------------------------------------------- diff --git a/metron-interface/metron-alerts/src/app/pcap/model/pcap.request.ts b/metron-interface/metron-alerts/src/app/pcap/model/pcap.request.ts index 91c2287..d00a6ac 100644 --- a/metron-interface/metron-alerts/src/app/pcap/model/pcap.request.ts +++ b/metron-interface/metron-alerts/src/app/pcap/model/pcap.request.ts @@ -20,9 +20,9 @@ export class PcapRequest { startTimeMs: number = 0; endTimeMs: number = 150000000000000000; ipSrcAddr: string = ''; - ipSrcPort: string = ''; + ipSrcPort: number = 0; ipDstAddr: string = ''; - ipDstPort: string = ''; + ipDstPort: number = 0; protocol: string = ''; packetFilter: string = ''; includeReverse: boolean = false; http://git-wip-us.apache.org/repos/asf/metron/blob/52de126f/metron-interface/metron-alerts/src/app/pcap/pcap-filters/pcap-filters.component.html ---------------------------------------------------------------------- diff --git a/metron-interface/metron-alerts/src/app/pcap/pcap-filters/pcap-filters.component.html b/metron-interface/metron-alerts/src/app/pcap/pcap-filters/pcap-filters.component.html index f4133df..039307a 100644 --- a/metron-interface/metron-alerts/src/app/pcap/pcap-filters/pcap-filters.component.html +++ b/metron-interface/metron-alerts/src/app/pcap/pcap-filters/pcap-filters.component.html @@ -24,21 +24,21 @@ <div class="form-group"> <label for="ipSrcAddr">IP Source Address</label> - <input name="ipSrcAddr" #ipSrcAddr="ngModel" class="form-control" pattern="^(?:\d{1,3}\.){3}\d{1,3}(.\d{1,2})?$" [(ngModel)]="model.ipSrcAddr"> + <input name="ipSrcAddr" #ipSrcAddr="ngModel" class="form-control" pattern="^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(\.|$)){4}$" [(ngModel)]="model.ipSrcAddr" data-qe-id="ip-src-addr"> </div> - + <div class="form-group"> <label for="ipSrcPort">IP Source Port</label> - <input name="ipSrcPort" #ipSrcPort="ngModel" class="form-control" type="number" [(ngModel)]="model.ipSrcPort"> + <input name="ipSrcPort" class="form-control" pattern="^([0-9]{1,4}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])$" [(ngModel)]="ipSrcPort" data-qe-id="ip-src-port"> </div> <div class="form-group"><label for="ipDstAddr">IP Dest Address</label> - <input name="ipDstAddr" #ipDstAddr="ngModel" class="form-control" pattern="^(?:\d{1,3}\.){3}\d{1,3}(.\d{1,2})?$" [(ngModel)]="model.ipDstAddr"> + <input name="ipDstAddr" #ipDstAddr="ngModel" class="form-control" pattern="^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(\.|$)){4}$" [(ngModel)]="model.ipDstAddr" data-qe-id="ip-dst-addr"> </div> <div class="form-group"> <label for="ipDstPort">IP Dest Port</label> - <input id="ipDstPort" name="ipDstPort" #ipDstPort="ngModel" class="form-control" type="number" [(ngModel)]="model.ipDstPort"> + <input id="ipDstPort" name="ipDstPort" class="form-control" pattern="^([0-9]{1,4}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])$" [(ngModel)]="ipDstPort" data-qe-id="ip-dest-port"> </div> <div class="form-group"> @@ -57,6 +57,6 @@ </div> <div class="form-group"> - <button type="submit" [ngClass]="{'disabled':!f.form.valid || queryRunning}" class="btn btn-primary btn-search" [disabled]="!f.form.valid || queryRunning"></button> + <button type="submit" [ngClass]="{'disabled':!f.form.valid || queryRunning}" class="btn btn-primary btn-search" [disabled]="!f.form.valid || queryRunning" data-qe-id="submit-button"></button> </div> </form> http://git-wip-us.apache.org/repos/asf/metron/blob/52de126f/metron-interface/metron-alerts/src/app/pcap/pcap-filters/pcap-filters.component.spec.ts ---------------------------------------------------------------------- diff --git a/metron-interface/metron-alerts/src/app/pcap/pcap-filters/pcap-filters.component.spec.ts b/metron-interface/metron-alerts/src/app/pcap/pcap-filters/pcap-filters.component.spec.ts index c0f9c3b..4336b22 100644 --- a/metron-interface/metron-alerts/src/app/pcap/pcap-filters/pcap-filters.component.spec.ts +++ b/metron-interface/metron-alerts/src/app/pcap/pcap-filters/pcap-filters.component.spec.ts @@ -16,20 +16,19 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { async, ComponentFixture, TestBed, fakeAsync, tick } from '@angular/core/testing'; +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { By } from '@angular/platform-browser'; import { PcapFiltersComponent } from './pcap-filters.component'; import { FormsModule } from '../../../../node_modules/@angular/forms'; -import { Component, Input, Output, EventEmitter } from '@angular/core'; +import { Component, Input, Output, EventEmitter, DebugElement } from '@angular/core'; import { PcapRequest } from '../model/pcap.request'; -import { emit } from 'cluster'; @Component({ selector: 'app-date-picker', template: '<input type="text" [(value)]="date">', }) -class FakeDatePicker { +class FakeDatePickerComponent { @Input() date: string; @Output() dateChange = new EventEmitter<string>(); } @@ -44,7 +43,7 @@ describe('PcapFiltersComponent', () => { FormsModule ], declarations: [ - FakeDatePicker, + FakeDatePickerComponent, PcapFiltersComponent, ] }) @@ -86,13 +85,21 @@ describe('PcapFiltersComponent', () => { expect(component.model.ipSrcAddr).toBe('192.168.0.1'); }); - it('IP Source Port should be bound to the model', () => { + it('IP Source Port should be bound to the property', () => { let input: HTMLInputElement = fixture.nativeElement.querySelector('[name="ipSrcPort"]'); input.value = '9345'; input.dispatchEvent(new Event('input')); fixture.detectChanges(); - expect(component.model.ipSrcPort).toBe(9345); + expect(component.ipSrcPort).toBe('9345'); + }); + + it('IP Source Port should be converted to number on submit', () => { + component.ipSrcPort = '42'; + component.search.emit = (model: PcapRequest) => { + expect(model.ipSrcPort).toBe(42); + }; + component.onSubmit(); }); it('IP Dest Address should be bound to the model', () => { @@ -104,13 +111,21 @@ describe('PcapFiltersComponent', () => { expect(component.model.ipDstAddr).toBe('256.0.0.7'); }); - it('IP Dest Port should be bound to the model', () => { + it('IP Dest Port should be bound to the property', () => { let input: HTMLInputElement = fixture.nativeElement.querySelector('[name="ipDstPort"]'); input.value = '8989'; input.dispatchEvent(new Event('input')); fixture.detectChanges(); - expect(component.model.ipDstPort).toBe(8989); + expect(component.ipDstPort).toBe('8989'); + }); + + it('IP Dest Port should be converted to number on submit', () => { + component.ipDstPort = '42'; + component.search.emit = (model: PcapRequest) => { + expect(model.ipDstPort).toBe(42); + }; + component.onSubmit(); }); it('Protocol should be bound to the model', () => { @@ -144,7 +159,7 @@ describe('PcapFiltersComponent', () => { component.startTimeStr = '2220-12-12 12:12:12'; component.search.emit = (model: PcapRequest) => { expect(model.startTimeMs).toBe(new Date(component.startTimeStr).getTime()); - } + }; component.onSubmit(); }); @@ -152,7 +167,7 @@ describe('PcapFiltersComponent', () => { component.endTimeStr = '2320-03-13 13:13:13'; component.search.emit = (model: PcapRequest) => { expect(model.endTimeMs).toBe(new Date(component.endTimeStr).getTime()); - } + }; component.onSubmit(); }); @@ -187,4 +202,195 @@ describe('PcapFiltersComponent', () => { expect(fixture.componentInstance.model.hasOwnProperty('includeReverse')).toBeTruthy(); }); + describe('Filter validation', () => { + + function setup() { + component.queryRunning = false; + fixture.detectChanges(); + } + + function getFieldWithSubmit(fieldId: string): { field: DebugElement, submit: DebugElement } { + const field = fixture.debugElement.query(By.css('[data-qe-id="' + fieldId + '"]')); + const submit = fixture.debugElement.query(By.css('[data-qe-id="submit-button"]')); + return { + field, + submit + }; + } + + function setFieldValue(field: DebugElement, value: any) { + field.nativeElement.value = value; + field.nativeElement.dispatchEvent(new Event('input')); + fixture.detectChanges(); + } + + function isSubmitDisabled(submit: DebugElement): boolean { + return submit.classes['disabled'] && submit.nativeElement.disabled; + } + + function isFieldInvalid(field: DebugElement): boolean { + return field.classes['ng-invalid']; + } + + function tearDown(field: DebugElement) { + setFieldValue(field, ''); + }; + + beforeEach(setup); + + it('should disable the form if the ip source port is invalid', () => { + const invalidValues = [ + '-42', + '-1', + 'foobar', + '.', + '-', + '+', + 'e', + 'E', + '3.14', + '123456', + '65536', + '99999', + '2352363474576', + '1e3', + ]; + + invalidValues.forEach((value) => { + const els = getFieldWithSubmit('ip-src-port'); + expect(isFieldInvalid(els.field)).toBe(false, 'the field should be valid without ' + value); + expect(isSubmitDisabled(els.submit)).toBe(false, 'the submit button should be enabled without ' + value); + + setFieldValue(els.field, value); + + expect(isFieldInvalid(els.field)).toBe(true, 'the field should be invalid with ' + value); + expect(isSubmitDisabled(els.submit)).toBe(true, 'the submit button should be disabled with ' + value); + tearDown(els.field); + }); + }); + + it('should keep the form enabled if the ip source port is valid', () => { + const validValues = [ + '8080', + '1024', + '3000', + '1', + '0', + '12345', + '65535', + ]; + + validValues.forEach((value) => { + const els = getFieldWithSubmit('ip-src-port'); + expect(isFieldInvalid(els.field)).toBe(false, 'the field should be valid without ' + value); + expect(isSubmitDisabled(els.submit)).toBe(false, 'the submit button should be enabled without ' + value); + + setFieldValue(els.field, value); + + expect(isFieldInvalid(els.field)).toBe(false, 'the field should be valid with ' + value); + expect(isSubmitDisabled(els.submit)).toBe(false, 'the submit button should be enabled with ' + value); + tearDown(els.field); + }); + }); + + it('should disable the form if the ip destination port is invalid', () => { + const invalidValues = [ + '-42', + '-1', + 'foobar', + '.', + '-', + '+', + 'e', + 'E', + '3.14', + '123456', + '65536', + '99999', + '2352363474576', + '1e3', + ]; + + invalidValues.forEach((value) => { + const els = getFieldWithSubmit('ip-dest-port'); + expect(isFieldInvalid(els.field)).toBe(false, 'the field should be valid without ' + value); + expect(isSubmitDisabled(els.submit)).toBe(false, 'the submit button should be enabled without ' + value); + + setFieldValue(els.field, value); + + expect(isFieldInvalid(els.field)).toBe(true, 'the field should be invalid with ' + value); + expect(isSubmitDisabled(els.submit)).toBe(true, 'the submit button should be disabled with ' + value); + tearDown(els.field); + }); + }); + + it('should keep the form enabled if the ip destination port is valid', () => { + const validValues = [ + '8080', + '1024', + '3000', + '1', + '0', + '12345', + '65535', + ]; + + validValues.forEach((value) => { + const els = getFieldWithSubmit('ip-dest-port'); + expect(isFieldInvalid(els.field)).toBe(false, 'the field should be valid without ' + value); + expect(isSubmitDisabled(els.submit)).toBe(false, 'the submit button should be enabled without ' + value); + + setFieldValue(els.field, value); + + expect(isFieldInvalid(els.field)).toBe(false, 'the field should be valid with ' + value); + expect(isSubmitDisabled(els.submit)).toBe(false, 'the submit button should be enabled with ' + value); + tearDown(els.field); + }); + }); + + + it('should disable the form if the ip source field is invalid', () => { + const invalidValues = [ + 'tst', + 0o0, + 0, + '111.111.111', + '222.222.222.222.222', + '333.333.333.333', + ]; + + invalidValues.forEach((value) => { + const els = getFieldWithSubmit('ip-src-addr'); + expect(isFieldInvalid(els.field)).toBe(false, 'the field should be valid without ' + value); + expect(isSubmitDisabled(els.submit)).toBe(false, 'the submit button should be enabled without ' + value); + + setFieldValue(els.field, value); + + expect(isFieldInvalid(els.field)).toBe(true, 'the field should be invalid with ' + value); + expect(isSubmitDisabled(els.submit)).toBe(true, 'the submit button should be disabled with ' + value); + tearDown(els.field); + }); + }); + + it('should keep the form enabled if the ip source field is valid', () => { + const validValues = [ + '0.0.0.0', + '222.222.222.222', + '255.255.255.255', + ]; + + validValues.forEach((value) => { + const els = getFieldWithSubmit('ip-src-addr'); + expect(isFieldInvalid(els.field)).toBe(false, 'the field should be valid without ' + value); + expect(isSubmitDisabled(els.submit)).toBe(false, 'the submit button should be enabled without ' + value); + + setFieldValue(els.field, value); + + expect(isFieldInvalid(els.field)).toBe(false, 'tthe field should be valid with ' + value); + expect(isSubmitDisabled(els.submit)).toBe(false, 'the submit button should be enabled with ' + value); + tearDown(els.field); + }); + }); + + }); }); http://git-wip-us.apache.org/repos/asf/metron/blob/52de126f/metron-interface/metron-alerts/src/app/pcap/pcap-filters/pcap-filters.component.ts ---------------------------------------------------------------------- diff --git a/metron-interface/metron-alerts/src/app/pcap/pcap-filters/pcap-filters.component.ts b/metron-interface/metron-alerts/src/app/pcap/pcap-filters/pcap-filters.component.ts index 4f6a3dd..5bbdb67 100644 --- a/metron-interface/metron-alerts/src/app/pcap/pcap-filters/pcap-filters.component.ts +++ b/metron-interface/metron-alerts/src/app/pcap/pcap-filters/pcap-filters.component.ts @@ -33,6 +33,8 @@ export class PcapFiltersComponent implements OnInit { startTimeStr: string; endTimeStr: string; + ipSrcPort: string = ''; + ipDstPort: string = ''; model = new PcapRequest(); @@ -49,6 +51,9 @@ export class PcapFiltersComponent implements OnInit { onSubmit() { this.model.startTimeMs = new Date(this.startTimeStr).getTime(); this.model.endTimeMs = new Date(this.endTimeStr).getTime(); + this.model.ipSrcPort = +this.ipSrcPort; + this.model.ipDstPort = +this.ipDstPort; + this.search.emit(this.model); } }