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:
Antoine Nguyen
2012-01-24 11:23:31 +01:00
parent 3681bb4198
commit 1c51e97179
18 changed files with 300 additions and 88 deletions
+2 -2
View File
@@ -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
View File
@@ -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
View File
@@ -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):
+8 -8
View File
@@ -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")
+4 -2
View File
@@ -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
View File
@@ -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))