Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

Fixed #6552, #12031 - Make django.core.context_processors.auth lazy t…

…o avoid "Vary: Cookie"

Thanks to olau@iola.dk, Suor for the report



git-svn-id: http://code.djangoproject.com/svn/django/trunk@11623 bcc190cf-cafb-0310-a4f2-bffc1f526a37
  • Loading branch information...
commit c161bf21f0ab9f2945ae7a44e5757b071f7eb712 1 parent f14833e
Luke Plant authored October 14, 2009
10  django/contrib/auth/tests/remote_user.py
@@ -2,7 +2,7 @@
2 2
 
3 3
 from django.conf import settings
4 4
 from django.contrib.auth.backends import RemoteUserBackend
5  
-from django.contrib.auth.models import AnonymousUser, User
  5
+from django.contrib.auth.models import User
6 6
 from django.test import TestCase
7 7
 
8 8
 
@@ -30,15 +30,15 @@ def test_no_remote_user(self):
30 30
         num_users = User.objects.count()
31 31
 
32 32
         response = self.client.get('/remote_user/')
33  
-        self.assert_(isinstance(response.context['user'], AnonymousUser))
  33
+        self.assert_(response.context['user'].is_anonymous())
34 34
         self.assertEqual(User.objects.count(), num_users)
35 35
 
36 36
         response = self.client.get('/remote_user/', REMOTE_USER=None)
37  
-        self.assert_(isinstance(response.context['user'], AnonymousUser))
  37
+        self.assert_(response.context['user'].is_anonymous())
38 38
         self.assertEqual(User.objects.count(), num_users)
39 39
 
40 40
         response = self.client.get('/remote_user/', REMOTE_USER='')
41  
-        self.assert_(isinstance(response.context['user'], AnonymousUser))
  41
+        self.assert_(response.context['user'].is_anonymous())
42 42
         self.assertEqual(User.objects.count(), num_users)
43 43
 
44 44
     def test_unknown_user(self):
@@ -115,7 +115,7 @@ class that doesn't create unknown users.
115 115
     def test_unknown_user(self):
116 116
         num_users = User.objects.count()
117 117
         response = self.client.get('/remote_user/', REMOTE_USER='newuser')
118  
-        self.assert_(isinstance(response.context['user'], AnonymousUser))
  118
+        self.assert_(response.context['user'].is_anonymous())
119 119
         self.assertEqual(User.objects.count(), num_users)
120 120
 
121 121
 
50  django/core/context_processors.py
@@ -8,6 +8,27 @@
8 8
 """
9 9
 
10 10
 from django.conf import settings
  11
+from django.utils.functional import lazy, memoize, LazyObject
  12
+
  13
+class ContextLazyObject(LazyObject):
  14
+    """
  15
+    A lazy object initialised from any function, useful for lazily
  16
+    adding things to the Context.
  17
+
  18
+    Designed for compound objects of unknown type. For simple objects of known
  19
+    type, use django.utils.functional.lazy.
  20
+    """
  21
+    def __init__(self, func):
  22
+        """
  23
+        Pass in a callable that returns the actual value to be used
  24
+        """
  25
+        self.__dict__['_setupfunc'] = func
  26
+        # For some reason, we have to inline LazyObject.__init__ here to avoid
  27
+        # recursion
  28
+        self._wrapped = None
  29
+
  30
+    def _setup(self):
  31
+        self._wrapped = self._setupfunc()
11 32
 
12 33
 def auth(request):
13 34
     """
@@ -17,15 +38,26 @@ def auth(request):
17 38
     If there is no 'user' attribute in the request, uses AnonymousUser (from
18 39
     django.contrib.auth).
