Source code for management.views.settings

"""Settings views."""

from __future__ import annotations

import logging
from typing import TYPE_CHECKING

from django.contrib import messages
from django.core.management import call_command
from django.shortcuts import redirect
from django.urls import reverse, reverse_lazy
from django.utils.translation import gettext as _
from django.views import View
from django.views.generic.edit import FormView

from management.forms import NotificationConfigForm, SecurityConfigForm
from management.models import LoggingConfig, NotificationConfig, SecurityConfig
from management.security.features import AutoGenPkiFeature
from management.security.mixins import SecurityLevelMixin
from pki.util.keys import AutoGenPkiKeyAlgorithm
from trustpoint.logger import LoggerMixin
from trustpoint.page_context import PageContextMixin

if TYPE_CHECKING:
    from typing import Any

    from django.http import HttpRequest, HttpResponse


[docs] LOG_LEVELS=['DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL']
[docs] class SettingsView(PageContextMixin, SecurityLevelMixin, LoggerMixin, FormView[SecurityConfigForm]): """A view for managing security settings in the Trustpoint application. This view handles the display and processing of the SecurityConfigForm, allowing users to configure security-related settings such as security mode, auto-generated PKI, and notification configurations. """
[docs] template_name = 'management/settings.html'
[docs] form_class = SecurityConfigForm
[docs] success_url = reverse_lazy('management:settings')
[docs] page_category = 'management'
[docs] page_name = 'settings'
[docs] def post(self, request: HttpRequest, *args: Any, **kwargs: Any) -> HttpResponse: """Handle POST requests for both SecurityConfig and NotificationConfig forms. Parameters ---------- request : HttpRequest The HTTP request object. *args : tuple Positional arguments. **kwargs : dict Keyword arguments. Returns: ------- HttpResponse A response object. """ # Check which form was submitted if 'security_configuration' in request.POST: # Handle SecurityConfigForm form = self.get_form() if form.is_valid(): return self.form_valid(form) return self.form_invalid(form) if 'notification_configuration' in request.POST: notification_config = NotificationConfig.get() notification_form = NotificationConfigForm(request.POST, instance=notification_config) self.logger.debug('NotificationConfigForm validation: is_valid=%s', notification_form.is_valid()) if notification_form.is_valid(): # Get the enabled status before and after was_enabled = notification_config.enabled notification_form.save() notification_config.refresh_from_db() cleaned_enabled = notification_config.enabled if cleaned_enabled: needs_init = not notification_config.notification_cycle_enabled or not was_enabled if needs_init: try: notification_config.notification_cycle_enabled = True notification_config.save(update_fields=['notification_cycle_enabled']) call_command('init_notifications') if not was_enabled: messages.success( request, _('Notifications enabled and notification cycle initialized.') ) else: messages.success( request, _('Notification configuration saved and cycle reinitialized.') ) except Exception: # pylint: disable=broad-except self.logger.exception('Error initializing notifications') messages.error( request, _('Notifications saved but error initializing notification cycle.') ) else: messages.success(request, _('Notification configuration saved successfully.')) else: if notification_config.notification_cycle_enabled: notification_config.notification_cycle_enabled = False notification_config.save(update_fields=['notification_cycle_enabled']) messages.success(request, _('Notifications disabled successfully.')) return redirect(self.success_url) self.logger.error('Notification form errors: %s', notification_form.errors) self.logger.error('Notification form non-field errors: %s', notification_form.non_field_errors()) messages.error(request, _('Error saving notification configuration')) return self.render_to_response( self.get_context_data(notification_form=notification_form) ) return super().post(request, *args, **kwargs)
[docs] def get_form_kwargs(self) -> dict[str, Any]: """Get the keyword arguments for instantiating the form. This method retrieves or creates the `SecurityConfig` instance and includes it in the form's keyword arguments. Returns: ------- dict The keyword arguments for the form, including the `instance`. """ kwargs = super().get_form_kwargs() try: security_config = SecurityConfig.objects.get(id=1) except SecurityConfig.DoesNotExist: security_config = SecurityConfig.objects.create() kwargs['instance'] = security_config return kwargs
[docs] def form_valid(self, form: SecurityConfigForm) -> HttpResponse: """Handle valid form submission. This method processes the form data, applies security settings, and displays success messages to the user. Parameters ---------- form : SecurityConfigForm The form instance containing the submitted data. Returns: ------- HttpResponse A redirect response to the success URL. """ old_conf = SecurityConfig.objects.get(pk=form.instance.pk) if form.instance.pk else None form.save() if 'security_mode' in form.changed_data: old_value = getattr(old_conf, 'security_mode', None) if old_conf else None new_value = form.cleaned_data.get('security_mode') if new_value is None: messages.error(self.request, 'Security mode value is missing.') return redirect(self.success_url) # Safely convert to int for comparison (default to 0 if None) old_int = int(old_value) if old_value is not None else 0 new_int = int(new_value) if new_int > old_int: self.sec.reset_settings(new_value) form.instance.apply_security_settings() if 'auto_gen_pki' in form.changed_data: old_auto = getattr(old_conf, 'auto_gen_pki', None) if old_conf else None new_auto = form.cleaned_data.get('auto_gen_pki', None) self.logger.info( 'auto_gen_pki changed: old=%s, new=%s', old_auto, new_auto ) if old_auto != new_auto and new_auto: # autogen PKI got enabled key_alg_value = form.cleaned_data.get('auto_gen_pki_key_algorithm') if key_alg_value is None: messages.error(self.request, 'Auto-generated PKI key algorithm is missing.') return redirect(self.success_url) key_alg = AutoGenPkiKeyAlgorithm(key_alg_value) self.logger.info('Calling enable_feature for AutoGenPkiFeature with key_alg: %s', key_alg) self.sec.enable_feature(AutoGenPkiFeature, {'key_algorithm': key_alg}) self.logger.info( 'Auto-generated PKI enabled with key algorithm: %s', key_alg.name ) elif old_auto != new_auto and not new_auto: # autogen PKI got disabled AutoGenPkiFeature.disable() self.logger.info('Auto-generated PKI disabled') messages.success(self.request, _('Your changes were saved successfully.')) return super().form_valid(form)
[docs] def form_invalid(self, form: SecurityConfigForm) -> HttpResponse: """Handle invalid form submission. This method displays an error message and re-renders the form with validation errors. Parameters ---------- form : SecurityConfigForm The form instance containing the submitted data. Returns: ------- HttpResponse A response rendering the form with errors. """ messages.error(self.request, _('Error saving the configuration')) extra: dict[str, Any] = {'form': form} if hasattr(form, '_violations'): extra['policy_violations'] = form._violations # noqa: SLF001 extra['policy_violations_mode_label'] = form._violations_mode_label # noqa: SLF001 form.errors.pop('__all__', None) return self.render_to_response(self.get_context_data(**extra))
[docs] def get_context_data(self, **kwargs: Any) -> dict[str, Any]: """Build the context dictionary for rendering the settings page. This method adds page metadata, notification configurations, log levels, task execution status, and the current log level to the context. Parameters ---------- **kwargs : dict Additional context variables. Returns: ------- dict[str, Any] The context dictionary for the template. """ context = super().get_context_data(**kwargs) context['page_category'] = 'management' context['page_name'] = 'settings' context['notification_configurations_json'] = SecurityConfig.get_settings_preview_json() context['loglevels'] = LOG_LEVELS current_level_num = logging.getLogger().getEffectiveLevel() context['current_loglevel'] = logging.getLevelName(current_level_num) # Add notification configuration form notification_config = NotificationConfig.get() context['notification_form'] = NotificationConfigForm(instance=notification_config) context['notification_config'] = notification_config return context
[docs] class ChangeLogLevelView(View): """A view for changing the logging level in the Trustpoint application. This view handles POST requests to update the logging level dynamically. """
[docs] def post(self, request: HttpRequest) -> HttpResponse: """Handle POST requests to change the logging level. This method validates the provided log level, updates the logger and database configuration if valid, and redirects back to the settings page. Parameters ---------- request : HttpRequest The HTTP request object containing the POST data. Returns: ------- HttpResponse A redirect response to the settings page. """ level = request.POST.get('loglevel', '').upper() if level not in LOG_LEVELS: messages.error(request, f'Invalid log level: {level}') else: logger = logging.getLogger() logger.setLevel(getattr(logging, level)) LoggingConfig.objects.update_or_create( id=1, defaults={'log_level': level} ) messages.success(request, f'Log level set to {level}') return redirect(reverse('management:settings'))