mirror of
https://github.com/jwinterm/modoboa.git
synced 2026-10-18 05:51:26 +02:00
* refactoring, limits are working pretty well * domain admins can now create domains (under control) when the limits extension is enabled see #105
297 lines
9.4 KiB
Python
297 lines
9.4 KiB
Python
# coding: utf-8
|
|
from functools import wraps
|
|
from django.http import HttpResponse, HttpResponseRedirect
|
|
from django.contrib.auth.models import User, Group
|
|
from django.contrib.contenttypes.models import ContentType
|
|
from django.utils.translation import ugettext as _
|
|
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
|
|
from models import *
|
|
from modoboa.lib.exceptions import ModoboaException
|
|
from modoboa.lib.webutils import _render_error, _render
|
|
from modoboa.admin.tables import *
|
|
|
|
class AdminError(ModoboaException):
|
|
"""
|
|
Just a Custom exception to identify errors coming from the admin
|
|
application.
|
|
"""
|
|
pass
|
|
|
|
def is_not_localadmin(errortpl="error"):
|
|
def dec(f):
|
|
def wrapped_f(request, *args, **kwargs):
|
|
if request.user.id == 1:
|
|
return _render_error(request, errortpl, {
|
|
"error" : _("Invalid action, %(user)s is a local user" \
|
|
% {"user" : request.user.username})
|
|
})
|
|
return f(request, *args, **kwargs)
|
|
|
|
wrapped_f.__name__ = f.__name__
|
|
wrapped_f.__dict__ = f.__dict__
|
|
wrapped_f.__doc__ = f.__doc__
|
|
wrapped_f.__module__ = f.__module__
|
|
return wrapped_f
|
|
return dec
|
|
|
|
def is_domain_admin(user):
|
|
"""Tell if a user is administrator for a domain of not
|
|
|
|
Whatever the domain, we just want to know if the given user is
|
|
declared as a domain administrator.
|
|
|
|
:param user: a User object
|
|
:return: True if the user is a domain administrator, False otherwise.
|
|
"""
|
|
grp = Group.objects.get(name="DomainAdmins")
|
|
return grp in user.groups.all()
|
|
|
|
def good_domain(f):
|
|
"""Custom permission decorator
|
|
|
|
Check if a domain administrator is accessing the good domain.
|
|
|
|
:param f: the original called function
|
|
"""
|
|
def dec(request, **kwargs):
|
|
if request.user.is_superuser:
|
|
return f(request, **kwargs)
|
|
|
|
if is_domain_admin(request.user):
|
|
mb = Mailbox.objects.get(user=request.user.id)
|
|
access = True
|
|
if request.GET.has_key("domid"):
|
|
dom_id = int(request.GET["domid"])
|
|
if dom_id != mb.domain.id:
|
|
access = False
|
|
else:
|
|
q = request.GET.copy()
|
|
q["domid"] = mb.domain.id
|
|
request.GET = q
|
|
if access:
|
|
return f(request, **kwargs)
|
|
|
|
from django.conf import settings
|
|
from django.utils.http import urlquote
|
|
path = urlquote(request.get_full_path())
|
|
login_url = settings.LOGIN_URL
|
|
return HttpResponseRedirect("%s?next=%s" % (login_url, path))
|
|
return dec
|
|
|
|
def render_listing(request, objtype, tplname="admin/listing.html",
|
|
**kwargs):
|
|
"""Common function to render a listing
|
|
|
|
All listing pages available into the admin application use the
|
|
same layout, rendered by this function.
|
|
|
|
:param request: a ``Request`` object
|
|
:param objtype: the object type's name (lowercase)
|
|
:param tplname: the template used to render the HTML
|
|
"""
|
|
tblclass = "%sTable" % objtype.capitalize()
|
|
if not globals().has_key(tblclass):
|
|
raise AdminError(_("Unknown object type"))
|
|
tblclass = globals()[tblclass]
|
|
|
|
if request.GET.has_key("domid"):
|
|
kwargs["domid"] = request.GET["domid"]
|
|
else:
|
|
kwargs["domid"] = ""
|
|
kwargs["selection"] = objtype
|
|
paginator = Paginator(kwargs["objects"],
|
|
int(parameters.get_admin("ITEMS_PER_PAGE")))
|
|
try:
|
|
page = request.GET.get("page", "1")
|
|
except ValueError:
|
|
page = 1
|
|
try:
|
|
kwargs["objects"] = paginator.page(page)
|
|
except (EmptyPage, PageNotAnInteger):
|
|
kwargs["objects"] = paginator.page(paginator.num_pages)
|
|
kwargs["last_page"] = paginator.num_pages
|
|
kwargs["total"] = paginator.count
|
|
kwargs["table"] = tblclass(request, kwargs["objects"].object_list)
|
|
|
|
return _render(request, tplname, kwargs)
|
|
|
|
def set_object_ownership(user, obj):
|
|
"""Simple shorcut to associate an object to a user
|
|
|
|
:param user: a ``User`` object
|
|
:param obj: an admin. object (Domain, Mailbox, ...)
|
|
"""
|
|
try:
|
|
ct = ContentType.objects.get_for_model(obj)
|
|
obj_owner = user.objectowner_set.get(content_type=ct, object_id=obj.id)
|
|
except ObjectOwner.DoesNotExist:
|
|
obj_owner = ObjectOwner(user=user, content_object=obj)
|
|
obj_owner.save()
|
|
|
|
def unset_object_ownership(obj):
|
|
"""Remove the ownership of the given object
|
|
|
|
:param obj: an object inheriting from ``models.Model``
|
|
"""
|
|
ct = ContentType.objects.get_for_model(obj)
|
|
ObjectOwner.objects.get(content_type=ct, object_id=obj.id).delete()
|
|
|
|
def is_object_owner(user, obj):
|
|
"""Check if a user is the object's owner
|
|
|
|
An object can only belongs to one user.
|
|
|
|
This function is recursive : if the given user is not the owner
|
|
and if he owns other ``User`` objects, we also check if one of
|
|
those users owns the object.
|
|
|
|
:param user: a ``User`` object
|
|
:param obj: a admin object
|
|
:return: a boolean
|
|
"""
|
|
if user.is_superuser:
|
|
return True
|
|
|
|
ct = ContentType.objects.get_for_model(obj)
|
|
try:
|
|
ooentry = user.objectowner_set.get(content_type=ct, object_id=obj.id)
|
|
except ObjectOwner.DoesNotExist:
|
|
pass
|
|
else:
|
|
return True
|
|
if ct.model == "user":
|
|
return False
|
|
|
|
ct = ContentType.objects.get_for_model(User)
|
|
qs = user.objectowner_set.filter(content_type=ct)
|
|
for ooentry in qs.all():
|
|
if is_object_owner(ooentry.content_object, obj):
|
|
return True
|
|
return False
|
|
|
|
def get_object_owner(obj):
|
|
"""Return this object's owner
|
|
|
|
:param obj: an object inheriting from ``model.Model``
|
|
:return: a ``User`` object
|
|
"""
|
|
ct = ContentType.objects.get_for_model(obj)
|
|
res = ObjectOwner.objects.filter(content_type=ct, object_id=obj.id)
|
|
if not len(res):
|
|
return None
|
|
return res[0].user
|
|
|
|
def check_domain_ownership(f):
|
|
"""Decorator to check if the current user can access to a domain
|
|
|
|
This decorator only applies to url that contain a 'domid' or
|
|
'mbid' parameter. (for a direct access)
|
|
|
|
Raise a ``PermDeniedError`` if the current user can't view the
|
|
requested object.
|
|
"""
|
|
@wraps(f)
|
|
def dec(request, **kwargs):
|
|
if not request.user.is_superuser:
|
|
domain = None
|
|
domid = request.GET.get("domid", None)
|
|
if domid:
|
|
domain = Domain.objects.get(pk=domid)
|
|
else:
|
|
mbid = request.GET.get("mbid", None)
|
|
if mbid:
|
|
domain = Mailbox.objects.get(pk=mbid).domain
|
|
if domain and not is_object_owner(request.user, domain):
|
|
raise PermDeniedError(_("You can't access this domain"))
|
|
|
|
return f(request, **kwargs)
|
|
return dec
|
|
|
|
def get_user_domains(user):
|
|
if user.is_superuser:
|
|
return Domain.objects.all()
|
|
|
|
domct = ContentType.objects.get_for_model(Domain)
|
|
qs = user.objectowner_set.filter(content_type=domct)
|
|
domains = []
|
|
for entry in qs.all():
|
|
domains += [entry.content_object]
|
|
|
|
userct = ContentType.objects.get_for_model(User)
|
|
qs = user.objectowner_set.filter(content_type=userct)
|
|
for entry in qs:
|
|
if not is_domain_admin(entry.content_object):
|
|
continue
|
|
domains += get_user_domains(entry.content_object)
|
|
return domains
|
|
|
|
def get_user_domains_qs(user):
|
|
"""Return the domains belonging to this user
|
|
|
|
The result is a ``QuerySet`` object, so this function can be used
|
|
to fill ``ModelChoiceField`` objects.
|
|
|
|
:param user: a ``User`` object
|
|
"""
|
|
if user.is_superuser:
|
|
return Domain.objects.all()
|
|
result = Domain.objects.filter(owners__user=user)
|
|
|
|
userct = ContentType.objects.get_for_model(User)
|
|
for entry in user.objectowner_set.filter(content_type=userct):
|
|
if not is_domain_admin(entry.content_object):
|
|
continue
|
|
result |= get_user_domains_qs(entry.content_object)
|
|
return result
|
|
|
|
def get_user_domaliases(user):
|
|
"""Return the domain aliases that belong to this user
|
|
|
|
The result will contain the domain aliases defined for each domain
|
|
that user can see.
|
|
|
|
:param user: a ``User`` object
|
|
:return: a list of ``DomainAlias`` objects
|
|
"""
|
|
if user.is_superuser:
|
|
return DomainAlias.objects.all()
|
|
domains = get_user_domains_qs(user)
|
|
domaliases = []
|
|
for dom in domains:
|
|
domaliases += dom.domainalias_set.all()
|
|
return domaliases
|
|
|
|
def get_user_mailboxes(user):
|
|
"""Return the mailboxes that belong to this user
|
|
|
|
The result will contain the mailboxes defined for each domain that
|
|
user can see.
|
|
|
|
:param user: a ``User`` object
|
|
:return: a list of ``Mailbox`` objects
|
|
"""
|
|
if user.is_superuser:
|
|
return Mailbox.objects.all()
|
|
mboxes = []
|
|
domains = get_user_domains_qs(user)
|
|
for dom in domains:
|
|
mboxes += dom.mailbox_set.all()
|
|
return mboxes
|
|
|
|
def get_user_mbaliases(user):
|
|
"""Return the mailbox aliases that belong to this user
|
|
|
|
The result will contain the mailbox aliases defined for each
|
|
domain that user can see.
|
|
|
|
:param user: a ``User`` object
|
|
:return: a list of ``Alias`` objects
|
|
"""
|
|
if user.is_superuser:
|
|
return Alias.objects.all()
|
|
domains = get_user_domains_qs(user)
|
|
mbaliases = []
|
|
for dom in domains:
|
|
mbaliases += dom.alias_set.all()
|
|
return mbaliases
|