Projet

Général

Profil

0001-emails-restrict-domains-for-default_from_email-72173.patch

Thomas Noël, 16 décembre 2022 12:15

Télécharger (8,63 ko)

Voir les différences:

Subject: [PATCH] emails: restrict domains for default_from_email (#72173)

 hobo/emails/forms.py      |  3 ++-
 hobo/emails/validators.py | 18 +++++++++++++
 hobo/settings.py          |  4 +++
 tests/test_emails.py      | 57 +++++++++++++++++++++++++++++++++++++--
 4 files changed, 79 insertions(+), 3 deletions(-)
hobo/emails/forms.py
17 17
from django.core.validators import validate_email
18 18
from django.utils.translation import ugettext_lazy as _
19 19

  
20
from hobo.emails.validators import validate_email_address, validate_email_spf
20
from hobo.emails.validators import validate_email_address, validate_email_spf, validate_email_domain
21 21

  
22 22

  
23 23
class ValidEmailField(forms.EmailField):
24 24
    def validate(self, value):
25 25
        validate_email(value)
26
        validate_email_domain(value)
26 27
        validate_email_address(value)
27 28
        validate_email_spf(value)
28 29

  
hobo/emails/validators.py
16 16

  
17 17
import smtplib
18 18
import socket
19
import urllib.parse
19 20

  
20 21
import dns.resolver
21 22
from django.conf import settings
......
75 76
            if allowed_record in spf_record:
76 77
                return
77 78
    raise ValidationError(_('No suitable SPF record found for %s') % email_domain)
79

  
80

  
81
def validate_email_domain(value):
82
    domain = value.split('@')[-1].strip().strip('.')
83
    domains = settings.EMAIL_FROM_ALLOWED_DOMAINS
84
    if '*' in domains or domain in domains:
85
        return
86
    known_services = getattr(settings, 'KNOWN_SERVICES', {})
87
    for app in known_services.values():
88
        for instance in app.values():
89
            url = instance.get('url')
90
            if not url:
91
                continue
92
            fqdn = urllib.parse.urlparse(url).netloc.split(':')[0]
93
            if fqdn == domain or fqdn == 'www.' + domain:
94
                return
95
    raise ValidationError(_('Domain %s is not allowed') % domain)
hobo/settings.py
29 29

  
30 30
HOBO_VALIDATE_EMAIL_WITH_SMTP = True
31 31
ALLOWED_SPF_RECORDS = []
32
# EMAIL_FROM_ALLOWED_DOMAINS: list of allowed domains for default_from_email.
33
# Use ['*'] to allow all domains.
34
# Note: all KNOWN_SERVICES url domains are always allowed
35
EMAIL_FROM_ALLOWED_DOMAINS = []
32 36

  
33 37
# Application definition
34 38

  
tests/test_emails.py
137 137
    assert 'Enter a valid email address' in force_text(response.content)
138 138

  
139 139

  
140
def test_unkown_address(client, admin_user, dns_resolver, smtp_server):
140
def test_unkown_address(client, admin_user, dns_resolver, smtp_server, settings):
141
    settings.EMAIL_FROM_ALLOWED_DOMAINS = ['*']
141 142
    client.post('/login/', {'username': 'admin', 'password': 'password'})
142 143
    response = client.post('/emails/', {'default_from_email': 'john.doe@unknown.com'})
143 144
    assert response.status_code == 200
144 145
    assert 'Email address not found' in force_text(response.content)
145 146

  
146 147

  
147
def test_kown_address_nospf(client, admin_user, dns_resolver, smtp_server):
148
def test_kown_address_nospf(client, admin_user, dns_resolver, smtp_server, settings):
149
    settings.EMAIL_FROM_ALLOWED_DOMAINS = ['*']
148 150
    client.post('/login/', {'username': 'admin', 'password': 'password'})
149 151
    response = client.post('/emails/', {'default_from_email': 'john.doe@example.com'}, follow=True)
150 152
    assert response.status_code == 200
......
154 156

  
155 157

  
156 158
def test_spf_allow_all_mail(client, admin_user, dns_resolver, smtp_server, settings):
159
    settings.EMAIL_FROM_ALLOWED_DOMAINS = ['*']
157 160
    client.post('/login/', {'username': 'admin', 'password': 'password'})
158 161
    response = client.post(
159 162
        '/emails/', {'default_from_email': 'john.doe@example-spf-allow-all.com'}, follow=True
......
165 168

  
166 169

  
167 170
def test_invalid_spf(client, admin_user, dns_resolver, smtp_server, settings):
171
    settings.EMAIL_FROM_ALLOWED_DOMAINS = ['*']
168 172
    settings.ALLOWED_SPF_RECORDS = ['include:example.com']
169 173
    client.post('/login/', {'username': 'admin', 'password': 'password'})
170 174
    response = client.post('/emails/', {'default_from_email': 'john.doe@example-invalid-spf.com'})
......
173 177

  
174 178

  
175 179
def test_strict_nospf(client, admin_user, dns_resolver, smtp_server, monkeypatch, settings):
180
    settings.EMAIL_FROM_ALLOWED_DOMAINS = ['*']
176 181
    settings.ALLOWED_SPF_RECORDS = ['include:allowed_mx.com']
177 182
    monkeypatch.setattr('hobo.emails.validators.validate_email_spf.__defaults__', (True,))
178 183
    client.post('/login/', {'username': 'admin', 'password': 'password'})
......
182 187

  
183 188

  
184 189
def test_valid_spf(client, admin_user, dns_resolver, smtp_server, settings):
190
    settings.EMAIL_FROM_ALLOWED_DOMAINS = ['*']
185 191
    settings.ALLOWED_SPF_RECORDS = ['include:allowed_mx.com']
186 192
    client.post('/login/', {'username': 'admin', 'password': 'password'})
187 193
    response = client.post('/emails/', {'default_from_email': 'john.doe@example-spf.com'}, follow=True)
......
192 198

  
193 199

  
194 200
def test_no_spf_validation(client, admin_user, dns_resolver, smtp_server, settings):
201
    settings.EMAIL_FROM_ALLOWED_DOMAINS = ['*']
195 202
    settings.ALLOWED_SPF_RECORDS = []
196 203
    client.post('/login/', {'username': 'admin', 'password': 'password'})
197 204
    response = client.post(
......
203 210
    )
204 211

  
205 212

  
213
def test_sender_allowed_domains(client, admin_user, dns_resolver, smtp_server, settings):
214
    settings.HOBO_VALIDATE_EMAIL_WITH_SMTP = False
215

  
216
    client.post('/login/', {'username': 'admin', 'password': 'password'})
217
    response = client.post('/emails/', {'default_from_email': 'john.doe@example.com'}, follow=True)
218
    assert response.status_code == 200
219
    assert 'Domain example.com is not allowed' in force_text(response.content)
220
    assert 'Emails settings have been updated.' not in force_text(response.content)
221

  
222
    settings.EMAIL_FROM_ALLOWED_DOMAINS = ['example.com', 'foo.bar']
223
    response = client.post('/emails/', {'default_from_email': 'john.doe@example.com'}, follow=True)
224
    assert response.status_code == 200
225
    assert 'Emails settings have been updated.' in force_text(response.content)
226
    assert 'Domain example.com is not allowed' not in force_text(response.content)
227

  
228
    settings.EMAIL_FROM_ALLOWED_DOMAINS = []
229
    settings.KNOWN_SERVICES = {
230
        'combo': {
231
            'portal': {
232
                'title': 'Portal',
233
                'url': 'https://example.org/test',
234
            },
235
            'other': {
236
                'title': 'Empty',
237
            },
238
        }
239
    }
240
    response = client.post('/emails/', {'default_from_email': 'john.doe@example.org'}, follow=True)
241
    assert response.status_code == 200
242
    assert 'Emails settings have been updated.' in force_text(response.content)
243

  
244
    response = client.post('/emails/', {'default_from_email': 'john.doe@example.com'}, follow=True)
245
    assert response.status_code == 200
246
    assert 'Domain example.com is not allowed' in force_text(response.content)
247

  
248
    response = client.post('/emails/', {'default_from_email': 'john.doe@brother.example.org'}, follow=True)
249
    assert response.status_code == 200
250
    assert 'Domain brother.example.org is not allowed' in force_text(response.content)
251

  
252
    settings.KNOWN_SERVICES['combo']['portal']['url'] = 'https://www.example.com'
253
    response = client.post('/emails/', {'default_from_email': 'john.doe@example.com'}, follow=True)
254
    assert response.status_code == 200
255
    assert 'Emails settings have been updated.' in force_text(response.content)
256

  
257

  
206 258
def test_emails_view(app, admin_user, dns_resolver, smtp_server, settings):
259
    settings.EMAIL_FROM_ALLOWED_DOMAINS = ['*']
207 260
    settings.ALLOWED_SPF_RECORDS = ['include:allowed_mx.com']
208 261
    app = login(app)
209 262
    resp = app.get('/emails/')
210
-