Skip to content

Commit

Permalink
Merge pull request #93 from qld-gov-au/QOLDEV-638-throttle-request-cr…
Browse files Browse the repository at this point in the history
…eation

QOLDEV-638 throttle data request creation
  • Loading branch information
ThrawnCA committed Oct 17, 2023
2 parents 26d973f + feef862 commit 23df564
Show file tree
Hide file tree
Showing 3 changed files with 58 additions and 4 deletions.
48 changes: 45 additions & 3 deletions ckanext/datarequests/actions.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,9 @@
except ImportError:
from cgi import escape

from ckan import model
from ckan import authz, model
from ckan.lib import mailer
from ckan.lib.redis import connect_to_redis
from ckan.plugins import toolkit as tk
from ckan.plugins.toolkit import h, config

Expand All @@ -38,6 +39,10 @@
# Avoid user_show lag
USERS_CACHE = {}

# Allow one request per account per five minutes
CREATION_THROTTLE_EXPIRY = 300
THROTTLE_ERROR = "Too many requests submitted, please wait {} minutes and try again"


def _get_user(user_id):
try:
Expand Down Expand Up @@ -180,6 +185,39 @@ def _send_mail(user_ids, action_type, datarequest):
log.exception("Error sending notification to {0}".format(user_id))


def throttle_datarequest(creator):
""" Check that the account is not creating requests too quickly.
This should happen after validation, so a request that fails
validation can be immediately corrected and resubmitted.
"""
if creator.sysadmin or authz.has_user_permission_for_some_org(creator.name, 'create_dataset'):
# privileged users can skip the throttle
return

# check cache to see if there's a record of a recent creation
cache_key = '{}.ckanext.datarequest.creation_attempts.{}'.format(
tk.config.get('ckan.site_id'), creator.id)
redis_conn = connect_to_redis()
try:
creation_attempts = int(redis_conn.get(cache_key) or 0)
except ValueError:
# shouldn't happen but let's play it safe
creation_attempts = 0

if creation_attempts:
# Increase the delay every time someone tries too soon
expiry = creation_attempts * CREATION_THROTTLE_EXPIRY
else:
expiry = CREATION_THROTTLE_EXPIRY
log.debug("Account %s has submitted %s request(s) recently, next allowed in %s seconds",
creator.id, creation_attempts, expiry)
# put a cap on the maximum delay
recorded_attempts = creation_attempts if creation_attempts >= 100 else creation_attempts + 1
redis_conn.set(cache_key, recorded_attempts, ex=expiry)
if creation_attempts:
raise tk.ValidationError({"": [THROTTLE_ERROR.format(int(expiry / 60))]})


def create_datarequest(context, data_dict):
'''
Action to create a new data request. The function checks the access rights
Expand Down Expand Up @@ -214,10 +252,14 @@ def create_datarequest(context, data_dict):
# Validate data
validator.validate_datarequest(context, data_dict)

# Ensure account isn't creating requests too fast
creator = context['auth_user_obj']
throttle_datarequest(creator)

# Store the data
data_req = db.DataRequest()
_undictize_datarequest_basic(data_req, data_dict)
data_req.user_id = context['auth_user_obj'].id
data_req.user_id = creator.id
data_req.open_time = datetime.datetime.utcnow()

session.add(data_req)
Expand All @@ -227,7 +269,7 @@ def create_datarequest(context, data_dict):

if datarequest_dict['organization']:
users = {user['id'] for user in datarequest_dict['organization']['users']}
users.discard(context['auth_user_obj'].id)
users.discard(creator.id)
_send_mail(users, 'new_datarequest', datarequest_dict)

return datarequest_dict
Expand Down
2 changes: 1 addition & 1 deletion test/features/comments.feature
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ Feature: Comments

@comment-add
Scenario: When a logged-in user submits a comment on a Data Request the comment should then be visible on the Comments tab of the Data Request
Given "CKANUser" as the persona
Given "TestOrgMember" as the persona
When I log in
And I create a datarequest
And I go to data request "$last_generated_title" comments
Expand Down
12 changes: 12 additions & 0 deletions test/features/datarequest.feature
Original file line number Diff line number Diff line change
Expand Up @@ -135,3 +135,15 @@ Feature: Datarequest
When I go to the "test_org_editor" profile page
And I press the element with xpath "//ul[contains(@class, 'nav-tabs')]//a[contains(string(), 'Data Requests')]"
Then I should see "No data requests found"

Scenario: An unprivileged user who tries to create multiple data requests close together should see an error
Given "CKANUser" as the persona
When I log in
And I create a datarequest
And I go to the data requests page
And I press "Add Data Request"
And I fill in title with random text
And I fill in "description" with "Test throttling"
And I press the element with xpath "//button[contains(@class, 'btn-primary')]"
Then I should see "Too many requests submitted, please wait"

0 comments on commit 23df564

Please sign in to comment.