Commit 5556dac7 authored by Raphaël Hertzog's avatar Raphaël Hertzog

Properly handle ValidationError where we create UserEmail objects

parent 4dca9600
......@@ -9,6 +9,7 @@
# except according to the terms contained in the LICENSE file.
"""Models for the :mod:`distro_tracker.core` app."""
import hashlib
import logging
import os
import random
import re
......@@ -65,6 +66,8 @@ from django_email_accounts.models import UserEmail
DISTRO_TRACKER_CONFIRMATION_EXPIRATION_DAYS = \
settings.DISTRO_TRACKER_CONFIRMATION_EXPIRATION_DAYS
logger_input = logging.getLogger('distro_tracker.input')
class Keyword(models.Model):
"""
......@@ -1662,12 +1665,16 @@ class News(models.Model):
signed_by = []
for name, email in signers:
signer_email, _ = UserEmail.objects.get_or_create(
email=email)
signer_name, _ = ContributorName.objects.get_or_create(
name=name,
contributor_email=signer_email)
signed_by.append(signer_name)
try:
signer_email, _ = UserEmail.objects.get_or_create(
email=email)
signer_name, _ = ContributorName.objects.get_or_create(
name=name,
contributor_email=signer_email)
signed_by.append(signer_name)
except ValidationError:
logger_input.warning('News "%s" has signature with '
'invalid email (%s).', self.title, email)
self.signed_by.set(signed_by)
......
......@@ -15,6 +15,7 @@ import re
from debian import deb822
from django.conf import settings
from django.core.exceptions import ValidationError
from django.db import models, transaction
import requests
......@@ -53,6 +54,7 @@ from distro_tracker.core.utils.packages import (
)
logger = logging.getLogger('distro_tracker.tasks')
logger_input = logging.getLogger('distro_tracker.input')
class InvalidRepositoryException(Exception):
......@@ -239,44 +241,61 @@ class UpdateRepositoriesTask(BaseTask):
entry['binary_packages'] = binaries
if 'maintainer' in entry:
maintainer_email, _ = UserEmail.objects.get_or_create(
email=entry['maintainer']['email'])
maintainer = ContributorName.objects.get_or_create(
contributor_email=maintainer_email,
name=entry['maintainer'].get('name', ''))[0]
entry['maintainer'] = maintainer
try:
maintainer_email, _ = UserEmail.objects.get_or_create(
email=entry['maintainer']['email'])
maintainer = ContributorName.objects.get_or_create(
contributor_email=maintainer_email,
name=entry['maintainer'].get('name', ''))[0]
entry['maintainer'] = maintainer
except ValidationError:
email = entry['maintainer']['email']
logger_input.warning(
'Invalid email in maintainer field of %s: %s',
src_pkg, email)
del entry['maintainer']
if 'uploaders' in entry:
uploader_emails = [
uploader['email']
for uploader in entry['uploaders']
]
uploader_names = [
uploader.get('name', '')
for uploader in entry['uploaders']
]
existing_contributor_emails_qs = UserEmail.objects.filter(
email__in=uploader_emails)
existing_contributor_emails = {
contributor.email: contributor
for contributor in existing_contributor_emails_qs
}
uploaders = []
for email, name in zip(uploader_emails, uploader_names):
if email not in existing_contributor_emails:
self._process_uploaders(entry, src_pkg)
return entry
def _process_uploaders(self, entry, src_pkg):
uploader_emails = [
uploader['email']
for uploader in entry['uploaders']
]
uploader_names = [
uploader.get('name', '')
for uploader in entry['uploaders']
]
existing_contributor_emails_qs = UserEmail.objects.filter(
email__in=uploader_emails)
existing_contributor_emails = {
contributor.email: contributor
for contributor in existing_contributor_emails_qs
}
uploaders = []
for email, name in zip(uploader_emails, uploader_names):
if email not in existing_contributor_emails:
try:
contributor_email, _ = UserEmail.objects.get_or_create(
email=email)
existing_contributor_emails[email] = contributor_email
else:
contributor_email = existing_contributor_emails[email]
except ValidationError:
contributor_email = None
logger_input.warning(
'Bad email in uploaders in %s for %s: %s',
src_pkg, name, email)
else:
contributor_email = existing_contributor_emails[email]
if contributor_email:
uploaders.append(ContributorName.objects.get_or_create(
contributor_email=contributor_email,
name=name)[0]
)
entry['uploaders'] = uploaders
return entry
entry['uploaders'] = uploaders
def _extract_information_from_packages_entry(self, bin_pkg, stanza):
entry = extract_information_from_packages_entry(stanza)
......
......@@ -11,6 +11,7 @@
import sys
from django.core.exceptions import ValidationError
from django.core.management.base import BaseCommand
from django.db import transaction
......@@ -67,13 +68,19 @@ class Command(BaseCommand):
package__name=package,
email_settings__user_email__email=email)
except Subscription.DoesNotExist:
self.write(f'Skip non-existing subscription '
f'({email} to {package}).\n')
continue
subscription.keywords.clear()
for keyword in keywords:
subscription.keywords.add(keyword)
else:
# User default keywords
user_email, _ = UserEmail.objects.get_or_create(email=email)
try:
user_email, _ = UserEmail.objects.get_or_create(email=email)
except ValidationError:
self.write(f'Skip invalid email {email}.\n')
continue
email_settings, _ = \
EmailSettings.objects.get_or_create(user_email=user_email)
email_settings.default_keywords.set(keywords)
......@@ -10,16 +10,19 @@
"""Authentication with the Debian SSO service."""
import json
import logging
from django.contrib import auth
from django.contrib.auth.backends import RemoteUserBackend
from django.contrib.auth.middleware import RemoteUserMiddleware
from django.core.exceptions import ImproperlyConfigured
from django.core.exceptions import ImproperlyConfigured, ValidationError
from django.utils.http import urlencode
from distro_tracker.accounts.models import User, UserEmail
from distro_tracker.core.utils.http import get_resource_content
logger = logging.getLogger(__name__)
class DebianSsoUserMiddleware(RemoteUserMiddleware):
"""
......@@ -111,7 +114,13 @@ class DebianSsoUserBackend(RemoteUserBackend):
email = remote_user
user_email, _ = UserEmail.objects.get_or_create(email=email)
try:
user_email, _ = UserEmail.objects.get_or_create(email=email)
except ValidationError:
logger.error('remote_user="%s" is not a valid email.',
remote_user)
return
if not user_email.user:
kwargs = {}
names = self.get_user_details(remote_user)
......
......@@ -559,6 +559,23 @@ class RetrieveLowThresholdNmuTest(TestCase):
# The Debian developer is no longer in the list of low threshold nmu
self.assertFalse(d.agree_with_low_threshold_nmu)
@mock.patch('distro_tracker.core.utils.http.requests')
def test_developer_bad_email(self, mock_requests):
"""
Ensure the task deals properly with a bad input email.
"""
set_mock_response(mock_requests,
"Text text text\n"
"text more text...\n"
" 1. [[DeveloperName|Name]] - "
"([[https://qa.debian.org/developer.php?"
"login=foobar@|all packages]])\n")
self.assertTrue(run_task(RetrieveLowThresholdNmuTask))
# Nothing created due to bad data
self.assertEqual(DebianContributor.objects.count(), 0)
class RetrieveDebianMaintainersTest(TestCase):
......@@ -663,6 +680,23 @@ class RetrieveDebianMaintainersTest(TestCase):
# The developer is no longer a debian maintainer
self.assertFalse(d.is_debian_maintainer)
@mock.patch('distro_tracker.core.utils.http.requests')
def test_bad_developer_email(self, mock_requests):
"""
Ensure that the task deals properly with invalid emails
in the input data.
"""
set_mock_response(
mock_requests,
"Fingerprint: CFC5B232C0D082CAE6B3A166F04CEFF6016CFFD0\n"
"Uid: Dummy Developer <bad-email>\n"
"Allow: dummy-package (709F54E4ECF3195623326AE3F82E5CC04B2B2B9E),\n"
" second-package (709F54E4ECF3195623326AE3F82E5CC04B2B2B9E)\n")
self.assertTrue(run_task(RetrieveDebianMaintainersTask))
self.assertFalse(UserEmail.objects.filter(email='bad-email').exists())
class DebianContributorExtraTest(TestCase):
......@@ -5119,6 +5153,11 @@ class DebianSsoLoginTests(TestCase):
user = auth_backend.authenticate(remote_user=self.DD_EMAIL)
self.assertIsInstance(user, User) # from distro_tracker.accounts.models
def test_authenticate_handles_invalid_email(self, get_user_details):
auth_backend = DebianSsoUserBackend()
user = auth_backend.authenticate(remote_user='foobar')
self.assertIsNone(user)
class DebianSsoLoginWithSSLClientCertificateTests(DebianSsoLoginTests):
"""
......
# Copyright 2013-2018 The Distro Tracker Developers
# Copyright 2013-2019 The Distro Tracker Developers
# See the COPYRIGHT file at the top-level directory of this distribution and
# at https://deb.li/DTAuthors
#
......@@ -28,6 +28,7 @@ from debian.debian_support import AptPkgVersion
import debianbts
from django.conf import settings
from django.core.exceptions import ValidationError
from django.db import transaction
from django.db.models import Prefetch
from django.utils.http import urlencode
......@@ -70,6 +71,7 @@ from distro_tracker.vendor.debian.models import (
from .models import DebianContributor
logger = logging.getLogger(__name__)
logger_input = logging.getLogger('distro_tracker.input')
class RetrieveDebianMaintainersTask(BaseTask):
......@@ -110,9 +112,15 @@ class RetrieveDebianMaintainersTask(BaseTask):
qs.update(is_debian_maintainer=False)
for email, packages in maintainers.items():
email, _ = UserEmail.objects.get_or_create(email=email)
try:
user_email, _ = UserEmail.objects.get_or_create(email=email)
except ValidationError:
logger_input.info('%s refers to invalid email "%s".',
url, email)
continue
contributor, _ = DebianContributor.objects.get_or_create(
email=email)
email=user_email)
contributor.is_debian_maintainer = True
contributor.allowed_packages = packages
......@@ -165,8 +173,15 @@ class RetrieveLowThresholdNmuTask(BaseTask):
qs = DebianContributor.objects.filter(
agree_with_low_threshold_nmu=True)
qs.update(agree_with_low_threshold_nmu=False)
for email in emails:
email, _ = UserEmail.objects.get_or_create(email=email)
try:
email, _ = UserEmail.objects.get_or_create(email=email)
except ValidationError:
logger_input.info(
'LowThresholdNmu refers to invalid email "%s".', email)
continue
contributor, _ = DebianContributor.objects.get_or_create(
email=email)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment