#!/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)