Code

Ticket #11010: object_permissions_r11712_#11010.diff

File object_permissions_r11712_#11010.diff, 12.8 KB (added by apollo13, 5 years ago)
Line 
1diff --git a/django/contrib/auth/__init__.py b/django/contrib/auth/__init__.py
2index b89aee1..c17f65e 100644
3--- a/django/contrib/auth/__init__.py
4+++ b/django/contrib/auth/__init__.py
5@@ -1,4 +1,7 @@
6 import datetime
7+
8+from warnings import warn as warn_
9+
10 from django.core.exceptions import ImproperlyConfigured
11 from django.utils.importlib import import_module
12 
13@@ -19,6 +22,14 @@ def load_backend(path):
14         cls = getattr(mod, attr)
15     except AttributeError:
16         raise ImproperlyConfigured, 'Module "%s" does not define a "%s" authentication backend' % (module, attr)
17+   
18+    try:
19+        getattr(cls, 'supports_object_perms')
20+    except AttributeError:
21+        warn_("%s should define `supports_object_perms`." % cls,
22+          PendingDeprecationWarning, stacklevel=3)
23+        cls.supports_object_perms = False
24+
25     return cls()
26 
27 def get_backends():
28diff --git a/django/contrib/auth/backends.py b/django/contrib/auth/backends.py
29index 05f9835..df73a29 100644
30--- a/django/contrib/auth/backends.py
31+++ b/django/contrib/auth/backends.py
32@@ -11,6 +11,8 @@ class ModelBackend(object):
33     """
34     Authenticates against django.contrib.auth.models.User.
35     """
36+    supports_object_perms = False
37+
38     # TODO: Model, login attribute name and password attribute name should be
39     # configurable.
40     def authenticate(self, username=None, password=None):
41diff --git a/django/contrib/auth/models.py b/django/contrib/auth/models.py
42index e337bec..29c32a0 100644
43--- a/django/contrib/auth/models.py
44+++ b/django/contrib/auth/models.py
45@@ -194,30 +194,41 @@ class User(models.Model):
46     def has_usable_password(self):
47         return self.password != UNUSABLE_PASSWORD
48 
49-    def get_group_permissions(self):
50+    def get_group_permissions(self, obj=None):
51         """
52         Returns a list of permission strings that this user has through
53         his/her groups. This method queries all available auth backends.
54+        If an object is passed in, only permissions matching this object
55+        are returned.
56         """
57         permissions = set()
58         for backend in auth.get_backends():
59             if hasattr(backend, "get_group_permissions"):
60-                permissions.update(backend.get_group_permissions(self))
61+                if obj:
62+                    if backend.supports_object_perms:
63+                        permissions.update(backend.get_group_permissions(self, obj))
64+                else:
65+                    permissions.update(backend.get_group_permissions(self))
66         return permissions
67 
68-    def get_all_permissions(self):
69+    def get_all_permissions(self, obj=None):
70         permissions = set()
71         for backend in auth.get_backends():
72             if hasattr(backend, "get_all_permissions"):
73-                permissions.update(backend.get_all_permissions(self))
74+                if obj:
75+                    if backend.supports_object_perms:
76+                        permissions.update(backend.get_all_permissions(self, obj))
77+                else:
78+                    permissions.update(backend.get_all_permissions(self))
79         return permissions
80 
81-    def has_perm(self, perm):
82+    def has_perm(self, perm, obj=None):
83         """
84         Returns True if the user has the specified permission. This method
85         queries all available auth backends, but returns immediately if any
86         backend returns True. Thus, a user who has permission from a single
87-        auth backend is assumed to have permission in general.
88+        auth backend is assumed to have permission in general. If an object
89+        is provided, permissions for this specific object are checked.
90         """
91         # Inactive users have no permissions.
92         if not self.is_active:
93@@ -230,14 +241,22 @@ class User(models.Model):
94         # Otherwise we need to check the backends.
95         for backend in auth.get_backends():
96             if hasattr(backend, "has_perm"):
97-                if backend.has_perm(self, perm):
98-                    return True
99+                if obj:
100+                    if backend.supports_object_perms:
101+                        if backend.has_perm(self, perm, obj):
102+                            return True
103+                else:
104+                    if backend.has_perm(self, perm):
105+                        return True
106         return False
107 
108-    def has_perms(self, perm_list):
109-        """Returns True if the user has each of the specified permissions."""
110+    def has_perms(self, perm_list, obj=None):
111+        """Returns True if the user has each of the specified permissions.
112+        If object is passed, it checks if the user has all required perms
113+        for this object.
114+        """
115         for perm in perm_list:
116-            if not self.has_perm(perm):
117+            if not self.has_perm(perm, obj):
118                 return False
119         return True
120 
121diff --git a/django/contrib/auth/tests/__init__.py b/django/contrib/auth/tests/__init__.py
122index 14428d0..9a078cf 100644
123--- a/django/contrib/auth/tests/__init__.py
124+++ b/django/contrib/auth/tests/__init__.py
125@@ -4,6 +4,7 @@ from django.contrib.auth.tests.views \
126 from django.contrib.auth.tests.forms import FORM_TESTS
127 from django.contrib.auth.tests.remote_user \
128         import RemoteUserTest, RemoteUserNoCreateTest, RemoteUserCustomTest
129+from django.contrib.auth.tests.auth_backends import BackendTest, RowlevelBackendTest
130 from django.contrib.auth.tests.tokens import TOKEN_GENERATOR_TESTS
131 
132 # The password for the fixture data users is 'password'
133diff --git a/docs/internals/deprecation.txt b/docs/internals/deprecation.txt
134index 480b527..b10a575 100644
135--- a/docs/internals/deprecation.txt
136+++ b/docs/internals/deprecation.txt
137@@ -13,6 +13,9 @@ their deprecation, as per the :ref:`Django deprecation policy
138           hooking up admin URLs.  This has been deprecated since the 1.1
139           release.
140 
141+        * Authentication backends need to define ``supports_object_perms``. The
142+          old backend style got is deprecated since the 1.2 release.
143+
144     * 1.4
145         * ``CsrfResponseMiddleware``.  This has been deprecated since the 1.2
146           release, in favour of the template tag method for inserting the CSRF
147@@ -28,6 +31,10 @@ their deprecation, as per the :ref:`Django deprecation policy
148         * The many to many SQL generation functions on the database backends
149           will be removed.  These have been deprecated since the 1.2 release.
150 
151+        * Authentication backends need to support the ``obj`` parameter for
152+          permission checking. The ``supports_object_perms`` variable is not
153+          checked anylonger and can be removed.
154+
155     * 2.0
156         * ``django.views.defaults.shortcut()``. This function has been moved
157           to ``django.contrib.contenttypes.views.shortcut()`` as part of the
158diff --git a/docs/topics/auth.txt b/docs/topics/auth.txt
159index 33461a0..9baa732 100644
160--- a/docs/topics/auth.txt
161+++ b/docs/topics/auth.txt
162@@ -199,28 +199,34 @@ Methods
163         :meth:`~django.contrib.auth.models.User.set_unusable_password()` has
164         been called for this user.
165 
166-    .. method:: models.User.get_group_permissions()
167+    .. method:: models.User.get_group_permissions(obj=None)
168 
169         Returns a list of permission strings that the user has, through his/her
170-        groups.
171+        groups. If ``obj`` is passed in, only returns the group permissions for
172+        this specific object.
173 
174-    .. method:: models.User.get_all_permissions()
175+    .. method:: models.User.get_all_permissions(obj=None)
176 
177         Returns a list of permission strings that the user has, both through
178-        group and user permissions.
179+        group and user permissions. If ``obj`` is passed in, only returns the
180+        permissions for this specific object.
181 
182-    .. method:: models.User.has_perm(perm)
183+
184+    .. method:: models.User.has_perm(perm, obj=None)
185 
186         Returns ``True`` if the user has the specified permission, where perm is
187         in the format ``"<app label>.<permission codename>"``.
188-        If the user is inactive, this method will always return ``False``.
189+        If the user is inactive, this method will always return ``False``. If
190+        ``obj`` is passed in this method won't check the permissions for the model,
191+        but the object.
192 
193-    .. method:: models.User.has_perms(perm_list)
194+    .. method:: models.User.has_perms(perm_list, obj=None)
195 
196         Returns ``True`` if the user has each of the specified permissions,
197         where each perm is in the format
198         ``"<app label>.<permission codename>"``. If the user is inactive,
199-        this method will always return ``False``.
200+        this method will always return ``False``. If ``obj`` is passed in
201+        this method won't check the permissions for the model, but the object.
202 
203     .. method:: models.User.has_module_perms(package_name)
204 
205@@ -1510,3 +1516,19 @@ A full authorization implementation can be found in
206 the ``auth_permission`` table most of the time.
207 
208 .. _django/contrib/auth/backends.py: http://code.djangoproject.com/browser/django/trunk/django/contrib/auth/backends.py
209+
210+Handling object permissions
211+---------------------------
212+
213+Django's permission framework has a foundation for object permissions, though
214+there is no implementation for it in the core. This means, that checking for
215+object permissions will always return ``False`` or an empty list (depending on
216+the check performed). To enable object permissions you will have to write your
217+own Backend which does support them, the only change you have to do is to add
218+an ``obj`` parameter to the permission functions and set ``supports_objects_perms``
219+to ``True``. In Django 1.2 a non existing ``supports_objects_perms`` will raise a
220+``PendingDeprecationWarning``. In 1.3 a non existant ``support_object_perms``
221+attribute will raise an error and setting it to ``False`` will raise a
222+``DeprecationWarning``. In 1.4 it's assumed that every backend supports object
223+permissions and no checking is performed, which means not supporting ``obj`` as
224+parameter will raise a ``TypeError``.
225diff --git a/tests/regressiontests/auth_backends/__init__.py b/tests/regressiontests/auth_backends/__init__.py
226deleted file mode 100644
227index e69de29..0000000
228diff --git a/tests/regressiontests/auth_backends/models.py b/tests/regressiontests/auth_backends/models.py
229deleted file mode 100644
230index e69de29..0000000
231diff --git a/tests/regressiontests/auth_backends/tests.py b/tests/regressiontests/auth_backends/tests.py
232deleted file mode 100644
233index d22f0bf..0000000
234--- a/tests/regressiontests/auth_backends/tests.py
235+++ /dev/null
236@@ -1,78 +0,0 @@
237-try:
238-    set
239-except NameError:
240-    from sets import Set as set     # Python 2.3 fallback
241-
242-__test__ = {'API_TESTS': """
243->>> from django.contrib.auth.models import User, Group, Permission, AnonymousUser
244->>> from django.contrib.contenttypes.models import ContentType
245-
246-# No Permissions assigned yet, should return False except for superuser
247-
248->>> user = User.objects.create_user('test', 'test@example.com', 'test')
249->>> user.has_perm("auth.test")
250-False
251->>> user.is_staff=True
252->>> user.save()
253->>> user.has_perm("auth.test")
254-False
255->>> user.is_superuser=True
256->>> user.save()
257->>> user.has_perm("auth.test")
258-True
259->>> user.is_staff = False
260->>> user.is_superuser = False
261->>> user.save()
262->>> user.has_perm("auth.test")
263-False
264->>> content_type=ContentType.objects.get_for_model(Group)
265->>> perm = Permission.objects.create(name="test", content_type=content_type, codename="test")
266->>> user.user_permissions.add(perm)
267->>> user.save()
268-
269-# reloading user to purge the _perm_cache
270-
271->>> user = User.objects.get(username="test")
272->>> user.get_all_permissions() == set([u'auth.test'])
273-True
274->>> user.get_group_permissions() == set([])
275-True
276->>> user.has_module_perms("Group")
277-False
278->>> user.has_module_perms("auth")
279-True
280->>> perm = Permission.objects.create(name="test2", content_type=content_type, codename="test2")
281->>> user.user_permissions.add(perm)
282->>> user.save()
283->>> perm = Permission.objects.create(name="test3", content_type=content_type, codename="test3")
284->>> user.user_permissions.add(perm)
285->>> user.save()
286->>> user = User.objects.get(username="test")
287->>> user.get_all_permissions() == set([u'auth.test2', u'auth.test', u'auth.test3'])
288-True
289->>> user.has_perm('test')
290-False
291->>> user.has_perm('auth.test')
292-True
293->>> user.has_perms(['auth.test2', 'auth.test3'])
294-True
295->>> perm = Permission.objects.create(name="test_group", content_type=content_type, codename="test_group")
296->>> group = Group.objects.create(name='test_group')
297->>> group.permissions.add(perm)
298->>> group.save()
299->>> user.groups.add(group)
300->>> user = User.objects.get(username="test")
301->>> exp = set([u'auth.test2', u'auth.test', u'auth.test3', u'auth.test_group'])
302->>> user.get_all_permissions() == exp
303-True
304->>> user.get_group_permissions() == set([u'auth.test_group'])
305-True
306->>> user.has_perms(['auth.test3', 'auth.test_group'])
307-True
308-
309->>> user = AnonymousUser()
310->>> user.has_perm('test')
311-False
312->>> user.has_perms(['auth.test2', 'auth.test3'])
313-False
314-"""}