diff --git a/ietf/api/tests.py b/ietf/api/tests.py index 2310d71d7..26fcd04a8 100644 --- a/ietf/api/tests.py +++ b/ietf/api/tests.py @@ -991,6 +991,28 @@ class CustomApiTests(TestCase): self.assertCountEqual(result.keys(), ["addresses"]) self.assertCountEqual(result["addresses"], Email.objects.filter(active=True).values_list("address", flat=True)) + @override_settings(APP_API_TOKENS={"ietf.api.views.role_holder_addresses": ["valid-token"]}) + def test_role_holder_addresses(self): + url = urlreverse("ietf.api.views.role_holder_addresses") + r = self.client.get(url, headers={}) + self.assertEqual(r.status_code, 403, "No api token, no access") + r = self.client.get(url, headers={"X-Api-Key": "not-valid-token"}) + self.assertEqual(r.status_code, 403, "Bad api token, no access") + r = self.client.post(url, headers={"X-Api-Key": "valid-token"}) + self.assertEqual(r.status_code, 405, "Bad method, no access") + + emails = EmailFactory.create_batch(5) + email_queryset = Email.objects.filter(pk__in=[e.pk for e in emails]) + with mock.patch("ietf.api.views.role_holder_emails", return_value=email_queryset): + r = self.client.get(url, headers={"X-Api-Key": "valid-token"}) + self.assertEqual(r.status_code, 200, "Good api token and method, access") + content_dict = json.loads(r.content) + self.assertCountEqual(content_dict.keys(), ["addresses"]) + self.assertEqual( + content_dict["addresses"], + sorted(e.address for e in emails), + ) + class DirectAuthApiTests(TestCase): diff --git a/ietf/api/urls.py b/ietf/api/urls.py index 1adc02a03..4fab83172 100644 --- a/ietf/api/urls.py +++ b/ietf/api/urls.py @@ -28,6 +28,8 @@ urlpatterns = [ url(r'^export/personal-information/$', api_views.PersonalInformationExportView.as_view()), # Email alias information for groups url(r'^group/group-aliases/$', api_views.group_aliases), + # Email addresses belonging to role holders + url(r'^group/role-holder-addresses/$', api_views.role_holder_addresses), # Let IESG members set positions programmatically url(r'^iesg/position', views_ballot.api_set_position), # Let Meetecho set session video URLs diff --git a/ietf/api/views.py b/ietf/api/views.py index 744c6548a..962164439 100644 --- a/ietf/api/views.py +++ b/ietf/api/views.py @@ -29,7 +29,7 @@ from ietf.api import _api_list from ietf.api.ietf_utils import is_valid_token, requires_api_token from ietf.api.serializer import JsonExportMixin from ietf.doc.utils import DraftAliasGenerator, fuzzy_find_documents -from ietf.group.utils import GroupAliasGenerator +from ietf.group.utils import GroupAliasGenerator, role_holder_emails from ietf.ietfauth.utils import role_required from ietf.ietfauth.views import send_account_creation_email from ietf.meeting.models import Meeting @@ -500,3 +500,18 @@ def active_email_list(request): } ) return HttpResponse(status=405) + + +@requires_api_token +def role_holder_addresses(request): + if request.method == "GET": + return JsonResponse( + { + "addresses": list( + role_holder_emails() + .order_by("address") + .values_list("address", flat=True) + ) + } + ) + return HttpResponse(status=405) diff --git a/ietf/group/tests.py b/ietf/group/tests.py index 8ae6a626b..0d5cddf10 100644 --- a/ietf/group/tests.py +++ b/ietf/group/tests.py @@ -18,10 +18,16 @@ import debug # pyflakes:ignore from ietf.doc.factories import DocumentFactory, WgDraftFactory, EditorialDraftFactory from ietf.doc.models import DocEvent, RelatedDocument, Document from ietf.group.models import Role, Group -from ietf.group.utils import get_group_role_emails, get_child_group_role_emails, get_group_ad_emails, GroupAliasGenerator +from ietf.group.utils import ( + get_group_role_emails, + get_child_group_role_emails, + get_group_ad_emails, + GroupAliasGenerator, + role_holder_emails, +) from ietf.group.factories import GroupFactory, RoleFactory from ietf.person.factories import PersonFactory, EmailFactory -from ietf.person.models import Person +from ietf.person.models import Email, Person from ietf.utils.test_utils import login_testing_unauthorized, TestCase class StreamTests(TestCase): @@ -240,3 +246,41 @@ class GroupRoleEmailTests(TestCase): self.assertGreater(len(emails), 0) for item in emails: self.assertIn('@', item) + + def test_role_holder_emails(self): + # The test fixtures create a bunch of addresses that pollute this test's results - disable them + Email.objects.update(active=False) + + role_holders = [ + RoleFactory(name_id="member", group__type_id=gt).person + for gt in [ + "ag", + "area", + "dir", + "iab", + "ietf", + "irtf", + "nomcom", + "rg", + "team", + "wg", + "rag", + ] + ] + # Expect an additional active email to be included + EmailFactory( + person=role_holders[0], + active=True, + ) + # Do not expect an inactive email to be included + EmailFactory( + person=role_holders[1], + active=False, + ) + # Do not expect address on a role-holder for a different group type + RoleFactory(name_id="member", group__type_id="adhoc") # arbitrary type not in the of-interest list + + self.assertCountEqual( + role_holder_emails(), + Email.objects.filter(active=True, person__in=role_holders), + ) diff --git a/ietf/group/utils.py b/ietf/group/utils.py index 36917d312..f9c2aa15b 100644 --- a/ietf/group/utils.py +++ b/ietf/group/utils.py @@ -425,3 +425,28 @@ class GroupAliasGenerator: chair_emails = get_group_role_emails(group, ["chair", "delegate"]) if chair_emails: yield group.acronym + "-chairs", ["ietf"], list(chair_emails) + + +def role_holder_emails(): + """Get queryset of active Emails for group role holders""" + group_types_of_interest = [ + "ag", + "area", + "dir", + "iab", + "ietf", + "irtf", + "nomcom", + "rg", + "team", + "wg", + "rag", + ] + roles = Role.objects.filter( + group__state__slug="active", + group__type__in=group_types_of_interest, + ) + emails = Email.objects.filter(active=True).exclude( + address__startswith="unknown-email-" + ) + return emails.filter(person__role__in=roles).distinct()