From 88fc62f89ce31bf7c1bc53eda321624bfe410c40 Mon Sep 17 00:00:00 2001 From: Antoine Nguyen Date: Mon, 26 Dec 2011 15:31:20 +0100 Subject: [PATCH 01/15] Do not try to fetch the mailbox associated to an alias if this last one is not defined! see #178 --- userprefs/views.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/userprefs/views.py b/userprefs/views.py index 807acc44a..6debcf45f 100644 --- a/userprefs/views.py +++ b/userprefs/views.py @@ -78,12 +78,12 @@ def setforward(request, tplname="userprefs/setforward.html"): form = ForwardForm() if al is not None: form.fields["dest"].initial = al.extmboxes - try: - selfmb = al.mboxes.get(pk=mb.id) - except Mailbox.DoesNotExist: - pass - else: - form.fields["keepcopies"].initial = True + try: + selfmb = al.mboxes.get(pk=mb.id) + except Mailbox.DoesNotExist: + pass + else: + form.fields["keepcopies"].initial = True return _render(request, tplname, { "form" : form }) From bdad3b675fe94c1da04b379dd6a075282145ada2 Mon Sep 17 00:00:00 2001 From: Antoine Nguyen Date: Tue, 27 Dec 2011 16:18:21 +0100 Subject: [PATCH 02/15] Fixed bad import statement. see #168 --- auth/views.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/auth/views.py b/auth/views.py index 6a08f5d7d..07c99ab03 100755 --- a/auth/views.py +++ b/auth/views.py @@ -6,7 +6,7 @@ from django.shortcuts import render_to_response from django.core.urlresolvers import reverse from django.utils.translation import ugettext as _ from modoboa.lib import events -import modoboa.admin +from modoboa.admin import views as admviews from forms import LoginForm from modoboa.auth.lib import * @@ -32,7 +32,7 @@ def dologin(request): next = request.POST["next"] if next.strip() in ["", "None"]: - next = reverse(modoboa.admin.views.domains) + next = reverse(admviews.domains) return HttpResponseRedirect(next) else: error = _("Account is disabled.") From 880880a374e960a05515e63ec4a2ee84475b4db5 Mon Sep 17 00:00:00 2001 From: Antoine Nguyen Date: Wed, 28 Dec 2011 11:58:50 +0100 Subject: [PATCH 03/15] We convert the given password to a string to avoid errors with the crypto module. fix #193 --- auth/lib.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/auth/lib.py b/auth/lib.py index 8592cad86..160cd67e7 100644 --- a/auth/lib.py +++ b/auth/lib.py @@ -8,10 +8,11 @@ import base64 from modoboa.lib import parameters def encrypt(clear): - obj = AES.new(parameters.get_admin("SECRET_KEY"), - AES.MODE_ECB) + key = parameters.get_admin("SECRET_KEY") + obj = AES.new(key, AES.MODE_ECB) + clear = str(clear) if len(clear) % AES.block_size: - clear += "\0" * (AES.block_size - len(clear) % AES.block_size) + clear += " " * (AES.block_size - len(clear) % AES.block_size) ciph = obj.encrypt(clear) ciph = base64.b64encode(ciph) return ciph @@ -20,7 +21,7 @@ def decrypt(ciph): obj = AES.new(parameters.get_admin("SECRET_KEY"), AES.MODE_ECB) ciph = base64.b64decode(ciph) clear = obj.decrypt(ciph) - return clear.rstrip('\0') + return clear.rstrip(' ') def get_password(request): return decrypt(request.session["password"]) From 96dfb877ae0cee6a0dbc73a52635a53a820f9791 Mon Sep 17 00:00:00 2001 From: Antoine Nguyen Date: Wed, 28 Dec 2011 13:30:01 +0100 Subject: [PATCH 04/15] Support for LDAP password modification (user level only for now). I need a working environment to check if postfix/dovecot accept the way I encrypt passwords. see #192 --- admin/__init__.py | 4 +++ admin/models.py | 89 ++++++++++++++++++++++++++++++---------------- auth/lib.py | 5 +-- userprefs/forms.py | 4 +++ userprefs/views.py | 19 ++++++---- 5 files changed, 82 insertions(+), 39 deletions(-) diff --git a/admin/__init__.py b/admin/__init__.py index c886038f4..2d8a8ff17 100644 --- a/admin/__init__.py +++ b/admin/__init__.py @@ -5,6 +5,10 @@ from django.utils.translation import ugettext as _ from django.db.utils import DatabaseError from models import Extension +parameters.register_admin("AUTHENTICATION_TYPE", type="list", deflt="local", + values=[('local', _("Local")), + ('ldap', "LDAP")], + help=_("The backend used for authentication")) parameters.register_admin("CREATE_DIRECTORIES", type="list_yesno", deflt="yes", help=_("Modoboa will handle mailbox creation on filesystem")) parameters.register_admin("STORAGE_PATH", type="string", deflt="/var/vmail", diff --git a/admin/models.py b/admin/models.py index f0895b644..bf3e764ef 100644 --- a/admin/models.py +++ b/admin/models.py @@ -6,6 +6,7 @@ from django.conf import settings from modoboa.lib import parameters, events from modoboa.lib.sysutils import exec_cmd, exec_as_vuser from modoboa.lib.emailutils import split_mailbox +from modoboa.lib.ldaputils import * from modoboa.auth.lib import crypt_password import os import pwd @@ -291,6 +292,26 @@ class Mailbox(DatesAware): 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(value) + self.save() + return + + 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 @@ -307,16 +328,16 @@ class Mailbox(DatesAware): # If we ever had numerical uid, don't resolve it v_uid = parameters.get_admin("VIRTUAL_UID"); if v_uid.isdigit(): - self.uid = v_uid + self.uid = v_uid else: - self.uid = pwd.getpwnam(parameters.get_admin("VIRTUAL_UID")).pw_uid + self.uid = pwd.getpwnam(parameters.get_admin("VIRTUAL_UID")).pw_uid # Just the same for gid v_gid = parameters.get_admin("VIRTUAL_GID") - if v_gid.isdigit: - self.gid = v_gid + if v_gid.isdigit(): + self.gid = v_gid else: - self.gid = pwd.getpwnam(parameters.get_admin("VIRTUAL_GID")).pw_gid - self.quota = self.domain.quota + self.gid = pwd.getpwnam(parameters.get_admin("VIRTUAL_GID")).pw_gid + self.quota = self.domain.quota super(Mailbox, self).save() def create_from_csv(self, line): @@ -489,31 +510,39 @@ class Extension(models.Model): events.raiseEvent("ExtDisabled", ext=self) -# -# Optional callback to execute if django-auth-ldap is enabled. -# +def populate_callback(sender, user=None, **kwargs): + """Populate signal callback + + If the LDAP authentication backend is in use, this callback will + be called each time a new user authenticates succesfuly to + Modoboa. This function is in charge of creating the mailbox + associated to the provided ``User`` object. + + :param sender: ?? + :param user: a ``User`` instance + """ + if parameters.get_admin("AUTHENTICATION_TYPE") != "ldap": + return + if user is None: + return + localpart, domname = split_mailbox(user.username) + try: + domain = Domain.objects.get(name=domname) + except Domain.DoesNotExist: + domain = Domain() + domain.name = domname + domain.enabled = True + domain.quota = 0 + domain.save() + try: + mb = Mailbox.objects.get(domain=domain, address=localpart) + except Mailbox.DoesNotExist: + mb = Mailbox() + mb.save_from_user(localpart, domain, user) + try: from django_auth_ldap.backend import populate_user - - def populate_callback(sender, user=None, **kwargs): - if user is None: - return - localpart, domname = split_mailbox(user.username) - try: - domain = Domain.objects.get(name=domname) - except Domain.DoesNotExist: - domain = Domain() - domain.name = domname - domain.enabled = True - domain.quota = 0 - domain.save() - try: - mb = Mailbox.objects.get(domain=domain, address=localpart) - except Mailbox.DoesNotExist: - mb = Mailbox() - mb.save_from_user(localpart, domain, user) - - populate_user.connect(populate_callback, dispatch_uid="myuid") - except ImportError, inst: pass +else: + populate_user.connect(populate_callback, dispatch_uid="myuid") diff --git a/auth/lib.py b/auth/lib.py index 160cd67e7..33b541601 100644 --- a/auth/lib.py +++ b/auth/lib.py @@ -34,11 +34,12 @@ def _check_password(password, crypted): return hashlib.md5(password).hexdigest() == crypted return password -def crypt_password(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": - return hashlib.md5(password).hexdigest() + obj = hashlib.md5(password) + return obj.hexdigest() if hexdigest else obj.digest() return password diff --git a/userprefs/forms.py b/userprefs/forms.py index 4bdd5ccd1..0d8a061b3 100644 --- a/userprefs/forms.py +++ b/userprefs/forms.py @@ -4,6 +4,7 @@ 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 class BadDestination(Exception): pass @@ -21,6 +22,9 @@ class ChangePasswordForm(forms.Form): self.target = target def clean_oldpassword(self): + if parameters.get_admin("AUTHENTICATION_TYPE", app="admin") != "local": + return self.cleaned_data["oldpassword"] + if not isinstance(self.target, Mailbox): func = check_password else: diff --git a/userprefs/views.py b/userprefs/views.py index 6debcf45f..7a23cf883 100644 --- a/userprefs/views.py +++ b/userprefs/views.py @@ -8,9 +8,9 @@ from django.utils.translation import ugettext as _ from modoboa.lib import parameters, events from modoboa.lib.webutils import _render, _render_error, ajax_response from forms import * -from modoboa.admin.models import Mailbox, Alias +from modoboa.admin.models import Mailbox, Alias, AdminError from modoboa.admin.lib import is_domain_admin -from modoboa.auth.lib import encrypt, crypt_password +from modoboa.auth.lib import encrypt @login_required def index(request): @@ -31,13 +31,18 @@ def changepassword(request, tplname="userprefs/chpassword.html"): if request.method == "POST": form = ChangePasswordForm(target, request.POST) if form.is_valid(): - if request.user.id != 1: - target.password = crypt_password(request.POST["confirmation"]) + 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"]) else: target.set_password(request.POST["confirmation"]) - request.session["password"] = encrypt(request.POST["confirmation"]) - target.save() - return ajax_response(request, respmsg=_("Password changed")) + + if error is None: + return ajax_response(request, respmsg=_("Password changed")) return ajax_response(request, status="ko", template=tplname, form=form, error=error) From babea7e61def9efaaddd7cdcd9225da68fd7bdbb Mon Sep 17 00:00:00 2001 From: Antoine Nguyen Date: Wed, 28 Dec 2011 13:30:42 +0100 Subject: [PATCH 05/15] Added missing file. see #192 --- lib/ldaputils.py | 109 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 109 insertions(+) create mode 100644 lib/ldaputils.py diff --git a/lib/ldaputils.py b/lib/ldaputils.py new file mode 100644 index 000000000..3db6841ff --- /dev/null +++ b/lib/ldaputils.py @@ -0,0 +1,109 @@ +# coding: utf-8 + +""" +:mod:`ldaputils` --- a collection of LDAP based classes/functions +----------------------------------------------------------------- + +For a first version, the LDAP support offered by Modoboa only supports +one global server definition : the one the django-auth-ldap uses. + +Extra information about Active Directory +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Extracted from `this blog +`_: + +- Active Directory doesn't appear to support the password modify + extended operation, so you must change passwords using a normal LDAP + modify operation. + +- Active Directory stores passwords in the unicodePwd attribute, + rather than userPassword. + +- Active Directory will only accept password changes over secure + connections. I have only ever used SSL. It may be that you can also + use StartTLS, or perhaps SASL with confidentiality, but I'm not sure + about that. + +- The new password must be enclosed in quotation marks, and it must + use a UTF-16 little-endian encoding. + +- Active Directory may impose some strength requirements on the + password, although exactly what those requirements are may vary from + one instance to another. + +""" +import ldap +import re +import base64 +from django.conf import settings +from modoboa.auth.lib import crypt_password +from modoboa.lib import parameters + +class LDAPException(Exception): pass + +class LDAPAuthBackend(object): + def __init__(self): + self.server_uri = self._setting("AUTH_LDAP_SERVER_URI", "ldap://localhost") + self.user_base = self._setting("LDAP_USER_BASE", "") + self.user_filter = self._setting("LDAP_USER_FILTER", "") + self.bind_dn = self._setting("AUTH_LDAP_BIND_DN", "") + self.bind_pwd = self._setting("AUTH_LDAP_BIND_PASSWORD", "") + self.pwd_attr = self._setting("LDAP_PASSWORD_ATTR", "userPassword") + self.ldap_ad = self._setting("LDAP_ACTIVE_DIRECTORY", False) + + self.conn = self._get_conn(self.bind_dn, self.bind_pwd) + + def _setting(self, name, default): + try: + value = getattr(settings, name) + except AttributeError: + value = default + return value + + def _get_conn(self, bind_dn, bind_pwd): + conn = ldap.initialize(self.server_uri) + conn.set_option(ldap.OPT_X_TLS_DEMAND, True) + conn.set_option(ldap.OPT_DEBUG_LEVEL, 255) + conn.simple_bind_s(bind_dn, bind_pwd) + return conn + + def _expand_filter(self, rawvalue, **kwargs): + return rawvalue % kwargs + + def _find_user_dn(self, user): + filtr = self._expand_filter(self.user_filter, user=user) + res = self.conn.search_s(self.user_base, ldap.SCOPE_SUBTREE, filtr) + try: + dn = res[0][0] + except IndexError: + return None + return dn + + def _crypt_password(self, clearpassword): + """Overidding of the crypt_password function (LDAP compliant) + + The crypted password in base64 encoded and we prepend the used + algorithm to the returned value. (between {}) + + :param clearpassword: the clear password + :return: the encrypted password + """ + scheme = parameters.get_admin("PASSWORD_SCHEME", app="admin") + crypted = crypt_password(clearpassword, False) + return str("{%s}%s" % (scheme.upper(), base64.encodestring(crypted))) + + def update_user_password(self, user, password, newpassword): + try: + dn = self._find_user_dn(user) + conn = self._get_conn(dn, password) + if self.ldap_ad: + newpassword = ('"%s"' % newpassword).encode('utf-16').lstrip('\377\376') + ldif = [(ldap.MOD_REPLACE, + self.pwd_attr, + self._crypt_password(newpassword))] + conn.modify_s(dn, ldif) + except ldap.LDAPError, e: + raise LDAPException(str(e)) + + From 361cf04106c04c23ba72ed8ebef7e4ff636ff9a0 Mon Sep 17 00:00:00 2001 From: Antoine Nguyen Date: Wed, 28 Dec 2011 15:39:52 +0100 Subject: [PATCH 06/15] Special case for clear passwords, do not touch them! see #192 --- lib/ldaputils.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/ldaputils.py b/lib/ldaputils.py index 3db6841ff..9d9f42143 100644 --- a/lib/ldaputils.py +++ b/lib/ldaputils.py @@ -90,6 +90,8 @@ class LDAPAuthBackend(object): :return: the encrypted password """ scheme = parameters.get_admin("PASSWORD_SCHEME", app="admin") + if scheme == "clear": + return str(clearpassword) crypted = crypt_password(clearpassword, False) return str("{%s}%s" % (scheme.upper(), base64.encodestring(crypted))) From 8ffed984309816a33695bd38117ab0f5d34b526f Mon Sep 17 00:00:00 2001 From: Antoine Nguyen Date: Mon, 9 Jan 2012 14:42:30 +0100 Subject: [PATCH 07/15] Default folders can also have children. fix #177 --- extensions/webmail/lib.py | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/extensions/webmail/lib.py b/extensions/webmail/lib.py index dd3e759b3..c823c075b 100644 --- a/extensions/webmail/lib.py +++ b/extensions/webmail/lib.py @@ -213,10 +213,9 @@ class IMAPconnector(object): sdescr["class"] = "subfolders" return True - def _listfolders(self, topfolder='INBOX', md_folders=[]): + def _listfolders(self, topfolder='INBOX', folders=[]): list_response_pattern = re.compile(r'\((?P.*?)\) "(?P.*)" (?P.*)') (status, data) = self.m.list() - result = [] for mb in data: flags, delimiter, name = list_response_pattern.match(mb).groups() name = name.strip('"').decode("imap4-utf-7") @@ -229,16 +228,20 @@ class IMAPconnector(object): parts[1:]): descr["class"] = "subfolders" continue - present = False + + pos = -1 descr = {"name" : name} - for mdf in md_folders: + for idx, mdf in enumerate(folders): if mdf["name"] == name: - present = True + pos = idx break - if not present: - result += [descr] + if pos == -1: + folders += [descr] + else: + folders[idx] = descr + from operator import itemgetter - return sorted(result, key=itemgetter("name")) + return sorted(folders, key=itemgetter("name")) def getfolders(self, user, unseen_messages=True): md_folders = [{"name" : "INBOX", "class" : "inbox"}, @@ -248,7 +251,8 @@ class IMAPconnector(object): {"name" : parameters.get_user(user, "SENT_FOLDER")}, {"name" : parameters.get_user(user, "TRASH_FOLDER"), "class" : "trash"}] - md_folders += self._listfolders(md_folders=md_folders) + self._listfolders(folders=md_folders) + if unseen_messages: for fd in md_folders: key = fd.has_key("path") and "path" or "name" From baec1f740e8ddb7f2e41c2cc3cfd3730a25d82c4 Mon Sep 17 00:00:00 2001 From: Antoine Nguyen Date: Mon, 9 Jan 2012 16:06:04 +0100 Subject: [PATCH 08/15] The 'save' button has been moved into the footer bar. Its position is now always the same. fix #180 --- static/css/default.css | 8 +++++++- templates/admin/parameters.html | 4 ++++ templates/common/parameters.html | 1 - templates/userprefs/preferences.html | 3 +++ 4 files changed, 14 insertions(+), 2 deletions(-) diff --git a/static/css/default.css b/static/css/default.css index ecf374c79..6d5a5ed31 100644 --- a/static/css/default.css +++ b/static/css/default.css @@ -483,4 +483,10 @@ div.row label { .element { margin: 10px 5px 10px 5px; -} \ No newline at end of file +} + +/* Parameters or Preferences */ +#footer > #update { + color: #000000; + margin: 2px; +} diff --git a/templates/admin/parameters.html b/templates/admin/parameters.html index c8b8fd2b4..af17aa99b 100644 --- a/templates/admin/parameters.html +++ b/templates/admin/parameters.html @@ -1,4 +1,5 @@ {% extends "admin/settings_header.html" %} +{% load i18n %} {% block menu %} {% load admin_extras %} {% settings_menu "parameters" user %} @@ -9,3 +10,6 @@ {% include "common/parameters.html" %} {% endblock %} +{% block footer %} + +{% endblock %} diff --git a/templates/common/parameters.html b/templates/common/parameters.html index 50102aa16..a934f9e70 100644 --- a/templates/common/parameters.html +++ b/templates/common/parameters.html @@ -1,7 +1,6 @@ {% load i18n %} {% load admin_extras %} {% if gparams %} -

