]> git-server-git.apps.pok.os.sepia.ceph.com Git - ceph.git/commitdiff
mgr/dashboard: add pattern validators
authorTatjana Dehler <tdehler@suse.com>
Thu, 27 Sep 2018 11:22:05 +0000 (13:22 +0200)
committerTatjana Dehler <tdehler@suse.com>
Tue, 9 Oct 2018 12:50:42 +0000 (14:50 +0200)
Add some pattern validators (ip, uuid, number, decimalNumber) that
are needed to validate the config option input values.

Fixes: http://tracker.ceph.com/issues/24455
Signed-off-by: Tatjana Dehler <tdehler@suse.com>
src/pybind/mgr/dashboard/frontend/src/app/shared/forms/cd-validators.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/forms/cd-validators.ts

index 9cad1c0e6918db2d82f72ed317e54819f9e51a70..fbf15dc87f68da92adc37d2e2e033059229b9153 100644 (file)
@@ -23,6 +23,420 @@ describe('CdValidators', () => {
     });
   });
 
+  describe('ip validator', () => {
+    let form: FormGroup;
+
+    beforeEach(() => {
+      form = new FormGroup({
+        x: new FormControl()
+      });
+    });
+
+    it('should not error on empty IPv4 addresses', () => {
+      const x = form.get('x');
+      x.setValidators(CdValidators.ip(4));
+      x.setValue('');
+      expect(x.valid).toBeTruthy();
+      expect(x.hasError('pattern')).toBeFalsy();
+    });
+
+    it('should accept valid IPv4 address', () => {
+      const x = form.get('x');
+      x.setValidators(CdValidators.ip(4));
+      x.setValue('19.117.23.141');
+      expect(x.valid).toBeTruthy();
+      expect(x.hasError('pattern')).toBeFalsy();
+    });
+
+    it('should error on IPv4 address containing whitespace', () => {
+      const x = form.get('x');
+      x.setValidators(CdValidators.ip(4));
+      x.setValue('155.144.133.122 ');
+      expect(x.valid).toBeFalsy();
+      expect(x.hasError('pattern')).toBeTruthy();
+
+      x.setValue('155. 144.133 .122');
+      expect(x.valid).toBeFalsy();
+      expect(x.hasError('pattern')).toBeTruthy();
+
+      x.setValue(' 155.144.133.122');
+      expect(x.valid).toBeFalsy();
+      expect(x.hasError('pattern')).toBeTruthy();
+    });
+
+    it('should error on IPv4 address containing invalid char', () => {
+      const x = form.get('x');
+      x.setValidators(CdValidators.ip(4));
+      x.setValue('155.144.eee.122 ');
+      expect(x.valid).toBeFalsy();
+      expect(x.hasError('pattern')).toBeTruthy();
+
+      x.setValue('155.1?.133 .1&2');
+      expect(x.valid).toBeFalsy();
+      expect(x.hasError('pattern')).toBeTruthy();
+    });
+
+    it('should error on IPv4 address containing blocks higher than 255', () => {
+      const x = form.get('x');
+      x.setValidators(CdValidators.ip(4));
+      x.setValue('155.270.133.122 ');
+      expect(x.valid).toBeFalsy();
+      expect(x.hasError('pattern')).toBeTruthy();
+
+      x.setValue('155.144.133.290 ');
+      expect(x.valid).toBeFalsy();
+      expect(x.hasError('pattern')).toBeTruthy();
+    });
+
+    it('should not error on empty IPv6 addresses', () => {
+      const x = form.get('x');
+      x.setValidators(CdValidators.ip(4));
+      x.setValue('');
+      expect(x.valid).toBeTruthy();
+      expect(x.hasError('pattern')).toBeFalsy();
+    });
+
+    it('should accept valid IPv6 address', () => {
+      const x = form.get('x');
+      x.setValidators(CdValidators.ip(6));
+      x.setValue('c4dc:1475:cb0b:24ed:3c80:468b:70cd:1a95');
+      expect(x.valid).toBeTruthy();
+      expect(x.hasError('pattern')).toBeFalsy();
+    });
+
+    it('should error on IPv6 address containing too many blocks', () => {
+      const x = form.get('x');
+      x.setValidators(CdValidators.ip(6));
+      x.setValue('c4dc:14753:cb0b:24ed:3c80:468b:70cd:1a95:a3f3');
+      expect(x.valid).toBeFalsy();
+      expect(x.hasError('pattern')).toBeTruthy();
+    });
+
+    it('should error on IPv6 address containing more than 4 digits per block', () => {
+      const x = form.get('x');
+      x.setValidators(CdValidators.ip(6));
+      x.setValue('c4dc:14753:cb0b:24ed:3c80:468b:70cd:1a95');
+      expect(x.valid).toBeFalsy();
+      expect(x.hasError('pattern')).toBeTruthy();
+    });
+
+    it('should error on IPv6 address containing whitespace', () => {
+      const x = form.get('x');
+      x.setValidators(CdValidators.ip(6));
+      x.setValue('c4dc:14753:cb0b:24ed:3c80:468b:70cd:1a95 ');
+      expect(x.valid).toBeFalsy();
+      expect(x.hasError('pattern')).toBeTruthy();
+
+      x.setValue('c4dc:14753 :cb0b:24ed:3c80 :468b:70cd :1a95');
+      expect(x.valid).toBeFalsy();
+      expect(x.hasError('pattern')).toBeTruthy();
+
+      x.setValue(' c4dc:14753:cb0b:24ed:3c80:468b:70cd:1a95');
+      expect(x.valid).toBeFalsy();
+      expect(x.hasError('pattern')).toBeTruthy();
+    });
+
+    it('should error on IPv6 address containing invalid char', () => {
+      const x = form.get('x');
+      x.setValidators(CdValidators.ip(6));
+      x.setValue('c4dx:14753:cb0b:24ed:3c80:468b:70cd:1a95 ');
+      expect(x.valid).toBeFalsy();
+      expect(x.hasError('pattern')).toBeTruthy();
+
+      x.setValue('c4dx:14753:cb0b:24ed:3$80:468b:70cd:1a95 ');
+      expect(x.valid).toBeFalsy();
+      expect(x.hasError('pattern')).toBeTruthy();
+    });
+
+    it('should accept valid IPv4/6 addresses if not protocol version is given', () => {
+      const x = form.get('x');
+      x.setValidators(CdValidators.ip());
+      x.setValue('19.117.23.141');
+      expect(x.valid).toBeTruthy();
+      expect(x.hasError('pattern')).toBeFalsy();
+
+      x.setValue('c4dc:1475:cb0b:24ed:3c80:468b:70cd:1a95');
+      expect(x.valid).toBeTruthy();
+      expect(x.hasError('pattern')).toBeFalsy();
+    });
+  });
+
+  describe('uuid validator', () => {
+    let form: FormGroup;
+
+    beforeEach(() => {
+      form = new FormGroup({
+        x: new FormControl()
+      });
+      form.controls['x'].setValidators(CdValidators.uuid());
+    });
+
+    it('should accept empty value', () => {
+      const x = form.get('x');
+      x.setValue('');
+      expect(x.valid).toBeTruthy();
+      expect(x.hasError('pattern')).toBeFalsy();
+    });
+
+    it('should accept valid version 1 uuid', () => {
+      const x = form.get('x');
+      x.setValue('171af0b2-c305-11e8-a355-529269fb1459');
+      expect(x.valid).toBeTruthy();
+      expect(x.hasError('pattern')).toBeFalsy();
+    });
+
+    it('should accept valid version 4 uuid', () => {
+      const x = form.get('x');
+      x.setValue('e33bbcb6-fcc3-40b1-ae81-3f81706a35d5');
+      expect(x.valid).toBeTruthy();
+      expect(x.hasError('pattern')).toBeFalsy();
+    });
+
+    it('should error on uuid containing too many blocks', () => {
+      const x = form.get('x');
+      x.setValue('e33bbcb6-fcc3-40b1-ae81-3f81706a35d5-23d3');
+      expect(x.valid).toBeFalsy();
+      expect(x.hasError('pattern')).toBeTruthy();
+    });
+
+    it('should error on uuid containing too many chars in block', () => {
+      const x = form.get('x');
+      x.setValue('aae33bbcb6-fcc3-40b1-ae81-3f81706a35d5');
+      expect(x.valid).toBeFalsy();
+      expect(x.hasError('pattern')).toBeTruthy();
+    });
+
+    it('should error on uuid containing invalid char', () => {
+      const x = form.get('x');
+      x.setValue('x33bbcb6-fcc3-40b1-ae81-3f81706a35d5');
+      expect(x.valid).toBeFalsy();
+      expect(x.hasError('pattern')).toBeTruthy();
+
+      x.setValue('$33bbcb6-fcc3-40b1-ae81-3f81706a35d5');
+      expect(x.valid).toBeFalsy();
+      expect(x.hasError('pattern')).toBeTruthy();
+    });
+  });
+
+  describe('number validator', () => {
+    let form: FormGroup;
+
+    beforeEach(() => {
+      form = new FormGroup({
+        x: new FormControl()
+      });
+      form.controls['x'].setValidators(CdValidators.number());
+    });
+
+    it('should accept empty value', () => {
+      const x = form.get('x');
+      x.setValue('');
+      expect(x.valid).toBeTruthy();
+      expect(x.hasError('pattern')).toBeFalsy();
+    });
+
+    it('should accept numbers', () => {
+      const x = form.get('x');
+      x.setValue(42);
+      expect(x.valid).toBeTruthy();
+      expect(x.hasError('pattern')).toBeFalsy();
+
+      x.setValue(-42);
+      expect(x.valid).toBeTruthy();
+      expect(x.hasError('pattern')).toBeFalsy();
+
+      x.setValue('42');
+      expect(x.valid).toBeTruthy();
+      expect(x.hasError('pattern')).toBeFalsy();
+    });
+
+    it('should error on decimal numbers', () => {
+      const x = form.get('x');
+      x.setValue(42.3);
+      expect(x.valid).toBeFalsy();
+      expect(x.hasError('pattern')).toBeTruthy();
+
+      x.setValue(-42.3);
+      expect(x.valid).toBeFalsy();
+      expect(x.hasError('pattern')).toBeTruthy();
+
+      x.setValue('42.3');
+      expect(x.valid).toBeFalsy();
+      expect(x.hasError('pattern')).toBeTruthy();
+    });
+
+    it('should error on chars', () => {
+      const x = form.get('x');
+      x.setValue('char');
+      expect(x.valid).toBeFalsy();
+      expect(x.hasError('pattern')).toBeTruthy();
+
+      x.setValue('42char');
+      expect(x.valid).toBeFalsy();
+      expect(x.hasError('pattern')).toBeTruthy();
+    });
+
+    it('should error on whitespaces', () => {
+      const x = form.get('x');
+      x.setValue('42 ');
+      expect(x.valid).toBeFalsy();
+      expect(x.hasError('pattern')).toBeTruthy();
+
+      x.setValue('4 2');
+      expect(x.valid).toBeFalsy();
+      expect(x.hasError('pattern')).toBeTruthy();
+    });
+  });
+
+  describe('number validator (without negative values)', () => {
+    let form: FormGroup;
+
+    beforeEach(() => {
+      form = new FormGroup({
+        x: new FormControl()
+      });
+      form.controls['x'].setValidators(CdValidators.number(false));
+    });
+
+    it('should accept positive numbers', () => {
+      const x = form.get('x');
+      x.setValue(42);
+      expect(x.valid).toBeTruthy();
+      expect(x.hasError('pattern')).toBeFalsy();
+
+      x.setValue('42');
+      expect(x.valid).toBeTruthy();
+      expect(x.hasError('pattern')).toBeFalsy();
+    });
+
+    it('should error on negative numbers', () => {
+      const x = form.get('x');
+      x.setValue(-42);
+      expect(x.valid).toBeFalsy();
+      expect(x.hasError('pattern')).toBeTruthy();
+
+      x.setValue('-42');
+      expect(x.valid).toBeFalsy();
+      expect(x.hasError('pattern')).toBeTruthy();
+    });
+  });
+
+  describe('decimal number validator', () => {
+    let form: FormGroup;
+
+    beforeEach(() => {
+      form = new FormGroup({
+        x: new FormControl()
+      });
+      form.controls['x'].setValidators(CdValidators.decimalNumber());
+    });
+
+    it('should accept empty value', () => {
+      const x = form.get('x');
+      x.setValue('');
+      expect(x.valid).toBeTruthy();
+      expect(x.hasError('pattern')).toBeFalsy();
+    });
+
+    it('should accept numbers and decimal numbers', () => {
+      const x = form.get('x');
+      x.setValue(42);
+      expect(x.valid).toBeTruthy();
+      expect(x.hasError('pattern')).toBeFalsy();
+
+      x.setValue(-42);
+      expect(x.valid).toBeTruthy();
+      expect(x.hasError('pattern')).toBeFalsy();
+
+      x.setValue(42.3);
+      expect(x.valid).toBeTruthy();
+      expect(x.hasError('pattern')).toBeFalsy();
+
+      x.setValue(-42.3);
+      expect(x.valid).toBeTruthy();
+      expect(x.hasError('pattern')).toBeFalsy();
+
+      x.setValue('42');
+      expect(x.valid).toBeTruthy();
+      expect(x.hasError('pattern')).toBeFalsy();
+
+      x.setValue('42.3');
+      expect(x.valid).toBeTruthy();
+      expect(x.hasError('pattern')).toBeFalsy();
+    });
+
+    it('should error on chars', () => {
+      const x = form.get('x');
+      x.setValue('42e');
+      expect(x.valid).toBeFalsy();
+      expect(x.hasError('pattern')).toBeTruthy();
+
+      x.setValue('e42.3');
+      expect(x.valid).toBeFalsy();
+      expect(x.hasError('pattern')).toBeTruthy();
+    });
+
+    it('should error on whitespaces', () => {
+      const x = form.get('x');
+      x.setValue('42.3 ');
+      expect(x.valid).toBeFalsy();
+      expect(x.hasError('pattern')).toBeTruthy();
+
+      x.setValue('42 .3');
+      expect(x.valid).toBeFalsy();
+      expect(x.hasError('pattern')).toBeTruthy();
+    });
+  });
+
+  describe('decimal number validator (without negative values)', () => {
+    let form: FormGroup;
+
+    beforeEach(() => {
+      form = new FormGroup({
+        x: new FormControl()
+      });
+      form.controls['x'].setValidators(CdValidators.decimalNumber(false));
+    });
+
+    it('should accept positive numbers and decimals', () => {
+      const x = form.get('x');
+      x.setValue(42);
+      expect(x.valid).toBeTruthy();
+      expect(x.hasError('pattern')).toBeFalsy();
+
+      x.setValue(42.3);
+      expect(x.valid).toBeTruthy();
+      expect(x.hasError('pattern')).toBeFalsy();
+
+      x.setValue('42');
+      expect(x.valid).toBeTruthy();
+      expect(x.hasError('pattern')).toBeFalsy();
+
+      x.setValue('42.3');
+      expect(x.valid).toBeTruthy();
+      expect(x.hasError('pattern')).toBeFalsy();
+    });
+
+    it('should error on negative numbers and decimals', () => {
+      const x = form.get('x');
+      x.setValue(-42);
+      expect(x.valid).toBeFalsy();
+      expect(x.hasError('pattern')).toBeTruthy();
+
+      x.setValue('-42');
+      expect(x.valid).toBeFalsy();
+      expect(x.hasError('pattern')).toBeTruthy();
+
+      x.setValue(-42.3);
+      expect(x.valid).toBeFalsy();
+      expect(x.hasError('pattern')).toBeTruthy();
+
+      x.setValue('-42.3');
+      expect(x.valid).toBeFalsy();
+      expect(x.hasError('pattern')).toBeTruthy();
+    });
+  });
+
   describe('requiredIf', () => {
     let form: FormGroup;
 
index ac1fe187f76965c553aaeed949fe6491d4c659f9..0d4824309305cc59d96299a1c68548846ed610f2 100644 (file)
@@ -29,6 +29,65 @@ export class CdValidators {
     return Validators.email(control);
   }
 
+  /**
+   * Validator function in order to validate IP addresses.
+   * @param {number} version determines the protocol version. It needs to be set to 4 for IPv4 and
+   * to 6 for IPv6 validation. For any other number (it's also the default case) it will return a
+   * function to validate the input string against IPv4 OR IPv6.
+   * @returns {ValidatorFn} A validator function that returns an error map containing `pattern`
+   * if the validation failed, otherwise `null`.
+   */
+  static ip(version: number = 0): ValidatorFn {
+    // prettier-ignore
+    const ipv4Rgx =
+      /^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/i;
+    const ipv6Rgx = /^(?:[a-f0-9]{1,4}:){7}[a-f0-9]{1,4}$/i;
+
+    if (version === 4) {
+      return Validators.pattern(ipv4Rgx);
+    } else if (version === 6) {
+      return Validators.pattern(ipv6Rgx);
+    } else {
+      return Validators.pattern(new RegExp(ipv4Rgx.source + '|' + ipv6Rgx.source));
+    }
+  }
+
+  /**
+   * Validator function in order to validate uuids.
+   * @returns {ValidatorFn} A validator function that returns an error map containing `pattern`
+   * if the validation failed, otherwise `null`.
+   */
+  static uuid(): ValidatorFn {
+    const uuidRgx = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
+    return Validators.pattern(uuidRgx);
+  }
+
+  /**
+   * Validator function in order to validate numbers.
+   * @returns {ValidatorFn} A validator function that returns an error map containing `pattern`
+   * if the validation failed, otherwise `null`.
+   */
+  static number(allowsNegative: boolean = true): ValidatorFn {
+    if (allowsNegative) {
+      return Validators.pattern(/^-?[0-9]+$/i);
+    } else {
+      return Validators.pattern(/^[0-9]+$/i);
+    }
+  }
+
+  /**
+   * Validator function in order to validate decimal numbers.
+   * @returns {ValidatorFn} A validator function that returns an error map containing `pattern`
+   * if the validation failed, otherwise `null`.
+   */
+  static decimalNumber(allowsNegative: boolean = true): ValidatorFn {
+    if (allowsNegative) {
+      return Validators.pattern(/^-?[0-9]+(.[0-9]+)?$/i);
+    } else {
+      return Validators.pattern(/^[0-9]+(.[0-9]+)?$/i);
+    }
+  }
+
   /**
    * Validator that requires controls to fulfill the specified condition if
    * the specified prerequisites matches. If the prerequisites are fulfilled,