Remove admin extension from this repo.

This commit is contained in:
Antoine Nguyen
2015-02-06 09:20:16 +01:00
parent 46e5cb5e85
commit 8d621f247f
73 changed files with 4 additions and 6176 deletions
@@ -57,7 +57,6 @@ MODOBOA_APPS = (
'modoboa.core',
'modoboa.lib',
# Modoboa extensions here.
'modoboa.extensions.admin',
'modoboa.extensions.postfix_relay_domains',
'modoboa.extensions.limits',
'modoboa.extensions.postfix_autoreply',
+2 -1
View File
@@ -41,7 +41,8 @@ def dologin(request):
if user.group == "SimpleUsers":
nextlocation = reverse("topredirection")
else:
nextlocation = reverse("admin:domain_list")
# FIXME
nextlocation = reverse("modoboa_admin:domain_list")
return HttpResponseRedirect(nextlocation)
error = _(
"Your username and password didn't match. Please try again.")
-100
View File
@@ -1,100 +0,0 @@
from django import forms
from django.conf import settings
from django.utils.translation import ugettext_lazy
from modoboa.lib.form_utils import YesNoField, SeparatorField
from modoboa.lib.sysutils import exec_cmd
from modoboa.lib import parameters
import os
class AdminParametersForm(parameters.AdminParametersForm):
app = "admin"
mbsep = SeparatorField(label=ugettext_lazy("Mailboxes"))
handle_mailboxes = YesNoField(
label=ugettext_lazy("Handle mailboxes on filesystem"),
initial="no",
help_text=ugettext_lazy(
"Rename or remove mailboxes on the filesystem when they get"
" renamed or removed within Modoboa"
)
)
mailboxes_owner = forms.CharField(
label=ugettext_lazy("Mailboxes owner"),
initial="vmail",
help_text=ugettext_lazy(
"The UNIX account who owns mailboxes on the filesystem"
)
)
default_domain_quota = forms.IntegerField(
label=ugettext_lazy("Default domain quota"),
initial=0,
help_text=ugettext_lazy(
"Default quota (in MB) applied to freshly created domains with no "
"value specified. A value of 0 means no quota."
)
)
auto_account_removal = YesNoField(
label=ugettext_lazy("Automatic account removal"),
initial="no",
help_text=ugettext_lazy(
"When a mailbox is removed, also remove the associated account")
)
auto_create_domain_and_mailbox = YesNoField(
label=ugettext_lazy("Automatic domain/mailbox creation"),
initial="yes",
help_text=ugettext_lazy(
"Create a domain and a mailbox when an account is automatically "
"created."
)
)
# Visibility rules
visibility_rules = {
"mailboxes_owner": "handle_mailboxes=yes",
}
def __init__(self, *args, **kwargs):
super(AdminParametersForm, self).__init__(*args, **kwargs)
self.field_widths = {
"default_domain_quota": 2
}
hide_fields = False
dpath = None
code, output = exec_cmd("which dovecot")
if not code:
dpath = output.strip()
else:
known_paths = getattr(
settings, "DOVECOT_LOOKUP_PATH",
("/usr/sbin/dovecot", "/usr/local/sbin/dovecot")
)
for fpath in known_paths:
if os.path.isfile(fpath) and os.access(fpath, os.X_OK):
dpath = fpath
if dpath:
try:
code, version = exec_cmd("%s --version" % dpath)
except OSError:
hide_fields = True
else:
if code or not version.strip().startswith("2"):
hide_fields = True
else:
hide_fields = True
if hide_fields:
del self.fields["handle_mailboxes"]
del self.fields["mailboxes_owner"]
def clean_default_domain_quota(self):
"""Ensure quota is a positive integer."""
if self.cleaned_data['default_domain_quota'] < 0:
raise forms.ValidationError(
ugettext_lazy('Must be a positive integer')
)
return self.cleaned_data['default_domain_quota']
-79
View File
@@ -1,79 +0,0 @@
"""
Fixtures factories.
"""
import factory
from modoboa.core.factories import PermissionFactory, UserFactory
from . import models
class DomainFactory(PermissionFactory):
"""Factory to create Domains."""
class Meta:
model = models.Domain
django_get_or_create = ("name", )
quota = 10
enabled = True
class MailboxFactory(PermissionFactory):
class Meta:
model = models.Mailbox
domain = factory.SubFactory(DomainFactory)
user = factory.SubFactory(UserFactory)
quota = 10
enabled = True
class AliasFactory(PermissionFactory):
class Meta:
model = models.Alias
enabled = True
def populate_database():
"""Create test data.
2 domains, 1 domain admin, 2 users
"""
dom = DomainFactory(name="test.com")
admin = UserFactory(
username="admin@test.com", groups=('DomainAdmins', ),
password='{PLAIN}toto'
)
MailboxFactory(address='admin', domain=dom, user=admin)
account = UserFactory.create(
username="user@test.com", groups=('SimpleUsers',),
)
MailboxFactory.create(address='user', domain=dom, user=account)
fwd = AliasFactory.create(
address='forward', domain=dom, extmboxes='user@external.com'
)
al = AliasFactory.create(
address='alias', domain=dom
)
al.mboxes.add(account.mailbox_set.all()[0])
AliasFactory.create(
address='postmaster', domain=dom, extmboxes='toto@titi.com,test@truc.fr'
)
dom.add_admin(admin)
dom2 = DomainFactory.create(name='test2.com', quota=0)
admin = UserFactory.create(
username='admin@test2.com', groups=('DomainAdmins',),
password='{PLAIN}toto'
)
MailboxFactory.create(address='admin', domain=dom2, user=admin)
u = UserFactory.create(
username='user@test2.com', groups=('SimpleUsers',)
)
MailboxFactory.create(address='user', domain=dom2, user=u)
dom2.add_admin(admin)
-310
View File
@@ -1,310 +0,0 @@
[
{
"pk": 1,
"model": "core.user",
"fields": {
"username": "admin",
"first_name": "",
"last_name": "",
"is_active": true,
"is_superuser": true,
"is_staff": false,
"last_login": "2012-05-03T16:58:51+01:00",
"groups": [],
"user_permissions": [],
"password": "{CRYPT}dTTsGDkA5ZHKg",
"email": "",
"date_joined": "2012-01-26T15:19:25+01:00"
}
},
{
"pk": 2,
"model": "core.user",
"fields": {
"username": "admin@demo.local",
"first_name": "Demo",
"last_name": "Administrator",
"is_active": true,
"is_superuser": false,
"is_staff": false,
"last_login": "2012-05-24T05:00:12+01:00",
"groups": [1],
"user_permissions": [],
"password": "{CRYPT}SJ2bQWwabfH/6",
"email": "admin@demo.local",
"date_joined": "2012-05-03T16:50:15+01:00"
}
},
{
"pk": 4,
"model": "core.user",
"fields": {
"username": "user@demo.local",
"first_name": "Demo", "last_name": "User",
"is_active": true,
"is_superuser": false,
"is_staff": false,
"last_login": "2012-05-03T16:56:50+01:00",
"groups": [2],
"user_permissions": [],
"password": "{CRYPT}2iAFXtxUA6.7Y",
"email": "user@demo.local",
"date_joined": "2012-05-03T16:56:50+01:00"
}
},
{
"pk": 2,
"model": "core.objectaccess",
"fields": {
"is_owner": true,
"user": 1,
"content_type": ["admin", "domain"],
"object_id": 1
}
},
{
"pk": 3,
"model": "core.objectaccess",
"fields": {
"is_owner": true,
"user": 1,
"content_type": ["core", "user"],
"object_id": 2
}
},
{
"pk": 4,
"model": "core.objectaccess",
"fields": {
"is_owner": true,
"user": 1,
"content_type": ["admin", "mailbox"],
"object_id": 1
}
},
{
"pk": 5,
"model": "core.objectaccess",
"fields": {
"is_owner": true,
"user": 1,
"content_type": ["admin", "alias"],
"object_id": 1
}
},
{
"pk": 6,
"model": "core.objectaccess",
"fields": {
"is_owner": false,
"user": 2,
"content_type": ["admin", "domain"],
"object_id": 1
}
},
{
"pk": 7,
"model": "core.objectaccess",
"fields": {
"is_owner": false,
"user": 2,
"content_type": ["admin", "mailbox"],
"object_id": 1
}
},
{
"pk": 8,
"model": "core.objectaccess",
"fields": {
"is_owner": false,
"user": 2,
"content_type": ["core", "user"],
"object_id": 2
}
},
{
"pk": 9,
"model": "core.objectaccess",
"fields": {
"is_owner": false,
"user": 2,
"content_type": ["admin",
"alias"],
"object_id": 1}},
{
"pk": 13,
"model": "core.objectaccess",
"fields": {
"is_owner": true,
"user": 1,
"content_type": ["core",
"user"],
"object_id": 4}},
{
"pk": 14,
"model": "core.objectaccess",
"fields": {
"is_owner": true,
"user": 1,
"content_type": ["admin",
"mailbox"],
"object_id": 3}},
{
"pk": 15,
"model": "core.objectaccess",
"fields": {
"is_owner": false,
"user": 2,
"content_type": ["admin",
"mailbox"],
"object_id": 3}},
{
"pk": 16,
"model": "core.objectaccess",
"fields": {
"is_owner": false,
"user": 2,
"content_type": ["core",
"user"],
"object_id": 4}},
{
"pk": 1,
"model": "admin.objectdates",
"fields": {
"creation": "2012-05-03T16:49:40+01:00",
"last_modification": "2012-05-03T16:49:40+01:00"}},
{
"pk": 2,
"model": "admin.objectdates",
"fields": {
"creation": "2012-05-03T16:50:15+01:00",
"last_modification": "2012-05-03T16:50:36+01:00"}},
{
"pk": 3,
"model": "admin.objectdates",
"fields": {
"creation": "2012-05-03T16:50:15+01:00",
"last_modification": "2012-05-03T16:50:15+01:00"}},
{
"pk": 4,
"model": "admin.objectdates",
"fields": {
"creation": "2012-05-03T16:51:04+01:00",
"last_modification": "2012-05-03T16:51:04+01:00"}},
{
"pk": 5,
"model": "admin.objectdates",
"fields": {
"creation": "2012-05-03T16:56:50+01:00",
"last_modification": "2012-05-03T16:56:50+01:00"}},
{
"pk": 1,
"model": "admin.domain",
"fields": {
"dates": 1,
"enabled": true,
"name": "demo.local",
"quota": 10}},
{
"pk": 1,
"model": "admin.mailbox",
"fields": {
"dates": 2,
"quota": 10,
"user": 2,
"domain": 1,
"address": "admin"
}
},
{
"pk": 3,
"model": "admin.mailbox",
"fields": {
"dates": 5,
"quota": 10,
"user": 4,
"domain": 1,
"address": "user"
}
},
{
"pk": 1,
"model": "admin.alias",
"fields": {
"dates": 3,
"enabled": true,
"domain": 1,
"mboxes": [1],
"address": "postmaster",
"extmboxes": ""}},
{
"pk": 1,
"model": "core.extension",
"fields": {
"enabled": true,
"name": "limits"}},
{
"pk": 2,
"model": "core.extension",
"fields": {
"enabled": true,
"name": "postfix_autoreply"}},
{
"pk": 3,
"model": "core.extension",
"fields": {
"enabled": true,
"name": "stats"}},
{
"pk": 4,
"model": "core.extension",
"fields": {
"enabled": true,
"name": "amavis"}},
{
"pk": 5,
"model": "core.extension",
"fields": {
"enabled": true,
"name": "webmail"}},
{
"pk": 6,
"model": "core.extension",
"fields": {
"enabled": true,
"name": "sievefilters"}},
{
"pk": 7,
"model": "core.extension",
"fields": {
"enabled": true,
"name": "postfix_relay_domains"
}
},
{
"pk": 8,
"model": "core.extension",
"fields": {
"enabled": true,
"name": "radicale"
}
},
{
"pk": "admin@demo.local",
"model": "admin.quota",
"fields": {
"username": "admin@demo.local",
"bytes": 0,
"messages": 0
}
},
{
"pk": "user@demo.local",
"model": "admin.quota",
"fields": {
"username": "user@demo.local",
"bytes": 0,
"messages": 0
}
}
]
@@ -1,19 +0,0 @@
from .domain import (
DomainFormGeneral, DomainFormOptions, DomainForm, DomainWizard
)
from .account import (
AccountFormGeneral, AccountFormMail, AccountPermissionsForm,
AccountForm, AccountWizard
)
from .alias import AliasForm
from .forward import ForwardForm
from .import_ import ImportDataForm, ImportIdentitiesForm
from .export import ExportDataForm, ExportDomainsForm, ExportIdentitiesForm
__all__ = [
'DomainFormGeneral', 'DomainFormOptions', 'DomainForm',
'AccountFormGeneral', 'AccountFormMail', 'AccountPermissionsForm',
'AccountForm', 'AliasForm', 'ImportDataForm', 'ImportIdentitiesForm',
'ExportDataForm', 'ExportDomainsForm', 'ExportIdentitiesForm',
'ForwardForm', 'DomainWizard', 'AccountWizard'
]
-478
View File
@@ -1,478 +0,0 @@
"""Forms related to accounts management."""
from collections import OrderedDict
from django import forms
from django.core.urlresolvers import reverse
from django.http import QueryDict
from django.utils.translation import ugettext as _, ugettext_lazy
from modoboa.core.models import User
from modoboa.extensions.admin.models import (
Domain, Mailbox, Alias
)
from modoboa.lib import events, parameters
from modoboa.lib.email_utils import split_mailbox
from modoboa.lib.exceptions import PermDeniedException, Conflict, NotFound
from modoboa.lib.form_utils import (
DomainNameField, DynamicForm, TabForms, WizardForm
)
from modoboa.lib.permissions import get_account_roles
from modoboa.lib.web_utils import render_to_json_response
class AccountFormGeneral(forms.ModelForm):
username = forms.CharField(
label=ugettext_lazy("Username"),
help_text=ugettext_lazy(
"The user's name. Must be a valid e-mail address for simple users "
"or administrators with a mailbox."
)
)
role = forms.ChoiceField(
label=ugettext_lazy("Role"),
choices=[('', ugettext_lazy("Choose"))],
help_text=ugettext_lazy("What level of permission this user will have")
)
password1 = forms.CharField(
label=ugettext_lazy("Password"), widget=forms.widgets.PasswordInput
)
password2 = forms.CharField(
label=ugettext_lazy("Confirmation"),
widget=forms.widgets.PasswordInput,
help_text=ugettext_lazy(
"Enter the same password as above, for verification."
)
)
class Meta:
model = User
fields = ("username", "first_name", "last_name", "role", "is_active")
def __init__(self, user, *args, **kwargs):
super(AccountFormGeneral, self).__init__(*args, **kwargs)
self.fields = OrderedDict(
(key, self.fields[key]) for key in
['role', 'username', 'first_name', 'last_name', 'password1',
'password2', 'is_active']
)
self.fields["is_active"].label = _("Enabled")
self.user = user
if user.group == "DomainAdmins":
self.fields["role"] = forms.CharField(
label="",
widget=forms.HiddenInput(attrs={"class": "form-control"}),
required=False
)
else:
self.fields["role"].choices = [('', ugettext_lazy("Choose"))]
self.fields["role"].choices += \
get_account_roles(user, kwargs['instance']) \
if 'instance' in kwargs else get_account_roles(user)
if "instance" in kwargs:
account = kwargs["instance"]
domain_disabled = account.mailbox_set.count() and \
not account.mailbox_set.all()[0].domain.enabled
if domain_disabled:
self.fields["is_active"].widget.attrs['disabled'] = "disabled"
if args:
if args[0].get("password1", "") == "" \
and args[0].get("password2", "") == "":
self.fields["password1"].required = False
self.fields["password2"].required = False
if domain_disabled:
del self.fields["is_active"]
self.fields["role"].initial = account.group
if not account.is_local \
and parameters.get_admin(
"LDAP_AUTH_METHOD", app="core") == "directbind":
del self.fields["password1"]
del self.fields["password2"]
def domain_is_disabled(self):
"""Little shortcut to get the domain's state.
We need this information inside a template and the form is the
only object available...
"""
if not self.instance.mailbox_set.count():
return False
return not self.instance.mailbox_set.all()[0].domain.enabled
def clean_role(self):
if self.user.group == "DomainAdmins":
if self.instance == self.user:
return "DomainAdmins"
return "SimpleUsers"
return self.cleaned_data["role"]
def clean_username(self):
from django.core.validators import validate_email
if "role" not in self.cleaned_data:
return self.cleaned_data["username"]
if self.cleaned_data["role"] != "SimpleUsers":
return self.cleaned_data["username"]
uname = self.cleaned_data["username"].lower()
validate_email(uname)
return uname
def clean_password2(self):
password1 = self.cleaned_data.get("password1", "")
password2 = self.cleaned_data["password2"]
if password1 != password2:
raise forms.ValidationError(
_("The two password fields didn't match."))
return password2
def save(self, commit=True):
account = super(AccountFormGeneral, self).save(commit=False)
if self.user == account and not self.cleaned_data["is_active"]:
raise PermDeniedException(_("You can't disable your own account"))
if commit:
if "password1" in self.cleaned_data \
and self.cleaned_data["password1"] != "":
account.set_password(self.cleaned_data["password1"])
account.save()
account.set_role(self.cleaned_data["role"])
return account
class AccountFormMail(forms.Form, DynamicForm):
"""Form to handle mail part."""
email = forms.EmailField(label=ugettext_lazy("E-mail"), required=False)
quota = forms.IntegerField(
label=ugettext_lazy("Quota"),
required=False,
help_text=_("Quota in MB for this mailbox. Define a custom value or "
"use domain's default one. Leave empty to define an "
"unlimited value (not allowed for domain "
"administrators)."),
widget=forms.widgets.TextInput(attrs={"class": "form-control"})
)
quota_act = forms.BooleanField(required=False)
aliases = forms.EmailField(
label=ugettext_lazy("Alias(es)"),
required=False,
help_text=ugettext_lazy(
"Alias(es) of this mailbox. Indicate only one address per input, "
"press ENTER to add a new input. Use the '*' character to create "
"a 'catchall' alias (ex: *@domain.tld)."
)
)
def __init__(self, *args, **kwargs):
if "instance" in kwargs:
self.mb = kwargs["instance"]
del kwargs["instance"]
else:
self.mb = None
super(AccountFormMail, self).__init__(*args, **kwargs)
self.field_widths = {
"quota": 3
}
self.extra_fields = []
result = events.raiseQueryEvent('ExtraFormFields', 'mailform', self.mb)
for fname, field in result:
self.fields[fname] = field
self.extra_fields.append(fname)
if self.mb is not None:
self.fields["email"].required = True
cpt = 1
for alias in self.mb.alias_set.all():
if len(alias.get_recipients()) >= 2:
continue
name = "aliases_%d" % cpt
self._create_field(forms.EmailField, name, alias.full_address)
cpt += 1
self.fields["email"].initial = self.mb.full_address
self.fields["quota_act"].initial = self.mb.use_domain_quota
if not self.mb.use_domain_quota and self.mb.quota:
self.fields["quota"].initial = self.mb.quota
else:
self.fields["quota_act"].initial = True
if len(args) and isinstance(args[0], QueryDict):
self._load_from_qdict(args[0], "aliases", forms.EmailField)
def clean_email(self):
"""Ensure lower case emails"""
return self.cleaned_data["email"].lower()
def clean(self):
"""Custom fields validation.
Check if quota is >= 0 only when the domain value is not used.
"""
super(AccountFormMail, self).clean()
if not self.cleaned_data["quota_act"] \
and self.cleaned_data['quota'] is not None:
if self.cleaned_data["quota"] < 0:
self.add_error("quota", _("Must be a positive integer"))
return self.cleaned_data
def create_mailbox(self, user, account):
"""Create a mailbox associated to :kw:`account`."""
locpart, domname = split_mailbox(self.cleaned_data["email"])
try:
domain = Domain.objects.get(name=domname)
except Domain.DoesNotExist:
raise NotFound(_("Domain does not exist"))
if not user.can_access(domain):
raise PermDeniedException
try:
Mailbox.objects.get(address=locpart, domain=domain)
except Mailbox.DoesNotExist:
pass
else:
raise Conflict(
_("Mailbox %s already exists" % self.cleaned_data["email"])
)
events.raiseEvent("CanCreate", user, "mailboxes")
self.mb = Mailbox(address=locpart, domain=domain, user=account,
use_domain_quota=self.cleaned_data["quota_act"])
self.mb.set_quota(self.cleaned_data["quota"],
user.has_perm("admin.add_domain"))
self.mb.save(creator=user)
def update_mailbox(self, user, account):
newaddress = None
if self.cleaned_data["email"] != self.mb.full_address:
newaddress = self.cleaned_data["email"]
elif (account.group == "SimpleUsers" and
account.username != self.mb.full_address):
newaddress = account.username
if newaddress is not None:
self.mb.old_full_address = self.mb.full_address
local_part, domname = split_mailbox(newaddress)
try:
domain = Domain.objects.get(name=domname)
except Domain.DoesNotExist:
raise NotFound(_("Domain does not exist"))
if not user.can_access(domain):
raise PermDeniedException
self.mb.rename(local_part, domain)
self.mb.use_domain_quota = self.cleaned_data["quota_act"]
override_rules = True \
if not self.mb.quota or user.has_perm("admin.add_domain") \
else False
self.mb.set_quota(self.cleaned_data["quota"], override_rules)
self.mb.save()
events.raiseEvent('MailboxModified', self.mb)
def _update_aliases(self, user, account):
"""Update mailbox aliases."""
aliases = []
for name, value in self.cleaned_data.iteritems():
if not name.startswith("aliases"):
continue
if value == "":
continue
aliases.append(value.lower())
for alias in self.mb.alias_set.all():
if alias.full_address not in aliases:
if len(alias.get_recipients()) >= 2:
continue
alias.delete()
else:
aliases.remove(alias.full_address)
if not aliases:
return
events.raiseEvent(
"CanCreate", user, "mailbox_aliases", len(aliases)
)
for alias in aliases:
local_part, domname = split_mailbox(alias)
try:
self.mb.alias_set.get(address=local_part, domain__name=domname)
except Alias.DoesNotExist:
pass
else:
continue
al = Alias(address=local_part, enabled=account.is_active)
al.domain = Domain.objects.get(name=domname)
al.save(int_rcpts=[self.mb])
al.post_create(user)
def save(self, user, account):
"""Save or update account mailbox."""
if self.cleaned_data["email"] == "":
return None
if self.cleaned_data["quota_act"]:
self.cleaned_data["quota"] = None
if not hasattr(self, "mb") or self.mb is None:
self.create_mailbox(user, account)
else:
self.update_mailbox(user, account)
events.raiseEvent(
'SaveExtraFormFields', 'mailform', self.mb, self.cleaned_data
)
account.email = self.cleaned_data["email"]
account.save()
self._update_aliases(user, account)
return self.mb
class AccountPermissionsForm(forms.Form, DynamicForm):
domains = DomainNameField(
label=ugettext_lazy("Domain(s)"),
required=False,
help_text=ugettext_lazy("Domain(s) that user administrates")
)
def __init__(self, *args, **kwargs):
if "instance" in kwargs:
self.account = kwargs["instance"]
del kwargs["instance"]
super(AccountPermissionsForm, self).__init__(*args, **kwargs)
if not hasattr(self, "account") or self.account is None:
return
for pos, dom in enumerate(Domain.objects.get_for_admin(self.account)):
name = "domains_%d" % (pos + 1)
self._create_field(DomainNameField, name, dom.name)
if len(args) and isinstance(args[0], QueryDict):
self._load_from_qdict(args[0], "domains", DomainNameField)
def save(self):
current_domains = [
dom.name for dom in Domain.objects.get_for_admin(self.account)
]
for name, value in self.cleaned_data.items():
if not name.startswith("domains"):
continue
if value in ["", None]:
continue
if value not in current_domains:
domain = Domain.objects.get(name=value)
domain.add_admin(self.account)
for domain in Domain.objects.get_for_admin(self.account):
if not filter(lambda name: self.cleaned_data[name] == domain.name,
self.cleaned_data.keys()):
domain.remove_admin(self.account)
class AccountForm(TabForms):
"""Account edition form."""
def __init__(self, request, *args, **kwargs):
self.user = request.user
self.forms = [
dict(id="general", title=_("General"),
formtpl="admin/account_general_form.html",
cls=AccountFormGeneral,
new_args=[self.user], mandatory=True),
dict(id="mail", title=_("Mail"), formtpl="admin/mailform.html",
cls=AccountFormMail),
dict(
id="perms", title=_("Permissions"),
formtpl="admin/permsform.html",
cls=AccountPermissionsForm
)
]
cbargs = [self.user]
if "instances" in kwargs:
cbargs += [kwargs["instances"]["general"]]
self.forms += events.raiseQueryEvent("ExtraAccountForm", *cbargs)
super(AccountForm, self).__init__(request, *args, **kwargs)
def extra_context(self, context):
account = self.instances["general"]
context.update({
'title': account.username,
'formid': 'accountform',
'action': reverse("admin:account_change", args=[account.id]),
})
def check_perms(self, account):
if account.is_superuser:
return False
return self.user.has_perm("admin.add_domain") \
and account.has_perm("core.add_user")
def _before_is_valid(self, form):
if form["id"] == "general":
return True
if hasattr(self, "check_%s" % form["id"]):
if not getattr(self, "check_%s" % form["id"])(self.account):
return False
return True
extra_forms = events.raiseQueryEvent(
"CheckExtraAccountForm", self.account, form)
if False in extra_forms:
return False
return True
def is_valid(self):
"""Two steps validation.
"""
self.instances["general"].oldgroup = self.instances["general"].group
if super(AccountForm, self).is_valid(mandatory_only=True):
self.account = self.forms[0]["instance"].save()
return super(AccountForm, self).is_valid(optional_only=True)
return False
def save(self):
"""Custom save method
As forms interact with each other, it is simpler to make
custom code to save them.
"""
events.raiseEvent(
"AccountModified", self.instances["general"], self.account
)
self.forms[1]["instance"].save(self.user, self.account)
if len(self.forms) <= 2:
return
for f in self.forms[2:]:
f["instance"].save()
def done(self):
return render_to_json_response(_("Account updated"))
class AccountWizard(WizardForm):
"""Account creation wizard.
"""
def __init__(self, request):
super(AccountWizard, self).__init__(request)
self.add_step(
AccountFormGeneral, _("General"), new_args=[request.user]
)
self.add_step(
AccountFormMail, _("Mail"), formtpl="admin/mailform.html"
)
def extra_context(self, context):
context.update({
'title': _("New account"),
'action': reverse("admin:account_add"),
'formid': 'newaccount_form'
})
def done(self):
from modoboa.lib.web_utils import render_to_json_response
account = self.first_step.form.save()
account.post_create(self.request.user)
mailform = self.steps[1].form
mailform.save(self.request.user, account)
return render_to_json_response(_("Account created"))
-152
View File
@@ -1,152 +0,0 @@
"""Forms related to aliases management."""
from collections import OrderedDict
from django import forms
from django.http import QueryDict
from django.utils.translation import ugettext as _, ugettext_lazy
from modoboa.extensions.admin.models import (
Domain, Mailbox, Alias
)
from modoboa.lib.email_utils import split_mailbox
from modoboa.lib.exceptions import BadRequest, NotFound, Conflict
from modoboa.lib.form_utils import (
DynamicForm
)
class AliasForm(forms.ModelForm, DynamicForm):
email = forms.EmailField(
label=ugettext_lazy("Email address"),
help_text=ugettext_lazy(
"The distribution list address. Use the '*' character to create a "
"'catchall' address (ex: *@domain.tld)."
),
widget=forms.TextInput(attrs={"class": "form-control"})
)
recipients = forms.EmailField(
label=ugettext_lazy("Recipients"), required=False,
help_text=ugettext_lazy(
"Mailbox(es) this alias will point to. Indicate only one address "
"per input, press ENTER to add a new input."
),
widget=forms.TextInput(attrs={"class": "form-control"})
)
class Meta:
model = Alias
fields = ("enabled",)
def __init__(self, user, *args, **kwargs):
self.user = user
super(AliasForm, self).__init__(*args, **kwargs)
self.fields = OrderedDict(
(key, self.fields[key]) for key in
['email', 'recipients', 'enabled']
)
if len(args) and isinstance(args[0], QueryDict):
if "instance" in kwargs:
if not kwargs["instance"].domain.enabled:
del self.fields["enabled"]
self._load_from_qdict(args[0], "recipients", forms.EmailField)
elif "instance" in kwargs:
dlist = kwargs["instance"]
self.fields["email"].initial = dlist.full_address
if not dlist.domain.enabled:
self.fields["enabled"].widget.attrs["disabled"] = "disabled"
cpt = 1
for al in dlist.aliases.all():
name = "recipients_%d" % cpt
self._create_field(forms.EmailField, name, al.full_address, 2)
cpt += 1
for mb in dlist.mboxes.all():
name = "recipients_%d" % (cpt)
self._create_field(forms.EmailField, name, mb.full_address, 2)
cpt += 1
for addr in dlist.extmboxes.split(','):
if addr == "":
continue
name = "recipients_%d" % (cpt)
self._create_field(forms.EmailField, name, addr, 2)
cpt += 1
def clean_email(self):
localpart, domname = split_mailbox(self.cleaned_data["email"])
try:
domain = Domain.objects.get(name=domname)
except Domain.DoesNotExist:
raise forms.ValidationError(_("Domain does not exist"))
if not self.user.can_access(domain):
raise forms.ValidationError(
_("You don't have access to this domain")
)
return self.cleaned_data["email"].lower()
def set_recipients(self):
"""Recipients dispatching
We make a difference between 'local' recipients (the ones hosted
by Modoboa) and 'external' recipients.
"""
self.ext_rcpts = []
self.int_rcpts = []
total = 0
for k, v in self.cleaned_data.items():
if not k.startswith("recipients"):
continue
if v == "":
continue
local_part, domname = split_mailbox(v)
if domname is None:
raise BadRequest(
u"%s %s" % (_("Invalid mailbox"), v)
)
try:
domain = Domain.objects.get(name=domname)
except Domain.DoesNotExist:
domain = None
if domain is not None:
try:
rcpt = Alias.objects.get(domain=domain, address=local_part)
if rcpt.full_address == self.cleaned_data["email"]:
rcpt = None
except Alias.DoesNotExist:
rcpt = None
if rcpt is None:
try:
rcpt = Mailbox.objects.get(
domain=domain, address=local_part)
except Mailbox.DoesNotExist:
raise NotFound(
_("Local recipient %s not found" % v)
)
if rcpt in self.int_rcpts:
raise Conflict(
_("Recipient %s already present" % v)
)
self.int_rcpts += [rcpt]
total += 1
continue
if v in self.ext_rcpts:
raise Conflict(
_("Recipient %s already present" % v)
)
self.ext_rcpts += [v]
total += 1
if total == 0:
raise BadRequest(_("No recipient defined"))
def save(self, commit=True):
alias = super(AliasForm, self).save(commit=False)
localpart, domname = split_mailbox(self.cleaned_data["email"])
alias.address = localpart
alias.domain = Domain.objects.get(name=domname)
if commit:
alias.save(int_rcpts=self.int_rcpts, ext_rcpts=self.ext_rcpts)
self.save_m2m()
return alias
-362
View File
@@ -1,362 +0,0 @@
"""Forms related to domains management."""
from django import forms
from django.core.urlresolvers import reverse
from django.http import QueryDict
from django.utils.translation import ugettext as _, ugettext_lazy
from modoboa.core.models import User
from modoboa.extensions.admin.lib import check_if_domain_exists
from modoboa.extensions.admin.models import (
Domain, DomainAlias, Mailbox, Alias, Quota
)
from modoboa.lib import events, parameters
from modoboa.lib.exceptions import ModoboaException, Conflict
from modoboa.lib.form_utils import (
DomainNameField, YesNoField, WizardForm, DynamicForm, TabForms
)
from modoboa.lib.web_utils import render_to_json_response
class DomainFormGeneral(forms.ModelForm, DynamicForm):
quota = forms.IntegerField(
label=ugettext_lazy("Quota"),
required=False,
help_text=ugettext_lazy(
"Default quota in MB applied to mailboxes. Leave empty to use the "
"default value."
)
)
aliases = DomainNameField(
label=ugettext_lazy("Alias(es)"),
required=False,
help_text=ugettext_lazy(
"Alias(es) of this domain. Indicate only one name per input, "
"press ENTER to add a new input."
)
)
name = DomainNameField(
widget=forms.TextInput()
)
class Meta:
model = Domain
fields = ("name", "quota", "aliases", "enabled")
def __init__(self, *args, **kwargs):
self.oldname = None
if "instance" in kwargs:
self.oldname = kwargs["instance"].name
super(DomainFormGeneral, self).__init__(*args, **kwargs)
self.field_widths = {
"quota": 3
}
if len(args) and isinstance(args[0], QueryDict):
self._load_from_qdict(args[0], "aliases", DomainNameField)
elif "instance" in kwargs:
d = kwargs["instance"]
for pos, dalias in enumerate(d.domainalias_set.all()):
name = "aliases_%d" % (pos + 1)
self._create_field(forms.CharField, name, dalias.name, 3)
def clean_quota(self):
if self.cleaned_data['quota'] is None:
return int(parameters.get_admin('DEFAULT_DOMAIN_QUOTA'))
if self.cleaned_data['quota'] < 0:
raise forms.ValidationError(_("Must be a positive integer"))
return self.cleaned_data['quota']
def clean(self):
"""Custom fields validation.
We want to prevent duplicate names between domains and domain
aliases. Extensions have the possibility to declare other
objects (see *CheckDomainName* event).
The validation way is not very smart...
"""
super(DomainFormGeneral, self).clean()
cleaned_data = self.cleaned_data
name = cleaned_data["name"]
label = check_if_domain_exists(name, [(DomainAlias, _('domain alias'))])
if label is not None:
self.add_error(
"name", _("A %s with this name already exists") % unicode(label)
)
for k in cleaned_data.keys():
if not k.startswith("aliases"):
continue
if cleaned_data[k] == "":
del cleaned_data[k]
continue
if cleaned_data[k] == name:
self._errors[k] = self.error_class(
[_("A %s with this name already exists") % _("domain")]
)
del cleaned_data[k]
continue
label = check_if_domain_exists(
cleaned_data[k], [(Domain, _("domain"))])
if label is not None:
self.add_error(
k, _("A %s with this name already exists") % unicode(label))
return cleaned_data
def update_mailbox_quotas(self, domain):
"""Update all quota records associated to this domain.
This method must be called only when a domain gets renamed. As
the primary key used for a quota is an email address, rename a
domain will change all associated email addresses, so it will
change the primary keys used for quotas. The consequence is we
can't issue regular UPDATE queries using the .save() method of
a Quota instance (it will trigger an INSERT as the primary key
has changed).
So, we use this ugly hack to bypass this behaviour. It is not
perfomant at all as it will generate one query per quota
record to update.
"""
for q in Quota.objects.filter(username__contains="@%s" % self.oldname):
username = q.username.replace(
'@%s' % self.oldname, '@%s' % domain.name)
newq = Quota.objects.create(
username=username, bytes=q.bytes, messages=q.messages)
q.delete()
def save(self, user, commit=True, domalias_post_create=False):
"""Custom save method
Updating a domain may have consequences on other objects
(domain alias, mailbox, quota). The most tricky part concerns
quotas update.
"""
d = super(DomainFormGeneral, self).save(commit=False)
if commit:
old_mail_homes = None
if self.oldname is not None and d.name != self.oldname:
d.name = self.oldname
old_mail_homes = \
dict((mb.id, mb.mail_home) for mb in d.mailbox_set.all())
d.name = self.cleaned_data['name']
d.save()
Mailbox.objects.filter(domain=d, use_domain_quota=True) \
.update(quota=d.quota)
aliases = []
for k, v in self.cleaned_data.iteritems():
if not k.startswith("aliases"):
continue
if v in ["", None]:
continue
aliases.append(v)
for dalias in d.domainalias_set.all():
if dalias.name not in aliases:
dalias.delete()
else:
aliases.remove(dalias.name)
if aliases:
events.raiseEvent(
"CanCreate", user, "domain_aliases", len(aliases))
for alias in aliases:
try:
d.domainalias_set.get(name=alias)
except DomainAlias.DoesNotExist:
pass
else:
continue
al = DomainAlias(name=alias, target=d, enabled=d.enabled)
al.save(creator=user) if domalias_post_create else al.save()
if old_mail_homes is not None:
self.update_mailbox_quotas(d)
for mb in d.mailbox_set.all():
mb.rename_dir(old_mail_homes[mb.id])
return d
class DomainFormOptions(forms.Form):
create_dom_admin = YesNoField(
label=ugettext_lazy("Create a domain administrator"),
initial="no",
help_text=ugettext_lazy(
"Automatically create an administrator for this domain"
)
)
dom_admin_username = forms.CharField(
label=ugettext_lazy("Name"),
initial="admin",
help_text=ugettext_lazy(
"The administrator's name. Don't include the domain's name here, "
"it will be automatically appended."
),
required=False
)
create_aliases = YesNoField(
label=ugettext_lazy("Create aliases"),
initial="yes",
help_text=ugettext_lazy(
"Automatically create standard aliases for this domain"
),
required=False
)
def __init__(self, user, *args, **kwargs):
super(DomainFormOptions, self).__init__(*args, **kwargs)
result = events.raiseQueryEvent('UserCanSetRole', user, 'DomainAdmins')
if False in result:
self.fields = {}
return
if args:
if args[0].get("create_dom_admin", "no") == "yes":
self.fields["dom_admin_username"].required = True
self.fields["create_aliases"].required = True
def clean_dom_admin_username(self):
"""Ensure admin username is an email address."""
if '@' in self.cleaned_data["dom_admin_username"]:
raise forms.ValidationError(_("Invalid format"))
return self.cleaned_data["dom_admin_username"]
def save(self, user, domain):
if not self.fields:
return
if self.cleaned_data["create_dom_admin"] == "no":
return
username = "%s@%s" % (
self.cleaned_data["dom_admin_username"], domain.name)
try:
da = User.objects.get(username=username)
except User.DoesNotExist:
pass
else:
raise Conflict(_("User '%s' already exists" % username))
events.raiseEvent("CanCreate", user, "mailboxes")
da = User(username=username, email=username, is_active=True)
da.set_password("password")
da.save()
da.set_role("DomainAdmins")
da.post_create(user)
mb = Mailbox(
address=self.cleaned_data["dom_admin_username"], domain=domain,
user=da, use_domain_quota=True
)
mb.set_quota(override_rules=user.has_perm("admin.change_domain"))
mb.save(creator=user)
if self.cleaned_data["create_aliases"] == "yes":
events.raiseEvent("CanCreate", user, "mailbox_aliases")
alias = Alias(address="postmaster", domain=domain, enabled=True)
alias.save(int_rcpts=[mb])
alias.post_create(user)
domain.add_admin(da)
class DomainForm(TabForms):
"""Domain edition form."""
template_name = "admin/editdomainform.html"
def __init__(self, request, *args, **kwargs):
self.user = request.user
self.forms = []
if self.user.has_perm("admin.change_domain"):
self.forms.append({
"id": "general",
"title": _("General"),
"formtpl": "admin/domain_general_form.html",
"cls": DomainFormGeneral,
"mandatory": True
})
cbargs = [self.user]
if "instances" in kwargs:
cbargs += [kwargs["instances"]["general"]]
self.forms += events.raiseQueryEvent("ExtraDomainForm", *cbargs)
if not self.forms:
self.active_id = "admins"
super(DomainForm, self).__init__(request, *args, **kwargs)
def extra_context(self, context):
domain = self.instances["general"]
domadmins = [u for u in domain.admins
if self.request.user.can_access(u) and not u.is_superuser]
if not self.request.user.is_superuser:
domadmins = [u for u in domadmins if u.group == "DomainAdmins"]
context.update({
"title": domain.name,
"action": reverse("admin:domain_change", args=[domain.pk]),
"formid": "domform",
"domain": domain,
"domadmins": domadmins
})
def is_valid(self):
"""Custom validation.
We just save the current name before it is potentially
modified.
"""
self.instances["general"].oldname = self.instances["general"].name
return super(DomainForm, self).is_valid()
def save(self):
"""Custom save method.
As forms interact with each other, it is easier to make custom
code to save them.
"""
self.forms[0]['instance'].save(
self.request.user, domalias_post_create=True
)
for f in self.forms[1:]:
f["instance"].save(self.request.user)
def done(self):
events.raiseEvent("DomainModified", self.instances["general"])
return render_to_json_response(_("Domain modified"))
class DomainWizard(WizardForm):
"""Domain creation wizard.
"""
def __init__(self, request):
super(DomainWizard, self).__init__(request)
self.add_step(
DomainFormGeneral, _("General"),
formtpl="admin/domain_general_form.html"
)
self.add_step(
DomainFormOptions, _("Options"),
formtpl="admin/domain_options_form.html",
new_args=[self.request.user]
)
def extra_context(self, context):
context.update({
"title": _("New domain"),
"action": reverse("admin:domain_add"),
"formid": "domform"
})
def done(self):
genform = self.first_step.form
domain = genform.save(self.request.user)
domain.post_create(self.request.user)
try:
self.steps[1].form.save(self.request.user, domain)
except ModoboaException:
from django.db import transaction
transaction.rollback()
raise
return render_to_json_response(_("Domain created"))
-45
View File
@@ -1,45 +0,0 @@
from django import forms
from django.utils.translation import ugettext_lazy
class ExportDataForm(forms.Form):
filename = forms.CharField(
label=ugettext_lazy("File name"),
max_length=100,
required=False,
widget=forms.TextInput(attrs={"class": "form-control"})
)
sepchar = forms.CharField(
label=ugettext_lazy("Separator"),
max_length=1,
required=False,
widget=forms.TextInput(attrs={"class": "form-control"})
)
def __init__(self, *args, **kwargs):
super(ExportDataForm, self).__init__(*args, **kwargs)
self.fields["sepchar"].widget.attrs = {"class": "col-md-1 form-control"}
def clean_sepchar(self):
if self.cleaned_data["sepchar"] == "":
return ";"
return str(self.cleaned_data["sepchar"])
def clean_filename(self):
if self.cleaned_data["filename"] == "":
return self.fields["filename"].initial
return str(self.cleaned_data["filename"])
class ExportDomainsForm(ExportDataForm):
def __init__(self, *args, **kwargs):
super(ExportDomainsForm, self).__init__(*args, **kwargs)
self.fields["filename"].initial = "modoboa-domains.csv"
class ExportIdentitiesForm(ExportDataForm):
def __init__(self, *args, **kwargs):
super(ExportIdentitiesForm, self).__init__(*args, **kwargs)
self.fields["filename"].initial = "modoboa-identities.csv"
-46
View File
@@ -1,46 +0,0 @@
"""Forms related to forwards management."""
from django import forms
from django.utils.translation import ugettext as _, ugettext_lazy
from modoboa.extensions.admin.models import (
Domain
)
from modoboa.lib.email_utils import split_mailbox
from modoboa.lib.exceptions import BadRequest, PermDeniedException
class ForwardForm(forms.Form):
dest = forms.CharField(
label=ugettext_lazy("Recipient(s)"),
widget=forms.Textarea(attrs={"class": "form-control"}),
required=False,
help_text=ugettext_lazy(
"Indicate one or more recipients separated by a ','")
)
keepcopies = forms.BooleanField(
label=ugettext_lazy("Keep local copies"),
required=False,
help_text=ugettext_lazy(
"Forward messages and store copies into your local mailbox")
)
def parse_dest(self):
self.dests = []
rawdata = self.cleaned_data["dest"].strip()
if rawdata == "":
return
for d in rawdata.split(","):
local_part, domname = split_mailbox(d)
if not local_part or not domname or not len(domname):
raise BadRequest("Invalid mailbox syntax for %s" % d)
try:
Domain.objects.get(name=domname)
except Domain.DoesNotExist:
self.dests += [d]
else:
raise PermDeniedException(
_("You can't define a forward to a local destination. "
"Please ask your administrator to create an alias "
"instead.")
)
-43
View File
@@ -1,43 +0,0 @@
"""Forms related to import operations."""
from django import forms
from django.utils.translation import ugettext_lazy
class ImportDataForm(forms.Form):
"""Base form to import objects."""
sourcefile = forms.FileField(label=ugettext_lazy("Select a file"))
sepchar = forms.CharField(
label=ugettext_lazy("Separator"),
max_length=1,
required=False,
widget=forms.TextInput(attrs={"class": "form-control"})
)
continue_if_exists = forms.BooleanField(
label=ugettext_lazy("Continue on error"), required=False,
help_text=ugettext_lazy("Don't treat duplicated objects as error")
)
def __init__(self, *args, **kwargs):
super(ImportDataForm, self).__init__(*args, **kwargs)
self.fields["sepchar"].widget.attrs = {"class": "col-md-1 form-control"}
def clean_sepchar(self):
if self.cleaned_data["sepchar"] == "":
return ";"
return str(self.cleaned_data["sepchar"])
class ImportIdentitiesForm(ImportDataForm):
"""A form to import identities."""
crypt_password = forms.BooleanField(
label=ugettext_lazy("Crypt passwords"), required=False,
help_text=ugettext_lazy(
"Check this option if passwords contained in your file "
"are not crypted"
)
)
-114
View File
@@ -1,114 +0,0 @@
# coding: utf-8
from functools import wraps
from itertools import chain
from django.db.models import Q
from django.contrib.contenttypes.models import ContentType
from django.utils.translation import ugettext as _
from modoboa.core.models import User
from modoboa.lib import events
from modoboa.lib.exceptions import PermDeniedException
from modoboa.lib.email_utils import split_mailbox
from modoboa.extensions.admin.models import (
Domain, Alias
)
def needs_mailbox():
"""Check if the current user owns at least one mailbox
Some applications (the webmail for example) need a mailbox to
work.
"""
def decorator(f):
@wraps(f)
def wrapped_f(request, *args, **kwargs):
if request.user.mailbox_set.count():
return f(request, *args, **kwargs)
raise PermDeniedException(_("A mailbox is required"))
return wrapped_f
return decorator
def get_identities(user, searchquery=None, idtfilter=None, grpfilter=None):
"""Return all the identities owned by a user.
:param user: the desired user
:param str searchquery: search pattern
:param list idtfilter: identity type filters
:param list grpfilter: group names filters
:return: a queryset
"""
accounts = []
if idtfilter is None or not idtfilter or idtfilter == "account":
ids = user.objectaccess_set \
.filter(content_type=ContentType.objects.get_for_model(user)) \
.values_list('object_id', flat=True)
q = Q(pk__in=ids)
if searchquery is not None:
q &= Q(username__icontains=searchquery) \
| Q(email__icontains=searchquery)
if grpfilter is not None and grpfilter:
if grpfilter == "SuperAdmins":
q &= Q(is_superuser=True)
else:
q &= Q(groups__name=grpfilter)
accounts = User.objects.select_related().filter(q)
aliases = []
if idtfilter is None or not idtfilter \
or (idtfilter in ["alias", "forward", "dlist"]):
alct = ContentType.objects.get_for_model(Alias)
ids = user.objectaccess_set.filter(content_type=alct) \
.values_list('object_id', flat=True)
q = Q(pk__in=ids)
if searchquery is not None:
if '@' in searchquery:
local_part, domname = split_mailbox(searchquery)
if local_part:
q &= Q(address__icontains=local_part)
if domname:
q &= Q(domain__name__icontains=domname)
else:
q &= Q(address__icontains=searchquery) | \
Q(domain__name__icontains=searchquery)
aliases = Alias.objects.select_related().filter(q)
if idtfilter is not None and idtfilter:
aliases = [al for al in aliases if al.type == idtfilter]
return chain(accounts, aliases)
def get_domains(user, domfilter=None, searchquery=None, **extrafilters):
"""Return all the domains the user can access.
:param ``User`` user: user object
:param str searchquery: filter
:rtype: list
:return: a list of domains and/or relay domains
"""
domains = []
if domfilter is None or not domfilter or domfilter == 'domain':
domains = Domain.objects.get_for_admin(user)
if searchquery is not None:
q = Q(name__contains=searchquery)
q |= Q(domainalias__name__contains=searchquery)
domains = domains.filter(q).distinct()
extra_domain_entries = events.raiseQueryEvent(
'ExtraDomainEntries', user, domfilter, searchquery, **extrafilters
)
return chain(domains, extra_domain_entries)
def check_if_domain_exists(name, extra_checks=None):
"""Check if a domain already exists.
We not only look for domains, we also look for every object that
could conflict with a domain (domain alias, etc.)
"""
dtypes = events.raiseQueryEvent('CheckDomainName')
if extra_checks is not None:
dtypes = extra_checks + dtypes
for dtype, label in dtypes:
if dtype.objects.filter(name=name).exists():
return label
return None
@@ -1,97 +0,0 @@
import logging
from optparse import make_option
import os
from django.core.management.base import BaseCommand
from modoboa.core.management.commands import CloseConnectionMixin
from modoboa.lib import parameters
from modoboa.lib.sysutils import exec_cmd
from modoboa.lib.exceptions import InternalError
from modoboa.extensions.admin.models import MailboxOperation
class OperationError(Exception):
pass
class Command(BaseCommand, CloseConnectionMixin):
help = 'Handles rename and delete operations on mailboxes'
option_list = BaseCommand.option_list + (
make_option(
'--pidfile', default='/tmp/handle_mailbox_operations.pid',
help='Path to the file that will contain the PID of this process'
),
)
def __init__(self, *args, **kwargs):
super(Command, self).__init__(*args, **kwargs)
self.logger = logging.getLogger('modoboa.admin')
def rename_mailbox(self, operation):
if not os.path.exists(operation.argument):
return
new_mail_home = operation.mailbox.mail_home
dirname = os.path.dirname(new_mail_home)
if not os.path.exists(dirname):
try:
os.makedirs(dirname)
except os.error as e:
raise OperationError(str(e))
code, output = exec_cmd(
"mv %s %s" % (operation.argument, new_mail_home)
)
if code:
raise OperationError(output)
def delete_mailbox(self, operation):
if not os.path.exists(operation.argument):
return
code, output = exec_cmd(
"rm -r %s" % operation.argument
)
if code:
raise OperationError(output)
def check_pidfile(self, path):
"""Check if this command is already running
:param str path: path to the file containing the PID
:return: a boolean, True means we can go further
"""
if os.path.exists(path):
with open(path) as fp:
pid = fp.read().strip()
code, output = exec_cmd(
"grep handle_mailbox_operations /proc/%s/cmdline" % pid
)
if not code:
return False
with open(path, 'w') as fp:
print >> fp, os.getpid()
return True
def handle(self, *args, **options):
try:
if parameters.get_admin("HANDLE_MAILBOXES") == 'no':
return
except parameters.NotDefined:
return
if not self.check_pidfile(options['pidfile']):
return
for ope in MailboxOperation.objects.all():
try:
f = getattr(self, '%s_mailbox' % ope.type)
except AttributeError:
continue
try:
f(ope)
except (OperationError, InternalError) as e:
self.logger.critical('%s failed (reason: %s)',
ope, str(e).encode('utf-8'))
else:
self.logger.info('%s succeed', ope)
ope.delete()
os.unlink(options['pidfile'])
@@ -1,161 +0,0 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
from django.conf import settings
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name='Alias',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('address', models.CharField(help_text="The alias address (without the domain part). For a 'catch-all' address, just enter an * character.", max_length=254, verbose_name='address')),
('extmboxes', models.TextField(blank=True)),
('enabled', models.BooleanField(default=True, help_text='Check to activate this alias', verbose_name='enabled')),
('aliases', models.ManyToManyField(help_text='The aliases this alias points to', to='admin.Alias')),
],
options={
'ordering': ['domain__name', 'address'],
'permissions': (('view_aliases', 'View aliases'),),
},
bases=(models.Model,),
),
migrations.CreateModel(
name='Domain',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('name', models.CharField(help_text='The domain name', unique=True, max_length=100, verbose_name='name')),
('quota', models.IntegerField()),
('enabled', models.BooleanField(default=True, help_text='Check to activate this domain', verbose_name='enabled')),
],
options={
'ordering': ['name'],
'permissions': (('view_domain', 'View domain'), ('view_domains', 'View domains')),
},
bases=(models.Model,),
),
migrations.CreateModel(
name='DomainAlias',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('name', models.CharField(help_text='The alias name', unique=True, max_length=100, verbose_name='name')),
('enabled', models.BooleanField(default=True, help_text='Check to activate this alias', verbose_name='enabled')),
],
options={
'permissions': (('view_domaliases', 'View domain aliases'),),
},
bases=(models.Model,),
),
migrations.CreateModel(
name='Mailbox',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('address', models.CharField(help_text='Mailbox address (without the @domain.tld part)', max_length=252, verbose_name='address')),
('quota', models.PositiveIntegerField()),
('use_domain_quota', models.BooleanField(default=False)),
],
options={
'permissions': (('view_mailboxes', 'View mailboxes'),),
},
bases=(models.Model,),
),
migrations.CreateModel(
name='MailboxOperation',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('type', models.CharField(max_length=20, choices=[(b'rename', b'rename'), (b'delete', b'delete')])),
('argument', models.TextField()),
('mailbox', models.ForeignKey(blank=True, to='admin.Mailbox', null=True)),
],
options={
},
bases=(models.Model,),
),
migrations.CreateModel(
name='ObjectDates',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('creation', models.DateTimeField(auto_now_add=True)),
('last_modification', models.DateTimeField(auto_now=True)),
],
options={
},
bases=(models.Model,),
),
migrations.CreateModel(
name='Quota',
fields=[
('username', models.EmailField(max_length=254, serialize=False, primary_key=True)),
('bytes', models.BigIntegerField(default=0)),
('messages', models.IntegerField(default=0)),
],
options={
},
bases=(models.Model,),
),
migrations.AddField(
model_name='mailbox',
name='dates',
field=models.ForeignKey(to='admin.ObjectDates'),
preserve_default=True,
),
migrations.AddField(
model_name='mailbox',
name='domain',
field=models.ForeignKey(to='admin.Domain'),
preserve_default=True,
),
migrations.AddField(
model_name='mailbox',
name='user',
field=models.ForeignKey(to=settings.AUTH_USER_MODEL),
preserve_default=True,
),
migrations.AddField(
model_name='domainalias',
name='dates',
field=models.ForeignKey(to='admin.ObjectDates'),
preserve_default=True,
),
migrations.AddField(
model_name='domainalias',
name='target',
field=models.ForeignKey(verbose_name='target', to='admin.Domain', help_text='The domain this alias points to'),
preserve_default=True,
),
migrations.AddField(
model_name='domain',
name='dates',
field=models.ForeignKey(to='admin.ObjectDates'),
preserve_default=True,
),
migrations.AddField(
model_name='alias',
name='dates',
field=models.ForeignKey(to='admin.ObjectDates'),
preserve_default=True,
),
migrations.AddField(
model_name='alias',
name='domain',
field=models.ForeignKey(to='admin.Domain'),
preserve_default=True,
),
migrations.AddField(
model_name='alias',
name='mboxes',
field=models.ManyToManyField(help_text='The mailboxes this alias points to', to='admin.Mailbox', verbose_name='mailboxes'),
preserve_default=True,
),
migrations.AlterUniqueTogether(
name='alias',
unique_together=set([('address', 'domain')]),
),
]
@@ -1,10 +0,0 @@
from .base import AdminObject
from .domain import Domain
from .domain_alias import DomainAlias
from .mailbox import Mailbox, Quota, MailboxOperation
from .alias import Alias
__all__ = [
'Domain', 'DomainAlias', 'Mailbox', 'Quota', 'Alias',
'MailboxOperation', 'AdminObject'
]
-210
View File
@@ -1,210 +0,0 @@
"""Models related to aliases management."""
from django.db import models
from django.utils.translation import ugettext as _, ugettext_lazy
import reversion
from .base import AdminObject
from .domain import Domain
from .mailbox import Mailbox
from modoboa.lib.email_utils import split_mailbox
from modoboa.lib.exceptions import (
PermDeniedException, BadRequest, Conflict
)
class Alias(AdminObject):
"""
Mailbox alias.
"""
address = models.CharField(
ugettext_lazy('address'), max_length=254,
help_text=ugettext_lazy(
"The alias address (without the domain part). For a 'catch-all' "
"address, just enter an * character."
)
)
domain = models.ForeignKey(Domain)
mboxes = models.ManyToManyField(
Mailbox, verbose_name=ugettext_lazy('mailboxes'),
help_text=ugettext_lazy("The mailboxes this alias points to")
)
aliases = models.ManyToManyField(
'Alias',
help_text=ugettext_lazy("The aliases this alias points to")
)
extmboxes = models.TextField(blank=True)
enabled = models.BooleanField(
ugettext_lazy('enabled'),
help_text=ugettext_lazy("Check to activate this alias"),
default=True
)
_objectname = 'MailboxAlias'
class Meta:
permissions = (
("view_aliases", "View aliases"),
)
unique_together = (("address", "domain"),)
ordering = ["domain__name", "address"]
app_label = 'admin'
def __unicode__(self):
return self.full_address
@property
def full_address(self):
return "%s@%s" % (self.address, self.domain.name)
@property
def identity(self):
return self.full_address
@property
def name_or_rcpt(self):
rcpts_count = self.get_recipients_count()
if not rcpts_count:
return "---"
rcpts = self.get_recipients()
if rcpts_count > 1:
return "%s, ..." % rcpts[0]
return rcpts[0]
@property
def type(self):
cpt = self.get_recipients_count()
if cpt > 1:
return "dlist"
if self.extmboxes != "":
return "forward"
return "alias"
@property
def tags(self):
labels = {
"dlist": _("distribution list"),
"forward": _("forward"),
"alias": _("alias")
}
altype = self.type
return [{"name": altype, "label": labels[altype], "type": "idt"}]
def post_create(self, creator):
from modoboa.lib.permissions import grant_access_to_object
super(Alias, self).post_create(creator)
if creator.is_superuser:
for admin in self.domain.admins:
grant_access_to_object(admin, self)
def save(self, *args, **kwargs):
"""Custom save method."""
if 'ext_rcpts' in kwargs:
self.extmboxes = ",".join(kwargs['ext_rcpts']) \
if kwargs['ext_rcpts'] else ""
del kwargs['ext_rcpts']
if 'int_rcpts' in kwargs:
int_rcpts = kwargs['int_rcpts']
del kwargs['int_rcpts']
else:
int_rcpts = []
super(Alias, self).save(*args, **kwargs)
curaliases = self.aliases.all()
curmboxes = self.mboxes.all()
for rcpt in int_rcpts:
if isinstance(rcpt, Alias):
if rcpt not in curaliases:
self.aliases.add(rcpt)
continue
if rcpt not in curmboxes:
self.mboxes.add(rcpt)
for rcpt in curaliases:
if rcpt not in int_rcpts:
self.aliases.remove(rcpt)
for rcpt in curmboxes:
if rcpt not in int_rcpts:
self.mboxes.remove(rcpt)
def get_recipients(self, with_external=True):
"""Return the recipients list.
Internal and external addresses are mixed into a single list.
:param bool with_external: include external addresses or not
:rtype: list
:return: a list of addresses
"""
result = [al.full_address for al in self.aliases.all()]
result += [mb.full_address for mb in self.mboxes.all()]
if with_external and self.extmboxes != "":
result += self.extmboxes.split(',')
return result
def get_recipients_count(self):
"""Return the number of recipients of this alias.
:rtype: int
"""
total = 0
if self.extmboxes != "":
total += len(self.extmboxes.split(','))
return total + self.aliases.count() + self.mboxes.count()
def from_csv(self, user, row, expected_elements=5):
"""Create a new alias from a CSV file entry
"""
if len(row) < expected_elements:
raise BadRequest(_("Invalid line: %s" % row))
localpart, domname = split_mailbox(row[1].strip())
try:
domain = Domain.objects.get(name=domname)
except Domain.DoesNotExist:
raise BadRequest(_("Domain '%s' does not exist" % domname))
if not user.can_access(domain):
raise PermDeniedException
try:
Alias.objects.get(address=localpart, domain__name=domain)
except Alias.DoesNotExist:
pass
else:
raise Conflict
self.address = localpart
self.domain = domain
self.enabled = (row[2].strip() in ["True", "1", "yes", "y"])
int_rcpts = []
ext_rcpts = []
for rcpt in row[3:]:
rcpt = rcpt.strip()
if not rcpt:
continue
localpart, domname = split_mailbox(rcpt)
try:
Domain.objects.get(name=domname)
except Domain.DoesNotExist:
ext_rcpts += [rcpt]
continue
try:
target = Alias.objects.get(
domain__name=domname, address=localpart
)
if target.full_address == self.full_address:
target = None
except Alias.DoesNotExist:
target = None
if target is None:
try:
target = Mailbox.objects.get(address=localpart,
domain__name=domname)
except Mailbox.DoesNotExist:
raise BadRequest(_("Local recipient %s not found" % rcpt))
int_rcpts += [target]
self.save(int_rcpts=int_rcpts, ext_rcpts=ext_rcpts, creator=user)
def to_csv(self, csvwriter):
row = [self.type, self.full_address.encode("utf-8"), self.enabled]
row += self.get_recipients()
csvwriter.writerow(row)
reversion.register(Alias)
-81
View File
@@ -1,81 +0,0 @@
from django.db import models
from modoboa.lib import events
from modoboa.lib.permissions import (
grant_access_to_object, ungrant_access_to_object
)
class ObjectDates(models.Model):
"""Dates recording for admin objects
This table keeps creation and last modification dates for Domains,
domain aliases, mailboxes and aliases objects.
"""
creation = models.DateTimeField(auto_now_add=True)
last_modification = models.DateTimeField(auto_now=True)
class Meta:
app_label = 'admin'
@staticmethod
def set_for_object(obj):
"""Initialize or update dates for a given object.
:param obj: an admin object (Domain, Mailbox, etc)
"""
try:
dates = getattr(obj, "dates")
except ObjectDates.DoesNotExist:
dates = ObjectDates()
dates.save()
obj.dates = dates
class AdminObject(models.Model):
"""Abstract model to support dates
Inherit from this model to automatically add the "dates" feature
to another model. It defines the appropriate field and handles
saves.
"""
dates = models.ForeignKey(ObjectDates)
_objectname = None
class Meta:
abstract = True
@property
def creation(self):
return self.dates.creation
@property
def last_modification(self):
return self.dates.last_modification
@property
def objectname(self):
if self._objectname is None:
return self.__class__.__name__
return self._objectname
def post_create(self, creator):
grant_access_to_object(creator, self, is_owner=True)
events.raiseEvent("%sCreated" % self.objectname, creator, self)
def save(self, *args, **kwargs):
ObjectDates.set_for_object(self)
if "creator" in kwargs:
creator = kwargs["creator"]
del kwargs["creator"]
else:
creator = None
super(AdminObject, self).save(*args, **kwargs)
if creator is not None:
self.post_create(creator)
def delete(self):
events.raiseEvent("%sDeleted" % self.objectname, self)
ungrant_access_to_object(self)
super(AdminObject, self).delete()
-185
View File
@@ -1,185 +0,0 @@
"""Models related to domains management."""
from django.contrib.contenttypes import generic
from django.db import models
from django.db.models.manager import Manager
from django.utils.translation import ugettext as _, ugettext_lazy
import reversion
from .base import AdminObject
from modoboa.core.models import User, ObjectAccess
from modoboa.lib import events, parameters
from modoboa.lib.exceptions import BadRequest, Conflict
class DomainManager(Manager):
def get_for_admin(self, admin):
"""Return the domains belonging to this admin
The result is a ``QuerySet`` object, so this function can be used
to fill ``ModelChoiceField`` objects.
"""
if admin.is_superuser:
return self.get_query_set()
return self.get_query_set().filter(owners__user=admin)
class Domain(AdminObject):
"""Mail domain."""
name = models.CharField(ugettext_lazy('name'), max_length=100, unique=True,
help_text=ugettext_lazy("The domain name"))
quota = models.IntegerField()
enabled = models.BooleanField(
ugettext_lazy('enabled'),
help_text=ugettext_lazy("Check to activate this domain"),
default=True
)
owners = generic.GenericRelation(ObjectAccess)
objects = DomainManager()
class Meta:
permissions = (
("view_domain", "View domain"),
("view_domains", "View domains"),
)
ordering = ["name"]
app_label = 'admin'
@property
def domainalias_count(self):
return self.domainalias_set.count()
@property
def mailbox_count(self):
return self.mailbox_set.count()
@property
def mbalias_count(self):
return self.alias_set.count()
@property
def identities_count(self):
"""Total number of identities in this domain."""
return self.mailbox_set.count() + self.alias_set.count()
@property
def tags(self):
return [{"name": "domain", "label": _("Domain"), "type": "dom"}]
@property
def admins(self):
"""Return the domain administrators of this domain
:return: a list of User objects
"""
return [oa.user for oa in self.owners.filter(user__is_superuser=False)]
@property
def aliases(self):
return self.domainalias_set
def add_admin(self, account):
"""Add a new administrator for this domain
:param User account: the administrotor to add
"""
from modoboa.lib.permissions import grant_access_to_object
grant_access_to_object(account, self)
for mb in self.mailbox_set.all():
if mb.user.has_perm("admin.add_domain"):
continue
grant_access_to_object(account, mb)
grant_access_to_object(account, mb.user)
for al in self.alias_set.all():
grant_access_to_object(account, al)
def remove_admin(self, account):
"""Remove an administrator of this domain.
:param User account: administrator to remove
"""
from modoboa.lib.permissions import \
ungrant_access_to_object, get_object_owner
if get_object_owner(self) == account:
events.raiseEvent('DomainOwnershipRemoved', account, self)
ungrant_access_to_object(self, account)
for mb in self.mailbox_set.all():
if mb.user.has_perm("admin.add_domain"):
continue
ungrant_access_to_object(mb, account)
ungrant_access_to_object(mb.user, account)
for al in self.alias_set.all():
ungrant_access_to_object(al, account)
def delete(self, fromuser, keepdir=False):
"""Custom delete method.
"""
from modoboa.lib.permissions import ungrant_access_to_objects
from .mailbox import Quota
if self.domainalias_set.count():
events.raiseEvent("DomainAliasDeleted", self.domainalias_set.all())
ungrant_access_to_objects(self.domainalias_set.all())
if self.alias_set.count():
events.raiseEvent("MailboxAliasDeleted", self.alias_set.all())
ungrant_access_to_objects(self.alias_set.all())
if parameters.get_admin("AUTO_ACCOUNT_REMOVAL") == "yes":
for account in User.objects.filter(mailbox__domain__name=self.name):
account.delete(fromuser, keepdir)
elif self.mailbox_set.count():
Quota.objects.filter(username__contains='@%s' % self.name).delete()
events.raiseEvent("MailboxDeleted", self.mailbox_set.all())
ungrant_access_to_objects(self.mailbox_set.all())
super(Domain, self).delete()
def __str__(self):
return self.name
def from_csv(self, user, row):
"""Create a new domain from a CSV entry.
The expected fields order is the following::
"domain", name, quota, enabled
:param ``core.User`` user: user creating the domain
:param str row: a list containing domain's definition
"""
if len(row) < 4:
raise BadRequest(_("Invalid line"))
self.name = row[1].strip()
try:
Domain.objects.get(name=self.name)
except Domain.DoesNotExist:
pass
else:
raise Conflict
try:
self.quota = int(row[2].strip())
except ValueError:
raise BadRequest(
_("Invalid quota value for domain '%s'" % self.name))
self.enabled = (row[3].strip() in ["True", "1", "yes", "y"])
self.save(creator=user)
def to_csv(self, csvwriter):
csvwriter.writerow(["domain", self.name, self.quota, self.enabled])
for dalias in self.domainalias_set.all():
dalias.to_csv(csvwriter)
def post_create(self, creator):
"""Post creation actions.
:param ``User`` creator: user whos created this domain
"""
super(Domain, self).post_create(creator)
for domalias in self.domainalias_set.all():
domalias.post_create(creator)
reversion.register(Domain)
@@ -1,72 +0,0 @@
"""Models related to domain aliases management."""
from django.db import models
from django.utils.translation import ugettext as _, ugettext_lazy
import reversion
from .base import AdminObject
from .domain import Domain
from modoboa.lib.exceptions import BadRequest, Conflict
class DomainAlias(AdminObject):
"""Domain aliases."""
name = models.CharField(ugettext_lazy("name"), max_length=100, unique=True,
help_text=ugettext_lazy("The alias name"))
target = models.ForeignKey(
Domain, verbose_name=ugettext_lazy('target'),
help_text=ugettext_lazy("The domain this alias points to")
)
enabled = models.BooleanField(
ugettext_lazy('enabled'),
help_text=ugettext_lazy("Check to activate this alias"),
default=True
)
class Meta:
permissions = (
("view_domaliases", "View domain aliases"),
)
app_label = 'admin'
def __unicode__(self):
return self.name
def from_csv(self, user, row):
"""Create a domain alias from a CSV row
Expected format: ["domainalias", domain alias name, targeted domain, enabled]
:param user: a ``User`` object
:param row: a list containing the alias definition
"""
if len(row) < 4:
raise BadRequest(_("Invalid line"))
self.name = row[1].strip()
for model in [DomainAlias, Domain]:
try:
model.objects.get(name=self.name)
except model.DoesNotExist:
pass
else:
raise Conflict
domname = row[2].strip()
try:
self.target = Domain.objects.get(name=domname)
except Domain.DoesNotExist:
raise BadRequest(_("Unknown domain %s" % domname))
self.enabled = row[3].strip() in ["True", "1", "yes", "y"]
self.save(creator=user)
def to_csv(self, csvwriter):
"""Export a domain alias using CSV format
:param csvwriter: a ``csv.writer`` object
"""
csvwriter.writerow(["domainalias", self.name,
self.target.name, self.enabled])
reversion.register(DomainAlias)
-334
View File
@@ -1,334 +0,0 @@
"""Models related to mailboxes management."""
import os
import pwd
from django.contrib.contenttypes.models import ContentType
from django.db import models
from django.db.models import Q
from django.db.models.manager import Manager
from django.db.models.signals import pre_delete
from django.dispatch import receiver
from django.utils.translation import ugettext as _, ugettext_lazy
import reversion
from .base import AdminObject
from .domain import Domain
from modoboa.core.models import User
from modoboa.lib import parameters, events
from modoboa.lib.exceptions import BadRequest, InternalError
from modoboa.lib.sysutils import exec_cmd
class Quota(models.Model):
"""Keeps track of Mailbox current quota."""
username = models.EmailField(primary_key=True, max_length=254)
bytes = models.BigIntegerField(default=0)
messages = models.IntegerField(default=0)
class Meta:
app_label = 'admin'
class MailboxManager(Manager):
"""Custom manager for Mailbox."""
def get_for_admin(self, admin, squery=None):
"""Return the mailboxes that belong to this admin.
The result will contain the mailboxes defined for each domain that
user can see.
:param string squery: a search query
:return: a list of ``Mailbox`` objects
"""
qf = None
if squery is not None:
if '@' in squery:
parts = squery.split('@')
addrfilter = '@'.join(parts[:-1])
domfilter = parts[-1]
qf = (
Q(address__contains=addrfilter) &
Q(domain__name__contains=domfilter)
)
else:
qf = (
Q(address__contains=squery) |
Q(domain__name__contains=squery)
)
ids = admin.objectaccess_set \
.filter(content_type=ContentType.objects.get_for_model(Mailbox)) \
.values_list('object_id', flat=True)
if qf is not None:
qf = Q(pk__in=ids) & qf
else:
qf = Q(pk__in=ids)
return self.get_query_set().select_related().filter(qf)
class Mailbox(AdminObject):
"""User mailbox."""
address = models.CharField(
ugettext_lazy('address'), max_length=252,
help_text=ugettext_lazy(
"Mailbox address (without the @domain.tld part)")
)
quota = models.PositiveIntegerField()
use_domain_quota = models.BooleanField(default=False)
domain = models.ForeignKey(Domain)
user = models.ForeignKey(User)
objects = MailboxManager()
class Meta:
permissions = (
("view_mailboxes", "View mailboxes"),
)
app_label = 'admin'
def __init__(self, *args, **kwargs):
super(Mailbox, self).__init__(*args, **kwargs)
self.__mail_home = None
def __str__(self):
return self.full_address
def __full_address(self, localpart):
return "%s@%s" % (localpart, self.domain.name)
@property
def full_address(self):
return self.__full_address(self.address)
@property
def enabled(self):
return self.user.is_active
@property
def alias_count(self):
return self.alias_set.count()
@property
def mail_home(self):
"""Retrieve the home directory of this mailbox.
The home directory refers to the place on the file system
where the mailbox data is stored.
We ask dovecot to give us this information because there are
several patterns to understand and we don't want to implement
them.
"""
hm = parameters.get_admin("HANDLE_MAILBOXES", raise_error=False)
if hm is None or hm == "no":
return None
if self.__mail_home is None:
curuser = pwd.getpwuid(os.getuid()).pw_name
mbowner = parameters.get_admin("MAILBOXES_OWNER")
options = {}
if curuser != mbowner:
options['sudo_user'] = mbowner
code, output = exec_cmd(
"doveadm user %s -f home" % self.full_address, **options
)
if code:
raise InternalError(
_("Failed to retrieve mailbox location (%s)" % output))
self.__mail_home = output.strip()
return self.__mail_home
@property
def alias_addresses(self):
"""Return all alias address of this mailbox.
:rtype: list of string
"""
aliases = []
for alias in self.alias_set.all():
aliases += [alias.full_address]
return aliases
@property
def quota_value(self):
"""Retrieve the ``Quota`` instance associated to this mailbox."""
if not hasattr(self, "_quota_value"):
try:
self._quota_value = Quota.objects.get(
username=self.full_address)
except Quota.DoesNotExist:
return None
return self._quota_value
@quota_value.setter
def quota_value(self, instance):
"""Set the ``Quota`` for this mailbox."""
self._quota_value = instance
def rename_dir(self, old_mail_home):
hm = parameters.get_admin("HANDLE_MAILBOXES", raise_error=False)
if hm is None or hm == "no":
return
MailboxOperation.objects.create(
mailbox=self, type='rename', argument=old_mail_home
)
def rename(self, address, domain):
"""Rename the mailbox.
To update the associated Quota record, we must create a new
one first, update the foreign key and then we can delete the
original record!
:param string address: the new mailbox's address (local part)
:param Domain domain: the new mailbox's domain
"""
old_mail_home = self.mail_home
old_qvalue = self.quota_value
self.address = address
self.domain = domain
self.quota_value = Quota.objects.create(
username=self.full_address, bytes=old_qvalue.bytes,
messages=old_qvalue.messages
)
old_qvalue.delete()
self.rename_dir(old_mail_home)
def delete_dir(self):
hm = parameters.get_admin("HANDLE_MAILBOXES", raise_error=False)
if hm is None or hm == "no":
return
MailboxOperation.objects.create(type='delete', argument=self.mail_home)
def set_quota(self, value=None, override_rules=False):
"""Set or update quota's value for this mailbox.
A value equal to 0 means the mailbox won't have any quota. The
following cases allow people to define such behaviour:
* The domain has no quota
* :keyword:`override_rules` is True
:param integer value: the quota's value
:param bool override_rules: allow to override defined quota rules
"""
if value is None:
if self.use_domain_quota:
self.quota = self.domain.quota
else:
self.quota = 0
elif int(value) > self.domain.quota and not override_rules:
raise BadRequest(
_("Quota is greater than the allowed domain's limit (%dM)"
% self.domain.quota)
)
else:
self.quota = value
if not self.quota and self.domain.quota and not override_rules:
raise BadRequest(_("A quota is required"))
def get_quota(self):
"""Get quota limit.
:rtype: int
"""
return int(self.quota_value.bytes / 1048576)
def get_quota_in_percent(self):
"""Get current quota usage.
:rtype: int
"""
if not self.quota:
return 0
return int(
self.quota_value.bytes / float(self.quota * 1048576) * 100
)
def post_create(self, creator):
from modoboa.lib.permissions import grant_access_to_object
super(Mailbox, self).post_create(creator)
if creator.is_superuser and not self.user.has_perm("admin.add_domain"):
# A super user is creating a new mailbox. Give
# access to that mailbox (and the associated
# account) to the appropriate domain admins,
# except if the new account has a more important
# role (SuperAdmin, Reseller)
for admin in self.domain.admins:
grant_access_to_object(admin, self)
grant_access_to_object(admin, self.user)
def save(self, *args, **kwargs):
"""Custom save.
We just make sure a quota record is defined for this mailbox.
"""
if self.quota_value is None:
self.quota_value, created = Quota.objects.get_or_create(
username=self.full_address)
super(Mailbox, self).save(*args, **kwargs)
def delete(self, keepdir=False):
"""Custom delete method
We try to delete the associated quota in the same time (it may
has already been removed if we're deleting a domain).
:param bool keepdir: delete the mailbox home dir on the
filesystem or not
"""
try:
q = Quota.objects.get(username=self.full_address)
except Quota.DoesNotExist:
pass
else:
q.delete()
if not keepdir:
self.delete_dir()
super(Mailbox, self).delete()
reversion.register(Mailbox)
@receiver(pre_delete, sender=Mailbox)
def mailbox_deleted_handler(sender, **kwargs):
"""``Mailbox`` pre_delete signal receiver
In order to properly handle deletions (ie. we don't want to leave
orphan records into the db), we define this custom receiver.
It manually removes the mailbox from the aliases it is linked to
and then remove all empty aliases.
"""
from modoboa.lib.permissions import ungrant_access_to_object
mb = kwargs['instance']
events.raiseEvent("MailboxDeleted", mb)
ungrant_access_to_object(mb)
for alias in mb.alias_set.all():
alias.mboxes.remove(mb)
if alias.mboxes.count() == 0:
alias.delete()
class MailboxOperation(models.Model):
mailbox = models.ForeignKey(Mailbox, blank=True, null=True)
type = models.CharField(
max_length=20, choices=(('rename', 'rename'), ('delete', 'delete'))
)
argument = models.TextField()
class Meta:
app_label = 'admin'
def __str__(self):
if self.type == 'rename':
return 'Rename %s -> %s' % (self.argument, self.mailbox.mail_home)
return 'Delete %s' % self.argument
-285
View File
@@ -1,285 +0,0 @@
"""Registration with Modoboa takes place here."""
from django.core.urlresolvers import reverse
from django.utils.translation import ugettext as _, ugettext_lazy
from modoboa.core.extensions import ModoExtension, exts_pool
from modoboa.extensions.admin.models import (
Domain, DomainAlias, Mailbox, Alias
)
from modoboa.lib import parameters, events
from modoboa.lib.email_utils import split_mailbox
from modoboa.lib.exceptions import PermDeniedException, BadRequest, Conflict
ADMIN_EVENTS = [
"DomainCreated",
"DomainModified",
"DomainDeleted",
"DomainOwnershipRemoved",
"ExtraDomainEntries",
"ExtraDomainMenuEntries",
"ExtraDomainFilters",
"GetDomainActions",
"GetDomainModifyLink",
"CheckDomainName",
"DomainAliasCreated",
"DomainAliasDeleted",
"MailboxCreated",
"MailboxDeleted",
"MailboxModified",
"MailboxAliasCreated",
"MailboxAliasDeleted",
"ExtraDomainForm",
"FillDomainInstances",
"ExtraAccountForm",
"CheckExtraAccountForm",
"FillAccountInstances",
"ExtraDomainImportHelp",
"ImportObject"
]
PERMISSIONS = {
"DomainAdmins": [
["admin", "domain", "view_domain"],
["admin", "mailbox", "add_mailbox"],
["admin", "mailbox", "change_mailbox"],
["admin", "mailbox", "delete_mailbox"],
["admin", "alias", "add_alias"],
["admin", "alias", "change_alias"],
["admin", "alias", "delete_alias"],
["admin", "mailbox", "view_mailboxes"],
["admin", "alias", "view_aliases"],
["admin", "domainalias", "view_domaliases"],
["admin", "domainalias", "add_domainalias"],
["admin", "domainalias", "change_domainalias"],
["admin", "domainalias", "delete_domainalias"]
]
}
class AdminConsole(ModoExtension):
name = "admin"
label = ugettext_lazy("Administration console")
version = "1.0"
description = ugettext_lazy(
"Web based console to manage domains, accounts and aliases"
)
always_active = True
def load(self):
from modoboa.extensions.admin.app_settings import AdminParametersForm
parameters.register(
AdminParametersForm, ugettext_lazy("Administration"))
events.declare(ADMIN_EVENTS)
exts_pool.register_extension(AdminConsole)
@events.observe("GetExtraRolePermissions")
def extra_permissions(rolename):
"""Return extra permissions for :kw:`rolename`."""
return PERMISSIONS.get(rolename, [])
@events.observe("ExtraUprefsRoutes")
def extra_routes():
from django.conf.urls import url
return [
url(r'^user/forward/',
'modoboa.extensions.admin.views.user.forward',
name="user_forward"),
]
@events.observe("AdminMenuDisplay")
def admin_menu(target, user):
if target != "top_menu":
return []
entries = []
if user.has_perm("admin.view_domains"):
entries += [
{"name": "domains",
"url": reverse("admin:domain_list"),
"label": _("Domains")}
]
if user.has_perm("core.add_user") or user.has_perm("admin.add_alias"):
entries += [
{"name": "identities",
"url": reverse("admin:identity_list"),
"label": _("Identities")},
]
return entries
@events.observe("UserMenuDisplay")
def user_menu(target, user):
if target != "uprefs_menu":
return []
if not user.mailbox_set.count():
return []
return [
{"name": "forward",
"class": "ajaxnav",
"url": "forward/",
"label": ugettext_lazy("Forward")}
]
@events.observe("RoleChanged")
def grant_access_to_all_objects(user, role):
from django.contrib.contenttypes.models import ContentType
from modoboa.lib.permissions import grant_access_to_objects
from modoboa.core.models import User
if role != "SuperAdmins":
return
grant_access_to_objects(
user, User.objects.all(),
ContentType.objects.get_for_model(User)
)
grant_access_to_objects(
user, Domain.objects.all(),
ContentType.objects.get_for_model(Domain)
)
grant_access_to_objects(
user, DomainAlias.objects.all(),
ContentType.objects.get_for_model(DomainAlias)
)
grant_access_to_objects(
user, Mailbox.objects.all(),
ContentType.objects.get_for_model(Mailbox)
)
grant_access_to_objects(
user, Alias.objects.all(),
ContentType.objects.get_for_model(Alias)
)
@events.observe("AccountExported")
def export_admin_domains(admin):
result = [admin.mailbox_set.all()[0].quota] \
if admin.mailbox_set.count() else ['']
if admin.group != "DomainAdmins":
return result
return result + [dom.name for dom in Domain.objects.get_for_admin(admin)]
@events.observe("AccountImported")
def import_account_mailbox(user, account, row):
"""Handle extra fields when an account is imported.
Expected fields:
email address; quota; [domain; ...]
:param User user: user importing the account
:param User account: account being imported
:param list rom: list of fields (strings)
"""
account.email = row[0].strip()
if account.email:
account.save()
mailbox, domname = split_mailbox(account.email)
try:
domain = Domain.objects.get(name=domname)
except Domain.DoesNotExist:
raise BadRequest(
_("Account import failed (%s): domain does not exist"
% account.username)
)
if not user.can_access(domain):
raise PermDeniedException
try:
mb = Mailbox.objects.get(address=mailbox, domain=domain)
except Mailbox.DoesNotExist:
pass
else:
raise Conflict(_("Mailbox %s already exists" % account.email))
if len(row) == 1:
quota = None
else:
try:
quota = int(row[1].strip())
except ValueError:
raise BadRequest(
_("Account import failed (%s): wrong quota value"
% account.username)
)
use_domain_quota = True if not quota else False
mb = Mailbox(address=mailbox, domain=domain,
user=account, use_domain_quota=use_domain_quota)
mb.set_quota(quota, override_rules=user.has_perm("admin.change_domain"))
mb.save(creator=user)
if account.group == "DomainAdmins":
for domname in row[2:]:
try:
dom = Domain.objects.get(name=domname.strip())
except Domain.DoesNotExist:
continue
dom.add_admin(account)
@events.observe("AccountAutoCreated")
def account_auto_created(user):
from modoboa.core.models import User
from modoboa.extensions.admin.lib import check_if_domain_exists
from modoboa.lib.permissions import grant_access_to_object
if parameters.get_admin("AUTO_CREATE_DOMAIN_AND_MAILBOX") == "no":
return
localpart, domname = split_mailbox(user.username)
if user.group != 'SimpleUsers' and domname is None:
return
sadmins = User.objects.filter(is_superuser=True)
try:
domain = Domain.objects.get(name=domname)
except Domain.DoesNotExist:
label = check_if_domain_exists(
domname, [(DomainAlias, _('domain alias'))])
if label is not None:
return
domain = Domain(name=domname, enabled=True, quota=0)
domain.save(creator=sadmins[0])
for su in sadmins[1:]:
grant_access_to_object(su, domain)
try:
mb = Mailbox.objects.get(domain=domain, address=localpart)
except Mailbox.DoesNotExist:
mb = Mailbox(
address=localpart, domain=domain, user=user, use_domain_quota=True
)
mb.set_quota(override_rules=True)
mb.save(creator=sadmins[0])
for su in sadmins[1:]:
grant_access_to_object(su, mb)
@events.observe("UserLogin")
def user_logged_in(request, username, password):
from modoboa.lib.cryptutils import encrypt
if request.user.mailbox_set.count():
request.session["password"] = encrypt(password)
@events.observe("AccountDeleted")
def account_deleted(account, byuser, **kwargs):
"""'AccountDeleted' listener.
When an account is deleted, we also need to remove its mailbox (if
any).
"""
if not account.mailbox_set.count():
return
mb = account.mailbox_set.all()[0]
if not byuser.can_access(mb):
raise PermDeniedException
keep_mb_dir = kwargs.get("keep_mb_dir", True)
mb.delete(keepdir=keep_mb_dir)
@@ -1,33 +0,0 @@
#searchform {
margin-bottom: 10px;
}
span.title {
margin-left: 40px;
margin-bottom: 10px;
float: left;
font-weight: bold;
width: 180px;
}
table th#actions {
text-align: right;
}
table td[name="actions"] {
text-align: right;
}
table .progress {
margin-bottom: 0 !important;
}
table tbody tr:hover {
background-color: #F9F9F9;
}
.search {
width: 85%;
-webkit-border-radius:14px;-moz-border-radius:14px;border-radius:14px;
padding-left: 14px;
}
@@ -1,9 +0,0 @@
#settings_content .form-horizontal .control-label {
margin-left: 20px;
text-align: left;
}
#settings_content > .form-horizontal .controls {
margin-left: 240px;
}
@@ -1,556 +0,0 @@
/**
* Creates an instance of Admin.
*
* @constructor
* @param {Object} options - instance options
* @classdesc This class contains utility methods used by the admin
* application.
*/
var Admin = function(options) {
Listing.call(this, options);
};
Admin.prototype = {
defaults: {
deflocation: "list/",
squery: null
},
initialize: function(options) {
this.options = $.extend({}, this.defaults, options);
this.options.defcallback = $.proxy(this.list_cb, this);
Listing.prototype.initialize.call(this, this.options);
this.listen();
},
/**
* Callback used when the initial content of the listing is
* received.
*
* @this Admin
* @param {Object} data - response of the ajax call (JSON)
*/
list_cb: function(data) {
if (data.rows) {
$("#objects_table tbody").html(data.rows);
} else {
$("#objects_table tbody").html("");
}
this.update_listing(data);
if (data.length === 0) {
this.get_load_page_args();
this.end_of_list_reached();
}
},
/**
* Children must override this method.
*/
listen: function() {
},
/**
* Load a new page using an AJAX request.
*
* @this Admin
* @param {Object} e - event object
*/
page_loader: function(e) {
var $link = get_target(e);
e.preventDefault();
if ($link.hasClass("navigation")) {
$(".sidebar li.active").removeClass("active");
$link.parent().addClass("active");
}
this.navobj.baseurl($link.attr("href")).update();
},
importform_cb: function() {
$(".submit").one('click', function(e) {
e.preventDefault();
if ($("#id_sourcefile").val() === "") {
return;
}
$("#import_status").css("display", "block");
$("#import_result").html("").removeClass("alert alert-danger");
$("#importform").submit();
});
},
importdone: function(status, msg) {
$("#import_status").css("display", "none");
if (status == "ok") {
$("#modalbox").modal('hide');
this.reload_listing(msg);
} else {
$("#import_result").addClass("alert alert-danger");
$("#import_result").html(msg);
this.importform_cb();
}
},
exportform_cb: function() {
$(".submit").one('click', function(e) {
e.preventDefault();
$("#exportform").submit();
$("#modalbox").modal('hide');
});
},
reload_listing: function(data) {
this.navobj.update(true);
if (data) {
$("body").notify("success", data, 2000);
}
}
};
Admin.prototype = $.extend({}, Listing.prototype, Admin.prototype);
/**
* Creates an instance of Domains.
*
* @constructor
* @param {Object} options - instance options
* @classdesc This class extends the Admin one by adding methods
* specific to domains management.
*/
var Domains = function(options) {
Admin.call(this, options);
};
Domains.prototype = {
initialize: function(options) {
Admin.prototype.initialize.call(this, options);
this.options.navigation_params.push("domfilter", "srvfilter");
this.options.eor_message = gettext("No more domain to show");
this.register_tag_handler("dom");
},
/**
* Custom callbacks declaration.
*/
listen: function() {
Admin.prototype.listen.call(this);
$(document).on("click", "a.ajaxnav", $.proxy(this.page_loader, this));
},
/**
* Initialize the domain links embedded within a page.
*
* @param {Object} data - options
*/
init_domain_links: function(data) {
var deloptions = (data.handle_mailboxes) ?
{keepdir: gettext("Do not delete domain directory")} : {};
var warnmsg = (data.auto_account_removal && data.auto_account_removal == "yes")
? gettext("This operation will remove ALL data associated to this domain.")
: gettext("This operation will remove all data associated to this domain, excepting accounts.");
$("a[name=deldomain]").confirm({
question: function() { return this.$element.attr('title'); },
method: "POST",
warning: warnmsg,
checkboxes: deloptions,
success_cb: $.proxy(this.reload_listing, this)
});
$(document).trigger('domain_listing_refresh');
},
/**
* Navigation callback: default.
*
* @param {Object} data - response of the ajax call (JSON)
*/
list_cb: function(data) {
Admin.prototype.list_cb.call(this, data);
this.init_domain_links(data);
},
/**
* A new page has been received, inject it.
*
* @param {Object} data - page content
* @param {string} direction -
*/
add_new_page: function(data, direction) {
Admin.prototype.add_new_page.call(this, data, direction);
this.init_domain_links(data);
},
change_inputs_state: function(value) {
$("#id_dom_admin_username").attr("disabled", value);
$("input[name=create_aliases]").attr("disabled", value);
},
create_dom_admin_changed: function(e) {
var $target = $(e.target);
this.change_inputs_state(($target.val() == "yes") ? false : true);
},
/**
* Initialize the main form contained in the domain edition modal.
*
* @this Domains
*/
generalform_init: function() {
$('input:text:visible:first').focus();
$("#original_aliases").dynamic_input({
input_added: function($row) {
$row.find("label").html("");
},
input_removed: function($input) {
$input.parents(".form-group").remove();
return true;
}
});
},
optionsform_init: function() {
$("input[name=create_dom_admin]").click($.proxy(this.create_dom_admin_changed, this));
this.change_inputs_state(
$("input[name=create_dom_admin]:checked").val() == "yes" ? false : true
);
this.optionsform_prefill();
},
optionsform_prefill: function() {
var $span = $("#id_dom_admin_username").next("span");
$span.html("@" + $("#id_name").val());
},
domadminsform_init: function() {
$("a[name=removeperm]").click(function(e) {
var $tr = $(this).parents('tr');
simple_ajax_request.apply(this, [e, {
ok_cb: function(resp) {
$tr.remove();
if (!$("#domadmins").find("tr").length) {
$("#admins").html('<div class="alert alert-info">'
+ gettext("No domain administrator defined") + "</div>");
}
}
}]);
});
},
newdomain_cb: function() {
this.generalform_init();
this.optionsform_init();
$("#wizard").cwizard({
formid: "domform",
transition_callbacks: {
1: this.optionsform_prefill
},
error_callbacks: {
2: $.proxy(this.optionsform_init, this)
},
success_callback: $.proxy(this.reload_listing, this)
});
},
domainform_cb: function() {
this.generalform_init();
this.domadminsform_init();
$(".submit").one('click', $.proxy(function(e) {
simple_ajax_form_post(e, {
formid: "domform",
error_cb: $.proxy(this.domainform_cb, this),
reload_on_success: false,
success_cb: $.proxy(this.reload_listing, this)
});
}, this));
$(document).trigger('domform_init');
}
};
Domains.prototype = $.extend({}, Admin.prototype, Domains.prototype);
/**
* Creates an instance of Identities.
*
* @constructor
* @param {Object} options - instance options
* @classdesc This class extends the Admin one by adding methods
* specific to identities management.
*/
var Identities = function(options) {
Admin.call(this, options);
};
Identities.prototype = {
initialize: function(options) {
Admin.prototype.initialize.call(this, options);
this.options.navigation_params.push("idtfilter", "grpfilter");
this.options.eor_message = gettext("No more identity to show");
this.domain_list = [];
this.register_tag_handler("idt");
this.register_tag_handler("grp", this.grp_tag_handler);
},
listen: function() {
Admin.prototype.listen.call(this);
$(document).on("click", "a.ajaxnav", $.proxy(this.page_loader, this));
$(document).on("click", "a.ajaxcall", $.proxy(this.send_call, this));
},
/**
* Send an ajax call.
*/
send_call: function(evt) {
var $link = get_target(evt, "a");
var $this = this;
var method = $link.attr("data-call_method");
if (method === undefined) {
method = "GET";
}
evt.preventDefault();
$.ajax({
url: $link.attr("href"),
type: method,
dataType: "json"
}).done(function(data) {
$this.reload_listing(data.respmsg);
});
},
/**
* Initialize the links embedded within a page.
*
* @param {Object} data - options
*/
init_identity_links: function(data) {
var deloptions = {};
if (data.handle_mailboxes == "yes") {
deloptions = {keepdir: gettext("Do not delete mailbox directory")};
}
$("a[name=delaccount]").confirm({
question: function() { return this.$element.attr('title'); },
method: "POST",
checkboxes: deloptions,
success_cb: $.proxy(this.reload_listing, this)
});
$("a[name=delalias]").confirm({
question: function() { return this.$element.attr('title'); },
method: "DELETE",
success_cb: $.proxy(this.reload_listing, this)
});
},
/**
* Add a type to let the server know what kind of object we expect.
*
* @this Listing
*/
get_load_page_args: function() {
var args = Admin.prototype.get_load_page_args.call(this);
if (this.navobj.getbaseurl() == "list") {
args.objtype = "identity";
this.options.eor_message = gettext("No more identity to show");
} else {
args.objtype = "quota";
this.options.eor_message = gettext("No more quota to show");
}
return args;
},
/**
* A new page has been received, inject it.
*
* @param {Object} data - page content
* @param {string} direction -
*/
add_new_page: function(data, direction) {
Admin.prototype.add_new_page.call(this, data, direction);
this.init_identity_links(data);
},
/**
* Navigation callback: default.
*
* @param {Object} data - response of the ajax call (JSON)
*/
list_cb: function(data) {
if ($(".sidebar li.active").length === 0) {
var menu = this.navobj.getbaseurl();
if (menu == "list") {
menu = "identities";
}
$("a[name={0}]".format(menu)).parent().addClass("active");
}
$("#objects_table thead tr").html(data.headers);
Admin.prototype.list_cb.call(this, data);
this.init_identity_links(data);
},
grp_tag_handler: function(tag, $link) {
if (this.navobj.getparam(tag + "filter") === undefined &&
$link.hasClass(tag)) {
var text = $link.attr("name");
this.navobj
.setparam("idtfilter", "account")
.setparam(tag + "filter", text)
.update();
if ($("a[name=idt]").length === 0) {
$("#taglist").append(this.make_tag("account", "idt"));
}
$("#taglist").append(this.make_tag(text, tag));
return true;
}
return false;
},
/**
* Retrieve a list of domain from the server.
*
* @this Identities
* @return {Array} a list of domain names
*/
get_domain_list: function() {
if (!this.domain_list.length) {
$.ajax({
url: this.options.domain_list_url,
dataType: "json",
async: false
}).done($.proxy(function(data) {
this.domain_list = data;
}, this));
}
return this.domain_list;
},
simpleuser_mode: function() {
$("#id_username").autocompleter({
from_character: "@",
choices: $.proxy(this.get_domain_list, this)
});
$("#id_email").addClass("disabled")
.attr("readonly", "")
.autocompleter("unbind");
},
normal_mode: function() {
$("#id_email").removeClass("disabled")
.attr("readonly", null)
.autocompleter("listen");
},
generalform_init: function(notrigger) {
$("#id_role").change($.proxy(function(e) {
var $this = $(e.target);
var value = $this.val();
if (value == "SimpleUsers" || value === "") {
this.simpleuser_mode();
} else {
this.normal_mode();
}
}, this));
if (notrigger !== undefined && notrigger) {
return;
}
$("#id_role").trigger("change");
},
mailform_init: function() {
$("#id_aliases").autocompleter({
from_character: "@",
choices: $.proxy(this.get_domain_list, this)
});
$("#original_aliases").dynamic_input({
input_added: function($row) {
$row.find("label").html("");
},
input_removed: function($input) {
$input.parents(".form-group").remove();
return true;
}
});
$("#id_email").autocompleter({
from_character: "@",
choices: $.proxy(this.get_domain_list, this)
});
if ($("#id_role").length) {
$("#id_role").trigger("change");
} else {
this.simpleuser_mode();
}
$("#id_domains").autocompleter({
choices: $.proxy(this.get_domain_list, this)
});
$("#original_domains").dynamic_input({
input_added: function($row) {
$row.find("label").html("");
},
input_removed: function($input) {
$input.parents(".form-group").remove();
return true;
}
});
activate_widget.call($("#id_quota_act"));
},
accountform_init: function() {
this.generalform_init(true);
this.mailform_init();
},
mailform_prefill: function() {
var $role = $("#id_role");
if (!$role.length || $role.val() === "" || $role.val() == "SimpleUsers") {
$("#id_email").val($("#id_username").val());
}
},
newaccount_cb: function() {
this.accountform_init();
$("#wizard").cwizard({
formid: "newaccount_form",
transition_callbacks: {
1: this.mailform_prefill
},
success_callback: $.proxy(this.reload_listing, this)
});
},
editaccount_cb: function() {
this.accountform_init();
$('.submit').on('click', $.proxy(function(e) {
simple_ajax_form_post(e, {
formid: "accountform",
reload_on_success: false,
success_cb: $.proxy(this.reload_listing, this)
});
}, this));
},
aliasform_cb: function() {
$("#id_email").autocompleter({
from_character: "@",
choices: $.proxy(this.get_domain_list, this)
});
$("#original_recipients").dynamic_input({
input_added: function($row) {
$row.find("label").html("");
},
input_removed: function($input) {
$input.parents(".form-group").remove();
return true;
}
});
$(".submit").on('click', $.proxy(function(e) {
simple_ajax_form_post(e, {
formid: "aliasform",
reload_on_success: false,
success_cb: $.proxy(this.reload_listing, this)
});
}, this));
}
};
Identities.prototype = $.extend({}, Admin.prototype, Identities.prototype);
-15
View File
@@ -1,15 +0,0 @@
# coding: utf-8
from django.utils.translation import ugettext_lazy
from modoboa.lib import tables
class ExtensionsTable(tables.Table):
idkey = "id"
selection = tables.SelectionColumn("selection", width="4%", header=False)
label = tables.Column("label", label=ugettext_lazy("Name"), width="15%")
version = tables.Column(
"version", label=ugettext_lazy("Version"), width="6%"
)
descr = tables.Column("description", label=ugettext_lazy("Description"))
cols_order = ["selection", "label", "version", "descr"]
@@ -1,12 +0,0 @@
{% load form_tags %}
{% render_field form.role %}
{% render_field form.username %}
{% render_field form.first_name %}
{% render_field form.last_name %}
{% render_field form.password1 %}
{% render_field form.password2 %}
{% if form.domain_is_disabled %}
{% include "common/generic_field.html" with field=form.is_active label_width="col-sm-4" inline_message=_("Domain is disabled") %}
{% else %}
{% render_field form.is_active %}
{% endif %}
@@ -1,10 +0,0 @@
{% extends "common/generic_modal_form.html" %}{% load form_tags %}
{% block formbody %}
{% render_field form.email %}
{% render_fields_group form "recipients" %}
{% if not form.instance.pk or form.instance.domain.enabled %}
{% render_field form.enabled %}
{% else %}
{% include "common/generic_field.html" with field=form.enabled label_width="col-sm-4" inline_message=_("Domain is disabled") extra_classes="info" %}
{% endif %}
{% endblock %}
@@ -1,5 +0,0 @@
{% load i18n form_tags %}
{% render_field form.name %}
{% render_field form.quota appended_text=_("MB") %}
{% render_fields_group form "aliases" %}
{% render_field form.enabled %}
@@ -1,8 +0,0 @@
{% load i18n form_tags %}
{% if form.fields %}
{% render_field form.create_dom_admin %}
{% render_field form.dom_admin_username appended_text="@" %}
{% render_field form.create_aliases %}
{% else %}
<div class="alert alert-warning">{% trans "You can't create a new domain administrator" %}</div>
{% endif %}
@@ -1,63 +0,0 @@
{% extends "admin/header.html" %}
{% load i18n %}
{% load admin_tags %}
{% block leftcol %}
<ul class="nav nav-sidebar">
{% domains_menu "statistics" user %}
</ul>
{% get_extra_admin_content user "leftcol" selection %}
{% endblock %}
{% block apparea %}
<h2>{% trans "Statistics" %} <small>{% trans "Domains and identities" %}</small></h2>
<hr>
<div class="row">
{% if user.group == "SuperAdmins" %}
<div class="col-xs-12 col-sm-6">
<div class="panel panel-info">
<div class="panel-heading"><h3 class="panel-title">{% trans "Global counters" %}</h3></div>
<div class="panel-body"></div>
<table class="table">
<tr>
<th>{% trans "Type" %}</th>
<th>{% trans "Quantity" %}</th>
</tr>
<tr>
<td>{% trans "Domains" %}</td><td>{{ domains_counter }}</td>
</tr>
<tr>
<td>{% trans "Domain aliases" %}</td><td>{{ domain_aliases_counter }}</td>
</tr>
<tr>
<td>{% trans "Identities" %}</td><td>{{ identities_counter }}</td>
</tr>
</table>
</div>
</div>
{% endif %}
<div class="col-xs-12 col-sm-6">
<div class="panel panel-info">
<div class="panel-heading"><h3 class="panel-title">{% trans "Per-domain counters" %}</h3></div>
<div class="panel-body"></div>
<table class="table">
<tr>
<th>{% trans "Domain" %}</th>
<th>{% trans "Identities" %}</th>
</tr>
{% for domain in domains %}
<tr>
<td>{{ domain.name }}</td><td>{{ domain.identities_count }}</td>
</tr>
{% endfor %}
</table>
</div>
</div>
</div>
{% endblock %}
@@ -1,29 +0,0 @@
{% extends "admin/listing.html" %}{% load i18n admin_tags lib_tags %}
{% block pagetitle %}{% trans "Domains" %}{% endblock %}
{% block extra_css %}
{{ block.super }}
{% extra_static_content "domains" "css" user %}
{% endblock %}
{% block extra_js %}
{{ block.super }}
<script type="text/javascript">
$(document).ready(function() {
admin = new Domains({
load_page_url: "{% url 'admin:_domain_list' %}"
});
});
</script>
{% extra_static_content "domains" "js" user %}
{% endblock %}
{% block actions %}{% domains_menu "domains" user %}{% endblock %}
{% block table_header %}
<th id="name" class="sortable" data-sort_order="name" width="">{% trans "Name" %}</th>
<th id="aliases" width="">{% trans "Alias(es)" %}</th>
<th id="tags" class="sortable" data-sort_order="tags" width="">{% trans "Tags" %}</th>
<th id="actions" width="">{% trans "Actions" %}</th>
{% endblock %}
@@ -1,9 +0,0 @@
{% load i18n admin_tags lib_tags %}
{% for domain in domains %}
<tr id="{{ domain.id }}" class="{% if not domain.enabled %}text-muted{% endif %}" name="{{ forloop.counter }}">
<td name="name">{% domain_modify_link domain %}{% if not domain.enabled %} ({% trans "disabled" %}){% endif %}</td>
<td name="aliases">{% domain_aliases domain %} </td>
<td name="tags">{% render_tags domain.tags %}</td>
<td name="actions">{% domain_actions user domain %}</td>
</tr>
{% endfor %}
@@ -1,28 +0,0 @@
{% extends "common/tabforms.html" %}{% load i18n lib_tags admin_tags %}
{% block extratabs %}
<li class="{% if tabs.active_id == 'admins' %}active{% endif %}">
<a href="#admins" data-toggle="tab">{% trans "Administrators" %}</a>
</li>
<li class="{% if tabs.active_id == 'misc' %}active{% endif %}">
<a href="#misc" data-toggle="tab">{% trans "Miscellaneous" %}</a>
</li>
{% endblock %}
{% block extratabs_content %}
<div class="tab-pane{% if tabs.active_id == 'admins' %} active{% endif %}" id="admins">{% if domadmins %}
<table id="domadmins" class="table table-condensed">
<tr><th>{% trans "Username" %}</th><th>{% trans "Name" %}</th><th></th></tr>
{% for da in domadmins %}<tr>
<td>{{ da.username }}</td>
<td>{{ da.fullname }}</td>
<td name="actions">{% if perms.admin.change_domain %}{% domadmin_actions da.id domain.id %}{% endif %}</td>
</tr>{% endfor %}
</table>{% else %}<div class="alert alert-info">{% trans "No domain administrator defined" %}</div>{% endif %}
</div>
<div class="tab-pane{% if tabs.active_id == 'misc' %} active{% endif %}" id="misc">
<div class="row"><span class="title">{% trans "Creation date" %}</span><span>{{ domain.creation }}</span></div>
<div class="row"><span class="title">{% trans "Last modification date" %}</span><span>{{ domain.last_modification }}</span></div>
<div class="row"><span class="title">{% trans "Domain aliases" %}</span><span>{{ domain.domainalias_count }}</span></div>
<div class="row"><span class="title">{% trans "Mailboxes" %}</span><span>{{ domain.mailbox_count }}</span></div>
<div class="row"><span class="title">{% trans "Mailbox aliases" %}</span><span>{{ domain.mbalias_count }}</span></div>
</div>
{% endblock %}
@@ -1,11 +0,0 @@
{% load i18n form_tags %}{% load url from future %}
<h2>{% trans "Forward" %} <small>{% trans "Automatically forward emails to another address" %}</small></h2>
<hr>
<form class="form-horizontal" method="POST" action="{% url 'user_forward' %}"
id="uprefs_form">
{% csrf_token %}
{% render_form form %}
<div class="form-actions">
<button type="submit" class="btn btn-primary col-lg-offset-8 col-md-offset-8 col-sm-offset-7 col-lg-1 col-md-1 col-sm-2" id="update" name="update">{% trans 'Update' %}</button>
</div>
</form>
@@ -1,12 +0,0 @@
{% extends "twocols.html" %}
{% load static %}
{% block extra_css %}
<link rel="stylesheet" type="text/css" href="{% static 'admin/css/admin.css' %}" />
{% endblock %}
{% block extra_js %}
<script type="text/javascript" src="{% static 'js/history.js' %}"></script>
<script type="text/javascript" src="{% static 'js/listing.js' %}"></script>
<script type="text/javascript" src="{% static 'admin/js/admin.js' %}"></script>
{% endblock %}
@@ -1,26 +0,0 @@
{% extends "admin/listing.html" %}
{% load admin_tags lib_tags i18n %}{% load url from future %}
{% load static %}
{% block pagetitle %}{% trans "Identities" %}{% endblock %}
{% block extra_css %}
{{ block.super }}
{% extra_static_content "identities" "css" user %}
{% endblock %}
{% block extra_js %}
{{ block.super }}
<script type="text/javascript" src="{% static 'js/autocompleter.js' %}"></script>
<script type="text/javascript">
$(document).ready(function() {
admin = new Identities({
domain_list_url: "{% url 'admin:domain_flat_list' %}",
load_page_url: "{% url 'admin:identity_page' %}"
});
});
</script>
{% extra_static_content "identities" "js" user %}
{% endblock %}
{% block actions %}{% identities_menu user "identities" %}{% endblock %}
@@ -1,10 +0,0 @@
{% load i18n admin_tags lib_tags %}
{% for ident in identities %}
{% check_identity_status ident as identity_is_enabled %}
<tr id="{{ ident.id }}" class="{% if not identity_is_enabled %}text-muted{% endif %}" name="{{ forloop.counter }}">
<td name="identity">{% identity_modify_link ident %}{% if not identity_is_enabled %} (disabled){% endif %}</td>
<td name="name_or_rcpt">{{ ident.name_or_rcpt }}</td>
<td name="tags">{% render_tags ident.tags %}</td>
<td name="actions">{% identity_actions user ident %}</td>
</tr>
{% endfor %}
@@ -1,5 +0,0 @@
{% load i18n %}
<th id="identity" class="sortable" data-sort_order="identity" width="">{% trans "Name" %}</th>
<th id="name_or_rcpt" class="sortable" data-sort_order="name_or_rcpt" width="">{% trans "Fullname/Recipient" %}</th>
<th id="tags" class="sortable" data-sort_order="tags" width="">{% trans "Tags" %}</th>
<th id="actions" width="">{% trans "Actions" %}</th>
@@ -1,11 +0,0 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head profile="http://www.w3.org/2005/10/profile">
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<script type="text/javascript">
parent.admin.importdone("{{ status }}", "{{ msg|escapejs }}");
</script>
</head>
<body></body>
</html>
@@ -1,16 +0,0 @@
{% extends "common/generic_modal_form.html" %}
{% load form_tags i18n %}
{% load static %}
{% block formbody %}
<div class="alert alert-info">
<h4>{% trans "Help" %}</h4>
{{ helptext|safe }}
</div>
{% render_form form %}
<iframe id="import_target" name="import_target" src="#"
style="width:0;height:0;border:0px solid #fff;"></iframe>
<div id="import_status" style="text-align: center;display:none">
<img src="{% static 'css/spinner.gif' %}" /> {% trans "Importing..." %}
</div>
<div id="import_result"></div>
{% endblock %}
@@ -1,47 +0,0 @@
{% extends "admin/header.html" %}{% load i18n admin_tags %}
{% load static %}
{% block extra_css %}
{{ block.super }}
<link rel="stylesheet" type="text/css" href="{% static 'css/jquery.sortable.css' %}" />
{% endblock %}
{% block extra_js %}
{{ block.super }}
<script type="text/javascript" src="{% static 'js/creation_wizard.js' %}"></script>
<script type="text/javascript" src="{% static 'js/dynamic_input.js' %}"></script>
<script type="text/javascript" src="{% static 'js/jquery.sortable.js' %}"></script>
<script type="text/javascript" src="{% static 'js/jquery.infinitescroll.js' %}"></script>
<script type="text/javascript">
var admin;
</script>
{% endblock %}
{% block leftcol %}
<ul class="nav nav-sidebar">
{% block actions %}{% endblock %}
</ul>
{% get_extra_admin_content user "leftcol" selection %}
{% endblock %}
{% block apparea %}
<div class="row">
<div class="col-sm-5">
{% include "admin/searchform.html" %}
</div>
<div id="taglist" class="col-sm-5"> </div>
</div>
<div id="listing">
<table id="objects_table" class="table {{ styles }}">
<thead>
<tr>
{% block table_header %}{% endblock %}
</tr>
</thead>
<tbody>
</tbody>
</table>
</div>
{% endblock %}
@@ -1,11 +0,0 @@
{% load i18n %}
{% if target == "permissions" %}
<option value="">{% trans "Choose a value" %}</option>
{% endif %}
{% for mb in mailboxes %}
{% if target == "permissions" %}
<option value='{{ mb.id }}'>{{ mb.name }}</option>
{% else %}
<option value='{{ mb.id }}'>{{ mb.full_address }}</option>
{% endif %}
{% endfor %}
@@ -1,5 +0,0 @@
{% load form_tags %}
{% render_field form.email %}
{% include "common/generic_field.html" with field=form.quota help_display_mode="tooltip" label_width="col-sm-4" appended_text=_("MB") activator=True activator_value=form.quota_act.value deactivate_if_empty=False activator_label=_("Use domain default value") activator_size="col-sm-5" %}
{% render_extra_fields form %}
{% render_fields_group form "aliases" %}
@@ -1,6 +0,0 @@
{% extends "common/generic_modal_form.html" %}{% load form_tags %}{% block formbody %}
{% render_field form.name %}
{% render_field form.quota appended_text=_("MB") %}
{% render_fields_group form "aliases" %}
{% render_field form.enabled %}
{% endblock %}
@@ -1,2 +0,0 @@
{% load form_tags %}
{% render_fields_group form "domains" %}
@@ -1,5 +0,0 @@
{% load i18n %}
<th width="50%" class="sortable" data-sort_order="address">{% trans "Address" %}</th>
<th class="sortable" data-sort_order="quota_value__bytes">{% trans "Value" %}</th>
<th class="sortable" data-sort_order="quota">{% trans "Limit" %}</th>
<th class="sortable" data-sort_order="quota_usage" id="quotabar">{% trans "Usage (%)" %}</th>
@@ -1,15 +0,0 @@
{% load lib_tags admin_tags %}
{% for mbox in mboxes %}
<tr>
<td>{% identity_modify_link mbox.user "mail" %}</td>
<td>{{ mbox.get_quota }}M</td>
<td>{{ mbox.quota }}M</td>
<td name="quota">
<div id="quotabar" rel="tooltip" title="{{ mbox.get_quota_in_percent }}%"
class="progress">
<div class="{% progress_color mbox.get_quota_in_percent %}" style="width: {{ mbox.get_quota_in_percent }}%"></div>
</div>
</td>
</tr>
{% endfor %}
@@ -1,11 +0,0 @@
{% load i18n %}
<form id="searchform" class="form-horizontal" role="form">
<div class="form-group has-feedback">
<div class="col-sm-12">
<input class="form-control" type="text"
value={% if squery %}"{{ squery }}"{% else %}"{% trans 'Search' %}"{% endif %}
id="searchquery" name="searchquery" />
<span class="fa fa-search form-control-feedback"></span>
</div>
</div>
</form>
@@ -1,280 +0,0 @@
"""
Admin extension tags.
"""
from django import template
from django.core.urlresolvers import reverse
from django.template.loader import render_to_string
from django.utils.translation import ugettext as _, ugettext_lazy
from modoboa.lib import events
from modoboa.lib.templatetags.lib_tags import render_link
from modoboa.lib.web_utils import render_actions
register = template.Library()
genders = {
"Enabled": (ugettext_lazy("enabled_m"), ugettext_lazy("enabled_f"))
}
@register.simple_tag
def domains_menu(selection, user):
"""Specific menu for domain related operations.
Corresponds to the menu visible on the left column when you go to
*Domains*.
:param str selection: menu entry currently selected
:param ``User`` user: connected user
:rtype: str
:return: rendered menu (as HTML)
"""
domain_list_url = (
"list/" if selection != "statistics" else reverse("admin:domain_list")
)
entries = [
{"name": "domains",
"label": _("List domains"),
"img": "fa fa-user",
"class": "ajaxnav navigation",
"url": domain_list_url},
{"name": "statistics",
"label": _("Statistics"),
"img": "fa fa-line-chart",
"class": "navigation",
"url": reverse("admin:domain_statistics")}
]
if user.has_perm("admin.add_domain"):
entries += [
{"name": "newdomain",
"label": _("Add domain"),
"img": "fa fa-plus",
"modal": True,
"modalcb": "admin.newdomain_cb",
"url": reverse("admin:domain_add")},
]
entries += events.raiseQueryEvent("ExtraDomainMenuEntries", user)
entries += [
{"name": "import",
"label": _("Import"),
"img": "fa fa-folder-open",
"url": reverse("admin:domain_import"),
"modal": True,
"modalcb": "admin.importform_cb"},
{"name": "export",
"label": _("Export"),
"img": "fa fa-share-alt",
"url": reverse("admin:domain_export"),
"modal": True,
"modalcb": "admin.exportform_cb"}
]
return render_to_string('common/menulist.html', {
"entries": entries,
"selection": selection,
"user": user
})
@register.simple_tag
def identities_menu(user, selection=None):
"""Menu specific to the Identities page.
:param ``User`` user: the connecter user
:rtype: str
:return: the rendered menu
"""
entries = [
{"name": "identities",
"label": _("List identities"),
"img": "fa fa-user",
"class": "ajaxnav navigation",
"url": "list/"},
{"name": "quotas",
"label": _("List quotas"),
"img": "fa fa-hdd-o",
"class": "ajaxnav navigation",
"url": "quotas/"},
{"name": "newaccount",
"label": _("Add account"),
"img": "fa fa-plus",
"modal": True,
"modalcb": "admin.newaccount_cb",
"url": reverse("admin:account_add")},
{"name": "newalias",
"label": _("Add alias"),
"img": "fa fa-plus",
"modal": True,
"modalcb": "admin.aliasform_cb",
"url": reverse("admin:alias_add")},
{"name": "newforward",
"label": _("Add forward"),
"img": "fa fa-plus",
"modal": True,
"modalcb": "admin.aliasform_cb",
"url": reverse("admin:forward_add")},
{"name": "newdlist",
"label": _("Add distribution list"),
"img": "fa fa-plus",
"modal": True,
"modalcb": "admin.aliasform_cb",
"url": reverse("admin:dlist_add")},
{"name": "import",
"label": _("Import"),
"img": "fa fa-folder-open",
"url": reverse("admin:identity_import"),
"modal": True,
"modalcb": "admin.importform_cb"},
{"name": "export",
"label": _("Export"),
"img": "fa fa-share-alt",
"url": reverse("admin:identity_export"),
"modal": True,
"modalcb": "admin.exportform_cb"
}
]
return render_to_string('common/menulist.html', {
"entries": entries,
"user": user
})
@register.simple_tag
def domain_actions(user, domain):
actions = []
if domain.__class__.__name__ == 'Domain':
actions = [
{"name": "listidentities",
"url": "{0}#list/?searchquery=@{1}".format(
reverse("admin:identity_list"), domain.name),
"title": _("View the domain's identities"),
"img": "fa fa-user"}
]
if user.has_perm("admin.delete_domain"):
actions.append({
"name": "deldomain",
"url": reverse("admin:domain_delete", args=[domain.id]),
"title": _("Delete %s?" % domain.name),
"img": "fa fa-trash"
})
else:
actions = events.raiseQueryEvent('GetDomainActions', user, domain)
return render_actions(actions)
@register.simple_tag
def identity_actions(user, ident):
name = ident.__class__.__name__
objid = ident.id
if name == "User":
actions = events.raiseQueryEvent("ExtraAccountActions", ident)
actions += [
{"name": "delaccount",
"url": reverse("admin:account_delete", args=[objid]),
"img": "fa fa-trash",
"title": _("Delete %s?" % ident.username)},
]
else:
actions = [
{"name": "delalias",
"url": reverse("admin:alias_delete") + "?selection=%s" % objid,
"img": "fa fa-trash",
"title": _("Delete %s?" % ident.full_address)},
]
return render_actions(actions)
@register.assignment_tag
def check_identity_status(identity):
"""Check if identity is enabled or not."""
if identity.__class__.__name__ == "User":
if identity.mailbox_set.count() \
and not identity.mailbox_set.all()[0].domain.enabled:
return False
elif not identity.is_active:
return False
elif not identity.enabled or not identity.domain.enabled:
return False
return True
@register.simple_tag
def domain_modify_link(domain):
linkdef = {"label": domain.name, "modal": True}
if domain.__class__.__name__ == "Domain":
linkdef["url"] = reverse("admin:domain_change", args=[domain.id])
linkdef["modalcb"] = "admin.domainform_cb"
else:
tmp = events.raiseDictEvent('GetDomainModifyLink', domain)
for key in ['url', 'modalcb']:
linkdef[key] = tmp[key]
return render_link(linkdef)
@register.simple_tag
def domain_aliases(domain):
"""Display domain aliases of this domain.
:param domain:
:rtype: str
"""
if not domain.aliases.count():
return '---'
res = ''
for alias in domain.aliases.all():
res += '%s<br/>' % alias.name
return res
@register.simple_tag
def identity_modify_link(identity, active_tab='default'):
"""Return the appropriate modification link.
According to the identity type, a specific modification link (URL)
must be used.
:param identity: a ``User`` or ``Alias`` instance
:param str active_tab: the tab to display
:rtype: str
"""
linkdef = {"label": identity.identity, "modal": True}
if identity.__class__.__name__ == "User":
linkdef["url"] = reverse("admin:account_change", args=[identity.id])
linkdef["url"] += "?active_tab=%s" % active_tab
linkdef["modalcb"] = "admin.editaccount_cb"
else:
linkdef["url"] = reverse("admin:alias_change", args=[identity.id])
linkdef["modalcb"] = "admin.aliasform_cb"
return render_link(linkdef)
@register.simple_tag
def domadmin_actions(daid, domid):
actions = [{
"name": "removeperm",
"url": "{0}?domid={1}&daid={2}".format(
reverse("admin:permission_remove"), domid, daid),
"img": "fa fa-trash",
"title": _("Remove this permission")
}]
return render_actions(actions)
@register.filter
def gender(value, target):
if value in genders:
trans = target == "m" and genders[value][0] or genders[value][1]
if trans.find("_") == -1:
return trans
return value
@register.simple_tag
def get_extra_admin_content(user, target, currentpage):
res = events.raiseQueryEvent(
"ExtraAdminContent", user, target, currentpage
)
return "".join(res)
@@ -1,14 +0,0 @@
from .domain import DomainTestCase
from .domain_alias import DomainAliasTestCase
from .account import AccountTestCase, PermissionsTestCase
from .alias import AliasTestCase
from .import_ import ImportTestCase
from .export import ExportTestCase
from .password_schemes import PasswordSchemesTestCase
from .user import ForwardTestCase
__all__ = [
'DomainTestCase', 'DomainAliasTestCase', 'AccountTestCase',
'PermissionsTestCase', 'AliasTestCase', 'ImportTestCase',
'ExportTestCase', 'PasswordSchemesTestCase', 'ForwardTestCase'
]
-197
View File
@@ -1,197 +0,0 @@
from django.core.urlresolvers import reverse
from modoboa.core.models import User
from modoboa.extensions.admin import factories
from modoboa.extensions.admin.models import (
Domain, Mailbox
)
from modoboa.lib.tests import ModoTestCase
class AccountTestCase(ModoTestCase):
fixtures = ["initial_users.json"]
def setUp(self):
super(AccountTestCase, self).setUp()
factories.populate_database()
def test_crud(self):
values = dict(
username="tester@test.com", first_name="Tester", last_name="Toto",
password1="toto", password2="toto", role="SimpleUsers",
quota_act=True,
is_active=True, email="tester@test.com", stepid='step2'
)
self.ajax_post(reverse("admin:account_add"), values)
account = User.objects.get(username="tester@test.com")
mb = account.mailbox_set.all()[0]
self.assertEqual(mb.full_address, "tester@test.com")
self.assertEqual(mb.quota, 10)
self.assertEqual(mb.enabled, True)
self.assertEqual(mb.quota_value.username, "tester@test.com")
self.assertEqual(account.username, mb.full_address)
self.assertTrue(account.check_password("toto"))
self.assertEqual(account.first_name, "Tester")
self.assertEqual(account.last_name, "Toto")
self.assertEqual(mb.domain.mailbox_count, 3)
values["username"] = "pouet@test.com"
self.ajax_post(
reverse("admin:account_change", args=[account.id]), values
)
mb = Mailbox.objects.get(pk=mb.id)
self.assertEqual(mb.full_address, "pouet@test.com")
self.assertEqual(mb.quota_value.username, "pouet@test.com")
self.ajax_post(
reverse("admin:account_delete", args=[account.id]), {}
)
def _set_quota(self, email, value, expected_status=200):
account = User.objects.get(username=email)
values = {
"username": email, "role": "SimpleUsers", "quota_act": False,
"is_active": True, "quota": value, "email": email
}
self.ajax_post(
reverse("admin:account_change", args=[account.id]),
values, status=expected_status
)
def test_set_nul_quota_as_superadmin(self):
self._set_quota("user@test.com", 0)
def test_set_nul_quota_as_domainadmin(self):
self.clt.logout()
self.assertTrue(
self.clt.login(username="admin@test.com", password="toto")
)
self._set_quota("user@test.com", 0, 400)
self.clt.logout()
self.assertTrue(
self.clt.login(username="admin@test2.com", password="toto")
)
self._set_quota("user@test2.com", 0)
class PermissionsTestCase(ModoTestCase):
fixtures = ["initial_users.json"]
def setUp(self):
super(PermissionsTestCase, self).setUp()
factories.populate_database()
self.user = User.objects.get(username='user@test.com')
self.values = dict(
username=self.user.username, role="DomainAdmins",
is_active=self.user.is_active, email="user@test.com",
quota_act=True
)
def tearDown(self):
self.clt.logout()
def test_domain_admins(self):
self.ajax_post(
reverse("admin:account_change", args=[self.user.id]),
self.values
)
self.assertEqual(self.user.group, "DomainAdmins")
self.values["role"] = "SimpleUsers"
self.ajax_post(
reverse("admin:account_change", args=[self.user.id]),
self.values
)
self.assertNotEqual(self.user.group, 'DomainAdmins')
def test_superusers(self):
self.values["role"] = "SuperAdmins"
self.ajax_post(
reverse("admin:account_change", args=[self.user.id]),
self.values
)
self.assertEqual(
User.objects.get(username="user@test.com").is_superuser, True
)
self.values["role"] = "SimpleUsers"
self.ajax_post(
reverse("admin:account_change", args=[self.user.id]),
self.values
)
self.assertEqual(User.objects.get(
username="user@test.com").is_superuser, False)
def test_self_modif(self):
self.clt.logout()
self.assertTrue(
self.clt.login(username="admin@test.com", password="toto")
)
admin = User.objects.get(username="admin@test.com")
values = dict(
username="admin@test.com", first_name="Admin",
password1="", password2="",
quota=10, is_active=True, email="admin@test.com"
)
self.ajax_post(
reverse("admin:account_change", args=[admin.id]),
values
)
self.assertEqual(admin.group, "DomainAdmins")
self.assertTrue(admin.can_access(Domain.objects.get(name="test.com")))
values["role"] = "SuperAdmins"
self.ajax_post(
reverse("admin:account_change", args=[admin.id]),
values
)
admin = User.objects.get(username="admin@test.com")
self.assertEqual(admin.group, "DomainAdmins")
def test_domadmin_access(self):
self.clt.logout()
self.assertEqual(
self.clt.login(username="admin@test.com", password="toto"),
True)
response = self.clt.get(reverse("admin:domain_list"))
self.assertEqual(response.status_code, 200)
response = self.clt.get(
reverse("admin:account_change", args=[self.user.id]),
HTTP_X_REQUESTED_WITH='XMLHttpRequest')
self.assertNotEqual(response["Content-Type"], "application/json")
def test_domainadmin_deletes_superadmin(self):
"""Check domain admins restrictions about super admins
When a super admin owns a mailbox and a domain admin exists
for the associated domain, this domain admin must not be able
to access the super admin.
"""
values = dict(
username="superadmin2@test.com", first_name="Super",
last_name="Admin", password1="toto", password2="toto",
role="SuperAdmins", is_active=True,
email="superadmin2@test.com", stepid='step2'
)
self.ajax_post(
reverse("admin:account_add"),
values
)
account = User.objects.get(username="superadmin2@test.com")
self.clt.logout()
self.clt.login(username="admin@test.com", password="toto")
self.ajax_post(
reverse("admin:account_delete", args=[account.id]), {}, 403
)
def test_domainadmin_dlist_local_domain_not_owned(self):
"""Check if a domain admin can use a local mailbox he can't
access as a recipient in a distribution list"""
values = dict(
email="all@test.com",
recipients="user@test.com",
recipients_1="user@test2.com",
enabled=True
)
self.ajax_post(reverse("admin:dlist_add"), values)
-130
View File
@@ -1,130 +0,0 @@
from django.core.urlresolvers import reverse
from modoboa.core.models import User
from modoboa.extensions.admin import factories
from modoboa.extensions.admin.models import (
Alias
)
from modoboa.lib.tests import ModoTestCase
class AliasTestCase(ModoTestCase):
fixtures = ["initial_users.json"]
def setUp(self):
super(AliasTestCase, self).setUp()
factories.populate_database()
def test_alias(self):
user = User.objects.get(username="user@test.com")
values = dict(
username="user@test.com", role=user.group,
is_active=user.is_active, email="user@test.com",
aliases="toto@test.com", aliases_1="titi@test.com"
)
self.ajax_post(
reverse("admin:account_change", args=[user.id]),
values
)
self.assertEqual(user.mailbox_set.all()[0].alias_set.count(), 2)
del values["aliases_1"]
self.ajax_post(
reverse("admin:account_change", args=[user.id]),
values
)
self.assertEqual(user.mailbox_set.all()[0].alias_set.count(), 1)
def test_upper_case_alias(self):
"""Try to create an upper case alias."""
user = User.objects.get(username="user@test.com")
values = dict(
username="user@test.com", role=user.group,
is_active=user.is_active, email="user@test.com",
aliases="Toto@test.com"
)
self.ajax_post(
reverse("admin:account_change", args=[user.id]),
values
)
self.assertEqual(user.mailbox_set.first().alias_set.count(), 1)
self.assertEqual(
user.mailbox_set.first().alias_set.first(), "toto@test.com")
values = {
"email": "Titi@test.com", "recipient": "user@test.com",
"enabled": True
}
self.ajax_post(reverse("admin:alias_add"), values)
self.assertEqual(
user.mailbox_set.first().alias_set.first(), "titi@test.com")
def test_dlist(self):
values = dict(email="all@test.com",
recipients="user@test.com",
recipients_1="admin@test.com",
recipients_2="ext@titi.com",
enabled=True)
self.ajax_post(
reverse("admin:dlist_add"), values
)
user = User.objects.get(username="user@test.com")
self.assertEqual(user.mailbox_set.all()[0].alias_set.count(), 2)
admin = User.objects.get(username="admin@test.com")
self.assertEqual(admin.mailbox_set.all()[0].alias_set.count(), 1)
dlist = Alias.objects.get(address="all", domain__name="test.com")
self.assertEqual(len(dlist.get_recipients()), 3)
del values["recipients_1"]
self.ajax_post(
reverse("admin:alias_change", args=[dlist.id]),
values
)
self.assertEqual(dlist.get_recipients_count(), 2)
self.ajax_post(
reverse("admin:alias_delete") + "?selection=%d" % dlist.id, {}
)
self.assertRaises(Alias.DoesNotExist, Alias.objects.get,
address="all", domain__name="test.com")
def test_forward(self):
values = dict(email="forward2@test.com", recipients="rcpt@dest.com")
self.ajax_post(
reverse("admin:forward_add"), values
)
fwd = Alias.objects.get(address="forward2", domain__name="test.com")
self.assertEqual(fwd.get_recipients_count(), 1)
values["recipients"] = "rcpt2@dest.com"
self.ajax_post(
reverse("admin:alias_change",
args=[fwd.id]),
values
)
self.assertEqual(fwd.get_recipients_count(), 1)
self.ajax_post(
reverse("admin:alias_delete") + "?selection=%d"
% fwd.id, {}
)
self.assertRaises(Alias.DoesNotExist, Alias.objects.get,
address="forward2", domain__name="test.com")
def test_forward_and_local_copies(self):
values = dict(email="user@test.com", recipients="rcpt@dest.com")
self.ajax_post(
reverse("admin:forward_add"), values
)
fwd = Alias.objects.get(address="user", domain__name="test.com")
self.assertEqual(fwd.get_recipients_count(), 1)
values["recipients"] = "rcpt@dest.com"
values["recipients_1"] = "user@test.com"
self.ajax_post(
reverse("admin:alias_change", args=[fwd.id]),
values
)
fwd = Alias.objects.get(pk=fwd.pk)
self.assertEqual(fwd.get_recipients_count(), 2)
self.assertEqual(fwd.aliases.count(), 0)
-112
View File
@@ -1,112 +0,0 @@
from django.core.urlresolvers import reverse
from modoboa.core.models import User
from modoboa.lib import parameters
from modoboa.lib.tests import ModoTestCase
from modoboa.extensions.admin.models import (
Domain, Alias
)
from modoboa.extensions.admin import factories
class DomainTestCase(ModoTestCase):
fixtures = ["initial_users.json"]
def setUp(self):
super(DomainTestCase, self).setUp()
factories.populate_database()
def test_create(self):
"""Test the creation of a domain
"""
values = {
"name": "pouet.com", "quota": 100, "create_dom_admin": "no",
"stepid": 'step2'
}
self.ajax_post(reverse("admin:domain_add"), values)
dom = Domain.objects.get(name="pouet.com")
self.assertEqual(dom.name, "pouet.com")
self.assertEqual(dom.quota, 100)
self.assertEqual(dom.enabled, False)
self.assertFalse(dom.admins)
def test_create_with_template(self):
"""Test the creation of a domain with a template
"""
values = {
"name": "pouet.com", "quota": 100, "create_dom_admin": "yes",
"dom_admin_username": "toto", "create_aliases": "yes",
"stepid": 'step2'
}
self.ajax_post(
reverse("admin:domain_add"),
values
)
dom = Domain.objects.get(name="pouet.com")
da = User.objects.get(username="toto@pouet.com")
self.assertIn(da, dom.admins)
al = Alias.objects.get(address="postmaster", domain__name="pouet.com")
self.assertIn(da.mailbox_set.all()[0], al.mboxes.all())
self.assertTrue(da.can_access(al))
def test_create_with_template_and_empty_quota(self):
"""Test the creation of a domain with a template and no quota"""
values = {
"name": "pouet.com", "quota": 0, "create_dom_admin": "yes",
"dom_admin_username": "toto", "create_aliases": "yes",
"stepid": 'step2'
}
self.ajax_post(
reverse("admin:domain_add"),
values
)
dom = Domain.objects.get(name="pouet.com")
da = User.objects.get(username="toto@pouet.com")
self.assertIn(da, dom.admins)
al = Alias.objects.get(address="postmaster", domain__name="pouet.com")
self.assertIn(da.mailbox_set.all()[0], al.mboxes.all())
self.assertTrue(da.can_access(al))
def test_create_using_default_quota(self):
parameters.save_admin('DEFAULT_DOMAIN_QUOTA', 50, app='admin')
values = {
"name": "pouet.com", "create_dom_admin": "yes",
"dom_admin_username": "toto", "create_aliases": "yes",
"stepid": 'step2'
}
self.ajax_post(
reverse("admin:domain_add"),
values
)
dom = Domain.objects.get(name="pouet.com")
self.assertEqual(dom.quota, 50)
da = User.objects.get(username="toto@pouet.com")
self.assertEqual(da.mailbox_set.all()[0].quota, 50)
def test_modify(self):
"""Test the modification of a domain
Rename 'test.com' domain to 'pouet.com'
"""
values = {
"name": "pouet.com", "quota": 100, "enabled": True
}
dom = Domain.objects.get(name="test.com")
self.ajax_post(
reverse("admin:domain_change", args=[dom.id]),
values
)
dom = Domain.objects.get(name="pouet.com")
self.assertEqual(dom.name, "pouet.com")
self.assertTrue(dom.enabled)
def test_delete(self):
"""Test the removal of a domain
"""
dom = Domain.objects.get(name="test.com")
self.ajax_post(
reverse("admin:domain_delete", args=[dom.id]),
{}
)
with self.assertRaises(Domain.DoesNotExist):
Domain.objects.get(pk=1)
@@ -1,47 +0,0 @@
from django.core.urlresolvers import reverse
from modoboa.lib.tests import ModoTestCase
from modoboa.extensions.admin.models import (
Domain, DomainAlias
)
from modoboa.extensions.admin import factories
class DomainAliasTestCase(ModoTestCase):
fixtures = ['initial_users.json']
def setUp(self):
super(DomainAliasTestCase, self).setUp()
factories.populate_database()
self.dom = Domain.objects.get(name='test.com')
def test_model(self):
dom = Domain.objects.get(name="test.com")
domal = DomainAlias()
domal.name = "domalias.net"
domal.target = dom
domal.save()
self.assertEqual(dom.domainalias_count, 1)
domal.name = "domalias.org"
domal.save()
domal.delete()
def test_form(self):
dom = Domain.objects.get(name="test.com")
values = dict(name=dom.name, quota=dom.quota, enabled=dom.enabled,
aliases="domalias.net", aliases_1="domalias.com")
self.ajax_post(
reverse("admin:domain_change",
args=[dom.id]),
values
)
self.assertEqual(dom.domainalias_set.count(), 2)
del values["aliases_1"]
self.ajax_post(
reverse("admin:domain_change",
args=[dom.id]),
values
)
self.assertEqual(dom.domainalias_set.count(), 1)
-84
View File
@@ -1,84 +0,0 @@
from django.core.urlresolvers import reverse
from modoboa.extensions.admin import factories
from modoboa.lib.tests import ModoTestCase
class ExportTestCase(ModoTestCase):
fixtures = ["initial_users.json"]
def setUp(self):
super(ExportTestCase, self).setUp()
factories.populate_database()
def __export_identities(self, idtfilter="", grpfilter=""):
self.clt.get(
reverse("admin:_identity_list")
+ "?grpfilter=%s&idtfilter=%s" % (grpfilter, idtfilter)
)
return self.clt.post(
reverse("admin:identity_export"),
{"filename": "test.csv"}
)
def assertListEqual(self, list1, list2):
list2 = list2.split('\r\n')
for entry in list1.split('\r\n'):
if not entry:
continue
self.assertIn(entry, list2)
def test_export_identities(self):
response = self.__export_identities()
self.assertListEqual(
"account;admin;{CRYPT}dTTsGDkA5ZHKg;;;True;SuperAdmins;;\r\naccount;admin@test.com;{PLAIN}toto;;;True;DomainAdmins;admin@test.com;10;test.com\r\naccount;admin@test2.com;{PLAIN}toto;;;True;DomainAdmins;admin@test2.com;10;test2.com\r\naccount;user@test.com;{PLAIN}toto;;;True;SimpleUsers;user@test.com;10\r\naccount;user@test2.com;{PLAIN}toto;;;True;SimpleUsers;user@test2.com;10\r\nalias;alias@test.com;True;user@test.com\r\nforward;forward@test.com;True;user@external.com\r\ndlist;postmaster@test.com;True;toto@titi.com;test@truc.fr\r\n",
response.content.strip()
)
def test_export_simpleusers(self):
response = self.__export_identities(
idtfilter="account", grpfilter="SimpleUsers"
)
self.assertListEqual(
"account;user@test.com;{PLAIN}toto;;;True;SimpleUsers;user@test.com;10\r\naccount;user@test2.com;{PLAIN}toto;;;True;SimpleUsers;user@test2.com;10",
response.content.strip()
)
def test_export_superadmins(self):
response = self.__export_identities(
idtfilter="account", grpfilter="SuperAdmins"
)
self.assertEqual(
response.content.strip(),
"account;admin;{CRYPT}dTTsGDkA5ZHKg;;;True;SuperAdmins;;"
)
def test_export_domainadmins(self):
response = self.__export_identities(
idtfilter="account", grpfilter="DomainAdmins"
)
self.assertListEqual(
"account;admin@test.com;{PLAIN}toto;;;True;DomainAdmins;admin@test.com;10;test.com\r\naccount;admin@test2.com;{PLAIN}toto;;;True;DomainAdmins;admin@test2.com;10;test2.com",
response.content.strip()
)
def test_export_aliases(self):
response = self.__export_identities(idtfilter="alias")
self.assertEqual(
response.content.strip(),
"alias;alias@test.com;True;user@test.com"
)
def test_export_forwards(self):
response = self.__export_identities(idtfilter="forward")
self.assertEqual(
response.content.strip(),
"forward;forward@test.com;True;user@external.com"
)
def test_export_dlists(self):
response = self.__export_identities(idtfilter="dlist")
self.assertEqual(
response.content.strip(),
"dlist;postmaster@test.com;True;toto@titi.com;test@truc.fr"
)
-191
View File
@@ -1,191 +0,0 @@
# coding: utf-8
from django.core.files.base import ContentFile
from django.core.urlresolvers import reverse
from modoboa.core.models import User
from modoboa.extensions.admin import factories
from modoboa.extensions.admin.models import (
Domain, Alias, DomainAlias
)
from modoboa.lib.tests import ModoTestCase
class ImportTestCase(ModoTestCase):
fixtures = ["initial_users.json"]
def setUp(self):
super(ImportTestCase, self).setUp()
factories.populate_database()
def test_domains_import(self):
f = ContentFile(b"""domain; domain1.com; 100; True
domain; domain2.com; 200; False
domainalias; domalias1.com; domain1.com; True
""", name="domains.csv")
self.clt.post(
reverse("admin:domain_import"), {
"sourcefile": f
}
)
admin = User.objects.get(username="admin")
dom = Domain.objects.get(name="domain1.com")
self.assertEqual(dom.quota, 100)
self.assertTrue(dom.enabled)
self.assertTrue(admin.is_owner(dom))
domalias = DomainAlias.objects.get(name="domalias1.com")
self.assertEqual(domalias.target, dom)
self.assertTrue(dom.enabled)
self.assertTrue(admin.is_owner(domalias))
dom = Domain.objects.get(name="domain2.com")
self.assertEqual(dom.quota, 200)
self.assertFalse(dom.enabled)
self.assertTrue(admin.is_owner(dom))
def test_import_domains_with_conflict(self):
f = ContentFile(b"""domain;test.alias;10;True
domainalias;test.alias;test.com;True
""", name="domains.csv")
resp = self.clt.post(
reverse("admin:domain_import"), {
"sourcefile": f
}
)
self.assertIn('Object already exists: domainalias', resp.content)
def test_identities_import(self):
f = ContentFile(b"""
account; user1@test.com; toto; User; One; True; SimpleUsers; user1@test.com; 0
account; truc@test.com; toto; René; Truc; True; DomainAdmins; truc@test.com; 5; test.com
alias; alias1@test.com; True; user1@test.com
forward; fwd1@test.com; True; user@extdomain.com
dlist; dlist@test.com; True; user1@test.com; user@extdomain.com
""", name="identities.csv")
self.clt.post(
reverse("admin:identity_import"),
{"sourcefile": f, "crypt_password": True}
)
admin = User.objects.get(username="admin")
u1 = User.objects.get(username="user1@test.com")
mb1 = u1.mailbox_set.all()[0]
self.assertTrue(admin.is_owner(u1))
self.assertEqual(u1.email, "user1@test.com")
self.assertEqual(u1.first_name, "User")
self.assertEqual(u1.last_name, "One")
self.assertTrue(u1.is_active)
self.assertEqual(u1.group, "SimpleUsers")
self.assertTrue(mb1.use_domain_quota)
self.assertEqual(mb1.quota, 0)
self.assertTrue(admin.is_owner(mb1))
self.assertEqual(mb1.full_address, "user1@test.com")
self.assertTrue(
self.clt.login(username="user1@test.com", password="toto")
)
da = User.objects.get(username="truc@test.com")
damb = da.mailbox_set.all()[0]
self.assertEqual(da.first_name, u"René")
self.assertEqual(da.group, "DomainAdmins")
self.assertEqual(damb.quota, 5)
self.assertFalse(damb.use_domain_quota)
self.assertEqual(damb.full_address, "truc@test.com")
dom = Domain.objects.get(name="test.com")
self.assertIn(da, dom.admins)
u = User.objects.get(username="user@test.com")
self.assertTrue(da.can_access(u))
al = Alias.objects.get(address="alias1", domain__name="test.com")
self.assertIn(u1.mailbox_set.all()[0], al.mboxes.all())
self.assertTrue(admin.is_owner(al))
fwd = Alias.objects.get(address="fwd1", domain__name="test.com")
self.assertIn("user@extdomain.com", fwd.extmboxes)
self.assertTrue(admin.is_owner(fwd))
dlist = Alias.objects.get(address="dlist", domain__name="test.com")
self.assertIn(u1.mailbox_set.all()[0], dlist.mboxes.all())
self.assertIn("user@extdomain.com", dlist.extmboxes)
self.assertTrue(admin.is_owner(dlist))
def test_import_invalid_quota(self):
f = ContentFile(b"""
account; user1@test.com; toto; User; One; True; SimpleUsers; user1@test.com; ; test.com
""", name="identities.csv")
resp = self.clt.post(
reverse("admin:identity_import"),
{"sourcefile": f, "crypt_password": True}
)
self.assertIn('wrong quota value', resp.content)
def test_import_quota_too_big(self):
self.clt.logout()
self.clt.login(username="admin@test.com", password="toto")
f = ContentFile(b"""
account; user1@test.com; toto; User; One; True; SimpleUsers; user1@test.com; 20
""", name="identities.csv")
resp = self.clt.post(
reverse("admin:identity_import"),
{"sourcefile": f, "crypt_password": True}
)
self.assertIn('Quota is greater than the allowed', resp.content)
def test_import_missing_quota(self):
f = ContentFile(b"""
account; user1@test.com; toto; User; One; True; SimpleUsers; user1@test.com
""", name="identities.csv")
resp = self.clt.post(
reverse("admin:identity_import"),
{"sourcefile": f, "crypt_password": True}
)
account = User.objects.get(username="user1@test.com")
self.assertEqual(
account.mailbox_set.all()[0].quota,
account.mailbox_set.all()[0].domain.quota
)
def test_import_duplicate(self):
f = ContentFile(b"""
account; admin@test.com; toto; Admin; ; True; DomainAdmins; admin@test.com; 0; test.com
account; truc@test.com; toto; René; Truc; True; DomainAdmins; truc@test.com; 0; test.com
""", name="identities.csv")
self.clt.post(
reverse("admin:identity_import"),
{"sourcefile": f, "crypt_password": True,
"continue_if_exists": True}
)
admin = User.objects.get(username="admin")
u1 = User.objects.get(username="truc@test.com")
self.assertTrue(admin.is_owner(u1))
def test_import_superadmin(self):
"""Check if a domain admin can import a superadmin
Expected result: no
"""
self.clt.logout()
self.assertTrue(
self.clt.login(username="admin@test.com", password="toto")
)
f = ContentFile(b"""
account; sa@test.com; toto; Super; Admin; True; SuperAdmins; superadmin@test.com; 50
""", name="identities.csv")
self.clt.post(
reverse("admin:identity_import"),
{"sourcefile": f, "crypt_password": True,
"continue_if_exists": True}
)
with self.assertRaises(User.DoesNotExist):
User.objects.get(username="sa@test.com")
def test_import_alias_with_empty_values(self):
f = ContentFile(b"""
alias;user.alias@test.com;True;user@test.com;;;;;;;;;;;;;;;;
""", name="identities.csv")
self.clt.post(
reverse("admin:identity_import"),
{"sourcefile": f, "crypt_password": True,
"continue_if_exists": True}
)
alias = Alias.objects.get(
domain__name="test.com", address="user.alias"
)
self.assertEqual(alias.type, "alias")
@@ -1,55 +0,0 @@
from django.core.urlresolvers import reverse
from modoboa.core import load_core_settings
from modoboa.core.models import User
from modoboa.lib.tests import ModoTestCase
from modoboa.lib import parameters
from modoboa.extensions.admin import factories
class PasswordSchemesTestCase(ModoTestCase):
fixtures = ['initial_users.json']
def setUp(self):
super(PasswordSchemesTestCase, self).setUp()
factories.populate_database()
load_core_settings()
def _create_account(self):
values = dict(
username="tester@test.com", first_name="Tester", last_name="Toto",
password1="toto", password2="toto", role="SimpleUsers",
quota_act=True,
is_active=True, email="tester@test.com", stepid='step2'
)
self.ajax_post(
reverse("admin:account_add"),
values
)
def _test_scheme(self, name, startpattern):
parameters.save_admin('PASSWORD_SCHEME', name, app='core')
self._create_account()
account = User.objects.get(username='tester@test.com')
self.assertTrue(account.password.startswith(startpattern))
self.assertTrue(account.check_password('toto'))
def test_sha512crypt_scheme(self):
self._test_scheme('sha512crypt', '{SHA512-CRYPT}')
def test_sha256crypt_scheme(self):
self._test_scheme('sha256crypt', '{SHA256-CRYPT}')
def test_md5crypt_scheme(self):
self._test_scheme('md5crypt', '{MD5-CRYPT}')
def test_sha256_scheme(self):
self._test_scheme('sha256', '{SHA256}')
def test_md5_scheme(self):
self._test_scheme('md5', '{MD5}')
def test_crypt(self):
self._test_scheme('crypt', '{CRYPT}')
def test_plain(self):
self._test_scheme('plain', '{PLAIN}')
-26
View File
@@ -1,26 +0,0 @@
from django.core.urlresolvers import reverse
from modoboa.core.models import User
from modoboa.lib.tests import ModoTestCase
from modoboa.extensions.admin.factories import populate_database
from modoboa.extensions.admin.models import Alias
class ForwardTestCase(ModoTestCase):
fixtures = ['initial_users.json']
def setUp(self):
super(ForwardTestCase, self).setUp()
populate_database()
def test_forward_permissions(self):
self.clt.logout()
self.clt.login(username='user@test.com', password='toto')
self.ajax_post(
reverse('user_forward'),
{'dest': 'user@extdomain.com', 'keepcopies': True}
)
forward = Alias.objects.get(address='user', domain__name='test.com')
sadmin = User.objects.get(username='admin')
self.assertTrue(sadmin.can_access(forward))
domadmin = User.objects.get(username='admin@test.com')
self.assertTrue(domadmin.can_access(forward))
-60
View File
@@ -1,60 +0,0 @@
from django.conf.urls import patterns, url
urlpatterns = patterns(
'modoboa.extensions.admin.views.domain',
url(r'^$', 'index', name="index"),
url(r'^domains/$', 'domains', name="domain_list"),
url(r'^domains/list/$', '_domains', name="_domain_list"),
url(r'^domains/flatlist/$', 'domains_list', name="domain_flat_list"),
url(r'^domains/stats/$', 'domain_statistics', name="domain_statistics"),
url(r'^domains/new/', 'newdomain', name='domain_add'),
url(r'^domains/(?P<dom_id>\d+)/edit/$', 'editdomain', name="domain_change"),
url(r'^domains/(?P<dom_id>\d+)/delete/$', 'deldomain',
name="domain_delete"),
)
urlpatterns += patterns(
'modoboa.extensions.admin.views.identity',
url(r'^permissions/remove/$', 'remove_permission',
name="permission_remove"),
url(r'^identities/$', 'identities', name="identity_list"),
url(r'^identities/list/$', '_identities', name="_identity_list"),
url(r'^identities/quotas/$', 'list_quotas', name="quota_list"),
url(r'^identities/page/$', 'get_next_page', name="identity_page"),
url(r'^accounts/list/$', 'accounts_list', name="account_list"),
url(r'^accounts/new/$', 'newaccount', name="account_add"),
url(r'^accounts/edit/(?P<accountid>\d+)/$', 'editaccount',
name="account_change"),
url(r'^accounts/delete/(?P<accountid>\d+)/$', 'delaccount',
name="account_delete"),
)
urlpatterns += patterns(
'modoboa.extensions.admin.views.alias',
url(r'^distriblists/new/$', 'newdlist', name="dlist_add"),
url(r'^forwards/new/$', 'newforward', name="forward_add"),
url(r'^aliases/new/$', 'newalias', name="alias_add"),
url(r'^aliases/edit/(?P<alid>\d+)/$', 'editalias', name="alias_change"),
url(r'^aliases/delete/$', 'delalias', name="alias_delete"),
)
urlpatterns += patterns(
'modoboa.extensions.admin.views.import',
url(r'^domains/import/$', 'import_domains', name="domain_import"),
url(r'^identities/import/$', 'import_identities', name="identity_import"),
)
urlpatterns += patterns(
'modoboa.extensions.admin.views.export',
url(r'^domains/export/$', 'export_domains', name="domain_export"),
url(r'^identities/export/$', 'export_identities', name="identity_export"),
)
-135
View File
@@ -1,135 +0,0 @@
import reversion
from django.db import IntegrityError
from django.utils.translation import ugettext as _, ungettext
from django.shortcuts import render
from django.core.urlresolvers import reverse
from django.contrib.auth.decorators import (
login_required, permission_required
)
from modoboa.lib import events
from modoboa.lib.web_utils import render_to_json_response
from modoboa.lib.exceptions import PermDeniedException, Conflict
from modoboa.extensions.admin.forms import AliasForm
from modoboa.extensions.admin.models import Alias
def _validate_alias(request, form, successmsg, callback=None):
"""Alias validation
Common function shared between creation and modification actions.
"""
if form.is_valid():
form.set_recipients()
try:
alias = form.save()
except IntegrityError:
raise Conflict(_("Alias with this name already exists"))
if callback:
callback(request.user, alias)
return render_to_json_response(successmsg)
return render_to_json_response({'form_errors': form.errors}, status=400)
def _new_alias(request, title, action, successmsg,
tplname="admin/aliasform.html"):
events.raiseEvent("CanCreate", request.user, "mailbox_aliases")
if request.method == "POST":
def callback(user, alias):
alias.post_create(user)
form = AliasForm(request.user, request.POST)
return _validate_alias(
request, form, successmsg, callback
)
ctx = {
"title": title,
"action": action,
"formid": "aliasform",
"action_label": _("Create"),
"action_classes": "submit",
"form": AliasForm(request.user)
}
return render(request, tplname, ctx)
@login_required
@permission_required("admin.add_alias")
@reversion.create_revision()
def newdlist(request):
return _new_alias(
request, _("New distribution list"), reverse("admin:dlist_add"),
_("Distribution list created")
)
@login_required
@permission_required("admin.add_alias")
@reversion.create_revision()
def newalias(request):
return _new_alias(
request, _("New alias"), reverse("admin:alias_add"),
_("Alias created")
)
@login_required
@permission_required("admin.add_alias")
@reversion.create_revision()
def newforward(request):
return _new_alias(
request, _("New forward"), reverse("admin:forward_add"),
_("Forward created")
)
@login_required
@permission_required("admin.change_alias")
@reversion.create_revision()
def editalias(request, alid, tplname="admin/aliasform.html"):
alias = Alias.objects.get(pk=alid)
if not request.user.can_access(alias):
raise PermDeniedException
if request.method == "POST":
if len(alias.get_recipients()) >= 2:
successmsg = _("Distribution list modified")
elif alias.extmboxes != "":
successmsg = _("Forward modified")
else:
successmsg = _("Alias modified")
form = AliasForm(request.user, request.POST, instance=alias)
return _validate_alias(request, form, successmsg)
ctx = {
'action': reverse("admin:alias_change", args=[alias.id]),
'formid': 'aliasform',
'title': alias.full_address,
'action_label': _('Update'),
'action_classes': 'submit',
'form': AliasForm(request.user, instance=alias)
}
return render(request, tplname, ctx)
@login_required
@permission_required("admin.delete_alias")
def delalias(request):
selection = request.GET["selection"].split(",")
for alid in selection:
alias = Alias.objects.get(pk=alid)
if not request.user.can_access(alias):
raise PermDeniedException
if alias.type == 'dlist':
msg = "Distribution list deleted"
msgs = "Distribution lists deleted"
elif alias.type == 'forward':
msg = "Forward deleted"
msgs = "Forwards deleted"
else:
msg = "Alias deleted"
msgs = "Aliases deleted"
alias.delete()
msg = ungettext(msg, msgs, len(selection))
return render_to_json_response(msg)
-154
View File
@@ -1,154 +0,0 @@
"""
Domain related views.
"""
from django.http import HttpResponseRedirect
from django.utils.translation import ugettext as _, ungettext
from django.core.urlresolvers import reverse
from django.shortcuts import render
from django.views.decorators.csrf import ensure_csrf_cookie
from django.contrib.auth.decorators import (
login_required, permission_required, user_passes_test
)
import reversion
from modoboa.core.models import User
from modoboa.lib import parameters, events
from modoboa.lib.web_utils import (
_render_to_string, render_to_json_response
)
from modoboa.lib.exceptions import (
PermDeniedException
)
from modoboa.lib.listing import get_sort_order, get_listing_page
from modoboa.extensions.admin.models import (
Domain, DomainAlias, Mailbox, Alias
)
from modoboa.extensions.admin.forms import (
DomainForm
)
from modoboa.extensions.admin.lib import get_domains
@login_required
def index(request):
return HttpResponseRedirect(reverse("admin:domain_list"))
@login_required
@user_passes_test(
lambda u: u.has_perm("admin.view_domains")
or u.has_perm("admin.view_mailboxes")
)
def _domains(request):
sort_order, sort_dir = get_sort_order(request.GET, "name")
filters = dict(
(flt, request.GET.get(flt, None))
for flt in ['domfilter', 'searchquery']
+ events.raiseQueryEvent('ExtraDomainFilters')
)
request.session['domains_filters'] = filters
domainlist = get_domains(request.user, **filters)
if sort_order == 'name':
domainlist = sorted(
domainlist,
key=lambda d: getattr(d, sort_order), reverse=sort_dir == '-'
)
else:
domainlist = sorted(domainlist, key=lambda d: d.tags[0],
reverse=sort_dir == '-')
context = {
"handle_mailboxes": parameters.get_admin(
"HANDLE_MAILBOXES", raise_error=False),
"auto_account_removal": parameters.get_admin("AUTO_ACCOUNT_REMOVAL")
}
page = get_listing_page(domainlist, request.GET.get("page", 1))
if page is None:
context["length"] = 0
else:
context["rows"] = _render_to_string(
request, 'admin/domains_table.html', {
'domains': page.object_list,
}
)
context["pages"] = [page.number]
return render_to_json_response(context)
@login_required
@ensure_csrf_cookie
def domains(request, tplname="admin/domains.html"):
if not request.user.has_perm("admin.view_domains"):
if request.user.has_perm("admin.view_mailboxes"):
return HttpResponseRedirect(
reverse("admin:identity_list")
)
return HttpResponseRedirect(reverse("core:user_index"))
return render(request, tplname, {"selection": "domains"})
@login_required
@permission_required("core.add_user")
def domains_list(request):
doms = [dom.name for dom in Domain.objects.get_for_admin(request.user)]
return render_to_json_response(doms)
@login_required
@permission_required("admin.add_domain")
@reversion.create_revision()
def newdomain(request):
from modoboa.extensions.admin.forms import DomainWizard
events.raiseEvent("CanCreate", request.user, "domains")
return DomainWizard(request).process()
@login_required
@permission_required("admin.view_domains")
@reversion.create_revision()
def editdomain(request, dom_id):
"""Edit domain view."""
domain = Domain.objects.get(pk=dom_id)
if not request.user.can_access(domain):
raise PermDeniedException
instances = dict(general=domain)
events.raiseEvent("FillDomainInstances", request.user, domain, instances)
return DomainForm(request, instances=instances).process()
@login_required
@permission_required("admin.delete_domain")
def deldomain(request, dom_id):
keepdir = True if request.POST.get("keepdir", "false") == "true" else False
try:
mb = Mailbox.objects.get(user__id=request.user.id)
except Mailbox.DoesNotExist:
mb = None
dom = Domain.objects.get(pk=dom_id)
if not request.user.can_access(dom):
raise PermDeniedException
if mb and mb.domain == dom:
raise PermDeniedException(_("You can't delete your own domain"))
dom.delete(request.user, keepdir)
msg = ungettext("Domain deleted", "Domains deleted", 1)
return render_to_json_response(msg)
@login_required
@permission_required("admin.view_domains")
def domain_statistics(request):
"""A simple page to present domain statistics."""
context = {}
if request.user.group == "SuperAdmins":
context.update({
"domains_counter": Domain.objects.count(),
"domain_aliases_counter": DomainAlias.objects.count(),
"identities_counter": User.objects.count() + Alias.objects.count(),
})
context.update({"domains": Domain.objects.get_for_admin(request.user)})
return render(request, "admin/domain_statistics.html", context)
-90
View File
@@ -1,90 +0,0 @@
"""Export related views."""
import cStringIO
import csv
from rfc6266 import build_header
from django.contrib.auth.decorators import (
login_required, permission_required, user_passes_test
)
from django.core.urlresolvers import reverse
from django.http import HttpResponse
from django.shortcuts import render
from django.utils.translation import ugettext as _
from modoboa.extensions.admin.forms import (
ExportIdentitiesForm, ExportDomainsForm
)
from modoboa.extensions.admin.lib import get_domains, get_identities
def _export(content, filename):
"""Export a csv file's content
:param content: the content to export (string)
:param filename: the name that will appear into the response
:return: an ``HttpResponse`` object
"""
resp = HttpResponse(content)
resp["Content-Type"] = "text/csv"
resp["Content-Length"] = len(content)
resp["Content-Disposition"] = build_header(filename)
return resp
@login_required
@user_passes_test(
lambda u: u.has_perm("core.add_user") or u.has_perm("admin.add_alias")
)
def export_identities(request):
ctx = {
"title": _("Export identities"),
"action_label": _("Export"),
"action_classes": "submit",
"formid": "exportform",
"action": reverse("admin:identity_export"),
}
if request.method == "POST":
form = ExportIdentitiesForm(request.POST)
form.is_valid()
fp = cStringIO.StringIO()
csvwriter = csv.writer(fp, delimiter=form.cleaned_data["sepchar"])
identities = get_identities(
request.user, **request.session['identities_filters'])
for ident in identities:
ident.to_csv(csvwriter)
content = fp.getvalue()
fp.close()
return _export(content, form.cleaned_data["filename"])
ctx["form"] = ExportIdentitiesForm()
return render(request, "common/generic_modal_form.html", ctx)
@login_required
@permission_required("admin.add_domain")
def export_domains(request):
ctx = {
"title": _("Export domains"),
"action_label": _("Export"),
"action_classes": "submit",
"formid": "exportform",
"action": reverse("admin:domain_export"),
}
if request.method == "POST":
form = ExportDomainsForm(request.POST)
form.is_valid()
fp = cStringIO.StringIO()
csvwriter = csv.writer(fp, delimiter=form.cleaned_data["sepchar"])
for dom in get_domains(request.user,
**request.session['domains_filters']):
dom.to_csv(csvwriter)
content = fp.getvalue()
fp.close()
return _export(content, form.cleaned_data["filename"])
ctx["form"] = ExportDomainsForm()
return render(request, "common/generic_modal_form.html", ctx)
-211
View File
@@ -1,211 +0,0 @@
"""Identity related views."""
from django.contrib.auth.decorators import (
login_required, permission_required, user_passes_test
)
from django.shortcuts import render
from django.utils.translation import ugettext as _, ungettext
from django.views.decorators.csrf import ensure_csrf_cookie
import reversion
from modoboa.core.models import User
from modoboa.extensions.admin.forms import (
AccountForm
)
from modoboa.extensions.admin.lib import get_identities
from modoboa.extensions.admin.models import Mailbox, Domain
from modoboa.lib import parameters, events
from modoboa.lib.exceptions import (
PermDeniedException, BadRequest
)
from modoboa.lib.listing import (
get_sort_order, get_listing_page
)
from modoboa.lib.web_utils import (
_render_to_string, render_to_json_response
)
@login_required
@user_passes_test(
lambda u: u.has_perm("core.add_user") or u.has_perm("admin.add_alias")
)
def _identities(request):
filters = dict((fname, request.GET.get(fname, None))
for fname in ['searchquery', 'idtfilter', 'grpfilter'])
request.session['identities_filters'] = filters
idents_list = get_identities(request.user, **filters)
sort_order, sort_dir = get_sort_order(request.GET, "identity",
["identity", "name_or_rcpt", "tags"])
if sort_order in ["identity", "name_or_rcpt"]:
objects = sorted(idents_list, key=lambda o: getattr(o, sort_order),
reverse=sort_dir == '-')
else:
objects = sorted(idents_list, key=lambda o: o.tags[0],
reverse=sort_dir == '-')
context = {
"handle_mailboxes": parameters.get_admin(
"HANDLE_MAILBOXES", raise_error=False)
}
page = get_listing_page(objects, request.GET.get("page", 1))
if page is None:
context["length"] = 0
else:
context["headers"] = _render_to_string(
request, "admin/identity_headers.html", {})
context["rows"] = _render_to_string(
request, "admin/identities_table.html", {
"identities": page.object_list
}
)
context["pages"] = [page.number]
return render_to_json_response(context)
@login_required
@permission_required("admin.add_mailbox")
def list_quotas(request):
from modoboa.lib.db_utils import db_type
sort_order, sort_dir = get_sort_order(request.GET, "address")
mboxes = Mailbox.objects.get_for_admin(
request.user, request.GET.get("searchquery", None)
)
mboxes = mboxes.exclude(quota=0)
if sort_order in ["address", "quota"]:
mboxes = mboxes.order_by("%s%s" % (sort_dir, sort_order))
elif sort_order == "quota_value__bytes":
where = "admin_mailbox.address||'@'||admin_domain.name"
mboxes = mboxes.extra(
select={"quota_value__bytes": "admin_quota.bytes"},
where=["admin_quota.username=%s" % where],
tables=["admin_quota", "admin_domain"],
order_by=["%s%s" % (sort_dir, sort_order)]
)
elif sort_order == "quota_usage":
where = "admin_mailbox.address||'@'||admin_domain.name"
db_type = db_type()
if db_type == "postgres":
select = (
"(admin_quota.bytes::float / (CAST(admin_mailbox.quota "
"AS BIGINT) * 1048576)) * 100"
)
else:
select = (
"admin_quota.bytes / (admin_mailbox.quota "
"* 1048576) * 100"
)
if db_type == "mysql":
where = "CONCAT(admin_mailbox.address,'@',admin_domain.name)"
mboxes = mboxes.extra(
select={'quota_usage': select},
where=["admin_quota.username=%s" % where],
tables=["admin_quota", "admin_domain"],
order_by=["%s%s" % (sort_dir, sort_order)]
)
else:
raise BadRequest(_("Invalid request"))
page = get_listing_page(mboxes, request.GET.get("page", 1))
context = {
"headers": _render_to_string(
request, "admin/quota_headers.html", {}
)
}
if page is None:
context["length"] = 0
else:
context["rows"] = _render_to_string(
request, "admin/quotas.html", {
"mboxes": page
}
)
context["pages"] = [page.number]
return render_to_json_response(context)
@login_required
@user_passes_test(
lambda u: u.has_perm("admin.add_user") or u.has_perm("admin.add_alias")
or u.has_perm("admin.add_mailbox")
)
def get_next_page(request):
"""Return the next page of the identity list."""
if request.GET.get("objtype", "identity") == "identity":
return _identities(request)
return list_quotas(request)
@login_required
@user_passes_test(
lambda u: u.has_perm("admin.add_user") or u.has_perm("admin.add_alias")
)
@ensure_csrf_cookie
def identities(request, tplname="admin/identities.html"):
return render(request, tplname, {
"selection": "identities",
"deflocation": "list/"
})
@login_required
@permission_required("core.add_user")
def accounts_list(request):
accs = User.objects.filter(is_superuser=False) \
.exclude(groups__name='SimpleUsers')
res = [a.username for a in accs.all()]
return render_to_json_response(res)
@login_required
@permission_required("core.add_user")
@reversion.create_revision()
def newaccount(request):
"""Create a new account."""
from modoboa.extensions.admin.forms import AccountWizard
return AccountWizard(request).process()
@login_required
@permission_required("core.change_user")
@reversion.create_revision()
def editaccount(request, accountid, tplname="common/tabforms.html"):
account = User.objects.get(pk=accountid)
if not request.user.can_access(account):
raise PermDeniedException
mb = None
if account.mailbox_set.count():
mb = account.mailbox_set.all()[0]
instances = dict(general=account, mail=mb, perms=account)
events.raiseEvent("FillAccountInstances", request.user, account, instances)
return AccountForm(request, instances=instances).process()
@login_required
@permission_required("core.delete_user")
def delaccount(request, accountid):
keepdir = True if request.POST.get("keepdir", "false") == "true" else False
User.objects.get(pk=accountid).delete(request.user, keep_mb_dir=keepdir)
return render_to_json_response(
ungettext("Account deleted", "Accounts deleted", 1)
)
@login_required
@permission_required("admin.add_domain")
def remove_permission(request):
domid = request.GET.get("domid", None)
daid = request.GET.get("daid", None)
if domid is None or daid is None:
raise BadRequest(_("Invalid request"))
try:
account = User.objects.get(pk=daid)
domain = Domain.objects.get(pk=domid)
except (User.DoesNotExist, Domain.DoesNotExist):
raise BadRequest(_("Invalid request"))
if not request.user.can_access(account) or \
not request.user.can_access(domain):
raise PermDeniedException
domain.remove_admin(account)
return render_to_json_response({})
-177
View File
@@ -1,177 +0,0 @@
"""Import related views."""
import csv
from django.contrib.auth.decorators import (
login_required, permission_required, user_passes_test
)
from django.core.urlresolvers import reverse
from django.shortcuts import render
from django.utils.translation import ugettext as _
import reversion
from modoboa.core.models import User
from modoboa.extensions.admin.forms import ImportIdentitiesForm, ImportDataForm
from modoboa.extensions.admin.models import (
Domain, DomainAlias, Alias
)
from modoboa.lib import events
from modoboa.lib.exceptions import ModoboaException, Conflict
def import_domain(user, row, formopts):
"""Specific code for domains import"""
dom = Domain()
dom.from_csv(user, row)
def import_domainalias(user, row, formopts):
"""Specific code for domain aliases import"""
domalias = DomainAlias()
domalias.from_csv(user, row)
@login_required
@permission_required("admin.add_domain")
def import_domains(request):
if request.method == "POST":
return importdata(request)
helptext = _("""Provide a CSV file where lines respect one of the following formats:
<ul>
<li><em>domain; name; quota; enabled</em></li>
<li><em>domainalias; name; targeted domain; enabled</em></li>
%s
</ul>
<p>The first element of each line is mandatory and must be equal to one of the previous values.</p>
<p>You can use a different character as separator.</p>
""" % ''.join([unicode(hlp) for hlp in
events.raiseQueryEvent('ExtraDomainImportHelp')]))
ctx = dict(
title=_("Import domains"),
action_label=_("Import"),
action_classes="submit",
action=reverse("admin:domain_import"),
formid="importform",
enctype="multipart/form-data",
target="import_target",
helptext=helptext,
form=ImportDataForm()
)
return render(request, "admin/importform.html", ctx)
def import_account(user, row, formopts):
"""Specific code for accounts import"""
account = User()
account.from_csv(user, row, formopts["crypt_password"])
def _import_alias(user, row, **kwargs):
"""Specific code for aliases import"""
alias = Alias()
alias.from_csv(user, row, **kwargs)
def import_alias(user, row, formopts):
_import_alias(user, row, expected_elements=4)
def import_forward(user, row, formopts):
_import_alias(user, row, expected_elements=4)
def import_dlist(user, row, formopts):
_import_alias(user, row)
@reversion.create_revision()
def importdata(request, formclass=ImportDataForm):
"""Generic import function
As the process of importing data from a CSV file is the same
whatever the type, we do a maximum of the work here.
:param request: a ``Request`` instance
:param typ: a string indicating the object type being imported
:return: a ``Response`` instance
"""
error = None
form = formclass(request.POST, request.FILES)
if form.is_valid():
try:
reader = csv.reader(request.FILES['sourcefile'],
delimiter=form.cleaned_data['sepchar'])
except csv.Error as inst:
error = str(inst)
if error is None:
try:
cpt = 0
for row in reader:
if not row:
continue
try:
fct = globals()["import_%s" % row[0].strip()]
except KeyError:
fct = events.raiseQueryEvent(
'ImportObject', row[0].strip()
)
if not fct:
continue
fct = fct[0]
try:
fct(request.user, row, form.cleaned_data)
except Conflict:
if form.cleaned_data["continue_if_exists"]:
continue
raise Conflict(
_("Object already exists: %s"
% form.cleaned_data['sepchar'].join(row[:2]))
)
cpt += 1
msg = _("%d objects imported successfully" % cpt)
return render(request, "admin/import_done.html", {
"status": "ok", "msg": msg
})
except (ModoboaException) as e:
error = str(e)
return render(request, "admin/import_done.html", {
"status": "ko", "msg": error
})
@login_required
@user_passes_test(
lambda u: u.has_perm("core.add_user") or u.has_perm("admin.add_alias")
)
def import_identities(request):
if request.method == "POST":
return importdata(request, ImportIdentitiesForm)
helptext = _("""Provide a CSV file where lines respect one of the following formats:
<ul>
<li><em>account; loginname; password; first name; last name; enabled; group; address; quota; [, domain, ...]</em></li>
<li><em>alias; address; enabled; internal recipient</em></li>
<li><em>forward; address; enabled; external recipient</em></li>
<li><em>dlist; address; enabled; recipient; recipient; ...</em></li>
</ul>
<p>The first element of each line is mandatory and must be equal to one of the previous values.</p>
<p>You can use a different character as separator.</p>
""")
ctx = dict(
title=_("Import identities"),
action_label=_("Import"),
action_classes="submit",
action=reverse("admin:identity_import"),
formid="importform",
enctype="multipart/form-data",
target="import_target",
form=ImportIdentitiesForm(),
helptext=helptext
)
return render(request, "admin/importform.html", ctx)
-57
View File
@@ -1,57 +0,0 @@
import reversion
from django.contrib.auth.decorators import login_required
from django.utils.translation import ugettext as _
from modoboa.lib.web_utils import (
render_to_json_response, _render_to_string
)
from modoboa.extensions.admin.lib import needs_mailbox
from modoboa.extensions.admin.models import Alias, Mailbox
from modoboa.extensions.admin.forms import ForwardForm
@login_required
@needs_mailbox()
@reversion.create_revision()
def forward(request, tplname='admin/forward.html'):
mb = request.user.mailbox_set.all()[0]
try:
al = Alias.objects.get(address=mb.address,
domain__name=mb.domain.name)
except Alias.DoesNotExist:
al = None
if request.method == "POST":
form = ForwardForm(request.POST)
error = None
if form.is_valid():
if al is None:
al = Alias()
al.address = mb.address
al.domain = mb.domain
al.enabled = mb.user.is_active
intdests = []
if form.cleaned_data["keepcopies"]:
intdests += [mb]
form.parse_dest()
al.save(
int_rcpts=intdests, ext_rcpts=form.dests, creator=request.user
)
return render_to_json_response(_("Forward updated"))
return render_to_json_response(
{'form_errors': form.errors}, status=400
)
form = ForwardForm()
if al is not None:
form.fields["dest"].initial = al.extmboxes
try:
al.mboxes.get(pk=mb.id)
except Mailbox.DoesNotExist:
pass
else:
form.fields["keepcopies"].initial = True
return render_to_json_response({
"content": _render_to_string(request, tplname, {
"form": form
})
})
+2 -1
View File
@@ -162,7 +162,8 @@ def topredirection(request):
else:
path = reverse("core:user_index")
else:
path = reverse("admin:domain_list")
# FIXME
path = reverse("modoboa_admin:domain_list")
return HttpResponseRedirect(path)