2025-07-16 14:13:26 +01:00
|
|
|
#!/usr/bin/env python3
|
|
|
|
"""
|
2025-07-20 10:13:38 +01:00
|
|
|
Enhanced Demo mode for SME Server Password Change Application - Python 3.6.8 Compatible
|
2025-07-16 14:13:26 +01:00
|
|
|
|
2025-07-20 10:13:38 +01:00
|
|
|
This version simulates SME Server functionality with enhanced password validation
|
|
|
|
and configurable strength levels for testing purposes.
|
2025-07-16 14:13:26 +01:00
|
|
|
|
|
|
|
Compatible with Python 3.6.8 and Flask 2.0.3
|
|
|
|
"""
|
|
|
|
|
|
|
|
import os
|
2025-07-20 10:13:38 +01:00
|
|
|
from flask import Flask, render_template, request, flash, redirect, url_for, jsonify
|
2025-07-16 14:13:26 +01:00
|
|
|
from flask_cors import CORS
|
|
|
|
|
|
|
|
app = Flask(__name__)
|
2025-07-20 10:13:38 +01:00
|
|
|
app.secret_key = os.environ.get('SECRET_KEY', 'sme-server-password-change-enhanced-demo-key')
|
2025-07-16 14:13:26 +01:00
|
|
|
CORS(app)
|
|
|
|
|
|
|
|
# Demo users for testing
|
|
|
|
DEMO_USERS = {
|
|
|
|
'testuser': 'oldpassword123',
|
|
|
|
'admin': 'adminpass456',
|
|
|
|
'john': 'johnpass789'
|
|
|
|
}
|
|
|
|
|
2025-07-20 10:13:38 +01:00
|
|
|
# Demo password strength setting (can be changed via admin panel)
|
|
|
|
DEMO_PASSWORD_STRENGTH = 'normal'
|
|
|
|
|
|
|
|
class DemoPasswordStrengthValidator:
|
|
|
|
"""Demo password strength validator with configurable levels"""
|
2025-07-16 14:13:26 +01:00
|
|
|
|
2025-07-20 10:13:38 +01:00
|
|
|
def __init__(self):
|
|
|
|
# Common weak passwords for strong validation
|
|
|
|
self.common_passwords = {
|
|
|
|
'password', 'password123', '123456', '123456789', 'qwerty', 'abc123',
|
|
|
|
'password1', 'admin', 'administrator', 'root', 'user', 'guest'
|
|
|
|
}
|
|
|
|
|
|
|
|
def validate_password_strength(self, password, strength_level='normal'):
|
|
|
|
"""Validate password based on configured strength level"""
|
|
|
|
errors = []
|
2025-07-16 14:13:26 +01:00
|
|
|
|
2025-07-20 10:13:38 +01:00
|
|
|
if strength_level == 'none':
|
|
|
|
# No validation - only basic length check
|
|
|
|
if len(password) < 1:
|
|
|
|
errors.append("Password cannot be empty")
|
|
|
|
return errors
|
2025-07-16 14:13:26 +01:00
|
|
|
|
2025-07-20 10:13:38 +01:00
|
|
|
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
|
2025-07-16 14:13:26 +01:00
|
|
|
|
2025-07-20 10:13:38 +01:00
|
|
|
def _validate_normal_strength(self, password):
|
|
|
|
"""Validate normal strength requirements"""
|
2025-07-16 14:13:26 +01:00
|
|
|
errors = []
|
|
|
|
|
2025-07-20 10:13:38 +01:00
|
|
|
# Minimum 12 characters
|
|
|
|
if len(password) < 12:
|
|
|
|
errors.append("Password must be at least 12 characters long")
|
2025-07-16 14:13:26 +01:00
|
|
|
|
2025-07-20 10:13:38 +01:00
|
|
|
# Maximum length check
|
2025-07-16 14:13:26 +01:00
|
|
|
if len(password) > 127:
|
|
|
|
errors.append("Password must be no more than 127 characters long")
|
|
|
|
|
2025-07-20 10:13:38 +01:00
|
|
|
# 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)
|
|
|
|
has_non_alpha = any(not c.isalnum() for c in password)
|
2025-07-16 14:13:26 +01:00
|
|
|
|
2025-07-20 10:13:38 +01:00
|
|
|
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)))
|
2025-07-16 14:13:26 +01:00
|
|
|
|
|
|
|
return errors
|
|
|
|
|
2025-07-20 10:13:38 +01:00
|
|
|
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
|
|
|
|
|
|
|
|
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):
|
|
|
|
"""Get current password strength setting and requirements"""
|
|
|
|
global DEMO_PASSWORD_STRENGTH
|
|
|
|
|
|
|
|
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'
|
|
|
|
}
|
|
|
|
|
|
|
|
return {
|
|
|
|
'level': DEMO_PASSWORD_STRENGTH,
|
|
|
|
'description': descriptions.get(DEMO_PASSWORD_STRENGTH, 'Unknown strength level')
|
|
|
|
}
|
|
|
|
|
|
|
|
def verify_current_password(self, username, password):
|
2025-07-16 14:13:26 +01:00
|
|
|
"""Verify current password in demo mode"""
|
|
|
|
return DEMO_USERS.get(username) == password
|
|
|
|
|
2025-07-20 10:13:38 +01:00
|
|
|
def change_password(self, username, new_password):
|
2025-07-16 14:13:26 +01:00
|
|
|
"""Change password in demo mode"""
|
|
|
|
try:
|
|
|
|
# Simulate password change
|
|
|
|
DEMO_USERS[username] = new_password
|
|
|
|
return True, "Password changed successfully (demo mode)"
|
|
|
|
except Exception as e:
|
|
|
|
return False, "Error changing password: {}".format(str(e))
|
|
|
|
|
2025-07-20 10:13:38 +01:00
|
|
|
class DemoConfigDB:
|
|
|
|
"""Demo configuration database"""
|
|
|
|
|
|
|
|
def get_password_strength_setting(self):
|
|
|
|
"""Get password strength setting"""
|
|
|
|
global DEMO_PASSWORD_STRENGTH
|
|
|
|
return DEMO_PASSWORD_STRENGTH
|
|
|
|
|
|
|
|
def set_password_strength_setting(self, strength):
|
|
|
|
"""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()
|
|
|
|
return True, "Password strength setting updated"
|
|
|
|
|
2025-07-16 14:13:26 +01:00
|
|
|
class DemoSystemInfo:
|
|
|
|
"""Demo system info for testing"""
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def get_version():
|
2025-07-20 10:13:38 +01:00
|
|
|
return "SME Server 11 (beta1) - Enhanced Demo Mode"
|
2025-07-16 14:13:26 +01:00
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def get_copyright_info():
|
|
|
|
return {
|
|
|
|
'mitel': 'Copyright 1999-2006 Mitel Corporation',
|
|
|
|
'rights': 'All rights reserved.',
|
|
|
|
'koozali': 'Copyright (C) 2013 - 2021 Koozali Foundation Inc.'
|
|
|
|
}
|
|
|
|
|
|
|
|
# Initialize demo utilities
|
|
|
|
password_manager = DemoPasswordManager()
|
|
|
|
system_info = DemoSystemInfo()
|
2025-07-20 10:13:38 +01:00
|
|
|
config_db = DemoConfigDB()
|
2025-07-16 14:13:26 +01:00
|
|
|
|
|
|
|
@app.route('/', methods=['GET', 'POST'])
|
|
|
|
def password_change():
|
2025-07-20 10:13:38 +01:00
|
|
|
"""Main password change form handler with enhanced validation"""
|
|
|
|
|
|
|
|
# Get current password requirements for display
|
|
|
|
password_requirements = password_manager.get_password_strength_info()
|
2025-07-16 14:13:26 +01:00
|
|
|
|
|
|
|
if request.method == 'POST':
|
|
|
|
# Get form data
|
|
|
|
username = request.form.get('username', '').strip()
|
|
|
|
old_password = request.form.get('old_password', '')
|
|
|
|
new_password = request.form.get('new_password', '')
|
|
|
|
verify_password = request.form.get('verify_password', '')
|
|
|
|
|
|
|
|
# Validation
|
|
|
|
errors = []
|
|
|
|
|
|
|
|
if not username:
|
|
|
|
errors.append("Username is required")
|
|
|
|
|
|
|
|
if not old_password:
|
|
|
|
errors.append("Current password is required")
|
|
|
|
|
|
|
|
if not new_password:
|
|
|
|
errors.append("New password is required")
|
|
|
|
|
|
|
|
if not verify_password:
|
|
|
|
errors.append("Password verification is required")
|
|
|
|
|
|
|
|
if new_password != verify_password:
|
|
|
|
errors.append("New password and verification do not match")
|
|
|
|
|
|
|
|
# Validate username
|
|
|
|
if username:
|
|
|
|
valid_username, username_msg = password_manager.validate_username(username)
|
|
|
|
if not valid_username:
|
|
|
|
errors.append(username_msg)
|
|
|
|
|
|
|
|
# Validate current password
|
|
|
|
if username and old_password and not errors:
|
|
|
|
if not password_manager.verify_current_password(username, old_password):
|
|
|
|
errors.append("Current password is incorrect")
|
|
|
|
|
2025-07-20 10:13:38 +01:00
|
|
|
# Enhanced password strength validation
|
2025-07-16 14:13:26 +01:00
|
|
|
if new_password:
|
|
|
|
strength_errors = password_manager.validate_password_strength(new_password)
|
|
|
|
errors.extend(strength_errors)
|
|
|
|
|
|
|
|
if errors:
|
|
|
|
for error in errors:
|
|
|
|
flash(error, 'error')
|
|
|
|
else:
|
|
|
|
# Change the password
|
|
|
|
success, message = password_manager.change_password(username, new_password)
|
|
|
|
|
|
|
|
if success:
|
|
|
|
flash(message, 'success')
|
|
|
|
return redirect(url_for('password_change'))
|
|
|
|
else:
|
|
|
|
flash(message, 'error')
|
|
|
|
|
|
|
|
# Get system information for the template
|
|
|
|
version = system_info.get_version()
|
|
|
|
copyright_info = system_info.get_copyright_info()
|
|
|
|
|
|
|
|
return render_template('password_change.html',
|
|
|
|
version=version,
|
2025-07-20 10:13:38 +01:00
|
|
|
copyright_info=copyright_info,
|
|
|
|
password_requirements=password_requirements)
|
|
|
|
|
|
|
|
@app.route('/api/password-strength', methods=['POST'])
|
|
|
|
def check_password_strength():
|
|
|
|
"""API endpoint for real-time password strength checking"""
|
|
|
|
try:
|
|
|
|
data = request.get_json()
|
|
|
|
password = data.get('password', '')
|
|
|
|
|
|
|
|
if not password:
|
|
|
|
return jsonify({'errors': ['Password cannot be empty']})
|
|
|
|
|
|
|
|
# Get validation errors
|
|
|
|
errors = password_manager.validate_password_strength(password)
|
|
|
|
|
|
|
|
# Calculate strength score
|
|
|
|
strength_score = 0
|
|
|
|
if len(password) >= 12:
|
|
|
|
strength_score += 1
|
|
|
|
if any(c.isupper() for c in password):
|
|
|
|
strength_score += 1
|
|
|
|
if any(c.islower() for c in password):
|
|
|
|
strength_score += 1
|
|
|
|
if any(c.isdigit() for c in password):
|
|
|
|
strength_score += 1
|
|
|
|
if any(not c.isalnum() for c in password):
|
|
|
|
strength_score += 1
|
|
|
|
|
|
|
|
strength_levels = ['Very Weak', 'Weak', 'Fair', 'Good', 'Strong']
|
|
|
|
strength_level = strength_levels[min(strength_score, len(strength_levels) - 1)]
|
|
|
|
|
|
|
|
return jsonify({
|
|
|
|
'errors': errors,
|
|
|
|
'strength_score': strength_score,
|
|
|
|
'max_score': 5,
|
|
|
|
'strength_level': strength_level,
|
|
|
|
'is_valid': len(errors) == 0
|
|
|
|
})
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
return jsonify({'error': 'Failed to check password strength'}), 500
|
|
|
|
|
|
|
|
@app.route('/api/password-config', methods=['GET', 'POST'])
|
|
|
|
def password_config():
|
|
|
|
"""API endpoint for managing password strength configuration"""
|
|
|
|
if request.method == 'GET':
|
|
|
|
# Get current configuration
|
|
|
|
try:
|
|
|
|
strength_setting = config_db.get_password_strength_setting()
|
|
|
|
requirements = password_manager.get_password_strength_info()
|
|
|
|
|
|
|
|
return jsonify({
|
|
|
|
'current_setting': strength_setting,
|
|
|
|
'requirements': requirements,
|
|
|
|
'available_levels': {
|
|
|
|
'none': 'No specific password requirements',
|
|
|
|
'normal': 'Minimum 12 characters with complexity requirements',
|
|
|
|
'strong': 'Normal requirements plus protection against common passwords'
|
|
|
|
}
|
|
|
|
})
|
|
|
|
except Exception as e:
|
|
|
|
return jsonify({'error': 'Failed to get password configuration'}), 500
|
|
|
|
|
|
|
|
elif request.method == 'POST':
|
|
|
|
# Update configuration
|
|
|
|
try:
|
|
|
|
data = request.get_json()
|
|
|
|
new_strength = data.get('strength', '').lower()
|
|
|
|
|
|
|
|
success, message = config_db.set_password_strength_setting(new_strength)
|
|
|
|
|
|
|
|
if success:
|
|
|
|
return jsonify({
|
|
|
|
'success': True,
|
|
|
|
'message': 'Password strength setting updated to: {}'.format(new_strength),
|
|
|
|
'new_setting': new_strength
|
|
|
|
})
|
|
|
|
else:
|
|
|
|
return jsonify({'error': message}), 500
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
return jsonify({'error': 'Failed to update password configuration'}), 500
|
|
|
|
|
|
|
|
@app.route('/admin')
|
|
|
|
def admin_panel():
|
|
|
|
"""Simple admin panel for password strength configuration"""
|
|
|
|
try:
|
|
|
|
current_setting = config_db.get_password_strength_setting()
|
|
|
|
requirements = password_manager.get_password_strength_info()
|
|
|
|
|
|
|
|
return render_template('admin_panel.html',
|
|
|
|
current_setting=current_setting,
|
|
|
|
requirements=requirements,
|
|
|
|
version=system_info.get_version())
|
|
|
|
except Exception as e:
|
|
|
|
flash('Error loading admin panel: {}'.format(str(e)), 'error')
|
|
|
|
return redirect(url_for('password_change'))
|
2025-07-16 14:13:26 +01:00
|
|
|
|
|
|
|
@app.route('/demo-info')
|
|
|
|
def demo_info():
|
|
|
|
"""Demo information page"""
|
2025-07-20 10:13:38 +01:00
|
|
|
global DEMO_PASSWORD_STRENGTH
|
|
|
|
return jsonify({
|
|
|
|
'mode': 'enhanced_demo',
|
2025-07-16 14:13:26 +01:00
|
|
|
'demo_users': list(DEMO_USERS.keys()),
|
2025-07-20 10:13:38 +01:00
|
|
|
'current_password_strength': DEMO_PASSWORD_STRENGTH,
|
|
|
|
'features': ['configurable_strength', 'password_visibility', 'crypto_validation'],
|
2025-07-16 14:13:26 +01:00
|
|
|
'instructions': 'Use any of the demo usernames with their corresponding passwords to test the application'
|
2025-07-20 10:13:38 +01:00
|
|
|
})
|
2025-07-16 14:13:26 +01:00
|
|
|
|
|
|
|
@app.route('/health')
|
|
|
|
def health_check():
|
|
|
|
"""Health check endpoint"""
|
2025-07-20 10:13:38 +01:00
|
|
|
return jsonify({
|
|
|
|
'status': 'healthy',
|
|
|
|
'service': 'sme-server-password-change-enhanced-demo',
|
|
|
|
'password_strength_setting': DEMO_PASSWORD_STRENGTH,
|
|
|
|
'features': ['configurable_strength', 'password_visibility', 'crypto_validation']
|
|
|
|
})
|
2025-07-16 14:13:26 +01:00
|
|
|
|
|
|
|
if __name__ == '__main__':
|
2025-07-20 10:13:38 +01:00
|
|
|
print("Starting Enhanced SME Server Password Change Application in Demo Mode")
|
|
|
|
print("Features:")
|
|
|
|
print(" - Configurable password strength validation")
|
|
|
|
print(" - Password visibility toggles")
|
|
|
|
print(" - Real-time strength checking")
|
|
|
|
print(" - Crypto validation for strong passwords")
|
|
|
|
print("")
|
2025-07-16 14:13:26 +01:00
|
|
|
print("Demo users available:")
|
|
|
|
for user, password in DEMO_USERS.items():
|
|
|
|
print(" Username: {}, Password: {}".format(user, password))
|
2025-07-20 10:13:38 +01:00
|
|
|
print("")
|
|
|
|
print("Current password strength setting: {}".format(DEMO_PASSWORD_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")
|
2025-07-16 14:13:26 +01:00
|
|
|
|
2025-07-20 10:13:38 +01:00
|
|
|
app.run(host='0.0.0.0', port=5002, debug=False)
|
2025-07-16 14:13:26 +01:00
|
|
|
|