Merge pull request #907 from taigaio/throttling-on-memberships
Adding throttling to membershipsremotes/origin/github-import
commit
8ca91f8d57
|
@ -440,6 +440,7 @@ REST_FRAMEWORK = {
|
|||
"user": None,
|
||||
"import-mode": None,
|
||||
"import-dump-mode": "1/minute",
|
||||
"memberships": None,
|
||||
},
|
||||
"FILTER_BACKEND": "taiga.base.filters.FilterBackend",
|
||||
"EXCEPTION_HANDLER": "taiga.base.exceptions.exception_handler",
|
||||
|
|
|
@ -33,4 +33,5 @@ REST_FRAMEWORK["DEFAULT_THROTTLE_RATES"] = {
|
|||
"user": None,
|
||||
"import-mode": None,
|
||||
"import-dump-mode": None,
|
||||
"memberships": None,
|
||||
}
|
||||
|
|
|
@ -21,7 +21,19 @@ from taiga.base.api import throttling
|
|||
|
||||
class AnonRateThrottle(throttling.AnonRateThrottle):
|
||||
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):
|
||||
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)
|
||||
|
|
|
@ -60,6 +60,7 @@ from . import serializers
|
|||
from . import validators
|
||||
from . import services
|
||||
from . import utils as project_utils
|
||||
from . import throttling
|
||||
|
||||
######################################################
|
||||
# Project
|
||||
|
@ -658,6 +659,7 @@ class MembershipViewSet(BlockedByProjectMixin, ModelCrudViewSet):
|
|||
permission_classes = (permissions.MembershipPermission,)
|
||||
filter_backends = (filters.CanViewProjectFilterBackend,)
|
||||
filter_fields = ("project", "role")
|
||||
throttle_classes = (throttling.MembershipsRateThrottle,)
|
||||
|
||||
def get_serializer_class(self):
|
||||
use_admin_serializer = False
|
||||
|
|
|
@ -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"]
|
|
@ -697,3 +697,88 @@ def test_api_create_bulk_members_max_pending_memberships(client, settings):
|
|||
client.login(john)
|
||||
response = client.json.post(url, json.dumps(data))
|
||||
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
|
||||
|
|
Loading…
Reference in New Issue