]> git-server-git.apps.pok.os.sepia.ceph.com Git - ceph.git/commitdiff
mgr/dashboard : Fix access denied issue for readonly user and user role
authorAbhishek Desai <abhishek.desai1@ibm.com>
Mon, 20 Jul 2026 15:01:51 +0000 (20:31 +0530)
committerAbhishek Desai <abhishek.desai1@ibm.com>
Mon, 20 Jul 2026 15:07:06 +0000 (20:37 +0530)
fixes : https://tracker.ceph.com/issues/78418
Signed-off-by: Abhishek Desai <abhishek.desai1@ibm.com>
src/pybind/mgr/dashboard/frontend/src/app/ceph/block/mirroring/overview/overview.component.html
src/pybind/mgr/dashboard/frontend/src/app/ceph/block/mirroring/overview/overview.component.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/core/navigation/administration/administration.component.html
src/pybind/mgr/dashboard/frontend/src/app/core/navigation/administration/administration.component.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/services/api-interceptor.service.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/services/api-interceptor.service.ts

index 44f6347b4510041e93c8db40fd0887fed4327c3f..19d2bfcf5c3706f31ac278ac47fc67b98c83e8bb 100644 (file)
             [attr.disabled]="!editing ? true : null"
             cdsText
           />
-          <cds-icon-button
-            kind="ghost"
-            size="md"
-            (click)="updateSiteName()"
-            [title]="editing ? 'Save' : 'Edit'"
-          >
-            <svg
-              [cdsIcon]="icons.edit"
-              [size]="icons.size32"
-              class="cds--btn__icon"
-              *ngIf="!editing"
-            ></svg>
-            <svg
-              [cdsIcon]="icons.check"
-              [size]="icons.size32"
-              class="cds--btn__icon"
-              *ngIf="editing"
-            ></svg>
-          </cds-icon-button>
+          @if (permission.update) {
+            <cds-icon-button
+              kind="ghost"
+              size="md"
+              (click)="updateSiteName()"
+              [title]="editing ? 'Save' : 'Edit'"
+            >
+              <svg
+                [cdsIcon]="icons.edit"
+                [size]="icons.size32"
+                class="cds--btn__icon"
+                *ngIf="!editing"
+              ></svg>
+              <svg
+                [cdsIcon]="icons.check"
+                [size]="icons.size32"
+                class="cds--btn__icon"
+                *ngIf="editing"
+              ></svg>
+            </cds-icon-button>
+          }
           <cd-copy-2-clipboard-button
             [source]="siteName"
             [byId]="false"
index 5f5b21f11fc9093bf43c7b1b0bc8ea2de6b71ce4..d154d4740aa0eb56c6a6df1bcc2cc6e0dd556557 100644 (file)
@@ -2,12 +2,14 @@ import { HttpClientTestingModule } from '@angular/common/http/testing';
 import { ComponentFixture, TestBed } from '@angular/core/testing';
 import { ReactiveFormsModule } from '@angular/forms';
 import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
+import { By } from '@angular/platform-browser';
 import { RouterTestingModule } from '@angular/router/testing';
 
 import { NgbNavModule, NgbProgressbarModule } from '@ng-bootstrap/ng-bootstrap';
 import { of } from 'rxjs';
 
 import { RbdMirroringService } from '~/app/shared/api/rbd-mirroring.service';
+import { Permission } from '~/app/shared/models/permissions';
 import { SharedModule } from '~/app/shared/shared.module';
 import { configureTestBed } from '~/testing/unit-test-helper';
 import { DaemonListComponent } from '../daemon-list/daemon-list.component';
@@ -78,4 +80,20 @@ describe('OverviewComponent', () => {
       expect(rbdMirroringService.setSiteName).toHaveBeenCalledWith('new-site-A');
     });
   });
+
+  describe('site name edit button visibility', () => {
+    const editButton = () => fixture.debugElement.query(By.css('cds-icon-button'));
+
+    it('should show the edit button when user has update permission', () => {
+      component.permission = new Permission(['read', 'update']);
+      fixture.detectChanges();
+      expect(editButton()).toBeTruthy();
+    });
+
+    it('should hide the edit button when user lacks update permission', () => {
+      component.permission = new Permission(['read']);
+      fixture.detectChanges();
+      expect(editButton()).toBeFalsy();
+    });
+  });
 });
index d2d7b840707120c3bd5ec5bc567eeab31ab1cf64..804c4dbc566114b5e343b3e6ebfddbd97b0e920f 100644 (file)
       User management
     </button>
   </li>
-  <li
-    class="cds--overflow-menu-options__option mb-2"
-    *ngIf="configOptPermission.read"
-  >
-    <button
-      routerLink="/telemetry"
-      class="cds--overflow-menu-options__btn"
-      type="button"
-      i18n
-    >
-      Telemetry configuration
-    </button>
-  </li>
+  @if (configOptPermission.read) {
+    <li class="cds--overflow-menu-options__option mb-2">
+      <button
+        routerLink="/telemetry"
+        class="cds--overflow-menu-options__btn"
+        type="button"
+        i18n
+      >
+        Telemetry configuration
+      </button>
+    </li>
+  }
 </cds-overflow-menu>
 
 <ng-template #customTrigger>
-  <svg
-    cdsIcon="settings"
-    size="20"
-    title="Settings"
-    *ngIf="userPermission.read"
-  ></svg>
+  @if (userPermission.read || configOptPermission.read) {
+    <svg
+      cdsIcon="settings"
+      size="20"
+      title="Settings"
+    ></svg>
+  }
   <span
     i18n
     class="d-md-none"
index 29392785bca3debe459a186684d95fed3ee72a8f..515df21749d00cf008ed68a75d617daa07ba7c45 100644 (file)
@@ -1,5 +1,7 @@
 import { ComponentFixture, TestBed } from '@angular/core/testing';
+import { By } from '@angular/platform-browser';
 
+import { Permission } from '~/app/shared/models/permissions';
 import { SharedModule } from '~/app/shared/shared.module';
 import { configureTestBed } from '~/testing/unit-test-helper';
 import { AdministrationComponent } from './administration.component';
@@ -22,4 +24,34 @@ describe('AdministrationComponent', () => {
   it('should create', () => {
     expect(component).toBeTruthy();
   });
+
+  describe('settings icon visibility', () => {
+    const setPermissions = (user: string[], configOpt: string[]) => {
+      component.userPermission = new Permission(user);
+      component.configOptPermission = new Permission(configOpt);
+      fixture.detectChanges();
+    };
+
+    const settingsIcon = () => fixture.debugElement.query(By.css('[cdsIcon="settings"]'));
+
+    it('should show the settings icon when user has user.read', () => {
+      setPermissions(['read'], []);
+      expect(settingsIcon()).toBeTruthy();
+    });
+
+    it('should show the settings icon when user has configOpt.read', () => {
+      setPermissions([], ['read']);
+      expect(settingsIcon()).toBeTruthy();
+    });
+
+    it('should show the settings icon when user has both user.read and configOpt.read', () => {
+      setPermissions(['read'], ['read']);
+      expect(settingsIcon()).toBeTruthy();
+    });
+
+    it('should hide the settings icon when user has neither user.read nor configOpt.read', () => {
+      setPermissions([], []);
+      expect(settingsIcon()).toBeFalsy();
+    });
+  });
 });
index 07bcb2bf315fac7bfe6edf0ce69225277ccc1237..a24005867879b3241862319a144949d21a06e2fb 100644 (file)
@@ -109,15 +109,26 @@ describe('ApiInterceptorService', () => {
       );
     });
 
-    it('should redirect 403', () => {
+    it('should redirect 403 for api/ requests', () => {
       runRouterTest(
         {
           status: 403
         },
-        [['error'], {'state': {'header': 'Access Denied', 'icon': 'locked', 'message': 'Sorry, you don’t have permission to view this page or resource.', 'source': 'forbidden'}}] // prettier-ignore
+        [['error'], {'state': {'header': 'Access Denied', 'icon': 'locked', 'message': "Sorry, you don't have permission to view this page or resource.", 'source': 'forbidden'}}] // prettier-ignore
       );
     });
 
+    it('should not redirect 403 for ui-api/ requests', () => {
+      const uiApiUrl = 'ui-api/prometheus/prometheus-api-host';
+      httpClient.get(uiApiUrl).subscribe(
+        () => true,
+        (_resp) => undefined
+      );
+      httpTesting.expectOne(uiApiUrl).error(new ErrorEvent('abc'), { status: 403 });
+      httpTesting.verify();
+      expect(router.navigate).not.toHaveBeenCalled();
+    });
+
     it('should show notification (error string)', () => {
       runNotificationTest(
         'foobar',
index 59eaf018019c35039b3f15fe168bb083193e125e..9b7b7b8904fe2a32fe143728e0b0f7de33163952 100644 (file)
@@ -109,14 +109,16 @@ export class ApiInterceptorService implements HttpInterceptor {
               this.router.navigate(['/login']);
               break;
             case 403:
-              this.router.navigate(['error'], {
-                state: {
-                  message: $localize`Sorry, you don’t have permission to view this page or resource.`,
-                  header: $localize`Access Denied`,
-                  icon: 'locked',
-                  source: 'forbidden'
-                }
-              });
+              if (!request.url.startsWith('ui-api/')) {
+                this.router.navigate(['error'], {
+                  state: {
+                    message: $localize`Sorry, you don't have permission to view this page or resource.`,
+                    header: $localize`Access Denied`,
+                    icon: 'locked',
+                    source: 'forbidden'
+                  }
+                });
+              }
               break;
             default:
               timeoutId = this.prepareNotification(resp);