allura-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From brond...@apache.org
Subject [1/5] allura git commit: [#7886] ticket:789 Rate limit ticket creation
Date Fri, 12 Jun 2015 19:38:22 GMT
Repository: allura
Updated Branches:
  refs/heads/master 51fc93b47 -> be5c8111f


[#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/master
Commit: 1004b07892ca6dafb9a19d963fb880f7d3a3cd93
Parents: d22046c
Author: Igor Bondarenko <jetmind2@gmail.com>
Authored: Wed Jun 10 08:41:17 2015 +0000
Committer: Dave Brondsema <dbrondsema@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()


Mime
View raw message