Files
StandalonePasswordChange/python-flask/smeserver-password-app/demo_mode.py

417 lines
15 KiB
Python
Raw Normal View History

#!/usr/bin/env python3
"""
Enhanced Demo mode for 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.
Compatible with Python 3.6.8 and Flask 2.0.3
"""
import os
from flask import Flask, render_template, request, flash, redirect, url_for, jsonify
from flask_cors import CORS
app = Flask(__name__)
app.secret_key = os.environ.get('SECRET_KEY', 'sme-server-password-change-enhanced-demo-key')
CORS(app)
# Demo users for testing
DEMO_USERS = {
'testuser': 'oldpassword123',
'admin': 'adminpass456',
'john': 'johnpass789'
}
# Demo password strength setting (can be changed via admin panel)
DEMO_PASSWORD_STRENGTH = 'normal'
class DemoPasswordStrengthValidator:
"""Demo password strength validator with configurable levels"""
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 = []
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)
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)))
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
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):
"""Verify current password in demo mode"""
return DEMO_USERS.get(username) == password
def change_password(self, username, new_password):
"""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))
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"
class DemoSystemInfo:
"""Demo system info for testing"""
@staticmethod
def get_version():
return "SME Server 11 (beta1) - Enhanced Demo Mode"
@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()
config_db = DemoConfigDB()
@app.route('/', methods=['GET', 'POST'])
def password_change():
"""Main password change form handler with enhanced validation"""
# Get current password requirements for display
password_requirements = password_manager.get_password_strength_info()
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")
# Enhanced password strength validation
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,
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'))
@app.route('/demo-info')
def demo_info():
"""Demo information page"""
global DEMO_PASSWORD_STRENGTH
return jsonify({
'mode': 'enhanced_demo',
'demo_users': list(DEMO_USERS.keys()),
'current_password_strength': DEMO_PASSWORD_STRENGTH,
'features': ['configurable_strength', 'password_visibility', 'crypto_validation'],
'instructions': 'Use any of the demo usernames with their corresponding passwords to test the application'
})
@app.route('/health')
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']
})
if __name__ == '__main__':
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("")
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("")
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")
app.run(host='0.0.0.0', port=5002, debug=False)