backends.py 8.96 KB
Newer Older
Lincoln Smith's avatar
Lincoln Smith committed
1
from django.contrib.auth import get_user_model
2
3
from django.contrib.auth.models import Permission
from django.core.exceptions import ObjectDoesNotExist
Lincoln Smith's avatar
Lincoln Smith committed
4
5
6
7
8
9
10
11

from perfieldperms.models import PerFieldPermission


class PFPBackend(object):
    """
    Provide permission checking hooks for PerFieldPermissions.
    """
12
13
14
15
16
17
18
19
20
21
22
    def authenticate(self, username=None, password=None):
        """We don't do authentication."""
        return None

    def _cache_to_set(self, cache):
        """Flatten a cache dict of sets into a single set."""
        perms = set()
        perms.update(cache.keys())
        perms.update(*cache.values())
        return perms

Lincoln Smith's avatar
Lincoln Smith committed
23
24
25
26
27
28
29
30
31
32
33
34
35
36
    def _merge_caches(self, to_cache, from_cache):
        """
        Merge the entries from one cache into another. In this case an empty
        set overwrites a non-empty set.
        """
        for key, value in from_cache.items():
            if key in to_cache and value:
                to_cache[key].update(value)
            else:
                # to_cache has no entry for this key, or we're overwriting with
                # an empty set
                to_cache[key] = value
        return to_cache

37
38
39
40
41
42
43
    def _create_perm_sets(self, perms):
        """
        With the perms iterable of (app_label, codename) tuples, create a dict
        of sets to support looking up field level permissions. Return the new dict.
        """
        perm_sets = dict()
        for perm in ['{}.{}'.format(ct, name) for ct, name in perms]:
44
            perm_sets[perm] = set()
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
        return perm_sets

    def _update_perm_sets(self, pfps, perm_sets=None):
        """
        With the pfps iterable of (app_label, codename, model_codename) tuples,
        update the appropriate set in the perm_sets dict with the field level
        perms. Return the new dict.
        """
        # Should test if perm_sets != None and pfps=False to avoid accidental
        # overwrites of cache structure
        if perm_sets is None:
            perm_sets = dict()

        for perm, key in [
                ('{}.{}'.format(ct, name), '{}.{}'.format(ct, model_name))
                for ct, name, model_name in pfps]:
            if key in perm_sets:
                perm_sets[key].add(perm)
63
            else:
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
                perm_sets[key] = set([perm])
        return perm_sets

    def _get_user_permissions(self, user_obj):
        # Get standard perms
        perms = user_obj.user_permissions.filter(
                perfieldpermission__isnull=True,
                ).values_list('content_type__app_label', 'codename')
        perm_sets = self._create_perm_sets(perms)
        # Get per field perms
        pfps = PerFieldPermission.objects.filter(
                user=user_obj
                ).values_list(
                'content_type__app_label',
                'codename',
                'model_permission__codename',
                )
        perm_sets = self._update_perm_sets(pfps, perm_sets)
        return perm_sets

    def _get_group_permissions(self, user_obj):
        user_groups_field = get_user_model()._meta.get_field('groups')
        user_groups_query = 'group__{}'.format(
                user_groups_field.related_query_name()
                )
        perms = Permission.objects.filter(**{
                'perfieldpermission__isnull': True,
                user_groups_query: user_obj
                }).values_list('content_type__app_label', 'codename')
        perm_sets = self._create_perm_sets(perms)
        pfps = PerFieldPermission.objects.filter(
95
                **{user_groups_query: user_obj}
96
97
98
99
100
101
102
103
104
                ).values_list(
                'content_type__app_label',
                'codename',
                'model_permission__codename',
                )
        perm_sets = self._update_perm_sets(pfps, perm_sets)
        return perm_sets

    def _get_permissions(self, user_obj, obj, from_name):
Lincoln Smith's avatar
Lincoln Smith committed
105
        """
106
107
108
109
        Lookup group or user permissions for `user_obj` depending on the
        contents of `from_name`. `from_name` can ber "user" or "group", and
        calls `_get_user_permissions` and `_get_group_permissions`
        respectively.
Lincoln Smith's avatar
Lincoln Smith committed
110
111
112
        """
        if not user_obj.is_active or user_obj.is_anonymous or obj is not None:
            return set()
113
114
115
116
117
118
119
120
121

        cache_name = '_pfp_{}_perm_cache'.format(from_name)
        if not hasattr(user_obj, cache_name):
            if user_obj.is_superuser:
                perms = Permission.objects.filter(
                        perfieldpermission__isnull=True,
                        ).values_list('content_type__app_label', 'codename')
                perm_sets = self._create_perm_sets(perms)
            else:
122
                perm_sets = getattr(self, '_get_{}_permissions'.format(from_name))(user_obj)
