#!/usr/bin/env python3 """ Demo mode for Corrected SME Server Password Change Application - Python 3.6.8 Compatible 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 """ 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-corrected-demo-key') CORS(app) # Demo users for testing DEMO_USERS = { 'testuser': 'oldpassword123', 'admin': 'adminpass456', 'john': 'johnpass789' } # Demo password strength settings (correct SME Server structure) DEMO_PASSWORD_STRENGTH = { 'Users': 'strong', 'Admin': 'strong', 'Ibays': 'strong' } class DemoPasswordManager: """Demo password manager with corrected DB structure and zxcvbn""" def __init__(self): # 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_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': if len(password) < 1: errors.append("Password cannot be empty") return errors 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") 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))) # 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 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 zxcvbn advanced validation' if self.external_validator else 'Normal requirements plus basic pattern protection' } return { '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): """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 with zxcvbn validation)" except Exception as e: return False, "Error changing password: {}".format(str(e)) class DemoConfigDB: """Demo configuration database with correct structure""" def get_all_password_strength_settings(self): """Get all password strength settings""" global DEMO_PASSWORD_STRENGTH return DEMO_PASSWORD_STRENGTH.copy() 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" 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: """Demo system info for testing""" @staticmethod def get_version(): return "SME Server 11 (beta1) - Corrected Demo Mode with zxcvbn" @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 corrected validation""" # Get current password requirements for display password_requirements = password_manager.get_password_strength_info('Users') 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 with zxcvbn if new_password: strength_errors = password_manager.validate_password_strength(new_password, username) 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 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 using zxcvbn errors = password_manager.validate_password_strength(password, username) # 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: basic_score += 1 if any(c.isupper() for c in password): basic_score += 1 if any(c.islower() for c in password): basic_score += 1 if any(c.isdigit() for c in password): basic_score += 1 if any(not c.isalnum() for c in password): basic_score += 1 strength_levels = ['Very Weak', 'Weak', 'Fair', 'Good', 'Strong'] # 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': display_score, 'max_score': max_score, 'strength_level': strength_level, 'is_valid': len(errors) == 0, 'using_zxcvbn': password_manager.external_validator is not None, 'zxcvbn_feedback': zxcvbn_feedback }) 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 for all account types try: all_settings = config_db.get_all_password_strength_settings() return jsonify({ 'current_settings': all_settings, 'available_levels': { 'none': 'No specific password requirements', 'normal': 'Minimum 12 characters with complexity requirements', '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 elif request.method == 'POST': # Update configuration 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, account_type) if success: return jsonify({ 'success': True, '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 except Exception as e: return jsonify({'error': 'Failed to update password configuration'}), 500 @app.route('/admin') def admin_panel(): """Admin panel for password strength configuration""" try: all_settings = config_db.get_all_password_strength_settings() return render_template('admin_panel.html', 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')) @app.route('/demo-info') def demo_info(): """Demo information page""" global DEMO_PASSWORD_STRENGTH return jsonify({ 'mode': 'corrected_demo_with_zxcvbn', 'demo_users': list(DEMO_USERS.keys()), 'current_password_strength': DEMO_PASSWORD_STRENGTH, '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' }) @app.route('/health') def health_check(): """Health check endpoint""" return jsonify({ 'status': 'healthy', '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 Corrected SME Server Password Change Application in Demo Mode") print("Features:") 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(" - 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 settings:") for account_type, strength in DEMO_PASSWORD_STRENGTH.items(): print(" {}: {}".format(account_type, strength.upper())) print("") 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=5003, debug=False)