From: Nizamudeen A Date: Sat, 16 Jan 2021 14:40:36 +0000 (+0530) Subject: mgr/dashboard: Splitting tenant$user when creating rgw user X-Git-Tag: v16.2.0~161^2 X-Git-Url: http://git-server-git.apps.pok.os.sepia.ceph.com/?a=commitdiff_plain;h=8cb8c8fb981ca7e8b728a72094bcc555c49cd4dd;p=ceph.git mgr/dashboard: Splitting tenant$user when creating rgw user Fixes: https://tracker.ceph.com/issues/47378 Signed-off-by: Nizamudeen A (cherry picked from commit 7f4387d34fd073a3b0d8c828fecdc5df4b498122) --- diff --git a/src/pybind/mgr/dashboard/frontend/cypress/integration/rgw/users.e2e-spec.ts b/src/pybind/mgr/dashboard/frontend/cypress/integration/rgw/users.e2e-spec.ts index 1b580db7dcfc..b5f366a09093 100644 --- a/src/pybind/mgr/dashboard/frontend/cypress/integration/rgw/users.e2e-spec.ts +++ b/src/pybind/mgr/dashboard/frontend/cypress/integration/rgw/users.e2e-spec.ts @@ -2,7 +2,9 @@ import { UsersPageHelper } from './users.po'; describe('RGW users page', () => { const users = new UsersPageHelper(); - const user_name = 'e2e_000user_create_edit_delete'; + const tenant = 'e2e_000tenant'; + const user_id = 'e2e_000user_create_edit_delete'; + const user_name = tenant + '$' + user_id; beforeEach(() => { cy.login(); @@ -19,8 +21,8 @@ describe('RGW users page', () => { describe('create, edit & delete user tests', () => { it('should create user', () => { users.navigateTo('create'); - users.create(user_name, 'Some Name', 'original@website.com', '1200'); - users.getFirstTableCell(user_name).should('exist'); + users.create(tenant, user_id, 'Some Name', 'original@website.com', '1200'); + users.getFirstTableCell(user_id).should('exist'); }); it('should edit users full name, email and max buckets', () => { diff --git a/src/pybind/mgr/dashboard/frontend/cypress/integration/rgw/users.po.ts b/src/pybind/mgr/dashboard/frontend/cypress/integration/rgw/users.po.ts index 6796ff5be5d5..7ded71f59a92 100644 --- a/src/pybind/mgr/dashboard/frontend/cypress/integration/rgw/users.po.ts +++ b/src/pybind/mgr/dashboard/frontend/cypress/integration/rgw/users.po.ts @@ -9,10 +9,13 @@ export class UsersPageHelper extends PageHelper { pages = pages; @PageHelper.restrictTo(pages.create.url) - create(username: string, fullname: string, email: string, maxbuckets: string) { - // Enter in username - cy.get('#uid').type(username); - + create(tenant: string, user_id: string, fullname: string, email: string, maxbuckets: string) { + // Enter in user_id + cy.get('#user_id').type(user_id); + // Show Tenanat + cy.get('#show_tenant').click({ force: true }); + // Enter in tenant + cy.get('#tenant').type(tenant); // Enter in full name cy.get('#display_name').click().type(fullname); @@ -26,7 +29,7 @@ export class UsersPageHelper extends PageHelper { // Click the create button and wait for user to be made cy.contains('button', 'Create User').click(); - this.getFirstTableCell(username).should('exist'); + this.getFirstTableCell(tenant + '$' + user_id).should('exist'); } @PageHelper.restrictTo(pages.index.url) @@ -54,26 +57,27 @@ export class UsersPageHelper extends PageHelper { } invalidCreate() { + const tenant = '000invalid_tenant'; const uname = '000invalid_create_user'; // creating this user in order to check that you can't give two users the same name this.navigateTo('create'); - this.create(uname, 'xxx', 'xxx@xxx', '1'); + this.create(tenant, uname, 'xxx', 'xxx@xxx', '1'); this.navigateTo('create'); // Username - cy.get('#uid') + cy.get('#user_id') // No username had been entered. Field should be invalid .should('have.class', 'ng-invalid') // Try to give user already taken name. Should make field invalid. - .type(uname) - .blur() - .should('have.class', 'ng-invalid'); - cy.contains('#uid + .invalid-feedback', 'The chosen user ID is already in use.'); + .type(uname); + cy.get('#show_tenant').click({ force: true }); + cy.get('#tenant').type(tenant).should('have.class', 'ng-invalid'); + cy.contains('#tenant + .invalid-feedback', 'The chosen user ID exists in this tenant.'); // check that username field is marked invalid if username has been cleared off - cy.get('#uid').clear().blur().should('have.class', 'ng-invalid'); - cy.contains('#uid + .invalid-feedback', 'This field is required.'); + cy.get('#user_id').clear().blur().should('have.class', 'ng-invalid'); + cy.contains('#user_id + .invalid-feedback', 'This field is required.'); // Full name cy.get('#display_name') @@ -96,14 +100,15 @@ export class UsersPageHelper extends PageHelper { cy.contains('#max_buckets + .invalid-feedback', 'The entered value must be >= 1.'); this.navigateTo(); - this.delete(uname); + this.delete(tenant + '$' + uname); } invalidEdit() { + const tenant = '000invalid_tenant'; const uname = '000invalid_edit_user'; // creating this user to edit for the test this.navigateTo('create'); - this.create(uname, 'xxx', 'xxx@xxx', '50'); + this.create(tenant, uname, 'xxx', 'xxx@xxx', '50'); this.navigateEdit(name); @@ -129,6 +134,6 @@ export class UsersPageHelper extends PageHelper { cy.contains('#max_buckets + .invalid-feedback', 'The entered value must be >= 1.'); this.navigateTo(); - this.delete(uname); + this.delete(tenant + '$' + uname); } } diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-details/rgw-user-details.component.html b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-details/rgw-user-details.component.html index bc821fba7c8e..ebd648d021ac 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-details/rgw-user-details.component.html +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-details/rgw-user-details.component.html @@ -10,6 +10,16 @@
+ + + + + + + + diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-details/rgw-user-details.component.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-details/rgw-user-details.component.spec.ts index 477cf180a9a1..126fdf44ff26 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-details/rgw-user-details.component.spec.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-details/rgw-user-details.component.spec.ts @@ -62,13 +62,13 @@ describe('RgwUserDetailsComponent', () => { const detailsTab = fixture.debugElement.nativeElement.querySelectorAll( '.table.table-striped.table-bordered tr td' ); - expect(detailsTab[6].textContent).toEqual('System'); - expect(detailsTab[7].textContent).toEqual('Yes'); + expect(detailsTab[10].textContent).toEqual('System'); + expect(detailsTab[11].textContent).toEqual('Yes'); component.selection.system = 'false'; component.ngOnChanges(); fixture.detectChanges(); - expect(detailsTab[7].textContent).toEqual('No'); + expect(detailsTab[11].textContent).toEqual('No'); }); }); diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-form/rgw-user-form.component.html b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-form/rgw-user-form.component.html index 71653c4e950b..c0c619b3ec4e 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-form/rgw-user-form.component.html +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-form/rgw-user-form.component.html @@ -8,25 +8,66 @@ class="card-header">{{ action | titlecase }} {{ resource | upperFirst }}
- +
+ for="user_id" + i18n>User ID
- + This field is required. + The value is not valid. + The chosen user ID is already in use. +
+
+ + +
+
+
+ + +
+
+
+ + +
+ +
+ This field is required. + *ngIf="userForm.showError('tenant', frm, 'pattern')" + i18n>The value is not valid. The chosen user ID is already in use. + *ngIf="userForm.showError('tenant', frm, 'notUnique')" + i18n>The chosen user ID exists in this tenant.
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-form/rgw-user-form.component.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-form/rgw-user-form.component.spec.ts index 61e40c757edd..6bb86dd4cda8 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-form/rgw-user-form.component.spec.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-form/rgw-user-form.component.spec.ts @@ -156,21 +156,21 @@ describe('RgwUserFormComponent', () => { describe('username validation', () => { it('should validate that username is required', () => { - formHelper.expectErrorChange('uid', '', 'required', true); + formHelper.expectErrorChange('user_id', '', 'required', true); }); it('should validate that username is valid', fakeAsync(() => { spyOn(rgwUserService, 'get').and.returnValue(throwError('foo')); - formHelper.setValue('uid', 'ab', true); + formHelper.setValue('user_id', 'ab', true); tick(500); - formHelper.expectValid('uid'); + formHelper.expectValid('user_id'); })); it('should validate that username is invalid', fakeAsync(() => { spyOn(rgwUserService, 'get').and.returnValue(observableOf({})); - formHelper.setValue('uid', 'abc', true); + formHelper.setValue('user_id', 'abc', true); tick(500); - formHelper.expectError('uid', 'notUnique'); + formHelper.expectError('user_id', 'notUnique'); })); }); diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-form/rgw-user-form.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-form/rgw-user-form.component.ts index 75dd109cd123..899ed0298941 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-form/rgw-user-form.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-form/rgw-user-form.component.ts @@ -46,6 +46,9 @@ export class RgwUserFormComponent extends CdForm implements OnInit { subuserLabel: string; s3keyLabel: string; capabilityLabel: string; + usernameExists: boolean; + showTenant = false; + previousTenant: string = null; constructor( private formBuilder: CdFormBuilder, @@ -69,10 +72,31 @@ export class RgwUserFormComponent extends CdForm implements OnInit { createForm() { this.userForm = this.formBuilder.group({ // General - uid: [ + user_id: [ null, - [Validators.required], - this.editing ? [] : [CdValidators.unique(this.rgwUserService.exists, this.rgwUserService)] + [Validators.required, Validators.pattern(/^[a-zA-Z0-9!@#%^&*()_-]+$/)], + this.editing + ? [] + : [ + CdValidators.unique(this.rgwUserService.exists, this.rgwUserService, () => + this.userForm.getValue('tenant') + ) + ] + ], + show_tenant: [this.editing], + tenant: [ + null, + [Validators.pattern(/^[a-zA-Z0-9!@#%^&*()_-]+$/)], + this.editing + ? [] + : [ + CdValidators.unique( + this.rgwUserService.exists, + this.rgwUserService, + () => this.userForm.getValue('user_id'), + true + ) + ] ], display_name: [null, [Validators.required]], email: [ @@ -241,7 +265,7 @@ export class RgwUserFormComponent extends CdForm implements OnInit { this.goToListView(); return; } - const uid = this.userForm.getValue('uid'); + const uid = this.getUID(); if (this.editing) { // Edit if (this._isGeneralDirty()) { @@ -278,6 +302,27 @@ export class RgwUserFormComponent extends CdForm implements OnInit { }); } + updateFieldsWhenTenanted() { + this.showTenant = this.userForm.getValue('show_tenant'); + if (!this.showTenant) { + this.userForm.get('user_id').markAsUntouched(); + this.userForm.get('tenant').patchValue(this.previousTenant); + } else { + this.userForm.get('user_id').markAsTouched(); + this.previousTenant = this.userForm.get('tenant').value; + this.userForm.get('tenant').patchValue(null); + } + } + + getUID(): string { + let uid = this.userForm.getValue('user_id'); + const tenant = this.userForm?.getValue('tenant'); + if (tenant && tenant.length > 0) { + uid = `${this.userForm.getValue('tenant')}$${uid}`; + } + return uid; + } + /** * Validate the quota maximum size, e.g. 1096, 1K, 30M or 1.9MiB. */ @@ -303,7 +348,7 @@ export class RgwUserFormComponent extends CdForm implements OnInit { 'full-control': 'full', 'read-write': 'readwrite' }; - const uid = this.userForm.getValue('uid'); + const uid = this.getUID(); const args = { subuser: subuser.id, access: @@ -341,9 +386,7 @@ export class RgwUserFormComponent extends CdForm implements OnInit { deleteSubuser(index: number) { 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('uid'), subuser.id) - ); + this.submitObservables.push(this.rgwUserService.deleteSubuser(this.getUID(), subuser.id)); // Remove the associated S3 keys. this.s3Keys = this.s3Keys.filter((key) => { return key.user !== subuser.id; @@ -362,7 +405,7 @@ export class RgwUserFormComponent extends CdForm implements OnInit { * Add/Update a capability. */ setCapability(cap: RgwUserCapability, index?: number) { - const uid = this.userForm.getValue('uid'); + const uid = this.getUID(); if (_.isNumber(index)) { // Modify const oldCap = this.capabilities[index]; @@ -394,7 +437,7 @@ export class RgwUserFormComponent extends CdForm 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('uid'), cap.type, cap.perm) + this.rgwUserService.deleteCapability(this.getUID(), cap.type, cap.perm) ); // Remove the capability to update the UI. this.capabilities.splice(index, 1); @@ -452,9 +495,7 @@ export class RgwUserFormComponent extends CdForm implements OnInit { deleteS3Key(index: number) { 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('uid'), key.access_key) - ); + this.submitObservables.push(this.rgwUserService.deleteS3Key(this.getUID(), key.access_key)); // Remove the S3 key to update the UI. this.s3Keys.splice(index, 1); // Mark the form as dirty to be able to submit it. @@ -466,7 +507,7 @@ export class RgwUserFormComponent extends CdForm implements OnInit { * @param {number | undefined} index The subuser to show. */ showSubuserModal(index?: number) { - const uid = this.userForm.getValue('uid'); + const uid = this.getUID(); const modalRef = this.modalService.show(RgwUserSubuserModalComponent); if (_.isNumber(index)) { // Edit @@ -587,7 +628,7 @@ export class RgwUserFormComponent extends CdForm implements OnInit { */ private _getCreateArgs() { const result = { - uid: this.userForm.getValue('uid'), + uid: this.getUID(), display_name: this.userForm.getValue('display_name'), suspended: this.userForm.getValue('suspended'), email: '', @@ -689,7 +730,7 @@ export class RgwUserFormComponent extends CdForm implements OnInit { private _getS3KeyUserCandidates() { let result = []; // Add the current user id. - const uid = this.userForm.getValue('uid'); + const uid = this.getUID(); if (_.isString(uid) && !_.isEmpty(uid)) { result.push(uid); } diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-list/rgw-user-list.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-list/rgw-user-list.component.ts index fc70ebc1495f..9a90e572d648 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-list/rgw-user-list.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-list/rgw-user-list.component.ts @@ -54,6 +54,11 @@ export class RgwUserListComponent extends ListWithDetails { prop: 'uid', flexGrow: 1 }, + { + name: $localize`Tenant`, + prop: 'tenant', + flexGrow: 1 + }, { name: $localize`Full name`, prop: 'display_name', diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/forms/cd-validators.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/forms/cd-validators.ts index 3541c2ba22b3..1537c1500045 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/shared/forms/cd-validators.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/forms/cd-validators.ts @@ -347,18 +347,30 @@ export class CdValidators { static unique( serviceFn: existsServiceFn, serviceFnThis: any = null, + usernameFn?: Function, + uidfield = false, dueTime = 500 ): AsyncValidatorFn { + let uname: string; return (control: AbstractControl): Observable => { // Exit immediately if user has not interacted with the control yet // or the control value is empty. if (control.pristine || isEmptyInputValue(control.value)) { return observableOf(null); } + uname = control.value; + if (_.isFunction(usernameFn) && usernameFn() !== null && usernameFn() !== '') { + if (uidfield) { + uname = `${control.value}$${usernameFn()}`; + } else { + uname = `${usernameFn()}$${control.value}`; + } + } + // Forgot previous requests if a new one arrives within the specified // delay time. return observableTimer(dueTime).pipe( - switchMapTo(serviceFn.call(serviceFnThis, control.value)), + switchMapTo(serviceFn.call(serviceFnThis, uname)), map((resp: boolean) => { if (!resp) { return null;
Tenant{{ user.tenant }}
User ID{{ user.user_id }}
Username