]> git.apps.os.sepia.ceph.com Git - ceph.git/commitdiff
mgr/dashboard: add password expiration warning banner 30939/head
authorTatjana Dehler <tdehler@suse.com>
Mon, 16 Sep 2019 13:30:00 +0000 (15:30 +0200)
committerTatjana Dehler <tdehler@suse.com>
Fri, 13 Dec 2019 07:53:08 +0000 (08:53 +0100)
It adds a new component to notify the user about the expiration
of the password. The banner is shown on top of the page and can
be dismissed by the user.

Fixes: https://tracker.ceph.com/issues/40329
Signed-off-by: Tatjana Dehler <tdehler@suse.com>
src/pybind/mgr/dashboard/frontend/src/app/app.component.html
src/pybind/mgr/dashboard/frontend/src/app/shared/components/components.module.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/components/pwd-expiration-notification/pwd-expiration-notification.component.html [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/src/app/shared/components/pwd-expiration-notification/pwd-expiration-notification.component.scss [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/src/app/shared/components/pwd-expiration-notification/pwd-expiration-notification.component.spec.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/src/app/shared/components/pwd-expiration-notification/pwd-expiration-notification.component.ts [new file with mode: 0644]

index 7e08eef685f116a34a7f146e45ab78f0fb22af3c..6cfd22b4164369029c836bb49cd124670b167608 100644 (file)
@@ -15,6 +15,7 @@
   <!-- Page content -->
   <div ng-sidebar-content>
     <block-ui>
+      <cd-pwd-expiration-notification *ngIf="!isLoginActive()"></cd-pwd-expiration-notification>
       <cd-navigation *ngIf="!isLoginActive()"></cd-navigation>
       <div class="container-fluid"
            [ngClass]="{'full-height':isLoginActive(), 'dashboard':isDashboardPage()} ">
index 2edff8c53ab191877c4d861b77a7a6cd2464fa9e..8d1d1ae644ea11953cc906f3611319849c0098ca 100644 (file)
@@ -1,6 +1,7 @@
 import { CommonModule } from '@angular/common';
 import { NgModule } from '@angular/core';
 import { FormsModule, ReactiveFormsModule } from '@angular/forms';
+import { RouterModule } from '@angular/router';
 
 import { NgBootstrapFormValidationModule } from 'ng-bootstrap-form-validation';
 import { ChartsModule } from 'ng2-charts';
@@ -25,6 +26,7 @@ import { LanguageSelectorComponent } from './language-selector/language-selector
 import { LoadingPanelComponent } from './loading-panel/loading-panel.component';
 import { ModalComponent } from './modal/modal.component';
 import { NotificationsSidebarComponent } from './notifications-sidebar/notifications-sidebar.component';
+import { PwdExpirationNotificationComponent } from './pwd-expiration-notification/pwd-expiration-notification.component';
 import { RefreshSelectorComponent } from './refresh-selector/refresh-selector.component';
 import { SelectBadgesComponent } from './select-badges/select-badges.component';
 import { SelectComponent } from './select/select.component';
@@ -48,7 +50,8 @@ import { ViewCacheComponent } from './view-cache/view-cache.component';
     ModalModule.forRoot(),
     DirectivesModule,
     BsDropdownModule,
-    NgBootstrapFormValidationModule
+    NgBootstrapFormValidationModule,
+    RouterModule
   ],
   declarations: [
     ViewCacheComponent,
@@ -69,7 +72,8 @@ import { ViewCacheComponent } from './view-cache/view-cache.component';
     RefreshSelectorComponent,
     ConfigOptionComponent,
     AlertPanelComponent,
-    FormModalComponent
+    FormModalComponent,
+    PwdExpirationNotificationComponent
   ],
   providers: [],
   exports: [
@@ -88,7 +92,8 @@ import { ViewCacheComponent } from './view-cache/view-cache.component';
     SelectComponent,
     RefreshSelectorComponent,
     ConfigOptionComponent,
-    AlertPanelComponent
+    AlertPanelComponent,
+    PwdExpirationNotificationComponent
   ],
   entryComponents: [
     ModalComponent,
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/components/pwd-expiration-notification/pwd-expiration-notification.component.html b/src/pybind/mgr/dashboard/frontend/src/app/shared/components/pwd-expiration-notification/pwd-expiration-notification.component.html
new file mode 100644 (file)
index 0000000..d524a90
--- /dev/null
@@ -0,0 +1,13 @@
+<alert class="no-margin-bottom"
+       type="{{ alertType }}"
+       *ngIf="expirationDays != null && expirationDays <= pwdExpirationSettings.pwdExpirationWarning1"
+       [dismissible]="true">
+  <div *ngIf="expirationDays === 0"
+       i18n>Your password will expire in <strong>less than 1</strong> day. Click
+  <a routerLink="/user-profile/edit"
+     class="alert-link">here</a> to change it now.</div>
+  <div *ngIf="expirationDays > 0"
+       i18n>Your password will expire in <strong>{{ expirationDays }}</strong> day(s). Click
+  <a routerLink="/user-profile/edit"
+     class="alert-link">here</a> to change it now.</div>
+</alert>
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/components/pwd-expiration-notification/pwd-expiration-notification.component.scss b/src/pybind/mgr/dashboard/frontend/src/app/shared/components/pwd-expiration-notification/pwd-expiration-notification.component.scss
new file mode 100644 (file)
index 0000000..85467dd
--- /dev/null
@@ -0,0 +1,3 @@
+::ng-deep .no-margin-bottom .alert {
+  margin-bottom: 0;
+}
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/components/pwd-expiration-notification/pwd-expiration-notification.component.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/components/pwd-expiration-notification/pwd-expiration-notification.component.spec.ts
new file mode 100644 (file)
index 0000000..c756074
--- /dev/null
@@ -0,0 +1,120 @@
+import { HttpClientTestingModule } from '@angular/common/http/testing';
+import { Component } from '@angular/core';
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+import { Routes } from '@angular/router';
+import { RouterTestingModule } from '@angular/router/testing';
+
+import { of as observableOf } from 'rxjs';
+
+import { AlertModule } from 'ngx-bootstrap/alert';
+
+import { configureTestBed } from '../../../../testing/unit-test-helper';
+
+import { SettingsService } from '../../api/settings.service';
+import { AuthStorageService } from '../../services/auth-storage.service';
+import { PwdExpirationNotificationComponent } from './pwd-expiration-notification.component';
+
+describe('PwdExpirationNotificationComponent', () => {
+  let component: PwdExpirationNotificationComponent;
+  let fixture: ComponentFixture<PwdExpirationNotificationComponent>;
+  let settingsService: SettingsService;
+  let authStorageService: AuthStorageService;
+
+  @Component({ selector: 'cd-fake', template: '' })
+  class FakeComponent {}
+
+  const routes: Routes = [{ path: 'login', component: FakeComponent }];
+
+  configureTestBed({
+    declarations: [PwdExpirationNotificationComponent, FakeComponent],
+    imports: [
+      AlertModule.forRoot(),
+      HttpClientTestingModule,
+      RouterTestingModule.withRoutes(routes)
+    ],
+    providers: [SettingsService, AuthStorageService]
+  });
+
+  describe('password expiration date has been set', () => {
+    beforeEach(() => {
+      authStorageService = TestBed.get(AuthStorageService);
+      settingsService = TestBed.get(SettingsService);
+      spyOn(authStorageService, 'getPwdExpirationDate').and.returnValue(1645488000);
+      spyOn(settingsService, 'pwdExpirationSettings').and.returnValue(
+        observableOf({
+          user_pwd_expiration_warning_1: 10,
+          user_pwd_expiration_warning_2: 5,
+          user_pwd_expiration_span: 90
+        })
+      );
+      fixture = TestBed.createComponent(PwdExpirationNotificationComponent);
+      component = fixture.componentInstance;
+      fixture.detectChanges();
+    });
+
+    it('should create', () => {
+      component.ngOnInit();
+      expect(component).toBeTruthy();
+    });
+
+    it('should set warning levels', () => {
+      component.ngOnInit();
+      expect(component.pwdExpirationSettings.pwdExpirationWarning1).toBe(10);
+      expect(component.pwdExpirationSettings.pwdExpirationWarning2).toBe(5);
+    });
+
+    it('should calculate password expiration in days', () => {
+      const dateValue = Date;
+      spyOn(global, 'Date').and.callFake((date) => {
+        if (date) {
+          return new dateValue(date);
+        } else {
+          return new Date('2022-02-18T00:00:00.000Z');
+        }
+      });
+      component.ngOnInit();
+      expect(component['expirationDays']).toBe(4);
+    });
+
+    it('should set alert type warning correctly', () => {
+      const dateValue = Date;
+      spyOn(global, 'Date').and.callFake((date) => {
+        if (date) {
+          return new dateValue(date);
+        } else {
+          return new Date('2022-02-14T00:00:00.000Z');
+        }
+      });
+      component.ngOnInit();
+      expect(component['alertType']).toBe('warning');
+    });
+
+    it('should set alert type danger correctly', () => {
+      const dateValue = Date;
+      spyOn(global, 'Date').and.callFake((date) => {
+        if (date) {
+          return new dateValue(date);
+        } else {
+          return new Date('2022-02-18T00:00:00.000Z');
+        }
+      });
+      component.ngOnInit();
+      expect(component['alertType']).toBe('danger');
+    });
+  });
+
+  describe('password expiration date has not been set', () => {
+    beforeEach(() => {
+      authStorageService = TestBed.get(AuthStorageService);
+      spyOn(authStorageService, 'getPwdExpirationDate').and.returnValue(null);
+      fixture = TestBed.createComponent(PwdExpirationNotificationComponent);
+      component = fixture.componentInstance;
+      fixture.detectChanges();
+    });
+
+    it('should calculate no expirationDays', () => {
+      component.ngOnInit();
+      expect(component['expirationDays']).toBeUndefined();
+    });
+  });
+});
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/components/pwd-expiration-notification/pwd-expiration-notification.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/components/pwd-expiration-notification/pwd-expiration-notification.component.ts
new file mode 100644 (file)
index 0000000..b8b1ce7
--- /dev/null
@@ -0,0 +1,44 @@
+import { Component, OnInit } from '@angular/core';
+
+import { SettingsService } from '../../api/settings.service';
+import { CdPwdExpirationSettings } from '../../models/cd-pwd-expiration-settings';
+import { AuthStorageService } from '../../services/auth-storage.service';
+
+@Component({
+  selector: 'cd-pwd-expiration-notification',
+  templateUrl: './pwd-expiration-notification.component.html',
+  styleUrls: ['./pwd-expiration-notification.component.scss']
+})
+export class PwdExpirationNotificationComponent implements OnInit {
+  alertType: string;
+  expirationDays: number;
+  pwdExpirationSettings: CdPwdExpirationSettings;
+
+  constructor(
+    private settingsService: SettingsService,
+    private authStorageService: AuthStorageService
+  ) {}
+
+  ngOnInit() {
+    this.settingsService.pwdExpirationSettings().subscribe((pwdExpirationSettings) => {
+      this.pwdExpirationSettings = new CdPwdExpirationSettings(pwdExpirationSettings);
+      const pwdExpirationDate = this.authStorageService.getPwdExpirationDate();
+      if (pwdExpirationDate) {
+        this.expirationDays = this.getExpirationDays(pwdExpirationDate);
+        if (this.expirationDays <= this.pwdExpirationSettings.pwdExpirationWarning2) {
+          this.alertType = 'danger';
+        } else {
+          this.alertType = 'warning';
+        }
+      }
+    });
+  }
+
+  private getExpirationDays(pwdExpirationDate: number): number {
+    if (pwdExpirationDate) {
+      const current = new Date();
+      const expiration = new Date(pwdExpirationDate * 1000);
+      return Math.floor((expiration.valueOf() - current.valueOf()) / (1000 * 3600 * 24));
+    }
+  }
+}