Add in proper passord check lib, show results in form

This commit is contained in:
2025-07-20 15:46:25 +01:00
parent 0127326b77
commit 2dae8b0ece
10 changed files with 1023 additions and 623 deletions

View File

@@ -1,9 +1,14 @@
#!/usr/bin/env python3
"""
Enhanced Demo mode for SME Server Password Change Application - Python 3.6.8 Compatible
Demo mode for Corrected SME Server Password Change Application - Python 3.6.8 Compatible
This version simulates SME Server functionality with enhanced password validation
and configurable strength levels for testing purposes.
This version simulates the correct SME Server database structure:
passwordstrength=configuration
Admin=strong
Ibays=strong
Users=strong
Features zxcvbn external password validation library.
Compatible with Python 3.6.8 and Flask 2.0.3
"""
@@ -13,7 +18,7 @@ from flask import Flask, render_template, request, flash, redirect, url_for, jso
from flask_cors import CORS
app = Flask(__name__)
app.secret_key = os.environ.get('SECRET_KEY', 'sme-server-password-change-enhanced-demo-key')
app.secret_key = os.environ.get('SECRET_KEY', 'sme-server-password-change-corrected-demo-key')
CORS(app)
# Demo users for testing
@@ -23,53 +28,119 @@ DEMO_USERS = {
'john': 'johnpass789'
}
# Demo password strength setting (can be changed via admin panel)
DEMO_PASSWORD_STRENGTH = 'normal'
# Demo password strength settings (correct SME Server structure)
DEMO_PASSWORD_STRENGTH = {
'Users': 'strong',
'Admin': 'strong',
'Ibays': 'strong'
}
class DemoPasswordStrengthValidator:
"""Demo password strength validator with configurable levels"""
class DemoPasswordManager:
"""Demo password manager with corrected DB structure and zxcvbn"""
def __init__(self):
# Common weak passwords for strong validation
self.common_passwords = {
'password', 'password123', '123456', '123456789', 'qwerty', 'abc123',
'password1', 'admin', 'administrator', 'root', 'user', 'guest'
}
# Try to import zxcvbn
self.external_validator = None
try:
import zxcvbn
self.external_validator = zxcvbn
print("✓ Using zxcvbn library for password validation")
except ImportError:
print("⚠ zxcvbn library not available, using basic validation")
def validate_password_strength(self, password, strength_level='normal'):
"""Validate password based on configured strength level"""
def validate_username(self, username):
"""Validate username in demo mode"""
if not username:
return False, "Username cannot be empty"
if username not in DEMO_USERS:
return False, "User account does not exist"
return True, "Username is valid"
def validate_password_strength(self, password, username=None):
"""Validate password strength using zxcvbn or fallback"""
global DEMO_PASSWORD_STRENGTH
strength_level = DEMO_PASSWORD_STRENGTH.get('Users', 'normal')
if self.external_validator:
return self._validate_with_zxcvbn(password, strength_level, username)
else:
return self._validate_basic(password, strength_level)
def _validate_with_zxcvbn(self, password, strength_level, username=None):
"""Validate password using zxcvbn library"""
errors = []
if strength_level == 'none':
if len(password) < 1:
errors.append("Password cannot be empty")
return errors
# Basic length and complexity checks first
if len(password) < 12:
errors.append("Password must be at least 12 characters long")
if len(password) > 127:
errors.append("Password must be no more than 127 characters long")
# Character type requirements for normal and strong
if strength_level in ['normal', 'strong']:
has_upper = any(c.isupper() for c in password)
has_lower = any(c.islower() for c in password)
has_numeric = any(c.isdigit() for c in password)
has_non_alpha = any(not c.isalnum() for c in password)
missing_types = []
if not has_upper:
missing_types.append("uppercase letter")
if not has_lower:
missing_types.append("lowercase letter")
if not has_numeric:
missing_types.append("number")
if not has_non_alpha:
missing_types.append("special character")
if missing_types:
errors.append("Password must contain at least one: {}".format(", ".join(missing_types)))
# Use zxcvbn for advanced validation on strong passwords
if strength_level == 'strong' and not errors:
user_inputs = [username] if username else []
result = self.external_validator.zxcvbn(password, user_inputs)
# zxcvbn scores: 0-4 (0 = very weak, 4 = very strong)
if result['score'] < 3: # Require score of 3 or higher for strong
feedback = result.get('feedback', {})
warning = feedback.get('warning', '')
suggestions = feedback.get('suggestions', [])
if warning:
errors.append("Password weakness: {}".format(warning))
for suggestion in suggestions:
errors.append("Suggestion: {}".format(suggestion))
if not warning and not suggestions:
errors.append("Password is not strong enough (zxcvbn score: {}/4)".format(result['score']))
return errors
def _validate_basic(self, password, strength_level):
"""Basic validation fallback"""
errors = []
if strength_level == 'none':
# No validation - only basic length check
if len(password) < 1:
errors.append("Password cannot be empty")
return errors
elif strength_level == 'normal':
# Normal validation: 12+ chars, complexity requirements
errors.extend(self._validate_normal_strength(password))
elif strength_level == 'strong':
# Strong validation: Normal + crypto testing
errors.extend(self._validate_normal_strength(password))
errors.extend(self._validate_strong_crypto(password))
return errors
def _validate_normal_strength(self, password):
"""Validate normal strength requirements"""
errors = []
# Minimum 12 characters
if len(password) < 12:
errors.append("Password must be at least 12 characters long")
# Maximum length check
if len(password) > 127:
errors.append("Password must be no more than 127 characters long")
# Character type requirements
has_upper = any(c.isupper() for c in password)
has_lower = any(c.islower() for c in password)
has_numeric = any(c.isdigit() for c in password)
@@ -88,72 +159,30 @@ class DemoPasswordStrengthValidator:
if missing_types:
errors.append("Password must contain at least one: {}".format(", ".join(missing_types)))
return errors
def _validate_strong_crypto(self, password):
"""Validate strong crypto requirements"""
errors = []
# Check against common passwords
if password.lower() in self.common_passwords:
errors.append("Password is too common and easily guessable")
# Check for repeated sequences
if self._has_repeated_sequences(password):
errors.append("Password contains repeated sequences that reduce security")
# Check for keyboard patterns
keyboard_patterns = ['qwerty', 'asdfgh', 'zxcvbn', '123456']
password_lower = password.lower()
for pattern in keyboard_patterns:
if pattern in password_lower:
errors.append("Password contains keyboard patterns that are easily guessable")
break
# Basic strong validation
if strength_level == 'strong':
common_passwords = {'password', 'password123', '123456', 'qwerty', 'admin'}
if password.lower() in common_passwords:
errors.append("Password is too common and easily guessable")
return errors
def _has_repeated_sequences(self, password):
"""Check for repeated character sequences"""
for i in range(len(password) - 2):
sequence = password[i:i+3]
if password.count(sequence) > 1:
return True
return False
class DemoPasswordManager:
"""Demo password manager with enhanced validation"""
def __init__(self):
self.strength_validator = DemoPasswordStrengthValidator()
def validate_username(self, username):
"""Validate username in demo mode"""
if not username:
return False, "Username cannot be empty"
if username not in DEMO_USERS:
return False, "User account does not exist"
return True, "Username is valid"
def validate_password_strength(self, password):
"""Validate password strength using current setting"""
global DEMO_PASSWORD_STRENGTH
return self.strength_validator.validate_password_strength(password, DEMO_PASSWORD_STRENGTH)
def get_password_strength_info(self):
def get_password_strength_info(self, account_type='Users'):
"""Get current password strength setting and requirements"""
global DEMO_PASSWORD_STRENGTH
strength_level = DEMO_PASSWORD_STRENGTH.get(account_type, 'normal')
descriptions = {
'none': 'No specific password requirements',
'normal': 'Minimum 12 characters with uppercase, lowercase, number, and special character',
'strong': 'Normal requirements plus protection against common passwords and patterns'
'strong': 'Normal requirements plus zxcvbn advanced validation' if self.external_validator else 'Normal requirements plus basic pattern protection'
}
return {
'level': DEMO_PASSWORD_STRENGTH,
'description': descriptions.get(DEMO_PASSWORD_STRENGTH, 'Unknown strength level')
'level': strength_level,
'description': descriptions.get(strength_level, 'Unknown strength level'),
'account_type': account_type,
'using_zxcvbn': self.external_validator is not None
}
def verify_current_password(self, username, password):
@@ -165,25 +194,28 @@ class DemoPasswordManager:
try:
# Simulate password change
DEMO_USERS[username] = new_password
return True, "Password changed successfully (demo mode)"
return True, "Password changed successfully (demo mode with zxcvbn validation)"
except Exception as e:
return False, "Error changing password: {}".format(str(e))
class DemoConfigDB:
"""Demo configuration database"""
"""Demo configuration database with correct structure"""
def get_password_strength_setting(self):
"""Get password strength setting"""
def get_all_password_strength_settings(self):
"""Get all password strength settings"""
global DEMO_PASSWORD_STRENGTH
return DEMO_PASSWORD_STRENGTH
return DEMO_PASSWORD_STRENGTH.copy()
def set_password_strength_setting(self, strength):
def set_password_strength_setting(self, strength, account_type='Users'):
"""Set password strength setting"""
global DEMO_PASSWORD_STRENGTH
if strength.lower() not in ['none', 'normal', 'strong']:
return False, "Invalid strength level"
DEMO_PASSWORD_STRENGTH = strength.lower()
if account_type not in ['Users', 'Admin', 'Ibays']:
return False, "Invalid account type"
DEMO_PASSWORD_STRENGTH[account_type] = strength.lower()
return True, "Password strength setting updated"
class DemoSystemInfo:
@@ -191,7 +223,7 @@ class DemoSystemInfo:
@staticmethod
def get_version():
return "SME Server 11 (beta1) - Enhanced Demo Mode"
return "SME Server 11 (beta1) - Corrected Demo Mode with zxcvbn"
@staticmethod
def get_copyright_info():
@@ -208,10 +240,10 @@ config_db = DemoConfigDB()
@app.route('/', methods=['GET', 'POST'])
def password_change():
"""Main password change form handler with enhanced validation"""
"""Main password change form handler with corrected validation"""
# Get current password requirements for display
password_requirements = password_manager.get_password_strength_info()
password_requirements = password_manager.get_password_strength_info('Users')
if request.method == 'POST':
# Get form data
@@ -249,9 +281,9 @@ def password_change():
if not password_manager.verify_current_password(username, old_password):
errors.append("Current password is incorrect")
# Enhanced password strength validation
# Enhanced password strength validation with zxcvbn
if new_password:
strength_errors = password_manager.validate_password_strength(new_password)
strength_errors = password_manager.validate_password_strength(new_password, username)
errors.extend(strength_errors)
if errors:
@@ -278,39 +310,59 @@ def password_change():
@app.route('/api/password-strength', methods=['POST'])
def check_password_strength():
"""API endpoint for real-time password strength checking"""
"""API endpoint for real-time password strength checking with zxcvbn"""
try:
data = request.get_json()
password = data.get('password', '')
username = data.get('username', '')
if not password:
return jsonify({'errors': ['Password cannot be empty']})
# Get validation errors
errors = password_manager.validate_password_strength(password)
# Get validation errors using zxcvbn
errors = password_manager.validate_password_strength(password, username)
# Calculate strength score
strength_score = 0
# Get zxcvbn score if available
zxcvbn_score = None
zxcvbn_feedback = None
if password_manager.external_validator:
try:
user_inputs = [username] if username else []
result = password_manager.external_validator.zxcvbn(password, user_inputs)
zxcvbn_score = result['score']
zxcvbn_feedback = result.get('feedback', {})
except Exception:
pass
# Calculate basic strength score for fallback
basic_score = 0
if len(password) >= 12:
strength_score += 1
basic_score += 1
if any(c.isupper() for c in password):
strength_score += 1
basic_score += 1
if any(c.islower() for c in password):
strength_score += 1
basic_score += 1
if any(c.isdigit() for c in password):
strength_score += 1
basic_score += 1
if any(not c.isalnum() for c in password):
strength_score += 1
basic_score += 1
strength_levels = ['Very Weak', 'Weak', 'Fair', 'Good', 'Strong']
strength_level = strength_levels[min(strength_score, len(strength_levels) - 1)]
# Use zxcvbn score if available, otherwise use basic score
display_score = zxcvbn_score if zxcvbn_score is not None else basic_score
max_score = 4 if zxcvbn_score is not None else 5
strength_level = strength_levels[min(display_score, len(strength_levels) - 1)]
return jsonify({
'errors': errors,
'strength_score': strength_score,
'max_score': 5,
'strength_score': display_score,
'max_score': max_score,
'strength_level': strength_level,
'is_valid': len(errors) == 0
'is_valid': len(errors) == 0,
'using_zxcvbn': password_manager.external_validator is not None,
'zxcvbn_feedback': zxcvbn_feedback
})
except Exception as e:
@@ -320,19 +372,18 @@ def check_password_strength():
def password_config():
"""API endpoint for managing password strength configuration"""
if request.method == 'GET':
# Get current configuration
# Get current configuration for all account types
try:
strength_setting = config_db.get_password_strength_setting()
requirements = password_manager.get_password_strength_info()
all_settings = config_db.get_all_password_strength_settings()
return jsonify({
'current_setting': strength_setting,
'requirements': requirements,
'current_settings': all_settings,
'available_levels': {
'none': 'No specific password requirements',
'normal': 'Minimum 12 characters with complexity requirements',
'strong': 'Normal requirements plus protection against common passwords'
}
'strong': 'Normal requirements plus zxcvbn advanced validation' if password_manager.external_validator else 'Normal requirements plus basic pattern protection'
},
'using_zxcvbn': password_manager.external_validator is not None
})
except Exception as e:
return jsonify({'error': 'Failed to get password configuration'}), 500
@@ -342,14 +393,16 @@ def password_config():
try:
data = request.get_json()
new_strength = data.get('strength', '').lower()
account_type = data.get('account_type', 'Users')
success, message = config_db.set_password_strength_setting(new_strength)
success, message = config_db.set_password_strength_setting(new_strength, account_type)
if success:
return jsonify({
'success': True,
'message': 'Password strength setting updated to: {}'.format(new_strength),
'new_setting': new_strength
'message': 'Password strength setting updated for {}: {}'.format(account_type, new_strength),
'new_setting': new_strength,
'account_type': account_type
})
else:
return jsonify({'error': message}), 500
@@ -359,15 +412,14 @@ def password_config():
@app.route('/admin')
def admin_panel():
"""Simple admin panel for password strength configuration"""
"""Admin panel for password strength configuration"""
try:
current_setting = config_db.get_password_strength_setting()
requirements = password_manager.get_password_strength_info()
all_settings = config_db.get_all_password_strength_settings()
return render_template('admin_panel.html',
current_setting=current_setting,
requirements=requirements,
version=system_info.get_version())
current_settings=all_settings,
version=system_info.get_version(),
using_zxcvbn=password_manager.external_validator is not None)
except Exception as e:
flash('Error loading admin panel: {}'.format(str(e)), 'error')
return redirect(url_for('password_change'))
@@ -377,10 +429,11 @@ def demo_info():
"""Demo information page"""
global DEMO_PASSWORD_STRENGTH
return jsonify({
'mode': 'enhanced_demo',
'mode': 'corrected_demo_with_zxcvbn',
'demo_users': list(DEMO_USERS.keys()),
'current_password_strength': DEMO_PASSWORD_STRENGTH,
'features': ['configurable_strength', 'password_visibility', 'crypto_validation'],
'features': ['corrected_db_structure', 'zxcvbn_validation', 'password_visibility', 'admin_panel'],
'using_zxcvbn': password_manager.external_validator is not None,
'instructions': 'Use any of the demo usernames with their corresponding passwords to test the application'
})
@@ -389,28 +442,34 @@ def health_check():
"""Health check endpoint"""
return jsonify({
'status': 'healthy',
'service': 'sme-server-password-change-enhanced-demo',
'password_strength_setting': DEMO_PASSWORD_STRENGTH,
'features': ['configurable_strength', 'password_visibility', 'crypto_validation']
'service': 'sme-server-password-change-corrected-demo',
'password_strength_settings': DEMO_PASSWORD_STRENGTH,
'features': ['corrected_db_structure', 'zxcvbn_validation', 'password_visibility', 'admin_panel'],
'using_zxcvbn': password_manager.external_validator is not None
})
if __name__ == '__main__':
print("Starting Enhanced SME Server Password Change Application in Demo Mode")
print("Starting Corrected SME Server Password Change Application in Demo Mode")
print("Features:")
print(" - Configurable password strength validation")
print(" - Correct SME Server database structure (Users/Admin/Ibays)")
print(" - External zxcvbn password validation library")
print(" - Password visibility toggles")
print(" - Real-time strength checking")
print(" - Crypto validation for strong passwords")
print(" - Admin configuration panel")
print("")
print("Demo users available:")
for user, password in DEMO_USERS.items():
print(" Username: {}, Password: {}".format(user, password))
print("")
print("Current password strength setting: {}".format(DEMO_PASSWORD_STRENGTH.upper()))
print("Current password strength settings:")
for account_type, strength in DEMO_PASSWORD_STRENGTH.items():
print(" {}: {}".format(account_type, strength.upper()))
print("")
print("Access the application at: http://localhost:5002")
print("Admin panel at: http://localhost:5002/admin")
print("Demo info at: http://localhost:5002/demo-info")
print("Using zxcvbn library: {}".format("Yes" if password_manager.external_validator else "No"))
print("")
print("Access the application at: http://localhost:5003")
print("Admin panel at: http://localhost:5003/admin")
print("Demo info at: http://localhost:5003/demo-info")
app.run(host='0.0.0.0', port=5002, debug=False)
app.run(host='0.0.0.0', port=5003, debug=False)