Fioddor Superconcentrado 376fe5a85b
email: Manage known installation conflicts
Signed-off-by: Fioddor Superconcentrado <fioddor@gmail.com>
[sunil: Don't show warning on conflict]
[sunil: Add statement to description about conflicts]
Signed-off-by: Sunil Mohan Adapa <sunil@medhas.org>
Reviewed-by: Sunil Mohan Adapa <sunil@medhas.org>
2021-10-10 19:22:22 -07:00

316 lines
11 KiB
Python

# SPDX-License-Identifier: AGPL-3.0-or-later
import io
import itertools
import pwd
from django.core.exceptions import ValidationError
from django.http import HttpResponseBadRequest
from django.shortcuts import redirect
from django.utils.html import escape
from django.utils.translation import gettext_lazy as _
from django.views.generic.base import TemplateView, View
import plinth.actions
import plinth.utils
from plinth.views import AppView, render_tabs
from . import aliases, audit, forms
class TabMixin(View):
admin_tabs = [
('', _('Home')),
('my_mail', _('My Mail')),
('my_aliases', _('My Aliases')),
('security', _('Security')),
('domains', _('Domains'))
]
user_tabs = [
('my_mail', _('Home')),
('my_aliases', _('My Aliases'))
]
def get_context_data(self, *args, **kwargs):
# Retrieve context data from the next method in the MRO
context = super().get_context_data(*args, **kwargs)
# Populate context with customized data
context['tabs'] = self.render_dynamic_tabs()
return context
def render_dynamic_tabs(self):
if plinth.utils.is_user_admin(self.request):
return render_tabs(self.request.path, self.admin_tabs)
else:
return render_tabs(self.request.path, self.user_tabs)
def render_validation_error(self, validation_error, status=400):
context = self.get_context_data()
context['error'] = validation_error
return self.render_to_response(context, status=status)
def render_exception(self, exception, status=500):
context = self.get_context_data()
context['error'] = [str(exception)]
return self.render_to_response(context, status=status)
def catch_exceptions(self, function, request):
try:
return function(request)
except ValidationError as validation_error:
return self.render_validation_error(validation_error)
except Exception as error:
return self.render_exception(error)
def find_button(self, post):
key_filter = (k for k in post.keys() if k.startswith('btn_'))
lst = list(itertools.islice(key_filter, 2))
if len(lst) != 1:
raise ValidationError('Bad post data')
if not isinstance(lst[0], str):
raise ValidationError('Bad post data')
return lst[0][len('btn_'):]
def find_form(self, post):
form_name = post.get('form')
for cls in self.form_classes:
if cls.__name__ == form_name:
return cls(post)
raise ValidationError('Form was unspecified')
class EmailServerView(TabMixin, AppView):
"""Server configuration page"""
app_id = 'email_server'
template_name = 'email_server.html'
audit_modules = ('domain', 'tls', 'rcube')
def get_context_data(self, *args, **kwargs):
dlist = []
for module_name in self.audit_modules:
self._get_audit_results(module_name, dlist)
dlist.sort(key=audit.models.Diagnosis.sorting_key)
context = super().get_context_data(*args, **kwargs)
context['related_diagnostics'] = dlist
return context
def _get_audit_results(self, module_name, dlist):
try:
results = getattr(audit, module_name).get()
except Exception as e:
title = _('Internal error in {0}').format('audit.' + module_name)
diagnosis = audit.models.Diagnosis(title)
diagnosis.critical(str(e))
diagnosis.critical(_('Check syslog for more information'))
results = [diagnosis]
for diagnosis in results:
if diagnosis.action:
diagnosis.action = '%s.%s' % (module_name, diagnosis.action)
if diagnosis.has_failed:
dlist.append(diagnosis)
def post(self, request):
repair_field = request.POST.get('repair')
module_name, sep, action_name = repair_field.partition('.')
if not sep or module_name not in self.audit_modules:
return HttpResponseBadRequest('Bad post data')
self._repair(module_name, action_name)
return redirect(request.path)
def _repair(self, module_name, action_name):
"""Repair the configuration of the given audit module."""
module = getattr(audit, module_name)
if not hasattr(module, 'repair_component'):
return
reload_list = []
try:
reload_list = module.repair_component(action_name)
except Exception:
pass
for service in reload_list:
# plinth.action_utils.service_reload(service)
plinth.actions.superuser_run('service', ['reload', service])
class MyMailView(TabMixin, TemplateView):
template_name = 'my_mail.html'
def get_context_data(self, *args, **kwargs):
context = super().get_context_data(*args, **kwargs)
nam = self.request.user.username
context['has_homedir'] = audit.home.exists_nam(nam)
return context
def post(self, request):
return self.catch_exceptions(self._post, request)
def _post(self, request):
if 'btn_mkhome' not in request.POST:
raise ValidationError('Bad post data')
audit.home.put_nam(request.user.username)
return self.render_to_response(self.get_context_data())
class AliasView(TabMixin, TemplateView):
class Checkboxes:
def __init__(self, post=None, initial=None):
self.models = initial
self.post = post
self.cleaned_data = {}
# HTML rendering
self.sb = io.StringIO()
self.counter = 0
def render(self):
if self.models is None:
raise RuntimeError('Uninitialized form')
if self.sb.tell() > 0:
raise RuntimeError('render has been called')
enabled = [a.email_name for a in self.models if a.enabled]
disabled = [a.email_name for a in self.models if not a.enabled]
self._render_fieldset(enabled, _('Enabled aliases'))
self._render_fieldset(disabled, _('Disabled aliases'))
return self.sb.getvalue()
def _render_fieldset(self, email_names, legend):
if len(email_names) > 0:
self.sb.write('<fieldset class="form-group">')
self.sb.write('<legend>%s</legend>' % escape(legend))
self._render_boxes(email_names)
self.sb.write('</fieldset>')
def _render_boxes(self, email_names):
for email_name in email_names:
input_id = 'cb_alias_%d' % self._count()
value = escape(email_name)
self.sb.write('<div class="form-check">')
self.sb.write('<input type="checkbox" name="alias" ')
self.sb.write('class="form-check-input" ')
self.sb.write('id="%s" value="%s">' % (input_id, value))
self.sb.write('<label class="form-check-label" ')
self.sb.write('for="%s">%s</label>' % (input_id, value))
self.sb.write('</div>')
def _count(self):
self.counter += 1
return self.counter
def is_valid(self):
lst = list(filter(None, self.post.getlist('alias')))
if not lst:
return False
else:
self.cleaned_data['alias'] = lst
return True
template_name = 'email_alias.html'
form_classes = (forms.AliasCreationForm, Checkboxes)
def get_context_data(self, *args, **kwargs):
context = super().get_context_data(*args, **kwargs)
context['form'] = forms.AliasCreationForm()
uid = pwd.getpwnam(self.request.user.username).pw_uid
models = aliases.get(uid)
if len(models) > 0:
form = AliasView.Checkboxes(initial=models)
context['alias_boxes'] = form.render()
else:
context['no_alias'] = True
return context
def post(self, request):
return self.catch_exceptions(self._post, request)
def _post(self, request):
form = self.find_form(request.POST)
button = self.find_button(request.POST)
if not form.is_valid():
raise ValidationError('Form invalid')
if isinstance(form, AliasView.Checkboxes):
if button not in ('delete', 'disable', 'enable'):
raise ValidationError('Bad button')
return self.alias_operation_form_valid(form, button)
if isinstance(form, forms.AliasCreationForm):
if button != 'add':
raise ValidationError('Bad button')
return self.alias_creation_form_valid(form, button)
raise RuntimeError('Unknown form')
def alias_operation_form_valid(self, form, button):
uid = pwd.getpwnam(self.request.user.username).pw_uid
alias_list = form.cleaned_data['alias']
if button == 'delete':
aliases.delete(uid, alias_list)
elif button == 'disable':
aliases.set_disabled(uid, alias_list)
elif button == 'enable':
aliases.set_enabled(uid, alias_list)
return self.render_to_response(self.get_context_data())
def alias_creation_form_valid(self, form, button):
uid = pwd.getpwnam(self.request.user.username).pw_uid
aliases.put(uid, form.cleaned_data['email_name'])
return self.render_to_response(self.get_context_data())
class TLSView(TabMixin, TemplateView):
template_name = 'email_security.html'
class DomainView(TabMixin, TemplateView):
template_name = 'email_domains.html'
def get_context_data(self, *args, **kwargs):
context = super().get_context_data(*args, **kwargs)
fields = audit.domain.get_domain_config()
# If having post data, display the posted values
for field in fields:
field.new_value = self.request.POST.get(field.key, '')
context['fields'] = fields
return context
def post(self, request):
return self.catch_exceptions(self._post, request)
def _post(self, request):
changed = {}
# Skip blank fields
for key, value in request.POST.items():
value = value.strip()
if value:
changed[key] = value
audit.domain.set_keys(changed)
return self.render_to_response(self.get_context_data())
class XmlView(TemplateView):
template_name = 'email_autoconfig.xml'
def render_to_response(self, *args, **kwargs):
kwargs['content_type'] = 'text/xml; charset=utf-8'
response = super().render_to_response(*args, **kwargs)
response['X-Robots-Tag'] = 'noindex, nofollow, noarchive'
return response
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['host'] = self.request.get_host()
return context