You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@allura.apache.org by he...@apache.org on 2015/06/15 14:27:54 UTC
[35/46] allura git commit: [#7886] ticket:789 Rate limit ticket
creation
[#7886] ticket:789 Rate limit ticket creation
Project: http://git-wip-us.apache.org/repos/asf/allura/repo
Commit: http://git-wip-us.apache.org/repos/asf/allura/commit/1004b078
Tree: http://git-wip-us.apache.org/repos/asf/allura/tree/1004b078
Diff: http://git-wip-us.apache.org/repos/asf/allura/diff/1004b078
Branch: refs/heads/hs/7873
Commit: 1004b07892ca6dafb9a19d963fb880f7d3a3cd93
Parents: d22046c
Author: Igor Bondarenko <je...@gmail.com>
Authored: Wed Jun 10 08:41:17 2015 +0000
Committer: Dave Brondsema <db...@slashdotmedia.com>
Committed: Fri Jun 12 18:44:25 2015 +0000
----------------------------------------------------------------------
Allura/allura/lib/exceptions.py | 7 +++
.../forgetracker/tests/functional/test_rest.py | 25 +++++++--
.../forgetracker/tests/functional/test_root.py | 55 +++++++++++++++++++-
ForgeTracker/forgetracker/tracker_main.py | 14 +++++
4 files changed, 95 insertions(+), 6 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/allura/blob/1004b078/Allura/allura/lib/exceptions.py
----------------------------------------------------------------------
diff --git a/Allura/allura/lib/exceptions.py b/Allura/allura/lib/exceptions.py
index 21dd1d2..45843bf 100644
--- a/Allura/allura/lib/exceptions.py
+++ b/Allura/allura/lib/exceptions.py
@@ -15,6 +15,7 @@
# specific language governing permissions and limitations
# under the License.
+import webob
from formencode import Invalid
@@ -99,3 +100,9 @@ class CompoundError(ForgeError):
parts.append(' ' + line)
parts.append('</%s>\n' % self.__class__.__name__)
return ''.join(parts)
+
+
+class HTTPTooManyRequests(webob.exc.HTTPClientError):
+ code = 429
+ title = 'Too Many Requests'
+ explanation = 'Too Many Requests'
http://git-wip-us.apache.org/repos/asf/allura/blob/1004b078/ForgeTracker/forgetracker/tests/functional/test_rest.py
----------------------------------------------------------------------
diff --git a/ForgeTracker/forgetracker/tests/functional/test_rest.py b/ForgeTracker/forgetracker/tests/functional/test_rest.py
index 0932435..7d8d91a 100644
--- a/ForgeTracker/forgetracker/tests/functional/test_rest.py
+++ b/ForgeTracker/forgetracker/tests/functional/test_rest.py
@@ -18,7 +18,9 @@
from pylons import tmpl_context as c
from datadiff.tools import assert_equal
+from nose.tools import assert_not_equal
from mock import patch
+from tg import config
from allura.lib import helpers as h
from allura.tests import decorators as td
@@ -41,18 +43,18 @@ class TestTrackerApiBase(TestRestApiBase):
h.set_context('test', 'bugs', neighborhood='Projects')
self.tracker_globals = c.app.globals
- def create_ticket(self):
+ def create_ticket(self, summary=None, status=None):
return self.api_post(
'/rest/p/test/bugs/new',
wrap_args='ticket_form',
params=dict(
- summary='test new ticket',
+ summary=summary or 'test new ticket',
status=self.tracker_globals.open_status_names.split()[0],
labels='',
description='',
assigned_to='',
- **{'custom_fields._milestone': ''})
- )
+ **{'custom_fields._milestone': ''}),
+ status=status)
class TestRestNewTicket(TestTrackerApiBase):
@@ -81,6 +83,21 @@ class TestRestNewTicket(TestTrackerApiBase):
def test_invalid_ticket(self):
self.app.get('/rest/p/test/bugs/2', status=404)
+ def test_create_limit(self):
+ self.create_ticket(summary='First ticket')
+ # Set rate limit to unlimit
+ with h.push_config(config, **{'forgetracker.rate_limits': '{}'}):
+ summary = 'Second ticket'
+ self.create_ticket(summary=summary)
+ t = TM.Ticket.query.get(summary=summary)
+ assert_not_equal(t, None)
+ # Set rate limit to 1 in first hour of project
+ with h.push_config(config, **{'forgetracker.rate_limits': '{"3600": 1}'}):
+ summary = 'Third ticket'
+ self.create_ticket(summary=summary, status=429)
+ t = TM.Ticket.query.get(summary=summary)
+ assert_equal(t, None)
+
class TestRestUpdateTicket(TestTrackerApiBase):
http://git-wip-us.apache.org/repos/asf/allura/blob/1004b078/ForgeTracker/forgetracker/tests/functional/test_root.py
----------------------------------------------------------------------
diff --git a/ForgeTracker/forgetracker/tests/functional/test_root.py b/ForgeTracker/forgetracker/tests/functional/test_root.py
index 7f67b3a..122d707 100644
--- a/ForgeTracker/forgetracker/tests/functional/test_root.py
+++ b/ForgeTracker/forgetracker/tests/functional/test_root.py
@@ -28,11 +28,20 @@ import mock
import PIL
from mock import patch
-from nose.tools import assert_true, assert_false, assert_equal, assert_in
-from nose.tools import assert_raises, assert_not_in, assert_items_equal
+from nose.tools import (
+ assert_true,
+ assert_false,
+ assert_equal,
+ assert_in,
+ assert_raises,
+ assert_not_in,
+ assert_items_equal,
+ assert_not_equal,
+)
from formencode.variabledecode import variable_encode
from pylons import tmpl_context as c
from pylons import app_globals as g
+from tg import config
from alluratest.controller import TestController, setup_basic_test
from allura import model as M
@@ -2363,6 +2372,48 @@ class TestFunctionalController(TrackerTestController):
assert query_filter_choices.call_count == 1
assert query_filter_choices.call_args[0][0] == '!status_s:wont-fix && !status_s:closed'
+ def test_rate_limit_new(self):
+ self.new_ticket(summary='First ticket')
+ # Set rate limit to unlimit
+ with h.push_config(config, **{'forgetracker.rate_limits': '{}'}):
+ r = self.app.get('/bugs/new/')
+ assert_equal(r.status_int, 200)
+ # Set rate limit to 1 in first hour of project
+ with h.push_config(config, **{'forgetracker.rate_limits': '{"3600": 1}'}):
+ r = self.app.get('/bugs/new/')
+ assert_equal(r.status_int, 302)
+ assert_equal(r.location, 'http://localhost/bugs/')
+ wf = json.loads(self.webflash(r))
+ assert_equal(wf['status'], 'error')
+ assert_equal(
+ wf['message'],
+ 'Ticket creation rate limit exceeded. Please try again later.')
+
+ def test_rate_limit_save_ticket(self):
+ # Set rate limit to unlimit
+ with h.push_config(config, **{'forgetracker.rate_limits': '{}'}):
+ summary = 'Ticket w/o limit'
+ post_data = {'ticket_form.summary': summary}
+ r = self.app.post('/bugs/save_ticket', post_data).follow()
+ assert_in(summary, r)
+ t = tm.Ticket.query.get(summary=summary)
+ assert_not_equal(t, None)
+ # Set rate limit to 1 in first hour of project
+ with h.push_config(config, **{'forgetracker.rate_limits': '{"3600": 1}'}):
+ summary = 'Ticket with limit'
+ post_data = {'ticket_form.summary': summary}
+ r = self.app.post('/bugs/save_ticket', post_data)
+ assert_equal(r.status_int, 302)
+ assert_equal(r.location, 'http://localhost/bugs/')
+ wf = json.loads(self.webflash(r))
+ assert_equal(wf['status'], 'error')
+ assert_equal(
+ wf['message'],
+ 'Ticket creation rate limit exceeded. Please try again later.')
+ assert_not_in(summary, r.follow())
+ t = tm.Ticket.query.get(summary=summary)
+ assert_equal(t, None)
+
class TestMilestoneAdmin(TrackerTestController):
def _post(self, params, **kw):
http://git-wip-us.apache.org/repos/asf/allura/blob/1004b078/ForgeTracker/forgetracker/tracker_main.py
----------------------------------------------------------------------
diff --git a/ForgeTracker/forgetracker/tracker_main.py b/ForgeTracker/forgetracker/tracker_main.py
index e69e349..68b1a0c 100644
--- a/ForgeTracker/forgetracker/tracker_main.py
+++ b/ForgeTracker/forgetracker/tracker_main.py
@@ -57,6 +57,7 @@ from allura.lib import validators as V
from allura.lib.widgets import form_fields as ffw
from allura.lib.widgets.subscriptions import SubscribeForm
from allura.lib.plugin import ImportIdConverter
+from allura.lib import exceptions as forge_exc
from allura.controllers import AppDiscussionController, AppDiscussionRestController
from allura.controllers import attachments as att
from allura.controllers import BaseController
@@ -630,6 +631,13 @@ class RootController(BaseController, FeedController):
def _check_security(self):
require_access(c.app, 'read')
+ def rate_limit(self, redir='..'):
+ if TM.Ticket.is_limit_exceeded(c.app.config):
+ msg = 'Ticket creation rate limit exceeded. '
+ log.warn(msg + c.app.config.url())
+ flash(msg + 'Please try again later.', 'error')
+ redirect(redir)
+
@expose('json:')
def bin_counts(self, *args, **kw):
bin_counts = []
@@ -865,6 +873,7 @@ class RootController(BaseController, FeedController):
@expose('jinja:forgetracker:templates/tracker/new_ticket.html')
def new(self, description=None, summary=None, labels=None, **kw):
require_access(c.app, 'create')
+ self.rate_limit(redir='..')
c.ticket_form = W.ticket_form
help_msg = c.app.config.options.get('TicketHelpNew', '').strip()
return dict(action=c.app.config.url() + 'save_ticket',
@@ -901,6 +910,7 @@ class RootController(BaseController, FeedController):
require_access(ticket, 'update')
else:
require_access(c.app, 'create')
+ self.rate_limit(redir='.')
ticket = TM.Ticket.new()
ticket.update(ticket_form)
c.app.globals.invalidate_bin_counts()
@@ -1786,6 +1796,10 @@ class RootRestController(BaseController, AppRestControllerMixin):
@validate(W.ticket_form, error_handler=h.json_validation_error)
def new(self, ticket_form=None, **post_data):
require_access(c.app, 'create')
+ if TM.Ticket.is_limit_exceeded(c.app.config):
+ msg = 'Ticket creation rate limit exceeded. '
+ log.warn(msg + c.app.config.url())
+ raise forge_exc.HTTPTooManyRequests()
if c.app.globals.milestone_names is None:
c.app.globals.milestone_names = ''
ticket = TM.Ticket.new()