mirror of
https://github.com/freedombox/FreedomBox.git
synced 2026-08-05 12:19:31 +00:00
email: Add UI for creating the home directory
email_server: - `-i` option passes all remaining arguments to action - delete unused "touch file" option Views: - delete broken links - add tabs to every page - separate admin tabs from user tabs
This commit is contained in:
parent
2bd1ad4533
commit
62c501e9c7
@ -32,9 +32,9 @@ def main():
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
group = parser.add_mutually_exclusive_group(required=True)
|
||||
group.add_argument('-i', nargs=2, dest='ipc')
|
||||
group.add_argument('-t', nargs=1, dest='touch_file')
|
||||
group.add_argument('-i', nargs='+', dest='ipc')
|
||||
|
||||
# Select the first non-empty dict item
|
||||
adict = vars(parser.parse_args())
|
||||
generator = (kv for kv in adict.items() if kv[1] is not None)
|
||||
subcommand, arguments = next(generator)
|
||||
@ -49,7 +49,7 @@ def main():
|
||||
|
||||
|
||||
@reserved_for_root
|
||||
def subcommand_ipc(module_name, action_name):
|
||||
def subcommand_ipc(module_name, action_name, *args):
|
||||
import plinth.modules.email_server.audit as audit
|
||||
|
||||
# We only run actions defined in the audit module
|
||||
@ -63,18 +63,7 @@ def subcommand_ipc(module_name, action_name):
|
||||
logger.critical('Bad action: %s/%r', module_name, action_name)
|
||||
sys.exit(EXIT_SYNTAX)
|
||||
|
||||
function()
|
||||
|
||||
|
||||
def subcommand_touch_file(path):
|
||||
import pathlib
|
||||
|
||||
if os.getuid() == 0:
|
||||
logger.critical('Do not run the `-t` option as root')
|
||||
sys.exit(EXIT_PERM)
|
||||
|
||||
# mode is influenced by umask
|
||||
pathlib.Path(path).touch(mode=0o660, exist_ok=True)
|
||||
function(*args)
|
||||
|
||||
|
||||
def _log_additional_info():
|
||||
|
||||
@ -63,7 +63,7 @@ class EmailServerApp(plinth.app.App):
|
||||
name=info.name,
|
||||
short_description=info.short_description,
|
||||
icon='roundcube',
|
||||
url=reverse_lazy('email_server:my_aliases'),
|
||||
url=reverse_lazy('email_server:my_mail'),
|
||||
clients=manifest.clients,
|
||||
login_required=True
|
||||
)
|
||||
|
||||
@ -3,8 +3,9 @@
|
||||
Provides diagnosis and repair of email server configuration issues
|
||||
"""
|
||||
|
||||
from . import ldap
|
||||
from . import domain
|
||||
from . import home
|
||||
from . import ldap
|
||||
from . import spam
|
||||
|
||||
__all__ = ['ldap', 'domain', 'spam']
|
||||
__all__ = ['domain', 'home', 'ldap', 'spam']
|
||||
|
||||
71
plinth/modules/email_server/audit/home.py
Normal file
71
plinth/modules/email_server/audit/home.py
Normal file
@ -0,0 +1,71 @@
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
import logging
|
||||
import os
|
||||
import pwd
|
||||
import subprocess
|
||||
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from plinth.actions import superuser_run
|
||||
from plinth.errors import ActionError
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def exists_nam(username):
|
||||
"""Returns True if the user's home directory exists"""
|
||||
try:
|
||||
passwd = pwd.getpwnam(username)
|
||||
except KeyError as e:
|
||||
raise ValidationError(_('User does not exist')) from e
|
||||
return _exists(passwd)
|
||||
|
||||
|
||||
def exists_uid(uid_number):
|
||||
"""Returns True if the user's home directory exists"""
|
||||
try:
|
||||
passwd = pwd.getpwuid(uid_number)
|
||||
except KeyError as e:
|
||||
raise ValidationError(_('User does not exist')) from e
|
||||
return _exists(passwd)
|
||||
|
||||
|
||||
def _exists(passwd):
|
||||
return os.path.exists(passwd.pw_dir)
|
||||
|
||||
|
||||
def put_nam(username):
|
||||
"""Create a home directory for the user (identified by username)"""
|
||||
_put('nam', username)
|
||||
|
||||
|
||||
def put_uid(uid_number):
|
||||
"""Create a home directory for the user (identified by UID)"""
|
||||
_put('uid', str(uid_number))
|
||||
|
||||
|
||||
def _put(arg_type, user_info):
|
||||
try:
|
||||
args = ['-i', 'home', 'mk', arg_type, user_info]
|
||||
superuser_run('email_server', args)
|
||||
except ActionError as e:
|
||||
raise RuntimeError('Action script failure') from e
|
||||
|
||||
|
||||
def action_mk(arg_type, user_info):
|
||||
if arg_type == 'nam':
|
||||
passwd = pwd.getpwnam(user_info)
|
||||
elif arg_type == 'uid':
|
||||
passwd = pwd.getpwuid(int(user_info))
|
||||
else:
|
||||
raise ValueError('Unknown arg_type')
|
||||
|
||||
args = ['sudo', '-n', '--user=#' + str(passwd.pw_uid)]
|
||||
args.extend(['/bin/sh', '-c', 'mkdir -p ~'])
|
||||
completed = subprocess.run(args, capture_output=True)
|
||||
if completed.returncode != 0:
|
||||
logger.critical('Subprocess returned %d', completed.returncode)
|
||||
logger.critical('Stdout: %r', completed.stdout)
|
||||
logger.critical('Stderr: %r', completed.stderr)
|
||||
raise OSError('Could not create home directory')
|
||||
@ -1,24 +1,12 @@
|
||||
{# SPDX-License-Identifier: AGPL-3.0-or-later #}
|
||||
{% extends "app.html" %}
|
||||
{% extends "form_base.html" %}
|
||||
|
||||
{% load bootstrap %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block configuration %}
|
||||
{% block content %}
|
||||
|
||||
{{ tabs|safe }}
|
||||
<h3>{% trans "Alias Management" %}</h3>
|
||||
|
||||
{% if error %}
|
||||
<div class="alert alert-danger" role="alert">
|
||||
<p>
|
||||
{% trans "There was a problem with your request. Please try again." %}
|
||||
</p>
|
||||
{% for message in error %}
|
||||
<p>{{ message }}</p>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{{ block.super }}
|
||||
|
||||
{% if no_alias %}
|
||||
<p>{% trans "You have no email aliases." %}</p>
|
||||
|
||||
@ -3,8 +3,12 @@
|
||||
|
||||
{% load i18n %}
|
||||
|
||||
{% block configuration %}
|
||||
{% block content %}
|
||||
{{ tabs|safe }}
|
||||
{{ block.super }}
|
||||
{% endblock %}
|
||||
|
||||
{% block extra_content %}
|
||||
<p>
|
||||
<a href="/rspamd/">
|
||||
{% trans "Visit Rspamd administration interface" %}
|
||||
|
||||
19
plinth/modules/email_server/templates/form_base.html
Normal file
19
plinth/modules/email_server/templates/form_base.html
Normal file
@ -0,0 +1,19 @@
|
||||
{# SPDX-License-Identifier: AGPL-3.0-or-later #}
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% load i18n %}
|
||||
|
||||
{% block content %}
|
||||
{{ tabs|safe }}
|
||||
{{ block.super }}
|
||||
{% if error %}
|
||||
<div class="alert alert-danger" role="alert">
|
||||
<p>
|
||||
{% trans "There was a problem with your request. Please try again." %}
|
||||
</p>
|
||||
{% for message in error %}
|
||||
<p>{{ message }}</p>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
32
plinth/modules/email_server/templates/my_mail.html
Normal file
32
plinth/modules/email_server/templates/my_mail.html
Normal file
@ -0,0 +1,32 @@
|
||||
{# SPDX-License-Identifier: AGPL-3.0-or-later #}
|
||||
{% extends "form_base.html" %}
|
||||
|
||||
{% load bootstrap %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
{{ block.super }}
|
||||
|
||||
{% if not has_homedir %}
|
||||
<form action="{{ request.path }}" method="post">
|
||||
<div class="alert alert-warning" role="alert">
|
||||
<p>
|
||||
<strong>{% trans "You do not have a home directory." %}</strong>
|
||||
<span>
|
||||
{% trans "Create one to begin receiving emails." %}
|
||||
</span>
|
||||
</p>
|
||||
{% csrf_token %}
|
||||
<button class="btn btn-primary" type="submit" name="btn_mkhome">
|
||||
{% trans "Create home directory" %}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
{% endif %}
|
||||
|
||||
<p>
|
||||
<a href="/roundcube/">Roundcube login</a>
|
||||
</p>
|
||||
|
||||
{% endblock %}
|
||||
@ -6,6 +6,10 @@ from . import views
|
||||
|
||||
urlpatterns = [
|
||||
path('apps/email_server/', views.EmailServerView.as_view(), name='index'),
|
||||
path('apps/email_server/security', views.TLSView.as_view()),
|
||||
|
||||
path('apps/email_server/my_mail',
|
||||
non_admin_view(views.MyMailView.as_view()), name='my_mail'),
|
||||
path('apps/email_server/my_aliases',
|
||||
non_admin_view(views.AliasView.as_view()), name='my_aliases')
|
||||
non_admin_view(views.AliasView.as_view())),
|
||||
]
|
||||
|
||||
@ -3,6 +3,7 @@ import io
|
||||
import itertools
|
||||
import pwd
|
||||
|
||||
import plinth.utils
|
||||
import plinth.views
|
||||
|
||||
from django.core.exceptions import ValidationError
|
||||
@ -10,35 +11,64 @@ from django.utils.html import escape
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.views.generic.base import TemplateView
|
||||
|
||||
from . import forms
|
||||
from . import aliases
|
||||
from . import audit
|
||||
from . import forms
|
||||
|
||||
tabs = [
|
||||
admin_tabs = [
|
||||
('', _('Home')),
|
||||
('alias', _('Alias')),
|
||||
('relay', _('Relay')),
|
||||
('security', _('Security'))
|
||||
('my_mail', _('My Mail')),
|
||||
('my_aliases', _('My Aliases'))
|
||||
]
|
||||
|
||||
user_tabs = [
|
||||
('my_mail', _('Home')),
|
||||
('my_aliases', _('My Aliases'))
|
||||
]
|
||||
|
||||
|
||||
class EmailServerView(plinth.views.AppView):
|
||||
"""Server configuration page"""
|
||||
app_id = 'email_server'
|
||||
form_class = forms.EmailServerForm
|
||||
template_name = 'email_server.html'
|
||||
|
||||
def form_valid(self, form):
|
||||
# old_settings = form.initial
|
||||
# new_status = form.cleaned_data
|
||||
# plinth.actions.superuser_run('email_server', ['--help'])
|
||||
return super().form_valid(form)
|
||||
|
||||
def get_context_data(self, *args, **kwargs):
|
||||
context = super().get_context_data(*args, **kwargs)
|
||||
context['tabs'] = render_tabs(self.request)
|
||||
return context
|
||||
|
||||
|
||||
class MyMailView(TemplateView):
|
||||
template_name = 'my_mail.html'
|
||||
|
||||
def get_context_data(self, *args, **kwargs):
|
||||
context = super().get_context_data(*args, **kwargs)
|
||||
context['tabs'] = render_tabs(self.request)
|
||||
|
||||
nam = self.request.user.username
|
||||
context['has_homedir'] = audit.home.exists_nam(nam)
|
||||
|
||||
return context
|
||||
|
||||
def post(self, request):
|
||||
try:
|
||||
return self._post(request)
|
||||
except ValidationError as validation_error:
|
||||
context = self.get_context_data()
|
||||
context['error'] = validation_error
|
||||
return self.render_to_response(context, status=400)
|
||||
except RuntimeError as runtime_error:
|
||||
context = self.get_context_data()
|
||||
context['error'] = [str(runtime_error)]
|
||||
return self.render_to_response(context, status=500)
|
||||
|
||||
def _post(self, request):
|
||||
if not 'btn_mkhome' 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(TemplateView):
|
||||
class Checkboxes:
|
||||
def __init__(self, post=None, initial=None):
|
||||
@ -103,6 +133,7 @@ class AliasView(TemplateView):
|
||||
def get_context_data(self, *args, **kwargs):
|
||||
context = super().get_context_data(*args, **kwargs)
|
||||
context['form'] = forms.AliasCreationForm()
|
||||
context['tabs'] = render_tabs(self.request)
|
||||
|
||||
uid = pwd.getpwnam(self.request.user.username).pw_uid
|
||||
models = aliases.get(uid)
|
||||
@ -173,9 +204,16 @@ class AliasView(TemplateView):
|
||||
|
||||
|
||||
def render_tabs(request):
|
||||
if plinth.utils.is_user_admin(request):
|
||||
return _render_tabs(request, admin_tabs)
|
||||
else:
|
||||
return _render_tabs(request, user_tabs)
|
||||
|
||||
|
||||
def _render_tabs(request, tab_data):
|
||||
sb = io.StringIO()
|
||||
sb.write('<ul class="nav nav-tabs">')
|
||||
for page_name, link_text in tabs:
|
||||
for page_name, link_text in tab_data:
|
||||
if request.path.endswith('/' + page_name):
|
||||
cls = 'active'
|
||||
else:
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user