from pathlib import Path import re from datetime import timedelta from tempfile import NamedTemporaryFile import json from io import BytesIO from celery import current_app from django.conf import settings from django.db import connection from django.db import IntegrityError from django.db.models import Q from django.db.models import Count from django.shortcuts import get_object_or_404, redirect, render from django.contrib import messages from django.contrib.auth import get_user_model, login as auth_login from django.contrib.auth.decorators import login_required from django.contrib.auth.tokens import default_token_generator from django.http import JsonResponse from django.views.decorators.http import require_POST from django.views.decorators.csrf import ensure_csrf_cookie from django.utils import timezone from django.utils.encoding import force_bytes from django.utils.http import urlsafe_base64_encode from django.utils.translation import gettext as _ from django.urls import reverse from .app_registry import build_portal_app_sections, get_portal_app_registry_rows, normalize_portal_app_sort_orders from .backup_ops import ( create_backup_bundle, delete_backup_bundle, latest_backup_health_snapshot, list_backup_bundles, verify_backup_bundle, ) from .branding import get_branding_email_copy, get_company_email_domain, get_default_notification_templates, get_portal_trial_config, is_trial_expired from . import account_views, admin_config_views, integrations_views, request_views from .admin_section_builders import ( build_branding_sections as _build_branding_sections, build_company_config_sections as _build_company_config_sections, ) from .admin_user_helpers import ( render_user_management as _render_user_management, send_user_access_email as _send_user_access_email, would_remove_last_platform_owner as _would_remove_last_platform_owner, would_remove_last_super_admin as _would_remove_last_super_admin, ) from .forms import AccountAvatarForm, AccountDetailsForm, AccountNotificationPreferencesForm, AccountTOTPDisableForm, AccountTOTPEnableForm, AccountTOTPRegenerateRecoveryCodesForm, AppLoginForm, AppTOTPChallengeForm, OffboardingRequestForm, OnboardingRequestForm, PortalBrandingForm, PortalCompanyConfigForm, PortalTrialConfigForm, UserManagementCreateForm from .form_builder import ( DEFAULT_FIELD_ORDER, DEFAULT_CONDITIONAL_RULES, FORM_PRESETS, LOCKED_FIELD_RULES, LOCKED_SECTION_RULES, OFFBOARDING_PAGE_LABELS, OFFBOARDING_PAGE_ORDER, ONBOARDING_DEFAULT_PAGE, build_custom_field_key, custom_field_target_key, ensure_form_field_configs, ensure_form_conditional_rule_configs, ensure_form_section_configs, get_custom_field_configs, get_custom_section_configs, get_default_page_map, get_section_definitions, get_section_labels, get_section_order, apply_form_preset, ) from .form_builder_views import form_builder_page_impl, form_builder_save_order_impl from .intro_builder_views import intro_builder_page_impl from .observability_views import ( audit_log_page_impl, backup_recovery_page_impl, create_backup_from_admin_impl, job_monitor_page_impl, verify_backup_from_admin_impl, ) from .view_audit import audit as _audit, audit_action_label as _audit_action_label, display_user_name as _display_user_name from .view_context import ( form_field_labels as _form_field_labels, request_custom_field_details as _request_custom_field_details, request_status_label as _request_status_label, request_target_label as _request_target_label, ) from .view_form_runtime import ( CONDITIONAL_RULE_OPERATOR_CHOICES, ONBOARDING_CHECKBOX_LISTS, ONBOARDING_GROUPS, ONBOARDING_INLINE_CHECKS, active_conditional_target_keys as _active_conditional_target_keys, build_offboarding_sections as _build_offboarding_sections, build_onboarding_layout as _build_onboarding_layout, build_onboarding_sections as _build_onboarding_sections, conditional_rule_summary as _conditional_rule_summary, field_rule_summary as _field_rule_summary, normalized_conditional_rule_payload as _normalized_conditional_rule_payload, translate_choice_list as _translate_choice_list, ) from .view_permissions import require_capability as _require_capability from .models import AdminAuditLog, AsyncTaskLog, EmployeeProfile, FormConditionalRuleConfig, FormCustomFieldConfig, FormCustomSectionConfig, FormFieldConfig, FormOption, FormSectionConfig, NotificationRule, NotificationTemplate, OffboardingRequest, OnboardingIntroductionSession, OnboardingRequest, PortalAppConfig, PortalBranding, PortalCompanyConfig, PortalTrialConfig, ScheduledWelcomeEmail, SystemEmailConfig, UserNotification, UserProfile, WorkflowConfig from .emailing import send_system_email from .notifications import notify_user from .roles import ROLE_GROUP_NAMES, ROLE_LABELS, ROLE_PLATFORM_OWNER, ROLE_SUPER_ADMIN, assign_user_role, get_user_role_key, get_user_role_label, user_has_capability from .services import get_email_test_redirect, is_email_test_mode, is_nextcloud_enabled, upload_to_nextcloud from .totp import build_otpauth_uri, generate_recovery_codes, generate_totp_secret from .tasks import ( _generate_onboarding_intro_pdf, _generate_onboarding_intro_session_pdf, build_intro_sections_for_request, process_offboarding_request, process_onboarding_request, send_scheduled_welcome_email, ) def _redirect_back(request, fallback: str): target = (request.POST.get('next') or request.GET.get('next') or '').strip() if target.startswith('/'): return redirect(target) referer = (request.META.get('HTTP_REFERER') or '').strip() if referer.startswith('http://127.0.0.1') or referer.startswith('http://localhost') or referer.startswith('/'): return redirect(referer) return redirect(fallback) @login_required @require_POST def mark_notification_read(request, notification_id: int): notification = get_object_or_404(UserNotification, id=notification_id, user=request.user) notification.mark_read() return _redirect_back(request, 'home') @login_required @require_POST def mark_all_notifications_read(request): UserNotification.objects.filter(user=request.user, read_at__isnull=True).update(read_at=timezone.now()) return _redirect_back(request, 'home') @login_required @require_POST def session_keepalive(request): now_ts = int(timezone.now().timestamp()) request.session['last_activity_ts'] = now_ts request.session['auth_fresh_ts'] = now_ts return JsonResponse( { 'status': 'ok', 'idle_timeout_seconds': settings.SESSION_IDLE_TIMEOUT_SECONDS, 'reauth_timeout_seconds': settings.SENSITIVE_ACTION_REAUTH_SECONDS, 'refreshed_at': now_ts, } ) def healthz(request): db_ok = True try: with connection.cursor() as cursor: cursor.execute('SELECT 1') cursor.fetchone() except Exception: db_ok = False status_code = 200 if db_ok else 503 return JsonResponse( { 'status': 'ok' if db_ok else 'degraded', 'service': 'workdock', 'db': 'ok' if db_ok else 'error', 'time': timezone.now().isoformat(), }, status=status_code, ) def login_page(request): return account_views.login_page_impl(request) def login_totp_page(request): return account_views.login_totp_page_impl(request) @login_required def account_profile_page(request): return account_views.account_profile_page_impl(request) @login_required def home(request): config, _ = WorkflowConfig.objects.get_or_create(name='Default') role_key = get_user_role_key(request.user) return render( request, 'workflows/home.html', { 'nextcloud_enabled': is_nextcloud_enabled(), 'email_test_mode': is_email_test_mode(), 'workflow_config': config, 'role_label': get_user_role_label(request.user), 'role_key': role_key, 'portal_app_sections': build_portal_app_sections(request.user), }, ) @_require_capability('manage_app_registry') def portal_app_registry_page(request): return admin_config_views.portal_app_registry_page_impl(request, translate_choice_list=_translate_choice_list) @_require_capability('view_job_monitor') def job_monitor_page(request): return job_monitor_page_impl(request) @_require_capability('manage_app_registry') @require_POST def save_portal_app_registry(request): return admin_config_views.save_portal_app_registry_impl(request, audit_fn=_audit) @_require_capability('manage_users') def user_management_page(request): return admin_config_views.user_management_page_impl( request, render_user_management_fn=lambda req, create_form=None, status_code=200: _render_user_management( req, create_form=create_form, status_code=status_code, audit_action_label_fn=_audit_action_label, display_user_name_fn=_display_user_name, ), ) @_require_capability('manage_product_branding') def portal_branding_page(request): return admin_config_views.portal_branding_page_impl(request, build_branding_sections_fn=_build_branding_sections) @_require_capability('manage_product_branding') @require_POST def save_portal_branding(request): return admin_config_views.save_portal_branding_impl(request, audit_fn=_audit, build_branding_sections_fn=_build_branding_sections) @_require_capability('manage_company_config') def portal_company_config_page(request): return admin_config_views.portal_company_config_page_impl(request, build_company_config_sections_fn=_build_company_config_sections) @_require_capability('manage_company_config') @require_POST def save_portal_company_config(request): return admin_config_views.save_portal_company_config_impl(request, audit_fn=_audit, build_company_config_sections_fn=_build_company_config_sections) @_require_capability('manage_trial_lifecycle') def portal_trial_config_page(request): return admin_config_views.portal_trial_config_page_impl(request) @_require_capability('manage_trial_lifecycle') @require_POST def save_portal_trial_config(request): return admin_config_views.save_portal_trial_config_impl(request, audit_fn=_audit) @_require_capability('manage_users') @require_POST def create_user_from_admin(request): return admin_config_views.create_user_from_admin_impl( request, render_user_management_fn=lambda req, create_form=None, status_code=200: _render_user_management( req, create_form=create_form, status_code=status_code, audit_action_label_fn=_audit_action_label, display_user_name_fn=_display_user_name, ), send_user_access_email_fn=lambda req, target_user, invitation: _send_user_access_email( req, target_user, invitation=invitation, display_user_name_fn=_display_user_name, ), audit_fn=_audit, display_user_name_fn=_display_user_name, ) @_require_capability('manage_users') @require_POST def update_user_from_admin(request, user_id: int): return admin_config_views.update_user_from_admin_impl( request, user_id, would_remove_last_platform_owner_fn=_would_remove_last_platform_owner, would_remove_last_super_admin_fn=_would_remove_last_super_admin, audit_fn=_audit, display_user_name_fn=_display_user_name, ) @_require_capability('manage_users') @require_POST def send_password_reset_from_admin(request, user_id: int): return admin_config_views.send_password_reset_from_admin_impl( request, user_id, send_user_access_email_fn=lambda req, target_user, invitation: _send_user_access_email( req, target_user, invitation=invitation, display_user_name_fn=_display_user_name, ), audit_fn=_audit, display_user_name_fn=_display_user_name, ) @_require_capability('manage_users') @require_POST def delete_user_from_admin(request, user_id: int): return admin_config_views.delete_user_from_admin_impl( request, user_id, would_remove_last_platform_owner_fn=_would_remove_last_platform_owner, would_remove_last_super_admin_fn=_would_remove_last_super_admin, audit_fn=_audit, display_user_name_fn=_display_user_name, ) @_require_capability('view_docs') def handbook_page(request): return admin_config_views.handbook_page_impl(request) @_require_capability('view_docs') def project_wiki_page(request): return admin_config_views.project_wiki_page_impl(request) @_require_capability('view_docs') def developer_handbook_page(request): return admin_config_views.developer_handbook_page_impl(request) @_require_capability('view_docs') def deployment_hosts_page(request): return admin_config_views.deployment_hosts_page_impl(request) @_require_capability('view_docs') def release_checklist_page(request): return admin_config_views.release_checklist_page_impl(request) @_require_capability('view_audit_log') def audit_log_page(request): return audit_log_page_impl(request) @_require_capability('manage_backups') def backup_recovery_page(request): return backup_recovery_page_impl(request) @_require_capability('manage_backups') @require_POST def create_backup_from_admin(request): return create_backup_from_admin_impl( request, audit_fn=_audit, notify_user_fn=notify_user, create_backup_bundle_fn=create_backup_bundle, ) @_require_capability('manage_backups') @require_POST def verify_backup_from_admin(request, backup_name: str): return verify_backup_from_admin_impl( request, backup_name, audit_fn=_audit, notify_user_fn=notify_user, verify_backup_bundle_fn=verify_backup_bundle, ) @_require_capability('manage_backups') @require_POST def delete_backup_from_admin(request, backup_name: str): try: result = delete_backup_bundle(backup_name) _audit( request, 'backup_deleted', target_type='backup_bundle', target_label=backup_name, details={}, ) messages.success(request, _('Backup wurde gelöscht: %(name)s') % {'name': result['name']}) except Exception as exc: messages.error(request, _('Backup konnte nicht gelöscht werden: %(error)s') % {'error': exc}) return redirect('backup_recovery_page') @_require_capability('view_request_timeline') def request_timeline_page(request, kind: str, request_id: int): return request_views.request_timeline_page_impl( request, kind, request_id, request_target_label_fn=_request_target_label, request_custom_field_details_fn=_request_custom_field_details, audit_action_label_fn=_audit_action_label, ) @login_required def requests_dashboard(request): return request_views.requests_dashboard_impl( request, audit_fn=_audit, request_target_label_fn=_request_target_label, request_status_label_fn=_request_status_label, ) @login_required @ensure_csrf_cookie def onboarding_create(request): return request_views.onboarding_create_impl( request, build_onboarding_layout_fn=_build_onboarding_layout, build_onboarding_sections_fn=_build_onboarding_sections, normalized_conditional_rule_payload_fn=_normalized_conditional_rule_payload, display_user_name_fn=_display_user_name, onboarding_inline_checks=ONBOARDING_INLINE_CHECKS, onboarding_checkbox_lists=ONBOARDING_CHECKBOX_LISTS, ) @login_required def onboarding_success(request, request_id: int): return request_views.onboarding_success_impl(request, request_id) @_require_capability('generate_intro_pdfs') @require_POST def generate_onboarding_intro_pdf(request, request_id: int): return request_views.generate_onboarding_intro_pdf_impl(request, request_id, audit_fn=_audit) @_require_capability('generate_intro_pdfs') @require_POST def generate_onboarding_intro_session_pdf(request, request_id: int): return request_views.generate_onboarding_intro_session_pdf_impl(request, request_id, audit_fn=_audit, display_user_name_fn=_display_user_name) @_require_capability('run_intro_session') def onboarding_intro_session_page(request, request_id: int): return request_views.onboarding_intro_session_page_impl(request, request_id, audit_fn=_audit, display_user_name_fn=_display_user_name) @login_required @ensure_csrf_cookie def offboarding_create(request): return request_views.offboarding_create_impl( request, build_offboarding_sections_fn=_build_offboarding_sections, display_user_name_fn=_display_user_name, ) @login_required def offboarding_success(request, request_id: int): return request_views.offboarding_success_impl(request, request_id) @_require_capability('manage_builders') def form_builder_page(request): return form_builder_page_impl( request, audit_fn=_audit, translate_choice_list=_translate_choice_list, form_field_labels_fn=_form_field_labels, field_rule_summary_fn=_field_rule_summary, conditional_rule_summary_fn=_conditional_rule_summary, onboarding_groups=ONBOARDING_GROUPS, conditional_rule_operator_choices=CONDITIONAL_RULE_OPERATOR_CHOICES, ) @_require_capability('manage_builders') def intro_builder_page(request): return intro_builder_page_impl( request, audit_fn=_audit, translate_choice_list=_translate_choice_list, ) @_require_capability('manage_integrations') def integrations_setup_page(request): return integrations_views.integrations_setup_page_impl(request) @_require_capability('manage_welcome_emails') def welcome_emails_page(request): return integrations_views.welcome_emails_page_impl(request) @_require_capability('manage_welcome_emails') @require_POST def trigger_welcome_email_now(request, schedule_id: int): return integrations_views.trigger_welcome_email_now_impl( request, schedule_id, audit_fn=_audit, send_task_fn=send_scheduled_welcome_email, ) @_require_capability('manage_welcome_emails') @require_POST def save_welcome_email_settings(request): return integrations_views.save_welcome_email_settings_impl(request, audit_fn=_audit) @_require_capability('manage_welcome_emails') @require_POST def bulk_welcome_email_action(request): return integrations_views.bulk_welcome_email_action_impl( request, audit_fn=_audit, send_task_fn=send_scheduled_welcome_email, ) @_require_capability('manage_welcome_emails') @require_POST def pause_welcome_email(request, schedule_id: int): return integrations_views.pause_welcome_email_impl(request, schedule_id, audit_fn=_audit) @_require_capability('manage_welcome_emails') @require_POST def resume_welcome_email(request, schedule_id: int): return integrations_views.resume_welcome_email_impl( request, schedule_id, audit_fn=_audit, send_task_fn=send_scheduled_welcome_email, ) @_require_capability('manage_welcome_emails') @require_POST def cancel_welcome_email(request, schedule_id: int): return integrations_views.cancel_welcome_email_impl(request, schedule_id, audit_fn=_audit) @_require_capability('manage_builders') @require_POST def form_builder_save_order(request): return form_builder_save_order_impl(request, audit_fn=_audit) @_require_capability('manage_integrations') @require_POST def send_test_email(request): return integrations_views.send_test_email_impl(request, audit_fn=_audit, redirect_back_fn=_redirect_back) @_require_capability('manage_integrations') @require_POST def nextcloud_test_upload(request): return integrations_views.nextcloud_test_upload_impl(request, audit_fn=_audit, redirect_back_fn=_redirect_back) @_require_capability('manage_integrations') @require_POST def toggle_nextcloud_enabled(request): return integrations_views.toggle_nextcloud_enabled_impl(request, audit_fn=_audit, redirect_back_fn=_redirect_back) @_require_capability('manage_integrations') @require_POST def toggle_email_mode(request): return integrations_views.toggle_email_mode_impl(request, audit_fn=_audit, redirect_back_fn=_redirect_back) @_require_capability('manage_integrations') @require_POST def save_integrations_settings(request): return integrations_views.save_integrations_settings_impl(request, audit_fn=_audit) @_require_capability('manage_integrations') @require_POST def save_nextcloud_settings(request): return integrations_views.save_nextcloud_settings_impl(request, audit_fn=_audit) @_require_capability('manage_integrations') @require_POST def save_workflow_rules(request): return integrations_views.save_workflow_rules_impl(request, audit_fn=_audit) @_require_capability('manage_integrations') @require_POST def save_backup_settings(request): return integrations_views.save_backup_settings_impl(request, audit_fn=_audit) @_require_capability('manage_integrations') @require_POST def save_mail_settings(request): return integrations_views.save_mail_settings_impl(request, audit_fn=_audit) @_require_capability('manage_integrations') @require_POST def save_email_routing_settings(request): return integrations_views.save_email_routing_settings_impl(request, audit_fn=_audit) @_require_capability('manage_integrations') @require_POST def save_notification_rules(request): return integrations_views.save_notification_rules_impl(request, audit_fn=_audit) @_require_capability('delete_requests') @require_POST def delete_request_from_dashboard(request, kind: str, request_id: int): return request_views.delete_request_from_dashboard_impl(request, kind, request_id, audit_fn=_audit, request_target_label_fn=_request_target_label) @_require_capability('retry_requests') @require_POST def retry_request_from_dashboard(request, kind: str, request_id: int): return request_views.retry_request_from_dashboard_impl(request, kind, request_id, audit_fn=_audit, request_target_label_fn=_request_target_label)