#!/usr/bin/env python3 """ Corrected SME Server Password Change Application - Python 3.6.8 Compatible A Flask web application for changing user passwords on SME Server, with correct database structure and external password validation library. Database Structure: passwordstrength=configuration Admin=strong Ibays=strong Users=strong Features: - Correct SME Server database integration - External zxcvbn password validation library - Password visibility toggles - Real-time password strength feedback - Admin configuration panel 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 from smeserver_utils import SMEPasswordManager, SMESystemInfo, SMEConfigDB app = Flask(__name__) app.secret_key = os.environ.get('SECRET_KEY', 'sme-server-password-change-corrected-key') CORS(app) # Initialize SME Server utilities password_manager = SMEPasswordManager() system_info = SMESystemInfo() config_db = SMEConfigDB() @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) # Calculate basic strength score for UI feedback # 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 # 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 strength_levels = ["Very Weak", "Weak", "Fair", "Good", "Strong"] # Map zxcvbn score (0-4) to strength_levels (0-4) display_score = zxcvbn_score if password_manager.external_validator else 0 strength_level = strength_levels[min(display_score, len(strength_levels) - 1)] return jsonify({ 'errors': errors, 'strength_score': display_score, 'max_score': 4 if zxcvbn_score is not None else 5, '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') if account_type not in ['Users', 'Admin', 'Ibays']: return jsonify({'error': 'Invalid account type'}), 400 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('/health') def health_check(): """Health check endpoint""" try: # Test database connectivity all_settings = config_db.get_all_password_strength_settings() return jsonify({ 'status': 'healthy', 'service': 'sme-server-password-change-corrected', 'password_strength_settings': all_settings, 'features': ['corrected_db_structure', 'zxcvbn_validation', 'password_visibility', 'admin_panel'], 'using_zxcvbn': password_manager.external_validator is not None }) except Exception as e: return jsonify({ 'status': 'unhealthy', 'error': str(e) }), 500 @app.errorhandler(404) def not_found(error): """Handle 404 errors""" password_requirements = password_manager.get_password_strength_info('Users') return render_template('password_change.html', error="Page not found", password_requirements=password_requirements), 404 @app.errorhandler(500) def internal_error(error): """Handle 500 errors""" password_requirements = password_manager.get_password_strength_info('Users') return render_template('password_change.html', error="Internal server error", password_requirements=password_requirements), 500 if __name__ == '__main__': print("Starting Corrected SME Server Password Change Application") 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("") try: all_settings = config_db.get_all_password_strength_settings() print("Current password strength settings:") for account_type, strength in all_settings.items(): print(" {}: {}".format(account_type, strength.upper())) except: print("Password strength settings: Using defaults") print("") print("Using zxcvbn library: {}".format("Yes" if password_manager.external_validator else "No (fallback to basic validation)")) print("") print("Access the application at: http://localhost:5000") print("Admin panel at: http://localhost:5000/admin") # Run the application app.run(host='0.0.0.0', port=5000, debug=True)