diff --git a/backend/backend/settings.py b/backend/backend/settings.py index 5226316..bf7e141 100644 --- a/backend/backend/settings.py +++ b/backend/backend/settings.py @@ -43,6 +43,7 @@ AUTH_LDAP_USER_SEARCH = LDAPSearch( AUTHENTICATION_BACKENDS = [ "django_auth_ldap.backend.LDAPBackend", "django.contrib.auth.backends.ModelBackend", + "multimail.auth_backends.MailboxBackend", ] LOGIN_REDIRECT_URL = '/' diff --git a/backend/multimail/actions.py b/backend/multimail/actions.py index 75b92f0..dfb4911 100644 --- a/backend/multimail/actions.py +++ b/backend/multimail/actions.py @@ -1,13 +1,13 @@ from django.http import HttpResponseRedirect, Http404 from django.urls import reverse -from django.contrib.auth.decorators import login_required +from django.contrib.auth.decorators import user_passes_test from django.contrib.auth import logout as auth_logout from .models import Domain, Mailbox, Alias -from .owner import user_from_request +from .owner import user_from_request, may_own_domain -@login_required(login_url='/login/') +@user_passes_test(may_own_domain, login_url='/login/') def delete_domain(request, domain_id): try: user = user_from_request(request) @@ -19,7 +19,7 @@ def delete_domain(request, domain_id): return HttpResponseRedirect(reverse('multimail:domains')) -@login_required(login_url='/login/') +@user_passes_test(may_own_domain, login_url='/login/') def delete_mailbox(request, mailbox_id): try: user = user_from_request(request) @@ -31,7 +31,7 @@ def delete_mailbox(request, mailbox_id): return HttpResponseRedirect(reverse('multimail:mailboxes')) -@login_required(login_url='/login/') +@user_passes_test(may_own_domain, login_url='/login/') def delete_alias(request, alias_id): try: user = user_from_request(request) diff --git a/backend/multimail/auth_backends.py b/backend/multimail/auth_backends.py new file mode 100644 index 0000000..bbfb565 --- /dev/null +++ b/backend/multimail/auth_backends.py @@ -0,0 +1,47 @@ +from django.contrib.auth.backends import BaseBackend +from django.contrib.auth.models import AbstractUser +import re + +from multimail.models import Mailbox + + +class MailUser(AbstractUser): + def __init__(self, *args, **kwargs): + m = kwargs['mailbox'] + super().__init__(username=m.username+"@"+m.domain) + self.id = m.id + self.mailbox = m + + def is_active(self): + return True + + def is_authenticated(self): + return True + + def save(self, *args, **kwargs): + pass + + +class MailboxBackend(BaseBackend): + + def authenticate(self, request, **kwargs): + m = re.match("([^@]+)@([^@]+)", kwargs['username']) + if not m: + return None + username, domain = m.groups() + password = kwargs['password'] + try: + mailbox = Mailbox.objects.get(username=username, domain=domain) + if mailbox.check_password(password) is True: + return MailUser(mailbox=mailbox) + else: + return None + except Mailbox.DoesNotExist: + return None + + def get_user(self, user_id): + try: + mailbox = Mailbox.objects.get(pk=user_id) + return MailUser(mailbox=mailbox) + except Mailbox.DoesNotExist: + return None \ No newline at end of file diff --git a/backend/multimail/forms.py b/backend/multimail/forms.py index fe0650b..d51fbcf 100644 --- a/backend/multimail/forms.py +++ b/backend/multimail/forms.py @@ -1,11 +1,12 @@ -from django.contrib.auth.decorators import login_required +from django.contrib.auth.decorators import login_required, user_passes_test from django.forms import ModelForm, CharField, PasswordInput from django.http import HttpResponseRedirect, Http404 from django.shortcuts import render from django.db import IntegrityError +from multimail import owner from multimail.models import Domain, Mailbox, Alias -from multimail.owner import user_from_request +from multimail.owner import user_from_request, may_own_domain class DomainForm(ModelForm): @@ -35,7 +36,23 @@ class AliasForm(ModelForm): fields = '__all__' -@login_required(login_url='/login/') +class PasswdForm(ModelForm): + plain_password = CharField(label='Password', required=False, widget=PasswordInput()) + + class Meta: + model = Mailbox + fields = ['plain_password'] + + def save(self, commit=True): + mailbox = super(PasswdForm, self).save(commit=False) + if not self.cleaned_data["plain_password"] == '': + mailbox.set_password(self.cleaned_data["plain_password"]) + if commit: + mailbox.save() + return mailbox + + +@user_passes_test(owner.may_own_domain, login_url='/login/') def edit_domain(request, domain_id): try: user = user_from_request(request) @@ -58,7 +75,7 @@ def edit_domain(request, domain_id): return render(request, 'multimail/edit.html', {'form': form}) -@login_required(login_url='/login/') +@user_passes_test(owner.may_own_domain, login_url='/login/') def new_domain(request): user = user_from_request(request) if request.method == 'POST': @@ -78,7 +95,7 @@ def new_domain(request): return render(request, 'multimail/edit.html', {'form': form}) -@login_required(login_url='/login/') +@user_passes_test(owner.may_own_domain, login_url='/login/') def edit_mailbox(request, mailbox_id): try: user = user_from_request(request) @@ -102,7 +119,7 @@ def edit_mailbox(request, mailbox_id): return render(request, 'multimail/edit.html', {'form': form}) -@login_required(login_url='/login/') +@user_passes_test(owner.may_own_domain, login_url='/login/') def new_mailbox(request): if request.method == 'POST': user = user_from_request(request) @@ -122,7 +139,7 @@ def new_mailbox(request): return render(request, 'multimail/edit.html', {'form': form}) -@login_required(login_url='/login/') +@user_passes_test(owner.may_own_domain, login_url='/login/') def edit_alias(request, alias_id): try: user = user_from_request(request) @@ -146,7 +163,7 @@ def edit_alias(request, alias_id): return render(request, 'multimail/edit.html', {'form': form}) -@login_required(login_url='/login/') +@user_passes_test(owner.may_own_domain, login_url='/login/') def new_alias(request): if request.method == 'POST': user = user_from_request(request) @@ -164,3 +181,24 @@ def new_alias(request): form = AliasForm() return render(request, 'multimail/edit.html', {'form': form}) + + +@login_required(login_url='/login/') +def mailbox_passwd(request): + if may_own_domain(request.user): + raise Http404 + mailbox = request.user.mailbox + if request.method == 'POST': + form = PasswdForm(request.POST, instance=mailbox) + try: + if form.is_valid(): + form.save() + return HttpResponseRedirect('') + + except IntegrityError as e: + form.add_error(None, e) + + else: + form = PasswdForm(instance=mailbox) + + return render(request, 'multimail/mailboxindex.html', {'form': form}) diff --git a/backend/multimail/models.py b/backend/multimail/models.py index 605f6b9..41a193c 100644 --- a/backend/multimail/models.py +++ b/backend/multimail/models.py @@ -1,4 +1,7 @@ import crypt +from hmac import compare_digest as compare_hash +from django.contrib.auth.models import AbstractBaseUser +from django.contrib.auth.models import PermissionsMixin from django.db import models @@ -48,6 +51,13 @@ class Mailbox(models.Model): def set_password(self, password): self.password = '{SHA512-CRYPT}' + crypt.crypt(password) + def check_password(self, password): + hash = crypt.crypt(password, self.password.lstrip('{SHA512-CRYPT}')) + return compare_hash(hash, self.password.lstrip('{SHA512-CRYPT}')) + + def is_active(self): + return bool(self.enabled) + def __str__(self): return self.username + '@' + self.domain diff --git a/backend/multimail/owner.py b/backend/multimail/owner.py index e2ddcef..110f971 100644 --- a/backend/multimail/owner.py +++ b/backend/multimail/owner.py @@ -1,7 +1,16 @@ +def classify_user(user): + if hasattr(user, 'ldap_user'): + return {'name': user.username, 'source': 'ldap'} + elif hasattr(user, 'mailbox'): + return {'name': user.username, 'source': 'mail'} + else: + return {'name': user.username, 'source': 'system'} + def user_from_request(request): - if hasattr(request.user, 'ldap_user'): - # print(request.user.ldap_user.attrs.data) - return {'name': request.user.username, 'source': 'ldap'} - else: - return {'name': request.user.username, 'source': 'system'} \ No newline at end of file + return classify_user(request.user) + + +def may_own_domain(user): + u = classify_user(user) + return u["source"] == "system" or u["source"] == "ldap" diff --git a/backend/multimail/templates/multimail/mailboxindex.html b/backend/multimail/templates/multimail/mailboxindex.html new file mode 100644 index 0000000..46d2781 --- /dev/null +++ b/backend/multimail/templates/multimail/mailboxindex.html @@ -0,0 +1,40 @@ +{% extends 'multimail/base.html' %} +{% load bootstrap4 %} + +{% block main %} + + +
{{ error_message }}
{% endif %} + +