{% for app in gparams %}

{{ app.name }}

diff --git a/templates/userprefs/preferences.html b/templates/userprefs/preferences.html index 5fd09e9a5..401b56059 100644 --- a/templates/userprefs/preferences.html +++ b/templates/userprefs/preferences.html @@ -14,3 +14,6 @@ {% include "common/parameters.html" %} {% endblock %} +{% block footer %} + +{% endblock %} From ba0ed344c58ecfa3cf9ef4da004719ddf0f71c63 Mon Sep 17 00:00:00 2001 From: Antoine Nguyen Date: Tue, 10 Jan 2012 10:40:22 +0100 Subject: [PATCH 09/15] Simple hack to catch redirections after an ajax request. It is not a clean and perfect solution but it should address most cases. fix #181 --- .../amavis_quarantine/static/js/quarantine.js | 2 +- extensions/sievefilters/static/js/lib.js | 10 +++---- extensions/stats/templates/stats/index.html | 4 +-- extensions/webmail/lib.py | 3 ++ extensions/webmail/static/js/fdmenu.js | 2 +- extensions/webmail/static/js/webmail.js | 18 ++++++----- .../templates/webmail/attachments.html | 2 +- lib/middleware.py | 9 +++++- settings_sample.py | 1 + static/js/anchor_nav.js | 3 ++ static/js/common.js | 2 +- static/js/utils.js | 30 +++++++++++++++++++ templates/admin/add_domain_admin.html | 4 ++- templates/admin/mbaliasform.html | 3 +- templates/layout_simple.html | 1 + templates/nlayout.html | 1 + 16 files changed, 74 insertions(+), 21 deletions(-) create mode 100644 static/js/utils.js diff --git a/extensions/amavis_quarantine/static/js/quarantine.js b/extensions/amavis_quarantine/static/js/quarantine.js index 8e34dd589..5a6347947 100644 --- a/extensions/amavis_quarantine/static/js/quarantine.js +++ b/extensions/amavis_quarantine/static/js/quarantine.js @@ -30,7 +30,7 @@ function actioncallback(resp) { } function actionclick(evt) { - new Request.JSON({ + new Request.JSON.mdb({ url: evt.target.get("href"), onSuccess: actioncallback }).get(); diff --git a/extensions/sievefilters/static/js/lib.js b/extensions/sievefilters/static/js/lib.js index d2c19da31..39add7588 100644 --- a/extensions/sievefilters/static/js/lib.js +++ b/extensions/sievefilters/static/js/lib.js @@ -14,7 +14,7 @@ function removefilter(evt) { if (!confirm(gettext("Remove this filter?"))) { return; } - new Request.JSON({ + new Request.JSON.mdb({ url: this.get("href"), onSuccess: function(response) { if (response.status == "ok") { @@ -72,7 +72,7 @@ function loadfs(response) { * (remove, activate) */ function send_command(cmd, extracb) { - new Request.JSON({ + new Request.JSON.mdb({ url: cmd, onSuccess: function(response) { if (response.status == "ko") { @@ -116,7 +116,7 @@ function savefs(evt) { if (!check_prereqs(evt, gettext("Save changes?"))) { return; } - new Request.JSON({ + new Request.JSON.mdb({ url: $("feditor").get("action"), method: $("feditor").get("method"), data: $("feditor").toQueryString(), @@ -177,7 +177,7 @@ function activatefs(evt) { function toggle_filter_state(event) { event.stop(); - new Request.JSON({ + new Request.JSON.mdb({ url: this.get("href"), onSuccess: function(response) { if (response.status == "ok") { @@ -193,7 +193,7 @@ function toggle_filter_state(event) { function move_filter(event) { event.stop(); - new Request.JSON({ + new Request.JSON.mdb({ url: this.get("href"), onSuccess: function(response) { $("filters_list").set("html", response.content); diff --git a/extensions/stats/templates/stats/index.html b/extensions/stats/templates/stats/index.html index 6e33f0f4f..bd041abbd 100644 --- a/extensions/stats/templates/stats/index.html +++ b/extensions/stats/templates/stats/index.html @@ -68,8 +68,8 @@ window.addEvent("domready", function() { if (p == "") { return; } - new Request.JSON({url : url + "?" + p, noCache: true, - onSuccess : getgraph_callback}).get(); + new Request.JSON.mdb({url : url + "?" + p, noCache: true, + onSuccess : getgraph_callback}).get(); }; {% if user.is_superuser %} diff --git a/extensions/webmail/lib.py b/extensions/webmail/lib.py index c823c075b..8d9281820 100644 --- a/extensions/webmail/lib.py +++ b/extensions/webmail/lib.py @@ -691,6 +691,9 @@ def html2plaintext(content): return plaintext def get_current_url(request): + if not request.session.has_key("folder"): + return "" + res = "%s?page=%s" % (request.session["folder"], request.session["page"]) for p in ["criteria", "pattern", "order"]: if p in request.session.keys(): diff --git a/extensions/webmail/static/js/fdmenu.js b/extensions/webmail/static/js/fdmenu.js index 721d69ecd..570d6e80d 100644 --- a/extensions/webmail/static/js/fdmenu.js +++ b/extensions/webmail/static/js/fdmenu.js @@ -46,7 +46,7 @@ var FdMenu = new Class({ if (!confirm(gettext("Delete folder?"))) { return; } - new Request.JSON({ + new Request.JSON.mdb({ url: evt.target.get("href"), method: "GET", onSuccess: function(resp) { diff --git a/extensions/webmail/static/js/webmail.js b/extensions/webmail/static/js/webmail.js index 74d6f283b..bdc67251c 100644 --- a/extensions/webmail/static/js/webmail.js +++ b/extensions/webmail/static/js/webmail.js @@ -219,13 +219,17 @@ function refreshFolder() { profile : "gray", spinner : true }); - new Request.JSON({url: query, noCache : true, onSuccess: function(resp) { - var callback = ($defined(resp.callback)) ? resp.callback : "default"; + new Request.JSON.mdb({ + url: query, + noCache : true, + onSuccess: function(resp) { + var callback = ($defined(resp.callback)) ? resp.callback : "default"; - current_anchor.get_callback(callback)(resp); - infobox.info(gettext("Done")); - infobox.hide(1); - }}).get(); + current_anchor.get_callback(callback)(resp); + infobox.info(gettext("Done")); + infobox.hide(1); + } + }).get(); } /* @@ -240,7 +244,7 @@ function simple_request(url, params) { profile: "gray", spinner: true }); - new Request.JSON({ + new Request.JSON.mdb({ url : url, onSuccess : function(response) { if (response.status == "ok") { diff --git a/extensions/webmail/templates/webmail/attachments.html b/extensions/webmail/templates/webmail/attachments.html index d1660e605..bf4f2e591 100644 --- a/extensions/webmail/templates/webmail/attachments.html +++ b/extensions/webmail/templates/webmail/attachments.html @@ -6,7 +6,7 @@ + {% url django.views.i18n.javascript_catalog as catalog_url %} {% block html_head %}{% endblock %} diff --git a/templates/nlayout.html b/templates/nlayout.html index 3e34463f8..fe931702a 100644 --- a/templates/nlayout.html +++ b/templates/nlayout.html @@ -14,6 +14,7 @@ + {% url django.views.i18n.javascript_catalog as catalog_url %}