]> git-server-git.apps.pok.os.sepia.ceph.com Git - ceph.git/commitdiff
mgr/dashboard: User permissions control (frontend) 22283/head
authorRicardo Marques <rimarques@suse.com>
Wed, 13 Jun 2018 17:29:30 +0000 (18:29 +0100)
committerRicardo Dias <rdias@suse.com>
Tue, 26 Jun 2018 11:29:08 +0000 (12:29 +0100)
Signed-off-by: Ricardo Marques <rimarques@suse.com>
32 files changed:
src/pybind/mgr/dashboard/frontend/src/app/app-routing.module.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-form/rbd-form.component.html
src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-form/rbd-form.component.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-list/rbd-list.component.html
src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-list/rbd-list.component.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-snapshot-list/rbd-snapshot-list.component.html
src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-snapshot-list/rbd-snapshot-list.component.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-snapshot-list/rbd-snapshot-list.component.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/hosts/hosts.component.html
src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/hosts/hosts.component.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/hosts/hosts.component.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-list/osd-list.component.html
src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-list/osd-list.component.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-list/osd-list.component.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard/health/health.component.html
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-bucket-list/rgw-bucket-list.component.html
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-list/rgw-user-list.component.html
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-list/rgw-user-list.component.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-list/rgw-user-list.component.ts
src/pybind/mgr/dashboard/frontend/src/app/core/core.module.ts
src/pybind/mgr/dashboard/frontend/src/app/core/forbidden/forbidden.component.html [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/src/app/core/forbidden/forbidden.component.scss [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/src/app/core/forbidden/forbidden.component.spec.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/src/app/core/forbidden/forbidden.component.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/src/app/core/navigation/navigation/navigation.component.html
src/pybind/mgr/dashboard/frontend/src/app/core/navigation/navigation/navigation.component.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/api/auth.service.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/models/credentials.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/models/permissions.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/src/app/shared/services/api-interceptor.service.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/services/auth-storage.service.ts

index 03ef61c751281f8368df23576567583e293e7363..3ed80e16c40c8be6f7004f7a2b8d1d11b4048edd 100644 (file)
@@ -20,6 +20,7 @@ import { RgwDaemonListComponent } from './ceph/rgw/rgw-daemon-list/rgw-daemon-li
 import { RgwUserFormComponent } from './ceph/rgw/rgw-user-form/rgw-user-form.component';
 import { RgwUserListComponent } from './ceph/rgw/rgw-user-list/rgw-user-list.component';
 import { LoginComponent } from './core/auth/login/login.component';
+import { ForbiddenComponent } from './core/forbidden/forbidden.component';
 import { NotFoundComponent } from './core/not-found/not-found.component';
 import { AuthGuardService } from './shared/services/auth-guard.service';
 import { ModuleStatusGuardService } from './shared/services/module-status-guard.service';
@@ -107,6 +108,7 @@ const routes: Routes = [
   { path: 'cephfs', component: CephfsListComponent, canActivate: [AuthGuardService] },
   { path: 'configuration', component: ConfigurationComponent, canActivate: [AuthGuardService] },
   { path: 'mirroring', component: MirroringComponent, canActivate: [AuthGuardService] },
+  { path: '403', component: ForbiddenComponent },
   { path: '404', component: NotFoundComponent },
   { path: 'osd', component: OsdListComponent, canActivate: [AuthGuardService] },
   { path: '**', redirectTo: '/404' }
index 9ee46fab099711ab9e57583bd7b7600afcaa0ec5..bb52bee45c1a9fc77481b4872d6456edd32c7bde 100644 (file)
           <div class="col-sm-9">
             <input class="form-control"
                    type="text"
+                   placeholder="Pool name..."
                    id="pool"
                    name="pool"
                    formControlName="pool"
-                   *ngIf="mode === 'editing'">
+                   *ngIf="mode === 'editing' || !poolPermission.read">
             <select id="pool"
                     name="pool"
                     class="form-control"
                     formControlName="pool"
-                    *ngIf="mode !== 'editing'">
+                    *ngIf="mode !== 'editing' && poolPermission.read">
               <option *ngIf="pools === null"
                       [ngValue]="null">Loading...
               </option>
           <div class="col-sm-9">
             <input class="form-control"
                    type="text"
+                   placeholder="Data pool name..."
                    id="dataPool"
                    name="dataPool"
                    formControlName="dataPool"
-                   *ngIf="mode === 'editing'">
+                   *ngIf="mode === 'editing' || !poolPermission.read">
             <select id="dataPool"
                     name="dataPool"
                     class="form-control"
                     formControlName="dataPool"
                     (change)="onDataPoolChange($event.target.value)"
-                    *ngIf="mode !== 'editing'">
+                    *ngIf="mode !== 'editing' && poolPermission.read">
               <option *ngIf="dataPools === null"
                       [ngValue]="null">Loading...
               </option>
index 631bfaa2547f462dc66a32d18bd7fe9b13f7ddb1..6bdf73f1089bd44839e4dd1c3ad7141a08875e6c 100644 (file)
@@ -8,7 +8,9 @@ import { Observable } from 'rxjs/Observable';
 import { PoolService } from '../../../shared/api/pool.service';
 import { RbdService } from '../../../shared/api/rbd.service';
 import { FinishedTask } from '../../../shared/models/finished-task';
+import { Permission } from '../../../shared/models/permissions';
 import { DimlessBinaryPipe } from '../../../shared/pipes/dimless-binary.pipe';
+import { AuthStorageService } from '../../../shared/services/auth-storage.service';
 import { FormatterService } from '../../../shared/services/formatter.service';
 import { TaskWrapperService } from '../../../shared/services/task-wrapper.service';
 import { RbdFormCloneRequestModel } from './rbd-form-clone-request.model';
@@ -25,6 +27,7 @@ import { RbdFormResponseModel } from './rbd-form-response.model';
 })
 export class RbdFormComponent implements OnInit {
 
+  poolPermission: Permission;
   rbdForm: FormGroup;
   featuresFormGroups: FormGroup;
   deepFlattenFormControl: FormControl;
@@ -71,6 +74,7 @@ export class RbdFormComponent implements OnInit {
   ];
 
   constructor(
+    private authStorageService: AuthStorageService,
     private route: ActivatedRoute,
     private router: Router,
     private poolService: PoolService,
@@ -79,6 +83,7 @@ export class RbdFormComponent implements OnInit {
     private taskWrapper: TaskWrapperService,
     private dimlessBinaryPipe: DimlessBinaryPipe
   ) {
+    this.poolPermission = this.authStorageService.getPermissions().pool;
     this.features = {
       'deep-flatten': {
         desc: 'Deep flatten',
@@ -217,7 +222,7 @@ export class RbdFormComponent implements OnInit {
           this.setFeatures(defaultFeatures);
         });
     }
-    if (this.mode !== this.rbdFormMode.editing) {
+    if (this.mode !== this.rbdFormMode.editing && this.poolPermission.read) {
       this.poolService.list(['pool_name', 'type', 'flags_names', 'application_metadata']).then(
         resp => {
           const pools = [];
index 896d225724e02d2b47cc2d2d4ef9e5d5ca79d8bd..0d2f37e5b535b9c3e8a3bb1c3bffcbf14490e22e 100644 (file)
          dropdown>
       <button type="button"
               class="btn btn-sm btn-primary"
-              *ngIf="!selection.hasSingleSelection"
+              *ngIf="permission.create && (!permission.update || !selection.hasSingleSelection)"
               routerLink="/rbd/add">
-        <i class="fa fa-fw fa-plus"></i>
-        <span i18n>Add</span>
+        <i class="fa fa-fw fa-plus"></i><span i18n>Add</span>
       </button>
       <button type="button"
               class="btn btn-sm btn-primary"
-              *ngIf="selection.hasSingleSelection"
-              [ngClass]="{'disabled': selection.first().executing}"
+              *ngIf="permission.update && (!permission.create || permission.create && selection.hasSingleSelection)"
+              [ngClass]="{'disabled': !selection.hasSingleSelection || selection.first().executing}"
               routerLink="/rbd/edit/{{ selection.first()?.pool_name }}/{{ selection.first()?.name }}">
-        <i class="fa fa-fw fa-pencil"></i>
-        <span i18n>Edit</span>
+        <i class="fa fa-fw fa-pencil"></i><span i18n>Edit</span>
+      </button>
+      <button type="button"
+              class="btn btn-sm btn-primary"
+              *ngIf="permission.delete && !permission.update && !permission.create"
+              [ngClass]="{'disabled': !selection.hasSingleSelection || selection.first().executing}"
+              (click)="deleteRbdModal()">
+        <i class="fa fa-fw fa-trash-o"></i><span i18n>Delete</span>
       </button>
       <button type="button"
               dropdownToggle
-              class="btn btn-sm btn-primary dropdown-toggle dropdown-toggle-split">
+              class="btn btn-sm btn-primary dropdown-toggle dropdown-toggle-split"
+              *ngIf="permission.create || permission.update">
         <span class="caret"></span>
         <span class="sr-only"></span>
       </button>
       <ul *dropdownMenu
           class="dropdown-menu"
           role="menu">
-        <li role="menuitem">
+        <li role="menuitem"
+            *ngIf="permission.create">
           <a class="dropdown-item"
              routerLink="/rbd/add">
             <i class="fa fa-fw fa-plus"></i>
@@ -54,6 +61,7 @@
           </a>
         </li>
         <li role="menuitem"
+            *ngIf="permission.update"
             [ngClass]="{'disabled': !selection.hasSingleSelection || selection.first().executing}">
           <a class="dropdown-item"
              routerLink="/rbd/edit/{{ selection.first()?.pool_name }}/{{ selection.first()?.name }}">
@@ -62,6 +70,7 @@
           </a>
         </li>
         <li role="menuitem"
+            *ngIf="permission.create"
             [ngClass]="{'disabled': !selection.hasSingleSelection || selection.first().executing}">
           <a class="dropdown-item"
              routerLink="/rbd/copy/{{ selection.first()?.pool_name }}/{{ selection.first()?.name }}">
@@ -70,6 +79,7 @@
           </a>
         </li>
         <li role="menuitem"
+            *ngIf="permission.update"
             [ngClass]="{'disabled': !selection.hasSingleSelection || selection.first().executing || !selection.first().parent}">
           <a class="dropdown-item"
              (click)="flattenRbdModal()">
@@ -78,6 +88,7 @@
           </a>
         </li>
         <li role="menuitem"
+            *ngIf="permission.delete"
             [ngClass]="{'disabled': !selection.hasSingleSelection || selection.first().executing}">
           <a class="dropdown-item"
              (click)="deleteRbdModal()">
index 2587f086db2e3a1575b7727c176c1f659acc9c59..d05fec8325f5495abb9165cdb77e33bfbee5afc8 100644 (file)
@@ -12,8 +12,10 @@ import { CdTableColumn } from '../../../shared/models/cd-table-column';
 import { CdTableSelection } from '../../../shared/models/cd-table-selection';
 import { ExecutingTask } from '../../../shared/models/executing-task';
 import { FinishedTask } from '../../../shared/models/finished-task';
+import { Permission } from '../../../shared/models/permissions';
 import { DimlessBinaryPipe } from '../../../shared/pipes/dimless-binary.pipe';
 import { DimlessPipe } from '../../../shared/pipes/dimless.pipe';
+import { AuthStorageService } from '../../../shared/services/auth-storage.service';
 import { SummaryService } from '../../../shared/services/summary.service';
 import { TaskWrapperService } from '../../../shared/services/task-wrapper.service';
 import { RbdParentModel } from '../rbd-form/rbd-parent.model';
@@ -28,9 +30,9 @@ export class RbdListComponent implements OnInit, OnDestroy {
   @ViewChild('usageTpl') usageTpl: TemplateRef<any>;
   @ViewChild('parentTpl') parentTpl: TemplateRef<any>;
   @ViewChild('nameTpl') nameTpl: TemplateRef<any>;
-
   @ViewChild('flattenTpl') flattenTpl: TemplateRef<any>;
 
+  permission: Permission;
   images: any;
   executingTasks: ExecutingTask[] = [];
   columns: CdTableColumn[];
@@ -43,13 +45,15 @@ export class RbdListComponent implements OnInit, OnDestroy {
   modalRef: BsModalRef;
 
   constructor(
+    private authStorageService: AuthStorageService,
     private rbdService: RbdService,
     private dimlessBinaryPipe: DimlessBinaryPipe,
     private dimlessPipe: DimlessPipe,
     private summaryService: SummaryService,
     private modalService: BsModalService,
-    private taskWrapper: TaskWrapperService
-  ) {}
+    private taskWrapper: TaskWrapperService) {
+    this.permission = this.authStorageService.getPermissions().rbdImage;
+  }
 
   ngOnInit() {
     this.columns = [
index 3f50579a371ef005fc6565a8e42b995deb400fd5..1e6ed5fc98f98d9efd728753eaafd046786b74ab 100644 (file)
@@ -8,29 +8,39 @@
          dropdown>
       <button type="button"
               class="btn btn-sm btn-primary"
-              *ngIf="!selection.hasSingleSelection"
+              *ngIf="permission.create && (!permission.update || !selection.hasSingleSelection)"
               (click)="openCreateSnapshotModal()">
         <i class="fa fa-fw fa-plus"></i>
         <span i18n>Create</span>
       </button>
       <button type="button"
               class="btn btn-sm btn-primary"
-              *ngIf="selection.hasSingleSelection"
-              [ngClass]="{'disabled': selection.first().executing}"
+              *ngIf="permission.update && (!permission.create || permission.create && selection.hasSingleSelection)"
+              [ngClass]="{'disabled': !selection.hasSingleSelection || selection.first().executing}"
               (click)="openEditSnapshotModal()">
         <i class="fa fa-fw fa-pencil"></i>
         <span i18n>Rename</span>
       </button>
+      <button type="button"
+              class="btn btn-sm btn-primary"
+              *ngIf="permission.delete && !permission.update && !permission.create"
+              [ngClass]="{'disabled': !selection.hasSingleSelection || selection.first().executing}"
+              (click)="deleteSnapshotModal()">
+        <i class="fa fa-fw fa-trash-o"></i>
+        <span i18n>Delete</span>
+      </button>
       <button type="button"
               dropdownToggle
-              class="btn btn-sm btn-primary dropdown-toggle dropdown-toggle-split">
+              class="btn btn-sm btn-primary dropdown-toggle dropdown-toggle-split"
+              *ngIf="permission.create || permission.update">
         <span class="caret"></span>
         <span class="sr-only"></span>
       </button>
       <ul *dropdownMenu
           class="dropdown-menu"
           role="menu">
-        <li role="menuitem">
+        <li role="menuitem"
+            *ngIf="permission.create">
           <a class="dropdown-item"
              (click)="openCreateSnapshotModal()">
             <i class="fa fa-fw fa-plus"></i>
           </a>
         </li>
         <li role="menuitem"
+            *ngIf="permission.update"
             [ngClass]="{'disabled': !selection.hasSingleSelection || selection.first().executing}">
-          <a class="dropdown-item"
-             (click)="openEditSnapshotModal()">
+          <a class="dropdown-item" (click)="openEditSnapshotModal()">
             <i class="fa fa-fw fa-pencil"></i>
             <span i18n>Rename</span>
           </a>
         </li>
         <li role="menuitem"
+            *ngIf="permission.update"
             [ngClass]="{'disabled': !selection.hasSingleSelection || selection.first().executing}">
-          <a class="dropdown-item"
-             (click)="toggleProtection()">
+          <a class="dropdown-item" (click)="toggleProtection()">
             <span *ngIf="!selection.first()?.is_protected">
               <i class="fa fa-fw fa-lock"></i>
               <span i18n>Protect</span>
@@ -60,6 +70,7 @@
           </a>
         </li>
         <li role="menuitem"
+            *ngIf="permission.create"
             [ngClass]="{'disabled': !selection.hasSingleSelection || selection.first().executing}">
           <a class="dropdown-item"
              routerLink="/rbd/clone/{{ poolName }}/{{ rbdName }}/{{ selection.first()?.name }}">
           </a>
         </li>
         <li role="menuitem"
+            *ngIf="permission.create"
             [ngClass]="{'disabled': !selection.hasSingleSelection || selection.first().executing}">
-          <a class="dropdown-item"
-             routerLink="/rbd/copy/{{ poolName }}/{{ rbdName }}/{{ selection.first()?.name }}">
-            <i class="fa fa-fw fa-copy"></i>
-            <span i18n>Copy</span>
+          <a class="dropdown-item" routerLink="/rbd/copy/{{ poolName }}/{{ rbdName }}/{{ selection.first()?.name }}">
+            <i class="fa fa-fw fa-copy"></i><span i18n>Copy</span>
           </a>
         </li>
         <li role="menuitem"
+            *ngIf="permission.update"
             [ngClass]="{'disabled': !selection.hasSingleSelection || selection.first().executing}">
           <a class="dropdown-item"
              (click)="rollbackModal()">
@@ -84,6 +95,7 @@
           </a>
         </li>
         <li role="menuitem"
+            *ngIf="permission.delete"
             [ngClass]="{'disabled': !selection.hasSingleSelection || selection.first().executing || selection.first().is_protected}">
           <a class="dropdown-item"
              (click)="deleteSnapshotModal()">
index 1ed7feceb064bb812970582b6286b16b9e0a6bd8..03fe0a050f3dff6d5be0f32625caa671b4eff26b 100644 (file)
@@ -10,6 +10,7 @@ import { ApiModule } from '../../../shared/api/api.module';
 import { RbdService } from '../../../shared/api/rbd.service';
 import { ComponentsModule } from '../../../shared/components/components.module';
 import { DataTableModule } from '../../../shared/datatable/datatable.module';
+import { Permissions } from '../../../shared/models/permissions';
 import { AuthStorageService } from '../../../shared/services/auth-storage.service';
 import { NotificationService } from '../../../shared/services/notification.service';
 import { ServicesModule } from '../../../shared/services/services.module';
@@ -20,6 +21,15 @@ describe('RbdSnapshotListComponent', () => {
   let component: RbdSnapshotListComponent;
   let fixture: ComponentFixture<RbdSnapshotListComponent>;
 
+  const fakeAuthStorageService = {
+    isLoggedIn: () => {
+      return true;
+    },
+    getPermissions: () => {
+      return new Permissions({ 'rbd-image': ['read', 'update', 'create', 'delete'] });
+    }
+  };
+
   configureTestBed({
     declarations: [RbdSnapshotListComponent],
     imports: [
@@ -32,7 +42,7 @@ describe('RbdSnapshotListComponent', () => {
       HttpClientTestingModule,
       RouterTestingModule
     ],
-    providers: [AuthStorageService]
+    providers: [{ provide: AuthStorageService, useValue: fakeAuthStorageService }]
   });
 
   beforeEach(() => {
@@ -49,12 +59,16 @@ describe('RbdSnapshotListComponent', () => {
     let called;
     let rbdService: RbdService;
     let notificationService: NotificationService;
+    let authStorageService: AuthStorageService;
 
     beforeEach(() => {
       called = false;
       rbdService = new RbdService(null);
       notificationService = new NotificationService(null, null);
+      authStorageService = new AuthStorageService();
+      authStorageService.set('user', { 'rbd-image': ['create', 'read', 'update', 'delete'] });
       component = new RbdSnapshotListComponent(
+        authStorageService,
         null,
         null,
         null,
index aa3a2f1b614e414c64b39ac330e41485fb5e4360..a3f2b40c2827876e842663948f03659bf79739aa 100644 (file)
@@ -11,8 +11,10 @@ import { CdTableColumn } from '../../../shared/models/cd-table-column';
 import { CdTableSelection } from '../../../shared/models/cd-table-selection';
 import { ExecutingTask } from '../../../shared/models/executing-task';
 import { FinishedTask } from '../../../shared/models/finished-task';
+import { Permission } from '../../../shared/models/permissions';
 import { CdDatePipe } from '../../../shared/pipes/cd-date.pipe';
 import { DimlessBinaryPipe } from '../../../shared/pipes/dimless-binary.pipe';
+import { AuthStorageService } from '../../../shared/services/auth-storage.service';
 import { NotificationService } from '../../../shared/services/notification.service';
 import { TaskManagerService } from '../../../shared/services/task-manager.service';
 import { RbdSnapshotFormComponent } from '../rbd-snapshot-form/rbd-snapshot-form.component';
@@ -33,6 +35,8 @@ export class RbdSnapshotListComponent implements OnInit, OnChanges {
   @ViewChild('protectTpl') protectTpl: TemplateRef<any>;
   @ViewChild('rollbackTpl') rollbackTpl: TemplateRef<any>;
 
+  permission: Permission;
+
   data: RbdSnapshotModel[];
 
   columns: CdTableColumn[];
@@ -42,13 +46,16 @@ export class RbdSnapshotListComponent implements OnInit, OnChanges {
   selection = new CdTableSelection();
 
   constructor(
+    private authStorageService: AuthStorageService,
     private modalService: BsModalService,
     private dimlessBinaryPipe: DimlessBinaryPipe,
     private cdDatePipe: CdDatePipe,
     private rbdService: RbdService,
     private taskManagerService: TaskManagerService,
     private notificationService: NotificationService
-  ) {}
+  ) {
+    this.permission = this.authStorageService.getPermissions().rbdImage;
+  }
 
   ngOnInit() {
     this.columns = [
index f2935c3a0a15d65c60f15398758cbd110f8eea16..9948dded8c3f478163ea266d787baa3ccae8775a 100644 (file)
           (fetchData)="getHosts()">
   <ng-template #servicesTpl let-value="value">
     <span *ngFor="let service of value; last as isLast">
-      <a [routerLink]="[service.cdLink]">{{ service.type }}.{{ service.id }}</a>{{ !isLast ? ", " : "" }}
+      <a [routerLink]="[service.cdLink]"
+         *ngIf="service.canRead">{{ service.type }}.{{ service.id }}</a>
+      <span *ngIf="!service.canRead">{{ service.type }}.{{ service.id }}</span>
+      {{ !isLast ? ", " : "" }}
     </span>
   </ng-template>
 </cd-table>
index 81b1acc2f9e5c401bb857090f9528d6d9216663c..e23a52ba162e3ec44897811061820a55fdd8eef4 100644 (file)
@@ -5,6 +5,8 @@ import { RouterTestingModule } from '@angular/router/testing';
 import { BsDropdownModule } from 'ngx-bootstrap';
 
 import { ComponentsModule } from '../../../shared/components/components.module';
+import { Permissions } from '../../../shared/models/permissions';
+import { AuthStorageService } from '../../../shared/services/auth-storage.service';
 import { SharedModule } from '../../../shared/shared.module';
 import { configureTestBed } from '../../../shared/unit-test-helper';
 import { HostsComponent } from './hosts.component';
@@ -13,6 +15,12 @@ describe('HostsComponent', () => {
   let component: HostsComponent;
   let fixture: ComponentFixture<HostsComponent>;
 
+  const fakeAuthStorageService = {
+    getPermissions: () => {
+      return new Permissions({ hosts: ['read', 'update', 'create', 'delete'] });
+    }
+  };
+
   configureTestBed({
     imports: [
       SharedModule,
@@ -21,6 +29,7 @@ describe('HostsComponent', () => {
       BsDropdownModule.forRoot(),
       RouterTestingModule
     ],
+    providers: [{ provide: AuthStorageService, useValue: fakeAuthStorageService }],
     declarations: [HostsComponent]
   });
 
index bb22e86a9ab212965d787fd190e321840ddcdf05..6ce752d6edc752260f8c1388e4fda91070e2e572 100644 (file)
@@ -2,7 +2,9 @@ import { Component, OnInit, TemplateRef, ViewChild } from '@angular/core';
 
 import { HostService } from '../../../shared/api/host.service';
 import { CdTableColumn } from '../../../shared/models/cd-table-column';
+import { Permissions } from '../../../shared/models/permissions';
 import { CephShortVersionPipe } from '../../../shared/pipes/ceph-short-version.pipe';
+import { AuthStorageService } from '../../../shared/services/auth-storage.service';
 
 @Component({
   selector: 'cd-hosts',
@@ -10,15 +12,20 @@ import { CephShortVersionPipe } from '../../../shared/pipes/ceph-short-version.p
   styleUrls: ['./hosts.component.scss']
 })
 export class HostsComponent implements OnInit {
-
+  permissions: Permissions;
   columns: Array<CdTableColumn> = [];
   hosts: Array<object> = [];
   isLoadingHosts = false;
 
   @ViewChild('servicesTpl') public servicesTpl: TemplateRef<any>;
 
-  constructor(private hostService: HostService,
-              private cephShortVersionPipe: CephShortVersionPipe) { }
+  constructor(
+    private authStorageService: AuthStorageService,
+    private hostService: HostService,
+    private cephShortVersionPipe: CephShortVersionPipe
+  ) {
+    this.permissions = this.authStorageService.getPermissions();
+  }
 
   ngOnInit() {
     this.columns = [
@@ -46,19 +53,32 @@ export class HostsComponent implements OnInit {
     if (this.isLoadingHosts) {
       return;
     }
+    const typeToPermissionKey = {
+      mds: 'cephfs',
+      mon: 'monitor',
+      osd: 'osd',
+      rgw: 'rgw',
+      'rbd-mirror': 'rbdMirroring',
+      mgr: 'manager'
+    };
     this.isLoadingHosts = true;
-    this.hostService.list().then((resp) => {
-      resp.map((host) => {
-        host.services.map((service) => {
-          service.cdLink = `/perf_counters/${service.type}/${service.id}`;
-          return service;
+    this.hostService
+      .list()
+      .then((resp) => {
+        resp.map((host) => {
+          host.services.map((service) => {
+            service.cdLink = `/perf_counters/${service.type}/${service.id}`;
+            const permissionKey = typeToPermissionKey[service.type];
+            service.canRead = this.permissions[permissionKey].read;
+            return service;
+          });
+          return host;
         });
-        return host;
+        this.hosts = resp;
+        this.isLoadingHosts = false;
+      })
+      .catch(() => {
+        this.isLoadingHosts = false;
       });
-      this.hosts = resp;
-      this.isLoadingHosts = false;
-    }).catch(() => {
-      this.isLoadingHosts = false;
-    });
   }
 }
index c25b416d2766e96d83e4814d84734c5a1c756c9d..426ec736978b7dbe6f9a740a259257f8c8e9ef53 100644 (file)
@@ -10,7 +10,8 @@
           selectionType="single"
           (updateSelection)="updateSelection($event)"
           [updateSelectionOnRefresh]="false">
-  <div class="table-actions">
+  <div class="table-actions"
+       *ngIf="permission.update">
     <div class="btn-group"
          dropdown>
       <button dropdownToggle
index d025fd3197159f7637775f8647a22c4669668f48..b1095b8d9ed5493a509c54c03e28ebb82b681152 100644 (file)
@@ -5,7 +5,9 @@ import { TabsModule } from 'ngx-bootstrap/tabs';
 
 import { ComponentsModule } from '../../../../shared/components/components.module';
 import { DataTableModule } from '../../../../shared/datatable/datatable.module';
+import { Permissions } from '../../../../shared/models/permissions';
 import { DimlessPipe } from '../../../../shared/pipes/dimless.pipe';
+import { AuthStorageService } from '../../../../shared/services/auth-storage.service';
 import { FormatterService } from '../../../../shared/services/formatter.service';
 import { SharedModule } from '../../../../shared/shared.module';
 import { configureTestBed } from '../../../../shared/unit-test-helper';
@@ -18,6 +20,12 @@ describe('OsdListComponent', () => {
   let component: OsdListComponent;
   let fixture: ComponentFixture<OsdListComponent>;
 
+  const fakeAuthStorageService = {
+    getPermissions: () => {
+      return new Permissions({ osd: ['read', 'update', 'create', 'delete'] });
+    }
+  };
+
   configureTestBed({
     imports: [
       HttpClientModule,
@@ -28,7 +36,11 @@ describe('OsdListComponent', () => {
       SharedModule
     ],
     declarations: [OsdListComponent, OsdDetailsComponent, OsdPerformanceHistogramComponent],
-    providers: [DimlessPipe, FormatterService]
+    providers: [
+      DimlessPipe,
+      FormatterService,
+      { provide: AuthStorageService, useValue: fakeAuthStorageService }
+    ]
   });
 
   beforeEach(() => {
index 223debe42bfc89e02c85d4931a863faa9ad6b2b0..80e3db42c77820e3a6d2917cf7a5e54140d158e6 100644 (file)
@@ -7,7 +7,9 @@ import { TableComponent } from '../../../../shared/datatable/table/table.compone
 import { CellTemplate } from '../../../../shared/enum/cell-template.enum';
 import { CdTableColumn } from '../../../../shared/models/cd-table-column';
 import { CdTableSelection } from '../../../../shared/models/cd-table-selection';
+import { Permission } from '../../../../shared/models/permissions';
 import { DimlessBinaryPipe } from '../../../../shared/pipes/dimless-binary.pipe';
+import { AuthStorageService } from '../../../../shared/services/auth-storage.service';
 import { OsdScrubModalComponent } from '../osd-scrub-modal/osd-scrub-modal.component';
 
 @Component({
@@ -20,16 +22,20 @@ export class OsdListComponent implements OnInit {
   @ViewChild('osdUsageTpl') osdUsageTpl: TemplateRef<any>;
   @ViewChild(TableComponent) tableComponent: TableComponent;
 
+  permission: Permission;
   bsModalRef: BsModalRef;
   osds = [];
   columns: CdTableColumn[];
   selection = new CdTableSelection();
 
   constructor(
+    private authStorageService: AuthStorageService,
     private osdService: OsdService,
     private dimlessBinaryPipe: DimlessBinaryPipe,
     private modalService: BsModalService
-  ) {}
+  ) {
+    this.permission = this.authStorageService.getPermissions().osd;
+  }
 
   ngOnInit() {
     this.columns = [
index a90be7f20384e50e8864601856f58baf9a9d7df2..4ce0feae1be38332d1cd8685241998d362147c04 100644 (file)
@@ -20,7 +20,8 @@
       <!--STATS -->
       <div class="row">
         <div class="col-md-6">
-          <div class="well">
+          <div class="well"
+               *ngIf="contentData.mon_status">
             <div class="media">
               <div class="media-left">
                 <i class="fa fa-database fa-fw"></i>
@@ -36,7 +37,8 @@
           </div>
         </div>
         <div class="col-md-6">
-          <div class="well">
+          <div class="well"
+               *ngIf="contentData.osd_map">
             <div class="media">
               <div class="media-left">
                 <i class="fa fa-hdd-o fa-fw"></i>
@@ -54,7 +56,8 @@
       </div>
       <div class="row">
         <div class="col-md-6">
-          <div class="well">
+          <div class="well"
+               *ngIf="contentData.fs_map">
             <div class="media">
               <div class="media-left">
                 <i class="fa fa-folder fa-fw"></i>
@@ -68,7 +71,8 @@
           </div>
         </div>
         <div class="col-md-6">
-          <div class="well">
+          <div class="well"
+               *ngIf="contentData.mgr_map">
             <div class="media">
               <div class="media-left">
                 <i class="fa fa-cog fa-fw"></i>
@@ -88,7 +92,8 @@
   <div class="row">
     <!-- USAGE -->
     <div class="col-md-6">
-      <div class="well">
+      <div class="well"
+           *ngIf="contentData.df?.stats?.total_objects">
         <fieldset class="usage">
           <legend i18n>Usage</legend>
 
     </div>
 
     <div class="col-md-6">
-      <div class="well">
+      <div class="well"
+           *ngIf="contentData.pools">
         <fieldset>
           <legend i18n>Pools</legend>
           <table class="table table-condensed">
   <div class="row">
     <div class="col-md-12">
       <!-- LOGS -->
-      <div class="well">
+      <div class="well"
+           *ngIf="contentData.clog || contentData.audit_log">
         <fieldset>
           <legend i18n>Logs</legend>
 
           <tabset>
             <tab heading="Cluster log"
                  class="text-monospace"
+                 *ngIf="contentData.clog"
                  i18n-heading>
               <span *ngFor="let line of contentData.clog">
                 {{ line.stamp }}&nbsp;{{ line.priority }}&nbsp;
             </tab>
             <tab heading="Audit log"
                  class="text-monospace"
+                 *ngIf="contentData.audit_log"
                  i18n-heading>
               <span *ngFor="let line of contentData.audit_log">
                 {{ line.stamp }}&nbsp;{{ line.priority }}&nbsp;
index 965884e7503742c5301218286208758f6b806573..bf47f7389bcf6c19c68ab1709defcd0d76789361 100644 (file)
     <div class="btn-group" dropdown>
       <button type="button"
               class="btn btn-sm btn-primary"
-              *ngIf="!selection.hasSelection"
+              *ngIf="permission.create && (
+                       permission.update && permission.delete && !selection.hasSelection ||
+                       !permission.update && !permission.delete ||
+                       !permission.update && permission.delete && !selection.hasMultiSelection ||
+                       permission.update && !selection.hasSingleSelection && !permission.delete)"
               routerLink="/rgw/bucket/add">
         <i class="fa fa-fw fa-plus"></i>
         <ng-container i18n>Add</ng-container>
       </button>
       <button type="button"
               class="btn btn-sm btn-primary"
-              *ngIf="selection.hasSingleSelection"
+              [ngClass]="{'disabled': !selection.hasSelection}"
+              *ngIf="permission.update && (!permission.create && !selection.hasMultiSelection || selection.hasSingleSelection)"
               routerLink="/rgw/bucket/edit/{{ selection.first()?.bucket }}">
         <i class="fa fa-fw fa-pencil"></i>
         <ng-container i18n>Edit</ng-container>
       </button>
       <button type="button"
               class="btn btn-sm btn-primary"
-              *ngIf="selection.hasMultiSelection"
+              [ngClass]="{'disabled': !selection.hasSelection}"
+              *ngIf="permission.delete && (!permission.update && !permission.create || selection.hasMultiSelection)"
               (click)="deleteAction()">
         <i class="fa fa-fw fa-trash-o"></i>
         <ng-container i18n>Delete</ng-container>
       </button>
       <button type="button"
               class="btn btn-sm btn-primary dropdown-toggle dropdown-toggle-split"
+              *ngIf="((permission.create?1:0) + (permission.update?1:0) + (permission.delete?1:0)) > 1"
               dropdownToggle>
         <span class="caret"></span>
         <span class="sr-only"></span>
@@ -48,7 +55,8 @@
       <ul class="dropdown-menu"
           *dropdownMenu
           role="menu">
-        <li role="menuitem">
+        <li role="menuitem"
+            *ngIf="permission.create">
           <a class="dropdown-item"
              routerLink="/rgw/bucket/add"
              i18n>
@@ -57,6 +65,7 @@
           </a>
         </li>
         <li role="menuitem"
+            *ngIf="permission.update"
             [ngClass]="{'disabled': !selection.hasSingleSelection}">
           <a class="dropdown-item"
              routerLink="/rgw/bucket/edit/{{ selection.first()?.bucket }}"
@@ -66,6 +75,7 @@
           </a>
         </li>
         <li role="menuitem"
+            *ngIf="permission.delete"
             [ngClass]="{'disabled': !selection.hasSelection}">
           <a class="dropdown-item"
              (click)="deleteAction()"
index fa40a44482b96d358112b41c96d4a539df6701c2..4136225e5033e267b48a299857b2a3c6b0791a75 100644 (file)
@@ -8,6 +8,8 @@ import { DeletionModalComponent } from '../../../shared/components/deletion-moda
 import { TableComponent } from '../../../shared/datatable/table/table.component';
 import { CdTableColumn } from '../../../shared/models/cd-table-column';
 import { CdTableSelection } from '../../../shared/models/cd-table-selection';
+import { Permission } from '../../../shared/models/permissions';
+import { AuthStorageService } from '../../../shared/services/auth-storage.service';
 
 @Component({
   selector: 'cd-rgw-bucket-list',
@@ -17,11 +19,17 @@ import { CdTableSelection } from '../../../shared/models/cd-table-selection';
 export class RgwBucketListComponent {
   @ViewChild(TableComponent) table: TableComponent;
 
+  permission: Permission;
   columns: CdTableColumn[] = [];
   buckets: object[] = [];
   selection: CdTableSelection = new CdTableSelection();
 
-  constructor(private rgwBucketService: RgwBucketService, private bsModalService: BsModalService) {
+  constructor(
+    private authStorageService: AuthStorageService,
+    private rgwBucketService: RgwBucketService,
+    private bsModalService: BsModalService
+  ) {
+    this.permission = this.authStorageService.getPermissions().rgw;
     this.columns = [
       {
         name: 'Name',
index b39c007a3e587be7f825ff907767e3b1605c0121..85a1427b4920d833087b2a5bf01b2046be7559e9 100644 (file)
     <div class="btn-group" dropdown>
       <button type="button"
               class="btn btn-sm btn-primary"
-              *ngIf="!selection.hasSelection"
+              *ngIf="permission.create && (
+                       permission.update && permission.delete && !selection.hasSelection ||
+                       !permission.update && !permission.delete ||
+                       !permission.update && permission.delete && !selection.hasMultiSelection ||
+                       permission.update && !selection.hasSingleSelection && !permission.delete)"
               routerLink="/rgw/user/add">
         <i class="fa fa-fw fa-plus"></i>
         <ng-container i18n>Add</ng-container>
       </button>
       <button type="button"
               class="btn btn-sm btn-primary"
-              *ngIf="selection.hasSingleSelection"
+              [ngClass]="{'disabled': !selection.hasSelection}"
+              *ngIf="permission.update && (!permission.create && !selection.hasMultiSelection || selection.hasSingleSelection)"
               routerLink="/rgw/user/edit/{{ selection.first()?.user_id }}">
         <i class="fa fa-fw fa-pencil"></i>
         <ng-container i18n>Edit</ng-container>
       </button>
       <button type="button"
               class="btn btn-sm btn-primary"
-              *ngIf="selection.hasMultiSelection"
+              [ngClass]="{'disabled': !selection.hasSelection}"
+              *ngIf="permission.delete && (!permission.update && !permission.create || selection.hasMultiSelection)"
               (click)="deleteAction()">
         <i class="fa fa-fw fa-trash-o"></i>
         <ng-container i18n>Delete</ng-container>
       </button>
       <button type="button"
               class="btn btn-sm btn-primary dropdown-toggle dropdown-toggle-split"
+              *ngIf="((permission.create?1:0) + (permission.update?1:0) + (permission.delete?1:0)) > 1"
               dropdownToggle>
         <span class="caret"></span>
         <span class="sr-only"></span>
@@ -48,7 +55,8 @@
       <ul class="dropdown-menu"
           *dropdownMenu
           role="menu">
-        <li role="menuitem">
+        <li role="menuitem"
+            *ngIf="permission.create">
           <a class="dropdown-item"
              routerLink="/rgw/user/add"
              i18n>
@@ -57,6 +65,7 @@
           </a>
         </li>
         <li role="menuitem"
+            *ngIf="permission.update"
             [ngClass]="{'disabled': !selection.hasSingleSelection}">
           <a class="dropdown-item"
              routerLink="/rgw/user/edit/{{ selection.first()?.user_id }}"
@@ -66,6 +75,7 @@
           </a>
         </li>
         <li role="menuitem"
+            *ngIf="permission.delete"
             [ngClass]="{'disabled': !selection.hasSelection}">
           <a class="dropdown-item"
              (click)="deleteAction()"
index 0c84ee36915d65262d2fe90e917dca4f8317f965..28f20df62a3e8de166ab3a1b341b25023acf5354 100644 (file)
@@ -6,6 +6,7 @@ import { RouterTestingModule } from '@angular/router/testing';
 import { ModalModule } from 'ngx-bootstrap';
 
 import { RgwUserService } from '../../../shared/api/rgw-user.service';
+import { SharedModule } from '../../../shared/shared.module';
 import { configureTestBed } from '../../../shared/unit-test-helper';
 import { RgwUserListComponent } from './rgw-user-list.component';
 
@@ -15,7 +16,7 @@ describe('RgwUserListComponent', () => {
 
   configureTestBed({
     declarations: [RgwUserListComponent],
-    imports: [RouterTestingModule, HttpClientTestingModule, ModalModule.forRoot()],
+    imports: [RouterTestingModule, HttpClientTestingModule, ModalModule.forRoot(), SharedModule],
     providers: [RgwUserService],
     schemas: [NO_ERRORS_SCHEMA]
   });
index 02d6e8d9211cb596efb3eec540acbd820d7092ef..48832b9a14ca8c23652ac8e9ae2cb4ea89fe4456 100644 (file)
@@ -9,6 +9,8 @@ import { TableComponent } from '../../../shared/datatable/table/table.component'
 import { CellTemplate } from '../../../shared/enum/cell-template.enum';
 import { CdTableColumn } from '../../../shared/models/cd-table-column';
 import { CdTableSelection } from '../../../shared/models/cd-table-selection';
+import { Permission } from '../../../shared/models/permissions';
+import { AuthStorageService } from '../../../shared/services/auth-storage.service';
 
 @Component({
   selector: 'cd-rgw-user-list',
@@ -18,11 +20,17 @@ import { CdTableSelection } from '../../../shared/models/cd-table-selection';
 export class RgwUserListComponent {
   @ViewChild(TableComponent) table: TableComponent;
 
+  permission: Permission;
   columns: CdTableColumn[] = [];
   users: object[] = [];
   selection: CdTableSelection = new CdTableSelection();
 
-  constructor(private rgwUserService: RgwUserService, private bsModalService: BsModalService) {
+  constructor(
+    private authStorageService: AuthStorageService,
+    private rgwUserService: RgwUserService,
+    private bsModalService: BsModalService
+  ) {
+    this.permission = this.authStorageService.getPermissions().rgw;
     this.columns = [
       {
         name: 'Username',
index bd1768158e7e27b666db0c1bdb5a191ed7f76fce..0190e52e22167e14bec33f8898c8d495c2e13155 100644 (file)
@@ -2,16 +2,13 @@ import { CommonModule } from '@angular/common';
 import { NgModule } from '@angular/core';
 
 import { AuthModule } from './auth/auth.module';
+import { ForbiddenComponent } from './forbidden/forbidden.component';
 import { NavigationModule } from './navigation/navigation.module';
 import { NotFoundComponent } from './not-found/not-found.component';
 
 @NgModule({
-  imports: [
-    CommonModule,
-    NavigationModule,
-    AuthModule
-  ],
+  imports: [CommonModule, NavigationModule, AuthModule],
   exports: [NavigationModule],
-  declarations: [NotFoundComponent]
+  declarations: [NotFoundComponent, ForbiddenComponent]
 })
-export class CoreModule { }
+export class CoreModule {}
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/core/forbidden/forbidden.component.html b/src/pybind/mgr/dashboard/frontend/src/app/core/forbidden/forbidden.component.html
new file mode 100644 (file)
index 0000000..ba06ebe
--- /dev/null
@@ -0,0 +1,10 @@
+<div class="row">
+  <div class="col-md-12 text-center">
+    <h1 i18n>Forbidden</h1>
+
+    <i class="fa fa-lock text-danger"></i>
+
+    <h2 i18n>Sorry, you are not allowed to see what you were looking for.</h2>
+
+  </div>
+</div>
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/core/forbidden/forbidden.component.scss b/src/pybind/mgr/dashboard/frontend/src/app/core/forbidden/forbidden.component.scss
new file mode 100644 (file)
index 0000000..47a9286
--- /dev/null
@@ -0,0 +1,13 @@
+h1 {
+  font-size: -webkit-xxx-large;
+  font-family: monospace;
+}
+
+h2 {
+  font-size: xx-large;
+  font-family: monospace;
+}
+
+i {
+  font-size: 200px;
+}
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/core/forbidden/forbidden.component.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/core/forbidden/forbidden.component.spec.ts
new file mode 100644 (file)
index 0000000..1358406
--- /dev/null
@@ -0,0 +1,24 @@
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { ForbiddenComponent } from './forbidden.component';
+
+describe('ForbiddenComponent', () => {
+  let component: ForbiddenComponent;
+  let fixture: ComponentFixture<ForbiddenComponent>;
+
+  beforeEach(async(() => {
+    TestBed.configureTestingModule({
+      declarations: [ForbiddenComponent]
+    }).compileComponents();
+  }));
+
+  beforeEach(() => {
+    fixture = TestBed.createComponent(ForbiddenComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/core/forbidden/forbidden.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/core/forbidden/forbidden.component.ts
new file mode 100644 (file)
index 0000000..8b70d3e
--- /dev/null
@@ -0,0 +1,10 @@
+import { Component } from '@angular/core';
+
+@Component({
+  selector: 'cd-forbidden',
+  templateUrl: './forbidden.component.html',
+  styleUrls: ['./forbidden.component.scss']
+})
+export class ForbiddenComponent {
+  constructor() {}
+}
index a215f51c5690effda9441d99ea468d646a1a6148..73036c54a8cc2ef17995be1d73bc9f4ae5f4708c 100644 (file)
@@ -40,7 +40,8 @@
       <!-- Cluster -->
       <li dropdown
           routerLinkActive="active"
-          class="dropdown tc_menuitem tc_menuitem_cluster">
+          class="dropdown tc_menuitem tc_menuitem_cluster"
+          *ngIf="permissions.hosts.read || permissions.monitor.read || permissions.osd.read || permissions.configOpt.read">
         <a dropdownToggle
            class="dropdown-toggle"
            data-toggle="dropdown">
         <ul *dropdownMenu
             class="dropdown-menu">
           <li routerLinkActive="active"
-              class="tc_submenuitem tc_submenuitem_hosts">
+              class="tc_submenuitem tc_submenuitem_hosts"
+              *ngIf="permissions.hosts.read">
             <a i18n
                class="dropdown-item"
                routerLink="/hosts">Hosts
             </a>
           </li>
           <li routerLinkActive="active"
-              class="tc_submenuitem tc_submenuitem_cluster_monitor">
+              class="tc_submenuitem tc_submenuitem_cluster_monitor"
+              *ngIf="permissions.monitor.read">
             <a i18n
                class="dropdown-item"
                routerLink="/monitor/"> Monitors
             </a>
           </li>
           <li routerLinkActive="active"
-              class="tc_submenuitem tc_submenuitem_hosts">
+              class="tc_submenuitem tc_submenuitem_hosts"
+              *ngIf="permissions.osd.read">
             <a i18n
                class="dropdown-item"
                routerLink="/osd">OSDs
             </a>
           </li>
           <li routerLinkActive="active"
-              class="tc_submenuitem tc_submenuitem_configuration">
+              class="tc_submenuitem tc_submenuitem_configuration"
+              *ngIf="permissions.configOpt.read">
             <a i18n
                class="dropdown-item"
                routerLink="/configuration">Configuration Doc.
@@ -82,7 +87,8 @@
 
       <!-- Pools -->
       <li routerLinkActive="active"
-          class="tc_menuitem tc_menuitem_pool">
+          class="tc_menuitem tc_menuitem_pool"
+          *ngIf="permissions.pool.read">
         <a i18n
            routerLink="/pool">Pool
         </a>
@@ -91,7 +97,8 @@
       <!-- Block -->
       <li dropdown
           routerLinkActive="active"
-          class="dropdown tc_menuitem tc_menuitem_block">
+          class="dropdown tc_menuitem tc_menuitem_block"
+          *ngIf="permissions.rbdImage.read || permissions.rbdMirroring.read || permissions.iscsi.read">
         <a dropdownToggle
            class="dropdown-toggle"
            data-toggle="dropdown"
         </a>
 
         <ul class="dropdown-menu">
-          <li routerLinkActive="active">
+          <li routerLinkActive="active"
+              *ngIf="permissions.rbdImage.read">
             <a i18n
                class="dropdown-item"
                routerLink="/block/rbd">Images</a>
           </li>
 
           <li routerLinkActive="active"
-              class="tc_submenuitem tc_submenuitem_block_mirroring">
+              class="tc_submenuitem tc_submenuitem_block_mirroring"
+              *ngIf="permissions.rbdMirroring.read">
             <a i18n
                class="dropdown-item"
                routerLink="/mirroring/"> Mirroring
             </a>
           </li>
 
-          <li routerLinkActive="active">
+          <li routerLinkActive="active"
+              *ngIf="permissions.iscsi.read">
             <a i18n
                class="dropdown-item"
                routerLink="/block/iscsi">iSCSI</a>
 
       <!-- Filesystem -->
       <li routerLinkActive="active"
-          class="tc_menuitem tc_menuitem_cephs">
+          class="tc_menuitem tc_menuitem_cephs"
+          *ngIf="permissions.cephfs.read">
         <a i18n
            routerLink="/cephfs">Filesystems
         </a>
       <!-- Object Gateway -->
       <li dropdown
           routerLinkActive="active"
-          class="dropdown tc_menuitem tc_menuitem_rgw">
+          class="dropdown tc_menuitem tc_menuitem_rgw"
+          *ngIf="permissions.rgw.read">
         <a dropdownToggle
            class="dropdown-toggle"
            data-toggle="dropdown">
index d17c53df10e8fd5b4d24b0c72a3e493f2e4ec156..9372e75d857679f1cb3cb92f05ef7b55441e3f0f 100644 (file)
@@ -1,4 +1,7 @@
 import { Component, OnInit } from '@angular/core';
+
+import { Permissions } from '../../../shared/models/permissions';
+import { AuthStorageService } from '../../../shared/services/auth-storage.service';
 import { SummaryService } from '../../../shared/services/summary.service';
 
 @Component({
@@ -7,10 +10,16 @@ import { SummaryService } from '../../../shared/services/summary.service';
   styleUrls: ['./navigation.component.scss']
 })
 export class NavigationComponent implements OnInit {
+  permissions: Permissions;
   summaryData: any;
   isCollapsed = true;
 
-  constructor(private summaryService: SummaryService) {}
+  constructor(
+    private authStorageService: AuthStorageService,
+    private summaryService: SummaryService
+  ) {
+    this.permissions = this.authStorageService.getPermissions();
+  }
 
   ngOnInit() {
     this.summaryService.summaryData$.subscribe((data: any) => {
index 74edcedf4ae23160cf6cb26db14c6ac60934cdf4..7284d862c18648229440338bc03e8e0b94d36556 100644 (file)
@@ -16,7 +16,7 @@ export class AuthService {
       .post('api/auth', credentials)
       .toPromise()
       .then((resp: Credentials) => {
-        this.authStorageService.set(resp.username);
+        this.authStorageService.set(resp.username, resp.permissions);
       });
   }
 
index b33c366c0376b284ab2571c0cd4e0aa585b61785..7e617d986797dd422250dc6b9d11f9d955e6b924 100644 (file)
@@ -1,5 +1,6 @@
 export class Credentials {
   username: string;
   password: string;
+  permissions: any;
   stay_signed_in = false;
 }
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/models/permissions.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/models/permissions.ts
new file mode 100644 (file)
index 0000000..f97652c
--- /dev/null
@@ -0,0 +1,43 @@
+export class Permission {
+  read: boolean;
+  create: boolean;
+  update: boolean;
+  delete: boolean;
+
+  constructor(serverPermission: Array<string> = []) {
+    this.read = serverPermission.indexOf('read') !== -1;
+    this.create = serverPermission.indexOf('create') !== -1;
+    this.update = serverPermission.indexOf('update') !== -1;
+    this.delete = serverPermission.indexOf('delete') !== -1;
+  }
+}
+
+export class Permissions {
+  hosts: Permission;
+  configOpt: Permission;
+  pool: Permission;
+  osd: Permission;
+  monitor: Permission;
+  rbdImage: Permission;
+  iscsi: Permission;
+  rbdMirroring: Permission;
+  rgw: Permission;
+  cephfs: Permission;
+  manager: Permission;
+  log: Permission;
+
+  constructor(serverPermissions: any) {
+    this.hosts = new Permission(serverPermissions['hosts']);
+    this.configOpt = new Permission(serverPermissions['config-opt']);
+    this.pool = new Permission(serverPermissions['pool']);
+    this.osd = new Permission(serverPermissions['osd']);
+    this.monitor = new Permission(serverPermissions['monitor']);
+    this.rbdImage = new Permission(serverPermissions['rbd-image']);
+    this.iscsi = new Permission(serverPermissions['iscsi']);
+    this.rbdMirroring = new Permission(serverPermissions['rbd-mirroring']);
+    this.rgw = new Permission(serverPermissions['rgw']);
+    this.cephfs = new Permission(serverPermissions['cephfs']);
+    this.manager = new Permission(serverPermissions['manager']);
+    this.log = new Permission(serverPermissions['log']);
+  }
+}
index 283978358870e6147296864b464a5f9ad5016c29..28ea70ff40c0a60d905c3f0368e38d157a083d66 100644 (file)
@@ -56,6 +56,9 @@ export class ApiInterceptorService implements HttpInterceptor {
               this.authStorageService.remove();
               this.router.navigate(['/login']);
               break;
+            case 403:
+              this.router.navigate(['/403']);
+              break;
             case 404:
               this.router.navigate(['/404']);
               break;
index 889220f93ba765503f99369acfab327213477de1..cbcda41209e1a9b0a4c9c8d5f3259bfc4d5191f8 100644 (file)
@@ -1,17 +1,17 @@
 import { Injectable } from '@angular/core';
 
+import { Permissions } from '../models/permissions';
 import { ServicesModule } from './services.module';
 
 @Injectable({
   providedIn: ServicesModule
 })
 export class AuthStorageService {
+  constructor() {}
 
-  constructor() {
-  }
-
-  set(username: string) {
+  set(username: string, permissions: any = {}) {
     localStorage.setItem('dashboard_username', username);
+    localStorage.setItem('dashboard_permissions', JSON.stringify(new Permissions(permissions)));
   }
 
   remove() {
@@ -22,4 +22,9 @@ export class AuthStorageService {
     return localStorage.getItem('dashboard_username') !== null;
   }
 
+  getPermissions(): Permissions {
+    return JSON.parse(
+      localStorage.getItem('dashboard_permissions') || JSON.stringify(new Permissions({}))
+    );
+  }
 }