]> git.apps.os.sepia.ceph.com Git - ceph-ci.git/commitdiff
mgr/dashboard: Can't handle user editing when tenants are specified
authorVolker Theile <vtheile@suse.com>
Tue, 20 Nov 2018 08:07:28 +0000 (09:07 +0100)
committerVolker Theile <vtheile@suse.com>
Tue, 20 Nov 2018 08:07:33 +0000 (09:07 +0100)
Fixes: https://tracker.ceph.com/issues/36480
Signed-off-by: Volker Theile <vtheile@suse.com>
13 files changed:
qa/tasks/mgr/dashboard/test_rgw.py
src/pybind/mgr/dashboard/controllers/rgw.py
src/pybind/mgr/dashboard/frontend/src/app/app-routing.module.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-bucket-details/rgw-bucket-details.component.html
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-bucket-form/rgw-bucket-form.component.html
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-bucket-form/rgw-bucket-form.component.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-bucket-list/rgw-bucket-list.component.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-details/rgw-user-details.component.html
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-details/rgw-user-details.component.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-form/rgw-user-form.component.html
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-form/rgw-user-form.component.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-form/rgw-user-form.component.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-list/rgw-user-list.component.ts

index 846eff4122e87615fc71d3f3f9b0b35c80619208..eb8cff6cb23869f0a070132d33f9bebf9a0e12f8 100644 (file)
@@ -1,8 +1,11 @@
 # -*- coding: utf-8 -*-
 from __future__ import absolute_import
+
 import logging
+import urllib
+import six
 
-from .helper import DashboardTestCase
+from .helper import DashboardTestCase, JObj, JList, JLeaf
 
 
 logger = logging.getLogger(__name__)
@@ -124,6 +127,16 @@ class RgwBucketTest(RgwTestCase):
     def setUpClass(cls):
         cls.create_test_user = True
         super(RgwBucketTest, cls).setUpClass()
+        # Create a tenanted user.
+        cls._radosgw_admin_cmd([
+            'user', 'create', '--tenant', 'testx', '--uid', 'teuth-test-user',
+            '--display-name', 'tenanted teuth-test-user'
+        ])
+
+    @classmethod
+    def tearDownClass(cls):
+        cls._radosgw_admin_cmd(['user', 'rm', '--tenant', 'testx', '--uid=teuth-test-user'])
+        super(RgwBucketTest, cls).tearDownClass()
 
     def test_all(self):
         # Create a new bucket.
@@ -135,14 +148,20 @@ class RgwBucketTest(RgwTestCase):
             })
         self.assertStatus(201)
         data = self.jsonBody()
-        self.assertIn('bucket_info', data)
-        data = data['bucket_info']
-        self.assertIn('bucket', data)
-        self.assertIn('quota', data)
-        self.assertIn('creation_time', data)
-        self.assertIn('name', data['bucket'])
-        self.assertIn('bucket_id', data['bucket'])
-        self.assertEqual(data['bucket']['name'], 'teuth-test-bucket')
+        self.assertSchema(data, JObj(sub_elems={
+            'bucket_info': JObj(sub_elems={
+                'bucket': JObj(allow_unknown=True, sub_elems={
+                    'name': JLeaf(str),
+                    'bucket_id': JLeaf(str),
+                    'tenant': JLeaf(str)
+                }),
+                'quota': JObj(sub_elems={}, allow_unknown=True),
+                'creation_time': JLeaf(str)
+            }, allow_unknown=True)
+        }, allow_unknown=True))
+        data = data['bucket_info']['bucket']
+        self.assertEqual(data['name'], 'teuth-test-bucket')
+        self.assertEqual(data['tenant'], '')
 
         # List all buckets.
         data = self._get('/api/rgw/bucket')
@@ -153,10 +172,14 @@ class RgwBucketTest(RgwTestCase):
         # Get the bucket.
         data = self._get('/api/rgw/bucket/teuth-test-bucket')
         self.assertStatus(200)
-        self.assertIn('id', data)
-        self.assertIn('bucket', data)
-        self.assertIn('bucket_quota', data)
-        self.assertIn('owner', data)
+        self.assertSchema(data, JObj(sub_elems={
+            'id': JLeaf(str),
+            'bid': JLeaf(str),
+            'tenant': JLeaf(str),
+            'bucket': JLeaf(str),
+            'bucket_quota': JObj(sub_elems={}, allow_unknown=True),
+            'owner': JLeaf(str)
+        }, allow_unknown=True))
         self.assertEqual(data['bucket'], 'teuth-test-bucket')
         self.assertEqual(data['owner'], 'admin')
 
@@ -170,6 +193,11 @@ class RgwBucketTest(RgwTestCase):
         self.assertStatus(200)
         data = self._get('/api/rgw/bucket/teuth-test-bucket')
         self.assertStatus(200)
+        self.assertSchema(data, JObj(sub_elems={
+            'owner': JLeaf(str),
+            'bid': JLeaf(str),
+            'tenant': JLeaf(str)
+        }, allow_unknown=True))
         self.assertEqual(data['owner'], 'teuth-test-user')
 
         # Delete the bucket.
@@ -179,6 +207,65 @@ class RgwBucketTest(RgwTestCase):
         self.assertStatus(200)
         self.assertEqual(len(data), 0)
 
+    def test_create_get_update_delete_w_tenant(self):
+        # Create a new bucket. The tenant of the user is used when
+        # the bucket is created.
+        self._post(
+            '/api/rgw/bucket',
+            params={
+                'bucket': 'teuth-test-bucket',
+                'uid': 'testx$teuth-test-user'
+            })
+        self.assertStatus(201)
+        # It's not possible to validate the result because there
+        # IS NO result object returned by the RGW Admin OPS API
+        # when a tenanted bucket is created.
+        data = self.jsonBody()
+        self.assertIsNone(data)
+
+        # List all buckets.
+        data = self._get('/api/rgw/bucket')
+        self.assertStatus(200)
+        self.assertEqual(len(data), 1)
+        self.assertIn('testx/teuth-test-bucket', data)
+
+        # Get the bucket.
+        data = self._get('/api/rgw/bucket/{}'.format(urllib.quote_plus(
+            'testx/teuth-test-bucket')))
+        self.assertStatus(200)
+        self.assertSchema(data, JObj(sub_elems={
+            'owner': JLeaf(str),
+            'bucket': JLeaf(str),
+            'tenant': JLeaf(str),
+            'bid': JLeaf(str)
+        }, allow_unknown=True))
+        self.assertEqual(data['owner'], 'testx$teuth-test-user')
+        self.assertEqual(data['bucket'], 'teuth-test-bucket')
+        self.assertEqual(data['tenant'], 'testx')
+        self.assertEqual(data['bid'], 'testx/teuth-test-bucket')
+
+        # Update the bucket.
+        self._put(
+            '/api/rgw/bucket/{}'.format(urllib.quote_plus('testx/teuth-test-bucket')),
+            params={
+                'bucket_id': data['id'],
+                'uid': 'admin'
+            })
+        self.assertStatus(200)
+        data = self._get('/api/rgw/bucket/{}'.format(urllib.quote_plus(
+            'testx/teuth-test-bucket')))
+        self.assertStatus(200)
+        self.assertIn('owner', data)
+        self.assertEqual(data['owner'], 'admin')
+
+        # Delete the bucket.
+        self._delete('/api/rgw/bucket/{}'.format(urllib.quote_plus(
+            'testx/teuth-test-bucket')))
+        self.assertStatus(204)
+        data = self._get('/api/rgw/bucket')
+        self.assertStatus(200)
+        self.assertEqual(len(data), 0)
+
 
 class RgwDaemonTest(DashboardTestCase):
 
@@ -237,17 +324,20 @@ class RgwUserTest(RgwTestCase):
         super(RgwUserTest, cls).setUpClass()
 
     def _assert_user_data(self, data):
