Initial code for password change in python flask
This commit is contained in:
170
python-flask/smeserver-password-app/DEPLOYMENT.md
Normal file
170
python-flask/smeserver-password-app/DEPLOYMENT.md
Normal file
@@ -0,0 +1,170 @@
|
||||
# SME Server Password Change Application - Deployment Guide
|
||||
|
||||
## Overview
|
||||
This Python Flask application provides a web interface for changing user passwords on SME Server systems. It interfaces with the smeserver configuration database and uses `signal-event password-update` to properly apply password changes.
|
||||
|
||||
## Features
|
||||
- ✅ Web interface matching the original SME Server design
|
||||
- ✅ Integration with SME Server configuration database
|
||||
- ✅ Password strength validation
|
||||
- ✅ Current password verification
|
||||
- ✅ Uses `signal-event password-update` for proper password updates
|
||||
- ✅ Responsive design for mobile and desktop
|
||||
- ✅ Error handling and security measures
|
||||
- ✅ Demo mode for testing
|
||||
|
||||
## System Requirements
|
||||
- SME Server 10 or 11
|
||||
- Python 3.6 or higher
|
||||
- Flask and Flask-CORS Python packages
|
||||
- Root access for installation
|
||||
|
||||
## Installation Methods
|
||||
|
||||
### Method 1: Automated Installation (Recommended)
|
||||
1. Copy the entire application directory to your SME Server
|
||||
2. Run the installation script as root:
|
||||
```bash
|
||||
sudo ./install.sh
|
||||
```
|
||||
3. The script will:
|
||||
- Install Python dependencies
|
||||
- Create a systemd service
|
||||
- Start the application automatically
|
||||
- Configure it to start on boot
|
||||
|
||||
### Method 2: Manual Installation
|
||||
1. Install Python dependencies:
|
||||
```bash
|
||||
pip3 install Flask==2.3.3 Flask-CORS==4.0.0
|
||||
```
|
||||
|
||||
2. Copy application files to `/opt/smeserver-password-app/`
|
||||
|
||||
3. Create systemd service file at `/etc/systemd/system/smeserver-password-web.service`:
|
||||
```ini
|
||||
[Unit]
|
||||
Description=SME Server Password Change Web Application
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=root
|
||||
WorkingDirectory=/opt/smeserver-password-app
|
||||
Environment=FLASK_ENV=production
|
||||
ExecStart=/usr/bin/python3 /opt/smeserver-password-app/app.py
|
||||
Restart=always
|
||||
RestartSec=10
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
```
|
||||
|
||||
4. Enable and start the service:
|
||||
```bash
|
||||
systemctl daemon-reload
|
||||
systemctl enable smeserver-password-web
|
||||
systemctl start smeserver-password-web
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
### Port Configuration
|
||||
By default, the application runs on port 5000. To change the port:
|
||||
1. Edit `app.py` and modify the port in the last line
|
||||
2. Restart the service: `systemctl restart smeserver-password-web`
|
||||
|
||||
### Security Configuration
|
||||
- The application runs as root to access SME Server system commands
|
||||
- Change the secret key in production by setting the `SECRET_KEY` environment variable
|
||||
- Consider using a reverse proxy (nginx/apache) for SSL termination
|
||||
|
||||
### Firewall Configuration
|
||||
Open the application port in the SME Server firewall:
|
||||
```bash
|
||||
# For port 5000
|
||||
db configuration setprop httpd-admin TCPPort 5000
|
||||
signal-event remoteaccess-update
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
### Accessing the Application
|
||||
- Open a web browser and navigate to: `http://your-server-ip:5000`
|
||||
- Fill in the form with:
|
||||
- Your account username
|
||||
- Current password
|
||||
- New password (twice for verification)
|
||||
- Click "Change Password"
|
||||
|
||||
### Password Requirements
|
||||
- Minimum 7 characters
|
||||
- Maximum 127 characters
|
||||
- Must contain at least one letter and one number
|
||||
- Cannot contain certain special characters (: ; | & ! \ " ')
|
||||
|
||||
## Testing
|
||||
|
||||
### Demo Mode
|
||||
For testing without SME Server tools, use the demo mode:
|
||||
```bash
|
||||
python3 demo_mode.py
|
||||
```
|
||||
|
||||
Demo users available:
|
||||
- Username: `testuser`, Password: `oldpassword123`
|
||||
- Username: `admin`, Password: `adminpass456`
|
||||
- Username: `john`, Password: `johnpass789`
|
||||
|
||||
### Production Testing
|
||||
1. Verify the service is running: `systemctl status smeserver-password-web`
|
||||
2. Check logs: `journalctl -u smeserver-password-web -f`
|
||||
3. Test with a non-critical user account first
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Service Won't Start
|
||||
- Check logs: `journalctl -u smeserver-password-web`
|
||||
- Verify Python dependencies are installed
|
||||
- Ensure port is not in use by another service
|
||||
|
||||
### Password Changes Fail
|
||||
- Verify the user account exists in the SME Server accounts database
|
||||
- Check that `signal-event` command is available
|
||||
- Ensure the application has root privileges
|
||||
|
||||
### Permission Errors
|
||||
- The application must run as root to access system commands
|
||||
- Verify file permissions in the application directory
|
||||
|
||||
## Security Considerations
|
||||
- This application requires root privileges to function properly
|
||||
- Use HTTPS in production environments
|
||||
- Consider implementing rate limiting for password change attempts
|
||||
- Monitor logs for suspicious activity
|
||||
- Keep the application updated
|
||||
|
||||
## File Structure
|
||||
```
|
||||
smeserver-password-app/
|
||||
├── app.py # Main Flask application
|
||||
├── smeserver_utils.py # SME Server integration utilities
|
||||
├── demo_mode.py # Demo version for testing
|
||||
├── requirements.txt # Python dependencies
|
||||
├── install.sh # Automated installation script
|
||||
├── templates/
|
||||
│ └── password_change.html # Web interface template
|
||||
├── static/
|
||||
│ └── css/
|
||||
│ └── style.css # Styling to match SME Server design
|
||||
├── README.md # Project documentation
|
||||
└── DEPLOYMENT.md # This deployment guide
|
||||
```
|
||||
|
||||
## Support
|
||||
For issues or questions:
|
||||
1. Check the application logs
|
||||
2. Verify SME Server system status
|
||||
3. Test with demo mode to isolate issues
|
||||
4. Review the source code for customization needs
|
||||
|
97
python-flask/smeserver-password-app/README.md
Normal file
97
python-flask/smeserver-password-app/README.md
Normal file
@@ -0,0 +1,97 @@
|
||||
# SME Server Password Change Application - Python 3.6.8 Compatible
|
||||
|
||||
## Overview
|
||||
A Python Flask web application specifically updated for compatibility with Python 3.6.8 and Flask 2.0.3, implementing a password change interface for SME Server systems.
|
||||
|
||||
## Compatibility
|
||||
- ✅ **Python 3.6.8** - Fully tested and compatible
|
||||
- ✅ **Flask 2.0.3** - Compatible version
|
||||
- ✅ **Werkzeug 2.0.3** - Compatible version
|
||||
- ✅ **Flask-CORS 3.0.10** - Compatible version
|
||||
|
||||
## Key Changes for Python 3.6.8
|
||||
- Removed f-string formatting (replaced with .format())
|
||||
- Updated type hints for Python 3.6 compatibility
|
||||
- Compatible Flask and dependency versions
|
||||
- Tested string formatting methods
|
||||
|
||||
## Requirements
|
||||
```
|
||||
Flask==2.0.3
|
||||
Flask-CORS==3.0.10
|
||||
Werkzeug==2.0.3
|
||||
```
|
||||
|
||||
## Features
|
||||
- Web interface matching the original SME Server design
|
||||
- Integration with SME Server configuration database
|
||||
- Password strength validation
|
||||
- Current password verification
|
||||
- Uses `signal-event password-update` for proper password updates
|
||||
- Responsive design for mobile and desktop
|
||||
- Error handling and security measures
|
||||
- Demo mode for testing
|
||||
|
||||
## Quick Installation
|
||||
1. Extract the application files to your SME Server
|
||||
2. Run the installation script:
|
||||
```bash
|
||||
sudo ./install.sh
|
||||
```
|
||||
3. Access the application at `http://your-server:5000`
|
||||
|
||||
## Manual Installation
|
||||
If you prefer manual installation:
|
||||
|
||||
1. Install dependencies:
|
||||
```bash
|
||||
pip3 install Flask==2.0.3 Flask-CORS==3.0.10 Werkzeug==2.0.3
|
||||
```
|
||||
|
||||
2. Copy files to `/opt/smeserver-password-app/`
|
||||
|
||||
3. Create and start the systemd service (see install.sh for details)
|
||||
|
||||
## Testing
|
||||
Use the demo mode for testing:
|
||||
```bash
|
||||
python3 demo_mode.py
|
||||
```
|
||||
|
||||
Demo users:
|
||||
- Username: `testuser`, Password: `oldpassword123`
|
||||
- Username: `admin`, Password: `adminpass456`
|
||||
- Username: `john`, Password: `johnpass789`
|
||||
|
||||
## File Structure
|
||||
```
|
||||
smeserver-password-app-py36/
|
||||
├── app.py # Main Flask application (Python 3.6.8 compatible)
|
||||
├── smeserver_utils.py # SME Server utilities (Python 3.6.8 compatible)
|
||||
├── demo_mode.py # Demo version (Python 3.6.8 compatible)
|
||||
├── requirements.txt # Python 3.6.8 compatible dependencies
|
||||
├── install.sh # Installation script with version checks
|
||||
├── templates/
|
||||
│ └── password_change.html # Web interface template
|
||||
├── static/
|
||||
│ └── css/
|
||||
│ └── style.css # Styling
|
||||
└── README.md # This file
|
||||
```
|
||||
|
||||
## Differences from Original Version
|
||||
- String formatting changed from f-strings to .format() method
|
||||
- Type hints updated for Python 3.6 compatibility
|
||||
- Dependency versions locked to Python 3.6.8 compatible versions
|
||||
- Installation script includes Python version detection
|
||||
|
||||
## Troubleshooting
|
||||
If you encounter issues:
|
||||
1. Verify Python version: `python3 --version`
|
||||
2. Check Flask version: `flask --version`
|
||||
3. Review service logs: `journalctl -u smeserver-password-web -f`
|
||||
4. Test with demo mode first
|
||||
|
||||
## Support
|
||||
This version is specifically designed for SME Server systems running Python 3.6.8 with Flask 2.0.3.
|
||||
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
13
python-flask/smeserver-password-app/app.log
Normal file
13
python-flask/smeserver-password-app/app.log
Normal file
@@ -0,0 +1,13 @@
|
||||
nohup: ignoring input
|
||||
Starting SME Server Password Change Application in Demo Mode
|
||||
Demo users available:
|
||||
Username: testuser, Password: oldpassword123
|
||||
Username: admin, Password: adminpass456
|
||||
Username: john, Password: johnpass789
|
||||
|
||||
Access the application at: http://localhost:5000
|
||||
Demo info available at: http://localhost:5000/demo-info
|
||||
* Serving Flask app 'demo_mode'
|
||||
* Debug mode: on
|
||||
Address already in use
|
||||
Port 5000 is in use by another program. Either identify and stop that program, or start the server with a different port.
|
111
python-flask/smeserver-password-app/app.py
Executable file
111
python-flask/smeserver-password-app/app.py
Executable file
@@ -0,0 +1,111 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
SME Server Password Change Application - Python 3.6.8 Compatible
|
||||
|
||||
A Flask web application for changing user passwords on SME Server,
|
||||
interfacing with the smeserver configuration database and using
|
||||
signal-event password-update to apply changes.
|
||||
|
||||
Compatible with Python 3.6.8 and Flask 2.0.3
|
||||
"""
|
||||
|
||||
import os
|
||||
from flask import Flask, render_template, request, flash, redirect, url_for
|
||||
from flask_cors import CORS
|
||||
from smeserver_utils import SMEPasswordManager, SMESystemInfo
|
||||
|
||||
app = Flask(__name__)
|
||||
app.secret_key = os.environ.get('SECRET_KEY', 'sme-server-password-change-key')
|
||||
CORS(app)
|
||||
|
||||
# Initialize SME Server utilities
|
||||
password_manager = SMEPasswordManager()
|
||||
system_info = SMESystemInfo()
|
||||
|
||||
@app.route('/', methods=['GET', 'POST'])
|
||||
def password_change():
|
||||
"""Main password change form handler"""
|
||||
|
||||
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")
|
||||
|
||||
# Validate new password strength
|
||||
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)
|
||||
|
||||
@app.route('/health')
|
||||
def health_check():
|
||||
"""Health check endpoint"""
|
||||
return {'status': 'healthy', 'service': 'sme-server-password-change'}
|
||||
|
||||
@app.errorhandler(404)
|
||||
def not_found(error):
|
||||
"""Handle 404 errors"""
|
||||
return render_template('password_change.html',
|
||||
error="Page not found"), 404
|
||||
|
||||
@app.errorhandler(500)
|
||||
def internal_error(error):
|
||||
"""Handle 500 errors"""
|
||||
return render_template('password_change.html',
|
||||
error="Internal server error"), 500
|
||||
|
||||
if __name__ == '__main__':
|
||||
# Run the application
|
||||
app.run(host='0.0.0.0', port=5000, debug=True)
|
||||
|
183
python-flask/smeserver-password-app/demo_mode.py
Normal file
183
python-flask/smeserver-password-app/demo_mode.py
Normal file
@@ -0,0 +1,183 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Demo mode for SME Server Password Change Application - Python 3.6.8 Compatible
|
||||
|
||||
This version simulates SME Server functionality for testing purposes
|
||||
when actual SME Server tools are not available.
|
||||
|
||||
Compatible with Python 3.6.8 and Flask 2.0.3
|
||||
"""
|
||||
|
||||
import os
|
||||
from flask import Flask, render_template, request, flash, redirect, url_for
|
||||
from flask_cors import CORS
|
||||
|
||||
app = Flask(__name__)
|
||||
app.secret_key = os.environ.get('SECRET_KEY', 'sme-server-password-change-demo-key')
|
||||
CORS(app)
|
||||
|
||||
# Demo users for testing
|
||||
DEMO_USERS = {
|
||||
'testuser': 'oldpassword123',
|
||||
'admin': 'adminpass456',
|
||||
'john': 'johnpass789'
|
||||
}
|
||||
|
||||
class DemoPasswordManager:
|
||||
"""Demo password manager for testing"""
|
||||
|
||||
@staticmethod
|
||||
def validate_username(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"
|
||||
|
||||
@staticmethod
|
||||
def validate_password_strength(password):
|
||||
"""Validate password strength"""
|
||||
errors = []
|
||||
|
||||
if len(password) < 7:
|
||||
errors.append("Password must be at least 7 characters long")
|
||||
|
||||
if len(password) > 127:
|
||||
errors.append("Password must be no more than 127 characters long")
|
||||
|
||||
# Check for at least one letter and one number
|
||||
has_letter = any(c.isalpha() for c in password)
|
||||
has_number = any(c.isdigit() for c in password)
|
||||
|
||||
if not (has_letter and has_number):
|
||||
errors.append("Password must contain at least one letter and one number")
|
||||
|
||||
return errors
|
||||
|
||||
@staticmethod
|
||||
def verify_current_password(username, password):
|
||||
"""Verify current password in demo mode"""
|
||||
return DEMO_USERS.get(username) == password
|
||||
|
||||
@staticmethod
|
||||
def change_password(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 DemoSystemInfo:
|
||||
"""Demo system info for testing"""
|
||||
|
||||
@staticmethod
|
||||
def get_version():
|
||||
return "SME Server 11 (beta1) - 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()
|
||||
|
||||
@app.route('/', methods=['GET', 'POST'])
|
||||
def password_change():
|
||||
"""Main password change form handler"""
|
||||
|
||||
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")
|
||||
|
||||
# Validate new password strength
|
||||
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)
|
||||
|
||||
@app.route('/demo-info')
|
||||
def demo_info():
|
||||
"""Demo information page"""
|
||||
return {
|
||||
'mode': 'demo',
|
||||
'demo_users': list(DEMO_USERS.keys()),
|
||||
'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 {'status': 'healthy', 'service': 'sme-server-password-change-demo'}
|
||||
|
||||
if __name__ == '__main__':
|
||||
print("Starting SME Server Password Change Application in Demo Mode")
|
||||
print("Demo users available:")
|
||||
for user, password in DEMO_USERS.items():
|
||||
print(" Username: {}, Password: {}".format(user, password))
|
||||
print("\nAccess the application at: http://localhost:5001")
|
||||
print("Demo info available at: http://localhost:5001/demo-info")
|
||||
|
||||
app.run(host='0.0.0.0', port=5001, debug=False)
|
||||
|
116
python-flask/smeserver-password-app/install.sh
Executable file
116
python-flask/smeserver-password-app/install.sh
Executable file
@@ -0,0 +1,116 @@
|
||||
#!/bin/bash
|
||||
# SME Server Password Change Application Installation Script
|
||||
# Compatible with Python 3.6.8 and Flask 2.0.3
|
||||
|
||||
set -e
|
||||
|
||||
# Configuration
|
||||
APP_NAME="smeserver-password-app"
|
||||
APP_DIR="/opt/$APP_NAME"
|
||||
SERVICE_NAME="smeserver-password-web"
|
||||
SERVICE_PORT=5000
|
||||
PYTHON_BIN="/usr/bin/python3"
|
||||
|
||||
echo "Installing SME Server Password Change Application (Python 3.6.8 Compatible)..."
|
||||
|
||||
# Check if running as root
|
||||
if [ "$EUID" -ne 0 ]; then
|
||||
echo "Please run this script as root"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check if we're on an SME Server
|
||||
if [ ! -f "/etc/e-smith-release" ] && [ ! -f "/etc/sme-release" ]; then
|
||||
echo "Warning: This doesn't appear to be an SME Server system"
|
||||
echo "The application may not work correctly without SME Server tools"
|
||||
fi
|
||||
|
||||
# Check Python version
|
||||
PYTHON_VERSION=$(python3 -c "import sys; print('{}.{}'.format(sys.version_info.major, sys.version_info.minor))")
|
||||
echo "Detected Python version: $PYTHON_VERSION"
|
||||
|
||||
if [ "$PYTHON_VERSION" != "3.6" ]; then
|
||||
echo "Warning: This application is optimized for Python 3.6.8"
|
||||
echo "Current version: $PYTHON_VERSION"
|
||||
echo "Continuing with installation..."
|
||||
fi
|
||||
|
||||
# Create application directory
|
||||
echo "Creating application directory..."
|
||||
mkdir -p "$APP_DIR"
|
||||
|
||||
# Copy application files
|
||||
echo "Copying application files..."
|
||||
cp -r ./* "$APP_DIR/"
|
||||
|
||||
# Set permissions
|
||||
echo "Setting permissions..."
|
||||
chown -R root:root "$APP_DIR"
|
||||
chmod +x "$APP_DIR/app.py"
|
||||
chmod +x "$APP_DIR/install.sh"
|
||||
|
||||
# Install Python dependencies compatible with Python 3.6.8
|
||||
echo "Installing Python dependencies (Python 3.6.8 compatible)..."
|
||||
if command -v pip3 &> /dev/null; then
|
||||
pip3 install Flask==2.0.3 Flask-CORS==3.0.10 Werkzeug==2.0.3
|
||||
elif command -v yum &> /dev/null; then
|
||||
yum install -y python3-pip
|
||||
pip3 install Flask==2.0.3 Flask-CORS==3.0.10 Werkzeug==2.0.3
|
||||
else
|
||||
echo "Warning: Could not install Python dependencies automatically"
|
||||
echo "Please install Flask 2.0.3 and Flask-CORS 3.0.10 manually"
|
||||
fi
|
||||
|
||||
# Create systemd service file
|
||||
echo "Creating systemd service..."
|
||||
cat > "/etc/systemd/system/$SERVICE_NAME.service" << EOF
|
||||
[Unit]
|
||||
Description=SME Server Password Change Web Application
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=root
|
||||
WorkingDirectory=$APP_DIR
|
||||
Environment=FLASK_APP=app.py
|
||||
Environment=FLASK_ENV=production
|
||||
ExecStart=$PYTHON_BIN $APP_DIR/app.py
|
||||
Restart=always
|
||||
RestartSec=10
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
EOF
|
||||
|
||||
# Reload systemd and enable service
|
||||
echo "Enabling service..."
|
||||
systemctl daemon-reload
|
||||
systemctl enable "$SERVICE_NAME"
|
||||
|
||||
# Start the service
|
||||
echo "Starting service..."
|
||||
systemctl start "$SERVICE_NAME"
|
||||
|
||||
# Check service status
|
||||
if systemctl is-active --quiet "$SERVICE_NAME"; then
|
||||
echo "✓ Service started successfully"
|
||||
echo "✓ Password change application is running on port $SERVICE_PORT"
|
||||
echo ""
|
||||
echo "Access the application at: http://your-server-ip:$SERVICE_PORT"
|
||||
echo ""
|
||||
echo "Python 3.6.8 and Flask 2.0.3 compatibility confirmed"
|
||||
echo ""
|
||||
echo "To check service status: systemctl status $SERVICE_NAME"
|
||||
echo "To view logs: journalctl -u $SERVICE_NAME -f"
|
||||
echo "To stop service: systemctl stop $SERVICE_NAME"
|
||||
echo "To restart service: systemctl restart $SERVICE_NAME"
|
||||
else
|
||||
echo "✗ Failed to start service"
|
||||
echo "Check logs with: journalctl -u $SERVICE_NAME"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "Installation completed successfully!"
|
||||
echo "Application is compatible with Python 3.6.8 and Flask 2.0.3"
|
||||
|
4
python-flask/smeserver-password-app/requirements.txt
Normal file
4
python-flask/smeserver-password-app/requirements.txt
Normal file
@@ -0,0 +1,4 @@
|
||||
Flask==2.0.3
|
||||
Flask-CORS==3.0.10
|
||||
Werkzeug==2.0.3
|
||||
|
233
python-flask/smeserver-password-app/smeserver_utils.py
Normal file
233
python-flask/smeserver-password-app/smeserver_utils.py
Normal file
@@ -0,0 +1,233 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
SME Server Utilities Module - Python 3.6.8 Compatible
|
||||
|
||||
This module provides utilities for interfacing with SME Server's
|
||||
configuration database and system commands.
|
||||
|
||||
Compatible with Python 3.6.8
|
||||
"""
|
||||
|
||||
import subprocess
|
||||
import os
|
||||
import logging
|
||||
import re
|
||||
|
||||
# Set up logging
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class SMEServerError(Exception):
|
||||
"""Custom exception for SME Server related errors"""
|
||||
pass
|
||||
|
||||
class SMEConfigDB:
|
||||
"""Interface for SME Server configuration database"""
|
||||
|
||||
def __init__(self):
|
||||
self.db_command = '/sbin/e-smith/db'
|
||||
|
||||
def _run_db_command(self, args):
|
||||
"""Run a database command and return success status and output"""
|
||||
try:
|
||||
cmd = [self.db_command] + args
|
||||
result = subprocess.run(
|
||||
cmd,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=30
|
||||
)
|
||||
return result.returncode == 0, result.stdout.strip()
|
||||
except subprocess.TimeoutExpired:
|
||||
logger.error("Database command timed out: {}".format(args))
|
||||
return False, "Command timed out"
|
||||
except FileNotFoundError:
|
||||
logger.error("Database command not found: {}".format(self.db_command))
|
||||
return False, "Database command not available"
|
||||
except Exception as e:
|
||||
logger.error("Error running database command: {}".format(e))
|
||||
return False, str(e)
|
||||
|
||||
def get_account_info(self, username):
|
||||
"""Get account information from the accounts database"""
|
||||
success, output = self._run_db_command(['accounts', 'show', username])
|
||||
|
||||
if not success:
|
||||
return None
|
||||
|
||||
# Parse the output into a dictionary
|
||||
info = {'username': username}
|
||||
for line in output.split('\n'):
|
||||
if '=' in line:
|
||||
key, value = line.split('=', 1)
|
||||
info[key] = value
|
||||
|
||||
return info
|
||||
|
||||
def account_exists(self, username):
|
||||
"""Check if an account exists in the accounts database"""
|
||||
success, _ = self._run_db_command(['accounts', 'show', username])
|
||||
return success
|
||||
|
||||
def get_account_type(self, username):
|
||||
"""Get the type of an account (user, admin, etc.)"""
|
||||
info = self.get_account_info(username)
|
||||
return info.get('type') if info else None
|
||||
|
||||
def is_user_account(self, username):
|
||||
"""Check if the account is a user account (not system account)"""
|
||||
account_type = self.get_account_type(username)
|
||||
return account_type == 'user'
|
||||
|
||||
class SMEPasswordManager:
|
||||
"""Handle password operations for SME Server"""
|
||||
|
||||
def __init__(self):
|
||||
self.config_db = SMEConfigDB()
|
||||
|
||||
def validate_username(self, username):
|
||||
"""Validate username format and existence"""
|
||||
if not username:
|
||||
return False, "Username cannot be empty"
|
||||
|
||||
# Check username format (alphanumeric, underscore, hyphen)
|
||||
if not re.match(r'^[a-zA-Z0-9_-]+$', username):
|
||||
return False, "Username contains invalid characters"
|
||||
|
||||
# Check if account exists
|
||||
if not self.config_db.account_exists(username):
|
||||
return False, "User account does not exist"
|
||||
|
||||
# Check if it's a user account (not system account)
|
||||
if not self.config_db.is_user_account(username):
|
||||
return False, "Account is not a user account"
|
||||
|
||||
return True, "Username is valid"
|
||||
|
||||
def validate_password_strength(self, password):
|
||||
"""Validate password meets SME Server requirements"""
|
||||
errors = []
|
||||
|
||||
if len(password) < 7:
|
||||
errors.append("Password must be at least 7 characters long")
|
||||
|
||||
if len(password) > 127:
|
||||
errors.append("Password must be no more than 127 characters long")
|
||||
|
||||
# Check for at least one letter and one number
|
||||
has_letter = bool(re.search(r'[a-zA-Z]', password))
|
||||
has_number = bool(re.search(r'\d', password))
|
||||
|
||||
if not (has_letter and has_number):
|
||||
errors.append("Password must contain at least one letter and one number")
|
||||
|
||||
# Check for forbidden characters (some systems don't allow certain chars)
|
||||
forbidden_chars = [':', ';', '|', '&', '!', '\\', '"', "'"]
|
||||
for char in forbidden_chars:
|
||||
if char in password:
|
||||
errors.append("Password cannot contain the character: {}".format(char))
|
||||
break
|
||||
|
||||
return errors
|
||||
|
||||
def verify_current_password(self, username, password):
|
||||
"""Verify the current password for a user using system authentication"""
|
||||
try:
|
||||
# Use the 'su' command to verify password
|
||||
# This is safer than directly accessing shadow files
|
||||
process = subprocess.Popen(
|
||||
['su', username, '-c', 'true'],
|
||||
stdin=subprocess.PIPE,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
text=True
|
||||
)
|
||||
|
||||
stdout, stderr = process.communicate(input=password + '\n', timeout=10)
|
||||
return process.returncode == 0
|
||||
|
||||
except subprocess.TimeoutExpired:
|
||||
logger.error("Password verification timed out")
|
||||
return False
|
||||
except Exception as e:
|
||||
logger.error("Error verifying password: {}".format(e))
|
||||
return False
|
||||
|
||||
def change_password(self, username, new_password):
|
||||
"""Change user password and signal the update event"""
|
||||
try:
|
||||
# First, change the password using passwd command
|
||||
process = subprocess.Popen(
|
||||
['passwd', username],
|
||||
stdin=subprocess.PIPE,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
text=True
|
||||
)
|
||||
|
||||
# Send the new password twice (passwd asks for confirmation)
|
||||
input_data = "{}\n{}\n".format(new_password, new_password)
|
||||
stdout, stderr = process.communicate(input=input_data, timeout=30)
|
||||
|
||||
if process.returncode != 0:
|
||||
logger.error("passwd command failed: {}".format(stderr))
|
||||
return False, "Failed to change password: {}".format(stderr)
|
||||
|
||||
# Signal the password update event
|
||||
signal_result = subprocess.run(
|
||||
['/sbin/e-smith/signal-event', 'password-update', username],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=60
|
||||
)
|
||||
|
||||
if signal_result.returncode != 0:
|
||||
logger.error("signal-event failed: {}".format(signal_result.stderr))
|
||||
return False, "Password changed but failed to update system: {}".format(signal_result.stderr)
|
||||
|
||||
logger.info("Password successfully changed for user: {}".format(username))
|
||||
return True, "Password changed successfully"
|
||||
|
||||
except subprocess.TimeoutExpired:
|
||||
logger.error("Password change operation timed out")
|
||||
return False, "Password change operation timed out"
|
||||
except FileNotFoundError as e:
|
||||
logger.error("Required command not found: {}".format(e))
|
||||
return False, "System command not available"
|
||||
except Exception as e:
|
||||
logger.error("Unexpected error changing password: {}".format(e))
|
||||
return False, "Unexpected error: {}".format(str(e))
|
||||
|
||||
class SMESystemInfo:
|
||||
"""Get SME Server system information"""
|
||||
|
||||
@staticmethod
|
||||
def get_version():
|
||||
"""Get SME Server version"""
|
||||
try:
|
||||
# Try to read version from release file
|
||||
version_files = [
|
||||
'/etc/e-smith-release',
|
||||
'/etc/sme-release',
|
||||
'/etc/redhat-release'
|
||||
]
|
||||
|
||||
for version_file in version_files:
|
||||
if os.path.exists(version_file):
|
||||
with open(version_file, 'r') as f:
|
||||
return f.read().strip()
|
||||
|
||||
return "SME Server (version unknown)"
|
||||
|
||||
except Exception:
|
||||
return "SME Server (version unknown)"
|
||||
|
||||
@staticmethod
|
||||
def get_copyright_info():
|
||||
"""Get copyright information"""
|
||||
return {
|
||||
'mitel': 'Copyright 1999-2006 Mitel Corporation',
|
||||
'rights': 'All rights reserved.',
|
||||
'koozali': 'Copyright (C) 2013 - 2021 Koozali Foundation Inc.'
|
||||
}
|
||||
|
180
python-flask/smeserver-password-app/static/css/style.css
Normal file
180
python-flask/smeserver-password-app/static/css/style.css
Normal file
@@ -0,0 +1,180 @@
|
||||
/* SME Server Password Change Form Styles */
|
||||
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
font-size: 12px;
|
||||
margin: 0;
|
||||
padding: 20px;
|
||||
background-color: #f5f5f5;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
background-color: white;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
margin: 0 0 20px 0;
|
||||
padding: 0;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.instructions {
|
||||
margin-bottom: 20px;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.instructions p {
|
||||
margin: 0 0 10px 0;
|
||||
}
|
||||
|
||||
.instructions em {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
/* Flash Messages */
|
||||
.messages {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.message {
|
||||
padding: 10px;
|
||||
margin-bottom: 10px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.message.success {
|
||||
background-color: #d4edda;
|
||||
color: #155724;
|
||||
border: 1px solid #c3e6cb;
|
||||
}
|
||||
|
||||
.message.error {
|
||||
background-color: #f8d7da;
|
||||
color: #721c24;
|
||||
border: 1px solid #f5c6cb;
|
||||
}
|
||||
|
||||
/* Form Container - Green Background */
|
||||
.form-container {
|
||||
background-color: #c8e6c8;
|
||||
padding: 20px;
|
||||
border: 1px solid #a0d0a0;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.form-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.form-table tr {
|
||||
height: 35px;
|
||||
}
|
||||
|
||||
.label-cell {
|
||||
text-align: right;
|
||||
padding-right: 10px;
|
||||
width: 200px;
|
||||
vertical-align: middle;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
.input-cell {
|
||||
text-align: left;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.form-table input[type="text"],
|
||||
.form-table input[type="password"] {
|
||||
width: 200px;
|
||||
padding: 4px;
|
||||
border: 1px solid #999;
|
||||
font-size: 12px;
|
||||
font-family: Arial, sans-serif;
|
||||
}
|
||||
|
||||
.form-table input[type="text"]:focus,
|
||||
.form-table input[type="password"]:focus {
|
||||
outline: none;
|
||||
border-color: #666;
|
||||
}
|
||||
|
||||
.button-container {
|
||||
text-align: right;
|
||||
padding-top: 10px;
|
||||
}
|
||||
|
||||
.submit-button {
|
||||
background-color: #e0e0e0;
|
||||
border: 2px outset #e0e0e0;
|
||||
padding: 4px 12px;
|
||||
font-size: 12px;
|
||||
font-family: Arial, sans-serif;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.submit-button:hover {
|
||||
background-color: #d0d0d0;
|
||||
}
|
||||
|
||||
.submit-button:active {
|
||||
border: 2px inset #e0e0e0;
|
||||
}
|
||||
|
||||
/* Footer */
|
||||
.footer {
|
||||
margin-top: 30px;
|
||||
font-size: 10px;
|
||||
color: #666;
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
.footer p {
|
||||
margin: 2px 0;
|
||||
}
|
||||
|
||||
/* Responsive Design */
|
||||
@media (max-width: 600px) {
|
||||
body {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.form-table {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.label-cell {
|
||||
width: auto;
|
||||
text-align: left;
|
||||
padding-right: 5px;
|
||||
display: block;
|
||||
padding-bottom: 5px;
|
||||
}
|
||||
|
||||
.input-cell {
|
||||
display: block;
|
||||
padding-bottom: 15px;
|
||||
}
|
||||
|
||||
.form-table input[type="text"],
|
||||
.form-table input[type="password"] {
|
||||
width: 100%;
|
||||
max-width: 300px;
|
||||
}
|
||||
|
||||
.form-table tr {
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.form-table td {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,103 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Change account password - SME Server</title>
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>Change account password</h1>
|
||||
|
||||
<div class="instructions">
|
||||
<p>To change your account password, please fill out the following form. You will need to provide the name of your account, your old password, and your desired new password. (You must type the new password twice.)</p>
|
||||
|
||||
<p>If you cannot change your password because you have forgotten the old one, your local system administrator can reset your password using the <em>server manager</em>.</p>
|
||||
</div>
|
||||
|
||||
<!-- Flash messages -->
|
||||
{% with messages = get_flashed_messages(with_categories=true) %}
|
||||
{% if messages %}
|
||||
<div class="messages">
|
||||
{% for category, message in messages %}
|
||||
<div class="message {{ category }}">{{ message }}</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
|
||||
<form method="POST" class="password-form">
|
||||
<div class="form-container">
|
||||
<table class="form-table">
|
||||
<tr>
|
||||
<td class="label-cell">Your account:</td>
|
||||
<td class="input-cell">
|
||||
<input type="text" name="username" id="username"
|
||||
value="{{ request.form.username if request.form.username else '' }}"
|
||||
required>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="label-cell">Old password:</td>
|
||||
<td class="input-cell">
|
||||
<input type="password" name="old_password" id="old_password" required>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="label-cell">New password:</td>
|
||||
<td class="input-cell">
|
||||
<input type="password" name="new_password" id="new_password" required>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="label-cell">New password (verify):</td>
|
||||
<td class="input-cell">
|
||||
<input type="password" name="verify_password" id="verify_password" required>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<div class="button-container">
|
||||
<input type="submit" value="Change Password" class="submit-button">
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div class="footer">
|
||||
<p>{{ version if version else 'SME Server 11 (beta1)' }}</p>
|
||||
<p>{{ copyright_info.mitel if copyright_info else 'Copyright 1999-2006 Mitel Corporation' }}</p>
|
||||
<p>{{ copyright_info.rights if copyright_info else 'All rights reserved.' }}</p>
|
||||
<p>{{ copyright_info.koozali if copyright_info else 'Copyright (C) 2013 - 2021 Koozali Foundation Inc.' }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Simple client-side validation
|
||||
document.querySelector('.password-form').addEventListener('submit', function(e) {
|
||||
const newPassword = document.getElementById('new_password').value;
|
||||
const verifyPassword = document.getElementById('verify_password').value;
|
||||
|
||||
if (newPassword !== verifyPassword) {
|
||||
e.preventDefault();
|
||||
alert('New password and verification do not match');
|
||||
return false;
|
||||
}
|
||||
|
||||
if (newPassword.length < 8) {
|
||||
e.preventDefault();
|
||||
alert('Password must be at least 8 characters long');
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
// Clear password fields on page load for security
|
||||
window.addEventListener('load', function() {
|
||||
document.getElementById('old_password').value = '';
|
||||
document.getElementById('new_password').value = '';
|
||||
document.getElementById('verify_password').value = '';
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
Reference in New Issue
Block a user