Files
TimeTracker/app/utils/permissions.py
T
Dries Peeters b4486a627f fix: CI tests, code quality, and duplicate DB indexes
- Webhook models: remove duplicate index definitions so db.create_all()
  no longer raises 'index already exists' (columns already have index=True)
- ImportService: fix circular import by late-importing ClientService,
  ProjectService, TimeTrackingService in __init__
- reports: fix F823 by renaming unpack variable _ to _entry_count to avoid
  shadowing gettext _ in export_task_excel()
- Code quality: add .flake8 with extend-ignore so flake8 CI passes;
  simplify pyproject.toml isort config (drop unsupported options)
- Format: run black and isort on app/
- tests: restore minimal app fixture in test_import_export_models
2026-03-15 10:51:52 +01:00

187 lines
5.0 KiB
Python

"""Utilities for permission checking and decorators"""
from functools import wraps
from flask import abort, flash, redirect, url_for
from flask_babel import gettext as _
from flask_login import current_user
def permission_required(*permissions, require_all=False):
"""
Decorator to require one or more permissions.
Args:
*permissions: Permission name(s) required
require_all: If True, user must have ALL permissions. If False, user needs ANY permission.
Example:
@permission_required('edit_projects')
def edit_project():
...
@permission_required('view_reports', 'export_reports', require_all=True)
def export_report():
...
"""
def decorator(f):
@wraps(f)
def decorated_function(*args, **kwargs):
if not current_user.is_authenticated:
flash(_("Please log in to access this page"), "error")
return redirect(url_for("auth.login"))
# Check if user has required permissions
if require_all:
has_access = current_user.has_all_permissions(*permissions)
else:
has_access = current_user.has_any_permission(*permissions)
if not has_access:
flash(_("You do not have permission to access this page"), "error")
abort(403)
return f(*args, **kwargs)
return decorated_function
return decorator
def admin_or_permission_required(*permissions):
"""
Decorator that allows access if user is an admin OR has any of the specified permissions.
This is useful for gradual migration from admin-only to permission-based access.
Example:
@admin_or_permission_required('delete_projects')
def delete_project():
...
"""
def decorator(f):
@wraps(f)
def decorated_function(*args, **kwargs):
if not current_user.is_authenticated:
flash(_("Please log in to access this page"), "error")
return redirect(url_for("auth.login"))
# Allow access if user is admin or has any of the required permissions
if current_user.is_admin or current_user.has_any_permission(*permissions):
return f(*args, **kwargs)
flash(_("You do not have permission to access this page"), "error")
abort(403)
return decorated_function
return decorator
def check_permission(user, permission_name):
"""
Check if a user has a specific permission.
Args:
user: User object
permission_name: Name of the permission to check
Returns:
bool: True if user has the permission, False otherwise
"""
if not user or not user.is_authenticated:
return False
return user.has_permission(permission_name)
def check_any_permission(user, *permission_names):
"""
Check if a user has any of the specified permissions.
Args:
user: User object
*permission_names: Names of permissions to check
Returns:
bool: True if user has any of the permissions, False otherwise
"""
if not user or not user.is_authenticated:
return False
return user.has_any_permission(*permission_names)
def check_all_permissions(user, *permission_names):
"""
Check if a user has all of the specified permissions.
Args:
user: User object
*permission_names: Names of permissions to check
Returns:
bool: True if user has all of the permissions, False otherwise
"""
if not user or not user.is_authenticated:
return False
return user.has_all_permissions(*permission_names)
def get_user_permissions(user):
"""
Get all permissions for a user.
Args:
user: User object
Returns:
list: List of Permission objects
"""
if not user:
return []
return user.get_all_permissions()
def get_user_permission_names(user):
"""
Get all permission names for a user.
Args:
user: User object
Returns:
list: List of permission name strings
"""
if not user:
return []
permissions = user.get_all_permissions()
return [p.name for p in permissions]
# Template helper functions (register these in app context)
def init_permission_helpers(app):
"""
Initialize permission helper functions for use in templates.
Usage in templates:
{% if has_permission('edit_projects') %}
<button>Edit Project</button>
{% endif %}
"""
@app.context_processor
def inject_permission_helpers():
return {
"has_permission": lambda perm: check_permission(current_user, perm),
"has_any_permission": lambda *perms: check_any_permission(current_user, *perms),
"has_all_permissions": lambda *perms: check_all_permissions(current_user, *perms),
"get_user_permissions": lambda: (
get_user_permission_names(current_user) if current_user.is_authenticated else []
),
}