Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 0 additions & 11 deletions ietf/doc/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -449,17 +449,6 @@ def test_drafts_in_last_call(self):
self.assertContains(r, draft.title)
self.assertContains(r, escape(draft.action_holders.first().name))

def test_in_iesg_process(self):
doc_in_process = IndividualDraftFactory()
doc_in_process.action_holders.set([PersonFactory()])
doc_in_process.set_state(State.objects.get(type='draft-iesg', slug='lc'))
doc_not_in_process = IndividualDraftFactory()
r = self.client.get(urlreverse('ietf.doc.views_search.drafts_in_iesg_process'))
self.assertEqual(r.status_code, 200)
self.assertContains(r, doc_in_process.title)
self.assertContains(r, escape(doc_in_process.action_holders.first().name))
self.assertNotContains(r, doc_not_in_process.title)

def test_indexes(self):
draft = IndividualDraftFactory()
rfc = WgRfcFactory()
Expand Down
4 changes: 2 additions & 2 deletions ietf/doc/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,13 +53,13 @@
url(r'^ad/?$', views_search.ad_workload),
url(r'^ad/(?P<name>[^/]+)/?$', views_search.docs_for_ad),
url(r'^ad2/(?P<name>[\w.-]+)/$', RedirectView.as_view(url='/doc/ad/%(name)s/', permanent=True)),
url(r'^for_iesg/?$', views_search.docs_for_iesg),
url(r'^for_iesg/?$', RedirectView.as_view(pattern_name='ietf.doc.views_search.docs_for_iesg', permanent=False)),
url(r'^rfc-status-changes/?$', views_status_change.rfc_status_changes),
url(r'^start-rfc-status-change/(?:%(name)s/)?$' % settings.URL_REGEXPS, views_status_change.start_rfc_status_change),
url(r'^bof-requests/?$', views_bofreq.bof_requests),
url(r'^bof-requests/new/$', views_bofreq.new_bof_request),
url(r'^statement/new/$', views_statement.new_statement),
url(r'^iesg/?$', views_search.drafts_in_iesg_process),
url(r'^iesg/?$', views_search.docs_for_iesg),
url(r'^email-aliases/?$', views_doc.email_aliases),
url(r'^downref/?$', views_downref.downref_registry),
url(r'^downref/add/?$', views_downref.downref_registry_add),
Expand Down
27 changes: 1 addition & 26 deletions ietf/doc/views_search.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@
import debug # pyflakes:ignore

from ietf.doc.models import ( Document, DocHistory, State,
LastCallDocEvent, NewRevisionDocEvent, IESG_SUBSTATE_TAGS,
NewRevisionDocEvent, IESG_SUBSTATE_TAGS,
IESG_BALLOT_ACTIVE_STATES, IESG_STATCHG_CONFLREV_ACTIVE_STATES,
IESG_CHARTER_ACTIVE_STATES )
from ietf.doc.fields import select2_id_doc_name_json
Expand Down Expand Up @@ -849,31 +849,6 @@ def drafts_in_last_call(request):
'form':form, 'docs':results, 'meta':meta, 'pages':pages
})

def drafts_in_iesg_process(request):
states = State.objects.filter(type="draft-iesg").exclude(slug__in=('idexists', 'pub', 'dead', 'rfcqueue'))
title = "Documents in IESG process"

grouped_docs = []

for s in states.order_by("order"):
docs = Document.objects.filter(type="draft", states=s).distinct().order_by("time").select_related("ad", "group", "group__parent")
if docs:
if s.slug == "lc":
for d in docs:
e = d.latest_event(LastCallDocEvent, type="sent_last_call")
# If we don't have an event, use an arbitrary date in the past (but not datetime.datetime.min,
# which causes problems with timezone conversions)
d.lc_expires = e.expires if e else datetime.datetime(1950, 1, 1)
docs = list(docs)
docs.sort(key=lambda d: d.lc_expires)

grouped_docs.append((s, docs))

return render(request, 'doc/drafts_in_iesg_process.html', {
"grouped_docs": grouped_docs,
"title": title,
})

def recent_drafts(request, days=7):
slowcache = caches['slowpages']
cache_key = f'recentdraftsview{days}'
Expand Down
31 changes: 30 additions & 1 deletion ietf/submit/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,9 @@
process_submission_xml, process_uploaded_submission,
process_and_validate_submission, apply_yang_checker_to_draft,
run_all_yang_model_checks)
from ietf.submit.views import access_token_is_valid, auth_token_is_valid
from ietf.utils import tool_version
from ietf.utils.accesstoken import generate_access_token
from ietf.utils.accesstoken import generate_access_token, generate_random_key
from ietf.utils.mail import outbox, get_payload_text
from ietf.utils.test_runner import TestBlobstoreManager
from ietf.utils.test_utils import login_testing_unauthorized, TestCase
Expand Down Expand Up @@ -3500,3 +3501,31 @@ def test_submissionerror(self, mock_sanitize_message):
mock_sanitize_message.call_args_list,
[mock.call("hi"), mock.call("there")],
)


