feat(authentication): added password policy checker function

This commit is contained in:
Nigel Kukard 2023-03-17 03:44:08 +00:00
parent bb6d2d0497
commit 1cea4b7ce3
2 changed files with 89 additions and 0 deletions

View File

@ -5,6 +5,8 @@ import traceback
import datetime import datetime
import ipaddress import ipaddress
import base64 import base64
import string
from zxcvbn import zxcvbn
from distutils.util import strtobool from distutils.util import strtobool
from yaml import Loader, load from yaml import Loader, load
from flask import Blueprint, render_template, make_response, url_for, current_app, g, session, request, redirect, abort from flask import Blueprint, render_template, make_response, url_for, current_app, g, session, request, redirect, abort
@ -649,6 +651,92 @@ def logout():
return redirect(redirect_uri) return redirect(redirect_uri)
def password_policy_check(user, password):
def check_policy(chars, user_password, setting):
lenreq = int(Setting().get(setting))
test_string = user_password
for c in chars:
test_string = test_string.replace(c, '')
return (lenreq, len(user_password) - len(test_string))
def matches_policy(item, policy_fails):
return "*" if item in policy_fails else ""
policy = []
policy_fails = {}
# If either policy is enabled check basics first ... this is obvious!
if Setting().get('pwd_enforce_characters') or Setting().get('pwd_enforce_complexity'):
# Cannot contain username
if user.username in password:
policy_fails["username"] = True
policy.append(f"{matches_policy('username', policy_fails)}cannot contain username")
# Cannot contain password
if user.firstname in password:
policy_fails["firstname"] = True
policy.append(f"{matches_policy('firstname', policy_fails)}cannot contain firstname")
# Cannot contain lastname
if user.lastname in password:
policy_fails["lastname"] = True
policy.append(f"{matches_policy('lastname', policy_fails)}cannot contain lastname")
# Cannot contain email
if user.email in password:
policy_fails["email"] = True
policy.append(f"{matches_policy('email', policy_fails)}cannot contain email")
# Check if we're enforcing character requirements
if Setting().get('pwd_enforce_characters'):
# Length
pwd_min_len_setting = int(Setting().get('pwd_min_len'))
pwd_len = len(password)
if pwd_len < pwd_min_len_setting:
policy_fails["length"] = True
policy.append(f"{matches_policy('length', policy_fails)}length={pwd_len}/{pwd_min_len_setting}")
# Digits
(pwd_min_digits_setting, pwd_digits) = check_policy(string.digits, password, 'pwd_min_digits')
if pwd_digits < pwd_min_digits_setting:
policy_fails["digits"] = True
policy.append(f"{matches_policy('digits', policy_fails)}digits={pwd_digits}/{pwd_min_digits_setting}")
# Lowercase
(pwd_min_lowercase_setting, pwd_lowercase) = check_policy(string.digits, password, 'pwd_min_lowercase')
if pwd_lowercase < pwd_min_lowercase_setting:
policy_fails["lowercase"] = True
policy.append(f"{matches_policy('lowercase', policy_fails)}lowercase={pwd_lowercase}/{pwd_min_lowercase_setting}")
# Uppercase
(pwd_min_uppercase_setting, pwd_uppercase) = check_policy(string.digits, password, 'pwd_min_uppercase')
if pwd_uppercase < pwd_min_uppercase_setting:
policy_fails["uppercase"] = True
policy.append(f"{matches_policy('uppercase', policy_fails)}uppercase={pwd_uppercase}/{pwd_min_uppercase_setting}")
# Special
(pwd_min_special_setting, pwd_special) = check_policy(string.digits, password, 'pwd_min_special')
if pwd_special < pwd_min_special_setting:
policy_fails["special"] = True
policy.append(f"{matches_policy('special', policy_fails)}special={pwd_special}/{pwd_min_special_setting}")
if Setting().get('pwd_enforce_complexity'):
# Complexity checking
zxcvbn_inputs = []
for input in (user.firstname, user.lastname, user.username, user.email):
if len(input):
zxcvbn_inputs.append(input)
result = zxcvbn(password, user_inputs=zxcvbn_inputs)
pwd_min_complexity_setting = int(Setting().get('pwd_min_complexity'))
pwd_complexity = result['guesses_log10']
if pwd_complexity < pwd_min_complexity_setting:
policy_fails["complexity"] = True
policy.append(f"{matches_policy('complexity', policy_fails)}complexity={pwd_complexity:.0f}/{pwd_min_complexity_setting}")
policy_str = {"password": f"Fails policy: {', '.join(policy)}. Items prefixed with '*' failed."}
# NK: the first item in the tuple indicates a PASS, so, we check for any True's and negate that
return (not any(policy_fails.values()), policy_str)
@index_bp.route('/register', methods=['GET', 'POST']) @index_bp.route('/register', methods=['GET', 'POST'])
def register(): def register():
CAPTCHA_ENABLE = current_app.config.get('CAPTCHA_ENABLE') CAPTCHA_ENABLE = current_app.config.get('CAPTCHA_ENABLE')

View File

@ -43,3 +43,4 @@ webcolors==1.12
werkzeug==2.1.2 werkzeug==2.1.2
zipp==3.11.0 zipp==3.11.0
rcssmin==1.1.1 rcssmin==1.1.1
zxcvbn==4.4.28