]> git.apps.os.sepia.ceph.com Git - ceph.git/commitdiff
mgr/dashboard: Bucket names cannot be formatted as IP address 30620/head
authorVolker Theile <vtheile@suse.com>
Fri, 27 Sep 2019 11:04:48 +0000 (13:04 +0200)
committerVolker Theile <vtheile@suse.com>
Mon, 30 Sep 2019 08:03:44 +0000 (10:03 +0200)
In general, bucket names should follow domain name constraints:
- Bucket names must be unique.
- Bucket names cannot be formatted as IP address.
- Bucket names can be between 3 and 63 characters long.
- Bucket names must not contain uppercase characters or underscores.
- Bucket names must start with a lowercase letter or number.
- Bucket names must be a series of one or more labels. Adjacent labels are separated by a single period (.). Bucket names can contain lowercase letters, numbers, and hyphens. Each label must start and end with a lowercase letter or a number.

Fixes: https://tracker.ceph.com/issues/42069
Signed-off-by: Volker Theile <vtheile@suse.com>
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-bucket-form/rgw-bucket-form.component.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-bucket-form/rgw-bucket-form.component.ts

index 8d3fec0cdfd62d0fda2631d0f4024191b639e44e..7f28e4b53d0a2cc63fcd7ee4cd57cfec7612894b 100644 (file)
@@ -46,45 +46,81 @@ describe('RgwBucketFormComponent', () => {
   });
 
   describe('bucketNameValidator', () => {
-    it('should validate name (1/4)', () => {
+    const testValidator = (name, valid) => {
       const validatorFn = component.bucketNameValidator();
-      const ctrl = new FormControl('');
+      const ctrl = new FormControl(name);
+      ctrl.markAsDirty();
       const validatorPromise = validatorFn(ctrl);
       expect(validatorPromise instanceof Promise).toBeTruthy();
       if (validatorPromise instanceof Promise) {
         validatorPromise.then((resp) => {
-          expect(resp).toBe(null);
+          if (valid) {
+            expect(resp).toBe(null);
+          } else {
+            expect(resp instanceof Object).toBeTruthy();
+            expect(resp.bucketNameInvalid).toBeTruthy();
+          }
         });
       }
+    };
+
+    it('should validate empty name', () => {
+      testValidator('', true);
     });
 
-    it('should validate name (2/4)', () => {
-      const validatorFn = component.bucketNameValidator();
-      const ctrl = new FormControl('ab');
-      ctrl.markAsDirty();
-      const validatorPromise = validatorFn(ctrl);
-      expect(validatorPromise instanceof Promise).toBeTruthy();
-      if (validatorPromise instanceof Promise) {
-        validatorPromise.then((resp) => {
-          expect(resp.bucketNameInvalid).toBeTruthy();
-        });
-      }
+    it('bucket names cannot be formatted as IP address', () => {
+      testValidator('172.10.4.51', false);
     });
 
-    it('should validate name (3/4)', () => {
-      const validatorFn = component.bucketNameValidator();
-      const ctrl = new FormControl('abc');
-      ctrl.markAsDirty();
-      const validatorPromise = validatorFn(ctrl);
-      expect(validatorPromise instanceof Promise).toBeTruthy();
-      if (validatorPromise instanceof Promise) {
-        validatorPromise.then((resp) => {
-          expect(resp).toBe(null);
-        });
-      }
+    it('bucket name must be >= 3 characters long (1/2)', () => {
+      testValidator('ab', false);
+    });
+
+    it('bucket name must be >= 3 characters long (2/2)', () => {
+      testValidator('abc', true);
+    });
+
+    it('bucket name must be <= than 63 characters long (1/2)', () => {
+      testValidator(_.repeat('a', 64), false);
+    });
+
+    it('bucket name must be <= than 63 characters long (2/2)', () => {
+      testValidator(_.repeat('a', 63), true);
+    });
+
+    it('bucket names must not contain uppercase characters or underscores (1/2)', () => {
+      testValidator('iAmInvalid', false);
+    });
+
+    it('bucket names must not contain uppercase characters or underscores (2/2)', () => {
+      testValidator('i_am_invalid', false);
+    });
+
+    it('bucket names with invalid labels (1/3)', () => {
+      testValidator('abc.1def.Ghi2', false);
+    });
+
+    it('bucket names with invalid labels (2/3)', () => {
+      testValidator('abc.1-xy', false);
+    });
+
+    it('bucket names with invalid labels (3/3)', () => {
+      testValidator('abc.*def', false);
+    });
+
+    it('bucket names must be a series of one or more labels and can contain lowercase letters, numbers, and hyphens (1/3)', () => {
+      testValidator('xyz.abc', true);
+    });
+
+    it('bucket names must be a series of one or more labels and can contain lowercase letters, numbers, and hyphens (2/3)', () => {
+      testValidator('abc.1-def', true);
+    });
+
+    it('bucket names must be a series of one or more labels and can contain lowercase letters, numbers, and hyphens (3/3)', () => {
+      testValidator('abc.ghi2', true);
     });
 
-    it('should validate name (4/4)', () => {
+    it('bucket names must be unique', () => {
       spyOn(rgwBucketService, 'enumerate').and.returnValue(observableOf(['abcd']));
       const validatorFn = component.bucketNameValidator();
       const ctrl = new FormControl('abcd');
index c9e0b47fe3182e777bb01cc3ce5652095ceb91c6..5c9be7ae2e324c4faa884cb4a7057c6bb46fae83 100644 (file)
@@ -12,6 +12,7 @@ import { ActionLabelsI18n, URLVerbs } from '../../../shared/constants/app.consta
 import { NotificationType } from '../../../shared/enum/notification-type.enum';
 import { CdFormBuilder } from '../../../shared/forms/cd-form-builder';
 import { CdFormGroup } from '../../../shared/forms/cd-form-group';
+import { CdValidators } from '../../../shared/forms/cd-validators';
 import { NotificationService } from '../../../shared/services/notification.service';
 
 @Component({
@@ -161,6 +162,19 @@ export class RgwBucketFormComponent implements OnInit {
     }
   }
 
+  /**
+   * Validate the bucket name. In general, bucket names should follow domain
+   * name constraints:
+   * - Bucket names must be unique.
+   * - Bucket names cannot be formatted as IP address.
+   * - Bucket names can be between 3 and 63 characters long.
+   * - Bucket names must not contain uppercase characters or underscores.
+   * - Bucket names must start with a lowercase letter or number.
+   * - Bucket names must be a series of one or more labels. Adjacent
+   *   labels are separated by a single period (.). Bucket names can
+   *   contain lowercase letters, numbers, and hyphens. Each label must
+   *   start and end with a lowercase letter or a number.
+   */
   bucketNameValidator(): AsyncValidatorFn {
     const rgwBucketService = this.rgwBucketService;
     return (control: AbstractControl): Promise<ValidationErrors | null> => {
@@ -171,13 +185,42 @@ export class RgwBucketFormComponent implements OnInit {
           resolve(null);
           return;
         }
-        // Validate the bucket name.
-        const nameRe = /^[0-9A-Za-z][\w-\.]{2,254}$/;
-        if (!nameRe.test(control.value)) {
+        const constraints = [];
+        // - Bucket names cannot be formatted as IP address.
+        constraints.push((name) => {
+          const validatorFn = CdValidators.ip();
+          return !validatorFn(name);
+        });
+        // - Bucket names can be between 3 and 63 characters long.
+        constraints.push((name) => _.inRange(name.length, 3, 64));
+        // - Bucket names must not contain uppercase characters or underscores.
+        // - Bucket names must start with a lowercase letter or number.
+        // - Bucket names must be a series of one or more labels. Adjacent
+        //   labels are separated by a single period (.). Bucket names can
+        //   contain lowercase letters, numbers, and hyphens. Each label must
+        //   start and end with a lowercase letter or a number.
+        constraints.push((name) => {
+          const labels = _.split(name, '.');
+          return _.every(labels, (label) => {
+            // Bucket names must not contain uppercase characters or underscores.
+            if (label !== _.toLower(label) || label.includes('_')) {
+              return false;
+            }
+            // Bucket names can contain lowercase letters, numbers, and hyphens.
+            if (!/[0-9a-z-]/.test(label)) {
+              return false;
+            }
+            // Each label must start and end with a lowercase letter or a number.
+            return _.every([0, label.length], (index) => {
+              return /[a-z]/.test(label[index]) || _.isInteger(_.parseInt(label[index]));
+            });
+          });
+        });
+        if (!_.every(constraints, (func) => func(control.value))) {
           resolve({ bucketNameInvalid: true });
           return;
         }
-        // Does any bucket with the given name already exist?
+        // - Bucket names must be unique.
         rgwBucketService.exists(control.value).subscribe((resp: boolean) => {
           if (!resp) {
             resolve(null);