]> git-server-git.apps.pok.os.sepia.ceph.com Git - ceph.git/commitdiff
mgr/dashboard: Enforce password change upon first login
authorTatjana Dehler <tdehler@suse.com>
Fri, 3 Jan 2020 15:52:51 +0000 (16:52 +0100)
committerVolker Theile <vtheile@suse.com>
Wed, 19 Feb 2020 14:54:42 +0000 (15:54 +0100)
Fixes: https://tracker.ceph.com/issues/24655
Signed-off-by: Tatjana Dehler <tdehler@suse.com>
21 files changed:
qa/tasks/mgr/dashboard/test_auth.py
qa/tasks/mgr/dashboard/test_user.py
src/pybind/mgr/dashboard/controllers/auth.py
src/pybind/mgr/dashboard/controllers/user.py
src/pybind/mgr/dashboard/frontend/src/app/core/auth/auth.module.ts
src/pybind/mgr/dashboard/frontend/src/app/core/auth/login/login.component.html
src/pybind/mgr/dashboard/frontend/src/app/core/auth/login/login.component.ts
src/pybind/mgr/dashboard/frontend/src/app/core/auth/user-form/user-form.component.html
src/pybind/mgr/dashboard/frontend/src/app/core/auth/user-form/user-form.component.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/core/auth/user-form/user-form.component.ts
src/pybind/mgr/dashboard/frontend/src/app/core/auth/user-form/user-form.model.ts
src/pybind/mgr/dashboard/frontend/src/app/core/auth/user-password-login-form/user-password-login-form.component.html [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/src/app/core/auth/user-password-login-form/user-password-login-form.component.scss [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/src/app/core/auth/user-password-login-form/user-password-login-form.component.spec.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/src/app/core/auth/user-password-login-form/user-password-login-form.component.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/src/app/shared/api/auth.service.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/models/login-response.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/services/api-interceptor.service.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/services/auth-guard.service.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/services/auth-storage.service.ts
src/pybind/mgr/dashboard/services/access_control.py

index 16536fa94e782988485223beb91a48bc18e25ad0..e76708a9c43beed7f7123f5355db7c7745ff9afe 100644 (file)
@@ -46,7 +46,8 @@ class AuthTest(DashboardTestCase):
             'username': JLeaf(str),
             'permissions': JObj(sub_elems={}, allow_unknown=True),
             'sso': JLeaf(bool),
-            'pwdExpirationDate': JLeaf(int, none=True)
+            'pwdExpirationDate': JLeaf(int, none=True),
+            'pwdUpdateRequired': JLeaf(bool)
         }, allow_unknown=False))
         self._validate_jwt_token(data['token'], "admin", data['permissions'])
 
@@ -154,7 +155,8 @@ class AuthTest(DashboardTestCase):
         self.assertSchema(data, JObj(sub_elems={
             "username": JLeaf(str),
             "permissions": JObj(sub_elems={}, allow_unknown=True),
-            "sso": JLeaf(bool)
+            "sso": JLeaf(bool),
+            "pwdUpdateRequired": JLeaf(bool)
         }, allow_unknown=False))
         self.logout()
 
index 17808482d125992a84436cfbd0565fb416faca39..2230be20e72124659552be38647f73540a98b01a 100644 (file)
@@ -33,7 +33,7 @@ class UserTest(DashboardTestCase):
 
     @classmethod
     def _create_user(cls, username=None, password=None, name=None, email=None, roles=None,
-                     enabled=True, pwd_expiration_date=None):
+                     enabled=True, pwd_expiration_date=None, pwd_update_required=False):
         data = {}
         if username:
             data['username'] = username
@@ -47,6 +47,7 @@ class UserTest(DashboardTestCase):
             data['roles'] = roles
         if pwd_expiration_date:
             data['pwdExpirationDate'] = pwd_expiration_date
+        data['pwdUpdateRequired'] = pwd_update_required
         data['enabled'] = enabled
         cls._post("/api/user", data)
 
