Merge pull request #907 from taigaio/throttling-on-memberships

Adding throttling to memberships
remotes/origin/github-import
Alejandro 2017-01-02 14:17:52 +01:00 committed by GitHub
commit 8ca91f8d57
6 changed files with 125 additions and 0 deletions

View File

@ -440,6 +440,7 @@ REST_FRAMEWORK = {
"user": None, "user": None,
"import-mode": None, "import-mode": None,
"import-dump-mode": "1/minute", "import-dump-mode": "1/minute",
"memberships": None,
}, },
"FILTER_BACKEND": "taiga.base.filters.FilterBackend", "FILTER_BACKEND": "taiga.base.filters.FilterBackend",
"EXCEPTION_HANDLER": "taiga.base.exceptions.exception_handler", "EXCEPTION_HANDLER": "taiga.base.exceptions.exception_handler",

View File

@ -33,4 +33,5 @@ REST_FRAMEWORK["DEFAULT_THROTTLE_RATES"] = {
"user": None, "user": None,
"import-mode": None, "import-mode": None,
"import-dump-mode": None, "import-dump-mode": None,
"memberships": None,
} }

View File

@ -21,7 +21,19 @@ from taiga.base.api import throttling
class AnonRateThrottle(throttling.AnonRateThrottle): class AnonRateThrottle(throttling.AnonRateThrottle):
scope = "anon" scope = "anon"
throttled_methods = ["GET", "POST", "PUT", "DELETE", "PATCH"]
def allow_request(self, request, view):
if request.method not in self.throttled_methods:
return True
return super().allow_request(request, view)
class UserRateThrottle(throttling.UserRateThrottle): class UserRateThrottle(throttling.UserRateThrottle):
scope = "user" scope = "user"
throttled_methods = ["GET", "POST", "PUT", "DELETE", "PATCH"]
def allow_request(self, request, view):
if request.method not in self.throttled_methods:
return True
return super().allow_request(request, view)

View File

@ -60,6 +60,7 @@ from . import serializers
from . import validators from . import validators
from . import services from . import services
from . import utils as project_utils from . import utils as project_utils
from . import throttling
###################################################### ######################################################
# Project # Project
@ -658,6 +659,7 @@ class MembershipViewSet(BlockedByProjectMixin, ModelCrudViewSet):
permission_classes = (permissions.MembershipPermission,) permission_classes = (permissions.MembershipPermission,)
filter_backends = (filters.CanViewProjectFilterBackend,) filter_backends = (filters.CanViewProjectFilterBackend,)
filter_fields = ("project", "role") filter_fields = ("project", "role")
throttle_classes = (throttling.MembershipsRateThrottle,)
def get_serializer_class(self): def get_serializer_class(self):
use_admin_serializer = False use_admin_serializer = False

View File

@ -0,0 +1,24 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2014-2016 Andrey Antukh <niwi@niwi.nz>
# Copyright (C) 2014-2016 Jesús Espino <jespinog@gmail.com>
# Copyright (C) 2014-2016 David Barragán <bameda@dbarragan.com>
# Copyright (C) 2014-2016 Alejandro Alonso <alejandro.alonso@kaleidos.net>
# 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 <http://www.gnu.org/licenses/>.
from taiga.base import throttling
class MembershipsRateThrottle(throttling.UserRateThrottle):
scope = "memberships"
throttled_methods = ["POST", "PUT"]

View File

@ -697,3 +697,88 @@ def test_api_create_bulk_members_max_pending_memberships(client, settings):
client.login(john) client.login(john)
response = client.json.post(url, json.dumps(data)) response = client.json.post(url, json.dumps(data))
assert response.status_code == 400 assert response.status_code == 400
assert "limit of pending memberships" in response.data["_error_message"]
def test_create_memberhips_throttling(client, settings):
settings.REST_FRAMEWORK["DEFAULT_THROTTLE_RATES"]["memberships"] = "1/minute"
membership = f.MembershipFactory(is_admin=True)
role = f.RoleFactory.create(project=membership.project)
user = f.UserFactory.create()
user2 = f.UserFactory.create()
client.login(membership.user)
url = reverse("memberships-list")
data = {"role": role.pk, "project": role.project.pk, "username": user.email}
response = client.json.post(url, json.dumps(data))
assert response.status_code == 201
assert response.data["user_email"] == user.email
data = {"role": role.pk, "project": role.project.pk, "username": user2.email}
response = client.json.post(url, json.dumps(data))
assert response.status_code == 429
settings.REST_FRAMEWORK["DEFAULT_THROTTLE_RATES"]["memberships"] = None
def test_api_resend_invitation_throttling(client, outbox, settings):
settings.REST_FRAMEWORK["DEFAULT_THROTTLE_RATES"]["memberships"] = "1/minute"
invitation = f.create_invitation(user=None)
f.MembershipFactory(project=invitation.project, user=invitation.project.owner, is_admin=True)
url = reverse("memberships-resend-invitation", kwargs={"pk": invitation.pk})
client.login(invitation.project.owner)
response = client.post(url)
assert response.status_code == 204
assert len(outbox) == 1
assert outbox[0].to == [invitation.email]
response = client.post(url)
assert response.status_code == 429
assert len(outbox) == 1
assert outbox[0].to == [invitation.email]
settings.REST_FRAMEWORK["DEFAULT_THROTTLE_RATES"]["memberships"] = None
def test_api_create_bulk_members_throttling(client, settings):
settings.REST_FRAMEWORK["DEFAULT_THROTTLE_RATES"]["memberships"] = "1/minute"
project = f.ProjectFactory()
john = f.UserFactory.create()
joseph = f.UserFactory.create()
other = f.UserFactory.create()
tester = f.RoleFactory(project=project, name="Tester", permissions=["view_project"])
gamer = f.RoleFactory(project=project, name="Gamer", permissions=["view_project"])
f.MembershipFactory(project=project, user=john, role=tester, is_admin=True)
# John and Other are members from another project
project2 = f.ProjectFactory()
f.MembershipFactory(project=project2, user=john, role=gamer, is_admin=True)
f.MembershipFactory(project=project2, user=other, role=gamer)
url = reverse("memberships-bulk-create")
data = {
"project_id": project.id,
"bulk_memberships": [
{"role_id": gamer.pk, "username": joseph.email},
{"role_id": gamer.pk, "username": other.username},
]
}
client.login(john)
response = client.json.post(url, json.dumps(data))
assert response.status_code == 200
response_user_ids = set([u["user"] for u in response.data])
user_ids = {other.id, joseph.id}
assert(user_ids.issubset(response_user_ids))
response = client.json.post(url, json.dumps(data))
assert response.status_code == 429
settings.REST_FRAMEWORK["DEFAULT_THROTTLE_RATES"]["memberships"] = None