diff --git a/taiga/projects/settings/api.py b/taiga/projects/settings/api.py
index 6ff89e17..4aabed59 100644
--- a/taiga/projects/settings/api.py
+++ b/taiga/projects/settings/api.py
@@ -28,11 +28,13 @@ from . import models
from . import permissions
from . import serializers
from . import services
+from . import validators
class UserProjectSettingsViewSet(ModelCrudViewSet):
serializer_class = serializers.UserProjectSettingsSerializer
permission_classes = (permissions.UserProjectSettingsPermission,)
+ validator_class = validators.UserProjectSettingsValidator
def _build_user_project_settings(self):
projects = Project.objects.filter(
diff --git a/taiga/projects/settings/serializers.py b/taiga/projects/settings/serializers.py
index dd10cbf7..f5539038 100644
--- a/taiga/projects/settings/serializers.py
+++ b/taiga/projects/settings/serializers.py
@@ -17,11 +17,10 @@
# along with this program. If not, see .
from taiga.base.api import serializers
-from taiga.permissions.services import is_project_admin, user_has_perm
from . import models
-from taiga.projects.settings.choices import Section
+from taiga.projects.settings.utils import get_allowed_sections
class UserProjectSettingsSerializer(serializers.ModelSerializer):
@@ -36,21 +35,4 @@ class UserProjectSettingsSerializer(serializers.ModelSerializer):
return obj.project.name
def get_allowed_sections(self, obj):
- sections = [Section.timeline, Section.search, Section.team]
- active_modules = {'epics': 'view_epics', 'backlog': 'view_us',
- 'kanban': 'view_us', 'wiki': 'view_wiki_pages',
- 'issues': 'view_issues'}
-
- for key in active_modules:
- module_name = "is_{}_activated".format(key)
- if getattr(obj.project, module_name) and \
- user_has_perm(obj.user, active_modules[key], obj.project):
- sections.append(getattr(Section, key))
-
- if obj.project.videoconferences:
- sections.append(Section.meetup)
-
- if is_project_admin(obj.user, obj.project):
- sections.append(Section.admin)
-
- return sections
+ return get_allowed_sections(obj)
diff --git a/taiga/projects/settings/utils.py b/taiga/projects/settings/utils.py
new file mode 100644
index 00000000..03560eb6
--- /dev/null
+++ b/taiga/projects/settings/utils.py
@@ -0,0 +1,23 @@
+from taiga.permissions.services import is_project_admin, user_has_perm
+from taiga.projects.settings.choices import Section
+
+
+def get_allowed_sections(obj):
+ sections = [Section.timeline, Section.search, Section.team]
+ active_modules = {'epics': 'view_epics', 'backlog': 'view_us',
+ 'kanban': 'view_us', 'wiki': 'view_wiki_pages',
+ 'issues': 'view_issues'}
+
+ for key in active_modules:
+ module_name = "is_{}_activated".format(key)
+ if getattr(obj.project, module_name) and \
+ user_has_perm(obj.user, active_modules[key], obj.project):
+ sections.append(getattr(Section, key))
+
+ if obj.project.videoconferences:
+ sections.append(Section.meetup)
+
+ if is_project_admin(obj.user, obj.project):
+ sections.append(Section.admin)
+
+ return sections
diff --git a/taiga/projects/settings/validators.py b/taiga/projects/settings/validators.py
new file mode 100644
index 00000000..ff7206fc
--- /dev/null
+++ b/taiga/projects/settings/validators.py
@@ -0,0 +1,38 @@
+# -*- coding: utf-8 -*-
+# Copyright (C) 2014-2017 Andrey Antukh
+# Copyright (C) 2014-2017 Jesús Espino
+# Copyright (C) 2014-2017 David Barragán
+# Copyright (C) 2014-2017 Alejandro Alonso
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see .
+from django.utils.translation import ugettext as _
+
+from taiga.base.api import validators
+from taiga.base.exceptions import ValidationError
+from taiga.projects.settings.utils import get_allowed_sections
+
+from . import models
+
+
+class UserProjectSettingsValidator(validators.ModelValidator):
+
+ class Meta:
+ model = models.UserProjectSettings
+ read_only_fields = ('id', 'created_at', 'modified_at', 'project',
+ 'user')
+
+ def validate_homepage(self, attrs, source):
+ if attrs[source] not in get_allowed_sections(self.object):
+ msg = _("You don't have access to this section")
+ raise ValidationError(msg)
+ return attrs
diff --git a/tests/integration/test_project_settings.py b/tests/integration/test_project_settings.py
index fa288326..4ebad339 100644
--- a/tests/integration/test_project_settings.py
+++ b/tests/integration/test_project_settings.py
@@ -1,3 +1,5 @@
+import json
+
import pytest
from django.apps import apps
@@ -34,7 +36,7 @@ def test_create_retrieve_home_page_setting():
assert setting.homepage == Section.timeline
-def test_retrieve_home_page_setting_with_allowed_sections(client):
+def test_retrieve_homepage_setting_with_allowed_sections(client):
# Default template has next configuration:
# "is_epics_activated": false,
# "is_backlog_activated": true,
@@ -66,3 +68,31 @@ def test_retrieve_home_page_setting_with_allowed_sections(client):
assert Section.epics not in response.data[0].get("allowed_sections")
assert Section.issues not in response.data[0].get("allowed_sections")
+
+
+def test_avoid_patch_homepage_setting_with_not_allowed_section(client):
+ # Default template has next configuration:
+ # "is_epics_activated": false,
+ # "is_backlog_activated": true,
+ # "is_kanban_activated": false,
+ # "is_wiki_activated": true,
+ # "is_issues_activated": true,
+ # "videoconferences": null,
+ user = f.UserFactory.create()
+ project = f.ProjectFactory.create(owner=user)
+ membership = f.MembershipFactory.create(user=user, project=project,
+ is_admin=False)
+ membership.role.permissions = ["view_us", "view_wiki_pages"]
+ membership.role.save()
+
+ setting = services.create_user_project_settings_if_not_exists(project,
+ project.owner)
+
+ url = reverse("user-project-settings-detail", args=[setting.pk])
+
+ client.login(project.owner)
+ response = client.json.patch(url, data=json.dumps({"homepage": Section.backlog}))
+ assert response.status_code == 200
+
+ response = client.json.patch(url, data=json.dumps({"homepage": Section.issues}))
+ assert response.status_code == 400