@@ -75,7 +76,8 @@ class UserTest(DashboardTestCase):
             'roles': ['administrator'],
             'lastUpdate': user['lastUpdate'],
             'enabled': True,
-            'pwdExpirationDate': None
+            'pwdExpirationDate': None,
+            'pwdUpdateRequired': False
         })
 
         self._put('/api/user/user1', {
@@ -92,7 +94,8 @@ class UserTest(DashboardTestCase):
             'roles': ['block-manager'],
             'lastUpdate': user['lastUpdate'],
             'enabled': True,
-            'pwdExpirationDate': None
+            'pwdExpirationDate': None,
+            'pwdUpdateRequired': False
         })
 
         self._delete('/api/user/user1')
@@ -122,7 +125,8 @@ class UserTest(DashboardTestCase):
             'roles': ['administrator'],
             'lastUpdate': user['lastUpdate'],
             'enabled': False,
-            'pwdExpirationDate': None
+            'pwdExpirationDate': None,
+            'pwdUpdateRequired': False
         })
 
         self._delete('/api/user/klara')
@@ -141,7 +145,8 @@ class UserTest(DashboardTestCase):
             'roles': ['administrator'],
             'lastUpdate': user['lastUpdate'],
             'enabled': True,
-            'pwdExpirationDate': None
+            'pwdExpirationDate': None,
+            'pwdUpdateRequired': False
         }])
 
     def test_create_user_already_exists(self):
@@ -344,7 +349,8 @@ class UserTest(DashboardTestCase):
             'roles': ['administrator'],
             'lastUpdate': user['lastUpdate'],
             'enabled': True,
-            'pwdExpirationDate': future_date
+            'pwdExpirationDate': future_date,
+            'pwdUpdateRequired': False
         })
         self._delete('/api/user/user1')
 
@@ -412,6 +418,49 @@ class UserTest(DashboardTestCase):
         self._delete('/api/user/user1')
         self._ceph_cmd(['dashboard', 'set-user-pwd-expiration-span', '0'])
 
