From 63d78edc0df64f7cf9f231b4064da6d3a0230f83 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emilio=20A=2E=20S=C3=A1nchez=20L=C3=B3pez?= Date: Wed, 9 Feb 2011 20:12:12 +0000 Subject: [PATCH] DoS Tresholds. Retrieve WG in a previous stage. Fixes #592 - Legacy-Id: 2837 --- ietf/settings.py | 9 +++ ietf/submit/forms.py | 84 ++++++++++++++++++++++--- ietf/submit/models.py | 7 ++- ietf/submit/utils.py | 32 +++++----- ietf/submit/views.py | 4 +- ietf/templates/submit/draft_status.html | 2 +- 6 files changed, 109 insertions(+), 29 deletions(-) diff --git a/ietf/settings.py b/ietf/settings.py index b4a0e1762..bab6b4b0d 100644 --- a/ietf/settings.py +++ b/ietf/settings.py @@ -197,6 +197,15 @@ STAGING_PATH = '/a/www/www6s/staging/' IDNITS_PATH = '/a/www/ietf-datatracker/release/idnits' MAX_PLAIN_DRAFT_SIZE = 6291456 # Max size of the txt draft in bytes +# DOS THRESHOLDS PER DAY (Sizes are in MB) +MAX_SAME_DRAFT_NAME = 20 +MAX_SAME_DRAFT_NAME_SIZE = 50 +MAX_SAME_SUBMITTER = 50 +MAX_SAME_SUBMITTER_SIZE = 150 +MAX_SAME_WG_DRAFT = 150 +MAX_SAME_WG_DRAFT_SIZE = 450 +MAX_DAILY_SUBMISSION = 1000 +MAX_DAILY_SUBMISSION_SIZE = 2000 # End of ID Submission Tool settings # Put SECRET_KEY in here, or any other sensitive or site-specific diff --git a/ietf/submit/forms.py b/ietf/submit/forms.py index 976ec1480..097cab676 100644 --- a/ietf/submit/forms.py +++ b/ietf/submit/forms.py @@ -5,8 +5,9 @@ import datetime from django import forms from django.conf import settings from django.template.loader import render_to_string +from django.utils.html import mark_safe -from ietf.idtracker.models import InternetDraft +from ietf.idtracker.models import InternetDraft, IETFWG from ietf.proceedings.models import Meeting from ietf.submit.models import IdSubmissionDetail, TempIdAuthors from ietf.submit.parsers.pdf_parser import PDFParser @@ -32,16 +33,19 @@ class UploadForm(forms.Form): css = {'all': ("/css/liaisons.css", )} def __init__(self, *args, **kwargs): + self.request=kwargs.pop('request', None) + self.remote_ip=self.request.META.get('REMOTE_ADDR', None) super(UploadForm, self).__init__(*args, **kwargs) self.in_first_cut_off = False self.idnits_message = None self.shutdown = False self.draft = None self.filesize = None + self.group = None self.read_dates() def read_dates(self): - now = datetime.datetime.now() + now = datetime.datetime.utcnow() first_cut_off = Meeting.get_first_cut_off() second_cut_off = Meeting.get_second_cut_off() ietf_monday = Meeting.get_ietf_monday() @@ -120,6 +124,51 @@ class UploadForm(forms.Form): def clean(self): if self.shutdown: raise forms.ValidationError('The tool is shut down') + self.check_paths() + if self.cleaned_data.get('txt', None): + self.get_draft() + self.group=self.get_working_group() + self.check_previous_submission() + self.check_tresholds() + return super(UploadForm, self).clean() + + def check_tresholds(self): + filename = self.draft.filename + revision = self.draft.revision + remote_ip = self.remote_ip + today = datetime.date.today() + + # Same draft by name + same_name = IdSubmissionDetail.objects.filter(filename=filename, revision=revision, submission_date=today) + if same_name.count() > settings.MAX_SAME_DRAFT_NAME: + raise forms.ValidationError('A same I-D cannot be submitted more than %s times a day' % settings.MAX_SAME_DRAFT_NAME) + if sum([i.filesize for i in same_name]) > (settings.MAX_SAME_DRAFT_NAME_SIZE * 1048576): + raise forms.ValidationError('A same I-D submission cannot exceed more than %s MByte a day' % settings.MAX_SAME_DRAFT_NAME_SIZE) + + # Total from same ip + same_ip = IdSubmissionDetail.objects.filter(remote_ip=remote_ip, submission_date=today) + if same_ip.count() > settings.MAX_SAME_SUBMITTER: + raise forms.ValidationError('The same submitter cannot submit more than %s I-Ds a day' % settings.MAX_SAME_SUBMITTER) + if sum([i.filesize for i in same_ip]) > (settings.MAX_SAME_SUBMITTER_SIZE * 1048576): + raise forms.ValidationError('The same submitter cannot exceed more than %s MByte a day' % settings.MAX_SAME_SUBMITTER_SIZE) + + # Total in same group + if self.group: + same_group = IdSubmissionDetail.objects.filter(group_acronym=self.group, submission_date=today) + if same_group.count() > settings.MAX_SAME_WG_DRAFT: + raise forms.ValidationError('A same working group I-Ds cannot be submitted more than %s times a day' % settings.MAX_SAME_WG_DRAFT) + if sum([i.filesize for i in same_group]) > (settings.MAX_SAME_WG_DRAFT_SIZE * 1048576): + raise forms.ValidationError('Total size of same working group I-Ds cannot exceed %s MByte a day' % settings.MAX_SAME_WG_DRAFT_SIZE) + + + # Total drafts for today + total_today = IdSubmissionDetail.objects.filter(submission_date=today) + if total_today.count() > settings.MAX_DAILY_SUBMISSION: + raise forms.ValidationError('The total number of today\'s submission has reached the maximum number of submission per day') + if sum([i.filesize for i in total_today]) > (settings.MAX_DAILY_SUBMISSION_SIZE * 1048576): + raise forms.ValidationError('The total size of today\'s submission has reached the maximum size of submission per day') + + def check_paths(self): self.staging_path = getattr(settings, 'STAGING_PATH', None) self.idnits = getattr(settings, 'IDNITS_PATH', None) if not self.staging_path: @@ -130,10 +179,6 @@ class UploadForm(forms.Form): raise forms.ValidationError('IDNITS_PATH not defined on settings.py') if not os.path.exists(self.idnits): raise forms.ValidationError('IDNITS_PATH defined on settings.py does not exist') - if self.cleaned_data.get('txt', None): - self.get_draft() - self.check_previous_submission() - return super(UploadForm, self).clean() def check_previous_submission(self): filename = self.draft.filename @@ -141,7 +186,7 @@ class UploadForm(forms.Form): existing = IdSubmissionDetail.objects.filter(filename=filename, revision=revision, status__pk__gte=0, status__pk__lt=100) if existing: - raise forms.ValidationError('Duplicate Internet-Draft submission is currently in process.') + raise forms.ValidationError('Duplicate Internet-Draft submission is currently in process') def get_draft(self): if self.draft: @@ -170,6 +215,29 @@ class UploadForm(forms.Form): p = subprocess.Popen([self.idnits, '--submitcheck', '--nitcount', filepath], stdout=subprocess.PIPE) self.idnits_message = p.stdout.read() + def get_working_group(self): + filename = self.draft.filename + existing_draft = InternetDraft.objects.filter(filename=filename) + if existing_draft: + group = existing_draft[0].group and existing_draft[0].group.ietfwg or None + if group and group.pk != 1027: + return group + else: + return None + else: + if filename.startswith('draft-ietf-'): + # Extra check for WG that contains dashes + for group in IETFWG.objects.filter(group_acronym__acronym__contains='-'): + if filename.startswith('draft-ietf-%s-' % group.group_acronym.acronym): + return group + group_acronym = filename.split('-')[2] + try: + return IETFWG.objects.get(group_acronym__acronym=group_acronym) + except IETFWG.DoesNotExist: + raise forms.ValidationError('There is no active group with acronym \'%s\', please rename your draft' % group_acronym) + else: + return None + def save_draft_info(self, draft): document_id = 0 existing_draft = InternetDraft.objects.filter(filename=draft.filename) @@ -185,6 +253,8 @@ class UploadForm(forms.Form): submission_date=datetime.date.today(), idnits_message=self.idnits_message, temp_id_document_tag=document_id, + group_acronym=self.group, + remote_ip=self.remote_ip, first_two_pages=''.join(draft.pages[:2]), status_id=1, # Status 1 - upload ) diff --git a/ietf/submit/models.py b/ietf/submit/models.py index a370d667e..df3d1372a 100644 --- a/ietf/submit/models.py +++ b/ietf/submit/models.py @@ -1,5 +1,8 @@ from django.db import models +from ietf.idtracker.models import IETFWG + + class IdSubmissionStatus(models.Model): status_id = models.IntegerField(primary_key=True) status_value = models.CharField(blank=True, max_length=255) @@ -7,6 +10,7 @@ class IdSubmissionStatus(models.Model): class Meta: db_table = 'id_submission_status' + class IdSubmissionDetail(models.Model): submission_id = models.AutoField(primary_key=True) temp_id_document_tag = models.IntegerField(null=True, blank=True) @@ -14,7 +18,7 @@ class IdSubmissionDetail(models.Model): last_updated_date = models.DateField(null=True, blank=True) last_updated_time = models.CharField(blank=True, max_length=25) id_document_name = models.CharField(blank=True, max_length=255) - group_acronym_id = models.IntegerField(null=True, blank=True) + group_acronym = models.ForeignKey(IETFWG, null=True, blank=True) filename = models.CharField(blank=True, max_length=255) creation_date = models.DateField(null=True, blank=True) submission_date = models.DateField(null=True, blank=True) @@ -41,6 +45,7 @@ class IdSubmissionDetail(models.Model): class Meta: db_table = 'id_submission_detail' + class TempIdAuthors(models.Model): id = models.AutoField(primary_key=True) id_document_tag = models.IntegerField() diff --git a/ietf/submit/utils.py b/ietf/submit/utils.py index 7c802ada0..acadd30f2 100644 --- a/ietf/submit/utils.py +++ b/ietf/submit/utils.py @@ -1,4 +1,5 @@ import re +import datetime from ietf.idtracker.models import InternetDraft, EmailAddress @@ -17,24 +18,9 @@ class DraftValidation(object): return passes_idnits def get_working_group(self): - filename = self.draft.filename - existing_draft = InternetDraft.objects.filter(filename=filename) - if existing_draft: - return existing_draft[0].group and existing_draft[0].group.ietfwg or None - else: - if filename.startswith('draft-ietf-'): - # Extra check for WG that contains dashes - for group in IETFWG.objects.filter(group_acronym__acronym__contains='-'): - if filename.startswith('draft-ietf-%s-' % group.group_acronym.acronym): - return group - group_acronym = filename.split('-')[2] - try: - return IETFWG.objects.get(group_acronym__acronym=group_acronym) - except IETFWG.DoesNotExist: - self.add_warning('group', 'Invalid WG ID: %s' % group_acronym) - return None - else: - return None + if self.draft.group_acronym and self.draft.group_acronym.pk == 1027: + return None + return self.draft.group_acronym def check_idnits_success(self, idnits_message): success_re = re.compile('\s+Summary:\s+0\s+|No nits found') @@ -54,6 +40,7 @@ class DraftValidation(object): def validate_metadata(self): self.validate_revision() self.validate_authors() + self.validate_creation_date() def add_warning(self, key, value): self.warnings.update({key: value}) @@ -71,6 +58,15 @@ class DraftValidation(object): if not self.authors: self.add_warning('authors', 'No authors found') + def validate_creation_date(self): + date = self.draft.creation_date + if not date: + self.add_warning('creation_date', 'Creation Date field is empty or the creation date is not in a proper format.') + return + submit_date = self.draft.submission_date + if date + datetime.timedelta(days=3) > submit_date: + self.add_warning('creation_date', 'Creation Date must be within 3 days of submission date.') + def get_authors(self): tmpauthors = self.draft.tempidauthors_set.all().order_by('author_order') authors = [] diff --git a/ietf/submit/views.py b/ietf/submit/views.py index 2d0954c27..6166659dd 100644 --- a/ietf/submit/views.py +++ b/ietf/submit/views.py @@ -12,12 +12,12 @@ from ietf.submit.utils import DraftValidation def submit_index(request): if request.method == 'POST': - form = UploadForm(data=request.POST, files=request.FILES) + form = UploadForm(request=request, data=request.POST, files=request.FILES) if form.is_valid(): submit = form.save() return HttpResponseRedirect(reverse(draft_status, None, kwargs={'submission_id': submit.submission_id})) else: - form = UploadForm() + form = UploadForm(request=request) return render_to_response('submit/submit_index.html', {'selected': 'index', 'form': form}, diff --git a/ietf/templates/submit/draft_status.html b/ietf/templates/submit/draft_status.html index e886fe3cc..7196a28fd 100644 --- a/ietf/templates/submit/draft_status.html +++ b/ietf/templates/submit/draft_status.html @@ -114,7 +114,7 @@ returned to the submitter. Revision{{ detail.revision }}
{{ validation.warnings.revision }}
Submission date{{ detail.submission_date }} Title{{ detail.id_document_name }} -WG{{ validation.wg|default:"" }}
{{ validation.warnings.group }} +WG{{ validation.wg|default:"Individual Submission" }}
{{ validation.warnings.group }} File size{{ detail.filesize|filesizeformat }} Creation date{{ detail.creation_date }}
{{ validation.warnings.creation_date }} Author(s) information