diff --git a/dev/coverage-action/package-lock.json b/dev/coverage-action/package-lock.json index 8481d75424..15feb073eb 100644 --- a/dev/coverage-action/package-lock.json +++ b/dev/coverage-action/package-lock.json @@ -17,7 +17,7 @@ "luxon": "3.3.0" }, "devDependencies": { - "eslint": "8.37.0", + "eslint": "8.39.0", "eslint-config-standard": "17.0.0", "eslint-plugin-import": "2.27.5", "eslint-plugin-node": "11.1.0", @@ -111,9 +111,9 @@ } }, "node_modules/@eslint/js": { - "version": "8.37.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.37.0.tgz", - "integrity": "sha512-x5vzdtOOGgFVDCUs81QRB2+liax8rFg3+7hqM+QhBG0/G3F1ZsoYl97UrqgHgQ9KKT7G6c4V+aTUCgu/n22v1A==", + "version": "8.39.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.39.0.tgz", + "integrity": "sha512-kf9RB0Fg7NZfap83B3QOqOGg9QmD9yBudqQXzzOtn3i4y7ZUXe5ONeW34Gwi+TxhH4mvj72R1Zc300KUMa9Bng==", "dev": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -1641,15 +1641,15 @@ } }, "node_modules/eslint": { - "version": "8.37.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.37.0.tgz", - "integrity": "sha512-NU3Ps9nI05GUoVMxcZx1J8CNR6xOvUT4jAUMH5+z8lpp3aEdPVCImKw6PWG4PY+Vfkpr+jvMpxs/qoE7wq0sPw==", + "version": "8.39.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.39.0.tgz", + "integrity": "sha512-mwiok6cy7KTW7rBpo05k6+p4YVZByLNjAZ/ACB9DRCu4YDRwjXI01tWHp6KAUWelsBetTxKK/2sHB0vdS8Z2Og==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.4.0", "@eslint/eslintrc": "^2.0.2", - "@eslint/js": "8.37.0", + "@eslint/js": "8.39.0", "@humanwhocodes/config-array": "^0.11.8", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", @@ -1659,7 +1659,7 @@ "debug": "^4.3.2", "doctrine": "^3.0.0", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^7.1.1", + "eslint-scope": "^7.2.0", "eslint-visitor-keys": "^3.4.0", "espree": "^9.5.1", "esquery": "^1.4.2", @@ -1990,9 +1990,9 @@ } }, "node_modules/eslint-scope": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz", - "integrity": "sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.0.tgz", + "integrity": "sha512-DYj5deGlHBfMt15J7rdtyKNq/Nqlv5KfU4iodrQ019XESsRnwXH9KAE0y3cwtUHDo2ob7CypAnCqefh6vioWRw==", "dev": true, "dependencies": { "esrecurse": "^4.3.0", @@ -2000,6 +2000,9 @@ }, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, "node_modules/eslint-utils": { @@ -6161,9 +6164,9 @@ } }, "@eslint/js": { - "version": "8.37.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.37.0.tgz", - "integrity": "sha512-x5vzdtOOGgFVDCUs81QRB2+liax8rFg3+7hqM+QhBG0/G3F1ZsoYl97UrqgHgQ9KKT7G6c4V+aTUCgu/n22v1A==", + "version": "8.39.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.39.0.tgz", + "integrity": "sha512-kf9RB0Fg7NZfap83B3QOqOGg9QmD9yBudqQXzzOtn3i4y7ZUXe5ONeW34Gwi+TxhH4mvj72R1Zc300KUMa9Bng==", "dev": true }, "@gar/promisify": { @@ -7311,15 +7314,15 @@ "dev": true }, "eslint": { - "version": "8.37.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.37.0.tgz", - "integrity": "sha512-NU3Ps9nI05GUoVMxcZx1J8CNR6xOvUT4jAUMH5+z8lpp3aEdPVCImKw6PWG4PY+Vfkpr+jvMpxs/qoE7wq0sPw==", + "version": "8.39.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.39.0.tgz", + "integrity": "sha512-mwiok6cy7KTW7rBpo05k6+p4YVZByLNjAZ/ACB9DRCu4YDRwjXI01tWHp6KAUWelsBetTxKK/2sHB0vdS8Z2Og==", "dev": true, "requires": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.4.0", "@eslint/eslintrc": "^2.0.2", - "@eslint/js": "8.37.0", + "@eslint/js": "8.39.0", "@humanwhocodes/config-array": "^0.11.8", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", @@ -7329,7 +7332,7 @@ "debug": "^4.3.2", "doctrine": "^3.0.0", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^7.1.1", + "eslint-scope": "^7.2.0", "eslint-visitor-keys": "^3.4.0", "espree": "^9.5.1", "esquery": "^1.4.2", @@ -7573,9 +7576,9 @@ "requires": {} }, "eslint-scope": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz", - "integrity": "sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.0.tgz", + "integrity": "sha512-DYj5deGlHBfMt15J7rdtyKNq/Nqlv5KfU4iodrQ019XESsRnwXH9KAE0y3cwtUHDo2ob7CypAnCqefh6vioWRw==", "dev": true, "requires": { "esrecurse": "^4.3.0", diff --git a/dev/coverage-action/package.json b/dev/coverage-action/package.json index 7c4e5ac2f1..05ceac94db 100644 --- a/dev/coverage-action/package.json +++ b/dev/coverage-action/package.json @@ -14,7 +14,7 @@ "luxon": "3.3.0" }, "devDependencies": { - "eslint": "8.37.0", + "eslint": "8.39.0", "eslint-config-standard": "17.0.0", "eslint-plugin-import": "2.27.5", "eslint-plugin-node": "11.1.0", diff --git a/ietf/doc/tests.py b/ietf/doc/tests.py index 7761337e3a..13bffdc671 100644 --- a/ietf/doc/tests.py +++ b/ietf/doc/tests.py @@ -40,7 +40,7 @@ ConflictReviewFactory, WgDraftFactory, IndividualDraftFactory, WgRfcFactory, IndividualRfcFactory, StateDocEventFactory, BallotPositionDocEventFactory, BallotDocEventFactory, DocumentAuthorFactory, NewRevisionDocEventFactory, - StatusChangeFactory, BofreqFactory, DocExtResourceFactory) + StatusChangeFactory, BofreqFactory, DocExtResourceFactory, RgDraftFactory) 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 @@ -2819,3 +2819,45 @@ def test_notify_validation(self): self.assertFalse(f.is_valid()) self.assertTrue("Invalid addresses" in f.errors["notify"][0]) self.assertTrue("Duplicate addresses" in f.errors["notify"][0]) + +class CanRequestConflictReviewTests(TestCase): + def test_gets_request_conflict_review_action_button(self): + ise_draft = IndividualDraftFactory(stream_id="ise") + irtf_draft = RgDraftFactory() + + # This is blunt, trading off precision for time. A more thorough test would ensure + # that the text is in a button and that the correct link is absent/present as well. + + target_string = "Begin IETF conflict review" + + url = urlreverse("ietf.doc.views_doc.document_main", kwargs=dict(name=irtf_draft.name)) + r = self.client.get(url) + self.assertNotContains(r, target_string) + self.client.login(username="secretary", password="secretary+password") + r = self.client.get(url) + self.assertContains(r, target_string) + self.client.logout() + self.client.login(username="irtf-chair", password="irtf-chair+password") + r = self.client.get(url) + self.assertContains(r, target_string) + self.client.logout() + self.client.login(username="ise-chair", password="ise-chair+password") + r = self.client.get(url) + self.assertNotContains(r, target_string) + self.client.logout() + + url = urlreverse("ietf.doc.views_doc.document_main", kwargs=dict(name=ise_draft.name)) + r = self.client.get(url) + self.assertNotContains(r, target_string) + self.client.login(username="secretary", password="secretary+password") + r = self.client.get(url) + self.assertContains(r, target_string) + self.client.logout() + self.client.login(username="irtf-chair", password="irtf-chair+password") + r = self.client.get(url) + self.assertNotContains(r, target_string) + self.client.logout() + self.client.login(username="ise-chair", password="ise-chair+password") + r = self.client.get(url) + self.assertContains(r, target_string) + diff --git a/ietf/doc/views_doc.py b/ietf/doc/views_doc.py index 8f136ac219..e33a51f8bf 100644 --- a/ietf/doc/views_doc.py +++ b/ietf/doc/views_doc.py @@ -427,12 +427,20 @@ def document_main(request, name, rev=None, document_html=False): urlreverse('ietf.doc.views_ballot.close_rsab_ballot', kwargs=dict(name=doc.name)) )) - if (doc.get_state_slug() not in ["rfc", "expired"] and doc.stream_id in ("ise", "irtf") - and has_role(request.user, ("Secretariat", "IRTF Chair")) and not conflict_reviews and not snapshot): - label = "Begin IETF Conflict Review" - if not doc.intended_std_level: - label += " (note that intended status is not set)" - actions.append((label, urlreverse('ietf.doc.views_conflict_review.start_review', kwargs=dict(name=doc.name)))) + if ( + doc.get_state_slug() not in ["rfc", "expired"] + and not conflict_reviews + and not snapshot + ): + if ( + doc.stream_id == "ise" and has_role(request.user, ("Secretariat", "ISE")) + ) or ( + doc.stream_id == "irtf" and has_role(request.user, ("Secretariat", "IRTF Chair")) + ): + label = "Begin IETF conflict review" # Note that the template feeds this through capfirst_allcaps + if not doc.intended_std_level: + label += " (note that intended status is not set)" + actions.append((label, urlreverse('ietf.doc.views_conflict_review.start_review', kwargs=dict(name=doc.name)))) if doc.get_state_slug() not in ["rfc", "expired"] and not snapshot: if can_request_rfc_publication(request.user, doc): diff --git a/ietf/ietfauth/views.py b/ietf/ietfauth/views.py index e186b1a544..ac47634499 100644 --- a/ietf/ietfauth/views.py +++ b/ietf/ietfauth/views.py @@ -123,16 +123,42 @@ def create_account(request): new_account_email = form.cleaned_data[ "email" ] # This will be lowercase if form.is_valid() - - user = User.objects.filter(username__iexact=new_account_email) - email = Email.objects.filter(address__iexact=new_account_email) - if user.exists() or email.exists(): - person_to_contact = user.first().person if user else email.first().person - to_email = person_to_contact.email_address() - if to_email: - send_account_creation_exists_email(request, new_account_email, to_email) - else: - raise ValidationError(f"Account for {new_account_email} exists, but cannot email it") + email_is_known = False # do we already know of the new_account_email address? + + # Find an existing Person to contact, if one exists + person_to_contact = None + user = User.objects.filter(username__iexact=new_account_email).first() + if user is not None: + email_is_known = True + try: + person_to_contact = user.person + except User.person.RelatedObjectDoesNotExist: + # User.person is a OneToOneField so it raises an exception if the field is null + pass # leave person_to_contact as None + if person_to_contact is None: + email = Email.objects.filter(address__iexact=new_account_email).first() + if email is not None: + email_is_known = True + # Email.person is a ForeignKey, so its value is None if the field is null + person_to_contact = email.person + # Get a "good" email to contact the existing Person + to_email = person_to_contact.email_address() if person_to_contact else None + + if to_email: + # We have a "good" email - send instructions to it + send_account_creation_exists_email(request, new_account_email, to_email) + elif email_is_known: + # Either a User or an Email matching new_account_email is in the system but we do not have a + # "good" email to use to contact its owner. Fail so the user can contact the secretariat to sort + # things out. + form.add_error( + "email", + ValidationError( + f"Unable to create account for {new_account_email}. Please contact " + f"the Secretariat at {settings.SECRETARIAT_SUPPORT_EMAIL} for assistance." + ), + ) + new_account_email = None # Indicate to the template that we failed to create the requested account else: # For the IETF 113 Registration period (at least) we are lowering the # barriers for account creation to the simple email round-trip check diff --git a/ietf/static/js/ietf.js b/ietf/static/js/ietf.js index 353dba005a..d6be021a2b 100644 --- a/ietf/static/js/ietf.js +++ b/ietf/static/js/ietf.js @@ -255,25 +255,26 @@ $(document) // Bootstrap doesn't load modals via href anymore, so let's do it ourselves. // See https://stackoverflow.com/a/48934494/2240756 +// Instead of attaching to the modal elements as in that example, though, +// listen on document and filter with the .modal selector. This allows handling +// of modals that are added dynamically (e.g., list.js apparently replaces DOM +// elements with identical copies, minus any attached listeners). $(document) - .ready(function () { - $('.modal') - .on('show.bs.modal', function (e) { - var button = $(e.relatedTarget); - if (!$(button) - .attr("href")) { - return; - } - var loc = $(button) - .attr("href") - .trim(); - // load content from value of button href - if (loc !== undefined && loc !== "#") { - $(this) - .find('.modal-content') - .load(loc); - } - }); + .on('show.bs.modal', '.modal', function (e) { + var button = $(e.relatedTarget); + if (!$(button) + .attr("href")) { + return; + } + var loc = $(button) + .attr("href") + .trim(); + // load content from value of button href + if (loc !== undefined && loc !== "#") { + $(this) + .find('.modal-content') + .load(loc); + } }); // Handle history snippet expansion. diff --git a/playwright/package-lock.json b/playwright/package-lock.json index 69af285eff..d483d91a2b 100644 --- a/playwright/package-lock.json +++ b/playwright/package-lock.json @@ -16,7 +16,7 @@ }, "devDependencies": { "@playwright/test": "1.32.3", - "eslint": "8.37.0", + "eslint": "8.39.0", "eslint-config-standard": "17.0.0", "eslint-plugin-import": "2.27.5", "eslint-plugin-n": "15.7.0", @@ -83,9 +83,9 @@ } }, "node_modules/@eslint/js": { - "version": "8.37.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.37.0.tgz", - "integrity": "sha512-x5vzdtOOGgFVDCUs81QRB2+liax8rFg3+7hqM+QhBG0/G3F1ZsoYl97UrqgHgQ9KKT7G6c4V+aTUCgu/n22v1A==", + "version": "8.39.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.39.0.tgz", + "integrity": "sha512-kf9RB0Fg7NZfap83B3QOqOGg9QmD9yBudqQXzzOtn3i4y7ZUXe5ONeW34Gwi+TxhH4mvj72R1Zc300KUMa9Bng==", "dev": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -1457,15 +1457,15 @@ } }, "node_modules/eslint": { - "version": "8.37.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.37.0.tgz", - "integrity": "sha512-NU3Ps9nI05GUoVMxcZx1J8CNR6xOvUT4jAUMH5+z8lpp3aEdPVCImKw6PWG4PY+Vfkpr+jvMpxs/qoE7wq0sPw==", + "version": "8.39.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.39.0.tgz", + "integrity": "sha512-mwiok6cy7KTW7rBpo05k6+p4YVZByLNjAZ/ACB9DRCu4YDRwjXI01tWHp6KAUWelsBetTxKK/2sHB0vdS8Z2Og==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.4.0", "@eslint/eslintrc": "^2.0.2", - "@eslint/js": "8.37.0", + "@eslint/js": "8.39.0", "@humanwhocodes/config-array": "^0.11.8", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", @@ -1475,7 +1475,7 @@ "debug": "^4.3.2", "doctrine": "^3.0.0", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^7.1.1", + "eslint-scope": "^7.2.0", "eslint-visitor-keys": "^3.4.0", "espree": "^9.5.1", "esquery": "^1.4.2", @@ -1797,9 +1797,9 @@ } }, "node_modules/eslint-scope": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz", - "integrity": "sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.0.tgz", + "integrity": "sha512-DYj5deGlHBfMt15J7rdtyKNq/Nqlv5KfU4iodrQ019XESsRnwXH9KAE0y3cwtUHDo2ob7CypAnCqefh6vioWRw==", "dev": true, "dependencies": { "esrecurse": "^4.3.0", @@ -1807,6 +1807,9 @@ }, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, "node_modules/eslint-utils": { @@ -5858,9 +5861,9 @@ } }, "@eslint/js": { - "version": "8.37.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.37.0.tgz", - "integrity": "sha512-x5vzdtOOGgFVDCUs81QRB2+liax8rFg3+7hqM+QhBG0/G3F1ZsoYl97UrqgHgQ9KKT7G6c4V+aTUCgu/n22v1A==", + "version": "8.39.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.39.0.tgz", + "integrity": "sha512-kf9RB0Fg7NZfap83B3QOqOGg9QmD9yBudqQXzzOtn3i4y7ZUXe5ONeW34Gwi+TxhH4mvj72R1Zc300KUMa9Bng==", "dev": true }, "@faker-js/faker": { @@ -6874,15 +6877,15 @@ "dev": true }, "eslint": { - "version": "8.37.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.37.0.tgz", - "integrity": "sha512-NU3Ps9nI05GUoVMxcZx1J8CNR6xOvUT4jAUMH5+z8lpp3aEdPVCImKw6PWG4PY+Vfkpr+jvMpxs/qoE7wq0sPw==", + "version": "8.39.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.39.0.tgz", + "integrity": "sha512-mwiok6cy7KTW7rBpo05k6+p4YVZByLNjAZ/ACB9DRCu4YDRwjXI01tWHp6KAUWelsBetTxKK/2sHB0vdS8Z2Og==", "dev": true, "requires": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.4.0", "@eslint/eslintrc": "^2.0.2", - "@eslint/js": "8.37.0", + "@eslint/js": "8.39.0", "@humanwhocodes/config-array": "^0.11.8", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", @@ -6892,7 +6895,7 @@ "debug": "^4.3.2", "doctrine": "^3.0.0", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^7.1.1", + "eslint-scope": "^7.2.0", "eslint-visitor-keys": "^3.4.0", "espree": "^9.5.1", "esquery": "^1.4.2", @@ -7117,9 +7120,9 @@ "requires": {} }, "eslint-scope": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz", - "integrity": "sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.0.tgz", + "integrity": "sha512-DYj5deGlHBfMt15J7rdtyKNq/Nqlv5KfU4iodrQ019XESsRnwXH9KAE0y3cwtUHDo2ob7CypAnCqefh6vioWRw==", "dev": true, "requires": { "esrecurse": "^4.3.0", diff --git a/playwright/package.json b/playwright/package.json index c3507d5b4e..551e1ce255 100644 --- a/playwright/package.json +++ b/playwright/package.json @@ -8,7 +8,7 @@ }, "devDependencies": { "@playwright/test": "1.32.3", - "eslint": "8.37.0", + "eslint": "8.39.0", "eslint-config-standard": "17.0.0", "eslint-plugin-import": "2.27.5", "eslint-plugin-n": "15.7.0",