Support file uploads for setting finger status
This commit is contained in:
@@ -1,12 +1,29 @@
|
|||||||
from flask import Flask, render_template, request, jsonify, redirect, url_for, flash
|
from flask import Flask, render_template, request, jsonify, redirect, url_for, flash
|
||||||
|
from flask_httpauth import HTTPBasicAuth
|
||||||
|
from werkzeug.utils import secure_filename
|
||||||
import os
|
import os
|
||||||
import subprocess
|
import subprocess
|
||||||
import shlex
|
import shlex
|
||||||
|
import datetime
|
||||||
from config import Config
|
from config import Config
|
||||||
|
|
||||||
app = Flask(__name__)
|
app = Flask(__name__)
|
||||||
app.config.from_object(Config)
|
app.config.from_object(Config)
|
||||||
|
|
||||||
|
# Initialize HTTP Basic Auth
|
||||||
|
auth = HTTPBasicAuth()
|
||||||
|
|
||||||
|
@auth.verify_password
|
||||||
|
def verify_password(username, password):
|
||||||
|
"""Verify basic authentication credentials against multiple users"""
|
||||||
|
return (username in app.config['BASIC_AUTH_USERS'] and
|
||||||
|
app.config['BASIC_AUTH_USERS'][username] == password)
|
||||||
|
|
||||||
|
def allowed_file(filename):
|
||||||
|
"""Check if file extension is allowed"""
|
||||||
|
return ('.' in filename and
|
||||||
|
filename.rsplit('.', 1)[1].lower() in app.config['ALLOWED_EXTENSIONS'])
|
||||||
|
|
||||||
@app.route('/')
|
@app.route('/')
|
||||||
def index():
|
def index():
|
||||||
"""Home page route"""
|
"""Home page route"""
|
||||||
@@ -117,11 +134,148 @@ def api_info():
|
|||||||
'/finger',
|
'/finger',
|
||||||
'/finger/<username>',
|
'/finger/<username>',
|
||||||
'/api/hello',
|
'/api/hello',
|
||||||
'/api/info'
|
'/api/info',
|
||||||
|
'/api/upload'
|
||||||
],
|
],
|
||||||
'framework': 'Flask'
|
'framework': 'Flask'
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@app.route('/api/upload', methods=['POST'])
|
||||||
|
@auth.login_required
|
||||||
|
def upload_file():
|
||||||
|
"""File upload endpoint with basic authentication and SCP transfer"""
|
||||||
|
try:
|
||||||
|
# Extract authenticated username
|
||||||
|
authenticated_user = auth.current_user()
|
||||||
|
|
||||||
|
# Check if the post request has the file part
|
||||||
|
if 'file' not in request.files:
|
||||||
|
return jsonify({
|
||||||
|
'error': 'No file part in the request',
|
||||||
|
'status': 'error'
|
||||||
|
}), 400
|
||||||
|
|
||||||
|
file = request.files['file']
|
||||||
|
|
||||||
|
# Check if user selected a file
|
||||||
|
if file.filename == '':
|
||||||
|
return jsonify({
|
||||||
|
'error': 'No file selected',
|
||||||
|
'status': 'error'
|
||||||
|
}), 400
|
||||||
|
|
||||||
|
# Check if file is allowed
|
||||||
|
if not allowed_file(file.filename):
|
||||||
|
return jsonify({
|
||||||
|
'error': f'File type not allowed. Allowed types: {", ".join(app.config["ALLOWED_EXTENSIONS"])}',
|
||||||
|
'status': 'error'
|
||||||
|
}), 400
|
||||||
|
|
||||||
|
if file:
|
||||||
|
# Generate secure filename with timestamp
|
||||||
|
original_filename = secure_filename(file.filename)
|
||||||
|
timestamp = datetime.datetime.now().strftime('%Y%m%d_%H%M%S')
|
||||||
|
filename = f"{timestamp}_{original_filename}"
|
||||||
|
|
||||||
|
# Ensure upload directory exists
|
||||||
|
upload_folder = app.config['UPLOAD_FOLDER']
|
||||||
|
if not os.path.exists(upload_folder):
|
||||||
|
os.makedirs(upload_folder)
|
||||||
|
|
||||||
|
# Save file locally
|
||||||
|
file_path = os.path.join(upload_folder, filename)
|
||||||
|
file.save(file_path)
|
||||||
|
|
||||||
|
# Get file info
|
||||||
|
file_size = os.path.getsize(file_path)
|
||||||
|
|
||||||
|
# Initialize response data
|
||||||
|
response_data = {
|
||||||
|
'message': 'File uploaded successfully',
|
||||||
|
'status': 'success',
|
||||||
|
'file_info': {
|
||||||
|
'original_filename': original_filename,
|
||||||
|
'saved_filename': filename,
|
||||||
|
'local_file_path': file_path,
|
||||||
|
'file_size': file_size,
|
||||||
|
'upload_time': datetime.datetime.now().isoformat()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# SCP file to remote server if enabled
|
||||||
|
if app.config['SCP_ENABLED']:
|
||||||
|
try:
|
||||||
|
remote_file_path = os.path.join(app.config['REMOTE_PATH'], authenticated_user).replace('\\', '/')
|
||||||
|
scp_destination = f"{app.config['REMOTE_USER']}@{app.config['REMOTE_HOST']}:{remote_file_path}"
|
||||||
|
|
||||||
|
# Build SCP command
|
||||||
|
scp_cmd = [
|
||||||
|
'scp',
|
||||||
|
'-P', str(app.config['REMOTE_PORT']),
|
||||||
|
'-o', 'StrictHostKeyChecking=no',
|
||||||
|
'-o', 'UserKnownHostsFile=/dev/null'
|
||||||
|
]
|
||||||
|
|
||||||
|
# Add private key if specified
|
||||||
|
if app.config['REMOTE_PRIVATE_KEY']:
|
||||||
|
scp_cmd.extend(['-i', app.config['REMOTE_PRIVATE_KEY']])
|
||||||
|
|
||||||
|
# Add source and destination
|
||||||
|
scp_cmd.extend([file_path, scp_destination])
|
||||||
|
|
||||||
|
# Execute SCP command
|
||||||
|
scp_process = subprocess.run(
|
||||||
|
scp_cmd,
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
timeout=30
|
||||||
|
)
|
||||||
|
|
||||||
|
if scp_process.returncode == 0:
|
||||||
|
response_data['scp_info'] = {
|
||||||
|
'remote_host': app.config['REMOTE_HOST'],
|
||||||
|
'remote_path': remote_file_path,
|
||||||
|
'scp_status': 'success',
|
||||||
|
'scp_message': 'File successfully transferred to remote server'
|
||||||
|
}
|
||||||
|
response_data['message'] = 'File uploaded and transferred to remote server successfully'
|
||||||
|
else:
|
||||||
|
response_data['scp_info'] = {
|
||||||
|
'scp_status': 'failed',
|
||||||
|
'scp_error': scp_process.stderr or 'SCP transfer failed',
|
||||||
|
'scp_message': 'File uploaded locally but remote transfer failed'
|
||||||
|
}
|
||||||
|
response_data['message'] = 'File uploaded locally but remote transfer failed'
|
||||||
|
|
||||||
|
except subprocess.TimeoutExpired:
|
||||||
|
response_data['scp_info'] = {
|
||||||
|
'scp_status': 'timeout',
|
||||||
|
'scp_error': 'SCP transfer timed out',
|
||||||
|
'scp_message': 'File uploaded locally but remote transfer timed out'
|
||||||
|
}
|
||||||
|
response_data['message'] = 'File uploaded locally but remote transfer timed out'
|
||||||
|
|
||||||
|
except Exception as scp_error:
|
||||||
|
response_data['scp_info'] = {
|
||||||
|
'scp_status': 'error',
|
||||||
|
'scp_error': str(scp_error),
|
||||||
|
'scp_message': 'File uploaded locally but SCP failed'
|
||||||
|
}
|
||||||
|
response_data['message'] = 'File uploaded locally but SCP failed'
|
||||||
|
else:
|
||||||
|
response_data['scp_info'] = {
|
||||||
|
'scp_status': 'disabled',
|
||||||
|
'scp_message': 'SCP transfer is disabled'
|
||||||
|
}
|
||||||
|
|
||||||
|
return jsonify(response_data), 200
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
return jsonify({
|
||||||
|
'error': f'Upload failed: {str(e)}',
|
||||||
|
'status': 'error'
|
||||||
|
}), 500
|
||||||
|
|
||||||
@app.errorhandler(404)
|
@app.errorhandler(404)
|
||||||
def not_found_error(error):
|
def not_found_error(error):
|
||||||
"""Handle 404 errors"""
|
"""Handle 404 errors"""
|
||||||
|
|||||||
@@ -15,3 +15,27 @@ class Config:
|
|||||||
MAIL_USE_TLS = os.environ.get('MAIL_USE_TLS', 'true').lower() in ['true', 'on', '1']
|
MAIL_USE_TLS = os.environ.get('MAIL_USE_TLS', 'true').lower() in ['true', 'on', '1']
|
||||||
MAIL_USERNAME = os.environ.get('MAIL_USERNAME')
|
MAIL_USERNAME = os.environ.get('MAIL_USERNAME')
|
||||||
MAIL_PASSWORD = os.environ.get('MAIL_PASSWORD')
|
MAIL_PASSWORD = os.environ.get('MAIL_PASSWORD')
|
||||||
|
|
||||||
|
# File upload configuration
|
||||||
|
MAX_CONTENT_LENGTH = int(os.environ.get('MAX_FILE_SIZE', 16 * 1024 * 1024)) # 16MB default
|
||||||
|
UPLOAD_FOLDER = '/tmp'
|
||||||
|
ALLOWED_EXTENSIONS = {'txt'}
|
||||||
|
|
||||||
|
# Remote server configuration for SCP
|
||||||
|
REMOTE_HOST = os.environ.get('REMOTE_HOST') or 'example.com'
|
||||||
|
REMOTE_USER = os.environ.get('REMOTE_USER') or 'user'
|
||||||
|
REMOTE_PATH = os.environ.get('REMOTE_PATH') or '/home/user/uploads/'
|
||||||
|
REMOTE_PORT = int(os.environ.get('REMOTE_PORT', 22))
|
||||||
|
REMOTE_PRIVATE_KEY = os.environ.get('REMOTE_PRIVATE_KEY') # Path to SSH private key file
|
||||||
|
SCP_ENABLED = os.environ.get('SCP_ENABLED', 'false').lower() == 'true'
|
||||||
|
|
||||||
|
# Basic authentication credentials - multiple users support
|
||||||
|
# Environment variable format: "user1:pass1,user2:pass2,user3:pass3"
|
||||||
|
BASIC_AUTH_USERS_STR = os.environ.get('BASIC_AUTH_USERS') or 'admin:password,uploader:upload123,user:user123'
|
||||||
|
|
||||||
|
# Parse users string into dictionary
|
||||||
|
BASIC_AUTH_USERS = {}
|
||||||
|
for user_pass in BASIC_AUTH_USERS_STR.split(','):
|
||||||
|
if ':' in user_pass:
|
||||||
|
username, password = user_pass.strip().split(':', 1) # Split only on first colon
|
||||||
|
BASIC_AUTH_USERS[username.strip()] = password.strip()
|
||||||
|
|||||||
@@ -5,3 +5,4 @@ MarkupSafe==2.1.3
|
|||||||
itsdangerous==2.1.2
|
itsdangerous==2.1.2
|
||||||
click==8.1.7
|
click==8.1.7
|
||||||
blinker==1.6.3
|
blinker==1.6.3
|
||||||
|
Flask-HTTPAuth==4.8.0
|
||||||
|
|||||||
Reference in New Issue
Block a user