]> git.apps.os.sepia.ceph.com Git - ceph.git/commitdiff
mgr/dashboard: create directive for AuthStorage service 41034/head
authorNgwa Sedrick Meh <nsedrick101@gmail.com>
Mon, 26 Apr 2021 20:17:08 +0000 (21:17 +0100)
committerNgwa Sedrick Meh <nsedrick101@gmail.com>
Mon, 12 Jul 2021 12:47:01 +0000 (13:47 +0100)
This commit adds a directive that can be used to conditionally display elements based on authorization/scopes criteria

Fixes: https://tracker.ceph.com/issues/47355
Signed-off-by: Ngwa Sedrick Meh <nsedrick101@gmail.com>
src/pybind/mgr/dashboard/frontend/src/app/ceph/pool/pool-list/pool-list.component.html
src/pybind/mgr/dashboard/frontend/src/app/shared/directives/auth-storage.directive.spec.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/src/app/shared/directives/auth-storage.directive.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/src/app/shared/directives/directives.module.ts

index 2d3ee6976b47415d72adac61d5b87423b077a41d..7122f749cfb9e84f33a7fa1774c2fd62a2dea70b 100644 (file)
@@ -33,7 +33,7 @@
   </li>
 
   <li ngbNavItem
-      *ngIf="permissions.grafana.read">
+      *cdScope="'grafana'">
     <a ngbNavLink
        i18n>Overall Performance</a>
     <ng-template ngbNavContent>
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/directives/auth-storage.directive.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/directives/auth-storage.directive.spec.ts
new file mode 100644 (file)
index 0000000..c6c0adb
--- /dev/null
@@ -0,0 +1,104 @@
+import { Component } from '@angular/core';
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { Permissions } from '~/app/shared/models/permissions';
+import { AuthStorageService } from '~/app/shared/services/auth-storage.service';
+import { configureTestBed } from '~/testing/unit-test-helper';
+import { AuthStorageDirective } from './auth-storage.directive';
+@Component({
+  template: `<div id="permitted" *cdScope="condition; matchAll: matchAll"></div>`
+})
+export class AuthStorageDirectiveTestComponent {
+  condition: string | string[] | object;
+  matchAll = true;
+}
+
+describe('AuthStorageDirective', () => {
+  let fixture: ComponentFixture<AuthStorageDirectiveTestComponent>;
+  let component: AuthStorageDirectiveTestComponent;
+  let getPermissionsSpy: jasmine.Spy;
+  let el: HTMLElement;
+
+  configureTestBed({
+    declarations: [AuthStorageDirective, AuthStorageDirectiveTestComponent],
+    providers: [AuthStorageService]
+  });
+
+  beforeEach(() => {
+    getPermissionsSpy = spyOn(TestBed.inject(AuthStorageService), 'getPermissions');
+    getPermissionsSpy.and.returnValue(new Permissions({ osd: ['read'], rgw: ['read'] }));
+    fixture = TestBed.createComponent(AuthStorageDirectiveTestComponent);
+    el = fixture.debugElement.nativeElement;
+    component = fixture.componentInstance;
+  });
+
+  it('should show div on valid condition', () => {
+    // String condition
+    component.condition = 'rgw';
+    fixture.detectChanges();
+    expect(el.querySelector('#permitted')).toBeTruthy();
+
+    // Array condition
+    component.condition = ['osd', 'rgw'];
+    fixture.detectChanges();
+    expect(el.querySelector('#permitted')).toBeTruthy();
+
+    // Object condition
+    component.condition = { rgw: ['read'], osd: ['read'] };
+    fixture.detectChanges();
+    expect(el.querySelector('#permitted')).toBeTruthy();
+  });
+
+  it('should show div with loose matching', () => {
+    component.matchAll = false;
+    fixture.detectChanges();
+
+    // Array condition
+    component.condition = ['configOpt', 'osd', 'rgw'];
+    fixture.detectChanges();
+    expect(el.querySelector('#permitted')).toBeTruthy();
+
+    // Object condition
+    component.condition = { rgw: ['read', 'update', 'fake'], osd: ['read'] };
+    fixture.detectChanges();
+    expect(el.querySelector('#permitted')).toBeTruthy();
+  });
+
+  it('should not show div on invalid condition', () => {
+    // String condition
+    component.condition = 'fake';
+    fixture.detectChanges();
+    expect(el.querySelector('#permitted')).toBeFalsy();
+
+    // Array condition
+    component.condition = ['configOpt', 'osd', 'rgw'];
+    fixture.detectChanges();
+    expect(el.querySelector('#permitted')).toBeFalsy();
+
+    // Object condition
+    component.condition = { rgw: ['read', 'update'], osd: ['read'] };
+    fixture.detectChanges();
+    expect(el.querySelector('#permitted')).toBeFalsy();
+  });
+
+  it('should hide div on condition change', () => {
+    component.condition = 'osd';
+    fixture.detectChanges();
+    expect(el.querySelector('#permitted')).toBeTruthy();
+
+    component.condition = 'grafana';
+    fixture.detectChanges();
+    expect(el.querySelector('#permitted')).toBeFalsy();
+  });
+
+  it('should hide div on permission change', () => {
+    component.condition = ['osd'];
+    fixture.detectChanges();
+    expect(el.querySelector('#permitted')).toBeTruthy();
+
+    getPermissionsSpy.and.returnValue(new Permissions({}));
+    component.condition = 'osd';
+    fixture.detectChanges();
+    expect(el.querySelector('#permitted')).toBeFalsy();
+  });
+});
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/directives/auth-storage.directive.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/directives/auth-storage.directive.ts
new file mode 100644 (file)
index 0000000..5f13090
--- /dev/null
@@ -0,0 +1,48 @@
+import { Directive, Input, TemplateRef, ViewContainerRef } from '@angular/core';
+
+import _ from 'lodash';
+
+import { Permission, Permissions } from '~/app/shared/models/permissions';
+import { AuthStorageService } from '~/app/shared/services/auth-storage.service';
+
+type Condition = string | string[] | Partial<{ [Property in keyof Permissions]: keyof Permission }>;
+
+@Directive({
+  selector: '[cdScope]'
+})
+export class AuthStorageDirective {
+  permissions: Permissions;
+
+  constructor(
+    private templateRef: TemplateRef<any>,
+    private viewContainer: ViewContainerRef,
+    private authStorageService: AuthStorageService
+  ) {}
+
+  @Input('cdScope') set cdScope(condition: Condition) {
+    this.permissions = this.authStorageService.getPermissions();
+    if (this.isAuthorized(condition)) {
+      this.viewContainer.createEmbeddedView(this.templateRef);
+    } else {
+      this.viewContainer.clear();
+    }
+  }
+
+  @Input() cdScopeMatchAll = true;
+
+  private isAuthorized(condition: Condition): boolean {
+    const everyOrSome = this.cdScopeMatchAll ? _.every : _.some;
+
+    if (_.isString(condition)) {
+      return _.get(this.permissions, [condition, 'read'], false);
+    } else if (_.isArray(condition)) {
+      return everyOrSome(condition, (permission) => this.permissions[permission]['read']);
+    } else if (_.isObject(condition)) {
+      return everyOrSome(condition, (value, key) => {
+        return everyOrSome(value, (val) => this.permissions[key][val]);
+      });
+    }
+
+    return false;
+  }
+}
index 00e5635d36b59be7057801748711b80141e6e181..4d6f80fd7456a7f4287a1c5be4588869928f18f2 100644 (file)
@@ -1,5 +1,6 @@
 import { NgModule } from '@angular/core';
 
+import { AuthStorageDirective } from './auth-storage.directive';
 import { AutofocusDirective } from './autofocus.directive';
 import { DimlessBinaryPerSecondDirective } from './dimless-binary-per-second.directive';
 import { DimlessBinaryDirective } from './dimless-binary.directive';
@@ -31,7 +32,8 @@ import { TrimDirective } from './trim.directive';
     FormScopeDirective,
     CdFormControlDirective,
     CdFormGroupDirective,
-    CdFormValidationDirective
+    CdFormValidationDirective,
+    AuthStorageDirective
   ],
   exports: [
     AutofocusDirective,
@@ -47,7 +49,8 @@ import { TrimDirective } from './trim.directive';
     FormScopeDirective,
     CdFormControlDirective,
     CdFormGroupDirective,
-    CdFormValidationDirective
+    CdFormValidationDirective,
+    AuthStorageDirective
   ]
 })
 export class DirectivesModule {}