From: Sagar Gopale Date: Mon, 1 Jun 2026 12:29:40 +0000 (+0530) Subject: mgr/dashboard: fix-user-creation-validation X-Git-Url: http://git-server-git.apps.pok.os.sepia.ceph.com/?a=commitdiff_plain;h=70a122f21b07ffefa38984411965655f68c12cd4;p=ceph.git mgr/dashboard: fix-user-creation-validation Fixes: https://tracker.ceph.com/issues/77024 Signed-off-by: Sagar Gopale --- diff --git a/src/pybind/mgr/dashboard/frontend/cypress/e2e/ui/user-mgmt.po.ts b/src/pybind/mgr/dashboard/frontend/cypress/e2e/ui/user-mgmt.po.ts index 76679806a88..eb79748be1d 100644 --- a/src/pybind/mgr/dashboard/frontend/cypress/e2e/ui/user-mgmt.po.ts +++ b/src/pybind/mgr/dashboard/frontend/cypress/e2e/ui/user-mgmt.po.ts @@ -16,6 +16,15 @@ export class UserMgmtPageHelper extends PageHelper { cy.get('#name').type(name); cy.get('#email').type(email); + // Select role 'administrator' + cy.get('cds-combo-box[type="multi"] input.cds--text-input').first().click({ force: true }); + cy.get('.cds--list-box__menu.cds--multi-select').should('be.visible'); + cy.get('.cds--list-box__menu.cds--multi-select .cds--checkbox-label') + .contains('.cds--checkbox-label-text', 'administrator', { matchCase: false }) + .parent() + .click({ force: true }); + cy.get('body').type('{esc}'); + // Click the create button and wait for user to be made cy.get('[data-testid=submitBtn]').click(); this.getFirstTableCell(username).should('exist'); diff --git a/src/pybind/mgr/dashboard/frontend/src/app/core/auth/user-form/user-form.component.html b/src/pybind/mgr/dashboard/frontend/src/app/core/auth/user-form/user-form.component.html index ea8f8aed077..843c08e5b5c 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/core/auth/user-form/user-form.component.html +++ b/src/pybind/mgr/dashboard/frontend/src/app/core/auth/user-form/user-form.component.html @@ -1,73 +1,82 @@ -
+
-
-
{{ action | titlecase }} {{ resource | upperFirst }} + +
+ {{ action | titlecase }} {{ resource | upperFirst }}
- Username - + Username + - - This field is required. - + This field is required. - - The username already exists. - + The username already exists.
- Password - - - + Password + + + - Password confirmation doesn't match the password. + Password confirmation doesn't match the password. - This field is required. - + This field is required. + {{ passwordValuation }} @@ -75,142 +84,171 @@
- Confirm password - + + Confirm password + - Password confirmation doesn't match the password. - This field is required. + Password confirmation doesn't match the password. + This field is required.
-
- {{'Password Expiration Date'}} - +
+ {{ 'Password Expiration Date' }} + - The Dashboard setting defining the expiration interval of - passwords is currently set to 0. This means - if a date is set, the user password will only expire once. + The Dashboard setting defining the expiration interval of passwords is currently set + to 0. This means if a date is set, the user password will only expire + once. - Consider configuring the Dashboard setting - USER_PWD_EXPIRATION_SPAN - in order to let passwords expire periodically. + Consider configuring the Dashboard setting + USER_PWD_EXPIRATION_SPAN + in order to let passwords expire periodically. - - - - This field is required. + + + + This field is required.
- Full Name - + + Full Name +
- + Email - + - Invalid email. + Invalid email.
-
- +
+ + + This field is required. +
-
- Enabled +
+ Enabled
-
- User must change password at next login +
+ User must change password at next login
- +
-

You are about to remove "user read / update" permissions from your own user.

-
+

+ You are about to remove "user read / update" permissions from your own user. +

+

If you continue, you will no longer be able to add or remove roles from any user.

Are you sure you want to continue?
- + diff --git a/src/pybind/mgr/dashboard/frontend/src/app/core/auth/user-form/user-form.component.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/core/auth/user-form/user-form.component.spec.ts index 13034934486..850dff1c5a7 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/core/auth/user-form/user-form.component.spec.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/core/auth/user-form/user-form.component.spec.ts @@ -62,12 +62,16 @@ describe('UserFormComponent', () => { form = component.userForm; httpTesting = TestBed.inject(HttpTestingController); userService = TestBed.inject(UserService); + spyOn(userService, 'validatePassword').and.returnValue( + of({ valid: true, credits: 10, valuation: 'strong' }) + ); modalService = TestBed.inject(ModalCdsService); router = TestBed.inject(Router); spyOn(router, 'navigate'); fixture.detectChanges(); const notify = TestBed.inject(NotificationService); spyOn(notify, 'show'); + spyOn(TestBed.inject(AuthStorageService), 'isSSO').and.returnValue(false); formHelper = new FormHelper(form); }); @@ -109,6 +113,34 @@ describe('UserFormComponent', () => { formHelper.expectErrorChange('email', 'aaa', 'email'); }); + it('should validate password required in create mode', () => { + formHelper.expectErrorChange('password', '', 'required'); + formHelper.expectValidChange('password', 'pass123'); + }); + + it('should validate confirmpassword required in create mode', () => { + formHelper.setValue('password', 'pass123'); + formHelper.expectErrorChange('confirmpassword', '', 'required'); + formHelper.expectValidChange('confirmpassword', 'pass123'); + }); + + it('should validate roles required in create mode', () => { + formHelper.expectErrorChange('roles', [], 'required'); + formHelper.expectValidChange('roles', ['administrator']); + }); + + it('should not validate password and roles if SSO is enabled', () => { + component.isSSO = true; + component.createForm(); + form = component.userForm; + form.get('password').updateValueAndValidity(); + expect(form.get('password').valid).toBeTruthy(); + form.get('confirmpassword').updateValueAndValidity(); + expect(form.get('confirmpassword').valid).toBeTruthy(); + form.get('roles').updateValueAndValidity(); + expect(form.get('roles').valid).toBeTruthy(); + }); + it('should set mode', () => { expect(component.mode).toBeUndefined(); }); @@ -210,6 +242,13 @@ describe('UserFormComponent', () => { expect(component.mode).toBe('editing'); }); + it('should not validate password required in edit mode', () => { + form.get('password').setValue(''); + expect(form.get('password').valid).toBeTruthy(); + form.get('confirmpassword').setValue(''); + expect(form.get('confirmpassword').valid).toBeTruthy(); + }); + it('should alert if user is removing needed role permission', () => { spyOn(TestBed.inject(AuthStorageService), 'getUsername').and.callFake(() => user.username); let modalBodyTpl = null; diff --git a/src/pybind/mgr/dashboard/frontend/src/app/core/auth/user-form/user-form.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/core/auth/user-form/user-form.component.ts index c67dbad95d3..6ee9b986525 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/core/auth/user-form/user-form.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/core/auth/user-form/user-form.component.ts @@ -58,6 +58,7 @@ export class UserFormComponent extends CdForm implements OnInit { pwdExpirationFormat = 'YYYY-MM-DD'; selectedRole: string[]; passwordexp: boolean = false; + isSSO = false; constructor( private authService: AuthService, private authStorageService: AuthStorageService, @@ -74,6 +75,7 @@ export class UserFormComponent extends CdForm implements OnInit { ) { super(); this.resource = $localize`user`; + this.isSSO = this.authStorageService.isSSO(); this.createForm(); this.messages = new SelectMessages({ empty: $localize`There are no roles.` }); } @@ -92,8 +94,12 @@ export class UserFormComponent extends CdForm implements OnInit { name: [''], password: [ '', - [], - + [ + (control) => + this.mode === this.userFormMode.editing || this.isSSO + ? null + : Validators.required(control) + ], [ CdValidators.passwordPolicy( this.userService, @@ -107,10 +113,18 @@ export class UserFormComponent extends CdForm implements OnInit { ) ] ], - confirmpassword: [''], + confirmpassword: [ + '', + [ + (control) => + this.mode === this.userFormMode.editing || this.isSSO + ? null + : Validators.required(control) + ] + ], pwdExpirationDate: [''], email: ['', [CdValidators.email]], - roles: [[]], + roles: [[], [(control) => (this.isSSO ? null : Validators.required(control))]], enabled: [true, [Validators.required]], pwdUpdateRequired: [true] }, @@ -129,6 +143,8 @@ export class UserFormComponent extends CdForm implements OnInit { this.action = this.actionLabels.CREATE; this.passwordexp = true; } + this.userForm.get('password').updateValueAndValidity(); + this.userForm.get('confirmpassword').updateValueAndValidity(); const observables = [this.roleService.list(), this.settingsService.getStandardSettings()]; observableForkJoin(observables).subscribe( @@ -149,6 +165,7 @@ export class UserFormComponent extends CdForm implements OnInit { expirationDate.add(this.pwdExpirationSettings.pwdExpirationSpan, 'day'); pwdExpirationDateField.setValue(expirationDate.format(this.pwdExpirationFormat)); pwdExpirationDateField.setValidators([Validators.required]); + pwdExpirationDateField.updateValueAndValidity(); } this.loadingReady(); 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 997564a983c..288151c24fa 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 @@ -349,7 +349,8 @@ export class CdValidators { return null; } if (ctrl1.value !== ctrl2.value) { - ctrl2.setErrors({ match: true }); + const errors = _.merge({}, ctrl2.errors, { match: true }); + ctrl2.setErrors(errors); } else { const hasError = ctrl2.hasError('match'); if (hasError) {