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'))