19 40
     """
20  
-    if hasattr(request, 'user'):
21  
-        user = request.user
22  
-    else:
23  
-        from django.contrib.auth.models import AnonymousUser
24  
-        user = AnonymousUser()
  41
+    # If we access request.user, request.session is accessed, which results in
  42
+    # 'Vary: Cookie' being sent in every request that uses this context
  43
+    # processor, which can easily be every request on a site if
  44
+    # TEMPLATE_CONTEXT_PROCESSORS has this context processor added.  This kills
  45
+    # the ability to cache.  So, we carefully ensure these attributes are lazy.
  46
+    # We don't use django.utils.functional.lazy() for User, because that
  47
+    # requires knowing the class of the object we want to proxy, which could
  48
+    # break with custom auth backends.  LazyObject is a less complete but more
  49
+    # flexible solution that is a good enough wrapper for 'User'.
  50
+    def get_user():
  51
+        if hasattr(request, 'user'):
  52
+            return request.user
  53
+        else:
  54
+            from django.contrib.auth.models import AnonymousUser
  55
+            return AnonymousUser()
  56
+
25 57
     return {
26  
-        'user': user,
27  
-        'messages': user.get_and_delete_messages(),
28  
-        'perms': PermWrapper(user),
  58
+        'user': ContextLazyObject(get_user),
  59
+        'messages': lazy(memoize(lambda: get_user().get_and_delete_messages(), {}, 0), list)(),
  60
+        'perms':  lazy(lambda: PermWrapper(get_user()), PermWrapper)(),
29 61
     }
30 62
 
31 63
 def debug(request):
@@ -79,7 +111,7 @@ def __init__(self, user):
79 111
 
80 112
     def __getitem__(self, module_name):
81 113
         return PermLookupDict(self.user, module_name)
82  
-        
  114
+
83 115
     def __iter__(self):
84 116
         # I am large, I contain multitudes.
85 117
         raise TypeError("PermWrapper is not iterable.")
17  tests/regressiontests/context_processors/fixtures/context-processors-users.xml
... ...
@@ -0,0 +1,17 @@
  1
+<?xml version="1.0" encoding="utf-8"?>
  2
+<django-objects version="1.0">
  3
+    <object pk="100" model="auth.user">
  4
+        <field type="CharField" name="username">super</field>
  5
+        <field type="CharField" name="first_name">Super</field>
  6
+        <field type="CharField" name="last_name">User</field>
  7
+        <field type="CharField" name="email">super@example.com</field>
  8
+        <field type="CharField" name="password">sha1$995a3$6011485ea3834267d719b4c801409b8b1ddd0158</field>
  9
+        <field type="BooleanField" name="is_staff">True</field>
  10
+        <field type="BooleanField" name="is_active">True</field>
  11
+        <field type="BooleanField" name="is_superuser">True</field>
  12
+        <field type="DateTimeField" name="last_login">2007-05-30 13:20:10</field>
  13
+        <field type="DateTimeField" name="date_joined">2007-05-30 13:20:10</field>
  14
+        <field to="auth.group" name="groups" rel="ManyToManyRel"></field>
  15
+        <field to="auth.permission" name="user_permissions" rel="ManyToManyRel"></field>
  16
+    </object>
  17
+</django-objects>
1  tests/regressiontests/context_processors/templates/context_processors/auth_attrs_access.html
... ...
@@ -0,0 +1 @@
  1
+{{ user }}
1  tests/regressiontests/context_processors/templates/context_processors/auth_attrs_messages.html
... ...
@@ -0,0 +1 @@
  1
+{% for m in messages %}{{ m }}{% endfor %}
1  tests/regressiontests/context_processors/templates/context_processors/auth_attrs_no_access.html
... ...
@@ -0,0 +1 @@
  1
+ 
1  tests/regressiontests/context_processors/templates/context_processors/auth_attrs_perms.html
... ...
@@ -0,0 +1 @@
  1
+{% if perms.auth %}Has auth permissions{% endif %}
1  tests/regressiontests/context_processors/templates/context_processors/auth_attrs_test_access.html
... ...
@@ -0,0 +1 @@
  1
+{% if session_accessed %}Session accessed{% else %}Session not accessed{% endif %}
3  tests/regressiontests/context_processors/templates/context_processors/auth_attrs_user.html
... ...
@@ -0,0 +1,3 @@
  1
+unicode: {{ user }}
  2
+id: {{ user.id }}
  3
+username: {{ user.username }}
43  tests/regressiontests/context_processors/tests.py
@@ -36,3 +36,46 @@ def test_request_attributes(self):
36 36
         self.assertContains(response, url)
37 37
         response = self.client.post(url, {'path': '/blah/'})
38 38
         self.assertContains(response, url)
  39
+
  40
+class AuthContextProcessorTests(TestCase):
  41
+    """
  42
