enforce size limitation control (#15350)

This commit is contained in:
Josue Kouka 2017-03-14 16:36:47 +01:00
parent 6378c7f549
commit 1d35d73c7f
7 changed files with 111 additions and 17 deletions

View File

@ -24,7 +24,7 @@ from rest_framework.views import APIView
from rest_framework.response import Response
from .models import CUTIdentifier, Petal, Partner
from .utils import get_petal_object_or_404, logit
from .utils import get_petal_object_or_404, logit, is_within_size_limits, notify_admins
from .exceptions import PetalException
from .renderers import PetalRenderer
@ -99,15 +99,28 @@ class PetalAPIView(APIView):
return Response({"error": "content-type-not-found", "description": "Header Content-Type not found"},
status=status.HTTP_400_BAD_REQUEST)
# checking size limitation
content_length = len(data)
partner_current_size = Petal.objects.filter(partner_id=self.id_partner).aggregate(Sum('size')).values()[0]
if partner_current_size is None:
partner_current_size = 0
if partner_current_size + content_length > self.id_partner.max_size:
# global size limits control
partner_current_size = Petal.objects.filter(partner_id=self.id_partner).aggregate(Sum('size')).values()[0] or 0
hard, soft = is_within_size_limits(partner_current_size + content_length, self.id_partner.hard_global_max_size,
self.id_partner.soft_global_max_size)
if not hard:
return Response({"error": "global-space-exhausted",
"description": "no more space left for %s" % self.id_partner.name},
status=status.HTTP_500_INTERNAL_SERVER_ERROR)
if hard and not soft:
notify_admins(self.id_partner.name, self.id_partner.admin_emails, partner_current_size + content_length,
self.id_partner.hard_global_max_size)
# key size limits control
hard, soft = is_within_size_limits(content_length, self.id_partner.hard_per_key_max_size,
self.id_partner.soft_per_key_max_size)
if not hard:
return Response({"error": "data-size-error",
"description": "%s data size(%d) over limit(%d)" % (self.id_key, content_length, self.id_partner.hard_per_key_max_size)},
status=status.HTTP_500_INTERNAL_SERVER_ERROR)
if hard and not soft:
notify_admins(self.id_partner.name, self.id_partner.admin_emails, content_length,
self.id_partner.hard_per_key_max_size, self.id_key)
def is_precondition_ok(etags):
if etags == '*':

View File

@ -24,13 +24,19 @@ from django.core.exceptions import ValidationError
class Partner(models.Model):
name = models.CharField(_('Partner'), max_length=64,
name = models.CharField(verbose_name=_('Partner'), max_length=64,
validators=[RegexValidator('^[A-Za-z0-9-_]+$', message="Invalid name format")])
max_size = models.IntegerField(_('Max Size'))
min_size = models.IntegerField(_('Min Size'))
admin_emails = models.CharField(verbose_name=_('Admin emails'), max_length=256,
help_text=_('List of admin emails separated by comma'))
hard_global_max_size = models.IntegerField(verbose_name=_('Hard max size'))
soft_global_max_size = models.IntegerField(_('Soft max size'))
hard_per_key_max_size = models.IntegerField(_('Hard max size per key'))
soft_per_key_max_size = models.IntegerField(_('Soft max size per key'))
def __unicode__(self):
return '%s %d %d' % (self.name, self.max_size, self.min_size)
return '%s - %s - (hg:%d, sg:%d, hk:%d, sk:%d)' % (self.name, self.admin_emails, self.hard_global_max_size,
self.soft_global_max_size, self.hard_per_key_max_size,
self.soft_per_key_max_size)
def save(self, *args, **kwargs):
self.full_clean()

View File

@ -18,6 +18,9 @@ import logging
from functools import wraps
from petale.exceptions import PetalException
from django.conf import settings
from django.core.mail import send_mail
def get_petal_object_or_404(model, **kwargs):
try:
@ -28,6 +31,26 @@ def get_petal_object_or_404(model, **kwargs):
'description': '%s %s does not exist' % (model.__name__, kwargs.values()[0])})
def is_within_size_limits(size, gmax, smax=None):
if size > gmax:
return (False, False)
if smax and gmax > size > smax:
return (True, False)
return (True, True)
def notify_admins(partner, recipients, size, hmax, key=None):
if key:
subject = 'Key %s space almost exhausted' % key
else:
subject = 'Partner %s space almost exhausted' % partner
body = 'Current size: %s, Max size: %s' % (size, hmax)
sender = settings.DEFAULT_FROM_EMAIL
recipients = recipients.split(',')
send_mail(subject, body, sender, recipients)
def logit(func):
@wraps(func)
def wrapper(self, request, *args, **kwargs):

