From: Tatjana Dehler Date: Fri, 3 Jan 2020 15:52:51 +0000 (+0100) Subject: mgr/dashboard: Enforce password change upon first login X-Git-Tag: v15.1.1~323^2~1 X-Git-Url: http://git-server-git.apps.pok.os.sepia.ceph.com/?a=commitdiff_plain;h=310f7876876b991ed8067228b7c86d0454476405;p=ceph.git mgr/dashboard: Enforce password change upon first login Fixes: https://tracker.ceph.com/issues/24655 Signed-off-by: Tatjana Dehler --- diff --git a/qa/tasks/mgr/dashboard/test_auth.py b/qa/tasks/mgr/dashboard/test_auth.py index 16536fa94e7..e76708a9c43 100644 --- a/qa/tasks/mgr/dashboard/test_auth.py +++ b/qa/tasks/mgr/dashboard/test_auth.py @@ -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() diff --git a/qa/tasks/mgr/dashboard/test_user.py b/qa/tasks/mgr/dashboard/test_user.py index 17808482d12..2230be20e72 100644 --- a/qa/tasks/mgr/dashboard/test_user.py +++ b/qa/tasks/mgr/dashboard/test_user.py @@ -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' diff --git a/src/pybind/mgr/dashboard/controllers/auth.py b/src/pybind/mgr/dashboard/controllers/auth.py index 44128ce3201..e7ceca3d673 100644 --- a/src/pybind/mgr/dashboard/controllers/auth.py +++ b/src/pybind/mgr/dashboard/controllers/auth.py @@ -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(), diff --git a/src/pybind/mgr/dashboard/controllers/user.py b/src/pybind/mgr/dashboard/controllers/user.py index 688f6008244..d32ee4cc833 100644 --- a/src/pybind/mgr/dashboard/controllers/user.py +++ b/src/pybind/mgr/dashboard/controllers/user.py @@ -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) diff --git a/src/pybind/mgr/dashboard/frontend/src/app/core/auth/auth.module.ts b/src/pybind/mgr/dashboard/frontend/src/app/core/auth/auth.module.ts index 56ce6b74c5a..43d56c9bad5 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/core/auth/auth.module.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/core/auth/auth.module.ts @@ -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 {} diff --git a/src/pybind/mgr/dashboard/frontend/src/app/core/auth/login/login.component.html b/src/pybind/mgr/dashboard/frontend/src/app/core/auth/login/login.component.html index aef59d07258..ab42a8ad83d 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/core/auth/login/login.component.html +++ b/src/pybind/mgr/dashboard/frontend/src/app/core/auth/login/login.component.html @@ -1,5 +1,5 @@ diff --git a/src/pybind/mgr/dashboard/frontend/src/app/core/auth/login/login.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/core/auth/login/login.component.ts index 9286e31d30f..de8a44ddfea 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/core/auth/login/login.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/core/auth/login/login.component.ts @@ -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(['']); + } }); } } diff --git a/src/pybind/mgr/dashboard/frontend/src/app/core/auth/user-form/user-form.component.html b/src/pybind/mgr/dashboard/frontend/src/app/core/auth/user-form/user-form.component.html index bdc0a32df77..da786a20db3 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/core/auth/user-form/user-form.component.html +++ b/src/pybind/mgr/dashboard/frontend/src/app/core/auth/user-form/user-form.component.html @@ -215,6 +215,23 @@ + +
+
+
+ + +
+
+
+