Files
TimeTracker/app/utils/context_processors.py
Dries Peeters 944b69a7fc feat: implement full permission enforcement and enhanced UI visibility
BREAKING CHANGE: Permission system now actively enforced across all routes

## Summary
Complete implementation of advanced role-based access control (RBAC) system
with full route protection, UI conditionals, and enhanced management interface.

## Route Protection
- Updated all admin routes to use @admin_or_permission_required decorator
- Replaced inline admin checks with granular permission checks in:
  * Admin routes: user management, settings, backups, telemetry, OIDC
  * Project routes: create, edit, delete, archive, bulk operations
  * Client routes: create, edit, delete, archive, bulk operations
- Maintained backward compatibility with existing @admin_required decorator

## UI Permission Integration
- Added template helpers (has_permission, has_any_permission) to all templates
- Navigation conditionally shows admin/OIDC links based on permissions
- Action buttons (Edit, Delete, Archive) conditional on user permissions
- Project and client pages respect permission requirements
- Create buttons visible only with appropriate permissions

## Enhanced Roles & Permissions UI
- Added statistics dashboard showing:
  * Total roles, system roles, custom roles, assigned users
- Implemented expandable permission details in roles list
  * Click to view all permissions grouped by category
  * Visual checkmarks for assigned permissions
- Enhanced user list with role visibility:
  * Shows all assigned roles as color-coded badges
  * Blue badges for system roles, gray for custom roles
  * Yellow badges for legacy roles with migration prompt
  * Merged legacy role column into unified "Roles & Permissions"
- User count per role now clickable and accurate

## Security Improvements
- Added CSRF tokens to all new permission system forms:
  * Role creation/edit form
  * Role deletion form
  * User role assignment form
- All POST requests now protected against CSRF attacks

## Technical Details
- Fixed SQLAlchemy relationship query issues (AppenderQuery)
- Proper use of .count() for relationship aggregation
- Jinja2 namespace for accumulating counts in templates
- Responsive grid layouts for statistics and permission cards

## Documentation
- Created comprehensive implementation guides
- Added permission enforcement documentation
- Documented UI enhancements and features
- Included CSRF protection review

## Impact
- Permissions are now actively enforced, not just defined
- Admins can easily see who has what access
- Clear visual indicators of permission assignments
- Secure forms with CSRF protection
- Production-ready permission system
2025-10-24 12:49:54 +02:00

90 lines
3.4 KiB
Python

from flask import g, request, current_app
from flask_babel import get_locale
from flask_login import current_user
from app.models import Settings
from app.utils.timezone import get_timezone_offset_for_timezone
def register_context_processors(app):
"""Register context processors for the application"""
# Register permission helpers for templates
from app.utils.permissions import init_permission_helpers
init_permission_helpers(app)
@app.context_processor
def inject_settings():
"""Inject settings into all templates"""
try:
from app import db
# Check if we have an active database session
if db.session.is_active:
settings = Settings.get_settings()
return {
'settings': settings,
'currency': settings.currency,
'timezone': settings.timezone
}
except Exception as e:
# Log the error but continue with defaults
print(f"Warning: Could not inject settings: {e}")
pass
# Return defaults if settings not available
return {
'settings': None,
'currency': 'EUR',
'timezone': 'Europe/Rome'
}
@app.context_processor
def inject_globals():
"""Inject global variables into all templates"""
try:
from app import db
# Check if we have an active database session
if db.session.is_active:
settings = Settings.get_settings()
timezone_name = settings.timezone if settings else 'Europe/Rome'
else:
timezone_name = 'Europe/Rome'
except Exception as e:
# Log the error but continue with defaults
print(f"Warning: Could not inject globals: {e}")
timezone_name = 'Europe/Rome'
# Determine app version from environment or config
try:
import os
from app.config import Config
env_version = os.getenv('APP_VERSION')
# If running in GitHub Actions build, prefer tag-like versions
version_value = env_version or getattr(Config, 'APP_VERSION', None) or 'dev-0'
except Exception:
version_value = 'dev-0'
# Current locale code (e.g., 'en', 'de')
try:
current_locale = str(get_locale())
except Exception:
current_locale = 'en'
# Normalize to short code for comparisons (e.g., 'en' from 'en_US')
short_locale = (current_locale.split('_', 1)[0] if current_locale else 'en')
available_languages = current_app.config.get('LANGUAGES', {}) or {}
current_language_label = available_languages.get(short_locale, short_locale.upper())
return {
'app_name': 'Time Tracker',
'app_version': version_value,
'timezone': timezone_name,
'timezone_offset': get_timezone_offset_for_timezone(timezone_name),
'current_locale': current_locale,
'current_language_code': short_locale,
'current_language_label': current_language_label,
'config': current_app.config
}
@app.before_request
def before_request():
"""Set up request-specific data"""
g.request_start_time = request.start_time if hasattr(request, 'start_time') else None