-        self.assertIn('caps', data)
-        self.assertIn('display_name', data)
-        self.assertIn('email', data)
-        self.assertIn('keys', data)
+        self.assertSchema(data, JObj(sub_elems={
+            'caps': JList(JObj(sub_elems={}, allow_unknown=True)),
+            'display_name': JLeaf(str),
+            'email': JLeaf(str),
+            'keys': JList(JObj(sub_elems={}, allow_unknown=True)),
+            'max_buckets': JLeaf(int),
+            'subusers': JList(JLeaf(str)),
+            'suspended': JLeaf(int),
+            'swift_keys': JList(JObj(sub_elems={}, allow_unknown=True)),
+            'tenant': JLeaf(str),
+            'user_id': JLeaf(str),
+            'uid': JLeaf(str)
+        }, allow_unknown=True))
         self.assertGreaterEqual(len(data['keys']), 1)
-        self.assertIn('max_buckets', data)
-        self.assertIn('subusers', data)
-        self.assertIn('suspended', data)
-        self.assertIn('swift_keys', data)
-        self.assertIn('tenant', data)
-        self.assertIn('user_id', data)
 
     def test_get(self):
         data = self.get_rgw_user('admin')
@@ -261,7 +351,7 @@ class RgwUserTest(RgwTestCase):
         self.assertGreaterEqual(len(data), 1)
         self.assertIn('admin', data)
 
