Skip to content

Commit 481fc3f

Browse files
committed
Merged in [18913] from housley@vigilsec.com:
Convert generate-draft-aliaes and generate-wg-aliases into management commands: generate_draft_aliases and generate_group_aliases. Also provide tests for the new management commands. - Legacy-Id: 18925 Note: SVN reference [18913] has been migrated to Git commit 45b14c8
2 parents 63b59a6 + 45b14c8 commit 481fc3f

5 files changed

Lines changed: 478 additions & 10 deletions

File tree

ietf/checks.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Copyright The IETF Trust 2015-2020, All Rights Reserved
1+
# Copyright The IETF Trust 2015-2021, All Rights Reserved
22
# -*- coding: utf-8 -*-
33

44

@@ -62,14 +62,14 @@ def check_group_email_aliases_exists(app_configs, **kwargs):
6262
if not ok:
6363
errors.append(checks.Error(
6464
"Found no aliases in the group email aliases file\n'%s'."%settings.GROUP_ALIASES_PATH,
65-
hint="Please run ietf/bin/generate-wg-aliases to generate them.",
65+
hint="Please run the generate_group_aliases management command to generate them.",
6666
obj=None,
6767
id="datatracker.E0002",
6868
))
6969
except IOError as e:
7070
errors.append(checks.Error(
7171
"Could not read group email aliases:\n %s" % e,
72-
hint="Please run ietf/bin/generate-wg-aliases to generate them.",
72+
hint="Please run the generate_group_aliases management command to generate them.",
7373
obj=None,
7474
id="datatracker.E0003",
7575
))
@@ -89,14 +89,14 @@ def check_doc_email_aliases_exists(app_configs, **kwargs):
8989
if not ok:
9090
errors.append(checks.Error(
9191
"Found no aliases in the document email aliases file\n'%s'."%settings.DRAFT_VIRTUAL_PATH,
92-
hint="Please run ietf/bin/generate-draft-aliases to generate them.",
92+
hint="Please run the generate_draft_aliases management command to generate them.",
9393
obj=None,
9494
id="datatracker.E0004",
9595
))
9696
except IOError as e:
9797
errors.append(checks.Error(
9898
"Could not read document email aliases:\n %s" % e,
99-
hint="Please run ietf/bin/generate-draft-aliases to generate them.",
99+
hint="Please run the generate_draft_aliases management command to generate them.",
100100
obj=None,
101101
id="datatracker.E0005",
102102
))
Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
# Copyright The IETF Trust 2012-2021, All Rights Reserved
2+
# -*- coding: utf-8 -*-
3+
4+
# This was written as a script by Markus Stenberg <markus.stenberg@iki.fi>.
5+
# It was turned into a management command by Russ Housley <housley@vigisec.com>.
6+
7+
import datetime
8+
import io
9+
import os
10+
import re
11+
import time
12+
13+
from django.conf import settings
14+
from django.core.management.base import BaseCommand
15+
16+
import debug # pyflakes:ignore
17+
18+
from ietf.doc.models import Document
19+
from ietf.group.utils import get_group_role_emails, get_group_ad_emails
20+
from ietf.utils.aliases import dump_sublist
21+
from utils.mail import parseaddr
22+
23+
DEFAULT_YEARS = 2
24+
25+
26+
def get_draft_ad_emails(doc):
27+
"""Get AD email addresses for the given draft, if any."""
28+
ad_emails = set()
29+
# If working group document, return current WG ADs
30+
if doc.group and doc.group.acronym != 'none':
31+
ad_emails.update(get_group_ad_emails(doc.group))
32+
# Document may have an explicit AD set
33+
if doc.ad:
34+
ad_emails.add(doc.ad.email_address())
35+
return ad_emails
36+
37+
38+
def get_draft_chair_emails(doc):
39+
"""Get chair email addresses for the given draft, if any."""
40+
chair_emails = set()
41+
if doc.group:
42+
chair_emails.update(get_group_role_emails(doc.group, ['chair', 'secr']))
43+
return chair_emails
44+
45+
46+
def get_draft_shepherd_email(doc):
47+
"""Get shepherd email addresses for the given draft, if any."""
48+
shepherd_email = set()
49+
if doc.shepherd:
50+
shepherd_email.add(doc.shepherd.email_address())
51+
return shepherd_email
52+
53+
54+
def get_draft_authors_emails(doc):
55+
"""Get list of authors for the given draft."""
56+
author_emails = set()
57+
for author in doc.documentauthor_set.all():
58+
if author.email and author.email.email_address():
59+
author_emails.add(author.email.email_address())
60+
return author_emails
61+
62+
63+
def get_draft_notify_emails(doc):
64+
"""Get list of email addresses to notify for the given draft."""
65+
ad_email_alias_regex = r"^%s.ad@(%s|%s)$" % (doc.name, settings.DRAFT_ALIAS_DOMAIN, settings.TOOLS_SERVER)
66+
all_email_alias_regex = r"^%s.all@(%s|%s)$" % (doc.name, settings.DRAFT_ALIAS_DOMAIN, settings.TOOLS_SERVER)
67+
author_email_alias_regex = r"^%s@(%s|%s)$" % (doc.name, settings.DRAFT_ALIAS_DOMAIN, settings.TOOLS_SERVER)
68+
notify_email_alias_regex = r"^%s.notify@(%s|%s)$" % (doc.name, settings.DRAFT_ALIAS_DOMAIN, settings.TOOLS_SERVER)
69+
shepherd_email_alias_regex = r"^%s.shepherd@(%s|%s)$" % (doc.name, settings.DRAFT_ALIAS_DOMAIN, settings.TOOLS_SERVER)
70+
notify_emails = set()
71+
if doc.notify:
72+
for e in doc.notify.split(','):
73+
e = e.strip()
74+
if re.search(ad_email_alias_regex, e):
75+
notify_emails.update(get_draft_ad_emails(doc))
76+
elif re.search(author_email_alias_regex, e):
77+
notify_emails.update(get_draft_authors_emails(doc))
78+
elif re.search(shepherd_email_alias_regex, e):
79+
notify_emails.update(get_draft_shepherd_email(doc))
80+
elif re.search(all_email_alias_regex, e):
81+
notify_emails.update(get_draft_ad_emails(doc))
82+
notify_emails.update(get_draft_authors_emails(doc))
83+
notify_emails.update(get_draft_shepherd_email(doc))
84+
elif re.search(notify_email_alias_regex, e):
85+
pass
86+
else:
87+
(name, email) = parseaddr(e)
88+
notify_emails.add(email)
89+
return notify_emails
90+
91+
92+
class Command(BaseCommand):
93+
help = ('Generate the draft-aliases and draft-virtual files for Internet-Draft '
94+
'mail aliases, placing them in the files configured in '
95+
'settings.DRAFT_ALIASES_PATH and settings.DRAFT_VIRTUAL_PATH, '
96+
'respectively. The generation includes aliases for Internet-Drafts '
97+
'that have seen activity in the last %s years.' % (DEFAULT_YEARS))
98+
99+
def handle(self, *args, **options):
100+
show_since = datetime.datetime.now() - datetime.timedelta(DEFAULT_YEARS*365)
101+
102+
date = time.strftime("%Y-%m-%d_%H:%M:%S")
103+
signature = '# Generated by %s at %s\n' % (os.path.abspath(__file__), date)
104+
105+
afile = io.open(settings.DRAFT_ALIASES_PATH, "w")
106+
vfile = io.open(settings.DRAFT_VIRTUAL_PATH, "w")
107+
108+
afile.write(signature)
109+
vfile.write(signature)
110+
vfile.write("%s anything\n" % settings.DRAFT_VIRTUAL_DOMAIN)
111+
112+
# Internet-Drafts with active status or expired within DEFAULT_YEARS
113+
drafts = Document.objects.filter(name__startswith='draft-')
114+
active_drafts = drafts.filter(states__slug='active')
115+
inactive_recent_drafts = drafts.exclude(states__slug='active').filter(expires__gte=show_since)
116+
interesting_drafts = active_drafts | inactive_recent_drafts
117+
118+
alias_domains = ['ietf.org', ]
119+
for draft in interesting_drafts.distinct().iterator():
120+
# Omit RFCs, unless they were published in the last DEFAULT_YEARS
121+
if draft.docalias.filter(name__startswith='rfc'):
122+
if draft.latest_event(type='published_rfc').time < show_since:
123+
continue
124+
125+
alias = draft.name
126+
all = set()
127+
128+
# no suffix and .authors are the same list
129+
emails = get_draft_authors_emails(draft)
130+
all.update(emails)
131+
dump_sublist(afile, vfile, alias, alias_domains, settings.DRAFT_VIRTUAL_DOMAIN, emails)
132+
dump_sublist(afile, vfile, alias+'.authors', alias_domains, settings.DRAFT_VIRTUAL_DOMAIN, emails)
133+
134+
# .chairs = group chairs
135+
emails = get_draft_chair_emails(draft)
136+
if emails:
137+
all.update(emails)
138+
dump_sublist(afile, vfile, alias+'.chairs', alias_domains, settings.DRAFT_VIRTUAL_DOMAIN, emails)
139+
140+
# .ad = sponsoring AD / WG AD (WG document)
141+
emails = get_draft_ad_emails(draft)
142+
if emails:
143+
all.update(emails)
144+
dump_sublist(afile, vfile, alias+'.ad', alias_domains, settings.DRAFT_VIRTUAL_DOMAIN, emails)
145+
146+
# .notify = notify email list from the Document
147+
emails = get_draft_notify_emails(draft)
148+
if emails:
149+
all.update(emails)
150+
dump_sublist(afile, vfile, alias+'.notify', alias_domains, settings.DRAFT_VIRTUAL_DOMAIN, emails)
151+
152+
# .shepherd = shepherd email from the Document
153+
emails = get_draft_shepherd_email(draft)
154+
if emails:
155+
all.update(emails)
156+
dump_sublist(afile, vfile, alias+'.shepherd', alias_domains, settings.DRAFT_VIRTUAL_DOMAIN, emails)
157+
158+
# .all = everything from above
159+
dump_sublist(afile, vfile, alias+'.all', alias_domains, settings.DRAFT_VIRTUAL_DOMAIN, all)
160+
161+
afile.close()
162+
vfile.close()
163+

