3614: djblets prevents handling of generic django IntegrityErrors

mrc***@gmai***** (Google Code) (Is this you? Claim this profile.)
What version are you running?

2.0.8

What's the URL of the page containing the problem?

account/login/

What steps will reproduce the problem?
1. Create a simple extension with the AuthBackend below
2. Try logging in with any username and password.

What is the expected output? What do you see instead?

In the logs, we should see "INTEGRITY ERROR" and a traceback.  Instead, we get the following traceback:

UNIQUE constraint failed: auth_user.username
Traceback (most recent call last):
  File "/home/mcote/experiment/reviewboard/local/lib/python2.7/site-packages/Django-1.6.7-py2.7.egg/django/core/handlers/base.py", line 112, in get_response
    response = wrapped_callback(request, *callback_args, **callback_kwargs)
  File "/home/mcote/experiment/reviewboard/local/lib/python2.7/site-packages/Django-1.6.7-py2.7.egg/django/views/decorators/debug.py", line 75, in sensitive_post_parameters_wrapper
    return view(request, *args, **kwargs)
  File "/home/mcote/experiment/reviewboard/local/lib/python2.7/site-packages/Django-1.6.7-py2.7.egg/django/utils/decorators.py", line 99, in _wrapped_view
    response = view_func(request, *args, **kwargs)
  File "/home/mcote/experiment/reviewboard/local/lib/python2.7/site-packages/Django-1.6.7-py2.7.egg/django/views/decorators/cache.py", line 52, in _wrapped_view_func
    response = view_func(request, *args, **kwargs)
  File "/home/mcote/experiment/reviewboard/local/lib/python2.7/site-packages/Django-1.6.7-py2.7.egg/django/contrib/auth/views.py", line 36, in login
    if form.is_valid():
  File "/home/mcote/experiment/reviewboard/local/lib/python2.7/site-packages/Django-1.6.7-py2.7.egg/django/forms/forms.py", line 129, in is_valid
    return self.is_bound and not bool(self.errors)
  File "/home/mcote/experiment/reviewboard/local/lib/python2.7/site-packages/Django-1.6.7-py2.7.egg/django/forms/forms.py", line 121, in errors
    self.full_clean()
  File "/home/mcote/experiment/reviewboard/local/lib/python2.7/site-packages/Django-1.6.7-py2.7.egg/django/forms/forms.py", line 274, in full_clean
    self._clean_form()
  File "/home/mcote/experiment/reviewboard/local/lib/python2.7/site-packages/Django-1.6.7-py2.7.egg/django/forms/forms.py", line 300, in _clean_form
    self.cleaned_data = self.clean()
  File "/home/mcote/experiment/reviewboard/local/lib/python2.7/site-packages/Django-1.6.7-py2.7.egg/django/contrib/auth/forms.py", line 189, in clean
    password=password)
  File "/home/mcote/experiment/reviewboard/local/lib/python2.7/site-packages/Django-1.6.7-py2.7.egg/django/contrib/auth/__init__.py", line 49, in authenticate
    user = backend.authenticate(**credentials)
  File "/home/mcote/experiment/reviewboard/src/integritytest/integritytest/extension.py", line 35, in authenticate
    email='foo@example.org')
  File "/home/mcote/experiment/reviewboard/local/lib/python2.7/site-packages/Django-1.6.7-py2.7.egg/django/db/models/manager.py", line 157, in create
    return self.get_queryset().create(**kwargs)
  File "/home/mcote/experiment/reviewboard/local/lib/python2.7/site-packages/Django-1.6.7-py2.7.egg/django/db/models/query.py", line 322, in create
    obj.save(force_insert=True, using=self.db)
  File "/home/mcote/experiment/reviewboard/local/lib/python2.7/site-packages/Django-1.6.7-py2.7.egg/django/db/models/base.py", line 545, in save
    force_update=force_update, update_fields=update_fields)
  File "/home/mcote/experiment/reviewboard/local/lib/python2.7/site-packages/Django-1.6.7-py2.7.egg/django/db/models/base.py", line 573, in save_base
    updated = self._save_table(raw, cls, force_insert, force_update, using, update_fields)
  File "/home/mcote/experiment/reviewboard/local/lib/python2.7/site-packages/Django-1.6.7-py2.7.egg/django/db/models/base.py", line 654, in _save_table
    result = self._do_insert(cls._base_manager, using, fields, update_pk, raw)
  File "/home/mcote/experiment/reviewboard/local/lib/python2.7/site-packages/Django-1.6.7-py2.7.egg/django/db/models/base.py", line 687, in _do_insert
    using=using, raw=raw)
  File "/home/mcote/experiment/reviewboard/local/lib/python2.7/site-packages/Django-1.6.7-py2.7.egg/django/db/models/manager.py", line 232, in _insert
    return insert_query(self.model, objs, fields, **kwargs)
  File "/home/mcote/experiment/reviewboard/local/lib/python2.7/site-packages/Django-1.6.7-py2.7.egg/django/db/models/query.py", line 1514, in insert_query
    return query.get_compiler(using=using).execute_sql(return_id)
  File "/home/mcote/experiment/reviewboard/local/lib/python2.7/site-packages/Django-1.6.7-py2.7.egg/django/db/models/sql/compiler.py", line 903, in execute_sql
    cursor.execute(sql, params)
  File "/home/mcote/experiment/reviewboard/local/lib/python2.7/site-packages/Djblets-0.8.11-py2.7.egg/djblets/log/middleware.py", line 52, in execute
    return self.cursor.execute(sql, params)
  File "/home/mcote/experiment/reviewboard/local/lib/python2.7/site-packages/Django-1.6.7-py2.7.egg/django/db/backends/sqlite3/base.py", line 452, in execute
    return Database.Cursor.execute(self, query, params)