-    def test_create_update_delete(self):
+    def test_create_get_update_delete(self):
         # Create a new user.
         self._post('/api/rgw/user', params={
             'uid': 'teuth-test-user',
@@ -277,7 +367,9 @@ class RgwUserTest(RgwTestCase):
         data = self.get_rgw_user('teuth-test-user')
         self.assertStatus(200)
         self._assert_user_data(data)
+        self.assertEqual(data['tenant'], '')
         self.assertEqual(data['user_id'], 'teuth-test-user')
+        self.assertEqual(data['uid'], 'teuth-test-user')
 
         # Update the user.
         self._put(
@@ -302,6 +394,49 @@ class RgwUserTest(RgwTestCase):
         self.assertIn('"HostId"', resp['detail'])
         self.assertIn('"RequestId"', resp['detail'])
 
+    def test_create_get_update_delete_w_tenant(self):
+        # Create a new user.
+        self._post('/api/rgw/user', params={
+            'uid': 'test01$teuth-test-user',
+            'display_name': 'display name'
+        })
+        self.assertStatus(201)
+        data = self.jsonBody()
+        self._assert_user_data(data)
+        self.assertEqual(data['user_id'], 'teuth-test-user')
+        self.assertEqual(data['display_name'], 'display name')
+
+        # Get the user.
+        data = self.get_rgw_user('test01$teuth-test-user')
+        self.assertStatus(200)
+        self._assert_user_data(data)
+        self.assertEqual(data['tenant'], 'test01')
+        self.assertEqual(data['user_id'], 'teuth-test-user')
+        self.assertEqual(data['uid'], 'test01$teuth-test-user')
+
+        # Update the user.
+        self._put(
+            '/api/rgw/user/test01$teuth-test-user',
+            params={
+                'display_name': 'new name'
+            })
+        self.assertStatus(200)
+        data = self.jsonBody()
+        self._assert_user_data(data)
+        self.assertEqual(data['display_name'], 'new name')
+
+        # Delete the user.
+        self._delete('/api/rgw/user/test01$teuth-test-user')
+        self.assertStatus(204)
+        self.get_rgw_user('test01$teuth-test-user')
+        self.assertStatus(500)
+        resp = self.jsonBody()
+        self.assertIn('detail', resp)
+        self.assertIn('failed request with status code 404', resp['detail'])
+        self.assertIn('"Code":"NoSuchUser"', resp['detail'])
+        self.assertIn('"HostId"', resp['detail'])
+        self.assertIn('"RequestId"', resp['detail'])
+
 
 class RgwUserCapabilityTest(RgwTestCase):
 
index 4576b97d81d3a4350eae71f5770a751eb88e3c12..e533287df27cceba0fd7e373f984fd02d21365f1 100644 (file)
@@ -105,11 +105,27 @@ class RgwRESTController(RESTController):
 @ApiController('/rgw/bucket', Scope.RGW)
 class RgwBucket(RgwRESTController):
 
+    def _append_bid(self, bucket):
+        """
+        Append the bucket identifier that looks like [<tenant>/]<bucket>.
+        See http://docs.ceph.com/docs/nautilus/radosgw/multitenancy/ for
+        more information.
+        :param bucket: The bucket parameters.
+        :type bucket: dict
+        :return: The modified bucket parameters including the 'bid' parameter.
+        :rtype: dict
+        """
+        if isinstance(bucket, dict):
+            bucket['bid'] = '{}/{}'.format(bucket['tenant'], bucket['bucket']) \
+                if bucket['tenant'] else bucket['bucket']
+        return bucket
+
     def list(self):
         return self.proxy('GET', 'bucket')
 
     def get(self, bucket):
-        return self.proxy('GET', 'bucket', {'bucket': bucket})
+        result = self.proxy('GET', 'bucket', {'bucket': bucket})
+        return self._append_bid(result)
 
     def create(self, bucket, uid):
         try:
@@ -119,11 +135,12 @@ class RgwBucket(RgwRESTController):
             raise DashboardException(e, http_status_code=500, component='rgw')
 
     def set(self, bucket, bucket_id, uid):
-        return self.proxy('PUT', 'bucket', {
+        result = self.proxy('PUT', 'bucket', {
             'bucket': bucket,
             'bucket-id': bucket_id,
             'uid': uid
         }, json_response=False)
+        return self._append_bid(result)
 
     def delete(self, bucket, purge_objects='true'):
         return self.proxy('DELETE', 'bucket', {
@@ -135,11 +152,27 @@ class RgwBucket(RgwRESTController):
 @ApiController('/rgw/user', Scope.RGW)
 class RgwUser(RgwRESTController):
 
+    def _append_uid(self, user):
+        """
+        Append the user identifier that looks like [<tenant>$]<user>.
+        See http://docs.ceph.com/docs/jewel/radosgw/multitenancy/ for
+        more information.
+        :param user: The user parameters.
+        :type user: dict
+        :return: The modified user parameters including the 'uid' parameter.
+        :rtype: dict
+        """
+        if isinstance(user, dict):
+            user['uid'] = '{}${}'.format(user['tenant'], user['user_id']) \
+                if user['tenant'] else user['user_id']
+        return user
+
     def list(self):
         return self.proxy('GET', 'metadata/user')
 
     def get(self, uid):
-        return self.proxy('GET', 'user', {'uid': uid})
+        result = self.proxy('GET', 'user', {'uid': uid})
+        return self._append_uid(result)
 
     def create(self, uid, display_name, email=None, max_buckets=None,
                suspended=None, generate_key=None, access_key=None,
@@ -159,7 +192,8 @@ class RgwUser(RgwRESTController):
             params['access-key'] = access_key
         if secret_key is not None:
             params['secret-key'] = secret_key
-        return self.proxy('PUT', 'user', params)
+        result = self.proxy('PUT', 'user', params)
+        return self._append_uid(result)
 
     def set(self, uid, display_name=None, email=None, max_buckets=None,
             suspended=None):
@@ -172,7 +206,8 @@ class RgwUser(RgwRESTController):
             params['max-buckets'] = max_buckets
         if suspended is not None:
             params['suspended'] = suspended
-        return self.proxy('POST', 'user', params)
+        result = self.proxy('POST', 'user', params)
+        return self._append_uid(result)
 
     def delete(self, uid):
         try:
index 5164c4b76ac863138523f07ce0497c802dff0284..a77015735e77cedc4a363959349fa8cb9c13a4b3 100644 (file)
@@ -205,7 +205,7 @@ const routes: Routes = [
         children: [
           { path: '', component: RgwBucketListComponent },
           { path: 'add', component: RgwBucketFormComponent, data: { breadcrumbs: 'Add' } },
-          { path: 'edit/:bucket', component: RgwBucketFormComponent, data: { breadcrumbs: 'Edit' } }
+          { path: 'edit/:bid', component: RgwBucketFormComponent, data: { breadcrumbs: 'Edit' } }
         ]
       }
     ]
index 306d6de9d02bb4ba2b00a1eb83316ada3551ada7..3ccb9cb684f061a82a40face882dd12272e93775 100644 (file)
@@ -6,7 +6,7 @@
           <tr>
             <td i18n
                 class="bold col-sm-1">Name</td>
-            <td class="col-sm-3">{{ bucket.bucket }}</td>
+            <td class="col-sm-3">{{ bucket.bid }}</td>
           </tr>
           <tr>
             <td i18n
index 391378c03e3166a7e8639b0f4b02bc71f1bcbc8c..2d2ddf4967f123453836a274d1e15ba02b1e4e9d 100644 (file)
 
         <!-- Name -->
         <div class="form-group"
-             [ngClass]="{'has-error': bucketForm.showError('bucket', frm)}">
+             [ngClass]="{'has-error': bucketForm.showError('bid', frm)}">
           <label class="control-label col-sm-3"
-                 for="bucket">
+                 for="bid">
             <ng-container i18n>Name</ng-container>
             <span class="required"
                   *ngIf="!editing"></span>
           </label>
           <div class="col-sm-9">
-            <input id="bucket"
-                   name="bucket"
+            <input id="bid"
+                   name="bid"
                    class="form-control"
                    type="text"
                    i18n-placeholder
                    placeholder="Name..."
-                   formControlName="bucket"
+                   formControlName="bid"
                    [readonly]="editing"
                    autofocus>
             <span class="help-block"
-                  *ngIf="bucketForm.showError('bucket', frm, 'required')"
+                  *ngIf="bucketForm.showError('bid', frm, 'required')"
                   i18n>This field is required.</span>
             <span class="help-block"
-                  *ngIf="bucketForm.showError('bucket', frm, 'bucketNameInvalid')"
+                  *ngIf="bucketForm.showError('bid', frm, 'bucketNameInvalid')"
                   i18n>The value is not valid.</span>
             <span class="help-block"
-                  *ngIf="bucketForm.showError('bucket', frm, 'bucketNameExists')"
+                  *ngIf="bucketForm.showError('bid', frm, 'bucketNameExists')"
                   i18n>The chosen name is already in use.</span>
           </div>
         </div>
index e8b812af4e84b098a7325802c5b8e60b356b0677..893a68e57123a31e886455e1cfb065fb06725a70 100644 (file)
@@ -34,7 +34,7 @@ export class RgwBucketFormComponent implements OnInit {
   createForm() {
     this.bucketForm = this.formBuilder.group({
       id: [null],
-      bucket: [null, [Validators.required], [this.bucketNameValidator()]],
+      bid: [null, [Validators.required], [this.bucketNameValidator()]],
       owner: [null, [Validators.required]]
     });
   }
@@ -47,15 +47,15 @@ export class RgwBucketFormComponent implements OnInit {
 
     // Process route parameters.
     this.route.params.subscribe(
-      (params: { bucket: string }) => {
-        if (!params.hasOwnProperty('bucket')) {
+      (params: { bid: string }) => {
+        if (!params.hasOwnProperty('bid')) {
           return;
         }
-        params.bucket = decodeURIComponent(params.bucket);
+        const bid = decodeURIComponent(params.bid);
         this.loading = true;
         // Load the bucket data in 'edit' mode.
         this.editing = true;
-        this.rgwBucketService.get(params.bucket).subscribe((resp: object) => {
+        this.rgwBucketService.get(bid).subscribe((resp: object) => {
           this.loading = false;
           // Get the default values.
           const defaults = _.clone(this.bucketForm.value);
@@ -82,12 +82,12 @@ export class RgwBucketFormComponent implements OnInit {
     if (this.bucketForm.pristine) {
       this.goToListView();
     }
-    const bucketCtl = this.bucketForm.get('bucket');
+    const bidCtl = this.bucketForm.get('bid');
     const ownerCtl = this.bucketForm.get('owner');
     if (this.editing) {
       // Edit
       const idCtl = this.bucketForm.get('id');
-      this.rgwBucketService.update(bucketCtl.value, idCtl.value, ownerCtl.value).subscribe(
+      this.rgwBucketService.update(bidCtl.value, idCtl.value, ownerCtl.value).subscribe(
         () => {
           this.goToListView();
         },
@@ -98,7 +98,7 @@ export class RgwBucketFormComponent implements OnInit {
       );
     } else {
       // Add
-      this.rgwBucketService.create(bucketCtl.value, ownerCtl.value).subscribe(
+      this.rgwBucketService.create(bidCtl.value, ownerCtl.value).subscribe(
         () => {
           this.goToListView();
         },
index 3c4e8ce054796cff29d9261eadc822b6469d9567..2a2791f7a92296bf7234bf189c5b719d65b0b2b6 100644 (file)
@@ -39,7 +39,7 @@ export class RgwBucketListComponent {
     this.columns = [
       {
         name: this.i18n('Name'),
-        prop: 'bucket',
+        prop: 'bid',
         flexGrow: 1
       },
       {
@@ -49,7 +49,7 @@ export class RgwBucketListComponent {
       }
     ];
     const getBucketUri = () =>
-      this.selection.first() && `${encodeURI(this.selection.first().bucket)}`;
+      this.selection.first() && `${encodeURIComponent(this.selection.first().bid)}`;
     const addAction: CdTableAction = {
       permission: 'create',
       icon: 'fa-plus',
@@ -97,7 +97,7 @@ export class RgwBucketListComponent {
             // Delete all selected data table rows.
             observableForkJoin(
               this.selection.selected.map((bucket: any) => {
-                return this.rgwBucketService.delete(bucket.bucket);
+                return this.rgwBucketService.delete(bucket.bid);
               })
             ).subscribe(
               null,
index 4a8ae5cae9b847b3ac299fca067cda942905ab6a..ab5f22f738721775580c99e38122ed06f596e2cf 100644 (file)
@@ -7,7 +7,7 @@
           <tr>
             <td i18n
                 class="bold col-sm-1">Username</td>
-            <td class="col-sm-3">{{ user.user_id }}</td>
+            <td class="col-sm-3">{{ user.uid }}</td>
           </tr>
           <tr>
             <td i18n
                 class="bold col-sm-1">Suspended</td>
             <td class="col-sm-3">{{ user.suspended ? "Yes" : "No" }}</td>
           </tr>
+          <tr>
+            <td i18n
+                class="bold col-sm-1">System</td>
+            <td class="col-sm-3">{{ user.system ? "Yes" : "No" }}</td>
+          </tr>
           <tr>
             <td i18n
                 class="bold col-sm-1">Maximum buckets</td>
index 108efc63b9f04b8571e3cdcbc612e7b8e28540d6..daa2ff74eaaeae515782c04266854546ba4422e5 100644 (file)
@@ -64,11 +64,9 @@ export class RgwUserDetailsComponent implements OnChanges, OnInit {
       this.user.caps = _.sortBy(this.user.caps, 'type');
 
       // Load the user/bucket quota of the selected user.
-      if (this.user.tenant === '') {
-        this.rgwUserService.getQuota(this.user.user_id).subscribe((resp: object) => {
-          _.extend(this.user, resp);
-        });
-      }
+      this.rgwUserService.getQuota(this.user.uid).subscribe((resp: object) => {
+        _.extend(this.user, resp);
+      });
 
       // Process the keys.
       this.keys = [];
index 33f6726169ca2d54d3f3e30690e8c17520c898ac..88466bbb1f9d49b276672a7fc3a08c3468ff9afc 100644 (file)
 
         <!-- Username -->
         <div class="form-group"
-             [ngClass]="{'has-error': userForm.showError('user_id', frm)}">
+             [ngClass]="{'has-error': userForm.showError('uid', frm)}">
           <label class="control-label col-sm-3"
-                 for="user_id">
+                 for="uid">
             <ng-container i18n>Username</ng-container>
             <span class="required"
                   *ngIf="!editing">
             </span>
           </label>
           <div class="col-sm-9">
-            <input id="user_id"
+            <input id="uid"
                    class="form-control"
                    type="text"
-                   formControlName="user_id"
+                   formControlName="uid"
                    [readonly]="editing"
                    autofocus>
             <span class="help-block"
-                  *ngIf="userForm.showError('user_id', frm, 'required')"
+                  *ngIf="userForm.showError('uid', frm, 'required')"
                   i18n>This field is required.</span>
             <span class="help-block"
-                  *ngIf="userForm.showError('user_id', frm, 'notUnique')"
+                  *ngIf="userForm.showError('uid', frm, 'notUnique')"
                   i18n>The chosen user ID is already in use.</span>
           </div>
         </div>
index 498c0fa0e58bc57d0c9ca62b005aff63daa6adf9..6e183172da8e706aaeacf39729ad915a403cdb22 100644 (file)
@@ -119,19 +119,19 @@ describe('RgwUserFormComponent', () => {
     });
 
     it('should validate that username is required', () => {
-      formHelper.expectErrorChange('user_id', '', 'required', true);
+      formHelper.expectErrorChange('uid', '', 'required', true);
     });
 
     it('should validate that username is valid', fakeAsync(() => {
-      formHelper.setValue('user_id', 'ab', true);
+      formHelper.setValue('uid', 'ab', true);
       tick(500);
-      formHelper.expectValid('user_id');
+      formHelper.expectValid('uid');
     }));
 
     it('should validate that username is invalid', fakeAsync(() => {
-      formHelper.setValue('user_id', 'abc', true);
+      formHelper.setValue('uid', 'abc', true);
       tick(500);
-      formHelper.expectError('user_id', 'notUnique');
+      formHelper.expectError('uid', 'notUnique');
     }));
   });
 
index 7daf07ebf1b2a6f20855e350595568b3eb3a517a..db8ebd178d85d9fb1df7f5ac53fb5b6919a7a08d 100644 (file)
@@ -51,7 +51,7 @@ export class RgwUserFormComponent implements OnInit {
   createForm() {
     this.userForm = this.formBuilder.group({
       // General
-      user_id: [
+      uid: [
         null,
         [Validators.required],
         [CdValidators.unique(this.rgwUserService.exists, this.rgwUserService)]
@@ -154,13 +154,14 @@ export class RgwUserFormComponent implements OnInit {
       if (!params.hasOwnProperty('uid')) {
         return;
       }
+      const uid = decodeURIComponent(params.uid);
       this.loading = true;
       // Load the user data in 'edit' mode.
       this.editing = true;
       // Load the user and quota information.
       const observables = [];
-      observables.push(this.rgwUserService.get(params.uid));
-      observables.push(this.rgwUserService.getQuota(params.uid));
+      observables.push(this.rgwUserService.get(uid));
+      observables.push(this.rgwUserService.getQuota(uid));
       observableForkJoin(observables).subscribe(
         (resp: any[]) => {
           this.loading = false;
@@ -224,7 +225,7 @@ export class RgwUserFormComponent implements OnInit {
     if (this.userForm.pristine) {
       this.goToListView();
     }
-    const uid = this.userForm.getValue('user_id');
+    const uid = this.userForm.getValue('uid');
     if (this.editing) {
       // Edit
       if (this._isGeneralDirty()) {
@@ -284,7 +285,7 @@ export class RgwUserFormComponent implements OnInit {
       'full-control': 'full',
       'read-write': 'readwrite'
     };
-    const uid = this.userForm.getValue('user_id');
+    const uid = this.userForm.getValue('uid');
     const args = {
       subuser: subuser.id,
       access:
@@ -323,7 +324,7 @@ export class RgwUserFormComponent implements OnInit {
     const subuser = this.subusers[index];
     // Create an observable to delete the subuser when the form is submitted.
     this.submitObservables.push(
-      this.rgwUserService.deleteSubuser(this.userForm.getValue('user_id'), subuser.id)
+      this.rgwUserService.deleteSubuser(this.userForm.getValue('uid'), subuser.id)
     );
     // Remove the associated S3 keys.
     this.s3Keys = this.s3Keys.filter((key) => {
@@ -343,7 +344,7 @@ export class RgwUserFormComponent implements OnInit {
    * Add/Update a capability.
    */
   setCapability(cap: RgwUserCapability, index?: number) {
-    const uid = this.userForm.getValue('user_id');
+    const uid = this.userForm.getValue('uid');
     if (_.isNumber(index)) {
       // Modify
       const oldCap = this.capabilities[index];
@@ -375,7 +376,7 @@ export class RgwUserFormComponent implements OnInit {
     const cap = this.capabilities[index];
     // Create an observable to delete the capability when the form is submitted.
     this.submitObservables.push(
-      this.rgwUserService.deleteCapability(this.userForm.getValue('user_id'), cap.type, cap.perm)
+      this.rgwUserService.deleteCapability(this.userForm.getValue('uid'), cap.type, cap.perm)
     );
     // Remove the capability to update the UI.
     this.capabilities.splice(index, 1);
@@ -423,7 +424,7 @@ export class RgwUserFormComponent implements OnInit {
     const key = this.s3Keys[index];
     // Create an observable to delete the S3 key when the form is submitted.
     this.submitObservables.push(
-      this.rgwUserService.deleteS3Key(this.userForm.getValue('user_id'), key.access_key)
+      this.rgwUserService.deleteS3Key(this.userForm.getValue('uid'), key.access_key)
     );
     // Remove the S3 key to update the UI.
     this.s3Keys.splice(index, 1);
@@ -436,7 +437,7 @@ export class RgwUserFormComponent implements OnInit {
    * @param {number | undefined} index The subuser to show.
    */
   showSubuserModal(index?: number) {
-    const uid = this.userForm.getValue('user_id');
+    const uid = this.userForm.getValue('uid');
     const modalRef = this.bsModalService.show(RgwUserSubuserModalComponent);
     if (_.isNumber(index)) {
       // Edit
@@ -555,7 +556,7 @@ export class RgwUserFormComponent implements OnInit {
    */
   private _getCreateArgs() {
     const result = {
-      uid: this.userForm.getValue('user_id'),
+      uid: this.userForm.getValue('uid'),
       display_name: this.userForm.getValue('display_name'),
       suspended: this.userForm.getValue('suspended'),
       email: '',
@@ -645,9 +646,9 @@ export class RgwUserFormComponent implements OnInit {
   private _getS3KeyUserCandidates() {
     let result = [];
     // Add the current user id.
-    const user_id = this.userForm.getValue('user_id');
-    if (_.isString(user_id) && !_.isEmpty(user_id)) {
-      result.push(user_id);
+    const uid = this.userForm.getValue('uid');
+    if (_.isString(uid) && !_.isEmpty(uid)) {
+      result.push(uid);
     }
     // Append the subusers.
     this.subusers.forEach((subUser) => {
index 2dacb8390c55b4ad41c5b8e4f254bcb4e5149e80..122c7564f8b6f0815b4b04e03c4ddf4a5c572423 100644 (file)
@@ -40,7 +40,7 @@ export class RgwUserListComponent {
     this.columns = [
       {
         name: this.i18n('Username'),
-        prop: 'user_id',
+        prop: 'uid',
         flexGrow: 1
       },
       {
@@ -65,7 +65,8 @@ export class RgwUserListComponent {
         flexGrow: 1
       }
     ];
-    const getUserUri = () => this.selection.first() && this.selection.first().user_id;
+    const getUserUri = () =>
+      this.selection.first() && `${encodeURIComponent(this.selection.first().uid)}`;
     const addAction: CdTableAction = {
       permission: 'create',
       icon: 'fa-plus',
@@ -111,7 +112,7 @@ export class RgwUserListComponent {
             // Delete all selected data table rows.
             observableForkJoin(
               this.selection.selected.map((user: any) => {
-                return this.rgwUserService.delete(user.user_id);
+                return this.rgwUserService.delete(user.uid);
               })
             ).subscribe(
               null,