class HelperTests(TestCase):
def test_access_token_is_valid(self):
submission: Submission = SubmissionFactory() # type: ignore
valid_token = submission.access_token()
access_key = submission.access_key # accept this for backwards compat
invalid_token = "not the valid token"
self.assertTrue(access_token_is_valid(submission, valid_token))
self.assertTrue(access_token_is_valid(submission, access_key))
self.assertFalse(access_token_is_valid(submission, invalid_token))

def test_auth_token_is_valid(self):
auth_key = generate_random_key()
submission: Submission = SubmissionFactory(auth_key = auth_key) # type: ignore
valid_token = generate_access_token(submission.auth_key)
auth_key = submission.auth_key # accept this for backwards compat
invalid_token = "not the valid token"
self.assertTrue(auth_token_is_valid(submission, valid_token))
self.assertTrue(auth_token_is_valid(submission, auth_key))
self.assertFalse(auth_token_is_valid(submission, invalid_token))

submission.auth_key = ""
submission.save()
self.assertFalse(auth_token_is_valid(submission, valid_token))
self.assertFalse(auth_token_is_valid(submission, auth_key))
self.assertFalse(auth_token_is_valid(submission, invalid_token))
self.assertFalse(auth_token_is_valid(submission, ""))
49 changes: 39 additions & 10 deletions ietf/submit/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
# -*- coding: utf-8 -*-
import re
import datetime
from secrets import compare_digest

from typing import Optional, cast # pyflakes:ignore
from urllib.parse import urljoin
Expand Down Expand Up @@ -255,19 +256,48 @@ def search_submission(request):
)


def can_edit_submission(user, submission, access_token):
key_matched = access_token and submission.access_token() == access_token
if not key_matched: key_matched = submission.access_key == access_token # backwards-compat
return key_matched or has_role(user, "Secretariat")
def access_token_is_valid(submission: Submission, access_token: str):
"""Check whether access_token is valid for submission, in constant time"""
token_matched = compare_digest(submission.access_token(), access_token)
# also compare key directly for backwards compatibility
key_matched = compare_digest(submission.access_key, access_token)
return token_matched or key_matched


def auth_token_is_valid(submission: Submission, auth_token: str):
"""Check whether auth_token is valid for submission, in constant time"""
auth_key = submission.auth_key
if not auth_key:
# Make the same calls as the other branch to keep constant time, then
# return False because there is no auth key
compare_digest(generate_access_token("fake"), auth_token)
compare_digest("fake", auth_token)
return False
else:
token_matched = compare_digest(generate_access_token(auth_key), auth_token)
# also compare key directly for backwards compatibility
key_matched = compare_digest(auth_key, auth_token)
return token_matched or key_matched


def can_edit_submission(user, submission: Submission, access_token: str | None):
if has_role(user, "Secretariat"):
return True
elif not access_token:
return False
return access_token_is_valid(submission, access_token)


def submission_status(request, submission_id, access_token=None):
# type: (HttpRequest, str, Optional[str]) -> HttpResponse
submission = get_object_or_404(Submission, pk=submission_id)

key_matched = access_token and submission.access_token() == access_token
if not key_matched: key_matched = submission.access_key == access_token # backwards-compat
if access_token and not key_matched:
raise Http404
if access_token:
key_matched = access_token_is_valid(submission, access_token)
if not key_matched:
raise Http404
else:
key_matched = False

if submission.state.slug == "cancel":
errors = {}
Expand Down Expand Up @@ -621,8 +651,7 @@ def edit_submission(request, submission_id, access_token=None):
def confirm_submission(request, submission_id, auth_token):
submission = get_object_or_404(Submission, pk=submission_id)

key_matched = submission.auth_key and auth_token == generate_access_token(submission.auth_key)
if not key_matched: key_matched = auth_token == submission.auth_key # backwards-compat
key_matched = submission.auth_key and auth_token_is_valid(submission, auth_token)

if request.method == 'POST' and submission.state_id in ("auth", "aut-appr") and key_matched:
# Set a temporary state 'confirmed' to avoid entering this code
Expand Down
5 changes: 4 additions & 1 deletion ietf/templates/doc/ad_list.html
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,10 @@ <h1>IESG Dashboard</h1>
are only shown to logged-in Area Directors.
</div>
{% endif %}
<p><a href="{% url 'ietf.doc.views_search.docs_for_iesg' %}">Documents in IESG Processing</a></p>
<p>
<a class="btn btn-primary" href="{% url 'ietf.doc.views_search.docs_for_iesg' %}">Documents in IESG Processing</a>
<a class="btn btn-primary" href="{% url 'ietf.iesg.views.working_groups' %}">IESG view of Working Groups</a>
</p>
{% for dt in metadata %}
<h2 class="mt-5" id="{{ dt.type.0 }}">{{ dt.type.1 }} State Counts</h2>
<table class="table table-sm table-striped table-bordered tablesorter navskip">
Expand Down
83 changes: 0 additions & 83 deletions ietf/templates/doc/drafts_in_iesg_process.html

This file was deleted.

Loading