Repository: allura Updated Branches: refs/heads/kt/8193 [created] cf6bdb395
[#8193] Adds ability to rate-limit comments Project: http://git-wip-us.apache.org/repos/asf/allura/repo Commit: http://git-wip-us.apache.org/repos/asf/allura/commit/cf6bdb39 Tree: http://git-wip-us.apache.org/repos/asf/allura/tree/cf6bdb39 Diff: http://git-wip-us.apache.org/repos/asf/allura/diff/cf6bdb39 Branch: refs/heads/kt/8193 Commit: cf6bdb395eaad635ea0bcce8c9bf6bde21e54210 Parents: 880090c Author: Kenton Taylor <ktay...@slashdotmedia.com> Authored: Wed Mar 7 12:06:15 2018 -0500 Committer: Kenton Taylor <ktay...@slashdotmedia.com> Committed: Wed Mar 7 12:06:15 2018 -0500 ---------------------------------------------------------------------- Allura/allura/controllers/base.py | 14 ++++++++++++++ Allura/allura/controllers/discuss.py | 2 ++ Allura/allura/tests/functional/test_discuss.py | 15 +++++++++++++-- Allura/development.ini | 2 ++ ForgeBlog/forgeblog/main.py | 16 ++++------------ ForgeTracker/forgetracker/tracker_main.py | 10 ++-------- ForgeWiki/forgewiki/wiki_main.py | 13 +++---------- 7 files changed, 40 insertions(+), 32 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/allura/blob/cf6bdb39/Allura/allura/controllers/base.py ---------------------------------------------------------------------- diff --git a/Allura/allura/controllers/base.py b/Allura/allura/controllers/base.py index 215b19d..2cf0c70 100644 --- a/Allura/allura/controllers/base.py +++ b/Allura/allura/controllers/base.py @@ -15,9 +15,16 @@ # specific language governing permissions and limitations # under the License. +import logging + from tg import expose from webob import exc from tg.controllers.dispatcher import ObjectDispatcher +from tg import redirect, flash +from pylons import tmpl_context as c + + +log = logging.getLogger(__name__) class BaseController(object): @@ -28,6 +35,13 @@ class BaseController(object): and possible loops.""" raise exc.HTTPNotFound, name + def rate_limit(self, artifact_class, message, redir='..'): + if artifact_class.is_limit_exceeded(c.app.config, user=c.user): + msg = '{} rate limit exceeded. '.format(message) + log.warn(msg + c.app.config.url()) + flash(msg + 'Please try again later.', 'error') + redirect(redir) + class DispatchIndex(object): http://git-wip-us.apache.org/repos/asf/allura/blob/cf6bdb39/Allura/allura/controllers/discuss.py ---------------------------------------------------------------------- diff --git a/Allura/allura/controllers/discuss.py b/Allura/allura/controllers/discuss.py index a61a9e1..ba31409 100644 --- a/Allura/allura/controllers/discuss.py +++ b/Allura/allura/controllers/discuss.py @@ -209,6 +209,7 @@ class ThreadController(BaseController, FeedController): @utils.AntiSpam.validate('Spambot protection engaged') def post(self, **kw): require_access(self.thread, 'post') + self.rate_limit(M.Post, "Comment", redir='..') if self.thread.ref: require_access(self.thread.ref.artifact, 'post') kw = self.W.edit_post.to_python(kw, None) @@ -344,6 +345,7 @@ class PostController(BaseController): @require_post(redir='.') def reply(self, file_info=None, **kw): require_access(self.thread, 'post') + self.rate_limit(M.Post, "Comment", redir='..') kw = self.W.edit_post.to_python(kw, None) p = self.thread.add_post(parent_id=self.post._id, **kw) p.add_multiple_attachments(file_info) http://git-wip-us.apache.org/repos/asf/allura/blob/cf6bdb39/Allura/allura/tests/functional/test_discuss.py ---------------------------------------------------------------------- diff --git a/Allura/allura/tests/functional/test_discuss.py b/Allura/allura/tests/functional/test_discuss.py index d6af6c9..8f3c968 100644 --- a/Allura/allura/tests/functional/test_discuss.py +++ b/Allura/allura/tests/functional/test_discuss.py @@ -17,12 +17,15 @@ import os from mock import patch -from nose.tools import assert_in, assert_not_in, assert_equal, assert_false, assert_true - +from nose.tools import assert_in, assert_not_in, assert_equal, assert_false, assert_true, assert_raises +from webtest.app import AppError from ming.odm import session from allura.tests import TestController from allura import model as M +from allura.lib import helpers as h +from tg import config + class TestDiscussBase(TestController): @@ -128,6 +131,14 @@ class TestDiscuss(TestDiscussBase): assert submit_spam.call_args[0] == ( 'This is a new post',), submit_spam.call_args[0] + def test_rate_limit(self): + with h.push_config(config, **{'allura.rate_limits_per_user': '{"3600": 2}'}): + for i in range(0, 2): + self._make_post('This is a post {}'.format(i)) + with assert_raises(AppError): + self._make_post('This is a post that should fail.') + return 'foo' + def test_permissions(self): thread_url = self._thread_link() thread_id = thread_url.rstrip('/').split('/')[-1] http://git-wip-us.apache.org/repos/asf/allura/blob/cf6bdb39/Allura/development.ini ---------------------------------------------------------------------- diff --git a/Allura/development.ini b/Allura/development.ini index 4a7b9d9..81ebe6c 100644 --- a/Allura/development.ini +++ b/Allura/development.ini @@ -529,6 +529,8 @@ forgemail.domain = .in.localhost ;forgewiki.rate_limits_per_user = {"60": 3, "120": 3, "900": 5, "1800": 7, "3600": 10, "7200": 15, "86400": 20, "604800": 50, "2592000": 200} ;forgetracker.rate_limits_per_user = {"60": 1, "120": 3, "900": 5, "1800": 7, "3600": 10, "7200": 15, "86400": 20, "604800": 50, "2592000": 200} ;forgeblog.rate_limits_per_user = {"60": 1, "120": 3, "900": 5, "1800": 7, "3600": 10, "7200": 15, "86400": 20, "604800": 50, "2592000": 200} +;allura.rate_limits_per_user = {"60": 1, "120": 3, "900": 5, "1800": 7, "3600": 10, "7200": 15, "86400": 20, "604800": 50, "2592000": 200} + ; set this to "false" if you are deploying to production and want performance improvements auto_reload_templates = true http://git-wip-us.apache.org/repos/asf/allura/blob/cf6bdb39/ForgeBlog/forgeblog/main.py ---------------------------------------------------------------------- diff --git a/ForgeBlog/forgeblog/main.py b/ForgeBlog/forgeblog/main.py index a227ca8..578432c 100644 --- a/ForgeBlog/forgeblog/main.py +++ b/ForgeBlog/forgeblog/main.py @@ -233,14 +233,6 @@ class ForgeBlogApp(Application): self.save_attachments(post_path, post.attachments) -def rate_limit(): - if BM.BlogPost.is_limit_exceeded(c.app.config, user=c.user): - msg = 'Create/edit rate limit exceeded. ' - log.warn(msg + c.app.config.url()) - flash(msg + 'Please try again later.', 'error') - redirect(c.app.config.url()) - - class RootController(BaseController, FeedController): def __init__(self): @@ -293,7 +285,7 @@ class RootController(BaseController, FeedController): @without_trailing_slash def new(self, **kw): require_access(c.app, 'write') - rate_limit() + self.rate_limit(BM.BlogPost, 'Create/edit', c.app.config.url()) post = dict( state='published') c.form = W.new_post_form @@ -305,7 +297,7 @@ class RootController(BaseController, FeedController): @without_trailing_slash def save(self, **kw): require_access(c.app, 'write') - rate_limit() + self.rate_limit(BM.BlogPost, 'Create/edit', c.app.config.url()) post = BM.BlogPost.new(**kw) g.spam_checker.check(kw['title'] + u'\n' + kw['text'], artifact=post, user=c.user, content_type='blog-post') @@ -373,7 +365,7 @@ class PostController(BaseController, FeedController): @without_trailing_slash def edit(self, **kw): require_access(self.post, 'write') - rate_limit() + self.rate_limit(BM.BlogPost, 'Create/edit', c.app.config.url()) c.form = W.edit_post_form c.attachment_add = W.attachment_add c.attachment_list = W.attachment_list @@ -400,7 +392,7 @@ class PostController(BaseController, FeedController): @without_trailing_slash def save(self, delete=None, **kw): require_access(self.post, 'write') - rate_limit() + self.rate_limit(BM.BlogPost, 'Create/edit', c.app.config.url()) if delete: self.post.delete() flash('Post deleted', 'info') http://git-wip-us.apache.org/repos/asf/allura/blob/cf6bdb39/ForgeTracker/forgetracker/tracker_main.py ---------------------------------------------------------------------- diff --git a/ForgeTracker/forgetracker/tracker_main.py b/ForgeTracker/forgetracker/tracker_main.py index 1f9822b..8c8e626 100644 --- a/ForgeTracker/forgetracker/tracker_main.py +++ b/ForgeTracker/forgetracker/tracker_main.py @@ -652,12 +652,6 @@ 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, user=c.user): - 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): @@ -905,7 +899,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='..') + self.rate_limit(TM.Ticket, 'Ticket creation', 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', @@ -942,7 +936,7 @@ class RootController(BaseController, FeedController): require_access(ticket, 'update') else: require_access(c.app, 'create') - self.rate_limit(redir='.') + self.rate_limit(TM.Ticket, 'Ticket creation', redir='.') ticket = TM.Ticket.new() g.spam_checker.check(ticket_form['summary'] + u'\n' + ticket_form.get('description', ''), artifact=ticket, user=c.user, content_type='ticket') http://git-wip-us.apache.org/repos/asf/allura/blob/cf6bdb39/ForgeWiki/forgewiki/wiki_main.py ---------------------------------------------------------------------- diff --git a/ForgeWiki/forgewiki/wiki_main.py b/ForgeWiki/forgewiki/wiki_main.py index 7c55221..377c50c 100644 --- a/ForgeWiki/forgewiki/wiki_main.py +++ b/ForgeWiki/forgewiki/wiki_main.py @@ -530,17 +530,10 @@ class PageController(BaseController, FeedController): if self.page.deleted: require_access(self.page, 'delete') elif has_access(c.app, 'create'): - self.rate_limit() + self.rate_limit(WM.Page, 'Page create/edit') else: raise exc.HTTPNotFound - def rate_limit(self): - if WM.Page.is_limit_exceeded(c.app.config, user=c.user): - msg = 'Page create/edit rate limit exceeded. ' - log.warn(msg + c.app.config.url()) - flash(msg + 'Please try again later.', 'error') - redirect('..') - def fake_page(self): return dict( title=self.title, @@ -613,7 +606,7 @@ class PageController(BaseController, FeedController): page = self.page else: page = self.fake_page() - self.rate_limit() # check before trying to save + self.rate_limit(WM.Page, 'Page create/edit') # check before trying to save c.confirmation = W.confirmation c.markdown_editor = W.markdown_editor c.attachment_add = W.attachment_add @@ -719,7 +712,7 @@ class PageController(BaseController, FeedController): flash('You must provide a title for the page.', 'error') redirect('edit') title = title.replace('/', '-') - self.rate_limit() + self.rate_limit(WM.Page, 'Page create/edit') if not self.page: # the page doesn't exist yet, so create it self.page = WM.Page.upsert(self.title)