IntegrityError: UNIQUE constraint failed: auth_user.username

What operating system are you using? What browser?

Ubuntu 14.04, Firefox Nightly.

Please provide any additional information below.

It seems that djblets is somehow preventing sqlite's IntegrityError from being captured by handlers looking for django.db.IntegrityError.  If you try adding a user with a duplicate user from a manage.py shell, you *can* catch it with "except django.db.IntegrityError".  The traceback is slightly different as well (note that "INTEGRITY ERROR" was printed, meaning the exception was caught--I just further raised it to see the traceback):

>>> from django.contrib.auth.models import User
>>> from django.db import IntegrityError
>>> try:
...     user = User.objects.create(username='admin', password='!', email='foo@example.org')
... except IntegrityError:
...     print 'INTEGRITY ERROR!'
...     raise
...
2014-10-10 20:28:31,285 - DEBUG -  - (0.000) QUERY = u'BEGIN' - PARAMS = (); args=None
2014-10-10 20:28:31,286 - DEBUG -  - (0.000) QUERY = u'INSERT INTO "auth_user" ("password", "last_login", "is_superuser", "username", "first_name", "last_name", "email", "is_staff", "is_active", "date_joined") VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s)' - PARAMS = (u'!', u'2014-10-10 20:28:31.285459', False, u'admin', u'', u'', u'foo@example.org', False, True, u'2014-10-10 20:28:31.285541'); args=['!', u'2014-10-10 20:28:31.285459', False, 'admin', u'', u'', 'foo@example.org', False, True, u'2014-10-10 20:28:31.285541']
INTEGRITY ERROR!
Traceback (most recent call last):
  File "<console>", line 2, in <module>
  File "/home/mcote/experiment/reviewboard/local/lib/python2.7/site-packages/Django-1.6.7-py2.7.egg/django/db/models/manager.py", line 157, in create
    return self.get_queryset().create(**kwargs)
  File "/home/mcote/experiment/reviewboard/local/lib/python2.7/site-packages/Django-1.6.7-py2.7.egg/django/db/models/query.py", line 322, in create
    obj.save(force_insert=True, using=self.db)
  File "/home/mcote/experiment/reviewboard/local/lib/python2.7/site-packages/Django-1.6.7-py2.7.egg/django/db/models/base.py", line 545, in save
    force_update=force_update, update_fields=update_fields)
  File "/home/mcote/experiment/reviewboard/local/lib/python2.7/site-packages/Django-1.6.7-py2.7.egg/django/db/models/base.py", line 573, in save_base
    updated = self._save_table(raw, cls, force_insert, force_update, using, update_fields)
  File "/home/mcote/experiment/reviewboard/local/lib/python2.7/site-packages/Django-1.6.7-py2.7.egg/django/db/models/base.py", line 654, in _save_table
    result = self._do_insert(cls._base_manager, using, fields, update_pk, raw)
  File "/home/mcote/experiment/reviewboard/local/lib/python2.7/site-packages/Django-1.6.7-py2.7.egg/django/db/models/base.py", line 687, in _do_insert
    using=using, raw=raw)
  File "/home/mcote/experiment/reviewboard/local/lib/python2.7/site-packages/Django-1.6.7-py2.7.egg/django/db/models/manager.py", line 232, in _insert
    return insert_query(self.model, objs, fields, **kwargs)
  File "/home/mcote/experiment/reviewboard/local/lib/python2.7/site-packages/Django-1.6.7-py2.7.egg/django/db/models/query.py", line 1514, in insert_query
    return query.get_compiler(using=using).execute_sql(return_id)
  File "/home/mcote/experiment/reviewboard/local/lib/python2.7/site-packages/Django-1.6.7-py2.7.egg/django/db/models/sql/compiler.py", line 903, in execute_sql
    cursor.execute(sql, params)
  File "/home/mcote/experiment/reviewboard/local/lib/python2.7/site-packages/Django-1.6.7-py2.7.egg/django/db/backends/util.py", line 69, in execute
    return super(CursorDebugWrapper, self).execute(sql, params)
  File "/home/mcote/experiment/reviewboard/local/lib/python2.7/site-packages/Django-1.6.7-py2.7.egg/django/db/backends/util.py", line 53, in execute
    return self.cursor.execute(sql, params)
  File "/home/mcote/experiment/reviewboard/local/lib/python2.7/site-packages/Django-1.6.7-py2.7.egg/django/db/utils.py", line 99, in __exit__
    six.reraise(dj_exc_type, dj_exc_value, traceback)
  File "/home/mcote/experiment/reviewboard/local/lib/python2.7/site-packages/Django-1.6.7-py2.7.egg/django/db/backends/util.py", line 53, in execute
    return self.cursor.execute(sql, params)
  File "/home/mcote/experiment/reviewboard/local/lib/python2.7/site-packages/Django-1.6.7-py2.7.egg/django/db/backends/sqlite3/base.py", line 452, in execute
    return Database.Cursor.execute(self, query, params)
IntegrityError: UNIQUE constraint failed: auth_user.username

The result is that I have no db-agnostic way to atomically verify that a username already exists except using a blanket try..except.
#1 mrc***@gmai***** (Google Code) (Is this you? Claim this profile.)
Forgot to include my extension code.  I created an extension with the generate_extension.py tool and added this backend to extension.py (with the appropriate entry point in setup.py).  This extension just tries to add an 'admin' user, which already exists in my setup (created as part of prepare-dev.py).  As mentioned, this should be caught by a generic django.db.IntegrityError, but it isn't.

class IntegrityTestBackend(AuthBackend):

    backend_id = _('integritytest')
    name = _('IntegrityTest')

    def authenticate(self, username, password):
        username = username.strip()
        try:
            user = User.objects.create(username='admin', password='!',
                                       email='foo@example.org')
        except IntegrityError:
            logging.exception('INTEGRITY ERROR')
        return None

    def get_or_create_user(self, username, request):
        try:
            return User.objects.get(username=username)
        except User.DoesNotExist:
            return None
david
#2 david
I don't really understand this. The djblets wrapper doesn't do anything substantially different from the django one.
david
#3 david
  • -reviewboard
    +djblets