US #1313: Setup throttling policy

remotes/origin/enhancement/email-actions
David Barragán Merino 2014-11-26 12:33:38 +01:00
parent af262e1410
commit 53266a512f
8 changed files with 204 additions and 3 deletions

View File

@ -291,6 +291,15 @@ REST_FRAMEWORK = {
# Mainly used for api debug. # Mainly used for api debug.
"taiga.auth.backends.Session", "taiga.auth.backends.Session",
), ),
"DEFAULT_THROTTLE_CLASSES": (
"taiga.base.throttling.AnonRateThrottle",
"taiga.base.throttling.UserRateThrottle"
),
"DEFAULT_THROTTLE_RATES": {
"anon": None,
"user": None,
"import-mode": 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",
"PAGINATE_BY": 30, "PAGINATE_BY": 30,
@ -299,6 +308,7 @@ REST_FRAMEWORK = {
"DATETIME_FORMAT": "%Y-%m-%dT%H:%M:%S%z" "DATETIME_FORMAT": "%Y-%m-%dT%H:%M:%S%z"
} }
DEFAULT_PROJECT_TEMPLATE = "scrum" DEFAULT_PROJECT_TEMPLATE = "scrum"
PUBLIC_REGISTER_ENABLED = False PUBLIC_REGISTER_ENABLED = False

View File

@ -32,20 +32,29 @@ from .development import *
#MEDIA_ROOT = '/home/taiga/media' #MEDIA_ROOT = '/home/taiga/media'
#STATIC_ROOT = '/home/taiga/static' #STATIC_ROOT = '/home/taiga/static'
# EMAIL SETTINGS EXAMPLE
#EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
#EMAIL_USE_TLS = False #EMAIL_USE_TLS = False
#EMAIL_HOST = 'localhost' #EMAIL_HOST = 'localhost'
#EMAIL_PORT = 25
#EMAIL_HOST_USER = 'user' #EMAIL_HOST_USER = 'user'
#EMAIL_HOST_PASSWORD = 'password' #EMAIL_HOST_PASSWORD = 'password'
#EMAIL_PORT = 25
#DEFAULT_FROM_EMAIL = "john@doe.com" #DEFAULT_FROM_EMAIL = "john@doe.com"
# GMAIL SETTINGS EXAMPLE # GMAIL SETTINGS EXAMPLE
#EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend' #EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
#EMAIL_USE_TLS = True #EMAIL_USE_TLS = True
#EMAIL_HOST = 'smtp.gmail.com' #EMAIL_HOST = 'smtp.gmail.com'
#EMAIL_PORT = 587
#EMAIL_HOST_USER = 'youremail@gmail.com' #EMAIL_HOST_USER = 'youremail@gmail.com'
#EMAIL_HOST_PASSWORD = 'yourpassword' #EMAIL_HOST_PASSWORD = 'yourpassword'
#EMAIL_PORT = 587
# THROTTLING
#REST_FRAMEWORK["DEFAULT_THROTTLE_RATES"] = {
# "anon": "20/min",
# "user": "200/min",
# "import-mode": "20/sec"
#}
# GITHUB SETTINGS # GITHUB SETTINGS
#GITHUB_URL = "https://github.com/" #GITHUB_URL = "https://github.com/"

View File

@ -24,3 +24,9 @@ MEDIA_ROOT = "/tmp"
EMAIL_BACKEND = "django.core.mail.backends.locmem.EmailBackend" EMAIL_BACKEND = "django.core.mail.backends.locmem.EmailBackend"
INSTALLED_APPS = INSTALLED_APPS + ["tests"] INSTALLED_APPS = INSTALLED_APPS + ["tests"]
REST_FRAMEWORK["DEFAULT_THROTTLE_RATES"] = {
"anon": None,
"user": None,
"import-mode": None
}

25
taiga/base/throttling.py Normal file
View File

@ -0,0 +1,25 @@
# Copyright (C) 2014 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014 Jesús Espino <jespinog@gmail.com>
# Copyright (C) 2014 David Barragán <bameda@dbarragan.com>
# 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 rest_framework import throttling
class AnonRateThrottle(throttling.AnonRateThrottle):
scope = "anon"
class UserRateThrottle(throttling.UserRateThrottle):
scope = "user"

View File

