]> git.apps.os.sepia.ceph.com Git - ceph.git/commitdiff
mgr/dashboard: Dashboard nfs export editor rejects ipv6 addresses 65169/head
authorAfreen Misbah <afreen@ibm.com>
Thu, 21 Aug 2025 09:41:43 +0000 (15:11 +0530)
committerAfreen Misbah <afreen@ibm.com>
Mon, 25 Aug 2025 10:49:53 +0000 (16:19 +0530)
Fixes https://tracker.ceph.com/issues/72660

Signed-off-by: Afreen Misbah <afreen@ibm.com>
src/pybind/mgr/dashboard/frontend/src/app/ceph/nfs/nfs-form-client/nfs-form-client.component.html
src/pybind/mgr/dashboard/frontend/src/app/ceph/nfs/nfs-form-client/nfs-form-client.component.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/nfs/nfs-form-client/nfs-form-client.component.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/forms/cd-validators.ts

index 6f52a00af0cfcc00f3443c74710599c4b677c945..5d2cef35e8a3a18bc135f196cc9789e73c04c9b5 100644 (file)
@@ -41,7 +41,7 @@
                        name="addresses"
                        id="addresses"
                        formControlName="addresses"
-                       placeholder="192.168.0.10, 192.168.1.0/8"
+                       placeholder="e.g. 192.168.0.10, 192.168.1.0/8"
                        [invalid]="!item.controls['addresses'].valid && (item.controls['addresses'].dirty)">
               </cds-text-label>
               <ng-template #addressesError>
@@ -49,8 +49,8 @@
                   <span *ngIf="showError(index, 'addresses', formDir, 'required')"
                         i18n>This field is required.</span>
 
-                  <span *ngIf="showError(index, 'addresses', formDir, 'pattern')">
-                    <ng-container i18n>Must contain one or more comma-separated values</ng-container>
+                  <span *ngIf="showError(index, 'addresses', formDir, 'invalidAddress')">
+                    <ng-container i18n>Must contain one or more comma-separated valid addresses.</ng-container>
                     <br>
                     <ng-container i18n>For example:</ng-container> 192.168.0.10, 192.168.1.0/8
                   </span>
index 70d885d848ea1ed0f02683ad435c0b817503ca32..4f1d95fd2ab379f050faea4acdaab0c025acbbd2 100644 (file)
@@ -68,4 +68,46 @@ describe('NfsFormClientComponent', () => {
     component.removeClient(0);
     expect(component.form.getValue('clients')).toEqual([]);
   });
+
+  describe(`test 'isValidClientAddress'`, () => {
+    it('should return false for empty value', () => {
+      expect(component.isValidClientAddress('')).toBeFalsy();
+      expect(component.isValidClientAddress(null)).toBeFalsy();
+    });
+
+    it('should return false for valid single IPv4 address', () => {
+      expect(component.isValidClientAddress('192.168.1.1')).toBeFalsy();
+    });
+
+    it('should return false for valid IPv6 address', () => {
+      expect(component.isValidClientAddress('2001:db8::1')).toBeFalsy();
+    });
+
+    it('should return false for valid FQDN', () => {
+      expect(component.isValidClientAddress('nfs.example.com')).toBeFalsy();
+    });
+
+    it('should return false for valid IP CIDR range', () => {
+      expect(component.isValidClientAddress('192.168.0.0/24')).toBeFalsy();
+      expect(component.isValidClientAddress('2001:db8::/64')).toBeFalsy();
+    });
+
+    it('should return false for multiple valid addresses separated by comma', () => {
+      const input = '192.168.1.1, 2001:db8::1, nfs.example.com, 10.0.0.0/8';
+      expect(component.isValidClientAddress(input)).toBeFalsy();
+    });
+
+    it('should return true for invalid single address', () => {
+      expect(component.isValidClientAddress('invalid-address')).toBeTruthy();
+    });
+
+    it('should return true for mixed valid and invalid addresses', () => {
+      const input = '192.168.1.1, invalid-address';
+      expect(component.isValidClientAddress(input)).toBeTruthy();
+    });
+
+    it('should return true for URLs with protocols', () => {
+      expect(component.isValidClientAddress('http://nfs.example.com')).toBeTruthy();
+    });
+  });
 });
index f7b4cc0fdf883ae3137e3fcf6189e19b9c97f1a7..834448bf72365f8638dfcfd06caee5338fc1bb75 100644 (file)
@@ -2,10 +2,12 @@ import { Component, ContentChild, Input, OnInit, TemplateRef } from '@angular/co
 import { UntypedFormArray, UntypedFormControl, NgForm, Validators } from '@angular/forms';
 
 import _ from 'lodash';
+import validator from 'validator';
 
 import { NfsService } from '~/app/shared/api/nfs.service';
 import { Icons } from '~/app/shared/enum/icons.enum';
 import { CdFormGroup } from '~/app/shared/forms/cd-form-group';
+import { CdValidators } from '~/app/shared/forms/cd-validators';
 
 @Component({
   selector: 'cd-nfs-form-client',
@@ -59,14 +61,36 @@ export class NfsFormClientComponent implements OnInit {
     return $localize`-- Select what kind of user id squashing is performed --`;
   }
 
+  isValidClientAddress(value: string): boolean {
+    if (_.isEmpty(value)) {
+      return false;
+    }
+
+    const addressList = value.includes(',')
+      ? value.split(',').map((addr) => addr.trim())
+      : [value.trim()];
+    const invalidAddresses = addressList.filter(
+      (address: string) =>
+        !validator.isFQDN(address, {
+          allow_underscores: true,
+          require_tld: true
+        }) &&
+        !validator.isIP(address) &&
+        !validator.isIPRange(address)
+    );
+
+    return invalidAddresses.length > 0;
+  }
+
   addClient() {
     this.clientsFormArray = this.form.get('clients') as UntypedFormArray;
 
-    const REGEX_IP = `(([0-9]{1,3})\\.([0-9]{1,3})\\.([0-9]{1,3})\.([0-9]{1,3})([/](\\d|[1-2]\\d|3[0-2]))?)`;
-    const REGEX_LIST_IP = `${REGEX_IP}([ ,]{1,2}${REGEX_IP})*`;
     const fg = new CdFormGroup({
       addresses: new UntypedFormControl('', {
-        validators: [Validators.required, Validators.pattern(REGEX_LIST_IP)]
+        validators: [
+          Validators.required,
+          CdValidators.custom('invalidAddress', this.isValidClientAddress)
+        ]
       }),
       access_type: new UntypedFormControl(''),
       squash: new UntypedFormControl('')
index 5c236730d467c5c88a6489f648bdcbe7e85bd268..039201456f9963763be301fdc586a609e0937874 100644 (file)
@@ -696,7 +696,9 @@ export class CdValidators {
       return null;
     }
 
-    const urls = value.includes(',') ? value.split(',') : [value];
+    const urls = value.includes(',')
+      ? value.split(',').map((v: string) => v.trim())
+      : [value.trim()];
 
     const invalidUrls = urls.filter(
       (url: string) =>