Skip to content

Commit

Permalink
BACKWARD-INCOMPATIBLE: Authentication classes now require users t…
Browse files Browse the repository at this point in the history
…o be ``is_active = True``.

This can be disabled to work the old way. Please see the documentation for an example.
  • Loading branch information
toastdriven committed Jun 26, 2012
1 parent 7175815 commit a57d85b
Show file tree
Hide file tree
Showing 5 changed files with 235 additions and 33 deletions.
1 change: 1 addition & 0 deletions BACKWARDS-INCOMPATIBLE.txt
@@ -1 +1,2 @@
[2012-05-04] AutoFields will now serialize and deserialize as Integers
[2012-06-26] ``Authentication`` classes now require ``User.is_active = True``
15 changes: 15 additions & 0 deletions docs/authentication_authorization.rst
Expand Up @@ -43,6 +43,21 @@ Authentication Options

Tastypie ships with the following ``Authentication`` classes:

.. warning:
Tastypie, when used with ``django.contrib.auth.models.User``, will check
to ensure that the ``User.is_active = True`` by default.
You can disable this behavior by initializing your ``Authentication`` class
with ``require_active=False``::
class UserResource(ModelResource):
class Meta:
# ...
authentication = BasicAuthentication(require_active=False)
*The behavior changed to active-by-default in v0.9.12.*
``Authentication``
~~~~~~~~~~~~~~~~~~

Expand Down
41 changes: 36 additions & 5 deletions tastypie/authentication.py
Expand Up @@ -37,6 +37,9 @@ class Authentication(object):
By default, this indicates the user is always authenticated.
"""
def __init__(self, require_active=True):
self.require_active = require_active

def is_authenticated(self, request, **kwargs):
"""
Identifies if the user is authenticated to continue or not.
Expand All @@ -54,6 +57,18 @@ def get_identifier(self, request):
"""
return "%s_%s" % (request.META.get('REMOTE_ADDR', 'noaddr'), request.META.get('REMOTE_HOST', 'nohost'))

def check_active(self, user):
"""
Ensures the user has an active account.
Optimized for the ``django.contrib.auth.models.User`` case.
"""
if not self.require_active:
# Ignore & move on.
return True

return user.is_active


class BasicAuthentication(Authentication):
"""
Expand All @@ -71,7 +86,8 @@ class BasicAuthentication(Authentication):
The realm to use in the ``HttpUnauthorized`` response. Default:
``django-tastypie``.
"""
def __init__(self, backend=None, realm='django-tastypie'):
def __init__(self, backend=None, realm='django-tastypie', **kwargs):
super(BasicAuthentication, self).__init__(**kwargs)
self.backend = backend
self.realm = realm

Expand Down Expand Up @@ -113,6 +129,9 @@ def is_authenticated(self, request, **kwargs):
if user is None:
return self._unauthorized()

if not self.check_active(user):
return False

request.user = user
return True

Expand Down Expand Up @@ -172,6 +191,9 @@ def is_authenticated(self, request, **kwargs):
except (User.DoesNotExist, User.MultipleObjectsReturned):
return self._unauthorized()

if not self.check_active(user):
return False

request.user = user
return self.get_key(user, api_key)

Expand Down Expand Up @@ -216,7 +238,8 @@ class DigestAuthentication(Authentication):
The realm to use in the ``HttpUnauthorized`` response. Default:
``django-tastypie``.
"""
def __init__(self, backend=None, realm='django-tastypie'):
def __init__(self, backend=None, realm='django-tastypie', **kwargs):
super(DigestAuthentication, self).__init__(**kwargs)
self.backend = backend
self.realm = realm

Expand Down Expand Up @@ -268,6 +291,9 @@ def is_authenticated(self, request, **kwargs):
if not digest_response.response == expected:
return self._unauthorized()

if not self.check_active(user):
return False

request.user = user
return True

Expand Down Expand Up @@ -319,8 +345,8 @@ class OAuthAuthentication(Authentication):
This does *NOT* provide OAuth authentication in your API, strictly
consumption.
"""
def __init__(self):
super(OAuthAuthentication, self).__init__()
def __init__(self, **kwargs):
super(OAuthAuthentication, self).__init__(**kwargs)

if oauth2 is None:
raise ImproperlyConfigured("The 'python-oauth2' package could not be imported. It is required for use with the 'OAuthAuthentication' class.")
Expand All @@ -346,6 +372,9 @@ def is_authenticated(self, request, **kwargs):
return oauth_provider.utils.send_oauth_error(e)

if consumer and token:
if not self.check_active(token.user):
return False

request.user = token.user
return True

Expand All @@ -359,6 +388,7 @@ def is_in(self, params):
provided ``params``.
"""
from oauth_provider.consts import OAUTH_PARAMETERS_NAMES

for param_name in OAUTH_PARAMETERS_NAMES:
if param_name not in params:
return False
Expand All @@ -383,7 +413,8 @@ class MultiAuthentication(object):
"""
An authentication backend that tries a number of backends in order.
"""
def __init__(self, *backends):
def __init__(self, *backends, **kwargs):
super(MultiAuthentication, self).__init__(**kwargs)
self.backends = backends

def is_authenticated(self, request, **kwargs):
Expand Down
14 changes: 12 additions & 2 deletions tests/core/fixtures/note_testdata.json
Expand Up @@ -17,7 +17,17 @@
"model": "auth.user",
"pk": 2
},

{
"fields": {
"username": "bobdoe",
"email": "bob@doe.com",
"password": "ghi789",
"is_active": false
},
"model": "auth.user",
"pk": 3
},

{
"fields": {
"author": 1,
Expand Down Expand Up @@ -96,7 +106,7 @@
"model": "core.note",
"pk": 6
},

{
"fields": {
"note": 1,
Expand Down

0 comments on commit a57d85b

Please sign in to comment.