Skip to content
Browse files

Added session-based authentication.

  • Loading branch information...
1 parent e08b4f6 commit d1bf2dd0bbc1ab5ec5ebbc6ece79899626d2da69 @toastdriven toastdriven committed
Showing with 124 additions and 4 deletions.
  1. +13 −1 docs/authentication_authorization.rst
  2. +50 −0 tastypie/authentication.py
  3. +60 −3 tests/core/tests/authentication.py
  4. +1 −0 tests/settings_core.py
View
14 docs/authentication_authorization.rst
@@ -106,11 +106,23 @@ objects. Hooking it up looks like::
models.signals.post_save.connect(create_api_key, sender=User)
+
+``SessionAuthentication``
+~~~~~~~~~~~~~~~~~~~~~~~~~
+
+This authentication scheme uses the built-in Django sessions to check if
+a user is logged. This is typically useful when used by Javascript on the same
+site as the API is hosted on.
+
+It requires that the user has logged in & has an active session. They also must
+have a valid CSRF token.
+
+
``DigestAuthentication``
~~~~~~~~~~~~~~~~~~~~~~~~~
This authentication scheme uses HTTP Digest Auth to check a user's
-credentials. The username is their ``django.contrib.auth.models.User``
+credentials.The username is their ``django.contrib.auth.models.User``
username (assuming it is present) and their password should be their
machine-generated api key. As with ApiKeyAuthentication, ``tastypie``
should be included in ``INSTALLED_APPS``.
View
50 tastypie/authentication.py
@@ -6,6 +6,8 @@
from django.conf import settings
from django.contrib.auth import authenticate
from django.core.exceptions import ImproperlyConfigured
+from django.middleware.csrf import _sanitize_token, constant_time_compare
+from django.utils.http import same_origin
from django.utils.translation import ugettext as _
from tastypie.http import HttpUnauthorized
@@ -221,6 +223,54 @@ def get_identifier(self, request):
return username or 'nouser'
+class SessionAuthentication(Authentication):
+ """
+ An authentication mechanism that piggy-backs on Django sessions.
+
+ This is useful when the API is talking to Javascript on the same site.
+ Relies on the user being logged in through the standard Django login
+ setup.
+
+ Requires a valid CSRF token.
+ """
+ def is_authenticated(self, request, **kwargs):
+ """
+ Checks to make sure the user is logged in & has a Django session.
+ """
+ # Cargo-culted from Django 1.3/1.4's ``django/middleware/csrf.py``.
+ # We can't just use what's there, since the return values will be
+ # wrong.
+ # We also can't risk accessing ``request.POST``, which will break with
+ # the serialized bodies.
+ csrf_token = _sanitize_token(request.COOKIES.get(settings.CSRF_COOKIE_NAME, ''))
+
+ if request.is_secure():
+ referer = request.META.get('HTTP_REFERER')
+
+ if referer is None:
+ return False
+
+ good_referer = 'https://%s/' % request.get_host()
+
+ if not same_origin(referer, good_referer):
+ return False
+
+ request_csrf_token = request.META.get('HTTP_X_CSRFTOKEN', '')
+
+ if not constant_time_compare(request_csrf_token, csrf_token):
+ return False
+
+ return request.user.is_authenticated()
+
+ def get_identifier(self, request):
+ """
+ Provides a unique string identifier for the requestor.
+
+ This implementation returns the user's username.
+ """
+ return request.user.username
+
+
class DigestAuthentication(Authentication):
"""
Handles HTTP Digest auth against a specific auth backend if provided,
View
63 tests/core/tests/authentication.py
@@ -1,11 +1,12 @@
import base64
+import os
import time
import warnings
-from django.contrib.auth.models import User
-from django.core import mail
+from django.conf import settings
+from django.contrib.auth.models import AnonymousUser, User
from django.http import HttpRequest
from django.test import TestCase
-from tastypie.authentication import Authentication, BasicAuthentication, ApiKeyAuthentication, DigestAuthentication, OAuthAuthentication, MultiAuthentication
+from tastypie.authentication import Authentication, BasicAuthentication, ApiKeyAuthentication, SessionAuthentication, DigestAuthentication, OAuthAuthentication, MultiAuthentication
from tastypie.http import HttpUnauthorized
from tastypie.models import ApiKey, create_api_key
@@ -221,6 +222,62 @@ def test_check_active_false(self):
self.assertTrue(auth.is_authenticated(request))
+class SessionAuthenticationTestCase(TestCase):
+ fixtures = ['note_testdata.json']
+
+ def test_is_authenticated(self):
+ auth = SessionAuthentication()
+ request = HttpRequest()
+ request.COOKIES = {
+ settings.CSRF_COOKIE_NAME: 'abcdef1234567890abcdef1234567890'
+ }
+
+ # No CSRF token.
+ request.META = {}
+ self.assertFalse(auth.is_authenticated(request))
+
+ # Invalid CSRF token.
+ request.META = {
+ 'HTTP_X_CSRFTOKEN': 'abc123'
+ }
+ self.assertFalse(auth.is_authenticated(request))
+
+ # Not logged in.
+ request.META = {
+ 'HTTP_X_CSRFTOKEN': 'abcdef1234567890abcdef1234567890'
+ }
+ request.user = AnonymousUser()
+ self.assertFalse(auth.is_authenticated(request))
+
+ # Logged in.
+ request.user = User.objects.get(username='johndoe')
+ self.assertTrue(auth.is_authenticated(request))
+
+ # Secure & wrong referrer.
+ os.environ["HTTPS"] = "on"
+ request.META['HTTP_HOST'] = 'example.com'
+ request.META['HTTP_REFERER'] = ''
+ self.assertFalse(auth.is_authenticated(request))
+
+ # Secure & correct referrer.
+ request.META['HTTP_REFERER'] = 'https://example.com/'
+ self.assertTrue(auth.is_authenticated(request))
+
+ os.environ["HTTPS"] = "off"
+
+ def test_get_identifier(self):
+ auth = SessionAuthentication()
+ request = HttpRequest()
+
+ # Not logged in.
+ request.user = AnonymousUser()
+ self.assertEqual(auth.get_identifier(request), '')
+
+ # Logged in.
+ request.user = User.objects.get(username='johndoe')
+ self.assertEqual(auth.get_identifier(request), 'johndoe')
+
+
class DigestAuthenticationTestCase(TestCase):
fixtures = ['note_testdata.json']
View
1 tests/settings_core.py
@@ -1,4 +1,5 @@
from settings import *
+INSTALLED_APPS.append('django.contrib.sessions')
INSTALLED_APPS.append('core')
INSTALLED_APPS.append('oauth_provider')

0 comments on commit d1bf2dd

Please sign in to comment.
Something went wrong with that request. Please try again.