Repository: digitalfabrik/integreat-cms

Issue:
Permission edit_page not working for region users
### Describe the Bug
<!-- A clear and concise description of what the bug is. -->
The permission `edit_page` throws an error for region users when called on a non-existing page

### Steps to Reproduce

1. Login as region user (not superadmin or staff)
2. Click on Pages
4. See error

### Expected Behavior
<!-- A clear and concise description of what you expected to happen. -->
The page tree should appear

### Actual Behavior
<!-- A clear and concise description of what actually happened. -->
The following error appears:
```
AttributeError at /augsburg/pages/de-de/

'NoneType' object has no attribute 'region'
```

### Additional Information
<!-- Add any other context (e.g. logs, screenshots, etc.) about the problem here. -->
The error arises because `page.region` is accessed, even if no page is supplied:
```
if page.region not in user.profile.regions.all():
    return False
```
We have two possibilities here:
1. somehow inject the current region into the rule predicate and use this instead of `page.region`
2. Rely on the fact that the region permissions are checked elsewhere. E.g. the user can't open a page form in another region than he belongs to (this is already checked by the `@method_decorator(region_permission_required, name="dispatch")` in the page form) and a page form can only be used to edit pages of the current region (this is alredy checked by `page = region.pages.filter(id=kwargs.get("page_id")).first()`). So in theory, the check might not be necessary altogether, or am I missing something?

<details>
  <summary>Debug Information</summary>
  
Environment:


Request Method: GET
Request URL: http://localhost:8000/augsburg/pages/de-de/

Django Version: 2.2.16
Python Version: 3.7.9
Installed Applications:
['cms.apps.CmsConfig',
 'gvz_api.apps.GvzApiConfig',
 'django.contrib.admin',
 'django.contrib.auth',
 'django.contrib.contenttypes',
 'django.contrib.messages',
 'django.contrib.sessions',
 'django.contrib.sitemaps',
 'django.contrib.staticfiles',
 'compressor',
 'compressor_toolkit',
 'corsheaders',
 'widget_tweaks',
 'easy_thumbnails',
 'filer',
 'mptt',
 'rules.apps.AutodiscoverRulesConfig']
Installed Middleware:
['corsheaders.middleware.CorsMiddleware',
 'django.middleware.security.SecurityMiddleware',
 'django.contrib.sessions.middleware.SessionMiddleware',
 'django.middleware.locale.LocaleMiddleware',
 'django.middleware.common.CommonMiddleware',
 'django.middleware.csrf.CsrfViewMiddleware',
 'django.contrib.auth.middleware.AuthenticationMiddleware',
 'django.contrib.messages.middleware.MessageMiddleware',
 'django.middleware.clickjacking.XFrameOptionsMiddleware']



Traceback:

File "/home/timo/job/integreat/django/.venv/lib/python3.7/site-packages/django/core/handlers/exception.py" in inner
  34.             response = get_response(request)

File "/home/timo/job/integreat/django/.venv/lib/python3.7/site-packages/django/core/handlers/base.py" in _get_response
  115.                 response = self.process_exception_by_middleware(e, request)

File "/home/timo/job/integreat/django/.venv/lib/python3.7/site-packages/django/core/handlers/base.py" in _get_response
  113.                 response = wrapped_callback(request, *callback_args, **callback_kwargs)

File "/home/timo/job/integreat/django/.venv/lib/python3.7/site-packages/django/views/generic/base.py" in view
  71.             return self.dispatch(request, *args, **kwargs)

File "/home/timo/job/integreat/django/.venv/lib/python3.7/site-packages/django/utils/decorators.py" in _wrapper
  45.         return bound_method(*args, **kwargs)

File "/home/timo/job/integreat/django/.venv/lib/python3.7/site-packages/django/contrib/auth/decorators.py" in _wrapped_view
  21.                 return view_func(request, *args, **kwargs)

File "/home/timo/job/integreat/django/.venv/lib/python3.7/site-packages/django/utils/decorators.py" in _wrapper
  45.         return bound_method(*args, **kwargs)

File "/home/timo/job/integreat/django/src/cms/decorators.py" in wrap
  56.             return function(request, *args, **kwargs)

File "/home/timo/job/integreat/django/.venv/lib/python3.7/site-packages/django/contrib/auth/mixins.py" in dispatch
  85.         return super().dispatch(request, *args, **kwargs)

File "/home/timo/job/integreat/django/.venv/lib/python3.7/site-packages/django/views/generic/base.py" in dispatch
  97.         return handler(request, *args, **kwargs)

