| Server IP : 85.214.239.14 / Your IP : 216.73.216.27 Web Server : Apache/2.4.65 (Debian) System : Linux h2886529.stratoserver.net 4.9.0 #1 SMP Mon Sep 30 15:36:27 MSK 2024 x86_64 User : www-data ( 33) PHP Version : 8.2.29 Disable Function : NONE MySQL : OFF | cURL : ON | WGET : ON | Perl : ON | Python : ON | Sudo : ON | Pkexec : OFF Directory : /proc/3/root/srv/modoboa/env/lib/python3.5/site-packages/modoboa/admin/models/ |
Upload File : |
"""Models related to mailboxes management."""
import os
import pwd
from reversion import revisions as reversion
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.utils.encoding import python_2_unicode_compatible, smart_text
from django.utils.translation import ugettext as _, ugettext_lazy
from modoboa.core.models import User
from modoboa.lib import exceptions as lib_exceptions
from modoboa.lib.email_utils import split_mailbox
from modoboa.lib.sysutils import doveadm_cmd
from modoboa.parameters import tools as param_tools
from .base import AdminObject
from .domain import Domain
class QuotaManager(models.Manager):
"""Custom manager for Quota."""
def get_domain_usage(self, domain):
"""Return current usage for domain."""
qset = self.get_queryset().filter(
username__endswith="@{}".format(domain.name))
result = qset.aggregate(usage=models.Sum("bytes")).get("usage", 0)
if result is None:
result = 0
return result
class Quota(models.Model):
"""Keeps track of Mailbox current quota."""
username = models.EmailField(primary_key=True, max_length=254)
bytes = models.BigIntegerField(default=0) # NOQA:A003
messages = models.IntegerField(default=0)
objects = QuotaManager()
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_queryset().select_related().filter(qf)
@python_2_unicode_compatible
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(default=0)
use_domain_quota = models.BooleanField(default=False)
domain = models.ForeignKey(Domain, on_delete=models.CASCADE)
user = models.OneToOneField(User, on_delete=models.CASCADE)
objects = MailboxManager()
class Meta:
app_label = "admin"
def __init__(self, *args, **kwargs):
super(Mailbox, self).__init__(*args, **kwargs)
self.__mail_home = None
self.old_full_address = self.full_address
def __str__(self):
return smart_text(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.recipientalias_set.select_related("alias")
.filter(alias__internal=False).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.
"""
admin_params = dict(param_tools.get_global_parameters("admin"))
if not admin_params.get("handle_mailboxes"):
return None
if self.__mail_home is None:
curuser = pwd.getpwuid(os.getuid()).pw_name
mbowner = admin_params["mailboxes_owner"]
options = {}
if curuser != mbowner:
options["sudo_user"] = mbowner
code, output = doveadm_cmd(
"user -f home %s" % self.full_address, **options
)
if code:
raise lib_exceptions.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
"""
qset = (
self.aliasrecipient_set.select_related("alias")
.filter(alias__internal=False)
)
aliases = [alr.alias.address for alr in qset]
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):
"""Rename local directory if needed."""
hm = param_tools.get_global_parameter(
"handle_mailboxes", raise_exception=False)
if not hm:
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 = param_tools.get_global_parameter(
"handle_mailboxes", raise_exception=False)
if not hm:
return
MailboxOperation.objects.create(type="delete", argument=self.mail_home)
def set_quota(self, value=None, override_rules=False):
"""Set or update quota 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
"""
old_quota = self.quota
if value is None:
if self.use_domain_quota:
self.quota = self.domain.default_mailbox_quota
else:
self.quota = 0
else:
self.quota = value
if self.quota == 0:
if self.domain.quota and not override_rules:
raise lib_exceptions.BadRequest(_("A quota is required"))
elif self.domain.quota:
quota_usage = self.domain.allocated_quota
if old_quota:
quota_usage -= old_quota
if quota_usage + self.quota > self.domain.quota:
raise lib_exceptions.BadRequest(_("Domain quota exceeded"))
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)
conditions = (
creator.has_perm("admin.add_mailbox"),
not self.user.has_perm("admin.add_domain")
)
if all(conditions):
# An admin 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:
if admin == creator:
continue
grant_access_to_object(admin, self)
grant_access_to_object(admin, self.user)
def update_from_dict(self, user, values):
"""Update mailbox from a dictionary."""
newaddress = None
if values["email"] != self.full_address:
newaddress = values["email"]
elif (self.user.role == "SimpleUsers" and
self.user.username != self.full_address):
newaddress = self.user.username
if newaddress is not None:
local_part, domname = split_mailbox(newaddress)
domain = Domain.objects.filter(name=domname).first()
if domain is None:
raise lib_exceptions.NotFound(_("Domain does not exist"))
if not user.can_access(domain):
raise lib_exceptions.PermDeniedException
if "use_domain_quota" in values:
self.use_domain_quota = values["use_domain_quota"]
if "use_domain_quota" in values or "quota" in values:
override_rules = (
not self.quota or user.is_superuser or
user.has_perm("admin.add_domain") and
not user.userobjectlimit_set.get(name="quota").max_value
)
self.set_quota(values["quota"], override_rules)
if newaddress:
self.rename(local_part, domain)
self.save()
def save(self, *args, **kwargs):
"""Custom save.
We check that the address is unique and we make sure a quota
record is defined for this mailbox.
"""
qset = Mailbox.objects.filter(address=self.address, domain=self.domain)
if self.pk:
qset = qset.exclude(pk=self.pk)
if qset.exists():
raise lib_exceptions.Conflict(
_("Mailbox {} already exists").format(self))
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)
reversion.register(Mailbox)
@python_2_unicode_compatible
class SenderAddress(models.Model):
"""Extra sender address for Mailbox."""
address = models.EmailField()
mailbox = models.ForeignKey(Mailbox, on_delete=models.CASCADE)
class Meta:
app_label = "admin"
unique_together = [
("address", "mailbox"),
]
def __str__(self):
"""Return address."""
return smart_text(self.address)
reversion.register(SenderAddress)
class MailboxOperation(models.Model):
"""An operation on a mailbox."""
mailbox = models.ForeignKey(Mailbox, blank=True, null=True,
on_delete=models.CASCADE)
type = models.CharField( # NOQA:A003
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