From 8f809bd95bf5e48d9e55981430c50cab3012cb3c Mon Sep 17 00:00:00 2001 From: Ngwa Sedrick Meh Date: Mon, 26 Apr 2021 21:17:08 +0100 Subject: [PATCH] mgr/dashboard: create directive for AuthStorage service 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 --- .../pool/pool-list/pool-list.component.html | 2 +- .../directives/auth-storage.directive.spec.ts | 104 ++++++++++++++++++ .../directives/auth-storage.directive.ts | 48 ++++++++ .../shared/directives/directives.module.ts | 7 +- 4 files changed, 158 insertions(+), 3 deletions(-) create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/directives/auth-storage.directive.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/directives/auth-storage.directive.ts diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/pool/pool-list/pool-list.component.html b/src/pybind/mgr/dashboard/frontend/src/app/ceph/pool/pool-list/pool-list.component.html index 2d3ee6976b474..7122f749cfb9e 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/pool/pool-list/pool-list.component.html +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/pool/pool-list/pool-list.component.html @@ -33,7 +33,7 @@
  • + *cdScope="'grafana'"> Overall Performance 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 index 0000000000000..c6c0adbbeac8a --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/directives/auth-storage.directive.spec.ts @@ -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: `
    ` +}) +export class AuthStorageDirectiveTestComponent { + condition: string | string[] | object; + matchAll = true; +} + +describe('AuthStorageDirective', () => { + let fixture: ComponentFixture; + 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 index 0000000000000..5f130902a23c5 --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/directives/auth-storage.directive.ts @@ -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, + 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; + } +} diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/directives/directives.module.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/directives/directives.module.ts index 00e5635d36b59..4d6f80fd7456a 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/shared/directives/directives.module.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/directives/directives.module.ts @@ -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 {} -- 2.39.5