+   def test_pwd_update_required(self):
+        self._create_user(username='user1',
+                          password='mypassword10#',
+                          name='My Name',
+                          email='my@email.com',
+                          roles=['administrator'],
+                          pwd_update_required=True)
+        self.assertStatus(201)
+
+        user_1 = self._get('/api/user/user1')
+        self.assertStatus(200)
+        self.assertEqual(user_1['pwdUpdateRequired'], True)
+
+        self.login('user1', 'mypassword10#')
+        self.assertStatus(201)
+
+        self._get('/api/osd')
+        self.assertStatus(403)
+        self._reset_login_to_admin('user1')
+
+    def test_pwd_update_required_change_pwd(self):
+        self._create_user(username='user1',
+                          password='mypassword10#',
+                          name='My Name',
+                          email='my@email.com',
+                          roles=['administrator'],
+                          pwd_update_required=True)
+        self.assertStatus(201)
+
+        self.login('user1', 'mypassword10#')
+        self._post('/api/user/user1/change_password', {
+            'old_password': 'mypassword10#',
+            'new_password': 'newpassword01#'
+        })
+
+        self.login('user1', 'newpassword01#')
+        user_1 = self._get('/api/user/user1')
+        self.assertStatus(200)
+        self.assertEqual(user_1['pwdUpdateRequired'], False)
+        self._get('/api/osd')
+        self.assertStatus(200)
+        self._reset_login_to_admin('user1')
+
     def test_validate_password_weak(self):
         self._post('/api/user/validate_password', {
             'password': 'mypassword1'
index 44128ce3201a128f4c2c0cc523a060f05c6d4ffd..e7ceca3d67335dacb71d0453574d6d16284399a2 100644 (file)
@@ -21,10 +21,11 @@ class Auth(RESTController):
 
     def create(self, username, password):
         user_data = AuthManager.authenticate(username, password)
-        user_perms, pwd_expiration_date = None, None
+        user_perms, pwd_expiration_date, pwd_update_required = None, None, None
         if user_data:
             user_perms = user_data.get('permissions')
             pwd_expiration_date = user_data.get('pwdExpirationDate')
+            pwd_update_required = user_data.get('pwdUpdateRequired')
 
         if user_perms is not None:
             logger.debug('Login successful')
@@ -36,7 +37,8 @@ class Auth(RESTController):
                 'username': username,
                 'permissions': user_perms,
                 'pwdExpirationDate': pwd_expiration_date,
-                'sso': mgr.SSO_DB.protocol == 'saml2'
+                'sso': mgr.SSO_DB.protocol == 'saml2',
+                'pwdUpdateRequired': pwd_update_required
             }
 
         logger.debug('Login failed')
@@ -69,7 +71,8 @@ class Auth(RESTController):
                 return {
                     'username': user.username,
                     'permissions': user.permissions_dict(),
-                    'sso': mgr.SSO_DB.protocol == 'saml2'
+                    'sso': mgr.SSO_DB.protocol == 'saml2',
+                    'pwdUpdateRequired': user.pwd_update_required
                 }
         return {
             'login_url': self._get_login_url(),
index 688f60082447da4189416330e8bd0396f2b5460b..d32ee4cc8339ae4ce3aa37568e060623ad3a1d79 100644 (file)
@@ -68,7 +68,7 @@ class User(RESTController):
         return User._user_to_dict(user)
 
     def create(self, username=None, password=None, name=None, email=None,
-               roles=None, enabled=True, pwdExpirationDate=None):
+               roles=None, enabled=True, pwdExpirationDate=None, pwdUpdateRequired=True):
         if not username:
             raise DashboardException(msg='Username is required',
                                      code='username_required',
@@ -80,7 +80,8 @@ class User(RESTController):
             validate_password_policy(password, username)
         try:
             user = mgr.ACCESS_CTRL_DB.create_user(username, password, name,
-                                                  email, enabled, pwdExpirationDate)
+                                                  email, enabled, pwdExpirationDate,
+                                                  pwdUpdateRequired)
         except UserAlreadyExists:
             raise DashboardException(msg='Username already exists',
                                      code='username_already_exists',
@@ -109,7 +110,7 @@ class User(RESTController):
         mgr.ACCESS_CTRL_DB.save()
 
     def set(self, username, password=None, name=None, email=None, roles=None,
-            enabled=None, pwdExpirationDate=None):
+            enabled=None, pwdExpirationDate=None, pwdUpdateRequired=False):
         if JwtManager.get_username() == username and enabled is False:
             raise DashboardException(msg='You are not allowed to disable your user',
                                      code='cannot_disable_current_user',
@@ -136,6 +137,7 @@ class User(RESTController):
             user.enabled = enabled
         user.pwd_expiration_date = pwdExpirationDate
         user.set_roles(user_roles)
+        user.pwd_update_required = pwdUpdateRequired
         mgr.ACCESS_CTRL_DB.save()
         return User._user_to_dict(user)
 
index 56ce6b74c5a5571dc41c59b594652120771b9921..43d56c9bad5eb703df588765a8a580db12dbafcb 100644 (file)
@@ -20,6 +20,7 @@ import { SsoNotFoundComponent } from './sso/sso-not-found/sso-not-found.componen
 import { UserFormComponent } from './user-form/user-form.component';
 import { UserListComponent } from './user-list/user-list.component';
 import { UserPasswordFormComponent } from './user-password-form/user-password-form.component';
+import { UserPasswordLoginFormComponent } from './user-password-login-form/user-password-login-form.component';
 import { UserTabsComponent } from './user-tabs/user-tabs.component';
 
 @NgModule({
@@ -45,7 +46,8 @@ import { UserTabsComponent } from './user-tabs/user-tabs.component';
     UserTabsComponent,
     UserListComponent,
     UserFormComponent,
-    UserPasswordFormComponent
+    UserPasswordFormComponent,
+    UserPasswordLoginFormComponent
   ]
 })
 export class AuthModule {}
index aef59d072588fef12dd83a1025bedd92521a2a1f..ab42a8ad83d03baa0b7834d191a98f057779008c 100644 (file)
@@ -1,5 +1,5 @@
 <div class="login"
-     *ngIf="isLoginActive">
+     *ngIf="isLoginActive || pwdUpdateRequired">
   <header>
     <nav class="navbar">
       <a class="navbar-brand"></a>
@@ -24,6 +24,7 @@
           <form name="loginForm"
                 (ngSubmit)="login()"
                 #loginForm="ngForm"
+                *ngIf="!pwdUpdateRequired"
                 novalidate>
 
             <!-- Username -->
@@ -70,6 +71,7 @@
                    value="Login"
                    i18n-value>
           </form>
+          <cd-user-password-login-form *ngIf="pwdUpdateRequired"></cd-user-password-login-form>
         </div>
       </div>
     </div>
index 9286e31d30f107ea11a64662cdd9111cbacbda0b..de8a44ddfea67a7b4265ee653de6c05ee8181493 100644 (file)
@@ -5,6 +5,7 @@ import { BsModalService } from 'ngx-bootstrap/modal';
 
 import { AuthService } from '../../../shared/api/auth.service';
 import { Credentials } from '../../../shared/models/credentials';
+import { LoginResponse } from '../../../shared/models/login-response';
 import { AuthStorageService } from '../../../shared/services/auth-storage.service';
 
 @Component({
@@ -15,6 +16,7 @@ import { AuthStorageService } from '../../../shared/services/auth-storage.servic
 export class LoginComponent implements OnInit {
   model = new Credentials();
   isLoginActive = false;
+  pwdUpdateRequired = false;
 
   constructor(
     private authService: AuthService,
@@ -25,6 +27,7 @@ export class LoginComponent implements OnInit {
 
   ngOnInit() {
     if (this.authStorageService.isLoggedIn()) {
+      this.pwdUpdateRequired = this.authStorageService.getPwdUpdateRequired();
       this.router.navigate(['']);
     } else {
       // Make sure all open modal dialogs are closed. This might be
@@ -49,13 +52,7 @@ export class LoginComponent implements OnInit {
             window.location.replace(login.login_url);
           }
         } else {
-          this.authStorageService.set(
-            login.username,
-            token,
-            login.permissions,
-            login.sso,
-            login.pwdExpirationDate
-          );
+          this.authStorageService.set(login.username, token, login.permissions, login.sso);
           this.router.navigate(['']);
         }
       });
@@ -63,8 +60,12 @@ export class LoginComponent implements OnInit {
   }
 
   login() {
-    this.authService.login(this.model).subscribe(() => {
-      this.router.navigate(['']);
+    this.authService.login(this.model).subscribe((resp: LoginResponse) => {
+      if (resp.pwdUpdateRequired) {
+        this.pwdUpdateRequired = true;
+      } else {
+        this.router.navigate(['']);
+      }
     });
   }
 }
index bdc0a32df775e8a488b419e518b0be18188af59d..da786a20db3753c3bdcaf182dac752d01a61135d 100644 (file)
           </div>
         </div>
 
+        <!-- Change Password -->
+        <div class="form-group row"
+             *ngIf="!isCurrentUser()">
+          <div class="offset-sm-3 col-sm-9">
+            <div class="custom-control custom-checkbox">
+              <input type="checkbox"
+                     class="custom-control-input"
+                     id="pwdUpdateRequired"
+                     name="pwdUpdateRequired"
+                     formControlName="pwdUpdateRequired">
+              <label class="custom-control-label"
+                     for="pwdUpdateRequired"
+                     i18n>User must change password at next logon</label>
+            </div>
+          </div>
+        </div>
+
       </div>
       <div class="card-footer">
         <div class="button-group text-right">
index 35a8e7bad7c1f95e9fc1efec55b1571dda3c2327..87ba10d690c6e646b06075eaa57fa31680b9c344 100644 (file)
@@ -128,7 +128,8 @@ describe('UserFormComponent', () => {
         email: 'user0@email.com',
         roles: ['administrator'],
         enabled: true,
-        pwdExpirationDate: undefined
+        pwdExpirationDate: undefined,
+        pwdUpdateRequired: true
       };
       formHelper.setMultipleValues(user);
       formHelper.setValue('confirmpassword', user.password);
@@ -149,7 +150,8 @@ describe('UserFormComponent', () => {
       email: 'user1@email.com',
       roles: ['administrator'],
       enabled: true,
-      pwdExpirationDate: undefined
+      pwdExpirationDate: undefined,
+      pwdUpdateRequired: true
     };
     const roles = [
       {
@@ -246,6 +248,7 @@ describe('UserFormComponent', () => {
       expect(userReq.request.body).toEqual({
         username: 'user1',
         password: '',
+        pwdUpdateRequired: true,
         name: 'User 1',
         email: 'user1@email.com',
         roles: ['administrator'],
index 3299a088d047ce34f0a3a54b08657f6b38675068..096e2e40a9bf97adf12eecab2cd8e2f4dd8ce005 100644 (file)
@@ -106,7 +106,8 @@ export class UserFormComponent implements OnInit {
         pwdExpirationDate: [''],
         email: ['', [CdValidators.email]],
         roles: [[]],
-        enabled: [true, [Validators.required]]
+        enabled: [true, [Validators.required]],
+        pwdUpdateRequired: [true]
       },
       {
         validators: [CdValidators.match('password', 'confirmpassword')]
@@ -165,7 +166,7 @@ export class UserFormComponent implements OnInit {
   }
 
   setResponse(response: UserFormModel) {
-    ['username', 'name', 'email', 'roles', 'enabled'].forEach((key) =>
+    ['username', 'name', 'email', 'roles', 'enabled', 'pwdUpdateRequired'].forEach((key) =>
       this.userForm.get(key).setValue(response[key])
     );
     const expirationDate = response['pwdExpirationDate'];
@@ -176,7 +177,7 @@ export class UserFormComponent implements OnInit {
 
   getRequest(): UserFormModel {
     const userFormModel = new UserFormModel();
-    ['username', 'password', 'name', 'email', 'roles', 'enabled'].forEach(
+    ['username', 'password', 'name', 'email', 'roles', 'enabled', 'pwdUpdateRequired'].forEach(
       (key) => (userFormModel[key] = this.userForm.get(key).value)
     );
     const expirationDate = this.userForm.get('pwdExpirationDate').value;
index 4989905800d86d4ab4344d16b9a68e0e64eb24dd..2dc88ab5a4ab258e704ea3f739b5aefd9b38d6ee 100644 (file)
@@ -6,4 +6,5 @@ export class UserFormModel {
   email: string;
   roles: Array<string>;
   enabled: boolean;
+  pwdUpdateRequired: boolean;
 }
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/core/auth/user-password-login-form/user-password-login-form.component.html b/src/pybind/mgr/dashboard/frontend/src/app/core/auth/user-password-login-form/user-password-login-form.component.html
new file mode 100644 (file)
index 0000000..b74107c
--- /dev/null
@@ -0,0 +1,85 @@
+<form #frm="ngForm"
+      [formGroup]="userForm"
+      (ngSubmit)="onSubmit()"
+      novalidate>
+  <div class="form-group has-feedback">
+    <div class="input-group">
+      <input class="form-control"
+             type="password"
+             placeholder="Old password..."
+             id="oldpassword"
+             formControlName="oldpassword"
+             autocomplete="off"
+             autofocus>
+      <span class="input-group-append">
+        <button class="btn btn-outline-light btn-password"
+                cdPasswordButton="oldpassword">
+        </button>
+      </span>
+    </div>
+    <span class="invalid-feedback"
+          *ngIf="userForm.showError('oldpassword', frm, 'required')"
+          i18n>This field is required.</span>
+    <span class="invalid-feedback"
+          *ngIf="userForm.showError('oldpassword', frm, 'notmatch')"
+          i18n>The old and new passwords must be different.</span>
+  </div>
+  <div class="form-group has-feedback">
+    <div class="input-group">
+      <input class="form-control"
+             type="password"
+             placeholder="Password..."
+             id="newpassword"
+             autocomplete="new-password"
+             formControlName="newpassword">
+      <span class="input-group-append">
+        <button type="button"
+                class="btn btn-outline-light btn-password"
+                cdPasswordButton="newpassword">
+        </button>
+      </span>
+    </div>
+    <div class="passwordStrengthLevel">
+      <div class="{{ passwordStrengthLevel }}"
+           data-toggle="tooltip"
+           title="{{ passwordStrengthDescription }}">
+      </div>
+    </div>
+    <span class="invalid-feedback"
+          *ngIf="userForm.showError('newpassword', frm, 'required')"
+          i18n>This field is required.</span>
+    <span class="invalid-feedback"
+          *ngIf="userForm.showError('newpassword', frm, 'notmatch')"
+          i18n>The old and new passwords must be different.</span>
+    <span class="invalid-feedback"
+          *ngIf="userForm.showError('newpassword', frm, 'checkPassword')"
+          i18n>Too weak</span>
+  </div>
+  <div class="form-group has-feedback">
+    <div class="input-group">
+      <input class="form-control"
+             type="password"
+             autocomplete="off"
+             placeholder="Confirm new password..."
+             id="confirmnewpassword"
+             formControlName="confirmnewpassword">
+      <span class="input-group-append">
+        <button class="btn btn-outline-light btn-password"
+                cdPasswordButton="confirmnewpassword">
+        </button>
+      </span>
+    </div>
+    <span class="invalid-feedback"
+          *ngIf="userForm.showError('confirmnewpassword', frm, 'required')"
+          i18n>This field is required.</span>
+    <span class="invalid-feedback"
+          *ngIf="userForm.showError('confirmnewpassword', frm, 'match')"
+          i18n>Password confirmation doesn't match the new password.</span>
+  </div>
+
+  <input type="submit"
+         class="btn btn-secondary btn-block"
+         [disabled]="userForm.invalid"
+         value="Change password"
+         i18n-value>
+</form>
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/core/auth/user-password-login-form/user-password-login-form.component.scss b/src/pybind/mgr/dashboard/frontend/src/app/core/auth/user-password-login-form/user-password-login-form.component.scss
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/core/auth/user-password-login-form/user-password-login-form.component.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/core/auth/user-password-login-form/user-password-login-form.component.spec.ts
new file mode 100644 (file)
index 0000000..a5607ad
--- /dev/null
@@ -0,0 +1,40 @@
+import { HttpClientTestingModule } from '@angular/common/http/testing';
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+import { ReactiveFormsModule } from '@angular/forms';
+import { RouterTestingModule } from '@angular/router/testing';
+
+import { ToastrModule } from 'ngx-toastr';
+
+import { configureTestBed, i18nProviders } from '../../../../testing/unit-test-helper';
+import { SharedModule } from '../../../shared/shared.module';
+import { UserPasswordLoginFormComponent } from './user-password-login-form.component';
+
+describe('UserPasswordLoginFormComponent', () => {
+  let component: UserPasswordLoginFormComponent;
+  let fixture: ComponentFixture<UserPasswordLoginFormComponent>;
+
+  configureTestBed(
+    {
+      imports: [
+        HttpClientTestingModule,
+        RouterTestingModule,
+        ReactiveFormsModule,
+        ToastrModule.forRoot(),
+        SharedModule
+      ],
+      declarations: [UserPasswordLoginFormComponent],
+      providers: i18nProviders
+    },
+    true
+  );
+
+  beforeEach(() => {
+    fixture = TestBed.createComponent(UserPasswordLoginFormComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/core/auth/user-password-login-form/user-password-login-form.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/core/auth/user-password-login-form/user-password-login-form.component.ts
new file mode 100644 (file)
index 0000000..1b1ecfe
--- /dev/null
@@ -0,0 +1,10 @@
+import { Component } from '@angular/core';
+
+import { UserPasswordFormComponent } from '../user-password-form/user-password-form.component';
+
+@Component({
+  selector: 'cd-user-password-login-form',
+  templateUrl: './user-password-login-form.component.html',
+  styleUrls: ['./user-password-login-form.component.scss']
+})
+export class UserPasswordLoginFormComponent extends UserPasswordFormComponent {}
index 7172ccba926a9754530e06c3632ff17cf2f91677..c6229377f90654525b2121115c888b9b5fb8a247 100644 (file)
@@ -32,7 +32,8 @@ export class AuthService {
           resp.token,
           resp.permissions,
           resp.sso,
-          resp.pwdExpirationDate
+          resp.pwdExpirationDate,
+          resp.pwdUpdateRequired
         );
       })
     );
index d9f1797a74c46965cf3f32435e1e4449a91a4fa5..7b9fc4b277e714f59509cf988b574a1c294b717f 100644 (file)
@@ -4,4 +4,5 @@ export class LoginResponse {
   permissions: object;
   pwdExpirationDate: number;
   sso: boolean;
+  pwdUpdateRequired: boolean;
 }
index c65f8c051882729ca601da4c9da20e9b25a89d7d..0d46ed1236858edea02b3aa5419f60607416a240 100644 (file)
@@ -61,7 +61,11 @@ export class ApiInterceptorService implements HttpInterceptor {
               this.router.navigate(['/login']);
               break;
             case 403:
-              this.router.navigate(['/403']);
+              if (this.authStorageService.getPwdUpdateRequired()) {
+                this.router.navigate(['/login']);
+              } else {
+                this.router.navigate(['/403']);
+              }
               break;
             default:
               timeoutId = this.prepareNotification(resp);
index 7e11d9a2d033804488e687e5504b884ede4bfb0d..48a367ff99545cdde72c2ac2482697cb5327f2a1 100644 (file)
@@ -10,7 +10,7 @@ export class AuthGuardService implements CanActivate, CanActivateChild {
   constructor(private router: Router, private authStorageService: AuthStorageService) {}
 
   canActivate() {
-    if (this.authStorageService.isLoggedIn()) {
+    if (this.authStorageService.isLoggedIn() && !this.authStorageService.getPwdUpdateRequired()) {
       return true;
     }
     this.router.navigate(['/login']);
index b603e7fec42feb8a9d2f57018ccb6854ad8a990d..fcc24cf5b957eda0196377622299ee4060b86357 100644 (file)
@@ -18,12 +18,14 @@ export class AuthStorageService {
     token: string,
     permissions = {},
     sso = false,
-    pwdExpirationDate: number = null
+    pwdExpirationDate: number = null,
+    pwdUpdateRequired: boolean = false
   ) {
     localStorage.setItem('dashboard_username', username);
     localStorage.setItem('access_token', token);
     localStorage.setItem('dashboard_permissions', JSON.stringify(new Permissions(permissions)));
     localStorage.setItem('user_pwd_expiration_date', String(pwdExpirationDate));
+    localStorage.setItem('user_pwd_update_required', String(pwdUpdateRequired));
     localStorage.setItem('sso', String(sso));
   }
 
@@ -31,6 +33,7 @@ export class AuthStorageService {
     localStorage.removeItem('access_token');
     localStorage.removeItem('dashboard_username');
     localStorage.removeItem('user_pwd_expiration_data');
+    localStorage.removeItem('user_pwd_update_required');
   }
 
   getToken(): string {
@@ -55,6 +58,10 @@ export class AuthStorageService {
     return Number(localStorage.getItem('user_pwd_expiration_date'));
   }
 
+  getPwdUpdateRequired(): boolean {
+    return localStorage.getItem('user_pwd_update_required') === 'true';
+  }
+
   isSSO() {
     return localStorage.getItem('sso') === 'true';
   }
index 99222a97cf6cf2ef9be4317f2ec687f624eaeb9f..86bbf8ae3fa3bb22339b03d842cd9aab955dd9f0 100644 (file)
@@ -292,7 +292,8 @@ SYSTEM_ROLES = {
 
 class User(object):
     def __init__(self, username, password, name=None, email=None, roles=None,
-                 last_update=None, enabled=True, pwd_expiration_date=None):
+                 last_update=None, enabled=True, pwd_expiration_date=None,
+                 pwd_update_required=False):
         self.username = username
         self.password = password
         self.name = name
@@ -309,6 +310,7 @@ class User(object):
         self.pwd_expiration_date = pwd_expiration_date
         if self.pwd_expiration_date is None:
             self.refresh_pwd_expiration_date()
+        self.pwd_update_required = pwd_update_required
 
     def refresh_last_update(self):
         self.last_update = int(time.time())
@@ -337,6 +339,7 @@ class User(object):
         self.password = hashed_password
         self.refresh_last_update()
         self.refresh_pwd_expiration_date()
+        self.pwd_update_required = False
 
     def compare_password(self, password):
         """
@@ -371,6 +374,9 @@ class User(object):
         self.refresh_last_update()
 
     def authorize(self, scope, permissions):
+        if self.pwd_update_required:
+            return False
+
         for role in self.roles:
             if role.authorize(scope, permissions):
                 return True
@@ -397,14 +403,16 @@ class User(object):
             'email': self.email,
             'lastUpdate': self.last_update,
             'enabled': self.enabled,
-            'pwdExpirationDate': self.pwd_expiration_date
+            'pwdExpirationDate': self.pwd_expiration_date,
+            'pwdUpdateRequired': self.pwd_update_required
         }
 
     @classmethod
     def from_dict(cls, u_dict, roles):
         return User(u_dict['username'], u_dict['password'], u_dict['name'],
                     u_dict['email'], {roles[r] for r in u_dict['roles']},
-                    u_dict['lastUpdate'], u_dict['enabled'], u_dict['pwdExpirationDate'])
+                    u_dict['lastUpdate'], u_dict['enabled'],
+                    u_dict['pwdExpirationDate'], u_dict['pwdUpdateRequired'])
 
 
 class AccessControlDB(object):
@@ -444,7 +452,8 @@ class AccessControlDB(object):
 
             del self.roles[name]
 
-    def create_user(self, username, password, name, email, enabled=True, pwd_expiration_date=None):
+    def create_user(self, username, password, name, email, enabled=True,
+                    pwd_expiration_date=None, pwd_update_required=False):
         logger.debug("creating user: username=%s", username)
         with self.lock:
             if username in self.users:
@@ -453,7 +462,8 @@ class AccessControlDB(object):
                (pwd_expiration_date < int(time.mktime(datetime.utcnow().timetuple()))):
                 raise PwdExpirationDateNotValid()
             user = User(username, password_hash(password), name, email, enabled=enabled,
-                        pwd_expiration_date=pwd_expiration_date)
+                        pwd_expiration_date=pwd_expiration_date,
+                        pwd_update_required=pwd_update_required)
             self.users[username] = user
             return user
 
@@ -907,7 +917,8 @@ class LocalAuthenticator(object):
                 if user.enabled and user.compare_password(password) \
                    and not user.is_pwd_expired():
                     return {'permissions': user.permissions_dict(),
-                            'pwdExpirationDate': user.pwd_expiration_date}
+                            'pwdExpirationDate': user.pwd_expiration_date,
+                            'pwdUpdateRequired': user.pwd_update_required}
         except UserDoesNotExist:
             logger.debug("User '%s' does not exist", username)
         return None