Erreur de frappe dans un des dossiers

This commit is contained in:
cedrick2711
2026-08-04 12:57:18 -04:00
parent 0f7788e973
commit 0dd4ecdb4c
7 changed files with 56 additions and 466 deletions
+1
View File
@@ -0,0 +1 @@
# supporting scripts package
+184
View File
@@ -0,0 +1,184 @@
"""Database backup script for the Team Tryouts application.
This module provides a simple backup mechanism for the SQLite database
and uploaded contract documents. Designed to be run as a scheduled task
(Windows Task Scheduler) or cron job.
Usage:
python backup.py
Configuration via environment variables:
BACKUP_DIR: Directory to store backups (default: ./backups)
BACKUP_RETENTION_DAYS: Number of days to keep backups (default: 30)
"""
import os
import shutil
import sqlite3
from datetime import datetime, timedelta
# Configuration
BACKUP_DIR = os.getenv('BACKUP_DIR', os.path.join(os.getcwd(), 'backups'))
BACKUP_RETENTION_DAYS = int(os.getenv('BACKUP_RETENTION_DAYS', 30))
DATABASE_PATH = os.getenv('DATABASE_PATH', os.path.join(os.getcwd(), 'instance', 'team_tryouts.db'))
DOCUMENTS_DIR = os.path.join(os.getcwd(), 'documents')
def create_backup_dir():
"""Create the backup directory if it doesn't exist."""
os.makedirs(BACKUP_DIR, exist_ok=True)
def backup_database():
"""Backup the SQLite database using sqlite3's built-in backup API.
Returns:
str: Path to the created backup file, or None if failed.
"""
if not os.path.exists(DATABASE_PATH):
print(f'[WARNING] Database not found at {DATABASE_PATH}. Skipping database backup.')
return None
timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
backup_filename = f'db_backup_{timestamp}.db'
backup_path = os.path.join(BACKUP_DIR, backup_filename)
try:
source = sqlite3.connect(DATABASE_PATH)
destination = sqlite3.connect(backup_path)
source.backup(destination)
source.close()
destination.close()
print(f'[OK] Database backed up to: {backup_path}')
return backup_path
except Exception as e:
print(f'[ERROR] Database backup failed: {e}')
return None
def backup_documents():
"""Backup the uploaded contract documents directory.
Returns:
str: Path to the created archive, or None if no documents exist.
"""
if not os.path.exists(DOCUMENTS_DIR):
print('[INFO] No documents directory found. Skipping document backup.')
return None
timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
archive_basename = f'documents_backup_{timestamp}'
archive_path = os.path.join(BACKUP_DIR, archive_basename)
try:
shutil.make_archive(archive_path, 'zip', DOCUMENTS_DIR)
zip_path = f'{archive_path}.zip'
print(f'[OK] Documents backed up to: {zip_path}')
return zip_path
except Exception as e:
print(f'[ERROR] Document backup failed: {e}')
return None
def cleanup_old_backups():
"""Remove backup files older than BACKUP_RETENTION_DAYS."""
if not os.path.exists(BACKUP_DIR):
return
cutoff = datetime.now() - timedelta(days=BACKUP_RETENTION_DAYS)
removed_count = 0
for filename in os.listdir(BACKUP_DIR):
file_path = os.path.join(BACKUP_DIR, filename)
if os.path.isfile(file_path):
file_time = datetime.fromtimestamp(os.path.getmtime(file_path))
if file_time < cutoff:
try:
os.remove(file_path)
removed_count += 1
print(f'[CLEANUP] Removed old backup: {filename}')
except OSError as e:
print(f'[WARNING] Could not remove {filename}: {e}')
if removed_count > 0:
print(f'[CLEANUP] Removed {removed_count} old backup(s).')
else:
print('[CLEANUP] No old backups to remove.')
def verify_backup(backup_path):
"""Verify a database backup by running a quick integrity check.
Args:
backup_path: Path to the backup file to verify.
Returns:
bool: True if backup is valid, False otherwise.
"""
if not backup_path or not os.path.exists(backup_path):
return False
try:
conn = sqlite3.connect(backup_path)
cursor = conn.cursor()
cursor.execute('PRAGMA integrity_check')
result = cursor.fetchone()
conn.close()
is_valid = result[0] == 'ok'
if is_valid:
print(f'[OK] Backup integrity verified: {backup_path}')
else:
print(f'[ERROR] Backup integrity check failed: {backup_path} - {result[0]}')
return is_valid
except Exception as e:
print(f'[ERROR] Backup verification failed: {e}')
return False
def main():
"""Run the full backup process.
Steps:
1. Create backup directory
2. Backup database
3. Backup documents (if any)
4. Verify database backup
5. Clean up old backups
Returns:
int: 0 on success, 1 on failure.
"""
print(f'=== Team Tryouts Backup ===')
print(f'Started at: {datetime.now().strftime("%Y-%m-%d %H:%M:%S")}')
print(f'Backup directory: {BACKUP_DIR}')
print(f'Retention period: {BACKUP_RETENTION_DAYS} days')
print()
create_backup_dir()
# 1. Backup database
db_backup_path = backup_database()
success = True
# 2. Verify database backup
if db_backup_path:
if not verify_backup(db_backup_path):
success = False
# 3. Backup documents
backup_documents()
# 4. Cleanup old backups
cleanup_old_backups()
print()
if success:
print('=== Backup completed successfully ===')
else:
print('=== Backup completed with warnings ===')
return 0 if success else 1
if __name__ == '__main__':
exit(main())
+129
View File
@@ -0,0 +1,129 @@
"""Development HTTPS server for testing production settings locally.
Generates a self-signed certificate (if not present) and runs the
application via Waitress wrapped in a TLS socket. This simulates the
production environment where Nginx handles TLS termination.
Usage:
python run_https.py
The server will listen on https://localhost:8443
Accept the self-signed certificate warning in your browser to proceed.
"""
import os
import socket
import subprocess
import ssl
import sys
from waitress.server import create_server
from app.app import create_app
CERT_FILE = 'certs/localhost.pem'
KEY_FILE = 'certs/localhost-key.pem'
def generate_self_signed_cert():
"""Generate a self-signed certificate for local HTTPS testing.
Uses OpenSSL to create a key and certificate valid for 365 days.
Skips generation if certificate files already exist.
"""
if os.path.exists(CERT_FILE) and os.path.exists(KEY_FILE):
print('[OK] Self-signed certificate already exists.')
return
os.makedirs('certs', exist_ok=True)
print('[INFO] Generating self-signed certificate for localhost...')
try:
subprocess.run([
'openssl', 'req', '-x509', '-newkey', 'rsa:2048',
'-keyout', KEY_FILE,
'-out', CERT_FILE,
'-days', '365',
'-nodes',
'-subj', '/CN=localhost'
], check=True, capture_output=True)
print('[OK] Certificate generated: certs/localhost.pem')
except FileNotFoundError:
print('[ERROR] OpenSSL not found. Install OpenSSL or use:')
print(' winget install OpenSSL.OpenSSL')
print(' OR download from https://slproweb.com/products/Win32OpenSSL.html')
sys.exit(1)
except subprocess.CalledProcessError as e:
print(f'[ERROR] Certificate generation failed: {e}')
sys.exit(1)
def main():
"""Start the HTTPS development server."""
port = int(os.getenv('HTTPS_PORT', 8443))
host = os.getenv('HOST', '127.0.0.1')
# Ensure certificate exists
generate_self_signed_cert()
# Create the Flask application
app = create_app()
# Create SSL context
context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
context.load_cert_chain(CERT_FILE, KEY_FILE)
# Create a TCP socket, wrap it with TLS, then pass to Waitress
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.bind((host, port))
sock.listen(5)
# Wrap the socket with TLS
ssl_sock = context.wrap_socket(sock, server_side=True)
# =====================================================================
# WSGI Middleware: Tell Flask the connection is HTTPS
#
# Waitress doesn't know the underlying socket is TLS, so Flask sees
# wsgi.url_scheme = "http". Without this middleware, force_https()
# would cause an infinite redirect loop (ERR_TOO_MANY_REDIRECTS).
# =====================================================================
class ForceHTTPSMiddleware:
"""WSGI middleware that sets url_scheme to 'https'.
Since we're wrapping the TCP socket with SSL before passing it
to Waitress, Flask's request.is_secure returns False because
Waitress reports wsgi.url_scheme='http'. This middleware fixes
that so Flask correctly identifies the connection as HTTPS.
"""
def __init__(self, wsgi_app):
self.wsgi_app = wsgi_app
def __call__(self, environ, start_response):
environ['wsgi.url_scheme'] = 'https'
environ['HTTPS'] = 'on'
return self.wsgi_app(environ, start_response)
# Wrap the Flask app with the HTTPS middleware
app.wsgi_app = ForceHTTPSMiddleware(app.wsgi_app)
print(f'\n╔══════════════════════════════════════════════════════╗')
print(f'║ TEAM TRYOUTS - Development HTTPS Server ║')
print(f'╠══════════════════════════════════════════════════════╣')
print(f'║ URL: https://{host}:{port}')
print(f'║ Cert: self-signed (accept browser warning) ║')
print(f'║ Press Ctrl+C to stop ║')
print(f'╚══════════════════════════════════════════════════════╝\n')
# Create Waitress server with the SSL-wrapped socket
server = create_server(
app,
sockets=[ssl_sock],
threads=4,
channel_timeout=30,
)
server.run()
if __name__ == '__main__':
main()
+372
View File
@@ -0,0 +1,372 @@
"""Security validation script for the Team Tryouts application.
This script performs pre-deployment security checks to validate:
- HTTP security headers
- Cookie security attributes
- Debug mode status
- HTTPS configuration
- Dependency vulnerabilities
- Database connectivity
Usage:
python security_scan.py [--url http://localhost:5000]
"""
import os
import sys
import json
import subprocess
import urllib.request
import ssl
from datetime import datetime
def check_environment():
"""Check required environment variables are set.
Returns:
bool: True if all critical variables are set.
"""
print('=' * 60)
print('1. ENVIRONMENT VARIABLES CHECK')
print('=' * 60)
critical_vars = ['SECRET_KEY']
recommended_vars = ['DATABASE_URL', 'CORS_ALLOWED_ORIGINS']
all_ok = True
for var in critical_vars:
value = os.getenv(var)
if value:
# Check SECRET_KEY is not a default/weak value
if var == 'SECRET_KEY' and len(value) < 32:
print(f'[WARN] {var} is set but too short (less than 32 chars)')
all_ok = False
else:
print(f'[OK] {var} is set')
else:
print(f'[FAIL] {var} is not set!')
all_ok = False
for var in recommended_vars:
value = os.getenv(var)
if value:
print(f'[OK] {var} is set')
else:
print(f'[INFO] {var} is not set (using default)')
# Check FLASK_DEBUG
debug = os.getenv('FLASK_DEBUG', 'false').lower()
if debug == 'true':
print('[WARN] FLASK_DEBUG is enabled! Should be disabled in production.')
else:
print('[OK] FLASK_DEBUG is disabled')
return all_ok
def check_https_headers(url):
"""Check HTTP security headers from a running application.
Args:
url: The base URL of the application to check.
Returns:
bool: True if all critical headers are present.
"""
print('\n' + '=' * 60)
print('2. HTTP SECURITY HEADERS CHECK')
print('=' * 60)
required_headers = {
'Strict-Transport-Security': 'HSTS enabled',
'X-Content-Type-Options': 'Prevents MIME sniffing',
'X-Frame-Options': 'Prevents clickjacking',
'Content-Security-Policy': 'CSP configured',
'Referrer-Policy': 'Referrer control',
'Permissions-Policy': 'Permissions control',
'Cross-Origin-Opener-Policy': 'Cross-origin isolation',
}
all_ok = True
try:
# Create a context that doesn't verify SSL (for local testing)
ctx = ssl.create_default_context()
ctx.check_hostname = False
ctx.verify_mode = ssl.CERT_NONE
req = urllib.request.Request(url, method='HEAD')
try:
with urllib.request.urlopen(req, context=ctx, timeout=10) as response:
headers = response.headers
status = response.status
print(f'[INFO] Response status: {status}')
for header, description in required_headers.items():
if header in headers:
print(f'[OK] {header}: {description}')
else:
print(f'[FAIL] {header} is missing: {description}')
all_ok = False
# Check cookie attributes if any set-cookie headers exist
if 'Set-Cookie' in headers:
cookie = headers['Set-Cookie']
if 'Secure' in cookie:
print('[OK] Cookies have Secure flag')
else:
print('[WARN] Cookies missing Secure flag')
all_ok = False
if 'HttpOnly' in cookie:
print('[OK] Cookies have HttpOnly flag')
else:
print('[WARN] Cookies missing HttpOnly flag')
all_ok = False
if 'SameSite' in cookie:
print(f'[OK] Cookies have SameSite={cookie.split("SameSite=")[1].split(";")[0] if "SameSite=" in cookie else "?"}')
else:
print('[WARN] Cookies missing SameSite attribute')
all_ok = False
else:
print('[INFO] No Set-Cookie headers in response')
except urllib.error.HTTPError as e:
print(f'[INFO] Got HTTP {e.code} (may need authentication)')
# Still check headers even on error responses
for header, description in required_headers.items():
if header in e.headers:
print(f'[OK] {header}: {description}')
else:
print(f'[FAIL] {header} is missing: {description}')
all_ok = False
except urllib.error.URLError as e:
print(f'[SKIP] Cannot connect to {url}: {e.reason}')
print('[SKIP] Run with --url <application_url> to check headers')
return True # Not a failure, just can't check
return all_ok
def check_dependencies():
"""Run pip-audit to check for known vulnerabilities.
Returns:
bool: True if no critical vulnerabilities found.
"""
print('\n' + '=' * 60)
print('3. DEPENDENCY VULNERABILITY SCAN')
print('=' * 60)
try:
result = subprocess.run(
[sys.executable, '-m', 'pip_audit', '--format', 'json'],
capture_output=True,
text=True,
timeout=60
)
if result.returncode == 0:
print('[OK] No known vulnerabilities found')
return True
else:
try:
data = json.loads(result.stdout)
vulns = data.get('dependencies', [])
if vulns:
for vuln in vulns:
print(f'[FAIL] {vuln["name"]}=={vuln["version"]}: {vuln.get("description", "Vulnerability found")}')
return False
else:
print('[OK] No vulnerabilities found')
return True
except json.JSONDecodeError:
if result.stdout:
print(f'[INFO] {result.stdout.strip()}')
if result.stderr:
print(f'[WARN] {result.stderr.strip()}')
return True
except FileNotFoundError:
print('[SKIP] pip-audit not installed. Run: pip install pip-audit')
return True
except subprocess.TimeoutExpired:
print('[WARN] pip-audit timed out')
return True
def check_file_permissions():
"""Check for common security issues in the project structure.
Returns:
bool: True if no critical issues found.
"""
print('\n' + '=' * 60)
print('4. PROJECT FILES CHECK')
print('=' * 60)
all_ok = True
# Check .gitignore exists and contains important patterns
gitignore_path = os.path.join(os.getcwd(), '.gitignore')
if os.path.exists(gitignore_path):
required_patterns = ['.env', 'instance/', '*.db', '*.log']
with open(gitignore_path, 'r') as f:
content = f.read()
for pattern in required_patterns:
if pattern in content:
print(f'[OK] .gitignore contains: {pattern}')
else:
print(f'[WARN] .gitignore missing: {pattern}')
all_ok = False
else:
print('[FAIL] .gitignore file not found!')
all_ok = False
# Check for .env in working directory (should NOT be committed)
env_path = os.path.join(os.getcwd(), '.env')
if os.path.exists(env_path):
print('[INFO] .env file exists (ensure it is NOT committed)')
else:
print('[WARN] No .env file found')
# Check for leftover .pyc or __pycache__
pycache_count = 0
for root, dirs, files in os.walk(os.getcwd()):
if '__pycache__' in dirs:
pycache_count += 1
for f in files:
if f.endswith('.pyc'):
pycache_count += 1
if pycache_count == 0:
print('[OK] No __pycache__ or .pyc files found')
else:
print(f'[INFO] Found {pycache_count} cache files/dirs (should be in .gitignore)')
return all_ok
def check_flask_config():
"""Check Flask application configuration for security.
Returns:
bool: True if configuration looks secure.
"""
print('\n' + '=' * 60)
print('5. FLASK CONFIGURATION CHECK')
print('=' * 60)
all_ok = True
try:
from app.app import create_app
app = create_app()
# Check session cookie settings
cookie_checks = [
('SESSION_COOKIE_SECURE', True, 'Secure cookies'),
('SESSION_COOKIE_HTTPONLY', True, 'HttpOnly cookies'),
('PERMANENT_SESSION_LIFETIME', 3600, 'Session timeout'),
]
for config_key, expected, description in cookie_checks:
value = app.config.get(config_key)
if config_key == 'PERMANENT_SESSION_LIFETIME':
if value and value <= 3600:
print(f'[OK] {description}: {value}s')
else:
print(f'[WARN] {description}: {value}s (should be <= 1 hour)')
all_ok = False
elif value == expected:
print(f'[OK] {description}: enabled')
else:
print(f'[FAIL] {description}: {value}')
all_ok = False
# Check MAX_CONTENT_LENGTH
max_content = app.config.get('MAX_CONTENT_LENGTH')
if max_content:
mb = max_content / (1024 * 1024)
print(f'[OK] MAX_CONTENT_LENGTH: {mb}MB')
else:
print('[WARN] MAX_CONTENT_LENGTH not set (unlimited uploads)')
all_ok = False
# Check CSRF
csrf_enabled = app.config.get('WTF_CSRF_ENABLED')
if csrf_enabled:
print('[OK] CSRF protection: enabled')
else:
print('[FAIL] CSRF protection: disabled')
all_ok = False
# Check if app is in DEBUG mode
if app.debug:
print('[FAIL] DEBUG mode is enabled!')
all_ok = False
else:
print('[OK] DEBUG mode: disabled')
except Exception as e:
print(f'[SKIP] Cannot check Flask config: {e}')
return all_ok
def main():
"""Run all security checks and produce a summary report.
Returns:
int: 0 if all checks pass, 1 if any fail.
"""
import argparse
parser = argparse.ArgumentParser(description='Security validation scanner')
parser.add_argument('--url', default='http://localhost:5000',
help='Application URL to check headers (default: http://localhost:5000)')
args = parser.parse_args()
print('╔══════════════════════════════════════════════════════════╗')
print('║ TEAM TRYOUTS - SECURITY VALIDATION SCANNER ║')
print('╠══════════════════════════════════════════════════════════╣')
print(f'║ Time: {datetime.now().strftime("%Y-%m-%d %H:%M:%S")}')
print('╚══════════════════════════════════════════════════════════╝')
checks = [
check_environment,
lambda: check_https_headers(args.url),
check_dependencies,
check_file_permissions,
check_flask_config,
]
results = []
for check in checks:
results.append(check())
print('\n' + '=' * 60)
print('SUMMARY')
print('=' * 60)
passed = sum(1 for r in results if r)
failed = sum(1 for r in results if not r)
total = len(results)
print(f'Passed: {passed}/{total}')
print(f'Failed: {failed}/{total}')
if failed == 0:
print('\n[OK] All security checks passed!')
return 0
else:
print(f'\n[WARN] {failed} check(s) failed. Review the output above.')
return 1
if __name__ == '__main__':
sys.exit(main())
+56
View File
@@ -0,0 +1,56 @@
"""Database seeding script for Team Tryouts application.
Wipes all data and creates an admin user. Run manually with:
python -m app.supporting_scripts.seed
"""
from app.extensions import db, hash_password
from app.models import Admin
def seed_database():
"""Delete all existing data and create a single admin account."""
# Delete all data in FK-safe order
print("Deleting existing data...")
tables = [
'one_on_one_requests',
'player_disponibilities', 'coach_availabilities',
'match_participants', 'team_match_participants',
'matches', 'team_matches',
'team_players', 'team_members', 'teams',
'evaluations', 'tryout_registrations', 'tryouts',
'org_team_coaches', 'org_team_managers',
'team_notes', 'personal_notes',
'user_gamertags',
'org_teams',
'users',
]
for table in tables:
db.session.execute(db.text(f'DELETE FROM {table}'))
db.session.commit()
print("[OK] All data deleted.")
# Create the admin user
admin = Admin(
username='admin',
password_hash=hash_password('password'),
role='admin',
full_name='Admin',
email='[email protected]',
)
db.session.add(admin)
db.session.commit()
print("[OK] Created admin user")
print("\n=== Admin Credentials ===")
print("Username: admin")
print("Password: password")
print("\n[SUCCESS] Database reset and seeded!")
if __name__ == '__main__':
from app.app import create_app
app = create_app()
with app.app_context():
seed_database()