@ -28,6 +28,7 @@ from taiga.base.decorators import detail_route
from taiga.projects.models import Project, Membership from taiga.projects.models import Project, Membership
from taiga.projects.issues.models import Issue from taiga.projects.issues.models import Issue
from . import mixins
from . import serializers from . import serializers
from . import service from . import service
from . import permissions from . import permissions
@ -37,7 +38,7 @@ class Http400(APIException):
status_code = 400 status_code = 400
class ProjectImporterViewSet(CreateModelMixin, GenericViewSet): class ProjectImporterViewSet(mixins.ImportThrottlingPolicyMixin, CreateModelMixin, GenericViewSet):
model = Project model = Project
permission_classes = (permissions.ImportPermission, ) permission_classes = (permissions.ImportPermission, )

View File

@ -0,0 +1,21 @@
# Copyright (C) 2014 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014 Jesús Espino <jespinog@gmail.com>
# Copyright (C) 2014 David Barragán <bameda@dbarragan.com>
# 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 . import throttling
class ImportThrottlingPolicyMixin:
throttle_classes = (throttling.ImportModeRateThrottle,)

View File

@ -0,0 +1,21 @@
# Copyright (C) 2014 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014 Jesús Espino <jespinog@gmail.com>
# Copyright (C) 2014 David Barragán <bameda@dbarragan.com>
# 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 ImportModeRateThrottle(throttling.UserRateThrottle):
scope = "import-mode"

View File

@ -0,0 +1,108 @@
import pytest
from unittest import mock
from django.core.urlresolvers import reverse
from django.core.cache import cache
from taiga.base.utils import json
from .. import factories as f
pytestmark = pytest.mark.django_db
anon_rate_path = "taiga.base.throttling.AnonRateThrottle.get_rate"
user_rate_path = "taiga.base.throttling.UserRateThrottle.get_rate"
import_rate_path = "taiga.export_import.throttling.ImportModeRateThrottle.get_rate"
def test_anonimous_throttling_policy(client, settings):
project = f.create_project()
url = reverse("projects-list")
with mock.patch(anon_rate_path) as anon_rate, \
mock.patch(user_rate_path) as user_rate, \
mock.patch(import_rate_path) as import_rate:
anon_rate.return_value = "2/day"
user_rate.return_value = "4/day"
import_rate.return_value = "7/day"
cache.clear()
response = client.json.get(url)
assert response.status_code == 200
response = client.json.get(url)
assert response.status_code == 200
response = client.json.get(url)
assert response.status_code == 429
def test_user_throttling_policy(client, settings):
project = f.create_project()
membership = f.MembershipFactory.create(project=project, user=project.owner, is_owner=True)
url = reverse("projects-detail", kwargs={"pk": project.pk})
client.login(project.owner)
with mock.patch(anon_rate_path) as anon_rate, \
mock.patch(user_rate_path) as user_rate, \
mock.patch(import_rate_path) as import_rate:
anon_rate.return_value = "2/day"
user_rate.return_value = "4/day"
import_rate.return_value = "7/day"
cache.clear()
response = client.json.get(url)
assert response.status_code == 200
response = client.json.get(url)
assert response.status_code == 200
response = client.json.get(url)
assert response.status_code == 200
response = client.json.get(url)
assert response.status_code == 200
response = client.json.get(url)
assert response.status_code == 429
client.logout()
def test_import_mode_throttling_policy(client, settings):
project = f.create_project()
membership = f.MembershipFactory.create(project=project, user=project.owner, is_owner=True)
project.default_issue_type = f.IssueTypeFactory.create(project=project)
project.default_issue_status = f.IssueStatusFactory.create(project=project)
project.default_severity = f.SeverityFactory.create(project=project)
project.default_priority = f.PriorityFactory.create(project=project)
project.save()
url = reverse("importer-issue", args=[project.pk])
data = {
"subject": "Test"
}
client.login(project.owner)
with mock.patch(anon_rate_path) as anon_rate, \
mock.patch(user_rate_path) as user_rate, \
mock.patch(import_rate_path) as import_rate:
anon_rate.return_value = "2/day"
user_rate.return_value = "4/day"
import_rate.return_value = "7/day"
cache.clear()
response = client.json.post(url, json.dumps(data))
assert response.status_code == 201
response = client.json.post(url, json.dumps(data))
assert response.status_code == 201
response = client.json.post(url, json.dumps(data))
assert response.status_code == 201
response = client.json.post(url, json.dumps(data))
assert response.status_code == 201
response = client.json.post(url, json.dumps(data))
assert response.status_code == 201
response = client.json.post(url, json.dumps(data))
assert response.status_code == 201
response = client.json.post(url, json.dumps(data))
assert response.status_code == 201
response = client.json.post(url, json.dumps(data))
assert response.status_code == 429
client.logout()