mirror of
https://github.com/jwinterm/modoboa.git
synced 2026-10-25 05:58:42 +01:00
313 lines
10 KiB
Python
313 lines
10 KiB
Python
"""
|
|
SQL connector module.
|
|
"""
|
|
|
|
import datetime
|
|
|
|
from django.db.models import Q
|
|
|
|
from modoboa.extensions.admin.models import Domain
|
|
from modoboa.lib.dbutils import db_type
|
|
|
|
from .models import Quarantine, Msgrcpt, Maddr
|
|
|
|
|
|
class SQLconnector(object):
|
|
|
|
"""
|
|
This class handles all database operations.
|
|
"""
|
|
|
|
ORDER_TRANSLATION_TABLE = {
|
|
"type": "content",
|
|
"score": "bspam_level",
|
|
"date": "mail__time_num",
|
|
"subject": "mail__subject",
|
|
"from": "mail__from_addr",
|
|
"to": "rid__email"
|
|
}
|
|
|
|
QUARANTINE_FIELDS = [
|
|
"content",
|
|
"bspam_level",
|
|
"rs",
|
|
"rid__email",
|
|
"mail__from_addr",
|
|
"mail__subject",
|
|
"mail__mail_id",
|
|
"mail__time_num",
|
|
]
|
|
|
|
def __init__(self, user=None, navparams=None):
|
|
"""Constructor."""
|
|
self.user = user
|
|
self.navparams = navparams
|
|
self.messages = None
|
|
|
|
self._messages_count = None
|
|
|
|
def _exec(self, query, args):
|
|
"""Execute a raw SQL query.
|
|
|
|
:param string query: query to execute
|
|
:param list args: a list of arguments to replace in :kw:`query`
|
|
"""
|
|
from django.db import connections, transaction
|
|
|
|
cursor = connections['amavis'].cursor()
|
|
cursor.execute(query, args)
|
|
transaction.commit_unless_managed(using='amavis')
|
|
|
|
def _apply_msgrcpt_filters(self, flt):
|
|
"""Apply filters based on user's role.
|
|
|
|
"""
|
|
if self.user.group == 'SimpleUsers':
|
|
rcpts = [self.user.email]
|
|
if self.user.mailbox_set.count():
|
|
rcpts += self.user.mailbox_set.all()[0].alias_addresses
|
|
return flt & Q(rid__email__in=rcpts)
|
|
elif not self.user.is_superuser:
|
|
doms = Domain.objects.get_for_admin(self.user)
|
|
regexp = "(%s)" % '|'.join([dom.name for dom in doms])
|
|
return flt & Q(rid__email__regex=regexp)
|
|
return flt
|
|
|
|
def _apply_extra_search_filter(self, crit, pattern):
|
|
"""Apply search filters using additional criterias.
|
|
|
|
"""
|
|
if crit == "to":
|
|
return Q(rid__email__contains=pattern)
|
|
return None
|
|
|
|
def _apply_extra_select_filters(self, messages):
|
|
"""Just a hook to apply additional filters to the queryset.
|
|
|
|
"""
|
|
return messages
|
|
|
|
def _get_quarantine_content(self):
|
|
"""Fetch quarantine content.
|
|
|
|
Filters: rs, rid, content
|
|
"""
|
|
flt = Q(rs__in=[' ', 'V', 'R', 'p', 'S', 'H']) \
|
|
if self.navparams.get('viewrequests', '0') != '1' else Q(rs='p')
|
|
flt = self._apply_msgrcpt_filters(flt)
|
|
pattern = self.navparams.get("pattern", "")
|
|
if pattern:
|
|
criteria = self.navparams.get('criteria')
|
|
if criteria == "both":
|
|
criteria = "from_addr,subject,to"
|
|
search_flt = None
|
|
for crit in criteria.split(","):
|
|
if crit == "from_addr":
|
|
nfilter = Q(mail__from_addr__contains=pattern)
|
|
elif crit == "subject":
|
|
nfilter = Q(mail__subject__contains=pattern)
|
|
else:
|
|
nfilter = self._apply_extra_search_filter(crit, pattern)
|
|
if nfilter is None:
|
|
continue
|
|
search_flt = nfilter \
|
|
if search_flt is None else search_flt | nfilter
|
|
if search_flt:
|
|
flt &= search_flt
|
|
msgtype = self.navparams.get('msgtype', None)
|
|
if msgtype is not None:
|
|
flt &= Q(content=msgtype)
|
|
|
|
flt &= Q(
|
|
mail__in=Quarantine.objects.filter(chunk_ind=1).values("mail_id")
|
|
)
|
|
|
|
messages = Msgrcpt.objects.select_related("mail", "rid").filter(flt)
|
|
messages = self._apply_extra_select_filters(messages)
|
|
return messages
|
|
|
|
def messages_count(self):
|
|
"""Return the total number of messages living in the quarantine.
|
|
|
|
We also store the built queryset for a later use.
|
|
"""
|
|
if self.user is None or self.navparams is None:
|
|
return None
|
|
if self._messages_count is None:
|
|
self.messages = self._get_quarantine_content()
|
|
self.messages = self.messages.values(*self.QUARANTINE_FIELDS)
|
|
|
|
order = self.navparams.get("order")
|
|
if order is not None:
|
|
sign = ""
|
|
if order[0] == "-":
|
|
sign = "-"
|
|
order = order[1:]
|
|
order = self.ORDER_TRANSLATION_TABLE[order]
|
|
self.messages = self.messages.order_by(sign + order)
|
|
|
|
self._messages_count = self.messages.count()
|
|
|
|
return self._messages_count
|
|
|
|
def fetch(self, start=None, stop=None):
|
|
"""Fetch a range of messages from the internal cache.
|
|
|
|
"""
|
|
emails = []
|
|
for qm in self.messages[start - 1:stop]:
|
|
if qm["rs"] == 'D':
|
|
continue
|
|
m = {
|
|
"from": qm["mail__from_addr"],
|
|
"to": qm["rid__email"],
|
|
"subject": qm["mail__subject"],
|
|
"mailid": qm["mail__mail_id"],
|
|
"date": datetime.datetime.fromtimestamp(qm["mail__time_num"]),
|
|
"type": qm["content"],
|
|
"score": qm["bspam_level"],
|
|
"status": qm["rs"]
|
|
}
|
|
if qm["rs"] in ['', ' ']:
|
|
m["class"] = "unseen"
|
|
elif qm["rs"] == 'p':
|
|
m["class"] = "pending"
|
|
emails.append(m)
|
|
return emails
|
|
|
|
def get_recipient_message(self, address, mailid):
|
|
"""Retrieve a message for a given recipient.
|
|
"""
|
|
return Msgrcpt.objects.get(mail=mailid, rid__email=address)
|
|
|
|
def set_msgrcpt_status(self, address, mailid, status):
|
|
"""Change the status (rs field) of a message recipient.
|
|
|
|
:param string status: status
|
|
"""
|
|
addr = Maddr.objects.get(email=address)
|
|
self._exec(
|
|
"UPDATE msgrcpt SET rs=%s WHERE mail_id=%s AND rid=%s",
|
|
[status, mailid, addr.id]
|
|
)
|
|
|
|
def get_domains_pending_requests(self, domains):
|
|
"""Retrieve pending release requests for a list of domains.
|
|
"""
|
|
regexp = "(%s)" % '|'.join([dom.name for dom in domains])
|
|
return Msgrcpt.objects.filter(rs='p', rid__email__regex=regexp)
|
|
|
|
def get_pending_requests(self):
|
|
"""Return the number of requests currently pending."""
|
|
rq = Q(rs='p')
|
|
if not self.user.is_superuser:
|
|
doms = Domain.objects.get_for_admin(self.user)
|
|
if not doms.count():
|
|
return 0
|
|
regexp = "(%s)" % '|'.join([dom.name for dom in doms])
|
|
doms_q = Q(rid__email__regex=regexp)
|
|
rq &= doms_q
|
|
return Msgrcpt.objects.filter(rq).count()
|
|
|
|
def get_mail_content(self, mailid):
|
|
"""Retrieve the content of a message."""
|
|
content = "".join([
|
|
str(qmail.mail_text)
|
|
for qmail in Quarantine.objects.filter(mail=mailid)
|
|
])
|
|
if isinstance(content, unicode):
|
|
content = content.encode("utf-8")
|
|
return content
|
|
|
|
|
|
class PgSQLconnector(SQLconnector):
|
|
|
|
"""
|
|
The postgres version.
|
|
|
|
Make use of ``QuerySet.extra`` and postgres ``convert_from``
|
|
function to let the quarantine manager work as expected !
|
|
|
|
The T4 alias is not a random choice. The generated query was
|
|
dumped to found it. Be careful since it can changes with
|
|
future Django versions...
|
|
|
|
"""
|
|
|
|
def _apply_msgrcpt_filters(self, flt):
|
|
"""Return filters based on user's role. """
|
|
self._where = []
|
|
if self.user.group == 'SimpleUsers':
|
|
rcpts = [self.user.email]
|
|
if self.user.mailbox_set.count():
|
|
rcpts += self.user.mailbox_set.all()[0].alias_addresses
|
|
self._where.append(
|
|
"convert_from(maddr.email, 'UTF8') IN (%s)" \
|
|
% (','.join(["'%s'" % rcpt for rcpt in rcpts])))
|
|
elif not self.user.is_superuser:
|
|
doms = Domain.objects.get_for_admin(self.user)
|
|
regexp = "(%s)" % '|'.join([dom.name for dom in doms])
|
|
self._where.append(
|
|
"convert_from(maddr.email, 'UTF8') ~ '%s'" % regexp)
|
|
return flt
|
|
|
|
def _apply_extra_search_filter(self, crit, pattern):
|
|
"""Apply search filters using additional criterias."""
|
|
if crit == "to":
|
|
self._where.append(
|
|
"convert_from(maddr.email, 'UTF8') LIKE '%%{0}%%'".format(
|
|
pattern)
|
|
)
|
|
return None
|
|
|
|
def _apply_extra_select_filters(self, messages):
|
|
"""Just a hook to apply additional filters to the queryset.
|
|
|
|
"""
|
|
return messages.extra(where=self._where)
|
|
|
|
def get_recipient_message(self, address, mailid):
|
|
"""Retrieve a message for a given recipient.
|
|
"""
|
|
qset = Msgrcpt.objects.filter(mail=mailid).extra(
|
|
where=["msgrcpt.rid=maddr.id",
|
|
"convert_from(maddr.email, 'UTF8') = '%s'" % address],
|
|
tables=['maddr']
|
|
)
|
|
return qset.all()[0]
|
|
|
|
def get_domains_pending_requests(self, domains):
|
|
"""Retrieve pending release requests for a list of domains.
|
|
"""
|
|
regexp = "(%s)" % '|'.join([dom.name for dom in domains])
|
|
return Msgrcpt.objects.filter(rs='p').extra(
|
|
where=["msgrcpt.rid=maddr.id",
|
|
"convert_from(maddr.email, 'UTF8') ~ '%s'" % regexp],
|
|
tables=['maddr']
|
|
)
|
|
|
|
def get_pending_requests(self):
|
|
"""Return the number of requests currently pending."""
|
|
rq = Q(rs='p')
|
|
if not self.user.is_superuser:
|
|
doms = Domain.objects.get_for_admin(self.user)
|
|
if not doms.count():
|
|
return 0
|
|
regexp = "(%s)" % '|'.join([dom.name for dom in doms])
|
|
return Msgrcpt.objects.filter(rq).extra(
|
|
where=["msgrcpt.rid=maddr.id",
|
|
"convert_from(maddr.email, 'UTF8') ~ '%s'" % (regexp,)],
|
|
tables=['maddr']
|
|
).count()
|
|
return Msgrcpt.objects.filter(rq).count()
|
|
|
|
|
|
def get_connector(**kwargs):
|
|
"""Return the appropriate *connector* class.
|
|
|
|
The result depends on the DB engine in use.
|
|
"""
|
|
if db_type("amavis") == 'postgres':
|
|
return PgSQLconnector(**kwargs)
|
|
return SQLconnector(**kwargs)
|