+    Tests for the ``django.core.context_processors.auth`` processor
  43
+    """
  44
+    urls = 'regressiontests.context_processors.urls'
  45
+    fixtures = ['context-processors-users.xml']
  46
+
  47
+    def test_session_not_accessed(self):
  48
+        """
  49
+        Tests that the session is not accessed simply by including
  50
+        the auth context processor
  51
+        """
  52
+        response = self.client.get('/auth_processor_no_attr_access/')
  53
+        self.assertContains(response, "Session not accessed")
  54
+
  55
+    def test_session_is_accessed(self):
  56
+        """
  57
+        Tests that the session is accessed if the auth context processor
  58
+        is used and relevant attributes accessed.
  59
+        """
  60
+        response = self.client.get('/auth_processor_attr_access/')
  61
+        self.assertContains(response, "Session accessed")
  62
+
  63
+    def test_perms_attrs(self):
  64
+        self.client.login(username='super', password='secret')
  65
+        response = self.client.get('/auth_processor_perms/')
  66
+        self.assertContains(response, "Has auth permissions")
  67
+
  68
+    def test_message_attrs(self):
  69
+        self.client.login(username='super', password='secret')
  70
+        response = self.client.get('/auth_processor_messages/')
  71
+        self.assertContains(response, "Message 1")
  72
+
  73
+    def test_user_attrs(self):
  74
+        """
  75
+        Test that ContextLazyObject wraps objects properly
  76
+        """
  77
+        self.client.login(username='super', password='secret')
  78
+        response = self.client.get('/auth_processor_user/')
  79
+        self.assertContains(response, "unicode: super")
  80
+        self.assertContains(response, "id: 100")
  81
+        self.assertContains(response, "username: super")
5  tests/regressiontests/context_processors/urls.py
@@ -5,4 +5,9 @@
5 5
 
6 6
 urlpatterns = patterns('',
7 7
     (r'^request_attrs/$', views.request_processor),
  8
+    (r'^auth_processor_no_attr_access/$', views.auth_processor_no_attr_access),
  9
+    (r'^auth_processor_attr_access/$', views.auth_processor_attr_access),
  10
+    (r'^auth_processor_user/$', views.auth_processor_user),
  11
+    (r'^auth_processor_perms/$', views.auth_processor_perms),
  12
+    (r'^auth_processor_messages/$', views.auth_processor_messages),
8 13
 )
26  tests/regressiontests/context_processors/views.py
@@ -6,3 +6,29 @@
6 6
 def request_processor(request):
7 7
     return render_to_response('context_processors/request_attrs.html',
8 8
         RequestContext(request, {}, processors=[context_processors.request]))
  9
+
  10
+def auth_processor_no_attr_access(request):
  11
+    r1 = render_to_response('context_processors/auth_attrs_no_access.html',
  12
+        RequestContext(request, {}, processors=[context_processors.auth]))
  13
+    # *After* rendering, we check whether the session was accessed
  14
+    return render_to_response('context_processors/auth_attrs_test_access.html',
  15
+        {'session_accessed':request.session.accessed})
  16
+
  17
+def auth_processor_attr_access(request):
  18
+    r1 = render_to_response('context_processors/auth_attrs_access.html',
  19
+        RequestContext(request, {}, processors=[context_processors.auth]))
  20
+    return render_to_response('context_processors/auth_attrs_test_access.html',
  21
+        {'session_accessed':request.session.accessed})
  22
+
  23
+def auth_processor_user(request):
  24
+    return render_to_response('context_processors/auth_attrs_user.html',
  25
+        RequestContext(request, {}, processors=[context_processors.auth]))
  26
+
  27
+def auth_processor_perms(request):
  28
+    return render_to_response('context_processors/auth_attrs_perms.html',
  29
+        RequestContext(request, {}, processors=[context_processors.auth]))
  30
+
  31
+def auth_processor_messages(request):
  32
+    request.user.message_set.create(message="Message 1")
  33
+    return render_to_response('context_processors/auth_attrs_messages.html',
  34
+         RequestContext(request, {}, processors=[context_processors.auth]))

0 notes on commit c161bf2

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