Skip to content

Commit 649de73

Browse files
fix: validate review_requests_history params (#9281)
* test: test null chars in GET params * fix: validate GET params
1 parent 8c4bff8 commit 649de73

File tree

2 files changed

+66
-18
lines changed

2 files changed

+66
-18
lines changed

ietf/group/tests_review.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -943,3 +943,42 @@ def test_requests_history_filter_page(self):
943943
self.assertNotContains(r, 'Assigned')
944944
self.assertNotContains(r, 'Accepted')
945945
self.assertNotContains(r, 'Completed')
946+
947+
def test_requests_history_invalid_filter_parameters(self):
948+
# First assignment as assigned
949+
review_req = ReviewRequestFactory(state_id="assigned", doc=DocumentFactory())
950+
group = review_req.team
951+
url = urlreverse(
952+
"ietf.group.views.review_requests_history",
953+
kwargs={"acronym": group.acronym},
954+
)
955+
invalid_reviewer_emails = [
956+
"%[email protected]", # urlencoded null character
957+
"null@exa%00mple.com", # urlencoded null character
958+
"\x00[email protected]", # literal null character
959+
"null@ex\x00ample.com", # literal null character
960+
]
961+
for invalid_email in invalid_reviewer_emails:
962+
r = self.client.get(
963+
url + f"?reviewer_email={invalid_email}"
964+
)
965+
self.assertEqual(
966+
r.status_code,
967+
400,
968+
f"should return a 400 response for reviewer_email={repr(invalid_email)}"
969+
)
970+
971+
invalid_since_choices = [
972+
"forever", # not an option
973+
"all\x00", # literal null character
974+
"a%00ll", # urlencoded null character
975+
]
976+
for invalid_since in invalid_since_choices:
977+
r = self.client.get(
978+
url + f"?since={invalid_since}"
979+
)
980+
self.assertEqual(
981+
r.status_code,
982+
400,
983+
f"should return a 400 response for since={repr(invalid_since)}"
984+
)

ietf/group/views.py

Lines changed: 27 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,13 @@
5151
from django.contrib.auth.decorators import login_required
5252
from django.db.models import Count, F, OuterRef, Prefetch, Q, Subquery, TextField, Value
5353
from django.db.models.functions import Coalesce
54-
from django.http import HttpResponse, HttpResponseRedirect, Http404, JsonResponse
54+
from django.http import (
55+
HttpResponse,
56+
HttpResponseRedirect,
57+
Http404,
58+
JsonResponse,
59+
HttpResponseBadRequest,
60+
)
5561
from django.shortcuts import render, redirect, get_object_or_404
5662
from django.template.loader import render_to_string
5763
from django.urls import reverse as urlreverse
@@ -96,11 +102,9 @@
96102
from ietf.review.policies import get_reviewer_queue_policy
97103
from ietf.review.utils import (can_manage_review_requests_for_team,
98104
can_access_review_stats_for_team,
99-
100105
extract_revision_ordered_review_requests_for_documents_and_replaced,
101106
assign_review_request_to_reviewer,
102107
close_review_request,
103-
104108
suggested_review_requests_for_team,
105109
unavailable_periods_to_list,
106110
current_unavailable_periods_for_reviewers,
@@ -686,13 +690,30 @@ def history(request, acronym, group_type=None):
686690
"can_add_comment": can_add_comment,
687691
}))
688692

693+
694+
class RequestsHistoryParamsForm(forms.Form):
695+
SINCE_CHOICES = (
696+
(None, "1 month"),
697+
("3m", "3 months"),
698+
("6m", "6 months"),
699+
("1y", "1 year"),
700+
("2y", "2 years"),
701+
("all", "All"),
702+
)
703+
704+
reviewer_email = forms.EmailField(required=False)
705+
since = forms.ChoiceField(choices=SINCE_CHOICES, required=False)
706+
689707
def review_requests_history(request, acronym, group_type=None):
690708
group = get_group_or_404(acronym, group_type)
691709
if not group.features.has_reviews:
692710
raise Http404
693711

694-
reviewer_email = request.GET.get("reviewer_email", None)
712+
params = RequestsHistoryParamsForm(request.GET)
713+
if not params.is_valid():
714+
return HttpResponseBadRequest("Invalid parameters")
695715

716+
reviewer_email = params.cleaned_data["reviewer_email"] or None
696717
if reviewer_email:
697718
history = ReviewAssignment.history.model.objects.filter(
698719
review_request__team__acronym=acronym,
@@ -702,19 +723,7 @@ def review_requests_history(request, acronym, group_type=None):
702723
review_request__team__acronym=acronym)
703724
reviewer_email = ''
704725

705-
since_choices = [
706-
(None, "1 month"),
707-
("3m", "3 months"),
708-
("6m", "6 months"),
709-
("1y", "1 year"),
710-
("2y", "2 years"),
711-
("all", "All"),
712-
]
713-
since = request.GET.get("since", None)
714-
715-
if since not in [key for key, label in since_choices]:
716-
since = None
717-
726+
since = params.cleaned_data["since"] or None
718727
if since != "all":
719728
date_limit = {
720729
None: datetime.timedelta(days=31),
@@ -731,7 +740,7 @@ def review_requests_history(request, acronym, group_type=None):
731740
"group": group,
732741
"acronym": acronym,
733742
"history": history,
734-
"since_choices": since_choices,
743+
"since_choices": params.SINCE_CHOICES,
735744
"since": since,
736745
"reviewer_email": reviewer_email
737746
}))

0 commit comments

Comments
 (0)