Merge branch 'master' of https://github.com/nguyen-ngo/QR_Code_Management
This commit is contained in:
@@ -27,6 +27,24 @@ from turnstile_utils import turnstile_utils
|
|||||||
from db_performance_optimization import initialize_performance_optimizations
|
from db_performance_optimization import initialize_performance_optimizations
|
||||||
from app_performance_middleware import PerformanceMonitor
|
from app_performance_middleware import PerformanceMonitor
|
||||||
|
|
||||||
|
from utils.validation import (
|
||||||
|
is_valid_role,
|
||||||
|
has_admin_privileges,
|
||||||
|
has_staff_level_access,
|
||||||
|
has_export_permissions,
|
||||||
|
get_role_permissions,
|
||||||
|
VALID_ROLES,
|
||||||
|
STAFF_LEVEL_ROLES
|
||||||
|
)
|
||||||
|
|
||||||
|
from utils.auth_decorators import (
|
||||||
|
login_required,
|
||||||
|
admin_required,
|
||||||
|
staff_or_admin_required,
|
||||||
|
project_manager_required,
|
||||||
|
payroll_required
|
||||||
|
)
|
||||||
|
|
||||||
# Initialize Flask application
|
# Initialize Flask application
|
||||||
app = Flask(__name__)
|
app = Flask(__name__)
|
||||||
app.config['SECRET_KEY'] = os.environ.get('SECRET_KEY')
|
app.config['SECRET_KEY'] = os.environ.get('SECRET_KEY')
|
||||||
@@ -211,12 +229,6 @@ def cache_coordinates(address, lat, lng, accuracy):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"⚠️ Error caching coordinates: {e}")
|
print(f"⚠️ Error caching coordinates: {e}")
|
||||||
|
|
||||||
# Valid user roles with new additions
|
|
||||||
VALID_ROLES = ['admin', 'staff', 'payroll', 'project_manager']
|
|
||||||
|
|
||||||
# Roles that have staff-level permissions (non-admin roles)
|
|
||||||
STAFF_LEVEL_ROLES = ['staff', 'payroll', 'project_manager']
|
|
||||||
|
|
||||||
# Import and initialize models
|
# Import and initialize models
|
||||||
from models import set_db
|
from models import set_db
|
||||||
User, QRCode, QRCodeStyle, Project, AttendanceData, Employee, TimeAttendance, UserProjectPermission, UserLocationPermission = set_db(db)
|
User, QRCode, QRCodeStyle, Project, AttendanceData, Employee, TimeAttendance, UserProjectPermission, UserLocationPermission = set_db(db)
|
||||||
@@ -224,82 +236,7 @@ User, QRCode, QRCodeStyle, Project, AttendanceData, Employee, TimeAttendance, Us
|
|||||||
# Initialize the logging system
|
# Initialize the logging system
|
||||||
logger_handler = AppLogger(app, db)
|
logger_handler = AppLogger(app, db)
|
||||||
|
|
||||||
# Utility functions
|
|
||||||
def is_valid_role(role):
|
|
||||||
"""Check if role is valid"""
|
|
||||||
return role in VALID_ROLES
|
|
||||||
|
|
||||||
def has_admin_privileges(role):
|
|
||||||
"""Check if role has admin privileges"""
|
|
||||||
return role == 'admin'
|
|
||||||
|
|
||||||
def has_staff_level_access(role):
|
|
||||||
"""Check if role has staff-level access (includes new roles)"""
|
|
||||||
return role in STAFF_LEVEL_ROLES
|
|
||||||
|
|
||||||
def get_role_permissions(role):
|
|
||||||
"""Get permissions description for a role"""
|
|
||||||
permissions = {
|
|
||||||
'admin': {
|
|
||||||
'title': 'Administrator Permissions',
|
|
||||||
'permissions': [
|
|
||||||
'Full QR code management (create, edit, delete)',
|
|
||||||
'Complete user management capabilities',
|
|
||||||
'System configuration access',
|
|
||||||
'View all system analytics',
|
|
||||||
'Bulk operations and data export',
|
|
||||||
'Access to all admin features'
|
|
||||||
],
|
|
||||||
'restrictions': ['With great power comes great responsibility!']
|
|
||||||
},
|
|
||||||
'staff': {
|
|
||||||
'title': 'Staff User Permissions',
|
|
||||||
'permissions': [
|
|
||||||
'Create and edit QR codes',
|
|
||||||
'View all QR codes in the system',
|
|
||||||
'Download QR code images',
|
|
||||||
'Update personal profile information',
|
|
||||||
],
|
|
||||||
'restrictions': [
|
|
||||||
'Cannot delete QR codes',
|
|
||||||
'Cannot manage other users',
|
|
||||||
'Cannot access admin settings'
|
|
||||||
]
|
|
||||||
},
|
|
||||||
'payroll': {
|
|
||||||
'title': 'Payroll Specialist Permissions',
|
|
||||||
'permissions': [
|
|
||||||
'Create and edit QR codes',
|
|
||||||
'View all QR codes in the system',
|
|
||||||
'Download QR code images',
|
|
||||||
'Update personal profile information',
|
|
||||||
'Access dashboard and reports',
|
|
||||||
'Same permissions as Staff (additional features coming soon)'
|
|
||||||
],
|
|
||||||
'restrictions': [
|
|
||||||
'Cannot delete QR codes',
|
|
||||||
'Cannot manage other users',
|
|
||||||
'Cannot access admin settings'
|
|
||||||
]
|
|
||||||
},
|
|
||||||
'project_manager': {
|
|
||||||
'title': 'Project Manager Permissions',
|
|
||||||
'permissions': [
|
|
||||||
'Create and edit QR codes',
|
|
||||||
'View all QR codes in the system',
|
|
||||||
'Download QR code images',
|
|
||||||
'Update personal profile information',
|
|
||||||
'Access dashboard and reports',
|
|
||||||
'Same permissions as Staff (additional features coming soon)'
|
|
||||||
],
|
|
||||||
'restrictions': [
|
|
||||||
'Cannot delete QR codes',
|
|
||||||
'Cannot manage other users',
|
|
||||||
'Cannot access admin settings'
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return permissions.get(role, {})
|
|
||||||
|
|
||||||
def log_google_maps_usage(operation_type):
|
def log_google_maps_usage(operation_type):
|
||||||
"""Log Google Maps API usage for monitoring"""
|
"""Log Google Maps API usage for monitoring"""
|
||||||
@@ -1354,49 +1291,6 @@ def format_time_interval(minutes):
|
|||||||
else:
|
else:
|
||||||
return f"{days}d {remaining_hours}h"
|
return f"{days}d {remaining_hours}h"
|
||||||
|
|
||||||
# Authentication decorator
|
|
||||||
def login_required(f):
|
|
||||||
"""Decorator to ensure user is logged in"""
|
|
||||||
@wraps(f)
|
|
||||||
def decorated_function(*args, **kwargs):
|
|
||||||
if 'user_id' not in session:
|
|
||||||
flash('Please log in to access this page.', 'error')
|
|
||||||
return redirect(url_for('login'))
|
|
||||||
return f(*args, **kwargs)
|
|
||||||
return decorated_function
|
|
||||||
|
|
||||||
def admin_required(f):
|
|
||||||
"""Decorator to ensure user has admin privileges"""
|
|
||||||
@wraps(f)
|
|
||||||
def decorated_function(*args, **kwargs):
|
|
||||||
if 'username' not in session:
|
|
||||||
flash('Please log in to access this page.', 'error')
|
|
||||||
return redirect(url_for('login'))
|
|
||||||
|
|
||||||
user_role = session.get('role')
|
|
||||||
if not has_admin_privileges(user_role):
|
|
||||||
flash('Administrator privileges required for this action.', 'error')
|
|
||||||
return redirect(url_for('dashboard'))
|
|
||||||
|
|
||||||
return f(*args, **kwargs)
|
|
||||||
return decorated_function
|
|
||||||
|
|
||||||
def staff_or_admin_required(f):
|
|
||||||
"""Decorator to ensure user has staff-level or admin privileges"""
|
|
||||||
@wraps(f)
|
|
||||||
def decorated_function(*args, **kwargs):
|
|
||||||
if 'username' not in session:
|
|
||||||
flash('Please log in to access this page.', 'error')
|
|
||||||
return redirect(url_for('login'))
|
|
||||||
|
|
||||||
user_role = session.get('role')
|
|
||||||
if not (has_admin_privileges(user_role) or has_staff_level_access(user_role)):
|
|
||||||
flash('Insufficient privileges to access this page.', 'error')
|
|
||||||
return redirect(url_for('dashboard'))
|
|
||||||
|
|
||||||
return f(*args, **kwargs)
|
|
||||||
return decorated_function
|
|
||||||
|
|
||||||
# Add this helper function to check admin requirements more safely
|
# Add this helper function to check admin requirements more safely
|
||||||
def is_admin_user(user_id):
|
def is_admin_user(user_id):
|
||||||
"""Helper function to safely check if user is admin"""
|
"""Helper function to safely check if user is admin"""
|
||||||
|
|||||||
@@ -0,0 +1,54 @@
|
|||||||
|
"""
|
||||||
|
Utilities Package for QR Attendance Management System
|
||||||
|
====================================================
|
||||||
|
|
||||||
|
This package provides consolidated utility functions extracted from app.py
|
||||||
|
for better code organization and reusability.
|
||||||
|
|
||||||
|
Modules:
|
||||||
|
- auth_decorators: Authentication and authorization decorators
|
||||||
|
- validation: Role validation and permissions checking
|
||||||
|
- location_utils: Location and geocoding utilities
|
||||||
|
- qr_utils: QR code generation utilities
|
||||||
|
- date_time_utils: Date and time formatting utilities
|
||||||
|
- helpers: General helper functions
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Authentication decorators
|
||||||
|
from .auth_decorators import (
|
||||||
|
login_required,
|
||||||
|
admin_required,
|
||||||
|
staff_or_admin_required,
|
||||||
|
project_manager_required,
|
||||||
|
payroll_required
|
||||||
|
)
|
||||||
|
|
||||||
|
# Validation functions
|
||||||
|
from .validation import (
|
||||||
|
is_valid_role,
|
||||||
|
has_admin_privileges,
|
||||||
|
has_staff_level_access,
|
||||||
|
has_export_permissions,
|
||||||
|
get_role_permissions,
|
||||||
|
VALID_ROLES,
|
||||||
|
STAFF_LEVEL_ROLES
|
||||||
|
)
|
||||||
|
|
||||||
|
# Export all for easy importing
|
||||||
|
__all__ = [
|
||||||
|
# Decorators
|
||||||
|
'login_required',
|
||||||
|
'admin_required',
|
||||||
|
'staff_or_admin_required',
|
||||||
|
'project_manager_required',
|
||||||
|
'payroll_required',
|
||||||
|
|
||||||
|
# Validation
|
||||||
|
'is_valid_role',
|
||||||
|
'has_admin_privileges',
|
||||||
|
'has_staff_level_access',
|
||||||
|
'has_export_permissions',
|
||||||
|
'get_role_permissions',
|
||||||
|
'VALID_ROLES',
|
||||||
|
'STAFF_LEVEL_ROLES',
|
||||||
|
]
|
||||||
@@ -0,0 +1,125 @@
|
|||||||
|
"""
|
||||||
|
Authentication and Authorization Decorators
|
||||||
|
==========================================
|
||||||
|
|
||||||
|
Consolidated authentication decorators to eliminate duplication.
|
||||||
|
Extracted from app.py for better code organization.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from functools import wraps
|
||||||
|
from flask import session, flash, redirect, url_for
|
||||||
|
from utils.validation import has_admin_privileges, has_staff_level_access
|
||||||
|
|
||||||
|
def login_required(f):
|
||||||
|
"""
|
||||||
|
Decorator to ensure user is logged in
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
@app.route('/dashboard')
|
||||||
|
@login_required
|
||||||
|
def dashboard():
|
||||||
|
return render_template('dashboard.html')
|
||||||
|
"""
|
||||||
|
@wraps(f)
|
||||||
|
def decorated_function(*args, **kwargs):
|
||||||
|
if 'user_id' not in session:
|
||||||
|
flash('Please log in to access this page.', 'error')
|
||||||
|
return redirect(url_for('login'))
|
||||||
|
return f(*args, **kwargs)
|
||||||
|
return decorated_function
|
||||||
|
|
||||||
|
def admin_required(f):
|
||||||
|
"""
|
||||||
|
Decorator to ensure user has admin privileges
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
@app.route('/admin/users')
|
||||||
|
@admin_required
|
||||||
|
def manage_users():
|
||||||
|
return render_template('users.html')
|
||||||
|
"""
|
||||||
|
@wraps(f)
|
||||||
|
def decorated_function(*args, **kwargs):
|
||||||
|
if 'username' not in session:
|
||||||
|
flash('Please log in to access this page.', 'error')
|
||||||
|
return redirect(url_for('login'))
|
||||||
|
|
||||||
|
user_role = session.get('role')
|
||||||
|
if not has_admin_privileges(user_role):
|
||||||
|
flash('Administrator privileges required for this action.', 'error')
|
||||||
|
return redirect(url_for('dashboard'))
|
||||||
|
|
||||||
|
return f(*args, **kwargs)
|
||||||
|
return decorated_function
|
||||||
|
|
||||||
|
def staff_or_admin_required(f):
|
||||||
|
"""
|
||||||
|
Decorator to ensure user has staff-level or admin privileges
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
@app.route('/attendance/records')
|
||||||
|
@staff_or_admin_required
|
||||||
|
def view_records():
|
||||||
|
return render_template('records.html')
|
||||||
|
"""
|
||||||
|
@wraps(f)
|
||||||
|
def decorated_function(*args, **kwargs):
|
||||||
|
if 'username' not in session:
|
||||||
|
flash('Please log in to access this page.', 'error')
|
||||||
|
return redirect(url_for('login'))
|
||||||
|
|
||||||
|
user_role = session.get('role')
|
||||||
|
if not (has_admin_privileges(user_role) or has_staff_level_access(user_role)):
|
||||||
|
flash('Insufficient privileges to access this page.', 'error')
|
||||||
|
return redirect(url_for('dashboard'))
|
||||||
|
|
||||||
|
return f(*args, **kwargs)
|
||||||
|
return decorated_function
|
||||||
|
|
||||||
|
def project_manager_required(f):
|
||||||
|
"""
|
||||||
|
Decorator to ensure user is project manager or admin
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
@app.route('/projects/dashboard')
|
||||||
|
@project_manager_required
|
||||||
|
def project_dashboard():
|
||||||
|
return render_template('project_dashboard.html')
|
||||||
|
"""
|
||||||
|
@wraps(f)
|
||||||
|
def decorated_function(*args, **kwargs):
|
||||||
|
if 'username' not in session:
|
||||||
|
flash('Please log in to access this page.', 'error')
|
||||||
|
return redirect(url_for('login'))
|
||||||
|
|
||||||
|
user_role = session.get('role')
|
||||||
|
if user_role not in ['admin', 'project_manager']:
|
||||||
|
flash('Project Manager access required.', 'error')
|
||||||
|
return redirect(url_for('dashboard'))
|
||||||
|
|
||||||
|
return f(*args, **kwargs)
|
||||||
|
return decorated_function
|
||||||
|
|
||||||
|
def payroll_required(f):
|
||||||
|
"""
|
||||||
|
Decorator to ensure user is payroll staff or admin
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
@app.route('/payroll/process')
|
||||||
|
@payroll_required
|
||||||
|
def process_payroll():
|
||||||
|
return render_template('payroll.html')
|
||||||
|
"""
|
||||||
|
@wraps(f)
|
||||||
|
def decorated_function(*args, **kwargs):
|
||||||
|
if 'username' not in session:
|
||||||
|
flash('Please log in to access this page.', 'error')
|
||||||
|
return redirect(url_for('login'))
|
||||||
|
|
||||||
|
user_role = session.get('role')
|
||||||
|
if user_role not in ['admin', 'payroll']:
|
||||||
|
flash('Payroll access required.', 'error')
|
||||||
|
return redirect(url_for('dashboard'))
|
||||||
|
|
||||||
|
return f(*args, **kwargs)
|
||||||
|
return decorated_function
|
||||||
@@ -0,0 +1,132 @@
|
|||||||
|
"""
|
||||||
|
Validation Utilities for QR Attendance Management System
|
||||||
|
========================================================
|
||||||
|
|
||||||
|
Role validation and permissions checking functions.
|
||||||
|
Extracted from app.py for better code organization.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Valid user roles
|
||||||
|
VALID_ROLES = ['admin', 'staff', 'payroll', 'project_manager']
|
||||||
|
STAFF_LEVEL_ROLES = ['staff', 'payroll', 'project_manager']
|
||||||
|
|
||||||
|
def is_valid_role(role):
|
||||||
|
"""
|
||||||
|
Check if role is valid
|
||||||
|
|
||||||
|
Args:
|
||||||
|
role (str): User role to validate
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool: True if role is valid
|
||||||
|
"""
|
||||||
|
return role in VALID_ROLES
|
||||||
|
|
||||||
|
def has_admin_privileges(role):
|
||||||
|
"""
|
||||||
|
Check if role has admin privileges
|
||||||
|
|
||||||
|
Args:
|
||||||
|
role (str): User role to check
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool: True if role is admin
|
||||||
|
"""
|
||||||
|
return role == 'admin'
|
||||||
|
|
||||||
|
def has_staff_level_access(role):
|
||||||
|
"""
|
||||||
|
Check if role has staff-level access (includes payroll, project_manager)
|
||||||
|
|
||||||
|
Args:
|
||||||
|
role (str): User role to check
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool: True if role has staff-level permissions
|
||||||
|
"""
|
||||||
|
return role in STAFF_LEVEL_ROLES
|
||||||
|
|
||||||
|
def has_export_permissions(role):
|
||||||
|
"""
|
||||||
|
Check if user role has export permissions
|
||||||
|
|
||||||
|
Args:
|
||||||
|
role (str): User role to check
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool: True if role can export data
|
||||||
|
"""
|
||||||
|
return role in ['admin', 'payroll']
|
||||||
|
|
||||||
|
def get_role_permissions(role):
|
||||||
|
"""
|
||||||
|
Get permissions description for a role
|
||||||
|
|
||||||
|
Args:
|
||||||
|
role (str): User role
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
dict: Role permissions with title, permissions list, and restrictions
|
||||||
|
"""
|
||||||
|
permissions = {
|
||||||
|
'admin': {
|
||||||
|
'title': 'Administrator Permissions',
|
||||||
|
'permissions': [
|
||||||
|
'Full QR code management (create, edit, delete)',
|
||||||
|
'Complete user management capabilities',
|
||||||
|
'System configuration access',
|
||||||
|
'View all system analytics',
|
||||||
|
'Bulk operations and data export',
|
||||||
|
'Access to all admin features'
|
||||||
|
],
|
||||||
|
'restrictions': ['With great power comes great responsibility!']
|
||||||
|
},
|
||||||
|
'staff': {
|
||||||
|
'title': 'Staff User Permissions',
|
||||||
|
'permissions': [
|
||||||
|
'View QR codes and attendance records',
|
||||||
|
'Check-in to attendance',
|
||||||
|
'View own attendance history'
|
||||||
|
],
|
||||||
|
'restrictions': [
|
||||||
|
'Cannot create or modify QR codes',
|
||||||
|
'Cannot manage users',
|
||||||
|
'Limited export capabilities'
|
||||||
|
]
|
||||||
|
},
|
||||||
|
'payroll': {
|
||||||
|
'title': 'Payroll Specialist Permissions',
|
||||||
|
'permissions': [
|
||||||
|
'Full payroll processing access',
|
||||||
|
'Export attendance and payroll data',
|
||||||
|
'View all attendance records',
|
||||||
|
'Generate payroll reports'
|
||||||
|
],
|
||||||
|
'restrictions': [
|
||||||
|
'Cannot manage QR codes',
|
||||||
|
'Cannot manage users',
|
||||||
|
'Read-only access to configurations'
|
||||||
|
]
|
||||||
|
},
|
||||||
|
'project_manager': {
|
||||||
|
'title': 'Project Manager Permissions',
|
||||||
|
'permissions': [
|
||||||
|
'View assigned project data',
|
||||||
|
'View assigned location data',
|
||||||
|
'Generate project reports',
|
||||||
|
'Monitor project attendance'
|
||||||
|
],
|
||||||
|
'restrictions': [
|
||||||
|
'Access limited to assigned projects/locations',
|
||||||
|
'Cannot modify QR codes',
|
||||||
|
'Cannot manage users',
|
||||||
|
'Read-only access'
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return permissions.get(role, {
|
||||||
|
'title': 'Unknown Role',
|
||||||
|
'permissions': [],
|
||||||
|
'restrictions': ['Invalid role specified']
|
||||||
|
})
|
||||||
Reference in New Issue
Block a user