From: Dziomdziora, El?bieta Date: Wed, 7 Aug 2019 13:49:20 +0000 (+0200) Subject: mgr/dashboard: Support minimum password complexity rules X-Git-Tag: v15.1.0~1390^2 X-Git-Url: http://git-server-git.apps.pok.os.sepia.ceph.com/?a=commitdiff_plain;h=ce3a9483eceff47ad8169a5d828e30af84629fed;p=ceph.git mgr/dashboard: Support minimum password complexity rules Fixes: https://tracker.ceph.com/issues/25232 Signed-off-by: Dziomdziora, Elżbieta --- diff --git a/qa/tasks/mgr/dashboard/test_user.py b/qa/tasks/mgr/dashboard/test_user.py index a72df6f1375..5a3566f105e 100644 --- a/qa/tasks/mgr/dashboard/test_user.py +++ b/qa/tasks/mgr/dashboard/test_user.py @@ -25,7 +25,7 @@ class UserTest(DashboardTestCase): def test_crud_user(self): self._create_user(username='user1', - password='mypassword', + password='mypassword10#', name='My Name', email='my@email.com', roles=['administrator']) @@ -79,7 +79,7 @@ class UserTest(DashboardTestCase): def test_create_user_already_exists(self): self._create_user(username='admin', - password='mypassword', + password='mypassword10#', name='administrator', email='my@email.com', roles=['administrator']) @@ -89,7 +89,7 @@ class UserTest(DashboardTestCase): def test_create_user_invalid_role(self): self._create_user(username='user1', - password='mypassword', + password='mypassword10#', name='My Name', email='my@email.com', roles=['invalid-role']) @@ -141,16 +141,71 @@ class UserTest(DashboardTestCase): self.assertStatus(400) self.assertError(code='invalid_old_password', component='user') + def test_change_password_as_old_password(self): + self.create_user('test1', 'mypassword10#', ['read-only']) + self.login('test1', 'mypassword10#') + self._post('/api/user/test1/change_password', { + 'old_password': 'mypassword10#', + 'new_password': 'mypassword10#' + }) + self.assertStatus(400) + self.assertError(code='the_same_as_old_password', component='user') + self.delete_user('test1') + + def test_change_password_contains_username(self): + self.create_user('test1', 'mypassword10#', ['read-only']) + self.login('test1', 'mypassword10#') + self._post('/api/user/test1/change_password', { + 'old_password': 'mypassword10#', + 'new_password': 'mypasstest1@#' + }) + self.assertStatus(400) + self.assertError(code='contains_username', component='user') + self.delete_user('test1') + + def test_change_password_contains_forbidden_words(self): + self.create_user('test1', 'mypassword10#', ['read-only']) + self.login('test1', 'mypassword10#') + self._post('/api/user/test1/change_password', { + 'old_password': 'mypassword10#', + 'new_password': 'mypassOSD01' + }) + self.assertStatus(400) + self.assertError(code='contains_forbidden_words', component='user') + self.delete_user('test1') + + def test_change_password_contains_sequential_characters(self): + self.create_user('test1', 'mypassword10#', ['read-only']) + self.login('test1', 'mypassword10#') + self._post('/api/user/test1/change_password', { + 'old_password': 'mypassword10#', + 'new_password': 'mypass123456!@$' + }) + self.assertStatus(400) + self.assertError(code='contains_sequential_characters', component='user') + self.delete_user('test1') + + def test_change_password_contains_repetetive_characters(self): + self.create_user('test1', 'mypassword10#', ['read-only']) + self.login('test1', 'mypassword10#') + self._post('/api/user/test1/change_password', { + 'old_password': 'mypassword10#', + 'new_password': 'aaaaA1@!#' + }) + self.assertStatus(400) + self.assertError(code='contains_repetetive_characters', component='user') + self.delete_user('test1') + def test_change_password(self): - self.create_user('test1', 'test1', ['read-only']) - self.login('test1', 'test1') + self.create_user('test1', 'mypassword10#', ['read-only']) + self.login('test1', 'mypassword10#') self._post('/api/user/test1/change_password', { - 'old_password': 'test1', - 'new_password': 'foo' + 'old_password': 'mypassword10#', + 'new_password': 'newpassword01#' }) self.assertStatus(200) self.logout() - self._post('/api/auth', {'username': 'test1', 'password': 'test1'}) + self._post('/api/auth', {'username': 'test1', 'password': 'mypassword10#'}) self.assertStatus(400) self.assertError(code='invalid_credentials', component='auth') self.delete_user('test1') diff --git a/src/pybind/mgr/dashboard/controllers/user.py b/src/pybind/mgr/dashboard/controllers/user.py index 07728c88bf5..a8d63a21adc 100644 --- a/src/pybind/mgr/dashboard/controllers/user.py +++ b/src/pybind/mgr/dashboard/controllers/user.py @@ -8,10 +8,41 @@ from .. import mgr from ..exceptions import DashboardException, UserAlreadyExists, \ UserDoesNotExist from ..security import Scope -from ..services.access_control import SYSTEM_ROLES +from ..services.access_control import SYSTEM_ROLES, PasswordCheck from ..services.auth import JwtManager +def check_password_complexity(password, username, old_password=None): + password_complexity = PasswordCheck(password, username, old_password) + if password_complexity.check_if_as_the_old_password(): + raise DashboardException(msg='Password cannot be the\ + same as the previous one.', + code='not-strong-enough-password', + component='user') + if password_complexity.check_if_contains_username(): + raise DashboardException(msg='Password cannot contain username.', + code='not-strong-enough-password', + component='user') + if password_complexity.check_if_contains_forbidden_words(): + raise DashboardException(msg='Password cannot contain keywords.', + code='not-strong-enough-password', + component='user') + if password_complexity.check_if_repetetive_characters(): + raise DashboardException(msg='Password cannot contain repetitive\ + characters.', + code='not-strong-enough-password', + component='user') + if password_complexity.check_if_sequential_characters(): + raise DashboardException(msg='Password cannot contain sequential\ + characters.', + code='not-strong-enough-password', + component='user') + if password_complexity.check_password_characters() < 10: + raise DashboardException(msg='Password is too weak.', + code='not-strong-enough-password', + component='user') + + @ApiController('/user', Scope.USER) class User(RESTController): @staticmethod @@ -52,6 +83,8 @@ class User(RESTController): user_roles = None if roles: user_roles = User._get_user_roles(roles) + if password: + check_password_complexity(password, username) try: user = mgr.ACCESS_CTRL_DB.create_user(username, password, name, email, enabled) @@ -91,6 +124,7 @@ class User(RESTController): if roles: user_roles = User._get_user_roles(roles) if password: + check_password_complexity(password, username) user.set_password(password) user.name = name user.email = email @@ -118,5 +152,6 @@ class UserChangePassword(BaseController): raise DashboardException(msg='Invalid old password', code='invalid_old_password', component='user') + check_password_complexity(new_password, username, old_password) user.set_password(new_password) mgr.ACCESS_CTRL_DB.save() 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 a752c7686ed..2139e702442 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 @@ -32,9 +32,13 @@
- +
+
+
+
+
This field is required. + Too weak
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/core/auth/user-form/user-form.component.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/core/auth/user-form/user-form.component.spec.ts index 603c1986272..950f8af0a97 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/core/auth/user-form/user-form.component.spec.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/core/auth/user-form/user-form.component.spec.ts @@ -101,6 +101,41 @@ describe('UserFormComponent', () => { formHelper.expectValidChange('confirmpassword', 'aaa'); }); + it('should validate password strength very strong', () => { + formHelper.setValue('password', 'testpassword#!$!@$'); + component.checkPassword('testpassword#!$!@$'); + expect(component.passwordStrengthDescription).toBe('Very strong'); + expect(component.passwordStrengthLevel).toBe('passwordStrengthLevel4'); + }); + + it('should validate password strength strong', () => { + formHelper.setValue('password', 'testpassword0047!@'); + component.checkPassword('testpassword0047!@'); + expect(component.passwordStrengthDescription).toBe('Strong'); + expect(component.passwordStrengthLevel).toBe('passwordStrengthLevel3'); + }); + + it('should validate password strength ok ', () => { + formHelper.setValue('password', 'mypassword1!@'); + component.checkPassword('mypassword1!@'); + expect(component.passwordStrengthDescription).toBe('OK'); + expect(component.passwordStrengthLevel).toBe('passwordStrengthLevel2'); + }); + + it('should validate password strength weak', () => { + formHelper.setValue('password', 'mypassword1'); + component.checkPassword('mypassword1'); + expect(component.passwordStrengthDescription).toBe('Weak'); + expect(component.passwordStrengthLevel).toBe('passwordStrengthLevel1'); + }); + + it('should validate password strength too weak', () => { + formHelper.setValue('password', 'bar0'); + component.checkPassword('bar0'); + expect(component.passwordStrengthDescription).toBe('Too weak'); + expect(component.passwordStrengthLevel).toBe('passwordStrengthLevel0'); + }); + it('should validate email', () => { formHelper.expectErrorChange('email', 'aaa', 'email'); }); diff --git a/src/pybind/mgr/dashboard/frontend/src/app/core/auth/user-form/user-form.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/core/auth/user-form/user-form.component.ts index 0b300684caa..699b1802b0e 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/core/auth/user-form/user-form.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/core/auth/user-form/user-form.component.ts @@ -12,11 +12,13 @@ import { UserService } from '../../../shared/api/user.service'; import { ConfirmationModalComponent } from '../../../shared/components/confirmation-modal/confirmation-modal.component'; import { SelectMessages } from '../../../shared/components/select/select-messages.model'; import { ActionLabelsI18n } from '../../../shared/constants/app.constants'; +import { Icons } from '../../../shared/enum/icons.enum'; import { NotificationType } from '../../../shared/enum/notification-type.enum'; import { CdFormGroup } from '../../../shared/forms/cd-form-group'; import { CdValidators } from '../../../shared/forms/cd-validators'; import { AuthStorageService } from '../../../shared/services/auth-storage.service'; import { NotificationService } from '../../../shared/services/notification.service'; +import { UserChangePasswordService } from '../../../shared/services/user-change-password.service'; import { UserFormMode } from './user-form-mode.enum'; import { UserFormRoleModel } from './user-form-role.model'; import { UserFormModel } from './user-form.model'; @@ -41,6 +43,10 @@ export class UserFormComponent implements OnInit { messages = new SelectMessages({ empty: 'There are no roles.' }, this.i18n); action: string; resource: string; + requiredPasswordRulesMessage: string; + passwordStrengthLevel: string; + passwordStrengthDescription: string; + icons = Icons; constructor( private authService: AuthService, @@ -52,7 +58,8 @@ export class UserFormComponent implements OnInit { private userService: UserService, private notificationService: NotificationService, private i18n: I18n, - public actionLabels: ActionLabelsI18n + public actionLabels: ActionLabelsI18n, + private userChangePasswordService: UserChangePasswordService ) { this.resource = this.i18n('user'); this.createForm(); @@ -60,6 +67,7 @@ export class UserFormComponent implements OnInit { } createForm() { + this.requiredPasswordRulesMessage = this.userChangePasswordService.getPasswordRulesMessage(); this.userForm = new CdFormGroup( { username: new FormControl('', { @@ -67,7 +75,11 @@ export class UserFormComponent implements OnInit { }), name: new FormControl(''), password: new FormControl('', { - validators: [] + validators: [ + CdValidators.custom('checkPassword', () => { + return this.userForm && this.checkPassword(this.userForm.getValue('password')); + }) + ] }), confirmpassword: new FormControl('', { updateOn: 'blur', @@ -172,6 +184,14 @@ export class UserFormComponent implements OnInit { } } + checkPassword(password: string) { + [ + this.passwordStrengthLevel, + this.passwordStrengthDescription + ] = this.userChangePasswordService.checkPasswordComplexity(password); + return password && this.passwordStrengthLevel === 'passwordStrengthLevel0'; + } + public isCurrentUser(): boolean { return this.authStorageService.getUsername() === this.userForm.getValue('username'); } diff --git a/src/pybind/mgr/dashboard/frontend/src/app/core/auth/user-password-form/user-password-form.component.html b/src/pybind/mgr/dashboard/frontend/src/app/core/auth/user-password-form/user-password-form.component.html index c0c134969c4..502ece1f260 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/core/auth/user-password-form/user-password-form.component.html +++ b/src/pybind/mgr/dashboard/frontend/src/app/core/auth/user-password-form/user-password-form.component.html @@ -32,6 +32,9 @@ This field is required. + The old and new passwords must be different. @@ -39,7 +42,10 @@
@@ -51,17 +57,27 @@ autocomplete="new-password" formControlName="newpassword"> -
+
+
+
+
This field is required. The old and new passwords must be different. + Too weak
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/core/auth/user-password-form/user-password-form.component.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/core/auth/user-password-form/user-password-form.component.spec.ts index cfd8e0c7120..057f914be13 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/core/auth/user-password-form/user-password-form.component.spec.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/core/auth/user-password-form/user-password-form.component.spec.ts @@ -65,6 +65,41 @@ describe('UserPasswordFormComponent', () => { formHelper.expectValidChange('confirmnewpassword', 'aaa'); }); + it('should validate password strength very strong', () => { + formHelper.setValue('newpassword', 'testpassword#!$!@$'); + component.checkPassword('testpassword#!$!@$'); + expect(component.passwordStrengthDescription).toBe('Very strong'); + expect(component.passwordStrengthLevel).toBe('passwordStrengthLevel4'); + }); + + it('should validate password strength strong', () => { + formHelper.setValue('newpassword', 'testpassword0047!@'); + component.checkPassword('testpassword0047!@'); + expect(component.passwordStrengthDescription).toBe('Strong'); + expect(component.passwordStrengthLevel).toBe('passwordStrengthLevel3'); + }); + + it('should validate password strength ok ', () => { + formHelper.setValue('newpassword', 'mypassword1!@'); + component.checkPassword('mypassword1!@'); + expect(component.passwordStrengthDescription).toBe('OK'); + expect(component.passwordStrengthLevel).toBe('passwordStrengthLevel2'); + }); + + it('should validate password strength weak', () => { + formHelper.setValue('newpassword', 'mypassword1'); + component.checkPassword('mypassword1'); + expect(component.passwordStrengthDescription).toBe('Weak'); + expect(component.passwordStrengthLevel).toBe('passwordStrengthLevel1'); + }); + + it('should validate password strength too weak', () => { + formHelper.setValue('newpassword', 'abc0'); + component.checkPassword('abc0'); + expect(component.passwordStrengthDescription).toBe('Too weak'); + expect(component.passwordStrengthLevel).toBe('passwordStrengthLevel0'); + }); + it('should submit', () => { spyOn(authStorageService, 'getUsername').and.returnValue('xyz'); formHelper.setMultipleValues({ diff --git a/src/pybind/mgr/dashboard/frontend/src/app/core/auth/user-password-form/user-password-form.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/core/auth/user-password-form/user-password-form.component.ts index e9abf89baf1..53ed7f3e884 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/core/auth/user-password-form/user-password-form.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/core/auth/user-password-form/user-password-form.component.ts @@ -6,12 +6,14 @@ import { I18n } from '@ngx-translate/i18n-polyfill'; import { UserService } from '../../../shared/api/user.service'; import { ActionLabelsI18n } from '../../../shared/constants/app.constants'; +import { Icons } from '../../../shared/enum/icons.enum'; import { NotificationType } from '../../../shared/enum/notification-type.enum'; import { CdFormBuilder } from '../../../shared/forms/cd-form-builder'; import { CdFormGroup } from '../../../shared/forms/cd-form-group'; import { CdValidators } from '../../../shared/forms/cd-validators'; import { AuthStorageService } from '../../../shared/services/auth-storage.service'; import { NotificationService } from '../../../shared/services/notification.service'; +import { UserChangePasswordService } from '../../../shared/services/user-change-password.service'; @Component({ selector: 'cd-user-password-form', @@ -22,6 +24,10 @@ export class UserPasswordFormComponent { userForm: CdFormGroup; action: string; resource: string; + requiredPasswordRulesMessage: string; + passwordStrengthLevel: string; + passwordStrengthDescription: string; + icons = Icons; constructor( private i18n: I18n, @@ -30,7 +36,8 @@ export class UserPasswordFormComponent { private userService: UserService, private authStorageService: AuthStorageService, private formBuilder: CdFormBuilder, - private router: Router + private router: Router, + private userChangePasswordService: UserChangePasswordService ) { this.action = this.actionLabels.CHANGE; this.resource = this.i18n('password'); @@ -38,9 +45,21 @@ export class UserPasswordFormComponent { } createForm() { + this.requiredPasswordRulesMessage = this.userChangePasswordService.getPasswordRulesMessage(); this.userForm = this.formBuilder.group( { - oldpassword: [null, [Validators.required]], + oldpassword: [ + null, + [ + Validators.required, + CdValidators.custom('notmatch', () => { + return ( + this.userForm && + this.userForm.getValue('newpassword') === this.userForm.getValue('oldpassword') + ); + }) + ] + ], newpassword: [ null, [ @@ -50,6 +69,9 @@ export class UserPasswordFormComponent { this.userForm && this.userForm.getValue('oldpassword') === this.userForm.getValue('newpassword') ); + }), + CdValidators.custom('checkPassword', () => { + return this.userForm && this.checkPassword(this.userForm.getValue('newpassword')); }) ] ], @@ -61,6 +83,14 @@ export class UserPasswordFormComponent { ); } + checkPassword(password: string) { + [ + this.passwordStrengthLevel, + this.passwordStrengthDescription + ] = this.userChangePasswordService.checkPasswordComplexity(password); + return password && this.passwordStrengthLevel === 'passwordStrengthLevel0'; + } + onSubmit() { if (this.userForm.pristine) { return; diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/components/helper/helper.component.html b/src/pybind/mgr/dashboard/frontend/src/app/shared/components/helper/helper.component.html index 5d9663b3d8c..1a09436c0de 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/shared/components/helper/helper.component.html +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/components/helper/helper.component.html @@ -1,5 +1,7 @@ -
+
+
?@[\\]^_`{{|}}~\n\ + * any other characters (signs)' + ); + } + + checkPasswordComplexity(password): [string, string] { + this.passwordStrengthLevel = 'passwordStrengthLevel0'; + this.passwordStrengthDescription = ''; + const credits = this.checkPasswordComplexityLetters(password); + if (credits) { + if (password.length < 8 || credits < 10) { + this.passwordStrengthLevel = 'passwordStrengthLevel0'; + this.passwordStrengthDescription = this.i18n('Too weak'); + } else { + if (credits < 15) { + this.passwordStrengthLevel = 'passwordStrengthLevel1'; + this.passwordStrengthDescription = this.i18n('Weak'); + } else { + if (credits < 20) { + this.passwordStrengthLevel = 'passwordStrengthLevel2'; + this.passwordStrengthDescription = this.i18n('OK'); + } else { + if (credits < 25) { + this.passwordStrengthLevel = 'passwordStrengthLevel3'; + this.passwordStrengthDescription = this.i18n('Strong'); + } else { + this.passwordStrengthLevel = 'passwordStrengthLevel4'; + this.passwordStrengthDescription = this.i18n('Very strong'); + } + } + } + } + } + return [this.passwordStrengthLevel, this.passwordStrengthDescription]; + } + + private checkPasswordComplexityLetters(password): number { + if (_.isString(password)) { + const digitsNumber = password.replace(/[^0-9]/g, '').length; + const smallLettersNumber = password.replace(/[^a-z]/g, '').length; + const bigLettersNumber = password.replace(/[^A-Z]/g, '').length; + const punctuationNumber = password.replace(/[^!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~]/g, '').length; + const othersCharactersNumber = + password.length - + (digitsNumber + smallLettersNumber + bigLettersNumber + punctuationNumber); + return ( + digitsNumber + + smallLettersNumber + + bigLettersNumber * 2 + + punctuationNumber * 3 + + othersCharactersNumber * 5 + ); + } else { + return 0; + } + } +} diff --git a/src/pybind/mgr/dashboard/frontend/src/styles.scss b/src/pybind/mgr/dashboard/frontend/src/styles.scss index 70ceed0212f..79785c53062 100644 --- a/src/pybind/mgr/dashboard/frontend/src/styles.scss +++ b/src/pybind/mgr/dashboard/frontend/src/styles.scss @@ -110,6 +110,9 @@ option { .text-monospace { font-family: monospace; } +.text-pre { + white-space: pre; +} /* Buttons */ .btn-light { @@ -397,3 +400,33 @@ bfv-messages { .form-group.has-error .invalid-feedback { display: block; } + +//Displaying the password strength +.passwordStrengthLevel { + flex: 100%; + margin-top: 2px; + .passwordStrengthLevel1, + .passwordStrengthLevel2, + .passwordStrengthLevel3, + .passwordStrengthLevel4 { + border-radius: 0.25rem; + height: 13px; + } + + .passwordStrengthLevel1 { + width: 25%; + background: $color-solid-red; + } + .passwordStrengthLevel2 { + width: 50%; + background: $color-bright-yellow; + } + .passwordStrengthLevel3 { + width: 75%; + background: $color-bright-green; + } + .passwordStrengthLevel4 { + width: 100%; + background: $color-green; + } +} diff --git a/src/pybind/mgr/dashboard/frontend/src/styles/defaults.scss b/src/pybind/mgr/dashboard/frontend/src/styles/defaults.scss index 288730d040a..7b323683474 100644 --- a/src/pybind/mgr/dashboard/frontend/src/styles/defaults.scss +++ b/src/pybind/mgr/dashboard/frontend/src/styles/defaults.scss @@ -22,7 +22,7 @@ $color-bright-yellow: #ffc200 !default; $color-light-yellow: #fff3cd !default; $color-bright-green: #00bb00 !default; -// $color-green: #71843f !default; +$color-green: #245e03 !default; $color-blue: #288cea !default; $color-light-blue: #d1ecf1 !default; diff --git a/src/pybind/mgr/dashboard/services/access_control.py b/src/pybind/mgr/dashboard/services/access_control.py index a4032f8dc47..b00aeef3e39 100644 --- a/src/pybind/mgr/dashboard/services/access_control.py +++ b/src/pybind/mgr/dashboard/services/access_control.py @@ -3,10 +3,13 @@ # pylint: disable=too-many-branches, too-many-locals, too-many-statements from __future__ import absolute_import +from string import punctuation, ascii_lowercase, digits, ascii_uppercase + import errno import json import threading import time +import re import bcrypt @@ -34,6 +37,64 @@ def password_hash(password, salt_password=None): _P = Permission # short alias +class PasswordCheck(object): + def __init__(self, password, username, old_password=None): + self.password = password + self.username = username + self.old_password = old_password + self.forbidden_words = ['osd', 'host', 'dashboard', 'pool', + 'block', 'nfs', 'ceph', 'monitors', + 'gateway', 'logs', 'crush', 'maps'] + self.complexity_credits = 0 + + @staticmethod + def _check_if_contains_word(password, word): + return re.compile('(?:{0})'.format(word), + flags=re.IGNORECASE).search(password) + + def check_password_characters(self): + digit_credit = 1 + small_letter_credit = 1 + big_letter_credit = 2 + special_character_credit = 3 + other_character_credit = 5 + for _ in self.password: + if _ in ascii_uppercase: + self.complexity_credits += big_letter_credit + elif _ in ascii_lowercase: + self.complexity_credits += small_letter_credit + elif _ in digits: + self.complexity_credits += digit_credit + elif _ in punctuation: + self.complexity_credits += special_character_credit + else: + self.complexity_credits += other_character_credit + return self.complexity_credits + + def check_if_as_the_old_password(self): + return self.old_password and self.password == self.old_password + + def check_if_contains_username(self): + return self._check_if_contains_word(self.password, self.username) + + def check_if_contains_forbidden_words(self): + return self._check_if_contains_word(self.password, + '|'.join(self.forbidden_words)) + + def check_if_sequential_characters(self): + for _ in range(1, len(self.password)-1): + if ord(self.password[_-1])+1 == ord(self.password[_])\ + == ord(self.password[_+1])-1: + return True + return False + + def check_if_repetetive_characters(self): + for _ in range(1, len(self.password)-1): + if self.password[_-1] == self.password[_] == self.password[_+1]: + return True + return False + + class Role(object): def __init__(self, name, description=None, scope_permissions=None): self.name = name