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:
Antoine Nguyen
2012-01-27 13:51:29 +01:00
parent c951541803
commit b7d77254ce
51 changed files with 849 additions and 604 deletions
+7 -7
View File
@@ -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)
+15
View File
@@ -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
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": 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
View File
@@ -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
View File
@@ -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
+116
View File
@@ -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']
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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 -1
View File
@@ -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]),
-2
View File
@@ -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
View File
@@ -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
-18
View File
@@ -25,21 +25,3 @@ def decrypt(ciph):
def get_password(request):
return decrypt(request.session["password"])
def _check_password(password, crypted):
scheme = parameters.get_admin("PASSWORD_SCHEME", app="admin")
if scheme == "crypt":
return crypt.crypt(password, crypted) == crypted
if scheme == "md5":
return hashlib.md5(password).hexdigest() == crypted
return password
def crypt_password(password, hexdigest=True):
scheme = parameters.get_admin("PASSWORD_SCHEME", app="admin")
if scheme == "crypt":
salt = ''.join(Random().sample(string.letters + string.digits, 2))
return crypt.crypt(password, salt)
if scheme == "md5":
obj = hashlib.md5(password)
return obj.hexdigest() if hexdigest else obj.digest()
return password
+6 -4
View File
@@ -6,10 +6,12 @@ from django.template.loader import render_to_string
from modoboa.lib import events
from modoboa.lib.webutils import static_url
def menu(**kwargs):
def menu(target, user):
import views
if kwargs["target"] != "top_menu_middle":
if target != "top_menu_middle":
return []
if not user.has_mailbox:
return []
return [
{"name" : "demo",
@@ -29,8 +31,8 @@ def menu(**kwargs):
}
]
def announcement(**kwargs):
if kwargs["target"] == "loginpage":
def announcement(target):
if target == "loginpage":
txt = render_to_string("demo/login_announcement.html")
return [txt]
return ""
+3 -3
View File
@@ -2,12 +2,12 @@
import os
from django.conf import settings
from django.contrib.auth.decorators import login_required
from modoboa.admin.lib import is_not_localadmin
from modoboa.lib.webutils import ajax_response
from modoboa.lib.emailutils import sendmail_simple, sendmail_fromfile
from modoboa.lib.decorators import needs_mailbox
@login_required
@is_not_localadmin()
@needs_mailbox()
def send_virus(request):
status, error = sendmail_fromfile("virus@example.net", request.user.username,
os.path.join(settings.MODOBOA_DIR,
@@ -17,7 +17,7 @@ def send_virus(request):
return ajax_response(request, "ko", respmsg=error)
@login_required
@is_not_localadmin()
@needs_mailbox()
def send_spam(request):
status, error = sendmail_simple("spam@example.net", request.user.username, """
This is the GTUBE, the
+1 -1
View File
@@ -20,7 +20,7 @@ if __name__ == "__main__":
l.setLevel(logging.DEBUG)
l.addHandler(logging.StreamHandler())
main.init()
main.load()
max_messages_age = int(parameters.get_admin("MAX_MESSAGES_AGE",
app="amavis_quarantine"))
+17 -11
View File
@@ -1,4 +1,4 @@
# -*- coding: utf-8 -*-
# coding: utf-8
"""
"""
@@ -16,13 +16,16 @@ def infos():
"url" : "quarantine"
}
def init():
events.register("UserMenuDisplay", menu)
parameters.register_admin("MAX_MESSAGES_AGE", type="int",
deflt=14,
help=_("Quarantine messages maximum age (in days) before deletion"))
parameters.register_admin("RELEASED_MSGS_CLEANUP", type="list_yesno", deflt="no",
help=_("Remove messages marked as released while cleaning up the database"))
def load():
parameters.register_admin(
"MAX_MESSAGES_AGE", type="int",
deflt=14,
help=_("Quarantine messages maximum age (in days) before deletion")
)
parameters.register_admin(
"RELEASED_MSGS_CLEANUP", type="list_yesno", deflt="no",
help=_("Remove messages marked as released while cleaning up the database")
)
parameters.register_admin("AM_PDP_MODE", type="list",
deflt="unix",
values=[("inet", "inet"), ("unix", "unix")],
@@ -37,9 +40,11 @@ def init():
deflt=_("/var/amavis/amavisd.sock"),
help=_("Path to the PDP server socket (if unix mode)"))
parameters.register_user("MESSAGES_PER_PAGE", type="int", deflt=40,
label="Number of displayed emails per page",
help=_("Sets the maximum number of messages displayed in a page"))
parameters.register_user(
"MESSAGES_PER_PAGE", type="int", deflt=40,
label="Number of displayed emails per page",
help=_("Sets the maximum number of messages displayed in a page")
)
def destroy():
events.unregister("UserMenuDisplay", menu)
@@ -49,6 +54,7 @@ def urls(prefix):
return (r'^%squarantine/' % prefix,
include('modoboa.extensions.amavis_quarantine.urls'))
@events.observe("UserMenuDisplay")
def menu(target, menu):
import views
+1 -2
View File
@@ -7,7 +7,6 @@ from modoboa.lib import tables
from modoboa.lib.webutils import static_url
from modoboa.lib.email_listing import MBconnector, EmailListing
from modoboa.lib.emailutils import *
from modoboa.admin.lib import is_domain_admin
from models import *
class Qtable(tables.Table):
@@ -84,7 +83,7 @@ class SQLlisting(EmailListing):
reset_wm_url = True
def __init__(self, user, msgs, filter, **kwargs):
if not user.is_superuser and not is_domain_admin(user):
if user.belongs_to_group('SimpleUsers'):
Qtable.cols_order = ['type', 'rstatus', 'from_', 'subject', 'time']
else:
Qtable.cols_order = ['type', 'rstatus', 'to', 'from_', 'subject', 'time']
@@ -5,7 +5,6 @@ from django.core.urlresolvers import reverse
from django.conf import settings
from modoboa.lib.webutils import static_url
from modoboa.extensions import amavis_quarantine
from modoboa.admin.lib import is_domain_admin
register = template.Library()
@@ -90,7 +89,7 @@ def quar_menu(selection, user):
menu = render_to_string('common/menu.html',
{"selection" : selection, "entries" : entries})
if user.is_superuser or is_domain_admin(user):
if not user.belongs_to_group('SimpleUsers'):
extraopts = [{"name" : "to", "label" : _("To")}]
else:
extraopts = []
+24 -16
View File
@@ -16,7 +16,6 @@ from lib import AMrelease
from templatetags.amextras import *
from modoboa.lib.email_listing import parse_search_parameters
from sql_listing import *
from modoboa.admin.lib import is_domain_admin
def __get_current_url(request):
if request.session.has_key("page"):
@@ -28,7 +27,13 @@ def __get_current_url(request):
if params != "":
res += "?%s" % (params)
return res
def empty_quarantine(request):
content = "<div class='info'>%s</div>" % _("Empty quarantine")
ctx = getctx("ok", level=2, listing=content, navbar="",
menu=quar_menu("", request.user))
return HttpResponse(simplejson.dumps(ctx), mimetype="application/json")
@login_required
def _listing(request):
filter = None
@@ -59,12 +64,16 @@ def _listing(request):
q = ~Q(rs='D')
if not request.user.is_superuser:
mb = Mailbox.objects.get(user=request.user.id)
if is_domain_admin(request.user):
q &= Q(rid__email__contains=mb.domain.name)
if not request.user.belongs_to_group('SimpleUsers'):
doms = request.user.get_domains()
if not len(doms):
return empty_quarantine(request)
regexp = "(%s)" % '|'.join(map(lambda dom: dom.name, doms))
q &= Q(rid__email__regex=regexp)
else:
q &= Q(rid__email=mb.full_address)
if (request.user.is_superuser or is_domain_admin(request.user)) \
q &= Q(rid__email=request.user.email)
if not request.user.belongs_to_group('SimpleUsers') \
and rcptfilter is not None:
q &= Q(rid__email__contains=rcptfilter)
msgs = Msgrcpt.objects.filter(q).values("mail_id")
@@ -82,12 +91,11 @@ def _listing(request):
elems_per_page=int(parameters.get_user(request.user,
"MESSAGES_PER_PAGE")))
page = lst.paginator.getpage(pageid)
if page:
content = lst.fetch(request, page.id_start, page.id_stop)
navbar = lst.render_navbar(page)
else:
content = "<div class='info'>%s</div>" % _("Empty quarantine")
navbar = ""
if not page:
return empty_quarantine(request)
content = lst.fetch(request, page.id_start, page.id_stop)
navbar = lst.render_navbar(page)
ctx = getctx("ok", listing=content, navbar=navbar,
menu=quar_menu("", request.user))
return HttpResponse(simplejson.dumps(ctx), mimetype="application/json")
@@ -115,7 +123,7 @@ def getmailcontent(request, mail_id):
@login_required
def viewmail(request, mail_id):
if request.user.is_superuser or is_domain_admin(request.user):
if not request.user.belongs_to_group('SimpleUsers'):
rcpt = request.GET["rcpt"]
else:
mb = Mailbox.objects.get(user=request.user)
@@ -158,7 +166,7 @@ def check_mail_id(request, mail_id):
@login_required
def delete(request, mail_id):
mail_id = check_mail_id(request, mail_id)
if not request.user.is_superuser and not is_domain_admin(request.user):
if request.user.belongs_to_group('SimpleUsers'):
mb = Mailbox.objects.get(user=request.user)
msgrcpts = Msgrcpt.objects.filter(mail__in=mail_id, rid__email=mb.full_address)
msgrcpts.update(rs='D')
@@ -178,7 +186,7 @@ def delete(request, mail_id):
@login_required
def release(request, mail_id):
mail_id = check_mail_id(request, mail_id)
if not request.user.is_superuser and not is_domain_admin(request.user):
if request.user.belongs_to_group('SimpleUsers'):
mb = Mailbox.objects.get(user=request.user)
msgrcpts = Msgrcpt.objects.filter(mail__in=mail_id, rid__email=mb.full_address)
else:
+7 -7
View File
@@ -6,7 +6,7 @@
"""
from modoboa.admin.models import Mailbox
from modoboa.admin.lib import \
get_object_owner, grant_access_to_object, is_object_owner, \
get_object_owner, grant_access_to_object, \
ungrant_access_to_object
from modoboa.lib import events
from lib import *
@@ -104,20 +104,20 @@ def create_pool(user):
p.save()
p.create_limits()
@events.observe("DomainAdminDeleted")
def move_pool_resource(domadmin):
owner = get_object_owner(domadmin)
@events.observe("DomainAdminDeleted", "SuperAdminPromotion")
def move_pool_resource(user):
owner = get_object_owner(user)
if not owner.is_superuser:
for ooentry in domadmin.objectaccess_set.all():
for ooentry in user.objectaccess_set.all():
if ooentry.is_owner:
grant_access_to_object(owner, ooentry.content_object, True)
ooentry.delete()
for lname in reseller_limits_tpl:
l = domadmin.limitspool.get_limit(lname)
l = user.limitspool.get_limit(lname)
ol = owner.limitspool.get_limit(lname)
ol.curvalue += l.curvalue
ol.maxvalue += l.maxvalue
ol.save()
domadmin.limitspool.delete()
user.limitspool.delete()
+10 -1
View File
@@ -6,7 +6,8 @@ The *limits* extension
"""
from django.contrib.auth.models import Permission, Group
from django.contrib.auth.models import Permission, User, Group
from django.db import IntegrityError
from django.contrib.contenttypes.models import ContentType
from django.utils.translation import ugettext_noop as _, ugettext
from django.core.urlresolvers import reverse
@@ -28,6 +29,12 @@ def init():
grp.permissions.add(perm)
grp.save()
for user in User.objects.filter(groups__name='DomainAdmins'):
try:
controls.create_pool(user)
except IntegrityError:
pass
def destroy():
ct = ContentType.objects.get(app_label="admin", model="domain")
grp = Group.objects.get(name="DomainAdmins")
@@ -50,6 +57,8 @@ def urls(prefix):
@events.observe('AdminFooterDisplay')
def display_pool_usage(user, objtype):
if user.id == 1:
return []
if objtype == "domaliases":
objtype = "domain_aliases"
elif objtype == "mbaliases":
@@ -0,0 +1,100 @@
# 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 'LimitsPool'
db.create_table('limits_limitspool', (
('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
('user', self.gf('django.db.models.fields.related.OneToOneField')(to=orm['admin.User'], unique=True)),
))
db.send_create_signal('limits', ['LimitsPool'])
# Adding model 'Limit'
db.create_table('limits_limit', (
('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
('name', self.gf('django.db.models.fields.CharField')(max_length=255)),
('curvalue', self.gf('django.db.models.fields.IntegerField')(default=0)),
('maxvalue', self.gf('django.db.models.fields.IntegerField')(default=-2)),
('pool', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['limits.LimitsPool'])),
))
db.send_create_signal('limits', ['Limit'])
# Adding unique constraint on 'Limit', fields ['name', 'pool']
db.create_unique('limits_limit', ['name', 'pool_id'])
def backwards(self, orm):
# Removing unique constraint on 'Limit', fields ['name', 'pool']
db.delete_unique('limits_limit', ['name', 'pool_id'])
# Deleting model 'LimitsPool'
db.delete_table('limits_limitspool')
# Deleting model 'Limit'
db.delete_table('limits_limit')
models = {
'admin.user': {
'Meta': {'object_name': 'User', '_ormbases': ['auth.User']},
'user_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['auth.User']", 'unique': 'True', 'primary_key': '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'})
},
'limits.limit': {
'Meta': {'unique_together': "(('name', 'pool'),)", 'object_name': 'Limit'},
'curvalue': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'maxvalue': ('django.db.models.fields.IntegerField', [], {'default': '-2'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'pool': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['limits.LimitsPool']"})
},
'limits.limitspool': {
'Meta': {'object_name': 'LimitsPool'},
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'user': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['admin.User']", 'unique': 'True'})
}
}
complete_apps = ['limits']
+1 -1
View File
@@ -3,8 +3,8 @@
from django.db import models
from django.contrib.contenttypes.models import ContentType
from django.contrib.contenttypes import generic
from django.contrib.auth.models import User
from django.utils.translation import ugettext as _, ugettext_noop
from modoboa.admin.models import User
reseller_limits_tpl = [
"domains_limit",
-1
View File
@@ -54,7 +54,6 @@ def get_resellers_table(request):
@events.observe("PermsGetClass")
def get_perms_class(role):
print role
if role != "resellers":
return []
return [ResellersPerms]
+28 -31
View File
@@ -22,17 +22,12 @@ def infos():
"url" : "postfix_autoreply"
}
def init():
events.register("CreateDomain", onCreateDomain)
events.register("DeleteDomain", onDeleteDomain)
events.register("CreateMailbox", onCreateMailbox)
events.register("DeleteMailbox", onDeleteMailbox)
events.register("ModifyMailbox", onModifyMailbox)
events.register("UserMenuDisplay", menu)
parameters.register_admin("AUTOREPLIES_TIMEOUT",
type="int", deflt=86400,
help=_("Timeout in seconds between two auto-replies to the same recipient"))
def load():
parameters.register_admin(
"AUTOREPLIES_TIMEOUT",
type="int", deflt=86400,
help=_("Timeout in seconds between two auto-replies to the same recipient")
)
def destroy():
events.unregister("CreateDomain", onCreateDomain)
@@ -47,11 +42,14 @@ def urls(prefix):
return (r'^%spostfix_autoreply/' % prefix,
include('modoboa.extensions.postfix_autoreply.urls'))
@events.observe("UserMenuDisplay")
def menu(target, user):
import views
if target != "uprefs_menu":
return []
if not user.has_mailbox:
return []
return [
{"name" : "autoreply",
"url" : reverse(views.autoreply),
@@ -61,41 +59,40 @@ def menu(target, user):
"label" : ugettext("Auto-reply message")}
]
def onCreateDomain(**kwargs):
dom = kwargs["dom"]
@events.observe("CreateDomain")
def onCreateDomain(user, domain):
transport = Transport()
transport.domain = "autoreply.%s" % dom.name
transport.domain = "autoreply.%s" % domain.name
transport.method = "autoreply:"
transport.save()
def onDeleteDomain(**kwargs):
dom = kwargs["dom"]
trans = Transport.objects.get(domain="autoreply.%s" % dom.name)
@events.observe("DeleteDomain")
def onDeleteDomain(domain):
trans = Transport.objects.get(domain="autoreply.%s" % domain.name)
trans.delete()
def onCreateMailbox(**kwargs):
mbox = kwargs["mbox"]
@events.observe("CreateMailbox")
def onCreateMailbox(user, mailbox):
alias = Alias()
alias.full_address = mbox.full_address
alias.full_address = mailbox.full_address
alias.autoreply_address = \
"%s@autoreply.%s" % (mbox.full_address, mbox.domain.name)
"%s@autoreply.%s" % (mailbox.full_address, mailbox.domain.name)
alias.save()
def onDeleteMailbox(**kwargs):
mbox = kwargs["mbox"]
@events.observe("DeleteMailbox")
def onDeleteMailbox(mailbox):
try:
alias = Alias.objects.get(full_address=mbox.full_address)
alias = Alias.objects.get(full_address=mailbox.full_address)
alias.delete()
except Alias.DoesNotExist:
pass
def onModifyMailbox(**kwargs):
mbox = kwargs["mbox"]
oldmbox = kwargs["oldmbox"]
if oldmbox.full_address == mbox.full_address:
@events.observe("ModifyMailbox")
def onModifyMailbox(mailbox, oldmailbox):
if oldmailbox.full_address == mailbox.full_address:
return
alias = Alias.objects.get(full_address=oldmbox.full_address)
alias.full_address = mbox.full_address
alias = Alias.objects.get(full_address=oldmailbox.full_address)
alias.full_address = mailbox.full_address
alias.autoreply_address = \
"%s@autoreply.%s" % (mbox.full_address, mbox.domain.name)
"%s@autoreply.%s" % (mailbox.full_address, mailbox.domain.name)
alias.save()
@@ -20,7 +20,7 @@ def send_autoreply(sender, mailbox, armessage):
try:
lastar = ARhistoric.objects.get(armessage=armessage.id, sender=sender)
main.init()
main.load()
timeout = parameters.get_admin("AUTOREPLIES_TIMEOUT",
app="postfix_autoreply")
delta = datetime.timedelta(seconds=int(timeout))
@@ -13,7 +13,7 @@ if __name__ == "__main__":
import modoboa.extensions.postfix_autoreply.main as par
from modoboa.extensions.postfix_autoreply.models import Transport, Alias
par.init()
par.load()
doms = Domain.objects.all()
for dom in doms:
try:
+2 -2
View File
@@ -8,12 +8,12 @@ from django.contrib import messages
from django.core.urlresolvers import reverse
from django.utils.translation import ugettext as _
from modoboa.lib.webutils import _render, ajax_response
from modoboa.admin.lib import is_not_localadmin
from modoboa.lib.decorators import needs_mailbox
from forms import *
from models import *
@login_required
@is_not_localadmin('boxerror')
@needs_mailbox()
def autoreply(request, tplname="postfix_autoreply/autoreply.html"):
mb = Mailbox.objects.get(user=request.user.id)
try:
+9 -8
View File
@@ -6,10 +6,7 @@ from modoboa.lib import events, parameters
from modoboa.lib.webutils import static_url
from sievelib.managesieve import SUPPORTED_AUTH_MECHS
def init():
events.register("UserMenuDisplay", menu)
events.register("UserLogout", userlogout)
def load():
parameters.register_admin("SERVER", type="string",
deflt="127.0.0.1",
help=_("Address of your MANAGESIEVE server"))
@@ -47,11 +44,14 @@ def urls(prefix):
return (r'^%ssfilters/' % prefix,
include('modoboa.extensions.sievefilters.urls'))
@events.observe("UserMenuDisplay")
def menu(target, user):
import views
if target != "options_menu":
return []
if not user.has_mailbox:
return []
return [
{"name" : "sievefilters",
"label" : ugettext("Message filters"),
@@ -59,14 +59,15 @@ def menu(target, user):
"img" : static_url("pics/filters.png")}
]
def userlogout(**kwargs):
@events.observe("Userlogout")
def userlogout(request):
from lib import SieveClient
if kwargs["request"].user.id == 1:
if not request.user.has_mailbox:
return
try:
sc = SieveClient(user=kwargs["request"].user.username,
password=kwargs["request"].session["password"])
sc = SieveClient(user=request.user.username,
password=request.session["password"])
except Exception, e:
pass
else:
+16 -16
View File
@@ -7,14 +7,14 @@ from django.core.urlresolvers import reverse
from modoboa.lib import parameters
from modoboa.lib.webutils import _render, _render_to_string, _render_error, \
ajax_response, ajax_simple_response
from modoboa.admin.lib import is_not_localadmin
from modoboa.lib.decorators import needs_mailbox
from modoboa.lib.connections import ConnectionError
from modoboa.auth.lib import get_password
from lib import *
from forms import *
@login_required
@is_not_localadmin()
@needs_mailbox()
def index(request, tplname="sievefilters/index.html"):
try:
sc = SieveClient(user=request.user.username,
@@ -38,14 +38,14 @@ def index(request, tplname="sievefilters/index.html"):
})
@login_required
@is_not_localadmin()
@needs_mailbox()
def get_templates(request, ftype):
if ftype == "condition":
return ajax_simple_response(FilterForm([], [], request).cond_templates)
return ajax_simple_response(FilterForm([], [], request).action_templates)
@login_required
@is_not_localadmin()
@needs_mailbox()
def get_user_folders(request):
from modoboa.extensions.webmail.lib import IMAPconnector
@@ -55,7 +55,7 @@ def get_user_folders(request):
return ajax_simple_response(ret)
@login_required
@is_not_localadmin()
@needs_mailbox()
def getfs(request, name):
sc = SieveClient(user=request.user.username,
password=request.session["password"])
@@ -109,7 +109,7 @@ def submitfilter(request, setname, okmsg, tplname, tplctx, update=False, sc=None
return ajax_response(request, status="ko", template=tplname, **tplctx)
@login_required
@is_not_localadmin()
@needs_mailbox()
def newfilter(request, setname, tplname="sievefilters/filter.html"):
ctx = {"title" : _("New filter"),
"actionurl" : reverse(newfilter, args=[setname])}
@@ -123,7 +123,7 @@ def newfilter(request, setname, tplname="sievefilters/filter.html"):
return _render(request, tplname, ctx)
@login_required
@is_not_localadmin()
@needs_mailbox()
def editfilter(request, setname, fname, tplname="sievefilters/filter.html"):
ctx = {"title" : _("Edit filter"),
"actionurl" : reverse(editfilter, args=[setname, fname])}
@@ -143,7 +143,7 @@ def editfilter(request, setname, fname, tplname="sievefilters/filter.html"):
return _render(request, tplname, ctx)
@login_required
@is_not_localadmin()
@needs_mailbox()
def removefilter(request, setname, fname):
sc = SieveClient(user=request.user.username,
password=request.session["password"])
@@ -154,7 +154,7 @@ def removefilter(request, setname, fname):
return ajax_response(request, "ko", respmsg=_("Failed to remove filter"))
@login_required
@is_not_localadmin()
@needs_mailbox()
def savefs(request, name):
if not request.POST.has_key("scriptcontent"):
return
@@ -168,7 +168,7 @@ def savefs(request, name):
return ajax_response(request, respmsg=_("Filters set saved"))
@login_required
@is_not_localadmin()
@needs_mailbox()
def new_filters_set(request, tplname="sievefilters/newfiltersset.html"):
ctx = {"title" : _("Create a new filters set"),
"fname" : "newfiltersset",
@@ -199,7 +199,7 @@ def new_filters_set(request, tplname="sievefilters/newfiltersset.html"):
return _render(request, tplname, ctx)
@login_required
@is_not_localadmin()
@needs_mailbox()
def remove_filters_set(request, name):
sc = SieveClient(user=request.user.username,
password=request.session["password"])
@@ -210,7 +210,7 @@ def remove_filters_set(request, name):
return ajax_response(request, respmsg=_("Filters set deleted"))
@login_required
@is_not_localadmin()
@needs_mailbox()
def activate_filters_set(request, name):
sc = SieveClient(user=request.user.username,
password=request.session["password"])
@@ -221,7 +221,7 @@ def activate_filters_set(request, name):
return ajax_response(request, respmsg=_("Filters set activated"))
@login_required
@is_not_localadmin()
@needs_mailbox()
def download_filters_set(request, name):
sc = SieveClient(user=request.user.username,
password=request.session["password"])
@@ -237,7 +237,7 @@ def download_filters_set(request, name):
return resp
@login_required
@is_not_localadmin()
@needs_mailbox()
def toggle_filter_state(request, setname, fname):
sc = SieveClient(user=request.user.username,
password=request.session["password"])
@@ -275,11 +275,11 @@ def move_filter(request, setname, fname, direction):
return ajax_response(request, template="sievefilters/filters.html", fs=fset)
@login_required
@is_not_localadmin()
@needs_mailbox()
def move_filter_up(request, setname, fname):
return move_filter(request, setname, fname, "up")
@login_required
@is_not_localadmin()
@needs_mailbox()
def move_filter_down(request, setname, fname):
return move_filter(request, setname, fname, "down")
+1 -1
View File
@@ -3,7 +3,7 @@
import os
import time
import datetime
import rrdtool
#import rrdtool
from django.utils.translation import ugettext as _
from modoboa.lib import parameters
+2 -2
View File
@@ -21,8 +21,7 @@ def infos():
"url" : "stats"
}
def init():
events.register("AdminMenuDisplay", menu)
def load():
parameters.register_admin("LOGFILE", type="string",
deflt="/var/log/mail.log",
help=_("Path to log file used to collect statistics"))
@@ -40,6 +39,7 @@ def urls(prefix):
return (r'^%sstats/' % prefix,
include('modoboa.extensions.stats.urls'))
@events.observe("AdminMenuDisplay")
def menu(target, user):
if target == "admin_menu_box":
return [
+1 -1
View File
@@ -259,7 +259,7 @@ class LogParser(object):
if __name__ == "__main__":
from modoboa.extensions.stats import main
main.init()
main.load()
log_file = parameters.get_admin("LOGFILE", app="stats")
rrd_rootdir = parameters.get_admin("RRD_ROOTDIR", app="stats")
+3 -8
View File
@@ -58,11 +58,7 @@ window.addEvent("domready", function() {
};
get_graph = function(evt) {
{% if user.is_superuser %}
var url = "{% url getgraph_root %}" + $("domain").value + "/";
{% else %}
var url = "{% url getgraph domain.id %}";
{% endif %}
var p = get_period();
if (p == "") {
@@ -72,9 +68,7 @@ window.addEvent("domready", function() {
onSuccess : getgraph_callback}).get();
};
{% if user.is_superuser %}
$("domain").addEvent("change", get_graph);
{% endif %}
$("period").addEvent("change", get_graph);
$("customsend").addEvent("click", get_graph);
});
@@ -84,17 +78,18 @@ window.addEvent("domready", function() {
{% block pagetitle %}{% trans "Statistics" %}{% endblock %}
{% block subcontent %}
<div id="selectors">
{% if user.is_superuser %}
<div id="domainbox">
<label for="domain">{% trans "Domain:" %}</label>
<select id="domain" name="domain">
{% if user.is_superuser %}
<option value="global" {% ifequal domain "global" %}selected{% endifequal %}>{% trans "Global" %}</option>
{% endif %}
{% for d in domains %}
<option value="{{ d.id }}" {% ifequal d.name domain %}selected{% endifequal %}>{{ d.name }}</option>
{% endfor %}
</select>
</div>
{% endif %}
<div id="periodbox">
<label for="period">{% trans "Period:" %}</label>
<select id="period" name="period">
+15 -15
View File
@@ -5,7 +5,7 @@ from django.core.urlresolvers import reverse
from django.utils.translation import ugettext as _, ugettext_noop
from django.utils import simplejson
from modoboa.lib.webutils import _render, _render_to_string, getctx
from modoboa.admin.views import good_domain
from modoboa.admin.lib import check_domain_access
from modoboa.admin.models import Domain, Mailbox
from django.contrib.auth.decorators \
import login_required, user_passes_test, permission_required
@@ -22,21 +22,17 @@ periods = [{"name" : "day", "label" : ugettext_noop("Day")},
{"name" : "custom", "label" : ugettext_noop("Custom")}]
@login_required
@good_domain
@permission_required("admin.view_mailboxes")
@check_domain_access
def index(request):
domains = None
period = request.GET.has_key("period") and request.GET["period"] or "day"
domid = request.GET.has_key("domid") and request.GET["domid"] or ""
if request.user.is_superuser:
domains = Domain.objects.all()
domain = "global"
else:
domain = Domain.objects.get(pk=domid)
period = request.GET.get("period", "day")
domid = request.GET.get("domid", "")
domains = request.user.get_domains()
return _render(request, 'stats/index.html', {
"domains" : domains,
"domain" : domain,
"graphs" : graph_list,
"periods" : periods,
"period" : period,
@@ -45,16 +41,20 @@ def index(request):
})
@login_required
@good_domain
@permission_required("admin.view_mailboxes")
def getgraph(request, dom_id):
period = request.GET.has_key("period") and request.GET["period"] or "day"
period = request.GET.get("period", "day")
if dom_id == "global":
if not request.user.is_superuser:
raise PermDeniedError(_("you're not allowed to see those statistics"))
domain = dom_id
else:
domain = Domain.objects.get(pk=dom_id).name
domain = Domain.objects.get(pk=dom_id)
if not request.user.can_access(domain):
raise PermDeniedError(_("You don't have access to this domain"))
ctx = None
tplvars = {"graphs" : graph_list, "period" : period, "domain" : domain}
tplvars = {"graphs" : graph_list, "period" : period, "domain" : domain.name}
if period == "custom":
if not request.GET.has_key("start") or not request.GET.has_key("end"):
ctx = getctx("ko", error=_("Bad custom period"))
@@ -64,7 +64,7 @@ def getgraph(request, dom_id):
G = Grapher()
period_name = "%s_%s" % (start.replace('-',''), end.replace('-',''))
for tpl_name in graph_list:
G.process(domain,
G.process(domain.name,
period_name,
str2Time(*start.split('-')),
str2Time(*end.split('-')),
+5 -6
View File
@@ -735,7 +735,6 @@ def send_mail(request, origmsg=None, posturl=None):
from email.mime.multipart import MIMEMultipart
from forms import ComposeMailForm
from modoboa.lib.webutils import getctx, ajax_simple_response
from modoboa.admin.models import Mailbox
from modoboa.auth.lib import get_password
form = ComposeMailForm(request.POST)
@@ -772,11 +771,11 @@ def send_mail(request, origmsg=None, posturl=None):
msg.attach(create_mail_attachment(attdef))
msg["Subject"] = form.cleaned_data["subject"]
address, domain = split_mailbox(form.cleaned_data["from_"])
try:
mb = Mailbox.objects.get(address=address, domain__name=domain)
msg["From"] = "%s <%s>" % (mb.name, form.cleaned_data["from_"])
except Mailbox.DoesNotExist:
if request.user.first_name != "" or request.user.last_name != "":
msg["From"] = "%s %s <%s>" % (request.user.first_name,
request.user.last_name,
form.cleaned_data["from_"])
else:
msg["From"] = form.cleaned_data["from_"]
msg["To"] = form.cleaned_data["to"]
msg["Message-ID"] = make_msgid()
+9 -8
View File
@@ -5,10 +5,7 @@ from django.conf.urls.defaults import include
from modoboa.lib import events, parameters
from modoboa.lib.webutils import static_url
def init():
events.register("UserMenuDisplay", menu)
events.register("UserLogout", userlogout)
def load():
parameters.register_admin("IMAP_SERVER", type="string",
deflt="127.0.0.1",
help=_("Address of your IMAP server"))
@@ -81,11 +78,14 @@ def urls(prefix):
return (r'^%swebmail/' % prefix,
include('modoboa.extensions.webmail.urls'))
@events.observe("UserMenuDisplay")
def menu(target, user):
import views
if target != "top_menu":
return []
if not user.has_mailbox:
return []
return [
{"name" : "webmail",
"label" : _("Webmail"),
@@ -93,14 +93,15 @@ def menu(target, user):
"img" : static_url("pics/webmail.png")}
]
def userlogout(**kwargs):
@events.observe("Userlogout")
def userlogout(request):
from lib import IMAPconnector
if kwargs["request"].user.id == 1:
if not request.user.has_mailbox:
return
try:
m = IMAPconnector(user=kwargs["request"].user.username,
password=kwargs["request"].session["password"])
m = IMAPconnector(user=request.user.username,
password=request.session["password"])
except Exception, e:
pass
else:
+19 -20
View File
@@ -9,13 +9,12 @@ from django.utils.translation import ugettext as _
from django.core.urlresolvers import reverse
from django.contrib.auth.decorators import login_required
from django.contrib import messages
from modoboa.admin.models import Mailbox
from modoboa.lib import parameters
from modoboa.lib.webutils import _render, _render_error, \
getctx, _render_to_string, ajax_response
from modoboa.lib.emailutils import split_mailbox
from modoboa.lib.email_listing import parse_search_parameters, Paginator
from modoboa.admin.lib import is_not_localadmin
from modoboa.lib.decorators import needs_mailbox
from modoboa.auth.lib import *
from lib import *
from forms import *
@@ -78,7 +77,7 @@ def __render_common_components(request, folder_name, lst=None, content=None, men
@login_required
@is_not_localadmin()
@needs_mailbox()
def folder(request, name, updatenav=True):
if not name:
name = "INBOX"
@@ -112,7 +111,7 @@ def folder(request, name, updatenav=True):
return HttpResponse(simplejson.dumps(ctx), mimetype="application/json")
@login_required
@is_not_localadmin()
@needs_mailbox()
def index(request):
try:
navp = request.session.has_key("navparams") \
@@ -134,7 +133,7 @@ def fetchmail(request, folder, mail_id, all=False):
return None
@login_required
@is_not_localadmin()
@needs_mailbox()
def viewmail(request, folder, mail_id=None):
from templatetags.webextras import viewm_menu
@@ -155,7 +154,7 @@ def viewmail(request, folder, mail_id=None):
return HttpResponse(simplejson.dumps(ctx), mimetype="application/json")
@login_required
@is_not_localadmin()
@needs_mailbox()
def getmailcontent(request, folder, mail_id):
msg = fetchmail(request, folder, mail_id, True)
if "class" in msg.keys() and msg["class"] == "unseen":
@@ -172,7 +171,7 @@ def getmailcontent(request, folder, mail_id):
})
@login_required
@is_not_localadmin()
@needs_mailbox()
def getattachment(request, folder, mail_id):
if request.GET.has_key("partnumber"):
headers = {"Content-Type" : "text/plain",
@@ -207,7 +206,7 @@ def getattachment(request, folder, mail_id):
raise Http404
@login_required
@is_not_localadmin()
@needs_mailbox()
def move(request):
for arg in ["msgset", "to"]:
if not request.GET.has_key(arg):
@@ -218,7 +217,7 @@ def move(request):
return folder(request, request.session["folder"], False)
@login_required
@is_not_localadmin()
@needs_mailbox()
def delete(request, fdname, mail_id):
mbc = IMAPconnector(user=request.user.username,
password=request.session["password"])
@@ -227,7 +226,7 @@ def delete(request, fdname, mail_id):
return HttpResponse(simplejson.dumps(ctx), mimetype="application/json")
@login_required
@is_not_localadmin()
@needs_mailbox()
def mark(request, name):
if not request.GET.has_key("status") or not request.GET.has_key("ids"):
return
@@ -240,7 +239,7 @@ def mark(request, name):
return folder(request, name, False)
@login_required
@is_not_localadmin()
@needs_mailbox()
def empty(request, name):
if name == parameters.get_user(request.user, "TRASH_FOLDER"):
mbc = IMAPconnector(user=request.user.username,
@@ -249,7 +248,7 @@ def empty(request, name):
return folder(request, name, False)
@login_required
@is_not_localadmin()
@needs_mailbox()
def compact(request, name):
mbc = IMAPconnector(user=request.user.username,
password=request.session["password"])
@@ -301,7 +300,7 @@ def render_compose(request, form, posturl, email=None, insert_signature=False):
return HttpResponse(simplejson.dumps(ctx), mimetype="application/json")
@login_required
@is_not_localadmin()
@needs_mailbox()
def compose(request):
if request.method == "POST":
status, resp = send_mail(request, posturl=reverse(compose))
@@ -312,7 +311,7 @@ def compose(request):
return render_compose(request, form, reverse(compose), insert_signature=True)
@login_required
@is_not_localadmin()
@needs_mailbox()
def reply(request, folder, mail_id):
msg = fetchmail(request, folder, mail_id, True)
if request.method == "POST":
@@ -331,7 +330,7 @@ def reply(request, folder, mail_id):
email)
@login_required
@is_not_localadmin()
@needs_mailbox()
def forward(request, folder, mail_id):
if request.method == "POST":
status, response = send_mail(request,
@@ -362,7 +361,7 @@ def separate_folder(fullname, sep="."):
return fullname, None
@login_required
@is_not_localadmin()
@needs_mailbox()
def newfolder(request, tplname="webmail/folder.html"):
mbc = IMAPconnector(user=request.user.username,
password=request.session["password"])
@@ -389,7 +388,7 @@ def newfolder(request, tplname="webmail/folder.html"):
return _render(request, tplname, ctx)
@login_required
@is_not_localadmin()
@needs_mailbox()
def editfolder(request, tplname="webmail/folder.html"):
mbc = IMAPconnector(user=request.user.username,
password=request.session["password"])
@@ -430,7 +429,7 @@ def editfolder(request, tplname="webmail/folder.html"):
return _render(request, tplname, ctx)
@login_required
@is_not_localadmin()
@needs_mailbox()
def delfolder(request):
if not request.GET.has_key("name") or request.GET["name"] == "":
return
@@ -440,7 +439,7 @@ def delfolder(request):
return ajax_response(request)
@login_required
@is_not_localadmin()
@needs_mailbox()
def attachments(request, tplname="webmail/attachments.html"):
if request.method == "POST":
csuploader = AttachmentUploadHandler()
@@ -475,7 +474,7 @@ def attachments(request, tplname="webmail/attachments.html"):
return _render(request, tplname, ctx)
@login_required
@is_not_localadmin()
@needs_mailbox()
def delattachment(request):
if not request.session.has_key("compose_mail") \
or not request.GET.has_key("name") \
+32 -8
View File
@@ -1,6 +1,4 @@
from django.contrib.auth.models import User
from modoboa.admin.models import Mailbox
from modoboa.auth.lib import _check_password
from modoboa.admin.models import User
class SimpleBackend:
@@ -13,11 +11,7 @@ class SimpleBackend:
user = User.objects.get(username=username)
except User.DoesNotExist:
return None
try:
mb = user.mailbox_set.all()[0]
except Exception:
return None
if not _check_password(password, mb.password):
if not user.check_password(password):
return None
return user
@@ -27,3 +21,33 @@ class SimpleBackend:
except User.DoesNotExist:
return None
try:
from django_auth_ldap.backend import LDAPBackend as orig_LDAPBackend, _LDAPUser
from modoboa.admin.models import User
class LDAPBackend(orig_LDAPBackend):
def get_or_create_user(self, username, ldap_user):
"""
This must return a (User, created) 2-tuple for the given LDAP user.
username is the Django-friendly username of the user. ldap_user.dn is
the user's DN and ldap_user.attrs contains all of their LDAP attributes.
"""
return User.objects.get_or_create(
username__iexact=username,
defaults={'username': username.lower()}
)
def get_user(self, user_id):
user = None
try:
user = User.objects.get(pk=user_id)
_LDAPUser(self, user=user) # This sets user.ldap_user
except User.DoesNotExist:
pass
return user
except ImportError:
pass
+22
View File
@@ -0,0 +1,22 @@
# coding: utf-8
"""
:mod:`decorators` --- common decorators
---------------------------------------
"""
from functools import wraps
from exceptions import NeedsMailboxException
def needs_mailbox():
"""Check if the current user owns at least one mailbox
Some applications (the webmail for example) need a mailbox to
work.
"""
def decorator(f):
@wraps(f)
def wrapped_f(request, *args, **kwargs):
if request.user.has_mailbox:
return f(request, *args, **kwargs)
raise NeedsMailboxException()
return wrapped_f
return decorator
+5 -3
View File
@@ -13,6 +13,8 @@ import inspect
import re
events = [
"SuperAdminPromotion",
"CanCreateDomain",
"CreateDomain",
"DeleteDomain",
@@ -87,8 +89,8 @@ class observe(object):
:param evtname: the event's name
"""
def __init__(self, evtname):
self.evtname = evtname
def __init__(self, *evtnames):
self.evtnames = evtnames
def __guess_extension_name(self, modname):
if modname.startswith('modoboa.extensions'):
@@ -112,7 +114,7 @@ class observe(object):
return []
return f(*args)
register(self.evtname, wrapped_f)
map(lambda evt: register(evt, wrapped_f), self.evtnames)
return wrapped_f
def unregister(event, callback):
+3
View File
@@ -16,3 +16,6 @@ class PermDeniedException(ModoboaException):
def __str__(self):
return _("Permission denied: %s" % self.msg)
class NeedsMailboxException(ModoboaException):
pass
+15 -7
View File
@@ -36,9 +36,9 @@ Extracted from `this blog
import sys
import ldap
import re
import base64
import base64, hashlib, string, crypt
from random import Random
from django.conf import settings
from modoboa.auth.lib import crypt_password
from modoboa.lib import parameters
class LDAPException(Exception): pass
@@ -81,7 +81,7 @@ class LDAPAuthBackend(object):
return None
return dn
def _crypt_password(self, clearpassword):
def _crypt_password(self, user, clearpassword):
"""Overidding of the crypt_password function (LDAP compliant)
The crypted password in base64 encoded and we prepend the used
@@ -91,10 +91,18 @@ class LDAPAuthBackend(object):
:return: the encrypted password
"""
scheme = parameters.get_admin("PASSWORD_SCHEME", app="admin")
if scheme == "clear":
if scheme == "crypt":
salt = ''.join(Random().sample(string.letters + string.digits, 2))
result = crypt.crypt(clearpassword, salt)
elif scheme == "md5":
obj = hashlib.md5(clearpassword)
result = obj.digest()
elif scheme == "sha256":
obj = hashlib.sha256(clearpassword)
result = obj.digest()
else:
return str(clearpassword)
crypted = crypt_password(clearpassword, False)
return str("{%s}%s" % (scheme.upper(), base64.encodestring(crypted)))
return str("{%s}%s" % (scheme.upper(), base64.b64encode(result)))
def update_user_password(self, user, password, newpassword):
try:
@@ -104,7 +112,7 @@ class LDAPAuthBackend(object):
newpassword = ('"%s"' % newpassword).encode('utf-16').lstrip('\377\376')
ldif = [(ldap.MOD_REPLACE,
self.pwd_attr,
self._crypt_password(newpassword))]
self._crypt_password(user, newpassword))]
conn.modify_s(dn, ldif)
except ldap.LDAPError, e:
raise LDAPException(str(e))
+1 -2
View File
@@ -155,7 +155,6 @@ def topredirection(request):
"""
from modoboa.lib import parameters
from modoboa.extensions import get_extension_infos
from modoboa.admin.lib import is_domain_admin
topredir = parameters.get_admin("DEFAULT_TOP_REDIRECTION", app="general")
if not topredir in ["admin", "userprefs"]:
@@ -165,7 +164,7 @@ def topredirection(request):
path = topredir
if topredir in ["admin", "stats"] and \
(not request.user.is_superuser and not is_domain_admin(request.user)):
request.user.belongs_to_group('SimpleUsers'):
path = "userprefs"
return HttpResponseRedirect(path)
+5
View File
@@ -1,6 +1,11 @@
{% 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 %}
{% load i18n %}
+1 -8
View File
@@ -1,7 +1,5 @@
from django import forms
from modoboa.admin.models import Domain, Mailbox
from modoboa.auth.lib import _check_password
from django.contrib.auth.models import check_password
from django.utils.translation import ugettext as _, ugettext_noop
from modoboa.lib.emailutils import split_mailbox
from modoboa.lib import parameters
@@ -25,12 +23,7 @@ class ChangePasswordForm(forms.Form):
if parameters.get_admin("AUTHENTICATION_TYPE", app="admin") != "local":
return self.cleaned_data["oldpassword"]
if not isinstance(self.target, Mailbox):
func = check_password
else:
func = _check_password
if not func(self.cleaned_data["oldpassword"],
self.target.password):
if not self.target.check_password(self.cleaned_data["oldpassword"]):
raise forms.ValidationError(_("Old password mismatchs"))
return self.cleaned_data["oldpassword"]
+3 -4
View File
@@ -5,7 +5,6 @@ from django.core.urlresolvers import reverse
from modoboa import userprefs
from modoboa.lib import events
from modoboa.lib.webutils import static_url
from modoboa.admin.lib import is_domain_admin
register = template.Library()
@@ -31,7 +30,7 @@ def options_menu(user):
]
},
]
if not user.is_superuser and not is_domain_admin(user):
if user.belongs_to_group('SimpleUsers'):
entries[0]["menu"] += [{
"name" : "setforwards",
"url" : reverse(userprefs.views.setforward),
@@ -41,7 +40,7 @@ def options_menu(user):
"rel" : "{handler:'iframe',size:{x:360,y:350},closeBtn:true}"
}]
entries[0]["menu"] += events.raiseQueryEvent("UserMenuDisplay", "options_menu")
entries[0]["menu"] += events.raiseQueryEvent("UserMenuDisplay", "options_menu", user)
return render_to_string('common/menulist.html',
{"entries" : entries, "user" : user})
@@ -49,7 +48,7 @@ def options_menu(user):
@register.simple_tag
def uprefs_menu(user):
entries = []
entries += events.raiseQueryEvent("UserMenuDisplay", "uprefs_menu")
entries += events.raiseQueryEvent("UserMenuDisplay", "uprefs_menu", user)
return render_to_string('common/menulist.html',
{"entries" : entries, "user" : user})
+13 -19
View File
@@ -9,7 +9,7 @@ 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
from modoboa.admin.lib import is_domain_admin, AdminError
from modoboa.admin.lib import AdminError
from modoboa.auth.lib import encrypt
@login_required
@@ -18,42 +18,36 @@ def index(request):
@login_required
def changepassword(request, tplname="userprefs/chpassword.html"):
if request.user.id == 1:
target = request.user
else:
target = Mailbox.objects.get(user=request.user.id)
res = events.raiseQueryEvent("PasswordChange", user=request.user)
res = events.raiseQueryEvent("PasswordChange", request.user)
if True in res:
ctx = dict(error=_("Password change is disabled for this user"))
return _render_error(request, errortpl="error_simple", user_context=ctx)
error = None
if request.method == "POST":
form = ChangePasswordForm(target, request.POST)
form = ChangePasswordForm(request.user, request.POST)
if form.is_valid():
if request.user.id != 1:
try:
target.set_password(form.cleaned_data["oldpassword"],
form.cleaned_data["confirmation"])
except AdminError, e:
error = str(e)
request.session["password"] = encrypt(request.POST["confirmation"])
try:
request.user.set_password(form.cleaned_data["confirmation"],
form.cleaned_data["oldpassword"])
except AdminError, e:
error = str(e)
else:
target.set_password(request.POST["confirmation"])
target.save()
request.session["password"] = encrypt(request.POST["confirmation"])
request.user.save()
if error is None:
return ajax_response(request, respmsg=_("Password changed"))
return ajax_response(request, status="ko", template=tplname,
form=form, error=error)
form = ChangePasswordForm(target)
form = ChangePasswordForm(request.user)
return _render(request, 'userprefs/chpassword.html', {
"form" : form
})
@login_required
@user_passes_test(lambda u: not u.is_superuser and not is_domain_admin(u))
@user_passes_test(lambda u: u.belongs_to_group('SimpleUsers'))
def setforward(request, tplname="userprefs/setforward.html"):
mb = Mailbox.objects.get(user=request.user.id)
try: