mirror of
https://github.com/jwinterm/modoboa.git
synced 2026-10-18 05:51:26 +02:00
wip: Limits and resellers
* refactoring, limits are working pretty well * domain admins can now create domains (under control) when the limits extension is enabled see #105
This commit is contained in:
+2
-2
@@ -7,7 +7,7 @@ from django.contrib.auth.models import User
|
||||
from modoboa.admin.templatetags.admin_extras import gender
|
||||
from modoboa.lib import tables
|
||||
from modoboa.lib.emailutils import split_mailbox
|
||||
from lib import get_user_domains
|
||||
from lib import get_user_domains, get_user_domains_qs
|
||||
|
||||
class DomainForm(forms.ModelForm):
|
||||
class Meta:
|
||||
@@ -34,7 +34,7 @@ class ProxyForm(forms.ModelForm):
|
||||
super(ProxyForm, self).__init__(*args, **kwargs)
|
||||
|
||||
if not user.is_superuser:
|
||||
self._domains = Domain.objects.filter(owners__user=user)
|
||||
self._domains = get_user_domains_qs(user)
|
||||
else:
|
||||
self._domains = None
|
||||
|
||||
|
||||
+115
-36
@@ -2,6 +2,7 @@
|
||||
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 *
|
||||
@@ -77,18 +78,6 @@ def good_domain(f):
|
||||
return HttpResponseRedirect("%s?next=%s" % (login_url, path))
|
||||
return dec
|
||||
|
||||
# def has_access_to_domain(user, domain):
|
||||
# """
|
||||
# """
|
||||
# access = events.raiseQueryEvent("CanViewDomain", user, domain)
|
||||
# if True in access:
|
||||
# return True
|
||||
# if is_domain_admin(user):
|
||||
# mb = Mailbox.objects.get(user=request.user.id)
|
||||
# if mb.domain == domain:
|
||||
# return True
|
||||
# return False
|
||||
|
||||
def render_listing(request, objtype, tplname="admin/listing.html",
|
||||
**kwargs):
|
||||
"""Common function to render a listing
|
||||
@@ -132,14 +121,29 @@ def set_object_ownership(user, obj):
|
||||
:param user: a ``User`` object
|
||||
:param obj: an admin. object (Domain, Mailbox, ...)
|
||||
"""
|
||||
obj_owner = ObjectOwner(user=user, content_object=obj)
|
||||
obj_owner.save()
|
||||
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 have multiple owners but this function only
|
||||
concerns one given user.
|
||||
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
|
||||
@@ -148,14 +152,44 @@ def is_object_owner(user, obj):
|
||||
if user.is_superuser:
|
||||
return True
|
||||
|
||||
qs = user.objectowner_set.filter(content_type__app_label="admin",
|
||||
content_type__model=obj._meta.module_name)
|
||||
for entry in qs.all():
|
||||
if entry.content_object == obj:
|
||||
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:
|
||||
@@ -177,41 +211,86 @@ def get_user_domains(user):
|
||||
if user.is_superuser:
|
||||
return Domain.objects.all()
|
||||
|
||||
qs = user.objectowner_set.filter(content_type__app_label="admin",
|
||||
content_type__model="domain")
|
||||
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()
|
||||
qs = user.objectowner_set.filter(content_type__app_label="admin",
|
||||
content_type__model="domain")
|
||||
domains = get_user_domains_qs(user)
|
||||
domaliases = []
|
||||
for entry in qs.all():
|
||||
domaliases += entry.content_object.domainalias_set.all()
|
||||
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()
|
||||
|
||||
qs = user.objectowner_set.filter(content_type__app_label="admin",
|
||||
content_type__model="domain")
|
||||
mboxes = []
|
||||
for ro in qs.all():
|
||||
mboxes += Mailbox.objects.filter(domain=ro.content_object)
|
||||
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()
|
||||
|
||||
qs = user.objectowner_set.filter(content_type__app_label="admin",
|
||||
content_type__model="domain")
|
||||
domains = get_user_domains_qs(user)
|
||||
mbaliases = []
|
||||
for ro in qs.all():
|
||||
mbaliases += Alias.objects.filter(domain=ro.content_object)
|
||||
for dom in domains:
|
||||
mbaliases += dom.alias_set.all()
|
||||
return mbaliases
|
||||
|
||||
+21
-3
@@ -24,6 +24,9 @@ class ObjectOwner(models.Model):
|
||||
object_id = models.PositiveIntegerField()
|
||||
content_object = generic.GenericForeignKey('content_type', 'object_id')
|
||||
|
||||
class Meta:
|
||||
unique_together = (("content_type", "object_id"),)
|
||||
|
||||
class ObjectDates(models.Model):
|
||||
"""Dates recording for admin objects
|
||||
|
||||
@@ -471,21 +474,33 @@ class Alias(DatesAware):
|
||||
|
||||
class Extension(models.Model):
|
||||
name = models.CharField(max_length=150)
|
||||
enabled = models.BooleanField(ugettext_noop('enabled'),
|
||||
help_text=ugettext_noop("Check to enable this extension"))
|
||||
enabled = models.BooleanField(
|
||||
ugettext_noop('enabled'),
|
||||
help_text=ugettext_noop("Check to enable this extension")
|
||||
)
|
||||
|
||||
def __getmodule(self):
|
||||
extname = "modoboa.extensions.%s" % self.name
|
||||
return __import__(extname, globals(), locals(), ['main'])
|
||||
|
||||
def init(self):
|
||||
module = self.__getmodule()
|
||||
if hasattr(module.main, "init"):
|
||||
module.main.init()
|
||||
|
||||
def load(self):
|
||||
self.__getmodule().main.init()
|
||||
module = self.__getmodule()
|
||||
if hasattr(module.main, "load"):
|
||||
module.main.load()
|
||||
|
||||
def unload(self):
|
||||
self.__getmodule().main.destroy()
|
||||
|
||||
def on(self):
|
||||
self.load()
|
||||
self.init()
|
||||
self.enabled = True
|
||||
self.save()
|
||||
|
||||
extdir = "%s/extensions/%s" % (settings.MODOBOA_DIR, self.name)
|
||||
scriptdir = "%s/%s" % (extdir, "scripts")
|
||||
@@ -503,6 +518,9 @@ class Extension(models.Model):
|
||||
def off(self):
|
||||
self.unload()
|
||||
|
||||
self.enabled = False
|
||||
self.save()
|
||||
|
||||
extdir = "%s/extensions/%s" % (settings.MODOBOA_DIR, self.name)
|
||||
scriptdir = "%s/%s" % (extdir, "scripts")
|
||||
if os.path.exists(scriptdir):
|
||||
|
||||
@@ -107,14 +107,14 @@ class DomainAdminsPerms(Permissions):
|
||||
return False, getctx("ko", content=content)
|
||||
|
||||
def delete(self, selection):
|
||||
User.objects.filter(id__in=selection).delete()
|
||||
# for s in selection:
|
||||
# user.delete()
|
||||
# try:
|
||||
# mbox = Mailbox.objects.get(pk=int(s))
|
||||
# grp = Group.objects.get(name="DomainAdmins")
|
||||
# mbox.user.groups.remove(grp)
|
||||
# mbox.user.save()
|
||||
grp = Group.objects.get(name="DomainAdmins")
|
||||
for uid in selection:
|
||||
u = User.objects.get(pk=uid)
|
||||
u.groups.remove(grp)
|
||||
u.save()
|
||||
events.raiseEvent("DomainAdminDeleted", u)
|
||||
if not len(u.mailbox_set.all()):
|
||||
u.delete()
|
||||
|
||||
def get(self, request):
|
||||
#domadmins = Mailbox.objects.filter(user__groups__name="DomainAdmins")
|
||||
|
||||
@@ -2,6 +2,7 @@ from django import template
|
||||
from django.template.loader import render_to_string
|
||||
from django.utils.translation import ugettext as _, ugettext_noop
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.contrib.auth.models import User
|
||||
from modoboa import admin
|
||||
from modoboa.lib import events
|
||||
from modoboa.lib.webutils import static_url, render_actions
|
||||
@@ -179,7 +180,7 @@ def domalias_actions(user, aid):
|
||||
"img" : static_url("pics/edit.png"),
|
||||
"title" : _("Edit alias"),
|
||||
"class" : "boxed",
|
||||
"rel" : "{handler:'iframe',size:{x:300,y:190}}"}
|
||||
"rel" : "{handler:'iframe',size:{x:310,y:190}}"}
|
||||
]
|
||||
return render_actions(actions)
|
||||
|
||||
@@ -191,7 +192,7 @@ def mailbox_actions(user, mboxid):
|
||||
"img" : static_url("pics/edit.png"),
|
||||
"title" : _("Edit mailbox"),
|
||||
"class" : "boxed",
|
||||
"rel" : "{handler:'iframe',size:{x:300,y:320}}"},
|
||||
"rel" : "{handler:'iframe',size:{x:310,y:320}}"},
|
||||
{"name" : "aliases",
|
||||
"url" : reverse(admin.views.mbaliases) + "?mbid=%d" % mboxid,
|
||||
"img" : static_url("pics/alias.png"),
|
||||
@@ -209,6 +210,7 @@ def domain_admin_actions(user, daid):
|
||||
"class" : "boxed",
|
||||
"rel" : "{handler:'iframe',size:{x:350,y:320}}"},
|
||||
]
|
||||
actions += events.raiseQueryEvent("DomainAdminActions", User.objects.get(pk=daid))
|
||||
return render_actions(actions)
|
||||
|
||||
@register.simple_tag
|
||||
|
||||
+18
-12
@@ -126,7 +126,8 @@ def deldomain(request):
|
||||
if mb and mb.domain == dom:
|
||||
raise AdminError(_("You can't delete your own domain"))
|
||||
|
||||
events.raiseEvent("DeleteDomain", request.user, dom)
|
||||
events.raiseEvent("DeleteDomain", dom)
|
||||
unset_object_ownership(dom)
|
||||
dom.delete(keepdir=keepdir)
|
||||
|
||||
msg = ungettext("Domain deleted", "Domains deleted", len(selection))
|
||||
@@ -152,6 +153,7 @@ def domaliases(request):
|
||||
@permission_required("admin.add_domainalias")
|
||||
@transaction.commit_on_success
|
||||
def newdomalias(request, tplname="admin/adminform.html"):
|
||||
events.raiseEvent("CanCreateDomainAlias", request.user)
|
||||
commonctx = {"title" : _("New domain alias"),
|
||||
"submit_label" : _("Create"),
|
||||
"action" : reverse(newdomalias),
|
||||
@@ -160,7 +162,6 @@ def newdomalias(request, tplname="admin/adminform.html"):
|
||||
form = DomainAliasForm(request.user, request.POST)
|
||||
error = None
|
||||
if form.is_valid():
|
||||
events.raiseEvent("CanCreateDomainAlias", request.user)
|
||||
domalias = form.save(commit=False)
|
||||
if Domain.objects.filter(name=domalias.name):
|
||||
error = _("A domain with this name already exists")
|
||||
@@ -170,6 +171,7 @@ def newdomalias(request, tplname="admin/adminform.html"):
|
||||
except IntegrityError:
|
||||
error = _("Alias with this name already exists")
|
||||
else:
|
||||
set_object_ownership(request.user, domalias)
|
||||
events.raiseEvent("DomainAliasCreated", request.user, domalias)
|
||||
messages.info(request, _("Domain alias created"), fail_silently=True)
|
||||
return ajax_response(request, url=reverse(admin.views.domaliases))
|
||||
@@ -227,7 +229,8 @@ def deldomalias(request):
|
||||
domalias = DomainAlias.objects.get(pk=daid)
|
||||
if not is_object_owner(request.user, domalias.target):
|
||||
raise PermDeniedException(_("You don't have acces to this domain"))
|
||||
events.raiseEvent("DomainAliasDeleted", request.user, domalias)
|
||||
events.raiseEvent("DomainAliasDeleted", domalias)
|
||||
unset_object_ownership(domalias)
|
||||
domalias.delete()
|
||||
msg = ungettext("Domain alias deleted", "Domain aliases deleted", len(selection))
|
||||
messages.info(request, msg, fail_silently=True)
|
||||
@@ -287,6 +290,7 @@ def mailboxes_search(request):
|
||||
@permission_required("admin.add_mailbox")
|
||||
@transaction.commit_on_success
|
||||
def newmailbox(request, tplname="admin/adminform.html"):
|
||||
events.raiseEvent("CanCreateMailbox", request.user)
|
||||
commonctx = {"title" : _("New mailbox"),
|
||||
"submit_label" : _("Create"),
|
||||
"action" : reverse(newmailbox),
|
||||
@@ -299,12 +303,12 @@ def newmailbox(request, tplname="admin/adminform.html"):
|
||||
if not is_object_owner(request.user, form.cleaned_data["domain"]):
|
||||
raise PermDeniedException(_("You do not have access to this domain"))
|
||||
|
||||
events.raiseEvent("CanCreateMailbox", request.user)
|
||||
try:
|
||||
mb = form.save()
|
||||
except AdminError as e:
|
||||
error = str(e)
|
||||
else:
|
||||
set_object_ownership(request.user, mb)
|
||||
events.raiseEvent("CreateMailbox", request.user, mb)
|
||||
messages.info(request, _("Mailbox created"),
|
||||
fail_silently=True)
|
||||
@@ -370,7 +374,8 @@ def delmailbox(request):
|
||||
raise PermDeniedException(_("You don't have access to this domain"))
|
||||
if usermb and usermb == mb:
|
||||
raise AdminError(_("You can't delete your own mailbox"))
|
||||
events.raiseEvent("DeleteMailbox", request.user, mb)
|
||||
events.raiseEvent("DeleteMailbox", mb)
|
||||
unset_object_ownership(mb)
|
||||
mb.delete(keepdir=keepdir)
|
||||
|
||||
msg = ungettext("Mailbox deleted", "Mailboxes deleted", len(selection))
|
||||
@@ -433,6 +438,7 @@ def newmbalias(request, tplname="admin/mbaliasform.html"):
|
||||
"formid" : "mbaliasform"}
|
||||
if request.method == "POST":
|
||||
def callback(user, mbalias):
|
||||
set_object_ownership(user, mbalias)
|
||||
events.raiseEvent("MailboxAliasCreated", user, mbalias)
|
||||
|
||||
form = AliasForm(request.user, request.POST)
|
||||
@@ -473,7 +479,8 @@ def delmbalias(request):
|
||||
mbalias = Alias.objects.get(pk=mbaid)
|
||||
if not is_object_owner(request.user, mbalias.domain):
|
||||
raise PermDeniedException(_("You do not have access to this domain"))
|
||||
events.raiseEvent("MailboxAliasDeleted", request.user, mbalias)
|
||||
events.raiseEvent("MailboxAliasDeleted", mbalias)
|
||||
unset_object_ownership(mbalias)
|
||||
mbalias.delete()
|
||||
|
||||
msg = ungettext("Alias deleted", "Aliases deleted", len(selection))
|
||||
@@ -534,6 +541,7 @@ def create_domain_admin(request, tplname="admin/create_domain_admin.html"):
|
||||
form = UserWithPasswordForm(request.POST)
|
||||
if form.is_valid():
|
||||
user = form.save(group="DomainAdmins")
|
||||
set_object_ownership(request.user, user)
|
||||
events.raiseEvent("DomainAdminCreated", user)
|
||||
messages.info(request, _("Domain admin created"), fail_silently=True)
|
||||
return ajax_response(request, url=reverse(admin.views.permissions))
|
||||
@@ -541,7 +549,7 @@ def create_domain_admin(request, tplname="admin/create_domain_admin.html"):
|
||||
return ajax_response(request, status="ko", template=tplname, form=form)
|
||||
|
||||
form = UserWithPasswordForm()
|
||||
return ajax_response(request, tplname, form=form)
|
||||
return ajax_response(request, template=tplname, form=form)
|
||||
|
||||
@login_required
|
||||
@permission_required("auth.view_permissions")
|
||||
@@ -580,6 +588,8 @@ def domain_admin_promotion(request):
|
||||
u = User.objects.get(username=form.cleaned_data["name"])
|
||||
u.groups.add(Group.objects.get(name="DomainAdmins"))
|
||||
u.save()
|
||||
set_object_ownership(request.user, u)
|
||||
events.raiseEvent("DomainAdminCreated", u)
|
||||
messages.info(request, _("Domain admin added"), fail_silently=True)
|
||||
return ajax_response(request, url=reverse(admin.views.permissions))
|
||||
|
||||
@@ -588,7 +598,7 @@ def domain_admin_promotion(request):
|
||||
form=form)
|
||||
|
||||
@login_required
|
||||
@permission_required("admin.view_permissions")
|
||||
@permission_required("auth.view_permissions")
|
||||
@check_domain_ownership
|
||||
def search_account(request):
|
||||
search = request.POST.get("search", None)
|
||||
@@ -687,15 +697,11 @@ def saveextensions(request):
|
||||
parts = k.split("_", 1)
|
||||
dbext = Extension.objects.get(name=parts[1])
|
||||
if not dbext in actived_exts:
|
||||
dbext.enabled = True
|
||||
dbext.save()
|
||||
dbext.on()
|
||||
else:
|
||||
found += [dbext]
|
||||
for ext in actived_exts:
|
||||
if not ext in found:
|
||||
ext.enabled = False
|
||||
ext.save()
|
||||
ext.off()
|
||||
messages.info(request, _("Modifications applied."), fail_silently=True)
|
||||
return HttpResponseRedirect(reverse(admin.views.viewextensions))
|
||||
|
||||
Reference in New Issue
Block a user