ietf/doc/tests.py

Lines changed: 118 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,12 @@
1010
import bibtexparser
1111
import mock
1212

13-
1413
from http.cookies import SimpleCookie
1514
from pyquery import PyQuery
1615
from urllib.parse import urlparse, parse_qs
1716
from tempfile import NamedTemporaryFile
1817

18+
from django.core.management import call_command
1919
from django.urls import reverse as urlreverse
2020
from django.conf import settings
2121

@@ -1379,7 +1379,123 @@ def test_references(self):
13791379
r = self.client.get(url)
13801380
self.assertEqual(r.status_code, 200)
13811381
self.assertContains(r, doc1.name)
1382-
1382+
1383+
class GenerateDraftAliasesTests(TestCase):
1384+
def setUp(self):
1385+
self.doc_aliases_file = NamedTemporaryFile(delete=False, mode='w+')
1386+
self.doc_aliases_file.close()
1387+
self.doc_virtual_file = NamedTemporaryFile(delete=False, mode='w+')
1388+
self.doc_virtual_file.close()
1389+
self.saved_draft_aliases_path = settings.DRAFT_ALIASES_PATH
1390+
self.saved_draft_virtual_path = settings.DRAFT_VIRTUAL_PATH
1391+
settings.DRAFT_ALIASES_PATH = self.doc_aliases_file.name
1392+
settings.DRAFT_VIRTUAL_PATH = self.doc_virtual_file.name
1393+
1394+
def tearDown(self):
1395+
settings.DRAFT_ALIASES_PATH = self.saved_draft_aliases_path
1396+
settings.DRAFT_VIRTUAL_PATH = self.saved_draft_virtual_path
1397+
os.unlink(self.doc_aliases_file.name)
1398+
os.unlink(self.doc_virtual_file.name)
1399+
1400+
def testManagementCommand(self):
1401+
a_month_ago = datetime.datetime.now() - datetime.timedelta(30)
1402+
ad = RoleFactory(name_id='ad', group__type_id='area', group__state_id='active').person
1403+
shepherd = PersonFactory()
1404+
author1 = PersonFactory()
1405+
author2 = PersonFactory()
1406+
author3 = PersonFactory()
1407+
author4 = PersonFactory()
1408+
author5 = PersonFactory()
1409+
author6 = PersonFactory()
1410+
mars = GroupFactory(type_id='wg', acronym='mars')
1411+
marschairman = PersonFactory(user__username='marschairman')
1412+
mars.role_set.create(name_id='chair', person=marschairman, email=marschairman.email())
1413+
doc1 = IndividualDraftFactory(authors=[author1], shepherd=shepherd.email(), ad=ad)
1414+
doc2 = WgDraftFactory(name='draft-ietf-mars-test', group__acronym='mars', authors=[author2], ad=ad)
1415+
doc3 = WgRfcFactory.create(name='draft-ietf-mars-finished', group__acronym='mars', authors=[author3], ad=ad, std_level_id='ps', states=[('draft','rfc'),('draft-iesg','pub')], time=a_month_ago)
1416+
DocEventFactory.create(doc=doc3, type='published_rfc', time=a_month_ago.strftime("%Y-%m-%d"))
1417+
doc4 = WgRfcFactory.create(authors=[author4,author5], ad=ad, std_level_id='ps', states=[('draft','rfc'),('draft-iesg','pub')], time=datetime.datetime(2010,10,10))
1418+
DocEventFactory.create(doc=doc4, type='published_rfc', time = '2010-10-10')
1419+
doc5 = IndividualDraftFactory(authors=[author6])
1420+
1421+
args = [ ]
1422+
kwargs = { }
1423+
out = io.StringIO()
1424+
call_command("generate_draft_aliases", *args, **kwargs, stdout=out, stderr=out)
1425+
self.assertFalse(out.getvalue())
1426+
1427+
with open(settings.DRAFT_ALIASES_PATH) as afile:
1428+
acontent = afile.read()
1429+
self.assertTrue(all([x in acontent for x in [
1430+
'xfilter-' + doc1.name,
1431+
'xfilter-' + doc1.name + '.ad',
1432+
'xfilter-' + doc1.name + '.authors',
1433+
'xfilter-' + doc1.name + '.shepherd',
1434+
'xfilter-' + doc1.name + '.all',
1435+
'xfilter-' + doc2.name,
1436+
'xfilter-' + doc2.name + '.ad',
1437+
'xfilter-' + doc2.name + '.authors',
1438+
'xfilter-' + doc2.name + '.chairs',
1439+
'xfilter-' + doc2.name + '.all',
1440+
'xfilter-' + doc3.name,
1441+
'xfilter-' + doc3.name + '.ad',
1442+
'xfilter-' + doc3.name + '.authors',
1443+
'xfilter-' + doc3.name + '.chairs',
1444+
'xfilter-' + doc5.name,
1445+
'xfilter-' + doc5.name + '.authors',
1446+
'xfilter-' + doc5.name + '.all',
1447+
]]))
1448+
self.assertFalse(all([x in acontent for x in [
1449+
'xfilter-' + doc1.name + '.chairs',
1450+
'xfilter-' + doc2.name + '.shepherd',
1451+
'xfilter-' + doc3.name + '.shepherd',
1452+
'xfilter-' + doc4.name,
1453+
'xfilter-' + doc5.name + '.shepherd',
1454+
'xfilter-' + doc5.name + '.ad',
1455+
]]))
1456+
1457+
with open(settings.DRAFT_VIRTUAL_PATH) as vfile:
1458+
vcontent = vfile.read()
1459+
self.assertTrue(all([x in vcontent for x in [
1460+
ad.email_address(),
1461+
shepherd.email_address(),
1462+
marschairman.email_address(),
1463+
author1.email_address(),
1464+
author2.email_address(),
1465+
author3.email_address(),
1466+
author6.email_address(),
1467+
]]))
1468+
self.assertFalse(all([x in vcontent for x in [
1469+
author4.email_address(),
1470+
author5.email_address(),
1471+
]]))
1472+
self.assertTrue(all([x in vcontent for x in [
1473+
'xfilter-' + doc1.name,
1474+
'xfilter-' + doc1.name + '.ad',
1475+
'xfilter-' + doc1.name + '.authors',
1476+
'xfilter-' + doc1.name + '.shepherd',
1477+
'xfilter-' + doc1.name + '.all',
1478+
'xfilter-' + doc2.name,
1479+
'xfilter-' + doc2.name + '.ad',
1480+
'xfilter-' + doc2.name + '.authors',
1481+
'xfilter-' + doc2.name + '.chairs',
1482+
'xfilter-' + doc2.name + '.all',
1483+
'xfilter-' + doc3.name,
1484+
'xfilter-' + doc3.name + '.ad',
1485+
'xfilter-' + doc3.name + '.authors',
1486+
'xfilter-' + doc3.name + '.chairs',
1487+
'xfilter-' + doc5.name,
1488+
'xfilter-' + doc5.name + '.authors',
1489+
'xfilter-' + doc5.name + '.all',
1490+
]]))
1491+
self.assertFalse(all([x in vcontent for x in [
1492+
'xfilter-' + doc1.name + '.chairs',
1493+
'xfilter-' + doc2.name + '.shepherd',
1494+
'xfilter-' + doc3.name + '.shepherd',
1495+
'xfilter-' + doc4.name,
1496+
'xfilter-' + doc5.name + '.shepherd',
1497+
'xfilter-' + doc5.name + '.ad',
1498+
]]))
13831499

13841500
class EmailAliasesTests(TestCase):
13851501

0 commit comments

Comments
 (0)