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