1# -*- coding: utf-8 -*-
2from collections import defaultdict
3from contextlib import contextmanager
4from functools import wraps
5from threading import local
6
7from django.contrib.auth import get_permission_codename, get_user_model
8from django.contrib.auth.models import Group
9from django.db.models import Q
10from django.utils.decorators import available_attrs
11from django.utils.lru_cache import lru_cache
12
13from cms.constants import ROOT_USER_LEVEL, SCRIPT_USERNAME
14from cms.exceptions import NoPermissionsException
15from cms.models import GlobalPagePermission, Page, PagePermission
16from cms.utils.compat import DJANGO_1_11
17from cms.utils.conf import get_cms_setting
18from cms.utils.page import get_clean_username
19
20
21# thread local support
22_thread_locals = local()
23
24
25def set_current_user(user):
26    """
27    Assigns current user from request to thread_locals, used by
28    CurrentUserMiddleware.
29    """
30    _thread_locals.user = user
31
32
33def get_current_user():
34    """
35    Returns current user, or None
36    """
37    return getattr(_thread_locals, 'user', None)
38
39
40def get_current_user_name():
41    current_user = get_current_user()
42
43    if not current_user:
44        return SCRIPT_USERNAME
45    return get_clean_username(current_user)
46
47
48@contextmanager
49def current_user(user):
50    """
51    Changes the current user just within a context.
52    """
53    old_user = get_current_user()
54    set_current_user(user)
55    yield
56    set_current_user(old_user)
57
58
59def get_model_permission_codename(model, action):
60    opts = model._meta
61    return opts.app_label + '.' + get_permission_codename(action, opts)
62
63
64def _has_global_permission(user, site, action):
65    if not user.is_authenticated:
66        return False
67
68    if user.is_superuser:
69        return True
70
71    codename = get_model_permission_codename(GlobalPagePermission, action=action)
72
73    if not user.has_perm(codename):
74        return False
75
76    if not get_cms_setting('PERMISSION'):
77        return True
78
79    has_perm = (
80        GlobalPagePermission
81        .objects
82        .get_with_change_permissions(user, site.pk)
83        .exists()
84    )
85    return has_perm
86
87
88def user_can_add_global_permissions(user, site):
89    return _has_global_permission(user, site, action='add')
90
91
92def user_can_change_global_permissions(user, site):
93    return _has_global_permission(user, site, action='change')
94
95
96def user_can_delete_global_permissions(user, site):
97    return _has_global_permission(user, site, action='delete')
98
99
100def get_user_permission_level(user, site):
101    """
102    Returns highest user level from the page/permission hierarchy on which
103    user haves can_change_permission. Also takes look into user groups. Higher
104    level equals to lower number. Users on top of hierarchy have level 0. Level
105    is the same like page.depth attribute.
106
107    Example:
108                              A,W                    level 0
109                            /    \
110                          user    B,GroupE           level 1
111                        /     \
112                      C,X     D,Y,W                  level 2
113
114        Users A, W have user level 0. GroupE and all his users have user level 1
115        If user D is a member of GroupE, his user level will be 1, otherwise is
116        2.
117
118    """
119    if not user.is_authenticated:
120        raise NoPermissionsException
121
122    if user.is_superuser or not get_cms_setting('PERMISSION'):
123        return ROOT_USER_LEVEL
124
125    has_global_perms = (
126        GlobalPagePermission
127        .objects
128        .get_with_change_permissions(user, site.pk)
129        .exists()
130    )
131
132    if has_global_perms:
133        return ROOT_USER_LEVEL
134
135    try:
136        permission = (
137            PagePermission
138            .objects
139            .get_with_change_permissions(user, site)
140            .select_related('page')
141            .order_by('page__node__path')
142        )[0]
143    except IndexError:
144        # user isn't assigned to any node
145        raise NoPermissionsException
146    return permission.page.node.depth
147
148
149def cached_func(func):
150    @wraps(func, assigned=available_attrs(func))
151    def cached_func(user, *args, **kwargs):
152        func_cache_name = '_djangocms_cached_func_%s' % func.__name__
153
154        if not hasattr(user, func_cache_name):
155            cached_func = lru_cache(maxsize=None)(func)
156            setattr(user, func_cache_name, cached_func)
157        return getattr(user, func_cache_name)(user, *args, **kwargs)
158
159    # Allows us to access the un-cached function
160    cached_func.without_cache = func
161    return cached_func
162
163
164@cached_func
165def get_global_actions_for_user(user, site):
166    actions = set()
167    global_perms = (
168        GlobalPagePermission
169        .objects
170        .get_with_site(user, site.pk)
171    )
172
173    for global_perm in global_perms.iterator():
174        actions.update(global_perm.get_configured_actions())
175    return actions
176
177
178@cached_func
179def get_page_actions_for_user(user, site):
180    actions = defaultdict(set)
181    pages = (
182        Page
183        .objects
184        .drafts()
185        .on_site(site)
186        .select_related('node')
187        .order_by('node__path')
188    )
189    nodes = [page.node for page in pages]
190    pages_by_id = {}
191
192    for page in pages:
193        if page.node.is_root():
194            page.node._set_hierarchy(nodes)
195        page.node.__dict__['item'] = page
196        pages_by_id[page.pk] = page
197
198    page_permissions = (
199        PagePermission
200        .objects
201        .with_user(user)
202        .filter(page__in=pages_by_id)
203    )
204
205    for perm in page_permissions.iterator():
206        # set internal fk cache to our page with loaded ancestors and descendants
207        if DJANGO_1_11:
208            perm._page_cache = pages_by_id[perm.page_id]
209        else:
210            # for django >= 2.0
211            PagePermission.page.field.set_cached_value(perm, pages_by_id[perm.page_id])
212
213        page_ids = frozenset(perm.get_page_ids())
214
215        for action in perm.get_configured_actions():
216            actions[action].update(page_ids)
217    return actions
218
219
220def has_global_permission(user, site, action, use_cache=True):
221    if use_cache:
222        actions = get_global_actions_for_user(user, site)
223    else:
224        actions = get_global_actions_for_user.without_cache(user, site)
225    return action in actions
226
227
228def has_page_permission(user, page, action, use_cache=True):
229    if use_cache:
230        actions = get_page_actions_for_user(user, page.node.site)
231    else:
232        actions = get_page_actions_for_user.without_cache(user, page.node.site)
233    return page.pk in actions[action]
234
235
236def get_subordinate_users(user, site):
237    """
238    Returns users queryset, containing all subordinate users to given user
239    including users created by given user and not assigned to any page.
240
241    Not assigned users must be returned, because they shouldn't get lost, and
242    user should still have possibility to see them.
243
244    Only users created_by given user which are on the same, or lover level are
245    returned.
246
247    If user haves global permissions or is a superuser, then he can see all the
248    users.
249
250    This function is currently used in PagePermissionInlineAdminForm for limit
251    users in permission combobox.
252
253    Example:
254                              A,W                    level 0
255                            /    \
256                          user    B,GroupE           level 1
257                Z       /     \
258                      C,X     D,Y,W                  level 2
259
260        Rules: W was created by user, Z was created by user, but is not assigned
261        to any page.
262
263        Will return [user, C, X, D, Y, Z]. W was created by user, but is also
264        assigned to higher level.
265    """
266    from cms.utils.page_permissions import get_change_permissions_id_list
267
268    try:
269        user_level = get_user_permission_level(user, site)
270    except NoPermissionsException:
271        # user has no Global or Page permissions.
272        # return only staff users created by user
273        # whose page permission record has no page attached.
274        qs = get_user_model().objects.distinct().filter(
275                Q(is_staff=True) &
276                Q(pageuser__created_by=user) &
277                Q(pagepermission__page=None)
278        )
279        qs = qs.exclude(pk=user.pk).exclude(groups__user__pk=user.pk)
280        return qs
281
282    if user_level == ROOT_USER_LEVEL:
283        return get_user_model().objects.all()
284
285    page_id_allow_list = get_change_permissions_id_list(user, site, check_global=False)
286
287    # normal query
288    qs = get_user_model().objects.distinct().filter(
289        Q(is_staff=True) &
290        (Q(pagepermission__page__id__in=page_id_allow_list) & Q(pagepermission__page__node__depth__gte=user_level))
291        | (Q(pageuser__created_by=user) & Q(pagepermission__page=None))
292    )
293    qs = qs.exclude(pk=user.pk).exclude(groups__user__pk=user.pk)
294    return qs
295
296
297def get_subordinate_groups(user, site):
298    """
299    Similar to get_subordinate_users, but returns queryset of Groups instead
300    of Users.
301    """
302    from cms.utils.page_permissions import get_change_permissions_id_list
303
304    try:
305        user_level = get_user_permission_level(user, site)
306    except NoPermissionsException:
307        # user has no Global or Page permissions.
308        # return only groups created by user
309        # whose page permission record has no page attached.
310        groups = (
311            Group
312            .objects
313            .filter(
314                Q(pageusergroup__created_by=user) &
315                Q(pagepermission__page__isnull=True)
316            )
317            .distinct()
318        )
319        # no permission no records
320        # page_id_allow_list is empty
321        return groups
322
323    if user_level == ROOT_USER_LEVEL:
324        return Group.objects.all()
325
326    page_id_allow_list = get_change_permissions_id_list(user, site, check_global=False)
327
328    return Group.objects.distinct().filter(
329        (Q(pagepermission__page__id__in=page_id_allow_list) & Q(pagepermission__page__node__depth__gte=user_level))
330        | (Q(pageusergroup__created_by=user) & Q(pagepermission__page__isnull=True))
331    )
332
333
334def get_view_restrictions(pages):
335    """
336    Load all view restrictions for the pages
337    """
338    restricted_pages = defaultdict(list)
339
340    if not get_cms_setting('PERMISSION'):
341        # Permissions are off. There's no concept of page restrictions.
342        return restricted_pages
343
344    if not pages:
345        return restricted_pages
346
347    nodes = [page.node for page in pages]
348    pages_by_id = {}
349
350    for page in pages:
351        if page.node.is_root():
352            page.node._set_hierarchy(nodes)
353        page.node.__dict__['item'] = page
354        pages_by_id[page.pk] = page
355
356    page_permissions = PagePermission.objects.filter(
357        page__in=pages_by_id,
358        can_view=True,
359    )
360
361    for perm in page_permissions:
362        # set internal fk cache to our page with loaded ancestors and descendants
363        if DJANGO_1_11:
364            perm._page_cache = pages_by_id[perm.page_id]
365        else:
366            # for django >= 2.0
367            PagePermission.page.field.set_cached_value(perm, pages_by_id[perm.page_id])
368
369        for page_id in perm.get_page_ids():
370            restricted_pages[page_id].append(perm)
371    return restricted_pages
372
373
374def has_plugin_permission(user, plugin_type, permission_type):
375    """
376    Checks that a user has permissions for the plugin-type given to perform
377    the action defined in permission_type
378    permission_type should be 'add', 'change' or 'delete'.
379    """
380    from cms.plugin_pool import plugin_pool
381    plugin_class = plugin_pool.get_plugin(plugin_type)
382    codename = get_model_permission_codename(
383        plugin_class.model,
384        action=permission_type,
385    )
386    return user.has_perm(codename)
387