1import types 2 3from functools import wraps 4 5import l18n 6 7from django.contrib.auth import get_user_model 8from django.core.exceptions import PermissionDenied 9from django.db.models import Q 10from django.shortcuts import redirect 11from django.urls import reverse 12from django.utils.timezone import activate as activate_tz 13from django.utils.translation import gettext as _ 14from django.utils.translation import override 15 16from wagtail.admin import messages 17from wagtail.core.models import GroupPagePermission 18 19 20def users_with_page_permission(page, permission_type, include_superusers=True): 21 # Get user model 22 User = get_user_model() 23 24 # Find GroupPagePermission records of the given type that apply to this page or an ancestor 25 ancestors_and_self = list(page.get_ancestors()) + [page] 26 perm = GroupPagePermission.objects.filter(permission_type=permission_type, page__in=ancestors_and_self) 27 q = Q(groups__page_permissions__in=perm) 28 29 # Include superusers 30 if include_superusers: 31 q |= Q(is_superuser=True) 32 33 return User.objects.filter(is_active=True).filter(q).distinct() 34 35 36def permission_denied(request): 37 """Return a standard 'permission denied' response""" 38 if request.is_ajax(): 39 raise PermissionDenied 40 41 from wagtail.admin import messages 42 43 messages.error(request, _('Sorry, you do not have permission to access this area.')) 44 return redirect('wagtailadmin_home') 45 46 47def user_passes_test(test): 48 """ 49 Given a test function that takes a user object and returns a boolean, 50 return a view decorator that denies access to the user if the test returns false. 51 """ 52 def decorator(view_func): 53 # decorator takes the view function, and returns the view wrapped in 54 # a permission check 55 56 @wraps(view_func) 57 def wrapped_view_func(request, *args, **kwargs): 58 if test(request.user): 59 # permission check succeeds; run the view function as normal 60 return view_func(request, *args, **kwargs) 61 else: 62 # permission check failed 63 return permission_denied(request) 64 65 return wrapped_view_func 66 67 return decorator 68 69 70def permission_required(permission_name): 71 """ 72 Replacement for django.contrib.auth.decorators.permission_required which returns a 73 more meaningful 'permission denied' response than just redirecting to the login page. 74 (The latter doesn't work anyway because Wagtail doesn't define LOGIN_URL...) 75 """ 76 def test(user): 77 return user.has_perm(permission_name) 78 79 # user_passes_test constructs a decorator function specific to the above test function 80 return user_passes_test(test) 81 82 83def any_permission_required(*perms): 84 """ 85 Decorator that accepts a list of permission names, and allows the user 86 to pass if they have *any* of the permissions in the list 87 """ 88 def test(user): 89 for perm in perms: 90 if user.has_perm(perm): 91 return True 92 93 return False 94 95 return user_passes_test(test) 96 97 98class PermissionPolicyChecker: 99 """ 100 Provides a view decorator that enforces the given permission policy, 101 returning the wagtailadmin 'permission denied' response if permission not granted 102 """ 103 def __init__(self, policy): 104 self.policy = policy 105 106 def require(self, action): 107 def test(user): 108 return self.policy.user_has_permission(user, action) 109 110 return user_passes_test(test) 111 112 def require_any(self, *actions): 113 def test(user): 114 return self.policy.user_has_any_permission(user, actions) 115 116 return user_passes_test(test) 117 118 119def user_has_any_page_permission(user): 120 """ 121 Check if a user has any permission to add, edit, or otherwise manage any 122 page. 123 """ 124 # Can't do nothin if you're not active. 125 if not user.is_active: 126 return False 127 128 # Superusers can do anything. 129 if user.is_superuser: 130 return True 131 132 # At least one of the users groups has a GroupPagePermission. 133 # The user can probably do something. 134 if GroupPagePermission.objects.filter(group__in=user.groups.all()).exists(): 135 return True 136 137 # Specific permissions for a page type do not mean anything. 138 139 # No luck! This user can not do anything with pages. 140 return False 141 142 143def reject_request(request): 144 if request.is_ajax(): 145 raise PermissionDenied 146 147 # import redirect_to_login here to avoid circular imports on model files that import 148 # wagtail.admin.auth, specifically where custom user models are involved 149 from django.contrib.auth.views import redirect_to_login as auth_redirect_to_login 150 return auth_redirect_to_login( 151 request.get_full_path(), login_url=reverse('wagtailadmin_login')) 152 153 154def require_admin_access(view_func): 155 def decorated_view(request, *args, **kwargs): 156 157 user = request.user 158 159 if user.is_anonymous: 160 return reject_request(request) 161 162 if user.has_perms(['wagtailadmin.access_admin']): 163 try: 164 preferred_language = None 165 if hasattr(user, 'wagtail_userprofile'): 166 preferred_language = user.wagtail_userprofile.get_preferred_language() 167 l18n.set_language(preferred_language) 168 time_zone = user.wagtail_userprofile.get_current_time_zone() 169 activate_tz(time_zone) 170 if preferred_language: 171 with override(preferred_language): 172 response = view_func(request, *args, **kwargs) 173 174 if hasattr(response, "render"): 175 # If the response has a render() method, Django treats it 176 # like a TemplateResponse, so we should do the same 177 # In this case, we need to guarantee that when the TemplateResponse 178 # is rendered, it is done within the override context manager 179 # or the user preferred_language will not be used 180 # (this could be replaced with simply rendering the TemplateResponse 181 # for simplicity but this does remove some of its middleware modification 182 # potential) 183 render = response.render 184 185 def overridden_render(response): 186 with override(preferred_language): 187 return render() 188 189 response.render = types.MethodType(overridden_render, response) 190 # decorate the response render method with the override context manager 191 return response 192 else: 193 return view_func(request, *args, **kwargs) 194 195 except PermissionDenied: 196 if request.is_ajax(): 197 raise 198 199 return permission_denied(request) 200 201 if not request.is_ajax(): 202 messages.error(request, _('You do not have permission to access the admin')) 203 204 return reject_request(request) 205 206 return decorated_view 207