File "/home/timo/job/integreat/django/src/cms/views/pages/page_tree_view.py" in get
  56.         if not request.user.has_perm("cms.edit_page"):

File "/home/timo/job/integreat/django/.venv/lib/python3.7/site-packages/django/contrib/auth/models.py" in has_perm
  268.         return _user_has_perm(self, perm, obj)

File "/home/timo/job/integreat/django/.venv/lib/python3.7/site-packages/django/contrib/auth/models.py" in _user_has_perm
  182.             if backend.has_perm(user, perm, obj):

File "/home/timo/job/integreat/django/.venv/lib/python3.7/site-packages/rules/permissions.py" in has_perm
  32.         return has_perm(perm, user, *args, **kwargs)

File "/home/timo/job/integreat/django/.venv/lib/python3.7/site-packages/rules/permissions.py" in has_perm
  24.     return permissions.test_rule(name, *args, **kwargs)

File "/home/timo/job/integreat/django/.venv/lib/python3.7/site-packages/rules/rulesets.py" in test_rule
  6.         return name in self and self[name].test(*args, **kwargs)

File "/home/timo/job/integreat/django/.venv/lib/python3.7/site-packages/rules/predicates.py" in test
  154.             return bool(self._apply(*args))

File "/home/timo/job/integreat/django/.venv/lib/python3.7/site-packages/rules/predicates.py" in _apply
  213.         result = self.fn(*callargs)

File "/home/timo/job/integreat/django/.venv/lib/python3.7/site-packages/rules/predicates.py" in OR
  165.             return self._combine(other, operator.or_, args)

File "/home/timo/job/integreat/django/.venv/lib/python3.7/site-packages/rules/predicates.py" in _combine
  184.         self_result = self._apply(*args)

File "/home/timo/job/integreat/django/.venv/lib/python3.7/site-packages/rules/predicates.py" in _apply
  213.         result = self.fn(*callargs)

File "/home/timo/job/integreat/django/.venv/lib/python3.7/site-packages/rules/predicates.py" in OR
  165.             return self._combine(other, operator.or_, args)

File "/home/timo/job/integreat/django/.venv/lib/python3.7/site-packages/rules/predicates.py" in _combine
  184.         self_result = self._apply(*args)

File "/home/timo/job/integreat/django/.venv/lib/python3.7/site-packages/rules/predicates.py" in _apply
  213.         result = self.fn(*callargs)

File "/home/timo/job/integreat/django/.venv/lib/python3.7/site-packages/rules/predicates.py" in OR
  165.             return self._combine(other, operator.or_, args)

File "/home/timo/job/integreat/django/.venv/lib/python3.7/site-packages/rules/predicates.py" in _combine
  184.         self_result = self._apply(*args)

File "/home/timo/job/integreat/django/.venv/lib/python3.7/site-packages/rules/predicates.py" in _apply
  213.         result = self.fn(*callargs)

File "/home/timo/job/integreat/django/src/cms/rules.py" in can_edit_all_pages
  79.         if page.region not in user.profile.regions.all():

Exception Type: AttributeError at /augsburg/pages/de-de/
Exception Value: 'NoneType' object has no attribute 'region'

  
</details>


Discussion:

Resolving patch:
diff --git a/src/cms/rules.py b/src/cms/rules.py
--- a/src/cms/rules.py
+++ b/src/cms/rules.py
@@ -76,7 +76,7 @@ def can_edit_all_pages(user, page):
     :rtype: bool
     """
     if not (user.is_superuser or user.is_staff):
-        if page.region not in user.profile.regions.all():
+        if page and page.region not in user.profile.regions.all():
             return False
     return user.has_perm("cms.edit_pages")
 
@@ -96,7 +96,7 @@ def can_publish_all_pages(user, page):
     :rtype: bool
     """
     if not (user.is_superuser or user.is_staff):
-        if page.region not in user.profile.regions.all():
+        if page and page.region not in user.profile.regions.all():
             return False
     return user.has_perm("cms.publish_pages")
 


Test patch:
diff --git a/src/cms/tests/views/view_test_utils.py b/src/cms/tests/views/view_test_utils.py
--- a/src/cms/tests/views/view_test_utils.py
+++ b/src/cms/tests/views/view_test_utils.py
@@ -21,7 +21,7 @@ def generate_test_functions(class_variables, views, kwargs):
         # pylint: disable=cell-var-from-loop
         url = reverse(view, args=(), kwargs=kwargs)
 
-        def test_function(self):
+        def test_function(self, url=url):
             response = self.client.get(url)
             self.assertHttp200(response)