mirror of
https://github.com/jwinterm/modoboa.git
synced 2026-10-22 06:28:41 +02:00
wip: Resellers and limits (see #105)
* Added support for migrations * Big refactoring of the admin code done: Support for SHA256 algorithm to store passwords (fix #195) done: admin models refactoring (fix #196)
This commit is contained in:
+7
-7
@@ -22,9 +22,11 @@ parameters.register_admin("MAILBOX_TYPE", type="list", deflt="maildir",
|
||||
help=_("Mailboxes storage format"))
|
||||
parameters.register_admin("MAILDIR_ROOT", type="string", deflt=".maildir",
|
||||
help=_("Sub-directory (inside the mailbox) where messages are stored when using the maildir format"))
|
||||
parameters.register_admin("PASSWORD_SCHEME", type="list", deflt="crypt",
|
||||
values=[("crypt", "crypt"), ("md5", "md5"), ("clear", "clear")],
|
||||
help=_("Scheme used to crypt mailbox passwords"))
|
||||
parameters.register_admin(
|
||||
"PASSWORD_SCHEME", type="list", deflt="crypt",
|
||||
values=[("crypt", "crypt"), ("md5", "md5"), ("sha256", "sha256"), ("clear", "clear")],
|
||||
help=_("Scheme used to crypt mailbox passwords")
|
||||
)
|
||||
parameters.register_admin("ITEMS_PER_PAGE", type="int", deflt=30,
|
||||
help=_("Number of displayed items per page"))
|
||||
|
||||
@@ -51,6 +53,7 @@ parameters.register_admin("DEFAULT_TOP_REDIRECTION", type="list", deflt="admin",
|
||||
values=enabled_applications(),
|
||||
help=_("The default redirection used when no application is specified"))
|
||||
|
||||
@events.observe("ExtDisabled")
|
||||
def unset_default_topredirection(extension):
|
||||
"""
|
||||
Simple callback to change the default redirection if the
|
||||
@@ -60,6 +63,7 @@ def unset_default_topredirection(extension):
|
||||
if topredirection == extension.name:
|
||||
parameters.save_admin("DEFAULT_TOP_REDIRECTION", "userprefs", app="general")
|
||||
|
||||
@events.observe("ExtDisabled", "ExtEnabled")
|
||||
def update_available_applications(extension):
|
||||
"""Simple callback to update the list of available applications
|
||||
|
||||
@@ -67,7 +71,3 @@ def update_available_applications(extension):
|
||||
"""
|
||||
parameters.update_admin("DEFAULT_TOP_REDIRECTION", app="general",
|
||||
values=enabled_applications())
|
||||
|
||||
events.register("ExtDisabled", unset_default_topredirection)
|
||||
events.register("ExtDisabled", update_available_applications)
|
||||
events.register("ExtEnabled", update_available_applications)
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
# coding: utf-8
|
||||
"""
|
||||
:mod:`exceptions` --- Custom exceptions of the admin. application
|
||||
-----------------------------------------------------------------
|
||||
"""
|
||||
|
||||
from modoboa.lib.exceptions import ModoboaException
|
||||
|
||||
class AdminError(ModoboaException):
|
||||
"""
|
||||
Just a Custom exception to identify errors coming from the admin
|
||||
application.
|
||||
"""
|
||||
pass
|
||||
|
||||
@@ -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": 2, "model": "auth.group", "fields": {"name": "SimpleUsers", "permissions": []}}]
|
||||
[{"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": []}},{"pk": 1, "model": "auth.user", "fields": {"username": "admin", "first_name": "", "last_name": "", "is_active": true, "is_superuser": true, "is_staff": false, "last_login": "2012-01-26 15:19:25", "groups": [], "user_permissions": [], "password": "{CRYPT}dTTsGDkA5ZHKg", "email": "", "date_joined": "2012-01-26 15:19:25"}}]
|
||||
|
||||
+44
-31
@@ -1,13 +1,10 @@
|
||||
# coding: utf-8
|
||||
from django import forms
|
||||
from modoboa.admin.models import *
|
||||
from modoboa.admin.lib import is_domain_admin
|
||||
from django.utils.translation import ugettext as _, ugettext_noop
|
||||
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, get_user_domains_qs
|
||||
|
||||
class DomainForm(forms.ModelForm):
|
||||
class Meta:
|
||||
@@ -34,7 +31,7 @@ class ProxyForm(forms.ModelForm):
|
||||
super(ProxyForm, self).__init__(*args, **kwargs)
|
||||
|
||||
if not user.is_superuser:
|
||||
self._domains = get_user_domains_qs(user)
|
||||
self._domains = user.get_domains()
|
||||
else:
|
||||
self._domains = None
|
||||
|
||||
@@ -50,25 +47,23 @@ class DomainAliasForm(ProxyForm):
|
||||
self.fields["target"].queryset = self._domains
|
||||
|
||||
class MailboxForm(ProxyForm):
|
||||
name = forms.CharField(
|
||||
label=ugettext_noop("Name"), max_length=100,
|
||||
help_text=ugettext_noop("First name and last name of mailbox owner")
|
||||
)
|
||||
quota = forms.IntegerField(label=ugettext_noop("Quota"), required=False,
|
||||
help_text=ugettext_noop("Mailbox quota in MB (default to domain quota if blank)"))
|
||||
password1 = forms.CharField(label=ugettext_noop("Password"),
|
||||
widget=forms.PasswordInput(render_value=True),
|
||||
help_text=ugettext_noop("Password used to log in"))
|
||||
password2 = forms.CharField(label=ugettext_noop("Confirmation"),
|
||||
widget=forms.PasswordInput(render_value=True),
|
||||
help_text=ugettext_noop("Password confirmation"))
|
||||
enabled = forms.BooleanField(label=gender("Enabled", "f"), required=False,
|
||||
initial=True,
|
||||
help_text=ugettext_noop("Check to activate this mailbox"))
|
||||
|
||||
class Meta:
|
||||
model = Mailbox
|
||||
fields = ('domain', 'name', 'address')
|
||||
fields = ('domain', 'address')
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(MailboxForm, self).__init__(*args, **kwargs)
|
||||
for f in ['name', 'address', 'quota', 'password1', 'password2']:
|
||||
for f in ['name', 'address', 'quota']:
|
||||
self.fields[f].widget.attrs['size'] = 14
|
||||
if self._domains is not None:
|
||||
self.fields["domain"].queryset = self._domains
|
||||
@@ -76,25 +71,21 @@ class MailboxForm(ProxyForm):
|
||||
#self.fields["domain"].initial = self._domain
|
||||
|
||||
if kwargs.has_key("instance"):
|
||||
self.fields['quota'].initial = kwargs["instance"].quota
|
||||
self.fields['enabled'].initial = kwargs["instance"].user.is_active
|
||||
self.fields['password1'].initial = "é"
|
||||
self.fields['password2'].initial = "é"
|
||||
mb = kwargs["instance"]
|
||||
self.fields['name'].initial = unicode(mb.user)
|
||||
self.fields['quota'].initial = mb.quota
|
||||
self.fields['enabled'].initial = mb.user.is_active
|
||||
|
||||
def clean_address(self):
|
||||
if self.cleaned_data["address"].find('@') != -1:
|
||||
return self.cleaned_data["address"].rsplit("@", 1)[0]
|
||||
return self.cleaned_data["address"]
|
||||
|
||||
def clean_password2(self):
|
||||
if self.cleaned_data["password1"] != self.cleaned_data["password2"]:
|
||||
raise forms.ValidationError(_("Passwords mismatch"))
|
||||
return self.cleaned_data["password2"]
|
||||
|
||||
def commit_save(self, mb):
|
||||
mb.save(enabled=self.cleaned_data["enabled"],
|
||||
password=self.cleaned_data["password1"],
|
||||
quota=self.cleaned_data["quota"])
|
||||
def commit_save(self, mb, **kwargs):
|
||||
mb.save(name=self.cleaned_data["name"],
|
||||
enabled=self.cleaned_data["enabled"],
|
||||
quota=self.cleaned_data["quota"],
|
||||
**kwargs)
|
||||
|
||||
def save(self, force_insert=False, force_update=False, commit=True):
|
||||
m = super(MailboxForm, self).save(commit=False)
|
||||
@@ -102,6 +93,31 @@ class MailboxForm(ProxyForm):
|
||||
self.commit_save(m)
|
||||
return m
|
||||
|
||||
class MailboxWithPasswordForm(MailboxForm):
|
||||
password1 = forms.CharField(label=ugettext_noop("Password"),
|
||||
widget=forms.PasswordInput(render_value=True),
|
||||
help_text=ugettext_noop("Password used to log in"))
|
||||
password2 = forms.CharField(label=ugettext_noop("Confirmation"),
|
||||
widget=forms.PasswordInput(render_value=True),
|
||||
help_text=ugettext_noop("Password confirmation"))
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(MailboxWithPasswordForm, self).__init__(*args, **kwargs)
|
||||
for f in ['password1', 'password2']:
|
||||
self.fields[f].widget.attrs['size'] = 14
|
||||
self.fields.keyOrder = ['name', 'domain', 'address', 'quota',
|
||||
'password1', 'password2', 'enabled']
|
||||
|
||||
def clean_password2(self):
|
||||
if self.cleaned_data["password1"] != self.cleaned_data["password2"]:
|
||||
raise forms.ValidationError(_("Passwords mismatch"))
|
||||
return self.cleaned_data["password2"]
|
||||
|
||||
def commit_save(self, mb):
|
||||
super(MailboxWithPasswordForm, self).commit_save(
|
||||
mb, password=self.cleaned_data["password1"]
|
||||
)
|
||||
|
||||
class AliasForm(ProxyForm):
|
||||
targets = forms.CharField(label=ugettext_noop("Target(s)"), required=False,
|
||||
help_text=ugettext_noop("Mailbox(es) this alias will point to"))
|
||||
@@ -152,8 +168,6 @@ class AliasForm(ProxyForm):
|
||||
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 = []
|
||||
|
||||
@@ -168,7 +182,7 @@ class AliasForm(ProxyForm):
|
||||
except Domain.DoesNotExist:
|
||||
domain = None
|
||||
if domain:
|
||||
if not is_object_owner(user, domain):
|
||||
if not user.is_owner(domain):
|
||||
raise PermDeniedException(addr)
|
||||
try:
|
||||
mb = Mailbox.objects.get(domain=domain, address=local_part)
|
||||
@@ -274,9 +288,8 @@ class AssignDomainsForm(forms.Form):
|
||||
|
||||
def __init__(self, user, domadmin, *args, **kwargs):
|
||||
super(AssignDomainsForm, self).__init__(*args, **kwargs)
|
||||
self.fields["domains"].queryset = get_user_domains_qs(user)
|
||||
self.fields["domains"].initial = get_user_domains_qs(domadmin)
|
||||
|
||||
self.fields["domains"].queryset = user.get_domains()
|
||||
self.fields["domains"].initial = domadmin.get_domains()
|
||||
|
||||
class ImportDataForm(forms.Form):
|
||||
sourcefile = forms.FileField(label=_("Select a file"))
|
||||
|
||||
+2
-207
@@ -1,82 +1,12 @@
|
||||
# coding: utf-8
|
||||
from functools import wraps
|
||||
from django.http import HttpResponse, HttpResponseRedirect
|
||||
from django.contrib.auth.models import User, Group
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
|
||||
from models import *
|
||||
from modoboa.lib.exceptions import ModoboaException
|
||||
from modoboa.lib.webutils import _render_error, _render
|
||||
from modoboa.admin.tables import *
|
||||
|
||||
class AdminError(ModoboaException):
|
||||
"""
|
||||
Just a Custom exception to identify errors coming from the admin
|
||||
application.
|
||||
"""
|
||||
pass
|
||||
|
||||
def is_not_localadmin(errortpl="error"):
|
||||
def dec(f):
|
||||
def wrapped_f(request, *args, **kwargs):
|
||||
if request.user.id == 1:
|
||||
return _render_error(request, errortpl, {
|
||||
"error" : _("Invalid action, %(user)s is a local user" \
|
||||
% {"user" : request.user.username})
|
||||
})
|
||||
return f(request, *args, **kwargs)
|
||||
|
||||
wrapped_f.__name__ = f.__name__
|
||||
wrapped_f.__dict__ = f.__dict__
|
||||
wrapped_f.__doc__ = f.__doc__
|
||||
wrapped_f.__module__ = f.__module__
|
||||
return wrapped_f
|
||||
return dec
|
||||
|
||||
def is_domain_admin(user):
|
||||
"""Tell if a user is administrator for a domain of not
|
||||
|
||||
Whatever the domain, we just want to know if the given user is
|
||||
declared as a domain administrator.
|
||||
|
||||
:param user: a User object
|
||||
:return: True if the user is a domain administrator, False otherwise.
|
||||
"""
|
||||
grp = Group.objects.get(name="DomainAdmins")
|
||||
return grp in user.groups.all()
|
||||
|
||||
def good_domain(f):
|
||||
"""Custom permission decorator
|
||||
|
||||
Check if a domain administrator is accessing the good domain.
|
||||
|
||||
:param f: the original called function
|
||||
"""
|
||||
def dec(request, **kwargs):
|
||||
if request.user.is_superuser:
|
||||
return f(request, **kwargs)
|
||||
|
||||
if is_domain_admin(request.user):
|
||||
mb = Mailbox.objects.get(user=request.user.id)
|
||||
access = True
|
||||
if request.GET.has_key("domid"):
|
||||
dom_id = int(request.GET["domid"])
|
||||
if dom_id != mb.domain.id:
|
||||
access = False
|
||||
else:
|
||||
q = request.GET.copy()
|
||||
q["domid"] = mb.domain.id
|
||||
request.GET = q
|
||||
if access:
|
||||
return f(request, **kwargs)
|
||||
|
||||
from django.conf import settings
|
||||
from django.utils.http import urlquote
|
||||
path = urlquote(request.get_full_path())
|
||||
login_url = settings.LOGIN_URL
|
||||
return HttpResponseRedirect("%s?next=%s" % (login_url, path))
|
||||
return dec
|
||||
from exceptions import *
|
||||
|
||||
def render_listing(request, objtype, tplname="admin/listing.html",
|
||||
**kwargs):
|
||||
@@ -162,53 +92,6 @@ def ungrant_access_to_object(obj, user=None):
|
||||
else:
|
||||
ObjectAccess.objects.filter(content_type=ct, object_id=obj.id).delete()
|
||||
|
||||
def can_access_object(user, obj):
|
||||
"""Check if a user can access a specific object
|
||||
|
||||
This function is recursive : if the given user hasn't got direct
|
||||
access to this object and if he has got access other ``User``
|
||||
objects, we check if one of those users owns the object.
|
||||
|
||||
:param user: a ``User`` object
|
||||
:param obj: a admin object
|
||||
:return: a boolean
|
||||
"""
|
||||
if user.is_superuser:
|
||||
return True
|
||||
|
||||
ct = ContentType.objects.get_for_model(obj)
|
||||
try:
|
||||
ooentry = user.objectaccess_set.get(content_type=ct, object_id=obj.id)
|
||||
except ObjectAccess.DoesNotExist:
|
||||
pass
|
||||
else:
|
||||
return True
|
||||
if ct.model == "user":
|
||||
return False
|
||||
if directowner:
|
||||
return False
|
||||
|
||||
ct = ContentType.objects.get_for_model(User)
|
||||
qs = user.objectaccess_set.filter(content_type=ct)
|
||||
for ooentry in qs.all():
|
||||
if is_object_owner(ooentry.content_object, obj):
|
||||
return True
|
||||
return False
|
||||
|
||||
def is_object_owner(user, obj):
|
||||
"""Tell is the user is the unique owner of this object
|
||||
|
||||
:param user: a ``User`` object
|
||||
:param obj: an object inheriting from ``models.Model``
|
||||
:return: a boolean
|
||||
"""
|
||||
ct = ContentType.objects.get_for_model(obj)
|
||||
try:
|
||||
ooentry = user.objectaccess_set.get(content_type=ct, object_id=obj.id)
|
||||
except ObjectAccess.DoesNotExist:
|
||||
return False
|
||||
return ooentry.is_owner
|
||||
|
||||
def get_object_owner(obj):
|
||||
"""Return the unique owner of this object
|
||||
|
||||
@@ -242,96 +125,8 @@ def check_domain_access(f):
|
||||
mbid = request.GET.get("mbid", None)
|
||||
if mbid:
|
||||
domain = Mailbox.objects.get(pk=mbid).domain
|
||||
if domain and not can_access_object(request.user, domain):
|
||||
if domain and not request.user.can_access(domain):
|
||||
raise PermDeniedError(_("You can't access this domain"))
|
||||
|
||||
return f(request, **kwargs)
|
||||
return dec
|
||||
|
||||
def get_user_domains(user):
|
||||
if user.is_superuser:
|
||||
return Domain.objects.all()
|
||||
|
||||
domct = ContentType.objects.get_for_model(Domain)
|
||||
qs = user.objectaccess_set.filter(content_type=domct)
|
||||
domains = []
|
||||
for entry in qs.all():
|
||||
domains += [entry.content_object]
|
||||
|
||||
userct = ContentType.objects.get_for_model(User)
|
||||
qs = user.objectaccess_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).distinct()
|
||||
|
||||
userct = ContentType.objects.get_for_model(User)
|
||||
for entry in user.objectaccess_set.filter(content_type=userct):
|
||||
if not is_domain_admin(entry.content_object):
|
||||
continue
|
||||
result |= get_user_domains_qs(entry.content_object).distinct()
|
||||
return result
|
||||
|
||||
def get_user_domaliases(user):
|
||||
"""Return the domain aliases that belong to this user
|
||||
|
||||
The result will contain the domain aliases defined for each domain
|
||||
that user can see.
|
||||
|
||||
:param user: a ``User`` object
|
||||
:return: a list of ``DomainAlias`` objects
|
||||
"""
|
||||
if user.is_superuser:
|
||||
return DomainAlias.objects.all()
|
||||
domains = get_user_domains_qs(user)
|
||||
domaliases = []
|
||||
for dom in domains:
|
||||
domaliases += dom.domainalias_set.all()
|
||||
return domaliases
|
||||
|
||||
def get_user_mailboxes(user):
|
||||
"""Return the mailboxes that belong to this user
|
||||
|
||||
The result will contain the mailboxes defined for each domain that
|
||||
user can see.
|
||||
|
||||
:param user: a ``User`` object
|
||||
:return: a list of ``Mailbox`` objects
|
||||
"""
|
||||
if user.is_superuser:
|
||||
return Mailbox.objects.all()
|
||||
mboxes = []
|
||||
domains = get_user_domains_qs(user)
|
||||
for dom in domains:
|
||||
mboxes += dom.mailbox_set.all()
|
||||
return mboxes
|
||||
|
||||
def get_user_mbaliases(user):
|
||||
"""Return the mailbox aliases that belong to this user
|
||||
|
||||
The result will contain the mailbox aliases defined for each
|
||||
domain that user can see.
|
||||
|
||||
:param user: a ``User`` object
|
||||
:return: a list of ``Alias`` objects
|
||||
"""
|
||||
if user.is_superuser:
|
||||
return Alias.objects.all()
|
||||
domains = get_user_domains_qs(user)
|
||||
mbaliases = []
|
||||
for dom in domains:
|
||||
mbaliases += dom.alias_set.all()
|
||||
return mbaliases
|
||||
|
||||
@@ -0,0 +1,116 @@
|
||||
# encoding: utf-8
|
||||
import datetime
|
||||
from south.db import db
|
||||
from south.v2 import DataMigration
|
||||
from django.db import models
|
||||
from modoboa.lib.models import Parameter
|
||||
|
||||
class Migration(DataMigration):
|
||||
|
||||
def forwards(self, orm):
|
||||
"Write your forwards methods here."
|
||||
try:
|
||||
s = Parameter.objects.get(name='PASSWORD_SCHEME').value
|
||||
except Parameter.DoesNotExist:
|
||||
s = "crypt"
|
||||
|
||||
for mb in orm.Mailbox.objects.all():
|
||||
mb.user.password = '{%s}%s' % (s.upper(), mb.password)
|
||||
mb.user.save()
|
||||
|
||||
def backwards(self, orm):
|
||||
"Write your backwards methods here."
|
||||
raise RuntimeError("Cannot reverse this migration.")
|
||||
|
||||
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'})
|
||||
},
|
||||
'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']
|
||||
+15
-2
@@ -21,6 +21,12 @@ class Migration(SchemaMigration):
|
||||
# Adding unique constraint on 'ObjectAccess', fields ['user', 'content_type', 'object_id']
|
||||
db.create_unique('admin_objectaccess', ['user_id', 'content_type_id', 'object_id'])
|
||||
|
||||
# Deleting field 'Mailbox.password'
|
||||
db.delete_column('admin_mailbox', 'password')
|
||||
|
||||
# Deleting field 'Mailbox.name'
|
||||
db.delete_column('admin_mailbox', 'name')
|
||||
|
||||
|
||||
def backwards(self, orm):
|
||||
|
||||
@@ -30,6 +36,12 @@ class Migration(SchemaMigration):
|
||||
# Deleting model 'ObjectAccess'
|
||||
db.delete_table('admin_objectaccess')
|
||||
|
||||
# User chose to not deal with backwards NULL issues for 'Mailbox.password'
|
||||
raise RuntimeError("Cannot reverse this migration. 'Mailbox.password' and its values cannot be restored.")
|
||||
|
||||
# User chose to not deal with backwards NULL issues for 'Mailbox.name'
|
||||
raise RuntimeError("Cannot reverse this migration. 'Mailbox.name' and its values cannot be restored.")
|
||||
|
||||
|
||||
models = {
|
||||
'admin.alias': {
|
||||
@@ -71,8 +83,6 @@ class Migration(SchemaMigration):
|
||||
'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', [], {}),
|
||||
@@ -92,6 +102,9 @@ class Migration(SchemaMigration):
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'last_modification': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'})
|
||||
},
|
||||
'admin.user': {
|
||||
'Meta': {'object_name': 'User', 'db_table': "'auth_user'", '_ormbases': ['auth.User'], 'proxy': 'True'}
|
||||
},
|
||||
'auth.group': {
|
||||
'Meta': {'object_name': 'Group'},
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
+223
-50
@@ -2,15 +2,20 @@
|
||||
from django.db import models, IntegrityError
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.contrib.contenttypes import generic
|
||||
from django.contrib.auth.models import User, Group
|
||||
from django.contrib.auth.models import User as DUser, UserManager, Group
|
||||
from django.utils.translation import ugettext as _, ugettext_noop
|
||||
from django.utils.crypto import constant_time_compare
|
||||
from django.conf import settings
|
||||
from modoboa.lib import parameters, events
|
||||
from modoboa.lib.exceptions import PermDeniedException
|
||||
from modoboa.lib.sysutils import exec_cmd, exec_as_vuser
|
||||
from modoboa.lib.emailutils import split_mailbox
|
||||
from modoboa.auth.lib import crypt_password
|
||||
from exceptions import *
|
||||
import os
|
||||
import pwd
|
||||
import re
|
||||
import crypt, hashlib, string, base64
|
||||
from random import Random
|
||||
|
||||
try:
|
||||
from modoboa.lib.ldaputils import *
|
||||
@@ -18,6 +23,205 @@ try:
|
||||
except ImportError:
|
||||
ldap_available = False
|
||||
|
||||
class User(DUser):
|
||||
"""Proxy for the ``User`` model.
|
||||
|
||||
It overloads the way passwords are stored into the database. The
|
||||
main reason to change this mechanism is to ensure the
|
||||
compatibility with the way Dovecot stores passwords.
|
||||
|
||||
It also adds new attributes and methods.
|
||||
"""
|
||||
class Meta:
|
||||
proxy = True
|
||||
|
||||
password_expr = re.compile(r'\{(\w+)\}(.+)')
|
||||
|
||||
def __unicode__(self):
|
||||
return u"%s %s" % (self.first_name, self.last_name)
|
||||
|
||||
def _crypt_password(self, raw_value):
|
||||
scheme = parameters.get_admin("PASSWORD_SCHEME")
|
||||
if scheme == "crypt":
|
||||
salt = ''.join(Random().sample(string.letters + string.digits, 2))
|
||||
result = crypt.crypt(raw_value, salt)
|
||||
elif scheme == "md5":
|
||||
obj = hashlib.md5(raw_value)
|
||||
result = obj.hexdigest()
|
||||
elif scheme == "sha256":
|
||||
obj = hashlib.sha256(raw_value)
|
||||
result = base64.b64encode(obj.digest())
|
||||
else:
|
||||
scheme = "plain"
|
||||
result = raw_value
|
||||
return "{%s}%s" % (scheme.upper(), result)
|
||||
|
||||
def set_password(self, raw_value, curvalue=None):
|
||||
"""Password update
|
||||
|
||||
Update the current mailbox's password with the given clear
|
||||
value. This value is encrypted according to the defined method
|
||||
before it is saved.
|
||||
|
||||
:param raw_value: the new password's value
|
||||
:param curvalue: the current password (for LDAP authentication)
|
||||
"""
|
||||
if parameters.get_admin("AUTHENTICATION_TYPE") == "local":
|
||||
self.password = self._crypt_password(raw_value)
|
||||
return
|
||||
|
||||
if not ldap_available:
|
||||
raise AdminError(_("Failed to update password: LDAP module not installed"))
|
||||
|
||||
ab = LDAPAuthBackend()
|
||||
try:
|
||||
ab.update_user_password(self.username, curvalue, raw_value)
|
||||
except LDAPException, e:
|
||||
raise AdminError(_("Failed to update password: %s" % str(e)))
|
||||
|
||||
def check_password(self, raw_value):
|
||||
m = self.password_expr.match(self.password)
|
||||
if m is None:
|
||||
return False
|
||||
scheme = m.group(1).lower()
|
||||
val2 = m.group(2)
|
||||
if scheme == "crypt":
|
||||
val1 = crypt.crypt(raw_value, m.group(2))
|
||||
elif scheme == "md5":
|
||||
val1 = hashlib.md5(raw_value).hexdigest()
|
||||
elif scheme == "sha256":
|
||||
val1 = base64.b64encode(hashlib.sha256(raw_value).digest())
|
||||
else:
|
||||
val1 = raw_value
|
||||
return constant_time_compare(val1, val2)
|
||||
|
||||
@property
|
||||
def has_mailbox(self):
|
||||
return len(self.mailbox_set.all()) != 0
|
||||
|
||||
def belongs_to_group(self, name):
|
||||
"""Simple shortcut to check if this user is a member of a
|
||||
specific group.
|
||||
|
||||
:param name: the group's name
|
||||
:return: a boolean
|
||||
"""
|
||||
try:
|
||||
self.groups.get(name=name)
|
||||
except Group.DoesNotExist:
|
||||
return False
|
||||
return True
|
||||
|
||||
def get_domains(self):
|
||||
"""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 self.is_superuser:
|
||||
return Domain.objects.all()
|
||||
result = Domain.objects.filter(owners__user=self).distinct()
|
||||
|
||||
userct = ContentType.objects.get_for_model(User)
|
||||
for entry in self.objectaccess_set.filter(content_type=userct):
|
||||
if not entry.content_object.belongs_to_group('DomainAdmins'):
|
||||
continue
|
||||
result |= entry.content_object.get_domains().distinct()
|
||||
return result
|
||||
|
||||
def get_domaliases(self):
|
||||
"""Return the domain aliases that belong to this user
|
||||
|
||||
The result will contain the domain aliases defined for each domain
|
||||
that user can see.
|
||||
|
||||
:return: a list of ``DomainAlias`` objects
|
||||
"""
|
||||
if self.is_superuser:
|
||||
return DomainAlias.objects.all()
|
||||
domains = self.get_domains()
|
||||
domaliases = []
|
||||
for dom in domains:
|
||||
domaliases += dom.domainalias_set.all()
|
||||
return domaliases
|
||||
|
||||
def get_mailboxes(self):
|
||||
"""Return the mailboxes that belong to this user
|
||||
|
||||
The result will contain the mailboxes defined for each domain that
|
||||
user can see.
|
||||
|
||||
:return: a list of ``Mailbox`` objects
|
||||
"""
|
||||
if self.is_superuser:
|
||||
return Mailbox.objects.all()
|
||||
mboxes = []
|
||||
domains = self.get_domains()
|
||||
for dom in domains:
|
||||
mboxes += dom.mailbox_set.all()
|
||||
return mboxes
|
||||
|
||||
def get_mbaliases(self):
|
||||
"""Return the mailbox aliases that belong to this user
|
||||
|
||||
The result will contain the mailbox aliases defined for each
|
||||
domain that user can see.
|
||||
|
||||
:return: a list of ``Alias`` objects
|
||||
"""
|
||||
if self.is_superuser:
|
||||
return Alias.objects.all()
|
||||
domains = self.get_domains()
|
||||
mbaliases = []
|
||||
for dom in domains:
|
||||
mbaliases += dom.alias_set.all()
|
||||
return mbaliases
|
||||
|
||||
def is_owner(self, obj):
|
||||
"""Tell is the user is the unique owner of this object
|
||||
|
||||
:param obj: an object inheriting from ``models.Model``
|
||||
:return: a boolean
|
||||
"""
|
||||
ct = ContentType.objects.get_for_model(obj)
|
||||
try:
|
||||
ooentry = self.objectaccess_set.get(content_type=ct, object_id=obj.id)
|
||||
except ObjectAccess.DoesNotExist:
|
||||
return False
|
||||
return ooentry.is_owner
|
||||
|
||||
def can_access(self, obj):
|
||||
"""Check if the user can access a specific object
|
||||
|
||||
This function is recursive : if the given user hasn't got direct
|
||||
access to this object and if he has got access other ``User``
|
||||
objects, we check if one of those users owns the object.
|
||||
|
||||
:param obj: a admin object
|
||||
:return: a boolean
|
||||
"""
|
||||
if self.is_superuser:
|
||||
return True
|
||||
|
||||
ct = ContentType.objects.get_for_model(obj)
|
||||
try:
|
||||
ooentry = self.objectaccess_set.get(content_type=ct, object_id=obj.id)
|
||||
except ObjectAccess.DoesNotExist:
|
||||
pass
|
||||
else:
|
||||
return True
|
||||
if ct.model == "user":
|
||||
return False
|
||||
|
||||
ct = ContentType.objects.get_for_model(User)
|
||||
qs = self.objectaccess_set.filter(content_type=ct)
|
||||
for ooentry in qs.all():
|
||||
if ooentry.content_object.is_owner(obj):
|
||||
return True
|
||||
return False
|
||||
|
||||
class ObjectAccess(models.Model):
|
||||
user = models.ForeignKey(User)
|
||||
content_type = models.ForeignKey(ContentType)
|
||||
@@ -27,7 +231,7 @@ class ObjectAccess(models.Model):
|
||||
|
||||
class Meta:
|
||||
unique_together = (("user", "content_type", "object_id"),)
|
||||
|
||||
|
||||
class ObjectDates(models.Model):
|
||||
"""Dates recording for admin objects
|
||||
|
||||
@@ -154,11 +358,10 @@ class DomainAlias(DatesAware):
|
||||
)
|
||||
|
||||
class Mailbox(DatesAware):
|
||||
name = models.CharField(ugettext_noop('name'), max_length=100,
|
||||
help_text=ugettext_noop("First name and last name of mailbox owner"))
|
||||
address = models.CharField(ugettext_noop('address'), max_length=100,
|
||||
help_text=ugettext_noop("Mailbox address (without the @domain.tld part)"))
|
||||
password = models.CharField(ugettext_noop('password'), max_length=100)
|
||||
address = models.CharField(
|
||||
ugettext_noop('address'), max_length=100,
|
||||
help_text=ugettext_noop("Mailbox address (without the @domain.tld part)")
|
||||
)
|
||||
quota = models.IntegerField()
|
||||
uid = models.IntegerField()
|
||||
gid = models.IntegerField()
|
||||
@@ -251,16 +454,17 @@ class Mailbox(DatesAware):
|
||||
except User.DoesNotExist:
|
||||
user = User()
|
||||
user.username = user.email = "%s@%s" % (self.address, self.domain.name)
|
||||
user.set_unusable_password()
|
||||
if kwargs.has_key("enabled"):
|
||||
user.is_active = kwargs["enabled"]
|
||||
try:
|
||||
fname, lname = self.name.split()
|
||||
fname, lname = kwargs["name"].split()
|
||||
except ValueError:
|
||||
fname = self.name
|
||||
fname = kwargs["name"]
|
||||
lname = ""
|
||||
user.first_name = fname
|
||||
user.last_name = lname
|
||||
if kwargs.has_key("password"):
|
||||
user.set_password(kwargs["password"])
|
||||
try:
|
||||
user.save()
|
||||
except IntegrityError:
|
||||
@@ -271,8 +475,6 @@ class Mailbox(DatesAware):
|
||||
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
|
||||
v_uid = parameters.get_admin("VIRTUAL_UID");
|
||||
if v_uid.isdigit():
|
||||
@@ -291,35 +493,13 @@ class Mailbox(DatesAware):
|
||||
self.quota = kwargs["quota"]
|
||||
else:
|
||||
self.quota = self.domain.quota
|
||||
for kw in ["enabled", "password", "quota"]:
|
||||
for kw in ["name", "enabled", "password", "quota"]:
|
||||
try:
|
||||
del kwargs[kw]
|
||||
except KeyError:
|
||||
pass
|
||||
super(Mailbox, self).save(*args, **kwargs)
|
||||
|
||||
def set_password(self, curvalue, newvalue):
|
||||
"""Password update
|
||||
|
||||
Update the current mailbox's password with the given clear
|
||||
value. This value is encrypted according to the defined method
|
||||
before it is saved.
|
||||
|
||||
:param value: the new password's value
|
||||
"""
|
||||
if parameters.get_admin("AUTHENTICATION_TYPE") == "local":
|
||||
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:
|
||||
ab.update_user_password(self.user.username, curvalue, newvalue)
|
||||
except LDAPException, e:
|
||||
raise AdminError(_("Failed to update password: %s" % str(e)))
|
||||
|
||||
def save_from_user(self, localpart, domain, user):
|
||||
"""Simple save method called for automatic creations
|
||||
|
||||
@@ -348,7 +528,7 @@ class Mailbox(DatesAware):
|
||||
self.quota = self.domain.quota
|
||||
super(Mailbox, self).save()
|
||||
|
||||
def create_from_csv(self, line):
|
||||
def create_from_csv(self, user, line):
|
||||
"""Create a new mailbox from a CSV file entry
|
||||
|
||||
The expected order is the following::
|
||||
@@ -365,8 +545,11 @@ class Mailbox(DatesAware):
|
||||
self.domain = Domain.objects.get(name=domname)
|
||||
except Domain.DoesNotExist:
|
||||
raise AdminError(_("Cannot import this mailbox because the associated domain does not exist"))
|
||||
self.name = "%s %s" % (line[2], line[3])
|
||||
self.save(password=line[1], enabled=True)
|
||||
if not user.is_owner(self.domain):
|
||||
raise PermDeniedException(_("You don't have access to this domain"))
|
||||
|
||||
name = "%s %s" % (line[2], line[3])
|
||||
self.save(name=name, password=line[1], enabled=True)
|
||||
|
||||
def delete(self, *args, **kwargs):
|
||||
keepdir = False
|
||||
@@ -382,14 +565,6 @@ class Mailbox(DatesAware):
|
||||
if not keepdir:
|
||||
self.delete_dir()
|
||||
|
||||
def tohash(self):
|
||||
return {
|
||||
"id" : self.id,
|
||||
"domain" : self.domain.name,
|
||||
"full_name" : self.name,
|
||||
"date_joined" : self.user.date_joined,
|
||||
"enabled" : self.user.is_active
|
||||
}
|
||||
|
||||
class Alias(DatesAware):
|
||||
address = models.CharField(ugettext_noop('address'), max_length=254,
|
||||
@@ -464,12 +639,10 @@ 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
|
||||
for mb in self.mboxes.all():
|
||||
if not is_object_owner(user, mb.domain):
|
||||
if not user.is_owner(mb.domain):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
+4
-22
@@ -2,10 +2,11 @@
|
||||
|
||||
import datetime
|
||||
from django.utils.translation import ugettext as _, ugettext_noop
|
||||
from django.contrib.auth.models import User, Group
|
||||
from django.contrib.auth.models import Group
|
||||
from modoboa.lib.webutils import _render, getctx, _render_to_string
|
||||
from modoboa.lib import events
|
||||
from forms import *
|
||||
from models import User
|
||||
from tables import SuperAdminsTable, DomainAdminsTable
|
||||
from lib import grant_access_to_object, ungrant_access_to_object
|
||||
|
||||
@@ -50,6 +51,7 @@ class SuperAdminsPerms(Permissions):
|
||||
user = User.objects.get(pk=request.POST["user"])
|
||||
user.is_superuser = True
|
||||
user.date_joined = datetime.datetime.now()
|
||||
events.raiseEvent("SuperAdminPromotion", user)
|
||||
user.groups.clear()
|
||||
user.save()
|
||||
return True, None
|
||||
@@ -61,7 +63,6 @@ class SuperAdminsPerms(Permissions):
|
||||
for s in selection:
|
||||
user = User.objects.get(pk=int(s))
|
||||
if user.username == "admin":
|
||||
#messages.error(request, _("admin is intouchable!!"), fail_silently=True)
|
||||
continue
|
||||
user.is_superuser = False
|
||||
user.save()
|
||||
@@ -89,22 +90,6 @@ class DomainAdminsPerms(Permissions):
|
||||
def get_add_form(self, request):
|
||||
form = DomainAdminForm()
|
||||
return self._render_form(request, form)
|
||||
|
||||
def add(self, request):
|
||||
form = DomainAdminForm(request.POST)
|
||||
if request.POST.get("user", "") != "":
|
||||
mboxid = request.POST['user']
|
||||
mb = Mailbox.objects.get(pk=mboxid)
|
||||
form.fields["user"].choices = [(mboxid, mb),]
|
||||
if form.is_valid():
|
||||
mb.user.is_superuser = False
|
||||
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 = self._render_form(request, form, True)
|
||||
return False, getctx("ko", content=content)
|
||||
|
||||
def delete(self, selection):
|
||||
dagrp = Group.objects.get(name="DomainAdmins")
|
||||
@@ -121,11 +106,8 @@ class DomainAdminsPerms(Permissions):
|
||||
u.groups.add(sugrp)
|
||||
|
||||
def get(self, request):
|
||||
#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()]
|
||||
domainadmins = filter(lambda da: request.user.is_owner(da), domainadmins)
|
||||
return DomainAdminsTable(request, domainadmins).render()
|
||||
|
||||
def get_perms_class(user, role):
|
||||
|
||||
+4
-1
@@ -68,7 +68,7 @@ class MailboxesTable(tables.Table):
|
||||
idkey = "id"
|
||||
|
||||
address = tables.Column("address", label=ugettext_noop("Address"))
|
||||
name = tables.Column("name", label=ugettext_noop("Name"))
|
||||
name = tables.Column("user", label=ugettext_noop("Name"))
|
||||
creation = tables.Column("creation", label=ugettext_noop("Created"),
|
||||
width="160px")
|
||||
modified = tables.Column("last_modification", label=ugettext_noop("Last modified"),
|
||||
@@ -95,6 +95,9 @@ class MailboxesTable(tables.Table):
|
||||
def parse_enabled(self, value):
|
||||
return _("yes") if value else _("no")
|
||||
|
||||
def parse_name(self, user):
|
||||
return "%s %s" % (user.first_name, user.last_name)
|
||||
|
||||
class MbaliasesTable(tables.Table):
|
||||
tableid = "objects_table"
|
||||
idkey = "id"
|
||||
|
||||
@@ -2,7 +2,6 @@ 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
|
||||
@@ -202,6 +201,8 @@ def mailbox_actions(user, mboxid):
|
||||
|
||||
@register.simple_tag
|
||||
def domain_admin_actions(user, daid):
|
||||
from modoboa.admin.models import User
|
||||
|
||||
actions = [
|
||||
{"name" : "editdomainadmin",
|
||||
"url" : reverse(admin.views.edit_domain_admin, args=[daid]),
|
||||
|
||||
@@ -25,8 +25,6 @@ urlpatterns = patterns(
|
||||
(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'),
|
||||
|
||||
+33
-42
@@ -6,7 +6,7 @@ from django.core.urlresolvers import reverse
|
||||
from django.contrib.auth.decorators \
|
||||
import login_required, permission_required, user_passes_test
|
||||
from django.db.models import Q
|
||||
from django.contrib.auth.models import User, Group
|
||||
from django.contrib.auth.models import Group
|
||||
from django.contrib import messages
|
||||
from django.db import transaction, IntegrityError, transaction
|
||||
|
||||
@@ -17,7 +17,6 @@ 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
|
||||
from modoboa.lib.emailutils import split_mailbox
|
||||
@@ -32,7 +31,7 @@ def domains(request):
|
||||
|
||||
return HttpResponseRedirect(reverse(userprefs.views.preferences))
|
||||
|
||||
domains = get_user_domains_qs(request.user)
|
||||
domains = request.user.get_domains()
|
||||
for dom in domains:
|
||||
dom.mbalias_counter = len(Alias.objects.filter(domain=dom.id))
|
||||
deloptions = {"keepdir" : _("Do not delete domain directory")}
|
||||
@@ -93,7 +92,7 @@ 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):
|
||||
if not request.user.is_owner(domain):
|
||||
raise PermDeniedException(_("You can't edit this domain"))
|
||||
|
||||
commonctx = {"title" : _("Domain editing"),
|
||||
@@ -121,7 +120,7 @@ def deldomain(request):
|
||||
mb = None
|
||||
|
||||
for dom in Domain.objects.filter(id__in=selection):
|
||||
if not is_object_owner(request.user, dom):
|
||||
if not request.user.is_owner(dom):
|
||||
raise PermDeniedException(_("You can't delete this domain"))
|
||||
if mb and mb.domain == dom:
|
||||
raise AdminError(_("You can't delete your own domain"))
|
||||
@@ -143,7 +142,7 @@ def domaliases(request):
|
||||
domaliases = DomainAlias.objects.filter(target=request.GET["domid"])
|
||||
else:
|
||||
domain = None
|
||||
domaliases = get_user_domaliases(request.user)
|
||||
domaliases = request.user.get_domaliases()
|
||||
return render_listing(request, "domaliases",
|
||||
title=_("Domain aliases"),
|
||||
rel="310 190",
|
||||
@@ -190,7 +189,7 @@ def newdomalias(request, tplname="admin/adminform.html"):
|
||||
@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):
|
||||
if not request.user.is_owner(domalias.target):
|
||||
raise PermDeniedException(_("You do not have access to this domain"))
|
||||
|
||||
commonctx = {"title" : _("Domain alias editing"),
|
||||
@@ -227,7 +226,7 @@ def deldomalias(request):
|
||||
selection = request.GET["selection"].split(",")
|
||||
for daid in selection:
|
||||
domalias = DomainAlias.objects.get(pk=daid)
|
||||
if not is_object_owner(request.user, domalias.target):
|
||||
if not request.user.is_owner(domalias.target):
|
||||
raise PermDeniedException(_("You don't have acces to this domain"))
|
||||
events.raiseEvent("DomainAliasDeleted", domalias)
|
||||
ungrant_access_to_object(domalias)
|
||||
@@ -246,7 +245,7 @@ def mailboxes(request):
|
||||
mboxes = Mailbox.objects.filter(domain=domain.id)
|
||||
title += " (%s)" % domain.name
|
||||
else:
|
||||
mboxes = get_user_mailboxes(request.user)
|
||||
mboxes = request.user.get_mailboxes()
|
||||
domain = None
|
||||
deloptions = {"keepdir" : _("Do not delete mailbox directory")}
|
||||
return render_listing(request, "mailboxes",
|
||||
@@ -254,20 +253,6 @@ def mailboxes(request):
|
||||
rel="310 320",
|
||||
objects=mboxes, domain=domain, deloptions=deloptions)
|
||||
|
||||
@login_required
|
||||
@good_domain
|
||||
@permission_required("admin.view_mailboxes")
|
||||
def mailboxes_raw(request, dom_id=None):
|
||||
target = request.GET.has_key("target") and request.GET["target"] or "permissions"
|
||||
if target == "permissions":
|
||||
mailboxes = Mailbox.objects.filter(domain=dom_id).exclude(user__id=request.user.id)
|
||||
else:
|
||||
mailboxes = Mailbox.objects.filter(domain=dom_id)
|
||||
return _render(request, 'admin/mailboxes_raw.html', {
|
||||
"mailboxes" : mailboxes,
|
||||
"target" : target
|
||||
})
|
||||
|
||||
@login_required
|
||||
@permission_required("admin.view_mailboxes")
|
||||
@check_domain_access
|
||||
@@ -281,7 +266,7 @@ def mailboxes_search(request):
|
||||
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
|
||||
query = Q(domain__in=request.user.get_domains()) & query
|
||||
mboxes = Mailbox.objects.filter(query)
|
||||
result = map(lambda mb: mb.full_address, mboxes)
|
||||
return ajax_simple_response(result)
|
||||
@@ -296,11 +281,11 @@ def newmailbox(request, tplname="admin/adminform.html"):
|
||||
"action" : reverse(newmailbox),
|
||||
"formid" : "mboxform"}
|
||||
if request.method == "POST":
|
||||
form = MailboxForm(request.user, request.POST)
|
||||
form = MailboxWithPasswordForm(request.user, request.POST)
|
||||
error = None
|
||||
if form.is_valid():
|
||||
if not request.user.is_superuser:
|
||||
if not can_access_object(request.user, form.cleaned_data["domain"]):
|
||||
if not request.user.can_access(form.cleaned_data["domain"]):
|
||||
raise PermDeniedException(_("You do not have access to this domain"))
|
||||
|
||||
try:
|
||||
@@ -319,7 +304,7 @@ def newmailbox(request, tplname="admin/adminform.html"):
|
||||
commonctx["error"] = error
|
||||
return ajax_response(request, status="ko", template=tplname, **commonctx)
|
||||
|
||||
form = MailboxForm(request.user)
|
||||
form = MailboxWithPasswordForm(request.user)
|
||||
commonctx["form"] = form
|
||||
return _render(request, tplname, commonctx)
|
||||
|
||||
@@ -327,7 +312,7 @@ def newmailbox(request, tplname="admin/adminform.html"):
|
||||
@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):
|
||||
if not request.user.is_owner(mb.domain):
|
||||
raise PermDeniedException(_("You do not have access to this domain"))
|
||||
|
||||
commonctx = {"title" : _("Mailbox editing"),
|
||||
@@ -335,8 +320,13 @@ def editmailbox(request, mbox_id=None, tplname="admin/adminform.html"):
|
||||
"action" : reverse(editmailbox, args=[mbox_id]),
|
||||
"formid" : "mboxform"}
|
||||
if request.method == "POST":
|
||||
if request.POST.get("password1", "") != "" or \
|
||||
request.POST.get("password2", "") != "":
|
||||
formclass = MailboxWithPasswordForm
|
||||
else:
|
||||
formclass = MailboxForm
|
||||
oldmb = copy.deepcopy(mb)
|
||||
form = MailboxForm(request.user, request.POST, instance=mb)
|
||||
form = formclass(request.user, request.POST, instance=mb)
|
||||
error = None
|
||||
if form.is_valid():
|
||||
try:
|
||||
@@ -356,7 +346,7 @@ def editmailbox(request, mbox_id=None, tplname="admin/adminform.html"):
|
||||
commonctx["error"] = error
|
||||
return ajax_response(request, status="ko", template=tplname, **commonctx)
|
||||
|
||||
form = MailboxForm(request.user, instance=mb)
|
||||
form = MailboxWithPasswordForm(request.user, instance=mb)
|
||||
commonctx["form"] = form
|
||||
return _render(request, tplname, commonctx)
|
||||
|
||||
@@ -371,7 +361,7 @@ def delmailbox(request):
|
||||
if len(request.user.mailbox_set.all()) else None
|
||||
|
||||
for mb in Mailbox.objects.filter(id__in=selection):
|
||||
if not can_access_object(request.user, mb.domain):
|
||||
if not request.user.can_access(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"))
|
||||
@@ -393,7 +383,7 @@ def mbaliases(request):
|
||||
elif request.GET.has_key("mbid"):
|
||||
aliases = Alias.objects.filter(mboxes__id=request.GET["mbid"]).distinct()
|
||||
else:
|
||||
aliases = get_user_mbaliases(request.user)
|
||||
aliases = request.user.get_mbaliases()
|
||||
|
||||
return render_listing(request, "mbaliases",
|
||||
title=_("Mailbox aliases"),
|
||||
@@ -456,7 +446,7 @@ def newmbalias(request, tplname="admin/mbaliasform.html"):
|
||||
@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):
|
||||
if not request.user.is_owner(alias.domain):
|
||||
raise PermDeniedException(_("You do not have access to this domain"))
|
||||
commonctx = {"title" : _("Mailbox alias editing"),
|
||||
"submit_label" : _("Update"),
|
||||
@@ -479,7 +469,7 @@ def delmbalias(request):
|
||||
selection = request.GET["selection"].split(",")
|
||||
for mbaid in selection:
|
||||
mbalias = Alias.objects.get(pk=mbaid)
|
||||
if not is_object_owner(request.user, mbalias.domain):
|
||||
if not request.user.is_owner(mbalias.domain):
|
||||
raise PermDeniedException(_("You do not have access to this domain"))
|
||||
events.raiseEvent("MailboxAliasDeleted", mbalias)
|
||||
ungrant_access_to_object(mbalias)
|
||||
@@ -590,7 +580,7 @@ def domain_admin_promotion(request):
|
||||
u = User.objects.get(username=form.cleaned_data["name"])
|
||||
u.groups.add(Group.objects.get(name="DomainAdmins"))
|
||||
u.save()
|
||||
if not can_access_object(request.user, u):
|
||||
if not request.user.can_access(u):
|
||||
# The only case where we need to grant access to the
|
||||
# object is when ``u`` has been create by a super
|
||||
# admin
|
||||
@@ -615,7 +605,7 @@ def assign_domains_to_admin(request, da_id, tplname="admin/adminform.html"):
|
||||
if request.method == "POST":
|
||||
form = AssignDomainsForm(request.user, da, request.POST)
|
||||
if form.is_valid():
|
||||
current_domains = get_user_domains(da)
|
||||
current_domains = da.get_domains()
|
||||
for domain in form.cleaned_data["domains"]:
|
||||
if not domain in current_domains:
|
||||
grant_access_to_object(da, domain)
|
||||
@@ -744,7 +734,8 @@ def saveextensions(request):
|
||||
return HttpResponseRedirect(reverse(admin.views.viewextensions))
|
||||
|
||||
@login_required
|
||||
@user_passes_test(lambda u: u.is_superuser)
|
||||
@permission_required("admin.add_mailbox")
|
||||
@transaction.commit_on_success
|
||||
def importdata(request, tplname="admin/import.html"):
|
||||
if request.method == "POST":
|
||||
error = None
|
||||
@@ -753,19 +744,19 @@ def importdata(request, tplname="admin/import.html"):
|
||||
import csv
|
||||
reader = csv.reader(request.FILES['sourcefile'],
|
||||
delimiter=form.cleaned_data['sepcar'])
|
||||
cpt = 0
|
||||
try:
|
||||
cpt = 0
|
||||
for row in reader:
|
||||
mb = Mailbox()
|
||||
mb.create_from_csv(row)
|
||||
mb.create_from_csv(request.user, row)
|
||||
events.raiseEvent("CreateMailbox", request.user, mb)
|
||||
cpt += 1
|
||||
except AdminError, e:
|
||||
error = str(e)
|
||||
else:
|
||||
messages.info(request, _("%d mailboxes imported successfully" % cpt))
|
||||
return _render(request, "admin/import_done.html", {
|
||||
"status" : "ok", "msg" : ""
|
||||
})
|
||||
except ModoboaException, e:
|
||||
error = str(e)
|
||||
|
||||
return _render(request, "admin/import_done.html", {
|
||||
"status" : "ko", "msg" : error
|
||||
|
||||
Reference in New Issue
Block a user