View File

@ -35,13 +35,13 @@ def service_cityhall(db):
@pytest.fixture
def partner_southpark(service_family, service_library, service_cityhall):
return create_partner('southpark', min_size=512, max_size=20240)
return create_partner('southpark', hg=20240, hk=19728)
@pytest.fixture
def partner_gotham():
create_service('arkham')
return create_partner('gotham', min_size=256, max_size=2048)
return create_partner('gotham', admins='b.wayne@gotham.gov', hg=2048, sg=1600, hk=400, sk=370)
@pytest.fixture

22
tests/data/taxe.json Normal file
View File

@ -0,0 +1,22 @@
{
"due_date": "11/12/2016",
"first_name": "loking",
"last_name": "josh",
"address": "274 West 12th Avenue, Vancouver, BC V5Y 1V4",
"rates": [
{
"name": "WA CITY TAX",
"rate": 0.021,
"type": "city"
},
{
"name": "WA STATE TAX",
"rate": 0.065,
"type": "state"
}
],
"taxe_no": "1234",
"totalRate": 0.086,
"total_due": 1290.0,
"total_income": 15000
}

View File

@ -219,14 +219,39 @@ def test_caching(app, partner_southpark, cut_kevin, acl):
resp = app.put_json(url, params=payload, headers={'If-Match': etag}, status=200)
def test_partner_size_limit(app, cut_kevin, acl, petal_invoice, petal_books):
def test_partner_size_limit(app, cut_kevin, acl, petal_invoice, petal_books, mailoutbox):
app.authorization = ('Basic', ('arkham', 'arkham'))
payload = json.loads(get_tests_file_content('books.json'))
payload = json.loads(get_tests_file_content('taxe.json'))
for i in range(3):
# test sending data sized above key limits
payload['phonenumber'] = '+18855776644'
payload['birthdate'] = '1980/06/21'
payload['birthplace'] = 'Chelsea, Quebec'
url = '/api/gotham/%s/taxes-fail/' % cut_kevin.uuid
resp = app.put_json(url, params=payload, headers={'If-None-Match': '*'}, status=500)
assert resp.json['error'] == 'data-size-error'
assert resp.json['description'] == 'taxes-fail data size(428) over limit(400)'
# test sending data sized within soft and hard key limit
payload.pop('birthplace')
resp = app.put_json(url, params=payload, headers={'If-None-Match': '*'}, status=201)
assert len(mailoutbox) == 1
sent_mail = mailoutbox[0]
assert sent_mail.to[0] == 'b.wayne@gotham.gov'
assert sent_mail.subject == 'Key taxes-fail space almost exhausted'
assert 'Current size: 395, Max size: 400' in sent_mail.message().as_string()
payload.pop('birthdate')
for i in range(4):
url = '/api/gotham/%s/taxes-%d/' % (cut_kevin.uuid, i)
app.put_json(url, params=payload, headers={'If-None-Match': '*'}, status=201)
assert len(mailoutbox) == 2
sent_mail = mailoutbox[1]
assert sent_mail.to[0] == 'b.wayne@gotham.gov'
assert sent_mail.subject == 'Partner gotham space almost exhausted'
assert 'Current size: 1867, Max size: 2048' in sent_mail.message().as_string()
url = '/api/gotham/%s/taxes-4/' % cut_kevin.uuid
resp = app.put_json(url, params=payload, headers={'If-None-Match': '*'}, status=500)
assert resp.json['error'] == 'global-space-exhausted'

View File

@ -35,8 +35,13 @@ def get_service(name):
return User.objects.get(username=name)
def create_partner(name, min_size=0, max_size=128):
return Partner.objects.create(name=name, max_size=max_size, min_size=min_size)
def create_partner(name, admins=None, hg=2048, sg=1536, hk=1024, sk=768):
if not admins:
admins = 'e.cartman@southpark.com,t.blakc@southpark.com'
return Partner.objects.create(
name=name, admin_emails=admins,
hard_global_max_size=hg, soft_global_max_size=sg,
hard_per_key_max_size=hk, soft_per_key_max_size=sk)
def create_acl_record(order, partner, user, key, methods='*'):