wip: resellers and limits

I've finally started to rethink the database structure. The goal I'm trying to reach: only use User objects to manage permissions and accesses to the webui and associate other objects to them if needed (like mailboxes).

see #105
This commit is contained in:
Antoine Nguyen
2012-01-20 17:33:05 +01:00
parent 2a0e9fdf3a
commit 3681bb4198
41 changed files with 1311 additions and 478 deletions
+2 -2
View File
@@ -51,13 +51,13 @@ parameters.register_admin("DEFAULT_TOP_REDIRECTION", type="list", deflt="admin",
values=enabled_applications(),
help=_("The default redirection used when no application is specified"))
def unset_default_topredirection(**kwargs):
def unset_default_topredirection(extension):
"""
Simple callback to change the default redirection if the
corresponding extension is being disabled.
"""
topredirection = parameters.get_admin("DEFAULT_TOP_REDIRECTION", app="general")
if topredirection == kwargs["ext"].name:
if topredirection == extension.name:
parameters.save_admin("DEFAULT_TOP_REDIRECTION", "userprefs", app="general")
def update_available_applications(extension):
+1 -1
View File
@@ -1 +1 @@
[{"pk": 1, "model": "auth.group", "fields": {"name": "DomainAdmins", "permissions": [["add_mailbox", "admin", "mailbox"], ["change_mailbox", "admin", "mailbox"], ["delete_mailbox", "admin", "mailbox"], ["add_alias", "admin", "alias"], ["change_alias", "admin", "alias"], ["delete_alias", "admin", "alias"], ["view_domain", "admin", "domain"], ["view_mailboxes", "admin", "mailbox"], ["view_aliases", "admin", "alias"], ["view_domaliases", "admin", "domainalias"], ["add_domainalias", "admin", "domainalias"], ["change_domainalias", "admin", "domainalias"], ["delete_domainalias", "admin", "domainalias"]]}}]
[{"pk": 1, "model": "auth.group", "fields": {"name": "DomainAdmins", "permissions": [["add_mailbox", "admin", "mailbox"], ["change_mailbox", "admin", "mailbox"], ["delete_mailbox", "admin", "mailbox"], ["add_alias", "admin", "alias"], ["change_alias", "admin", "alias"], ["delete_alias", "admin", "alias"], ["view_domain", "admin", "domain"], ["view_mailboxes", "admin", "mailbox"], ["view_aliases", "admin", "alias"], ["view_domaliases", "admin", "domainalias"], ["add_domainalias", "admin", "domainalias"], ["change_domainalias", "admin", "domainalias"], ["delete_domainalias", "admin", "domainalias"]]}},{"pk": 2, "model": "auth.group", "fields": {"name": "SimpleUsers", "permissions": []}}]
+112 -48
View File
@@ -7,6 +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
class DomainForm(forms.ModelForm):
class Meta:
@@ -32,10 +33,10 @@ class ProxyForm(forms.ModelForm):
def __init__(self, user, *args, **kwargs):
super(ProxyForm, self).__init__(*args, **kwargs)
if user.is_superuser:
self._domain = None
if not user.is_superuser:
self._domains = Domain.objects.filter(owners__user=user)
else:
self._domain = user.mailbox_set.all()[0].domain
self._domains = None
class DomainAliasForm(ProxyForm):
class Meta:
@@ -44,8 +45,9 @@ class DomainAliasForm(ProxyForm):
def __init__(self, *args, **kwargs):
super(DomainAliasForm, self).__init__(*args, **kwargs)
if self._domain is not None:
self.fields["target"].queryset = Domain.objects.filter(pk=self._domain.id)
if self._domains is not None:
#self.fields["target"].queryset = Domain.objects.filter(pk=self._domain.id)
self.fields["target"].queryset = self._domains
class MailboxForm(ProxyForm):
quota = forms.IntegerField(label=ugettext_noop("Quota"), required=False,
@@ -68,9 +70,10 @@ class MailboxForm(ProxyForm):
super(MailboxForm, self).__init__(*args, **kwargs)
for f in ['name', 'address', 'quota', 'password1', 'password2']:
self.fields[f].widget.attrs['size'] = 14
if self._domain is not None:
self.fields["domain"].queryset = Domain.objects.filter(pk=self._domain.id)
self.fields["domain"].initial = self._domain
if self._domains is not None:
self.fields["domain"].queryset = self._domains
#self.fields["domain"].queryset = Domain.objects.filter(pk=self._domain.id)
#self.fields["domain"].initial = self._domain
if kwargs.has_key("instance"):
self.fields['quota'].initial = kwargs["instance"].quota
@@ -111,22 +114,26 @@ class AliasForm(ProxyForm):
# member of Alias, we need to fill it manually. if
# self._domain exists, it means a DomainAdmin is going to
# create a new alias, we know how to fill the fields.
if self._domain is not None:
self.fields["domain"].queryset = Domain.objects.filter(pk=self._domain.id)
# if self._domain is not None:
# self.fields["domain"].queryset = Domain.objects.filter(pk=self._domain.id)
if self._domains is not None:
self.fields["domain"].queryset = self._domains
# if 'instance' exists, it means we are modifying an existing
# record, we need to select the right value for 'domain' and
# to fill the first "targets" input (because it is created by the
# Form object, not by us).
domain = None
if self._domain:
domain = self._domain
#domain = None
# if self._domain:
# domain = self._domain
if kwargs.has_key("instance"):
if self._domain is None:
domain = kwargs["instance"].domain
# if self._domain is None:
# domain = kwargs["instance"].domain
self.fields["targets"].initial = kwargs["instance"].first_target()
if domain is not None:
self.fields["domain"].initial = domain
# if domain is not None:
# self.fields["domain"].initial = domain
self.fields.keyOrder = ['address', 'domain', 'enabled', 'targets']
@@ -140,33 +147,37 @@ class AliasForm(ProxyForm):
return self.cleaned_data["address"]
def set_targets(self, user, values):
"""Targets dispatching
We make a difference between 'local' targets (the ones hosted
by Modoboa) and 'external targets.
"""
from modoboa.admin.lib import is_object_owner
self.ext_targets = []
self.int_targets = []
if user.id != 1:
umb = Mailbox.objects.get(user=user)
for addr in values:
if addr == "":
continue
local_part, domain = split_mailbox(addr)
if domain is None:
local_part, domname = split_mailbox(addr)
if domname is None:
raise AdminError("%s %s" % (_("Invalid mailbox"), addr))
if is_domain_admin(user) and umb.domain.name != domain:
try:
d = Domain.objects.get(name=domain)
except Domain.DoesNotExist:
pass
else:
raise AdminError("%s %s" % (_("Access denied for"), addr))
try:
mb = Mailbox.objects.get(address=local_part, domain__name=domain)
except Mailbox.DoesNotExist:
self.ext_targets += [addr]
else:
if not user.is_superuser:
usermb = Mailbox.objects.get(user=user.id)
if usermb.domain.id != mb.domain.id:
raise AdminError("%s %s" % (_("Permission denied on"), addr))
domain = Domain.objects.get(name=domname)
except Domain.DoesNotExist:
domain = None
if domain:
if not is_object_owner(user, domain):
raise PermDeniedException(addr)
try:
mb = Mailbox.objects.get(domain=domain, address=local_part)
except Mailbox.DoesNotExist:
raise AdminError(_("Mailbox %s does not exist" % addr))
self.int_targets += [mb]
continue
self.ext_targets += [addr]
def save(self, force_insert=False, force_update=False, commit=True):
a = super(AliasForm, self).save(commit=False)
@@ -186,19 +197,72 @@ class SuperAdminForm(forms.Form):
self.fields["user"].queryset = User.objects.exclude(pk__in=[1, user.id])
class DomainAdminForm(forms.Form):
def __init__(self, *args, **kwargs):
super(DomainAdminForm, self).__init__(*args, **kwargs)
createfrom = forms.ChoiceField(
choices=[("new", ugettext_noop("Create a new account")),
("select", ugettext_noop("Promote an existing account"))],
widget=forms.RadioSelect)
self.fields["domain"] = \
forms.ModelChoiceField(queryset=Domain.objects.all(),
label=_("Domain"),
required=True,
empty_label=_("Select a domain"),
help_text=_("Select a domain in the list"))
self.fields["user"] = \
forms.ChoiceField(label=_("User"), required=True,
choices=[("", _("Empty"))],
help_text=_("Select a user in the list"))
domain = forms.ModelChoiceField(
queryset=Domain.objects.all(),
label=_("Domain"),
required=True,
empty_label=ugettext_noop("Select a domain"),
help_text=ugettext_noop("Select a domain in the list")
)
user = forms.ChoiceField(
label=ugettext_noop("User"), required=True,
choices=[("", ugettext_noop("Empty"))],
help_text=ugettext_noop("Select a user in the list"))
class UserForm(forms.ModelForm):
createmb = forms.BooleanField(
label=ugettext_noop("Create mailbox"), required=False,
help_text=ugettext_noop("Create a mailbox for this user using the given address")
)
class Meta:
model = User
fields = ("username", "first_name", "last_name", "email")
def save(self, commit=True, group=None):
user = super(UserForm, self).save(commit=False)
if self.cleaned_data.has_key("password1"):
user.set_password(self.cleaned_data["password1"])
if commit:
user.save()
if group:
user.groups.add(Group.objects.get(name=group))
user.save()
return user
class UserWithPasswordForm(UserForm):
password1 = forms.CharField(label=_("Password"), widget=forms.PasswordInput)
password2 = forms.CharField(
label=ugettext_noop("Confirmation"),
widget=forms.PasswordInput,
help_text=ugettext_noop("Enter the same password as above, for verification.")
)
def clean_password2(self):
password1 = self.cleaned_data.get("password1", "")
password2 = self.cleaned_data["password2"]
if password1 != password2:
raise forms.ValidationError(_("The two password fields didn't match."))
return password2
class DomainAdminPromotionForm(forms.Form):
name = forms.CharField(
label=ugettext_noop("Name"),
help_text=ugettext_noop("The username/email address/name of the account to promote")
)
def clean_name(self):
try:
u = User.objects.get(username=self.cleaned_data["name"])
except User.DoesNotExist:
raise forms.ValidationError(_("Unknown user"))
return self.cleaned_data["name"]
class ImportDataForm(forms.Form):
sourcefile = forms.FileField(label=_("Select a file"))
+112
View File
@@ -1,12 +1,21 @@
# coding: utf-8
from functools import wraps
from django.http import HttpResponse, HttpResponseRedirect
from django.contrib.auth.models import User, Group
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):
@@ -46,6 +55,7 @@ def good_domain(f):
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
@@ -67,6 +77,18 @@ 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
@@ -103,3 +125,93 @@ def render_listing(request, objtype, tplname="admin/listing.html",
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, ...)
"""
obj_owner = ObjectOwner(user=user, content_object=obj)
obj_owner.save()
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.
:param user: a ``User`` object
:param obj: a admin object
:return: a boolean
"""
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:
return True
return False
def check_domain_ownership(f):
@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()
qs = user.objectowner_set.filter(content_type__app_label="admin",
content_type__model="domain")
domains = []
for entry in qs.all():
domains += [entry.content_object]
return domains
def get_user_domaliases(user):
if user.is_superuser:
return DomainAlias.objects.all()
qs = user.objectowner_set.filter(content_type__app_label="admin",
content_type__model="domain")
domaliases = []
for entry in qs.all():
domaliases += entry.content_object.domainalias_set.all()
return domaliases
def get_user_mailboxes(user):
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)
return mboxes
def get_user_mbaliases(user):
if user.is_superuser:
return Alias.objects.all()
qs = user.objectowner_set.filter(content_type__app_label="admin",
content_type__model="domain")
mbaliases = []
for ro in qs.all():
mbaliases += Alias.objects.filter(domain=ro.content_object)
return mbaliases
@@ -0,0 +1,125 @@
# encoding: utf-8
import datetime
from south.db import db
from south.v2 import SchemaMigration
from django.db import models
class Migration(SchemaMigration):
def forwards(self, orm):
# Adding model 'ObjectOwner'
db.create_table('admin_objectowner', (
('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
('user', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['auth.User'])),
('content_type', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['contenttypes.ContentType'])),
('object_id', self.gf('django.db.models.fields.PositiveIntegerField')()),
))
db.send_create_signal('admin', ['ObjectOwner'])
def backwards(self, orm):
# Deleting model 'ObjectOwner'
db.delete_table('admin_objectowner')
models = {
'admin.alias': {
'Meta': {'object_name': 'Alias'},
'address': ('django.db.models.fields.CharField', [], {'max_length': '254'}),
'dates': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['admin.ObjectDates']"}),
'domain': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['admin.Domain']"}),
'enabled': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'extmboxes': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'mboxes': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['admin.Mailbox']", 'symmetrical': 'False'})
},
'admin.domain': {
'Meta': {'object_name': 'Domain'},
'dates': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['admin.ObjectDates']"}),
'enabled': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '100'}),
'quota': ('django.db.models.fields.IntegerField', [], {})
},
'admin.domainalias': {
'Meta': {'object_name': 'DomainAlias'},
'dates': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['admin.ObjectDates']"}),
'enabled': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '100'}),
'target': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['admin.Domain']"})
},
'admin.extension': {
'Meta': {'object_name': 'Extension'},
'enabled': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '150'})
},
'admin.mailbox': {
'Meta': {'object_name': 'Mailbox'},
'address': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'dates': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['admin.ObjectDates']"}),
'domain': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['admin.Domain']"}),
'gid': ('django.db.models.fields.IntegerField', [], {}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'password': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'path': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'quota': ('django.db.models.fields.IntegerField', [], {}),
'uid': ('django.db.models.fields.IntegerField', [], {}),
'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"})
},
'admin.objectdates': {
'Meta': {'object_name': 'ObjectDates'},
'creation': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'last_modification': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'})
},
'admin.objectowner': {
'Meta': {'object_name': 'ObjectOwner'},
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'object_id': ('django.db.models.fields.PositiveIntegerField', [], {}),
'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"})
},
'auth.group': {
'Meta': {'object_name': 'Group'},
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
},
'auth.permission': {
'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
},
'auth.user': {
'Meta': {'object_name': 'User'},
'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
},
'contenttypes.contenttype': {
'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
}
}
complete_apps = ['admin']
+23 -18
View File
@@ -1,31 +1,28 @@
# coding: utf-8
from django.db import models, IntegrityError
from django.contrib.auth.models import User
from django.contrib.contenttypes.models import ContentType
from django.contrib.contenttypes import generic
from django.contrib.auth.models import User, Group
from django.utils.translation import ugettext as _, ugettext_noop
from django.conf import settings
from modoboa.lib import parameters, events
from modoboa.lib.sysutils import exec_cmd, exec_as_vuser
from modoboa.lib.emailutils import split_mailbox
from modoboa.lib.ldaputils import *
from modoboa.auth.lib import crypt_password
import os
import pwd
class AdminError(Exception):
"""Custom exception
try:
from modoboa.lib.ldaputils import *
ldap_available = True
except ImportError:
ldap_available = False
"""
def __init__(self, value):
"""Constructor
:param value: the information contained in this exception.
"""
self.value = str(value)
def __str__(self):
"""String representation"""
return self.value
class ObjectOwner(models.Model):
user = models.ForeignKey(User)
content_type = models.ForeignKey(ContentType)
object_id = models.PositiveIntegerField()
content_object = generic.GenericForeignKey('content_type', 'object_id')
class ObjectDates(models.Model):
"""Dates recording for admin objects
@@ -80,6 +77,7 @@ class Domain(DatesAware):
quota = models.IntegerField(help_text=ugettext_noop("Default quota in MB applied to mailboxes"))
enabled = models.BooleanField(ugettext_noop('enabled'),
help_text=ugettext_noop("Check to activate this domain"))
owners = generic.GenericRelation(ObjectOwner)
class Meta:
permissions = (
@@ -265,6 +263,10 @@ class Mailbox(DatesAware):
raise AdminError(_("Mailbox with this address already exists"))
self.user = user
if len(user.groups.all()) == 0:
user.groups.add(Group.objects.get(name="SimpleUsers"))
user.save()
if kwargs.has_key("password") and kwargs["password"] != u"é":
self.password = crypt_password(kwargs["password"])
# If we ever had numerical uid, don't resolve it
@@ -305,6 +307,8 @@ class Mailbox(DatesAware):
self.password = crypt_password(newvalue)
self.save()
return
if not ldap_available:
raise AdminError(_("Failed to update password: LDAP module not installed"))
ab = LDAPAuthBackend()
try:
@@ -456,11 +460,12 @@ class Alias(DatesAware):
return "<br/>".join(self.extmboxes.split(","))
def ui_disabled(self, user):
from modoboa.admin.lib import is_object_owner
if user.is_superuser:
return False
usermb = Mailbox.objects.get(user=user.id)
for mb in self.mboxes.all():
if mb.domain.id != usermb.domain.id:
if not is_object_owner(user, mb.domain):
return True
return False
+35 -38
View File
@@ -7,11 +7,15 @@ from modoboa.lib.webutils import _render, getctx, _render_to_string
from modoboa.lib import events
from forms import *
from tables import SuperAdminsTable, DomainAdminsTable
from lib import set_object_ownership
class Permissions(object):
submit_label = ugettext_noop("Add")
formtpl = 'admin/add_permission.html'
@staticmethod
def is_authorized(user):
return True
return user.is_superuser
def get_add_form(self, request):
raise NotImplementedError
@@ -25,21 +29,20 @@ class Permissions(object):
def get(self, request):
raise NotImplementedError
def _render_form(self, request, form, instring=False):
ctx = dict(form=form, title=self.title, submit_label=self.submit_label,
role=self.role)
if instring:
return _render_to_string(request, self.formtpl, ctx)
return _render(request, self.formtpl, ctx)
class SuperAdminsPerms(Permissions):
title = ugettext_noop("Add super administrator")
role = "super_admins"
@staticmethod
def is_authorized(user):
return user.is_superuser
def get_add_form(self, request):
form = SuperAdminForm(request.user)
return _render(request, 'admin/add_permission.html', {
"form" : form,
"title" : _(self.title),
"role": self.role
})
return self._render_form(request, form)
def add(self, request):
form = SuperAdminForm(request.user, request.POST)
@@ -51,11 +54,7 @@ class SuperAdminsPerms(Permissions):
user.save()
return True, None
content = _render_to_string(request, "admin/add_permission.html", {
"form" : form,
"title" : _(self.title),
"role" : self.role
})
content = self._render_form(request, form, True)
return False, getctx("ko", content=content)
def delete(self, selection):
@@ -81,22 +80,19 @@ class SuperAdminsPerms(Permissions):
class DomainAdminsPerms(Permissions):
title = ugettext_noop("Add domain administrator")
role = "domain_admins"
formtpl = 'admin/add_domain_admin.html'
@staticmethod
def is_authorized(user):
return user.has_perm("admin.view_domains")
def get_add_form(self, request):
form = DomainAdminForm()
return _render(request, 'admin/add_domain_admin.html', {
"form" : form,
"title" : _(self.title),
"role" : self.role
})
return self._render_form(request, form)
def add(self, request):
form = DomainAdminForm(request.POST)
if request.POST.has_key("user") and request.POST["user"] != "":
if request.POST.get("user", "") != "":
mboxid = request.POST['user']
mb = Mailbox.objects.get(pk=mboxid)
form.fields["user"].choices = [(mboxid, mb),]
@@ -105,27 +101,28 @@ class DomainAdminsPerms(Permissions):
mb.user.date_joined = datetime.datetime.now()
mb.user.groups.add(Group.objects.get(name="DomainAdmins"))
mb.user.save()
set_object_ownership(mb.user, mb.domain)
return True, None
content = _render_to_string(request, "admin/add_domain_admin.html", {
"form" : form,
"title" : _(self.title),
"role" : self.role
})
content = self._render_form(request, form, True)
return False, getctx("ko", content=content)
def delete(self, selection):
for s in selection:
mbox = Mailbox.objects.get(pk=int(s))
grp = Group.objects.get(name="DomainAdmins")
mbox.user.groups.remove(grp)
mbox.user.save()
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()
def get(self, request):
domadmins = Mailbox.objects.filter(user__groups__name="DomainAdmins")
domadmins_list = []
for admin in domadmins:
domadmins_list += [admin.tohash()]
return DomainAdminsTable(request, domadmins_list).render()
#domadmins = Mailbox.objects.filter(user__groups__name="DomainAdmins")
domainadmins = User.objects.filter(groups__name="DomainAdmins")
# domadmins_list = []
# for admin in domadmins:
# domadmins_list += [admin.tohash()]
return DomainAdminsTable(request, domainadmins).render()
def get_perms_class(user, role):
"""Retrieves the class associated to a given role
@@ -150,7 +147,7 @@ def get_perms_class(user, role):
if globals()[cname].is_authorized(user):
return globals()[cname]
return None
res = events.raiseQueryEvent("PermsGetClass", role=role)
res = events.raiseQueryEvent("PermsGetClass", role)
if not len(res):
return None
return res[0]
+16 -5
View File
@@ -142,11 +142,22 @@ class SuperAdminsTable(tables.Table):
class DomainAdminsTable(tables.Table):
idkey = "id"
selection = tables.SelectionColumn("selection", width="4%", first=True)
domain = tables.Column("domain", label=ugettext_noop("Domain"))
full_name = tables.Column("full_name", label=ugettext_noop("Full name"))
selection = tables.SelectionColumn("selection", width="4%")
username = tables.Column("username", label=ugettext_noop("User name"))
first_name = tables.Column("first_name", label=ugettext_noop("First name"))
last_name = tables.Column("last_name", label=ugettext_noop("Last name"))
date_joined = tables.Column("date_joined", label=ugettext_noop("Defined"))
enabled = tables.Column("enabled", label=gender("Enabled", "m"), width="10%")
enabled = tables.Column("is_active", label=gender("Enabled", "m"), width="10%")
actions = tables.ActionColumn("actions", label=ugettext_noop("Actions"),
width="50px", align="center",
defvalue=domain_admin_actions)
cols_order = ["selection", "domain", "full_name", "date_joined", "enabled"]
cols_order = ["selection", "username", "first_name",
"last_name", "date_joined", "enabled", "actions"]
def __init__(self, request, users):
super(DomainAdminsTable, self).__init__(request)
self.populate(self._rows_from_model(users))
def parse_is_active(self, value):
return _("yes") if value else _("no")
+17
View File
@@ -199,6 +199,18 @@ def mailbox_actions(user, mboxid):
]
return render_actions(actions)
@register.simple_tag
def domain_admin_actions(user, daid):
actions = [
{"name" : "editdomainadmin",
"url" : reverse(admin.views.edit_domain_admin, args=[daid]),
"img" : static_url("pics/edit.png"),
"title" : _("Edit domain admin"),
"class" : "boxed",
"rel" : "{handler:'iframe',size:{x:350,y:320}}"},
]
return render_actions(actions)
@register.simple_tag
def mbalias_actions(user, aliasid):
from modoboa.admin.models import Alias
@@ -274,3 +286,8 @@ def gender(value, target):
if trans.find("_") == -1:
return trans
return value
@register.simple_tag
def get_footer_content(user, objtype):
res = events.raiseQueryEvent("AdminFooterDisplay", user, objtype)
return "".join(res)
+41 -36
View File
@@ -1,38 +1,43 @@
from django.conf.urls.defaults import *
urlpatterns = patterns('modoboa.admin.views',
url(r'^$', 'domains', name="index"),
url(r'^domains/$', 'domains', name="domains"),
(r'^domains/new/', 'newdomain'),
(r'^domains/(?P<dom_id>\d+)/edit/$', 'editdomain'),
(r'^domains/delete/$', 'deldomain'),
(r'^domains/import/$', 'importdata'),
(r'^domaliases/$', "domaliases"),
(r'^domaliases/new/', 'newdomalias'),
(r'^domaliases/edit/(?P<alias_id>\d+)/', 'editdomalias'),
(r'^domaliases/delete/$', 'deldomalias'),
(r'^mailboxes/$', 'mailboxes'),
(r'^mailboxes/new/$', 'newmailbox'),
(r'^mailboxes/edit/(?P<mbox_id>\d+)/$', 'editmailbox'),
(r'^mailboxes/delete/$', 'delmailbox'),
(r'^mailboxes/search/$', 'mailboxes_search'),
(r'^mbaliases/$', 'mbaliases'),
(r'^mbaliases/new/$', 'newmbalias'),
(r'^mbaliases/edit/(?P<alias_id>\d+)/$', 'editmbalias'),
(r'^mbaliases/delete/$', 'delmbalias'),
(r'^domains/(?P<dom_id>\d+)/rawmboxes/$', 'mailboxes_raw'),
(r'^settings/$', 'viewparameters'),
(r'^settings/parameters/$', 'viewparameters'),
(r'^settings/parameters/save/$', 'saveparameters'),
(r'^settings/extensions/$', 'viewextensions'),
(r'^settings/extensions/save/$', 'saveextensions'),
(r'^permissions/$', 'permissions'),
(r'^permissions/add/$', 'add_permission'),
(r'^permissions/delete/$', 'delete_permissions'),
)
urlpatterns = patterns(
'modoboa.admin.views',
url(r'^$', 'domains', name="index"),
url(r'^domains/$', 'domains', name="domains"),
(r'^domains/new/', 'newdomain'),
(r'^domains/(?P<dom_id>\d+)/edit/$', 'editdomain'),
(r'^domains/delete/$', 'deldomain'),
(r'^domains/import/$', 'importdata'),
(r'^domaliases/$', "domaliases"),
(r'^domaliases/new/', 'newdomalias'),
(r'^domaliases/edit/(?P<alias_id>\d+)/', 'editdomalias'),
(r'^domaliases/delete/$', 'deldomalias'),
(r'^mailboxes/$', 'mailboxes'),
(r'^mailboxes/new/$', 'newmailbox'),
(r'^mailboxes/edit/(?P<mbox_id>\d+)/$', 'editmailbox'),
(r'^mailboxes/delete/$', 'delmailbox'),
(r'^mailboxes/search/$', 'mailboxes_search'),
(r'^mbaliases/$', 'mbaliases'),
(r'^mbaliases/new/$', 'newmbalias'),
(r'^mbaliases/edit/(?P<alias_id>\d+)/$', 'editmbalias'),
(r'^mbaliases/delete/$', 'delmbalias'),
(r'^domains/(?P<dom_id>\d+)/rawmboxes/$', 'mailboxes_raw'),
(r'^settings/$', 'viewparameters'),
(r'^settings/parameters/$', 'viewparameters'),
(r'^settings/parameters/save/$', 'saveparameters'),
(r'^settings/extensions/$', 'viewextensions'),
(r'^settings/extensions/save/$', 'saveextensions'),
(r'^permissions/$', 'permissions'),
(r'^permissions/add/$', 'add_permission'),
(r'^permissions/domainadmin/new/$', 'create_domain_admin'),
(r'^permissions/domainadmin/edit/(?P<da_id>\d+)/$', 'edit_domain_admin'),
(r'^permissions/domainadmin/promote/$', 'domain_admin_promotion'),
(r'^permissions/searchaccount/$', 'search_account'),
(r'^permissions/delete/$', 'delete_permissions'),
)
+182 -72
View File
@@ -16,6 +16,7 @@ from models import *
from modoboa.admin.permissions import *
from modoboa.admin.tables import *
from modoboa.lib import events, parameters
from modoboa.lib.exceptions import *
from modoboa.auth.lib import crypt_password
from modoboa.lib.webutils \
import _render, ajax_response, ajax_simple_response, getctx
@@ -31,10 +32,7 @@ def domains(request):
return HttpResponseRedirect(reverse(userprefs.views.preferences))
if request.user.is_superuser:
domains = Domain.objects.all()
else:
domains = events.raiseQueryEvent("GetUserDomains", request.user)
domains = get_user_domains(request.user)
for dom in domains:
dom.mbalias_counter = len(Alias.objects.filter(domain=dom.id))
deloptions = {"keepdir" : _("Do not delete domain directory")}
@@ -53,7 +51,6 @@ def _validate_domain(request, form, successmsg, commonctx,
"""
error = None
if form.is_valid():
events.raiseEvent("CanCreateDomain", request.user)
try:
domain = form.save()
except AdminError, e:
@@ -74,7 +71,9 @@ def _validate_domain(request, form, successmsg, commonctx,
@permission_required("admin.add_domain")
@transaction.commit_on_success
def newdomain(request, tplname="admin/adminform.html"):
events.raiseEvent("CanCreateDomain", request.user)
def newdomain_cb(user, domain):
set_object_ownership(user, domain)
events.raiseEvent("CreateDomain", user, domain)
commonctx = {"title" : _("New domain"),
@@ -94,6 +93,9 @@ def newdomain(request, tplname="admin/adminform.html"):
@permission_required("admin.change_domain")
def editdomain(request, dom_id, tplname="admin/adminform.html"):
domain = Domain.objects.get(pk=dom_id)
if not is_object_owner(request.user, domain):
raise PermDeniedException(_("You can't edit this domain"))
commonctx = {"title" : _("Domain editing"),
"submit_label" : _("Update"),
"action" : reverse(editdomain, args=[dom_id]),
@@ -108,47 +110,47 @@ def editdomain(request, dom_id, tplname="admin/adminform.html"):
@login_required
@permission_required("admin.delete_domain")
@transaction.commit_on_success
def deldomain(request):
selection = request.GET["selection"].split(",")
error = None
keepdir = True if request.GET.get("keepdir", "false") == "true" else False
try:
mb = Mailbox.objects.get(user__id=request.user.id)
except Mailbox.DoesNotExist:
pass
else:
if str(mb.domain.id) in selection:
error = _("You can't delete your own domain")
mb = None
if error is None:
if request.GET.has_key("keepdir") and request.GET["keepdir"] == "true":
keepdir = True
else:
keepdir = False
for dom in Domain.objects.filter(id__in=selection):
events.raiseEvent("DeleteDomain", dom)
dom.delete(keepdir=keepdir)
msg = ungettext("Domain deleted", "Domains deleted", len(selection))
messages.info(request, msg, fail_silently=True)
return ajax_response(request)
return ajax_response(request, status="ko", respmsg=error)
for dom in Domain.objects.filter(id__in=selection):
if not is_object_owner(request.user, dom):
raise PermDeniedException(_("You can't delete this domain"))
if mb and mb.domain == dom:
raise AdminError(_("You can't delete your own domain"))
events.raiseEvent("DeleteDomain", request.user, dom)
dom.delete(keepdir=keepdir)
msg = ungettext("Domain deleted", "Domains deleted", len(selection))
messages.info(request, msg, fail_silently=True)
return ajax_response(request)
@login_required
@permission_required("admin.view_domaliases")
@check_domain_ownership
def domaliases(request):
if request.GET.has_key("domid"):
domain = Domain.objects.get(pk=request.GET["domid"])
domaliases = DomainAlias.objects.filter(target=request.GET["domid"])
else:
domain = None
domaliases = DomainAlias.objects.all()
domaliases = get_user_domaliases(request.user)
return render_listing(request, "domaliases",
title=_("Domain aliases"),
rel="310 190",
objects=domaliases)
@login_required
@good_domain
@permission_required("admin.add_domainalias")
@transaction.commit_on_success
def newdomalias(request, tplname="admin/adminform.html"):
commonctx = {"title" : _("New domain alias"),
"submit_label" : _("Create"),
@@ -158,6 +160,7 @@ 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")
@@ -167,6 +170,7 @@ def newdomalias(request, tplname="admin/adminform.html"):
except IntegrityError:
error = _("Alias with this name already exists")
else:
events.raiseEvent("DomainAliasCreated", request.user, domalias)
messages.info(request, _("Domain alias created"), fail_silently=True)
return ajax_response(request, url=reverse(admin.views.domaliases))
@@ -181,10 +185,12 @@ def newdomalias(request, tplname="admin/adminform.html"):
return _render(request, tplname, commonctx)
@login_required
@good_domain
@permission_required("admin.change_domainalias")
def editdomalias(request, alias_id, tplname="admin/adminform.html"):
domalias = DomainAlias.objects.get(pk=alias_id)
if not is_object_owner(request.user, domalias.target):
raise PermDeniedException(_("You do not have access to this domain"))
commonctx = {"title" : _("Domain alias editing"),
"submit_label" : _("Update"),
"action" : reverse(editdomalias, args=[alias_id]),
@@ -213,18 +219,23 @@ def editdomalias(request, alias_id, tplname="admin/adminform.html"):
return _render(request, tplname, commonctx)
@login_required
@good_domain
@permission_required("admin.delete_domainalias")
@transaction.commit_on_success
def deldomalias(request):
selection = request.GET["selection"].split(",")
DomainAlias.objects.filter(id__in=selection).delete()
for daid in selection:
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)
domalias.delete()
msg = ungettext("Domain alias deleted", "Domain aliases deleted", len(selection))
messages.info(request, msg, fail_silently=True)
return ajax_response(request)
@login_required
@good_domain
@permission_required("admin.view_mailboxes")
@check_domain_ownership
def mailboxes(request):
title = _("Available mailboxes")
if request.GET.has_key("domid"):
@@ -232,7 +243,7 @@ def mailboxes(request):
mboxes = Mailbox.objects.filter(domain=domain.id)
title += " (%s)" % domain.name
else:
mboxes = Mailbox.objects.all()
mboxes = get_user_mailboxes(request.user)
domain = None
deloptions = {"keepdir" : _("Do not delete mailbox directory")}
return render_listing(request, "mailboxes",
@@ -255,8 +266,8 @@ def mailboxes_raw(request, dom_id=None):
})
@login_required
@good_domain
@permission_required("admin.view_mailboxes")
@check_domain_ownership
def mailboxes_search(request):
if request.method != "POST":
return
@@ -266,13 +277,15 @@ def mailboxes_search(request):
query &= Q(domain__name__startswith=domain)
if request.GET.has_key("domid"):
query = Q(domain=request.GET["domid"]) & query
elif not request.user.is_superuser:
query = Q(domain__in=get_user_domains(request.user)) & query
mboxes = Mailbox.objects.filter(query)
result = map(lambda mb: mb.full_address, mboxes)
return ajax_simple_response(result)
@login_required
@good_domain
@permission_required("admin.add_mailbox")
@transaction.commit_on_success
def newmailbox(request, tplname="admin/adminform.html"):
commonctx = {"title" : _("New mailbox"),
"submit_label" : _("Create"),
@@ -282,12 +295,17 @@ def newmailbox(request, tplname="admin/adminform.html"):
form = MailboxForm(request.user, request.POST)
error = None
if form.is_valid():
if not request.user.is_superuser:
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:
events.raiseEvent("CreateMailbox", mb)
events.raiseEvent("CreateMailbox", request.user, mb)
messages.info(request, _("Mailbox created"),
fail_silently=True)
return ajax_response(request, url=reverse(admin.views.mailboxes) + "?domid=%d" % mb.domain.id)
@@ -301,10 +319,12 @@ def newmailbox(request, tplname="admin/adminform.html"):
return _render(request, tplname, commonctx)
@login_required
@good_domain
@permission_required("admin.change_mailbox")
def editmailbox(request, mbox_id=None, tplname="admin/adminform.html"):
mb = Mailbox.objects.get(pk=mbox_id)
if not is_object_owner(request.user, mb.domain):
raise PermDeniedException(_("You do not have access to this domain"))
commonctx = {"title" : _("Mailbox editing"),
"submit_label" : _("Create"),
"action" : reverse(editmailbox, args=[mbox_id]),
@@ -336,66 +356,63 @@ def editmailbox(request, mbox_id=None, tplname="admin/adminform.html"):
return _render(request, tplname, commonctx)
@login_required
@good_domain
@permission_required("admin.delete_mailbox")
@transaction.commit_on_success
def delmailbox(request):
selection = request.GET["selection"].split(",")
error = None
if not request.user.is_superuser:
mb = Mailbox.objects.get(user__pk=request.user.id)
if str(mb.id) in selection:
error = _("You can't delete your own mailbox")
keepdir = True if request.GET.get("keepdir", "false") == "true" else False
usermb = request.user.mailbox_set.all()[0] \
if len(request.user.mailbox_set.all()) else None
if error is None:
if request.GET.has_key("keepdir") and request.GET["keepdir"] == "true":
keepdir = True
else:
keepdir = False
for mb in Mailbox.objects.filter(id__in=selection):
events.raiseEvent("DeleteMailbox", mb)
mb.delete(keepdir=keepdir)
msg = ungettext("Mailbox deleted", "Mailboxes deleted", len(selection))
messages.info(request, msg, fail_silently=True)
return ajax_response(request)
for mb in Mailbox.objects.filter(id__in=selection):
if not is_object_owner(request.user, mb.domain):
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)
mb.delete(keepdir=keepdir)
return ajax_response(request, status="ko", error=error)
msg = ungettext("Mailbox deleted", "Mailboxes deleted", len(selection))
messages.info(request, msg, fail_silently=True)
return ajax_response(request)
@login_required
@good_domain
@permission_required("admin.view_aliases")
@check_domain_ownership
def mbaliases(request):
if request.GET.has_key("domid"):
aliases = Alias.objects.filter(domain=request.GET["domid"])
elif request.GET.has_key("mbid"):
aliases = Alias.objects.filter(mboxes__id=request.GET["mbid"]).distinct()
else:
aliases = Alias.objects.all()
aliases = get_user_mbaliases(request.user)
return render_listing(request, "mbaliases",
title=_("Mailbox aliases"),
rel="320 300",
objects=aliases)
def _validate_mbalias(request, form, successmsg, tplname, commonctx):
def _validate_mbalias(request, form, successmsg, tplname, commonctx, callback=None):
"""Mailbox alias validation
Common function shared between creation and modification actions.
"""
error = None
if form.is_valid():
if not request.POST.has_key("targets"):
raise AdminError(_("No target defined"))
form.set_targets(request.user, request.POST.getlist("targets"))
try:
if not request.POST.has_key("targets"):
raise AdminError(_("No target defined"))
form.set_targets(request.user, request.POST.getlist("targets"))
try:
alias = form.save()
except IntegrityError:
raise AdminError(_("Alias with this name already exists"))
alias = form.save()
except IntegrityError:
raise AdminError(_("Alias with this name already exists"))
messages.info(request, successmsg, fail_silently=True)
return ajax_response(request, url=reverse(admin.views.mbaliases))
except AdminError, e:
error = str(e)
if callback:
callback(request.user, alias)
messages.info(request, successmsg, fail_silently=True)
return ajax_response(request, url=reverse(admin.views.mbaliases))
if request.POST.has_key("targets"):
targets = request.POST.getlist("targets")
@@ -406,17 +423,21 @@ def _validate_mbalias(request, form, successmsg, tplname, commonctx):
return ajax_response(request, status="ko", template=tplname, **commonctx)
@login_required
@good_domain
@permission_required("admin.add_alias")
@transaction.commit_on_success
def newmbalias(request, tplname="admin/mbaliasform.html"):
events.raiseEvent("CanCreateMailboxAlias", request.user)
commonctx = {"title" : _("New mailbox alias"),
"submit_label" : _("Create"),
"action" : reverse(newmbalias),
"formid" : "mbaliasform"}
if request.method == "POST":
def callback(user, mbalias):
events.raiseEvent("MailboxAliasCreated", user, mbalias)
form = AliasForm(request.user, request.POST)
return _validate_mbalias(request, form, _("Mailbox alias created"),
tplname, commonctx)
tplname, commonctx, callback)
form = AliasForm(request.user)
commonctx["form"] = form
@@ -424,10 +445,11 @@ def newmbalias(request, tplname="admin/mbaliasform.html"):
return _render(request, tplname, commonctx)
@login_required
@good_domain
@permission_required("admin.change_alias")
def editmbalias(request, alias_id, tplname="admin/mbaliasform.html"):
alias = Alias.objects.get(pk=alias_id)
if not is_object_owner(request.user, alias.domain):
raise PermDeniedException(_("You do not have access to this domain"))
commonctx = {"title" : _("Mailbox alias editing"),
"submit_label" : _("Update"),
"action" : reverse(editmbalias, args=[alias.id]),
@@ -443,11 +465,17 @@ def editmbalias(request, alias_id, tplname="admin/mbaliasform.html"):
return _render(request, tplname, commonctx)
@login_required
@good_domain
@permission_required("admin.delete_alias")
@transaction.commit_on_success
def delmbalias(request):
selection = request.GET["selection"].split(",")
Alias.objects.filter(id__in=selection).delete()
for mbaid in selection:
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)
mbalias.delete()
msg = ungettext("Alias deleted", "Aliases deleted", len(selection))
messages.info(request, msg, fail_silently=True)
return ajax_response(request)
@@ -464,12 +492,12 @@ def permissions(request, tplname='admin/permissions.html'):
"content" : SuperAdminsPerms().get(request)}
]
permtables += events.raiseQueryEvent("PermsGetTables", user=request.user)
permtables += events.raiseQueryEvent("PermsGetTables", request)
permtables += [
{"id" : "domain_admins",
"title" : _("Domain administrators"),
"rel" : "310 180",
"rel" : "350 250",
"content" : DomainAdminsPerms().get(request)}
]
@@ -492,10 +520,92 @@ def add_permission(request):
status, data = pobj.add(request)
if not status:
return HttpResponse(simplejson.dumps(data), mimetype="application/json")
messages.info(request, _("Permission added."),
fail_silently=True)
try:
msg = pobj.add_success_msg
except AttributeError:
msg = _("Permission added")
messages.info(request, msg, fail_silently=True)
return ajax_response(request, url=reverse(admin.views.permissions))
@login_required
@permission_required("auth.view_permissions")
def create_domain_admin(request, tplname="admin/create_domain_admin.html"):
if request.method == "POST":
form = UserWithPasswordForm(request.POST)
if form.is_valid():
user = form.save(group="DomainAdmins")
events.raiseEvent("DomainAdminCreated", user)
messages.info(request, _("Domain admin created"), fail_silently=True)
return ajax_response(request, url=reverse(admin.views.permissions))
return ajax_response(request, status="ko", template=tplname, form=form)
form = UserWithPasswordForm()
return ajax_response(request, tplname, form=form)
@login_required
@permission_required("auth.view_permissions")
def edit_domain_admin(request, da_id, tplname="admin/adminform.html"):
ctx = dict(title=_("Edit domain admin"),
action=reverse(edit_domain_admin, args=[da_id]),
formid="eda_form",
submit_label=_("Update"))
u = User.objects.get(pk=da_id, groups__name="DomainAdmins")
if request.method == "POST":
if request.POST.get("password1", "") != "" and \
request.POST.get("password2", "") != "":
form = UserWithPasswordForm(request.POST, instance=u)
else:
form = UserForm(request.POST, instance=u)
if form.is_valid():
form.save()
if form.cleaned_data["createmb"]:
pass
messages.info(request, _("Domain admin updated"), fail_silently=True)
return ajax_response(request,
url=reverse("modoboa.admin.views.permissions"))
ctx.update(form=form)
return ajax_response(request, status="ko", template=tplname, **ctx)
form = UserWithPasswordForm(instance=u)
ctx.update(form=form)
return _render(request, tplname, ctx)
@login_required
@permission_required("auth.view_permissions")
def domain_admin_promotion(request):
if request.method == "POST":
form = DomainAdminPromotionForm(request.POST)
if form.is_valid():
u = User.objects.get(username=form.cleaned_data["name"])
u.groups.add(Group.objects.get(name="DomainAdmins"))
u.save()
messages.info(request, _("Domain admin added"), fail_silently=True)
return ajax_response(request, url=reverse(admin.views.permissions))
form = DomainAdminPromotionForm()
return ajax_response(request, template="admin/domain_admin_promotion.html",
form=form)
@login_required
@permission_required("admin.view_permissions")
@check_domain_ownership
def search_account(request):
search = request.POST.get("search", None)
if search is None:
return ajax_simple_response([])
query = Q(username__startswith=search)
query &= Q(is_superuser=False)
if not request.user.has_perm("admin.add_domain"):
query &= Q(groups__name="SimpleUsers")
else:
query &= ~Q(groups__name__in=["DomainAdmins"])
users = User.objects.filter(query)
result = map(lambda u: u.username, users.all())
return ajax_simple_response(result)
@login_required
def delete_permissions(request):
pclass = get_perms_class(request.user, request.GET["role"])
+65 -25
View File
@@ -1,15 +1,19 @@
# coding: utf-8
"""
:mod:`controls` --- provides event handlers to check if limits are reached
--------------------------------------------------------------------------
:mod:`controls` --- provides event handlers that check if limits are reached
----------------------------------------------------------------------------
"""
from modoboa.admin.models import Mailbox
from modoboa.lib import events
from lib import *
from models import *
def check_domains_limit(user):
"""Check if user has reached its limit for domains creation
def check_limit(user, lname):
"""Check if a user has reached a defined limit
We check if the user will not reach the given limit after this
creation.
Raise a ``LimitReached`` exception if the limit is reached.
@@ -17,28 +21,64 @@ def check_domains_limit(user):
"""
if not is_reseller(user):
return
if not user.limitspool.inc_curvalue('domains_limit'):
raise LimitReached(user.limitspool.get_limit('domains_limit'))
try:
if user.limitspool.will_be_reached(lname):
raise LimitReached(user.limitspool.get_limit(lname))
except LimitsPool.DoesNotExist:
pass
def associate_domain_to_reseller(user, domain):
if not is_reseller(user):
return
ro = ResellerObject(user=user, content_object=domain)
ro.save()
def inc_limit(user, lname):
try:
user.limitspool.inc_curvalue(lname)
except LimitsPool.DoesNotExist:
pass
def get_reseller_domains(user):
qs = user.resellerobject_set.filter(content_type__app_label="admin",
content_type__model="domain")
domains = []
for ro in qs.all():
domains += [ro.content_object]
return domains
def dec_limit(user, lname):
try:
user.limitspool.dec_curvalue(lname)
except LimitsPool.DoesNotExist:
pass
def check_domaliases_limit(user, domalias):
pass
@events.observe('CanCreateDomain')
def check_domains_limit(user):
check_limit(user, 'domains_limit')
def check_mboxes_limit(user, mbox):
pass
@events.observe('CreateDomain')
def inc_nb_domains(user, domain):
inc_limit(user, 'domains_limit')
def check_mbaliases_limit(user, mbalias):
pass
@events.observe('DeleteDomain')
def dec_nb_domains(user, domain):
dec_limit(user, 'domains_limit')
@events.observe('CanCreateDomainAlias')
def check_domaliases_limit(user):
check_limit(user, 'domain_aliases_limit')
@events.observe('DomainAliasCreated')
def inc_nb_domaliases(user, domalias):
inc_limit(user, 'domain_aliases_limit')
@events.observe('DomainAliasDeleted')
def dec_nb_domaliases(user, domainalias):
dec_limit(user, 'domain_aliases_limit')
@events.observe('CanCreateMailbox')
def check_mboxes_limit(user):
check_limit(user, 'mailboxes_limit')
@events.observe('CreateMailbox')
def inc_nb_mailboxes(user, mailbox):
inc_limit(user, 'mailboxes_limit')
@events.observe('DeleteMailbox')
def dec_nb_mailboxes(user, mailbox):
dec_limit(user, 'mailboxes_limit')
@events.observe('CanCreateMailboxAlias')
def check_mbaliases_limit(user):
check_limit(user, 'mailbox_aliases_limit')
@events.observe('MailboxAliasCreated')
def inc_nb_mbaliases(user, mailboxalias):
inc_limit(user, 'mailbox_aliases_limit')
+1 -1
View File
@@ -1 +1 @@
[{"pk": 2, "model": "auth.group", "fields": {"name": "Resellers", "permissions": [["add_domain", "admin", "domain"], ["change_domain", "admin", "domain"], ["delete_domain", "admin", "domain"], ["view_domains", "admin", "domain"], ["add_mailbox", "admin", "mailbox"], ["change_mailbox", "admin", "mailbox"], ["delete_mailbox", "admin", "mailbox"], ["add_alias", "admin", "alias"], ["change_alias", "admin", "alias"], ["delete_alias", "admin", "alias"], ["view_domain", "admin", "domain"], ["view_mailboxes", "admin", "mailbox"], ["view_aliases", "admin", "alias"], ["view_domaliases", "admin", "domainalias"], ["add_domainalias", "admin", "domainalias"], ["change_domainalias", "admin", "domainalias"], ["delete_domainalias", "admin", "domainalias"]]}}]
[{"pk": 3, "model": "auth.group", "fields": {"name": "Resellers", "permissions": [["add_domain", "admin", "domain"], ["change_domain", "admin", "domain"], ["delete_domain", "admin", "domain"], ["view_domains", "admin", "domain"], ["add_mailbox", "admin", "mailbox"], ["change_mailbox", "admin", "mailbox"], ["delete_mailbox", "admin", "mailbox"], ["add_alias", "admin", "alias"], ["change_alias", "admin", "alias"], ["delete_alias", "admin", "alias"], ["view_domain", "admin", "domain"], ["view_mailboxes", "admin", "mailbox"], ["view_aliases", "admin", "alias"], ["view_domaliases", "admin", "domainalias"], ["add_domainalias", "admin", "domainalias"], ["change_domainalias", "admin", "domainalias"], ["delete_domainalias", "admin", "domainalias"], ["view_permissions", "auth", "permission"]]}}]
+27 -25
View File
@@ -3,28 +3,23 @@
from django import forms
from django.utils.translation import ugettext_noop as _
from django.contrib.auth.models import User, Group
from modoboa.admin.forms import UserForm, UserWithPasswordForm
from models import *
class ResellerForm(forms.ModelForm):
class Meta:
model = User
fields = ("username", "first_name", "last_name", "email")
class ResellerForm(UserForm):
def __init__(self, *args, **kwargs):
super(ResellerForm, self).__init__(*args, **kwargs)
del self.fields["createmb"]
def save(self, commit=True):
user = super(ResellerForm, self).save(commit=False)
if self.cleaned_data.has_key("password1"):
user.set_password(self.cleaned_data["password1"])
def save(self, commit=True, group=None):
user = super(ResellerForm, self).save(commit, group)
if commit:
user.save()
grp = Group.objects.get(name="Resellers")
if not grp in user.groups.all():
user.groups.add(grp)
user.save()
try:
pool = user.limitspool
except LimitsPool.DoesNotExist:
pool = LimitsPool()
pool.user = user
print pool.user
pool.save()
for lname in reseller_limits_tpl:
@@ -35,18 +30,8 @@ class ResellerForm(forms.ModelForm):
return user
class ResellerWithPasswordForm(ResellerForm):
password1 = forms.CharField(label=_("Password"), widget=forms.PasswordInput)
password2 = forms.CharField(label=_("Confirmation"),
widget=forms.PasswordInput,
help_text = _("Enter the same password as above, for verification."))
def clean_password2(self):
password1 = self.cleaned_data.get("password1", "")
password2 = self.cleaned_data["password2"]
if password1 != password2:
raise forms.ValidationError(_("The two password fields didn't match."))
return password2
class ResellerWithPasswordForm(ResellerForm, UserWithPasswordForm):
pass
class ResellerPoolForm(forms.Form):
domains_limit = forms.IntegerField(ugettext_noop("Max domains"),
@@ -58,6 +43,23 @@ class ResellerPoolForm(forms.Form):
mailbox_aliases_limit = forms.IntegerField(ugettext_noop("Max mailbox aliases"),
help_text=ugettext_noop("Maximum number of mailbox aliases that can be created by this user"))
def check_limit_value(self, lname):
if self.cleaned_data[lname] < -1:
raise forms.ValidationError(_("Invalid limit"))
return self.cleaned_data[lname]
def clean_domains_limit(self):
return self.check_limit_value("domains_limit")
def clean_domain_aliases_limit(self):
return self.check_limit_value("domain_aliases_limit")
def clean_mailboxes_limit(self):
return self.check_limit_value("mailboxes_limit")
def clean_mailbox_aliases_limit(self):
return self.check_limit_value("mailbox_aliases_limit")
def load_from_user(self, user):
for l in reseller_limits_tpl:
self.fields[l].initial = user.limitspool.getmaxvalue(l)
+5 -2
View File
@@ -4,16 +4,19 @@
-------------------------------
"""
from django.contrib.auth.models import Group
from modoboa.lib.exceptions import ModoboaException
class LimitReached(Exception):
class LimitReached(ModoboaException):
def __init__(self, limit):
self.limit = limit
def __str__(self):
return "%s reached" % self.limit.name
class BadLimitValue(ModoboaException):
pass
def is_reseller(user):
"""Tell if a user is a reseller or not
+24 -20
View File
@@ -11,18 +11,17 @@ from django.core.urlresolvers import reverse
from django.conf.urls.defaults import include
from modoboa.lib import events, parameters
from modoboa.lib.webutils import static_url
from controls import *
from models import *
import permissions
import controls
baseurl = "limits"
def init():
events.register("AdminMenuDisplay", menu)
events.register("CanCreateDomain", check_domains_limit)
events.register("CreateDomain", associate_domain_to_reseller)
events.register("GetUserDomains", get_reseller_domains)
pass
def destroy():
events.unregister("AdminMenuDisplay", menu)
pass
def infos():
return {
@@ -32,20 +31,25 @@ def infos():
"url" : baseurl
}
def menu(target, user):
import views
if target != "top_menu":
return []
if not user.is_superuser:
return []
return [
{"name" : "resellers",
"label" : _("Resellers"),
"url" : reverse(views.resellers),
"img" : static_url("pics/resellers.png")}
]
def urls(prefix):
return (r'^%s%s/' % (prefix, baseurl),
include('modoboa.extensions.limits.urls'))
@events.observe('AdminFooterDisplay')
def display_pool_usage(user, objtype):
if objtype == "domaliases":
objtype = "domain_aliases"
elif objtype == "mbaliases":
objtype = "mailbox_aliases"
try:
l = user.limitspool.get_limit('%s_limit' % objtype)
except LimitsPool.DoesNotExist:
return []
if l.maxvalue == -2:
label = _("undefined")
elif l.maxvalue == -1:
label = _("unlimited")
else:
label = str(l.maxvalue)
return [_("Pool usage: %s / %s") % (l.curvalue, label)]
+23 -13
View File
@@ -17,32 +17,42 @@ class LimitsPool(models.Model):
user = models.OneToOneField(User)
def getcurvalue(self, lname):
l = Limit.objects.get(name=lname)
l = self.limit_set.get(name=lname)
return l.curvalue
def getmaxvalue(self, lname):
l = Limit.objects.get(name=lname)
l = self.limit_set.get(name=lname)
return l.maxvalue
def inc_curvalue(self, lname, nb=1):
l = Limit.objects.get(name=lname)
if l.curvalue + nb > l.maxvalue:
return False
l = self.limit_set.get(name=lname)
l.curvalue += nb
l.save()
return True
def dec_curvalue(self, lname, nb=1):
l = self.limit_set.get(name=lname)
if not l.curvalue:
return
l.curvalue -= nb
l.save()
def will_be_reached(self, lname, nb=1):
l = self.limit_set.get(name=lname)
if l.maxvalue <= -1:
return False
if l.curvalue + nb > l.maxvalue:
return True
return False
def get_limit(self, lname):
return Limit.objects.get(name=lname)
try:
return self.limit_set.get(name=lname)
except Limit.DoesNotExist:
return None
class Limit(models.Model):
name = models.CharField(max_length=255)
curvalue = models.IntegerField(default=0)
maxvalue = models.IntegerField(default=0)
maxvalue = models.IntegerField(default=-2)
pool = models.ForeignKey(LimitsPool)
class ResellerObject(models.Model):
user = models.ForeignKey(User)
content_type = models.ForeignKey(ContentType)
object_id = models.PositiveIntegerField()
content_object = generic.GenericForeignKey('content_type', 'object_id')
+50
View File
@@ -0,0 +1,50 @@
# coding: utf-8
from django.utils.translation import ugettext as _, ugettext_noop
from django.contrib.auth.models import User
from modoboa.admin.permissions import Permissions
from modoboa.lib import events
from modoboa.lib.webutils import _render, _render_to_string
from tables import *
from forms import *
class ResellersPerms(Permissions):
title = ugettext_noop("New reseller")
role = "resellers"
add_success_msg = ugettext_noop("Reseller added")
def get_add_form(self, request):
form = ResellerWithPasswordForm()
return self._render_form(request, form)
def add(self, request):
form = ResellerWithPasswordForm(request.POST)
error = None
if form.is_valid():
form.save(group="Resellers")
return True, None
content = self._render_form(request, form, True)
return False, dict(status="ko", content=content)
def delete(self, selection):
User.objects.filter(id__in=selection).delete()
def get(self, request):
resellers = User.objects.filter(groups__name="Resellers")
return ResellersTable(request, resellers).render()
@events.observe('PermsGetTables')
def get_resellers_table(request):
if not request.user.is_superuser:
return []
return [{"id" : "resellers",
"title" : _("Resellers"),
"rel" : "350 300",
"content" : ResellersPerms().get(request)}]
@events.observe("PermsGetClass")
def get_perms_class(role):
print role
if role != "resellers":
return []
return [ResellersPerms]
+2 -1
View File
@@ -8,6 +8,7 @@ class ResellersTable(tables.Table):
tableid = "resellers_table"
idkey = "id"
selection = tables.SelectionColumn("selection", width="4%")
fname = tables.Column("first_name", label=ugettext_noop("First name"))
lname = tables.Column("last_name", label=ugettext_noop("Last name"))
username = tables.Column("username", label=ugettext_noop("Username"))
@@ -16,7 +17,7 @@ class ResellersTable(tables.Table):
width="50px",
align="center", defvalue=reseller_actions)
cols_order = ["username", "fname", "lname", "created", "actions"]
cols_order = ["selection", "username", "fname", "lname", "created", "actions"]
def __init__(self, request, users):
super(ResellersTable, self).__init__(request)
+7 -1
View File
@@ -2,11 +2,17 @@
{% load i18n %}
{% block html_head %}
<script src="{{ MEDIA_URL }}js/form.js" type="text/javascript"></script>
<script type="text/javascript">
window.addEvent("domready", function() {
var form = new Form();
});
</script>
{% endblock %}
{% block content %}
<div class="title"><h1>{% trans "Limits pool for" %} {{ reseller.username }}</h1></div>
{% if error %}<div class="error">{{ error }}</div>{% endif %}
<form action="" method="POST" id="limits_pool_form" name="limits_pool_form">
<form action="{% url extensions.limits.views.edit_limits_pool reseller.id %}"
method="POST" id="limits_pool_form" name="limits_pool_form">
{% include "common/genericform.html" %}
<div class="spacer"></div>
<div class="lastrow" align="center">
@@ -5,7 +5,7 @@ from django.utils.translation import ugettext as _
from django.template.loader import render_to_string
from django.core.urlresolvers import reverse
from modoboa.lib.webutils import static_url, render_actions
from modoboa.extensions import limits
from modoboa.extensions.limits import views
register = template.Library()
@@ -16,11 +16,11 @@ def resellers_menu(user):
"img" : static_url("pics/add.png"),
"class" : "boxed",
"rel" : "{handler:'iframe',size:{x:350,y:300}}",
"url" : reverse(limits.views.new_reseller)},
"url" : reverse(views.new_reseller)},
{"name" : "remove",
"label" : _("Remove"),
"img" : static_url("pics/remove.png"),
"url" : reverse(limits.views.delete_resellers)
"url" : reverse(views.delete_resellers)
}]
return render_to_string("common/menulist.html",
{"entries" : entries, "user" : user})
@@ -29,13 +29,13 @@ def resellers_menu(user):
def reseller_actions(user, resid):
actions = [
{"name" : "editreseller",
"url" : reverse(limits.views.edit_reseller, args=[resid]),
"url" : reverse(views.edit_reseller, args=[resid]),
"img" : static_url("pics/edit.png"),
"title" : _("Edit reseller"),
"class" : "boxed",
"rel" : "{handler:'iframe',size:{x:350,y:300}}"},
{"name" : "editlimits",
"url" : reverse(limits.views.edit_limits_pool, args=[resid]),
"url" : reverse(views.edit_limits_pool, args=[resid]),
"img" : static_url("pics/settings.png"),
"title" : _("Edit limits allocated to this reseller"),
"class" : "boxed",
-3
View File
@@ -2,9 +2,6 @@ from django.conf.urls.defaults import *
urlpatterns = patterns(
'modoboa.extensions.limits.views',
(r'^$', 'resellers'),
(r'reseller/new/$', 'new_reseller'),
(r'reseller/edit/(?P<resid>\d+)/$', 'edit_reseller'),
(r'reseller/delete/$', 'delete_resellers'),
(r'pool/edit/(?P<resid>\d+)/$', 'edit_limits_pool'),
)
+21 -66
View File
@@ -1,103 +1,58 @@
# coding: utf-8
from django.utils.translation import ugettext as _, ungettext
from django.utils.translation import ugettext as _
from django.db import IntegrityError
from django.contrib.auth.models import *
from django.contrib import messages
from django.contrib.auth.decorators \
import login_required, user_passes_test
from django.core.urlresolvers import reverse
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
from tables import *
from forms import *
from modoboa.lib import parameters
from modoboa.lib.webutils import _render, ajax_response
@login_required
@user_passes_test(lambda u: u.is_superuser)
def resellers(request, tplname="limits/resellers.html"):
users = User.objects.filter(groups__name="Resellers")
paginator = Paginator(users,
int(parameters.get_admin("ITEMS_PER_PAGE", app="admin")))
page = int(request.GET.get("page", "1"))
try:
subset = paginator.page(page)
except (EmptyPage, PageNotAnInteger):
subset = paginator.page(paginator.num_pages)
ctx = dict(table=ResellersTable(request, subset.object_list),
page=subset,
paginator=paginator)
return _render(request, tplname, ctx)
def validate_reseller(request, form, action, tplname="limits/reseller.html"):
error = None
if form.is_valid():
try:
form.save()
except IntegrityError:
error = _("User with this Username already exists")
else:
msg = _("Reseller created") if action == "create" \
else _("Reseller updated")
messages.info(request, msg, fail_silently=True)
return ajax_response(request, url=reverse(resellers))
submit_label = _("Create") if action == "create" else _("Update")
ctx = dict(form=form, error=error, submit_label=submit_label)
return ajax_response(request, status="ko", template=tplname, **ctx)
@login_required
@user_passes_test(lambda u: u.is_superuser)
def new_reseller(request, tplname="limits/reseller.html"):
if request.method == "POST":
form = ResellerWithPasswordForm(request.POST)
return validate_reseller(request, form, "create")
form = ResellerWithPasswordForm()
ctx = dict(form=form, submit_label=_("Create"))
return _render(request, tplname, ctx)
@login_required
@user_passes_test(lambda u: u.is_superuser)
def edit_reseller(request, resid, tplname="limits/reseller.html"):
def edit_reseller(request, resid, tplname="admin/add_permission.html"):
reseller = User.objects.get(pk=resid)
ctx = dict(title=_("Edit reseller"), submit_label=_("Update"),
addurl=reverse(edit_reseller, args=[resid]))
if request.method == "POST":
if request.POST.get("password1", "") != "" and \
request.POST.get("password2", "") != "":
form = ResellerWithPasswordForm(request.POST, instance=reseller)
else:
form = ResellerForm(request.POST, instance=reseller)
return validate_reseller(request, form, action="update")
if form.is_valid():
try:
form.save()
except IntegrityError:
error = _("User with this Username already exists")
else:
messages.info(request, _("Reseller updated"), fail_silently=True)
return ajax_response(request,
url=reverse("modoboa.admin.views.permissions"))
ctx.update(form=form, error=error)
return ajax_response(request, status="ko", template=tplname, **ctx)
form = ResellerWithPasswordForm(instance=reseller)
ctx = dict(form=form, submit_label=_("Update"))
ctx.update(form=form)
return _render(request, tplname, ctx)
@login_required
@user_passes_test(lambda u: u.is_superuser)
def delete_resellers(request):
selection = map(lambda idx: int(idx), request.GET.get("selection", "").split(","))
if not len(selection):
raise Exception("")
User.objects.filter(id__in=selection).delete()
msg = ungettext("Reselled deleted", "Resellers deleted", len(selection))
messages.info(request, msg, fail_silently=True)
return ajax_response(request)
@login_required
@user_passes_test(lambda u: u.is_superuser)
def edit_limits_pool(request, resid, tplname="limits/pool.html"):
reseller = User.objects.get(pk=resid)
ctx = dict(reseller=reseller)
if request.method == "POST":
form = ResellerPoolForm(request.POST)
if form.is_valid():
form.save_new_limits(reseller.limitspool)
messages.info(request, _("Pool updated"), fail_silently=True)
return ajax_response(request, url=reverse(resellers))
ctx = dict(form=form)
return ajax_response(request, url=reverse("modoboa.admin.views.permissions"))
ctx.update(form=form)
return ajax_response(request, status="ko", template=tplname, **ctx)
form = ResellerPoolForm()
form.load_from_user(reseller)
ctx = dict(form=form, reseller=reseller)
ctx.update(form=form)
return _render(request, tplname, ctx)
+61 -3
View File
@@ -8,17 +8,30 @@ This module provides a simple way of managing events between Modoboa
core application and additional components.
"""
from functools import wraps
import inspect
import re
events = [
"CanCreateDomain",
"CreateDomain",
"DeleteDomain",
"CanCreateDomainAlias",
"DomainAliasCreated",
"DomainAliasDeleted",
"CanCreateMailbox",
"CreateMailbox",
"DeleteMailbox",
"ModifyMailbox",
"GetUserDomains",
"CanCreateMailboxAlias",
"MailboxAliasCreated",
"MailboxAliasDeleted",
"DomainAdminCreated",
"PasswordChange",
"UserMenuDisplay",
@@ -33,7 +46,9 @@ events = [
"UserLogin",
"UserLogout",
"GetAnnouncement"
"GetAnnouncement",
"AdminFooterDisplay",
]
callbacks = {}
@@ -54,6 +69,49 @@ def register(event, callback):
callbacks[event].append(callback)
return 1
class observe(object):
"""Event observing decorator
Automatically register the decorated function to observe the given
event. If the decorated function is located into an extension, we
check before each call if the extension is enabled or not. If
that's not the case, the callback is not called.
.. note::
That's not a really optimized behaviour but I haven't found
another solution to achieve that feature.
:param evtname: the event's name
"""
def __init__(self, evtname):
self.evtname = evtname
def __guess_extension_name(self, modname):
if modname.startswith('modoboa.extensions'):
m = re.match(r'modoboa\.extensions\.([^\.]+)', modname)
if m:
return m.group(1)
return None
def __call__(self, f):
modname = inspect.getmodule(inspect.stack()[1][0]).__name__
extname = self.__guess_extension_name(modname)
@wraps(f)
def wrapped_f(*args):
if extname:
from modoboa.admin.models import Extension
try:
ext = Extension.objects.get(name=extname)
except Extension.DoesNotExist:
return []
if not ext.enabled:
return []
return f(*args)
register(self.evtname, wrapped_f)
return wrapped_f
def unregister(event, callback):
"""Unregister a callback for a specific event
+18
View File
@@ -0,0 +1,18 @@
# coding: utf-8
"""
:mod:`exceptions` --- Custom Modoboa exceptions
-----------------------------------------------
"""
from django.utils.translation import ugettext as _
class ModoboaException(Exception):
pass
class PermDeniedException(ModoboaException):
def __init__(self, msg):
self.msg = msg
def __str__(self):
return _("Permission denied: %s" % self.msg)
+1
View File
@@ -33,6 +33,7 @@ Extracted from `this blog
one instance to another.
"""
import sys
import ldap
import re
import base64
+13
View File
@@ -3,6 +3,8 @@
import re
from django.http import Http404, HttpResponseRedirect
from modoboa.admin.models import Extension
from modoboa.lib.webutils import _render_error, ajax_response
from modoboa.lib.exceptions import ModoboaException
class ExtControlMiddleware(object):
def process_view(self, request, view, args, kwargs):
@@ -22,3 +24,14 @@ class AjaxLoginRedirect(object):
if type(response) == HttpResponseRedirect:
response.status_code = 278
return response
class CommonExceptionCatcher(object):
def process_exception(self, request, exception):
if not isinstance(exception, ModoboaException):
return None
if not request.is_ajax():
return _render_error(request, errortpl="error_simple",
user_context=dict(error=str(exception)))
return ajax_response(request, status="ko", respmsg=str(exception))
+26 -1
View File
@@ -26,7 +26,7 @@ input {
}
input[type=text], input[type=password] {
width: 140px
width: 140px;
}
/*input:hover {
@@ -102,6 +102,10 @@ td.even {
font-weight: bold;
}
.center {
text-align: center;
}
#id_username {
width: 120px;
}
@@ -139,6 +143,15 @@ td.even {
clear: both;
}
ul {
list-style: none;
padding: 0;
}
li {
display: inline;
}
/*
* clearfix related
*/
@@ -164,3 +177,15 @@ html[xmlns] .clearfix {
height: 1%;
overflow: visible;
}
/*
* Domain admin form
*/
#createfrom {
margin: 10px auto;
width: auto;
}
#permform > #id_name {
width: 180px;
}
+6 -1
View File
@@ -39,9 +39,14 @@ div#footer {
font-size: 1em;
}
#adminfo {
float: left;
padding: 5px;
}
#navbar {
float: right;
padding: 0;
padding: 5px;
margin: 0;
text-align: right;
}
+2 -5
View File
@@ -66,7 +66,7 @@ var Confirmation = new Class({
}
params += item.get("name") + "=true";
});
new Request.JSON({
new Request.JSON.mdb({
method: "get",
url: Confirmation.url,
onSuccess: function(res) {
@@ -76,10 +76,7 @@ var Confirmation = new Class({
infobox.error(res.respmsg);
infobox.hide(2);
}
},
onFailure: function(xhr) {
$(parent.document.body).set("html", xhr.responseText);
}
}
}).send(params);
},
+194 -56
View File
@@ -1,3 +1,133 @@
var Form = new Class({
Implements: [Options],
options: {
resptarget: null
},
callbacks: {
error: {},
success: {},
extra_data: {}
},
initialize: function(options) {
this.setOptions(options);
$(document.body)
.removeEvents("submit")
.addEvent("submit", this.submit.bind(this));
this.init_help_messages();
},
init_help_messages: function() {
$$("input[type!=submit]", "select").addEvents({
focus: function(evt) {
var helpdiv = $(evt.target.name + "_helptext");
if (helpdiv) {
var coords = evt.target.getCoordinates();
helpdiv.setPosition({x: 10, y: coords['top'] + coords['height'] + 5});
helpdiv.setStyle("display", "block");
}
},
blur: function(evt) {
var helpdiv = $(evt.target.name + "_helptext");
if (helpdiv) {
helpdiv.setStyle("display", "none");
}
}
});
},
register_callback: function(category, name, callback) {
if (this.callbacks[category][name] == undefined) {
return false;
}
this.callbacks[category][name] = callback;
return true;
},
register_error_callback: function(name, callback) {
return this.register_callback("error", name, callback);
},
register_success_callback: function(name, callback) {
return this.register_callback("success", name, callback);
},
register_extra_data_callback: function(name, callback) {
return this.register_callback("extra_data", name, callback);
},
submit: function(event) {
var form = event.target;
var myid = form.get("id");
var data = form.toQueryString();
console.log("submit");
event.stop();
if (this.callbacks["extra_data"][myid] != undefined) {
if (data != "") {
data += "&";
}
data += this.callbacks["extra_data"][myid]();
}
new Request.JSON({
url: form.get("action"),
method: form.get("method"),
data: data,
onSuccess: function(response) {
if (response.status == "ko") {
if (response.norefresh != undefined && response.norefresh == true) {
$$("div[class=error]").set("html", response.respmsg);
} else {
var target = (this.options.target)
? $(this.options.target) : $(document.body);
target.set("html", response.content);
this.init_help_messages();
/*$(document.body).addEvent("submit", ajax_submit);*/
if (this.callbacks["error"][myid] != undefined) {
this.callbacks["error"][myid]();
}
}
return;
}
if ($defined(response.respmsg)) {
if ($defined(parent.current_anchor)) {
parent.current_anchor.loading_message = response.respmsg;
parent.current_anchor.loading_color = "green";
} else {
parent.infobox.info(response.respmsg);
parent.infobox.hide(1);
}
}
if ($defined(response.ajaxnav)) {
if ($defined(response.url)) {
parent.current_anchor.baseurl(response.url, 1);
}
parent.current_anchor.update(1);
} else if ($defined(response.url)) {
parent.location.href = response.url;
}
if (this.callbacks["success"][myid] != undefined) {
this.callbacks["success"][myid](response);
}
parent.SqueezeBox.close();
}.bind(this),
onFailure: function(xhr) {
parent.document.getElement('iframe')
.setStyles({width: 800, height: 600});
$(document.body).set("html", xhr.responseText);
}
}).send();
}
});
var error_callbacks = {
permform: function() {
if ($defined($("id_domain"))) {
@@ -36,70 +166,74 @@ function register_extra_data_callback(name, callback) {
}
function ajax_submit(event) {
var form = event.target;
var myid = form.get("id");
var data = form.toQueryString();
var form = event.target;
var myid = form.get("id");
var data = form.toQueryString();
event.stop();
if ($defined(extra_data_callbacks[myid])) {
if (data != "") {
data += "&";
event.stop();
if ($defined(extra_data_callbacks[myid])) {
if (data != "") {
data += "&";
}
data += extra_data_callbacks[myid]();
}
data += extra_data_callbacks[myid]();
}
new Request({
method: form.get("method"),
data: data,
onSuccess: function(responseText){
var response = JSON.decode(responseText);
new Request.JSON({
url: form.get("action"),
method: form.get("method"),
data: data,
onSuccess: function(response) {
if (response.status == "ko") {
if (response.norefresh != undefined && response.norefresh == true) {
$$("div[class=error]").set("html", response.respmsg);
} else {
var target = (resptarget != undefined)
? $(resptarget) : $(document.body);
if (response.status == "ko") {
if ($defined(response.norefresh) && response.norefresh == true) {
$("error").set("html", response.respmsg);
} else {
$(document.body).set("html", response.content);
$(document.body).addEvent("submit", ajax_submit);
if ($defined(error_callbacks[myid])) {
error_callbacks[myid]();
}
target.set("html", response.content);
/*$(document.body).addEvent("submit", ajax_submit);*/
if ($defined(error_callbacks[myid])) {
error_callbacks[myid]();
}
}
return;
}
if ($defined(response.respmsg)) {
if ($defined(parent.current_anchor)) {
parent.current_anchor.loading_message = response.respmsg;
parent.current_anchor.loading_color = "green";
} else {
parent.infobox.info(response.respmsg);
parent.infobox.hide(1);
}
}
if ($defined(response.ajaxnav)) {
if ($defined(response.url)) {
parent.current_anchor.baseurl(response.url, 1);
}
parent.current_anchor.update(1);
} else if ($defined(response.url)) {
parent.location.href = response.url;
}
if ($defined(success_callbacks[myid])) {
success_callbacks[myid](response);
}
parent.SqueezeBox.close();
},
onFailure: function(xhr) {
parent.document.getElement('iframe').setStyles({width: 800, height: 600});
$(document.body).set("html", xhr.responseText);
}
} else {
if ($defined(response.respmsg)) {
if ($defined(parent.current_anchor)) {
parent.current_anchor.loading_message = response.respmsg;
parent.current_anchor.loading_color = "green";
} else {
parent.infobox.info(response.respmsg);
parent.infobox.hide(1);
}
}
if ($defined(response.ajaxnav)) {
if ($defined(response.url)) {
parent.current_anchor.baseurl(response.url, 1);
}
parent.current_anchor.update(1);
} else if ($defined(response.url)) {
parent.location.href = response.url;
}
if ($defined(success_callbacks[myid])) {
success_callbacks[myid](response);
}
parent.SqueezeBox.close();
}
},
onFailure: function(xhr) {
parent.document.getElement('iframe').setStyles({width: 800, height: 600});
$(document.body).set("html", xhr.responseText);
}
}).send();
}).send();
}
window.addEvent("domready", function() {
$(document.body).addEvent("submit", ajax_submit);
function initform() {
$(document.body)
.removeEvents("submit")
.addEvent("submit", ajax_submit);
$$("input[type!=submit]", "select").addEvents({
focus: function(evt) {
focus: function(evt) {
var helpdiv = $(evt.target.name + "_helptext");
if (helpdiv) {
var coords = evt.target.getCoordinates();
@@ -114,4 +248,8 @@ window.addEvent("domready", function() {
}
}
});
});
}
/*window.addEvent("domready", function() {
initform();
});*/
+4
View File
@@ -16,6 +16,10 @@ window.addEvent('domready', function() {
}
}, $('accordion'));
SqueezeBox.assign($("content").getElements('a[class=boxed]'), {
parse: 'rel'
});
$$("a[name=addperm]").addEvent("click", function(evt) {
evt.stop();
var sizes = current_sizes.split(" ");
+1
View File
@@ -25,6 +25,7 @@ Request.JSON.mdb = new Class({
Extends: Request.JSON,
options: {
format: "json",
onComplete: ajax_login_redirect,
onFailure: function(xhr) {
$(document.body).setStyle("overflow", "auto");
+51 -22
View File
@@ -1,28 +1,57 @@
{% extends "admin/add_permission.html" %}
{% extends "layout_simple.html" %}
{% load i18n %}
{% block html_head %}
{{ block.super }}
<link rel="stylesheet" type="text/css" href="{{ MEDIA_URL }}css/Autocompleter.css" />
<script src="{{ MEDIA_URL }}js/form.js" type="text/javascript"></script>
<script type="text/javascript" src="{{ MEDIA_URL }}js/Observer.js"></script>
<script type="text/javascript" src="{{ MEDIA_URL }}js/Autocompleter.js"></script>
<script type="text/javascript" src="{{ MEDIA_URL }}js/Autocompleter.Request.js"></script>
<script type="text/javascript">
window.addEvent("domready", function() {
domadminform_init = function() {
$("id_domain").addEvent("change", function(event) {
var domid = this.value;
if (domid == "") {
$("id_user").set("html", "");
return;
}
{% url domains as doms_url %}
new Request({
url: "{% url domains %}" + domid + "/rawmboxes/?target=permissions",
method: "get",
onComplete: ajax_login_redirect,
onSuccess: function(data) {
$("id_user").set("html", data);
$("id_user").options[0].setProperty("selected", "selected");
}
}).send();
});
};
domadminform_init();
var form = new Form({target: "formcontainer"});
$("createfrom").addEvent("change", function(evt) {
var url = "";
var styles = null;
var from = this.get("value");
evt.stop();
if (from == "create") {
url = "{% url admin.views.create_domain_admin %}";
styles = {height: 380};
} else {
url = "{% url admin.views.domain_admin_promotion %}";
styles = {height: 200};
}
new Request.JSON.mdb({
url: url,
onSuccess: function(response) {
$("formcontainer").set("html", response.content);
form.init_help_messages();
if (from == "promote") {
new Autocompleter.Request.JSON("id_name",
"{% url admin.views.search_account %}", {
postVar: "search",
minLength: 3,
onComplete: ajax_login_redirect
});
}
$(parent.document).getElement('iframe').setStyles(styles);
}
}).get();
});
});
</script>
{% endblock %}
{% block content %}
<div class="title"><h1>{{ title }}</h1></div>
{% if error %}<div class="error">{{ error }}</div>{% endif %}
<div class="center">
<select id="createfrom">
<option value="none">{% trans "Choose" %}</option>
<option value="create">{% trans "Create a new account" %}</option>
<option value="promote">{% trans "Promote an existing account" %}</option>
</select>
</div>
<div id="formcontainer"></div>
{% endblock %}
+8 -2
View File
@@ -2,18 +2,24 @@
{% load i18n %}
{% block html_head %}
<script src="{{ MEDIA_URL }}js/form.js" type="text/javascript"></script>
<script type="text/javascript">
window.addEvent("domready", function() {
var form = new Form();
});
</script>
{% endblock %}
{% block content %}
<div class="title"><h1>{{ title }}</h1></div>
{% if error %}<div class="error">{{ error }}</div>{% endif %}
{% if not addurl %}
{% url admin.views.add_permission as addurl %}
{% endif %}
<form action="{{ addurl }}" method="POST" id="permform" name="permform">
<input type="hidden" name="role" id="role" value="{{ role }}" />
{% include "common/genericform.html" %}
<div class="spacer"></div>
<div class="lastrow" align="center">
<input type="submit" name="submit" value="{% trans 'Add' %}" />
<input type="submit" name="submit" value="{{ submit_label }}" />
</div>
</form>
{% endblock %}
+6 -1
View File
@@ -1,10 +1,15 @@
{% extends "layout_simple.html" %}
{% block html_head %}
<script src="{{ MEDIA_URL }}js/form.js" type="text/javascript"></script>
<script type="text/javascript">
window.addEvent("domready", function() {
var form = new Form();
});
</script>
{% endblock %}
{% block content %}
<div class="title"><h1>{{ title }}</h1></div>
{% if error %}<div class="error">{{ error }}</div>{% endif %}
<div class="error">{% if error %}{{ error }}{% endif %}</div>
<form action="{{ action }}" enctype="{{ enctype }}"
method="POST" id="{{ formid }}" name="{{ formid }}">
{% include "common/genericform.html" %}
+9
View File
@@ -0,0 +1,9 @@
{% load i18n %}
<form action="{% url admin.views.create_domain_admin %}" method="POST"
id="permform" name="permform">
{% include "common/genericform.html" %}
<div class="spacer"></div>
<div class="lastrow" align="center">
<input type="submit" name="submit" value="{% trans 'Create' %}" />
</div>
</form>
@@ -0,0 +1,10 @@
{% load i18n %}
<form action="{% url admin.views.domain_admin_promotion %}" method="POST"
id="permform" name="permform">
<input type="hidden" name="role" id="role" value="{{ role }}" />
{% include "common/genericform.html" %}
<div class="spacer"></div>
<div class="lastrow" align="center">
<input type="submit" name="submit" value="{% trans 'Promote' %}" />
</div>
</form>
+2 -1
View File
@@ -1,5 +1,5 @@
{% extends "admin/domains_header.html" %}
{% load i18n %}
{% load i18n admin_extras %}
{% block middletitle %}{{ title }}{% endblock %}
{% block subcontent %}
<div id="objects" name="{{ selection }}" rel="{{ rel }}">
@@ -7,6 +7,7 @@
</div>
{% endblock %}
{% block footer %}
<div id="adminfo">{% get_footer_content user selection %}</div>
<div id="navbar">
{% if total %}
<label>{% trans "Items" %} {{ objects.start_index }} {% trans "to" %} {{ objects.end_index }} {% trans "of" %} {{ total }}</label>
+1 -2
View File
@@ -49,8 +49,7 @@ def options_menu(user):
@register.simple_tag
def uprefs_menu(user):
entries = []
entries += events.raiseQueryEvent("UserMenuDisplay",
target="uprefs_menu")
entries += events.raiseQueryEvent("UserMenuDisplay", "uprefs_menu")
return render_to_string('common/menulist.html',
{"entries" : entries, "user" : user})
+2 -2
View File
@@ -8,8 +8,8 @@ from django.utils.translation import ugettext as _
from modoboa.lib import parameters, events
from modoboa.lib.webutils import _render, _render_error, ajax_response
from forms import *
from modoboa.admin.models import Mailbox, Alias, AdminError
from modoboa.admin.lib import is_domain_admin
from modoboa.admin.models import Mailbox, Alias
from modoboa.admin.lib import is_domain_admin, AdminError
from modoboa.auth.lib import encrypt
@login_required