-
Notifications
You must be signed in to change notification settings - Fork 378
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: investigate file authenticity #7331
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -266,3 +266,25 @@ def clean(self): | |
@staticmethod | ||
def valid_resource_tags(): | ||
return ExtResourceName.objects.all().order_by('slug').values_list('slug', flat=True) | ||
|
||
class InvestigateForm(forms.Form): | ||
name_fragment = forms.CharField( | ||
label="File name or fragment to investigate", | ||
required=True, | ||
help_text=( | ||
"Enter a filename such as draft-ietf-some-draft-00.txt or a fragment like draft-ietf-some-draft using at least 8 characters. The search will also work for files that are not necessarily drafts." | ||
), | ||
) | ||
|
||
def clean_name_fragment(self): | ||
disallowed_characters = ["%", "/", "\\", "*"] | ||
name_fragment = self.cleaned_data["name_fragment"] | ||
# Manual inspection of the directories at the time of this writing shows | ||
# looking for files with less than 8 characters in the name is not useful | ||
# Requiring this will help protect against the secretariat unintentionally | ||
# matching every draft. | ||
if len(name_fragment) < 8: | ||
raise ValidationError("Please enter at least 8 characters") | ||
if any([c in name_fragment for c in disallowed_characters]): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Completely trivial gains here, but if you leave out the |
||
raise ValidationError(f"The following characters are disallowed: {', '.join(disallowed_characters)}") | ||
return name_fragment |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -45,7 +45,7 @@ | |
StatusChangeFactory, DocExtResourceFactory, RgDraftFactory, BcpFactory) | ||
from ietf.doc.forms import NotifyForm | ||
from ietf.doc.fields import SearchableDocumentsField | ||
from ietf.doc.utils import create_ballot_if_not_open, uppercase_std_abbreviated_name, DraftAliasGenerator | ||
from ietf.doc.utils import create_ballot_if_not_open, investigate_fragment, uppercase_std_abbreviated_name, DraftAliasGenerator | ||
from ietf.group.models import Group, Role | ||
from ietf.group.factories import GroupFactory, RoleFactory | ||
from ietf.ipr.factories import HolderIprDisclosureFactory | ||
|
@@ -3141,3 +3141,137 @@ def test_state_index(self): | |
if not '-' in name: | ||
self.assertIn(name, content) | ||
|
||
class InvestigateTests(TestCase): | ||
settings_temp_path_overrides = TestCase.settings_temp_path_overrides + [ | ||
"AGENDA_PATH", | ||
# "INTERNET_DRAFT_PATH", | ||
# "INTERNET_DRAFT_ARCHIVE_DIR", | ||
# "INTERNET_ALL_DRAFTS_ARCHIVE_DIR", | ||
] | ||
|
||
def setUp(self): | ||
super().setUp() | ||
# Contort the draft archive dir temporary replacement | ||
# to match the "collections" concept | ||
archive_tmp_dir = Path(settings.INTERNET_DRAFT_ARCHIVE_DIR) | ||
new_archive_dir = archive_tmp_dir / "draft-archive" | ||
new_archive_dir.mkdir() | ||
settings.INTERNET_DRAFT_ARCHIVE_DIR = str(new_archive_dir) | ||
donated_personal_copy_dir = archive_tmp_dir / "donated-personal-copy" | ||
donated_personal_copy_dir.mkdir() | ||
meeting_dir = Path(settings.AGENDA_PATH) / "666" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 😈 |
||
meeting_dir.mkdir() | ||
all_archive_dir = Path(settings.INTERNET_ALL_DRAFTS_ARCHIVE_DIR) | ||
repository_dir = Path(settings.INTERNET_DRAFT_PATH) | ||
|
||
for path in [repository_dir, all_archive_dir]: | ||
(path / "draft-this-is-active-00.txt").touch() | ||
for path in [new_archive_dir, all_archive_dir]: | ||
(path / "draft-old-but-can-authenticate-00.txt").touch() | ||
(path / "draft-has-mixed-provenance-01.txt").touch() | ||
for path in [donated_personal_copy_dir, all_archive_dir]: | ||
(path / "draft-donated-from-a-personal-collection-00.txt").touch() | ||
(path / "draft-has-mixed-provenance-00.txt").touch() | ||
(path / "draft-has-mixed-provenance-00.txt.Z").touch() | ||
(all_archive_dir / "draft-this-should-not-be-possible-00.txt").touch() | ||
(meeting_dir / "draft-this-predates-the-archive-00.txt").touch() | ||
|
||
def test_investigate_fragment(self): | ||
|
||
result = investigate_fragment("this-is-active") | ||
self.assertEqual(len(result["can_verify"]), 1) | ||
self.assertEqual(len(result["unverifiable_collections"]), 0) | ||
self.assertEqual(len(result["unexpected"]), 0) | ||
self.assertEqual( | ||
list(result["can_verify"])[0].name, "draft-this-is-active-00.txt" | ||
) | ||
|
||
result = investigate_fragment("old-but-can") | ||
self.assertEqual(len(result["can_verify"]), 1) | ||
self.assertEqual(len(result["unverifiable_collections"]), 0) | ||
self.assertEqual(len(result["unexpected"]), 0) | ||
self.assertEqual( | ||
list(result["can_verify"])[0].name, "draft-old-but-can-authenticate-00.txt" | ||
) | ||
|
||
result = investigate_fragment("predates") | ||
self.assertEqual(len(result["can_verify"]), 1) | ||
self.assertEqual(len(result["unverifiable_collections"]), 0) | ||
self.assertEqual(len(result["unexpected"]), 0) | ||
self.assertEqual( | ||
list(result["can_verify"])[0].name, "draft-this-predates-the-archive-00.txt" | ||
) | ||
|
||
result = investigate_fragment("personal-collection") | ||
self.assertEqual(len(result["can_verify"]), 0) | ||
self.assertEqual(len(result["unverifiable_collections"]), 1) | ||
self.assertEqual(len(result["unexpected"]), 0) | ||
self.assertEqual( | ||
list(result["unverifiable_collections"])[0].name, | ||
"draft-donated-from-a-personal-collection-00.txt", | ||
) | ||
|
||
result = investigate_fragment("mixed-provenance") | ||
self.assertEqual(len(result["can_verify"]), 1) | ||
self.assertEqual(len(result["unverifiable_collections"]), 2) | ||
self.assertEqual(len(result["unexpected"]), 0) | ||
self.assertEqual( | ||
list(result["can_verify"])[0].name, "draft-has-mixed-provenance-01.txt" | ||
) | ||
self.assertEqual( | ||
set([p.name for p in result["unverifiable_collections"]]), | ||
set( | ||
[ | ||
"draft-has-mixed-provenance-00.txt", | ||
"draft-has-mixed-provenance-00.txt.Z", | ||
] | ||
), | ||
) | ||
|
||
result = investigate_fragment("not-be-possible") | ||
self.assertEqual(len(result["can_verify"]), 0) | ||
self.assertEqual(len(result["unverifiable_collections"]), 0) | ||
self.assertEqual(len(result["unexpected"]), 1) | ||
self.assertEqual( | ||
list(result["unexpected"])[0].name, | ||
"draft-this-should-not-be-possible-00.txt", | ||
) | ||
|
||
def test_investigate(self): | ||
url = urlreverse("ietf.doc.views_doc.investigate") | ||
login_testing_unauthorized(self, "secretary", url) | ||
r = self.client.get(url) | ||
self.assertEqual(r.status_code, 200) | ||
q = PyQuery(r.content) | ||
self.assertEqual(len(q("form#investigate")), 1) | ||
self.assertEqual(len(q("div#results")), 0) | ||
r = self.client.post(url, dict(name_fragment="this-is-not-found")) | ||
self.assertEqual(r.status_code, 200) | ||
q = PyQuery(r.content) | ||
self.assertEqual(len(q("div#results")), 1) | ||
self.assertEqual(len(q("table#authenticated")), 0) | ||
self.assertEqual(len(q("table#unverifiable")), 0) | ||
self.assertEqual(len(q("table#unexpected")), 0) | ||
r = self.client.post(url, dict(name_fragment="mixed-provenance")) | ||
self.assertEqual(r.status_code, 200) | ||
q = PyQuery(r.content) | ||
self.assertEqual(len(q("div#results")), 1) | ||
self.assertEqual(len(q("table#authenticated")), 1) | ||
self.assertEqual(len(q("table#unverifiable")), 1) | ||
self.assertEqual(len(q("table#unexpected")), 0) | ||
r = self.client.post(url, dict(name_fragment="not-be-possible")) | ||
self.assertEqual(r.status_code, 200) | ||
q = PyQuery(r.content) | ||
self.assertEqual(len(q("div#results")), 1) | ||
self.assertEqual(len(q("table#authenticated")), 0) | ||
self.assertEqual(len(q("table#unverifiable")), 0) | ||
self.assertEqual(len(q("table#unexpected")), 1) | ||
r = self.client.post(url, dict(name_fragment="short")) | ||
self.assertEqual(r.status_code, 200) | ||
q = PyQuery(r.content) | ||
self.assertEqual(len(q("#id_name_fragment.is-invalid")), 1) | ||
for char in ["*", "%", "/", "\\"]: | ||
r = self.client.post(url, dict(name_fragment=f"bad{char}character")) | ||
self.assertEqual(r.status_code, 200) | ||
q = PyQuery(r.content) | ||
self.assertEqual(len(q("#id_name_fragment.is-invalid")), 1) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you put
min_length=8
on theCharField
, it'll validate for you and (I think) catch it client-side with HTML validation