123
124
125
126
127
128
129
130
131
132
133
            setattr(user_obj, cache_name, perm_sets)
        return self._cache_to_set(getattr(user_obj, cache_name))

    def get_user_permissions(self, user_obj, obj=None):
        """Get permissions granted only to this user."""
        return self._get_permissions(user_obj, obj, 'user')

    def get_group_permissions(self, user_obj, obj=None):
        """Get permissions granted to groups this user is a member of."""
        return self._get_permissions(user_obj, obj, 'group')

Lincoln Smith's avatar
Lincoln Smith committed
134
135
136
137
138
139
    def get_all_permissions(self, user_obj, obj=None):
        """Get all user and role based permissions for this user. Creates a
        cache of permissions for life of user object.
        """
        if not user_obj.is_active or user_obj.is_anonymous or obj is not None:
            return set()
140

Lincoln Smith's avatar
Lincoln Smith committed
141
        if not hasattr(user_obj, '_pfp_perm_cache'):
142
143
144
145
146
            self.get_user_permissions(user_obj, obj)
            self.get_group_permissions(user_obj, obj)
            # Copy the group perm cache, then update it from the user cache
            # effectively overriding those settings
            user_obj._pfp_perm_cache = user_obj._pfp_group_perm_cache.copy()
147
148
            user_obj._pfp_perm_cache.update(user_obj._pfp_user_perm_cache)
            # The below will merge caches instead of overwriting
149
            # user_obj._pfp_perm_cache = self._merge_caches(
150
151
152
            #        user_obj._pfp_perm_cache,
            #        user_obj._pfp_user_perm_cache,
            #        )
153
        return self._cache_to_set(user_obj._pfp_perm_cache)
Lincoln Smith's avatar
Lincoln Smith committed
154
155

    def has_perm(self, user_obj, perm, obj=None):
156
157
158
        """
        Returns true if `user_obj` has `perm` in their set of all permissions.
        """
Lincoln Smith's avatar
Lincoln Smith committed
159
160
        if not user_obj.is_active:
            return False
161
        if not hasattr(user_obj, '_pfp_perm_cache'):
Lincoln Smith's avatar
Lincoln Smith committed
162
163
            if not self.get_all_permissions(user_obj, obj):
                return False
164
165
166
167
168
169
170
171
172
173
174
        cache = user_obj._pfp_perm_cache
        # Looking for model level perm, no need to go deeper
        if perm in cache:
            return True
        perm_split = perm.rsplit('__', maxsplit=1)
        perm_key = perm_split[0]
        # A user has a field level permission if the model level parent is set
        # and no fields are set, or the field permission is set.
        return perm_key in cache and (
                not cache[perm_key] or perm in cache[perm_key]
                )
Lincoln Smith's avatar
Lincoln Smith committed
175
176
177

    def has_module_perms(self, user_obj, app_label):
        """
178
179
        Returns True if `user_obj` has any permissions in the given
        `app_label`.
Lincoln Smith's avatar
Lincoln Smith committed
180
181
182
        """
        if not user_obj.is_active:
            return False
183
        if not hasattr(user_obj, '_pfp_perm_cache'):
Lincoln Smith's avatar
Lincoln Smith committed
184
185
            if not self.get_all_permissions(user_obj):
                return False
186
        for perm in user_obj._pfp_perm_cache.keys():
Lincoln Smith's avatar
Lincoln Smith committed
187
188
189
            if perm[:perm.index('.')] == app_label:
                return True
        return False
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204

    def has_all_fields(self, user_obj, perm, obj=None):
        """
        Return if `user_obj` has all field level permissions for a given
        permission, in which case the set of field permissions will be empty
        """
        if not user_obj.is_active or user_obj.is_anonymous or obj is not None:
            return False

        if isinstance(perm, type(str())):
            app_label, codename = perm.split('.')
            try:
                perm = PerFieldPermission.objects.get(
                    content_type__app_label=app_label,
                    codename=codename,
205
                    )
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
            except ObjectDoesNotExist:
                perm = Permission.objects.get(
                    content_type__app_label=app_label,
                    codename=codename,
                    )
        if isinstance(perm, PerFieldPermission):
            perm_key = '{}.{}'.format(
                    perm.content_type.app_label,
                    perm.model_permission.codename,
                    )
        elif isinstance(perm, Permission):
            perm_key = '{}.{}'.format(perm.content_type.app_label, perm.codename)
        else:
            raise TypeError("""Perm must be a string representation of a
            Permission, a Permission, or a PerFieldPermission""")

        if not hasattr(user_obj, '_pfp_perm_cache'):
            self.get_all_permissions(user_obj, obj)
        cache = user_obj._pfp_perm_cache
        return perm_key in cache and not cache[perm_key]