You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@allura.apache.org by br...@apache.org on 2012/09/21 22:18:34 UTC
[10/16] git commit: [#4445] Added rate limiting to project creation
[#4445] Added rate limiting to project creation
Signed-off-by: Cory Johns <jo...@geek.net>
Project: http://git-wip-us.apache.org/repos/asf/incubator-allura/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-allura/commit/896380a0
Tree: http://git-wip-us.apache.org/repos/asf/incubator-allura/tree/896380a0
Diff: http://git-wip-us.apache.org/repos/asf/incubator-allura/diff/896380a0
Branch: refs/heads/master
Commit: 896380a0a671c542bd56508998cb927446ae38aa
Parents: af0a1ea
Author: Cory Johns <jo...@geek.net>
Authored: Thu Sep 20 20:00:54 2012 +0000
Committer: Dave Brondsema <db...@geek.net>
Committed: Fri Sep 21 14:55:55 2012 +0000
----------------------------------------------------------------------
Allura/allura/controllers/project.py | 3 +
Allura/allura/lib/exceptions.py | 1 +
Allura/allura/lib/plugin.py | 21 ++++++++-
.../allura/tests/functional/test_neighborhood.py | 37 +++++++++++++++
4 files changed, 61 insertions(+), 1 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/896380a0/Allura/allura/controllers/project.py
----------------------------------------------------------------------
diff --git a/Allura/allura/controllers/project.py b/Allura/allura/controllers/project.py
index 7e262da..2ac2db0 100644
--- a/Allura/allura/controllers/project.py
+++ b/Allura/allura/controllers/project.py
@@ -182,6 +182,9 @@ class NeighborhoodController(object):
except exceptions.ProjectOverlimitError:
flash("You have exceeded the maximum number of projects you are allowed to create", 'error')
redirect('.')
+ except exceptions.ProjectRatelimitError:
+ flash("Project creation rate limit exceeded. Please try again later.", 'error')
+ redirect('.')
if project_description:
c.project.short_description = project_description
http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/896380a0/Allura/allura/lib/exceptions.py
----------------------------------------------------------------------
diff --git a/Allura/allura/lib/exceptions.py b/Allura/allura/lib/exceptions.py
index 30250f7..98273dd 100644
--- a/Allura/allura/lib/exceptions.py
+++ b/Allura/allura/lib/exceptions.py
@@ -1,6 +1,7 @@
class ForgeError(Exception): pass
class ProjectConflict(ForgeError): pass
class ProjectOverlimitError(ForgeError): pass
+class ProjectRatelimitError(ForgeError): pass
class ToolError(ForgeError): pass
class NoSuchProjectError(ForgeError): pass
class NoSuchNeighborhoodError(ForgeError): pass
http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/896380a0/Allura/allura/lib/plugin.py
----------------------------------------------------------------------
diff --git a/Allura/allura/lib/plugin.py b/Allura/allura/lib/plugin.py
index 4ccc06f..9c46116 100644
--- a/Allura/allura/lib/plugin.py
+++ b/Allura/allura/lib/plugin.py
@@ -11,7 +11,8 @@ from cStringIO import StringIO
from random import randint
from hashlib import sha256
from base64 import b64encode
-from datetime import datetime
+from datetime import datetime, timedelta
+import json
try:
import ldap
@@ -22,6 +23,7 @@ import pkg_resources
from tg import config, flash
from pylons import g, c
from webob import exc
+from bson.tz_util import FixedOffset
from ming.utils import LazyProperty
from ming.orm import state
@@ -334,6 +336,21 @@ class ProjectRegistrationProvider(object):
'''
return []
+ def rate_limit(self, user, neighborhood):
+ '''Check the various config-defined project registration rate
+ limits, and if any are exceeded, raise ProjectRatelimitError.
+ '''
+ if security.has_access(neighborhood, 'admin', user=user)():
+ return
+ now = datetime.now().replace(tzinfo=FixedOffset(0, 'UTC'))
+ project_count = len(list(user.my_projects()))
+ rate_limits = json.loads(config.get('project.rate_limits', '{}'))
+ for rate, count in rate_limits.items():
+ user_age = now - user._id.generation_time
+ user_age = (user_age.microseconds + (user_age.seconds + user_age.days * 24 * 3600) * 10**6) / 10**6
+ if user_age < int(rate) and project_count >= count:
+ raise forge_exc.ProjectRatelimitError()
+
def register_neighborhood_project(self, neighborhood, users, allow_register=False):
from allura import model as M
shortname='--init--'
@@ -391,6 +408,8 @@ class ProjectRegistrationProvider(object):
log.exception('Error registering project %s' % project_name)
raise forge_exc.ProjectOverlimitError()
+ self.rate_limit(user, neighborhood)
+
if not h.re_path_portion.match(shortname.replace('/', '')):
raise ValueError('Invalid project shortname: %s' % shortname)
try:
http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/896380a0/Allura/allura/tests/functional/test_neighborhood.py
----------------------------------------------------------------------
diff --git a/Allura/allura/tests/functional/test_neighborhood.py b/Allura/allura/tests/functional/test_neighborhood.py
index a5a8310..1a66945 100644
--- a/Allura/allura/tests/functional/test_neighborhood.py
+++ b/Allura/allura/tests/functional/test_neighborhood.py
@@ -14,6 +14,7 @@ import allura
from allura import model as M
from allura.tests import TestController
from allura.tests import decorators as td
+from allura.lib import helpers as h
class TestNeighborhood(TestController):
@@ -291,6 +292,42 @@ class TestNeighborhood(TestController):
r = r.follow()
assert 'You have exceeded the maximum number of projects' in r
+ def test_project_rate_limit(self):
+ # Set rate limit to unlimit
+ with h.push_config(config, **{'project.rate_limits': '{}'}):
+ r = self.app.post('/p/register',
+ params=dict(project_unixname='rateproject1', project_name='Rate project1', project_description='', neighborhood='Projects'),
+ antispam=True,
+ extra_environ=dict(username='test-user-1'), status=302)
+ assert '/p/rateproject1/admin' in r.location
+
+ # Set rate limit to 1 per hour
+ with h.push_config(config, **{'project.rate_limits': '{"3600": 1}'}):
+ r = self.app.post('/p/register',
+ params=dict(project_unixname='rateproject2', project_name='Rate project2', project_description='', neighborhood='Projects'),
+ antispam=True,
+ extra_environ=dict(username='test-user-1'))
+ while isinstance(r.response, HTTPFound):
+ r = r.follow()
+ assert 'Project creation rate limit exceeded. Please try again later.' in r
+
+ def test_project_rate_limit_admin(self):
+ # Set rate limit to unlimit
+ with h.push_config(config, **{'project.rate_limits': '{}'}):
+ r = self.app.post('/p/register',
+ params=dict(project_unixname='rateproject1', project_name='Rate project1', project_description='', neighborhood='Projects'),
+ antispam=True,
+ extra_environ=dict(username='root'), status=302)
+ assert '/p/rateproject1/admin' in r.location
+
+ # Set rate limit to 1 per hour
+ with h.push_config(config, **{'project.rate_limits': '{"3600": 1}'}):
+ r = self.app.post('/p/register',
+ params=dict(project_unixname='rateproject2', project_name='Rate project2', project_description='', neighborhood='Projects'),
+ antispam=True,
+ extra_environ=dict(username='root'))
+ assert '/p/rateproject2/admin' in r.location
+
def test_invite(self):
p_nbhd_id = str(M.Neighborhood.query.get(name='Projects')._id)
r = self.app.get('/adobe/_moderate/', extra_environ=dict(username='root'))