diff --git a/powerdnsadmin/models/setting.py b/powerdnsadmin/models/setting.py index 022b57c..fa6a2de 100644 --- a/powerdnsadmin/models/setting.py +++ b/powerdnsadmin/models/setting.py @@ -195,7 +195,9 @@ class Setting(db.Model): 'pwd_min_digits' : 2, 'pwd_min_special' : 1, 'pwd_must_not_contain' : 'username,firstname', - 'max_history_records': 1000 + 'max_history_records': 1000, + 'zxcvbn_enabled': True, + 'zxcvbn_guesses_log' : 11 } def __init__(self, id=None, name=None, value=None): diff --git a/powerdnsadmin/routes/admin.py b/powerdnsadmin/routes/admin.py index 352b8d0..fd9f8ff 100644 --- a/powerdnsadmin/routes/admin.py +++ b/powerdnsadmin/routes/admin.py @@ -1356,13 +1356,17 @@ def setting_authentication(): 'local_db_enabled') else False signup_enabled = True if request.form.get( 'signup_enabled', ) else False - min_len = int(request.form.get('min_len')) - min_lowercase = int(request.form.get('min_lowercase')) - min_uppercase = int(request.form.get('min_uppercase')) - min_digits = int(request.form.get('min_digits')) - min_special = int(request.form.get('min_special')) - must_not_contain = request.form.get('must_not_contain') - + password_package_enabled = request.form.get('zxcvbn') + if password_package_enabled is None: + min_len = int(request.form.get('min_len')) + min_lowercase = int(request.form.get('min_lowercase')) + min_uppercase = int(request.form.get('min_uppercase')) + min_digits = int(request.form.get('min_digits')) + min_special = int(request.form.get('min_special')) + must_not_contain = request.form.get('must_not_contain') + else: + Setting().set('zxcvbn_enabled', True) + if not has_an_auth_method(local_db_enabled=local_db_enabled): result = { 'status': @@ -1371,15 +1375,19 @@ def setting_authentication(): 'Must have at least one authentication method enabled.' } else: - Setting().set('local_db_enabled', local_db_enabled) - Setting().set('signup_enabled', signup_enabled) - Setting().set('pwd_min_len', min_len) - Setting().set('pwd_min_lowercase', min_lowercase) - Setting().set('pwd_min_uppercase', min_uppercase) - Setting().set('pwd_min_digits', min_digits) - Setting().set('pwd_min_special', min_special) - Setting().set('pwd_must_not_contain', must_not_contain) - + if password_package_enabled is None: + Setting().set('local_db_enabled', local_db_enabled) + Setting().set('signup_enabled', signup_enabled) + Setting().set('pwd_min_len', min_len) + Setting().set('pwd_min_lowercase', min_lowercase) + Setting().set('pwd_min_uppercase', min_uppercase) + Setting().set('pwd_min_digits', min_digits) + Setting().set('pwd_min_special', min_special) + Setting().set('pwd_must_not_contain', must_not_contain) + Setting().set('zxcvbn_enabled', False) + else: + Setting().set('zxcvbn_enabled', True) + result = {'status': True, 'msg': 'Saved successfully'} elif conf_type == 'ldap': ldap_enabled = True if request.form.get('ldap_enabled') else False diff --git a/powerdnsadmin/routes/index.py b/powerdnsadmin/routes/index.py index f27a5a5..6ff8ffd 100644 --- a/powerdnsadmin/routes/index.py +++ b/powerdnsadmin/routes/index.py @@ -7,8 +7,9 @@ import ipaddress from distutils.util import strtobool from yaml import Loader, load from onelogin.saml2.utils import OneLogin_Saml2_Utils -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, jsonify from flask_login import login_user, logout_user, login_required, current_user +from zxcvbn import zxcvbn from .base import login_manager from ..lib import utils @@ -676,14 +677,63 @@ def password_quality_check(user, password): return True -@index_bp.route('/ratepass', methods=['POST']) +@index_bp.route('/ratepassword', methods=['POST']) def rate_password(): + # print("\n\nGot pass = ", passwd) + # result = zxcvbn(pwd, user_inputs=[wordlist]) + fname = request.form['fname'] + lname = request.form['lname'] + email = request.form['email'] + username = request.form['username'] + password = request.form['password'] + inputs = [] + for i in [fname, lname, email, username]: + if len(i) != 0: + inputs.append(i) + if len(password) == 0: + return make_response( + jsonify({ + 'msg' : 'no-passwd', + 'feedback': '', + 'valid' : 'false', + 'strength': '' + }), 200) + + result = zxcvbn(password, user_inputs=inputs) + defined_guesses_log = 11 + # attrubutes to return as json + feedback = [] + rate = result['guesses_log10']/defined_guesses_log + if rate < 0.5: + strength = "very weak" + if rate < 0.6: + strength = "weak" + elif rate < 1: + strength = "medium" + else: + strength = "strong" + + if result['guesses_log10'] < defined_guesses_log: + feedback.append("Add more complexity to your password") + for s in result['sequence']: + if s['pattern'] == 'dictionary': + if s['dictionary_name'] == 'user_inputs': + feedback.append("Your password must not contain parts of your firstname, lastname, username or email") + break + for s in result['sequence']: + if s['pattern'] == 'dictionary' and s['dictionary_name'] != 'user_inputs': + feedback.append("Your password contains one or more words which exist in common wordlists.") + break + # in case complexity is high but feedback is still given, then downgrade to 'medium' + if strength == "strong" and (len(feedback) != 0 or result['score'] < 4): + strength = "medium" + return make_response( + jsonify({ + 'feedback': feedback, + 'strength': strength, + 'valid' : 'true' if strength == 'strong' and len(feedback) == 0 else 'false' + }), 200) - username = request.form.get('username') - fname = request.form.get('fname') - lname = request.form.get('name') - email = request.form.get('email') - @index_bp.route('/register', methods=['GET', 'POST']) def register(): diff --git a/powerdnsadmin/templates/admin_setting_authentication.html b/powerdnsadmin/templates/admin_setting_authentication.html index 405a535..c898c82 100644 --- a/powerdnsadmin/templates/admin_setting_authentication.html +++ b/powerdnsadmin/templates/admin_setting_authentication.html @@ -27,8 +27,9 @@ } window.onload = function() { - ldapSelection(); + ldapSelection(); } + {% endblock %} @@ -76,27 +77,39 @@ Password Policy
(database authentication)
- +
- +
- +
- +
- +
- + +
+
+
@@ -745,7 +758,21 @@ {%- endassets %} {% endblock %} diff --git a/requirements.txt b/requirements.txt index 5e97a12..bb36841 100644 --- a/requirements.txt +++ b/requirements.txt @@ -28,3 +28,4 @@ PyYAML==5.4 Flask-SSLify==0.1.5 Flask-Mail==0.9.1 flask-session==0.3.2 +zxcvbn==4.4.28 \ No newline at end of file