Move handling of secondary email addresses upstream
authorMagnus Hagander <magnus@hagander.net>
Tue, 11 Aug 2020 11:14:55 +0000 (13:14 +0200)
committerMagnus Hagander <magnus@hagander.net>
Tue, 11 Aug 2020 12:04:24 +0000 (14:04 +0200)
Since the upstream main website now handles secondary email addresses,
centralize the handling to there. This removes the local handling
completely, except that we store them in the database. The new
push-changes API ensures that they are kept in sync with upstream.

pgcommitfest/commitfest/apps.py [new file with mode: 0644]
pgcommitfest/settings.py
pgcommitfest/urls.py
pgcommitfest/userprofile/forms.py
pgcommitfest/userprofile/migrations/0003_emails_managed_upstream.py [new file with mode: 0644]
pgcommitfest/userprofile/models.py
pgcommitfest/userprofile/templates/extra_email_mail.txt [deleted file]
pgcommitfest/userprofile/templates/userprofileform.html
pgcommitfest/userprofile/util.py
pgcommitfest/userprofile/views.py

diff --git a/pgcommitfest/commitfest/apps.py b/pgcommitfest/commitfest/apps.py
new file mode 100644 (file)
index 0000000..e47efed
--- /dev/null
@@ -0,0 +1,11 @@
+from django.apps import AppConfig
+
+
+class CFAppConfig(AppConfig):
+    name = 'pgcommitfest.commitfest'
+
+    def ready(self):
+        from pgcommitfest.auth import auth_user_data_received
+        from pgcommitfest.userprofile.util import handle_user_data
+
+        auth_user_data_received.connect(handle_user_data)
index 3df6c0595576ccc6c5972a86171bc3541d1ce07d..46c6b985ee9eecab3fcc77518bdecf9a3f24e0ac 100644 (file)
@@ -123,7 +123,7 @@ INSTALLED_APPS = (
     # Uncomment the next line to enable admin documentation:
     # 'django.contrib.admindocs',
     'pgcommitfest.selectable',
-    'pgcommitfest.commitfest',
+    'pgcommitfest.commitfest.apps.CFAppConfig',
     'pgcommitfest.mailqueue',
     'pgcommitfest.userprofile',
 )
index ec013f44ef5f8e17f9047e674fd170d95b1788ab..6cdc21f73e59526f84ffb38de35bc84c83db75f9 100644 (file)
@@ -44,8 +44,6 @@ urlpatterns = [
 
     # Account management
     url(r'^account/profile/$', pgcommitfest.userprofile.views.userprofile),
-    url(r'^account/profile/delmail/$', pgcommitfest.userprofile.views.deletemail),
-    url(r'^account/profile/confirm/([0-9a-f]+)/$', pgcommitfest.userprofile.views.confirmemail),
 
     # Examples:
     # url(r'^$', 'pgpgcommitfest.commitfest.views.home', name='home),
index 66d68d20c3666d50c95b3ca14e86ef8463843ab6..35d74bd50288f6005f9080d9312c2ca36e2d67db 100644 (file)
@@ -1,5 +1,4 @@
 from django import forms
-from django.contrib.auth.models import User
 
 from .models import UserProfile, UserExtraEmail
 
@@ -13,33 +12,11 @@ class UserProfileForm(forms.ModelForm):
         super(UserProfileForm, self).__init__(*args, **kwargs)
         self.user = user
 
+        mailhelp = "To add a new address to choose from, update your user profile on <a href=\"https://www.postgresql.org/account/profile/\">postgresql.org</a>."
+
         self.fields['selectedemail'].empty_label = self.user.email
-        self.fields['selectedemail'].queryset = UserExtraEmail.objects.filter(user=self.user, confirmed=True)
+        self.fields['selectedemail'].queryset = UserExtraEmail.objects.filter(user=self.user)
+        self.fields['selectedemail'].help_text = mailhelp
         self.fields['notifyemail'].empty_label = self.user.email
-        self.fields['notifyemail'].queryset = UserExtraEmail.objects.filter(user=self.user, confirmed=True)
-
-
-class MailForm(forms.Form):
-    email = forms.EmailField()
-    email2 = forms.EmailField(label="Repeat email")
-
-    def clean_email(self):
-        email = self.cleaned_data['email']
-
-        if User.objects.filter(email=email).exists():
-            raise forms.ValidationError("This email is already in use by another account")
-
-        return email
-
-    def clean_email2(self):
-        # If the primary email checker had an exception, the data will be gone
-        # from the cleaned_data structure
-        if 'email' not in self.cleaned_data:
-            return self.cleaned_data['email2']
-        email1 = self.cleaned_data['email']
-        email2 = self.cleaned_data['email2']
-
-        if email1 != email2:
-            raise forms.ValidationError("Email addresses don't match")
-
-        return email2
+        self.fields['notifyemail'].queryset = UserExtraEmail.objects.filter(user=self.user)
+        self.fields['notifyemail'].help_text = mailhelp
diff --git a/pgcommitfest/userprofile/migrations/0003_emails_managed_upstream.py b/pgcommitfest/userprofile/migrations/0003_emails_managed_upstream.py
new file mode 100644 (file)
index 0000000..165b6e4
--- /dev/null
@@ -0,0 +1,36 @@
+# Generated by Django 2.2.11 on 2020-08-11 11:09
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('userprofile', '0002_notifications'),
+    ]
+
+    operations = [
+        migrations.RemoveField(
+            model_name='userextraemail',
+            name='confirmed',
+        ),
+        migrations.RemoveField(
+            model_name='userextraemail',
+            name='token',
+        ),
+        migrations.RemoveField(
+            model_name='userextraemail',
+            name='tokensent',
+        ),
+        migrations.AlterField(
+            model_name='userprofile',
+            name='notifyemail',
+            field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='notifier', to='userprofile.UserExtraEmail', verbose_name='Notifications sent to'),
+        ),
+        migrations.AlterField(
+            model_name='userprofile',
+            name='selectedemail',
+            field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='userprofile.UserExtraEmail', verbose_name='Sender email'),
+        ),
+    ]
index ea1cae9749f9388d34fd33a58214580452f95e5a..79da68882dc97248fb8de4d026fa8146f000d319 100644 (file)
@@ -5,9 +5,6 @@ from django.contrib.auth.models import User
 class UserExtraEmail(models.Model):
     user = models.ForeignKey(User, null=False, blank=False, db_index=True, on_delete=models.CASCADE)
     email = models.EmailField(max_length=100, null=False, blank=False, unique=True)
-    confirmed = models.BooleanField(null=False, blank=False, default=False)
-    token = models.CharField(max_length=100, null=False, blank=True)
-    tokensent = models.DateTimeField(null=False, blank=False)
 
     def __str__(self):
         return self.email
@@ -20,10 +17,10 @@ class UserExtraEmail(models.Model):
 class UserProfile(models.Model):
     user = models.OneToOneField(User, null=False, blank=False, on_delete=models.CASCADE)
     selectedemail = models.ForeignKey(UserExtraEmail, null=True, blank=True,
-                                      verbose_name='Sender email', on_delete=models.CASCADE)
+                                      verbose_name='Sender email', on_delete=models.SET_NULL)
     notifyemail = models.ForeignKey(UserExtraEmail, null=True, blank=True,
                                     verbose_name='Notifications sent to',
-                                    related_name='notifier', on_delete=models.CASCADE)
+                                    related_name='notifier', on_delete=models.SET_NULL)
     notify_all_author = models.BooleanField(null=False, blank=False, default=False, verbose_name="Notify on all where author")
     notify_all_reviewer = models.BooleanField(null=False, blank=False, default=False, verbose_name="Notify on all where reviewer")
     notify_all_committer = models.BooleanField(null=False, blank=False, default=False, verbose_name="Notify on all where committer")
diff --git a/pgcommitfest/userprofile/templates/extra_email_mail.txt b/pgcommitfest/userprofile/templates/extra_email_mail.txt
deleted file mode 100644 (file)
index 34c93cc..0000000
+++ /dev/null
@@ -1,7 +0,0 @@
-Somebody, probably you, has registered this email address as a secondary
-address for the account {{user.username}} on commitfest.postgresql.org.
-
-To confirm this addition, please click on the following link:
-
-https://commitfest.postgresql.org/account/profile/confirm/{{token}}/
-
index da8b73446c1b7dd9c04856bcf3194f0a850c8615..ed5e2638358a9abe1e647d2a76d4820304f92eea 100644 (file)
  </div>
 </form>
 
-<h2>Extra email addresses</h2>
-<p>
-The following extra email addresses are registered for your account:
-</p>
-<ul>
-{%for e in extramails%}
- <li>{{e.email}}{%if not e.confirmed%} (<i>Pending confirmation</i>){%endif%} <a href="delmail/?{{e.id}}">delete</a></li>
-{%endfor%}
-</ul>
-
-<h3>Add email</h3>
-<form class="form-horizontal" method="post" action=".">{%csrf_token%}
-{%if mailform.errors%}
- <div class="alert">Please correct the errors below, and re-submit the form.</div>
-{%endif%}
-{%if mailform.non_field_errors%}
- <div class="alert alert-danger">{{mailform.non_field_errors}}</div>
-{%endif%}
- {%for field in mailform%}
- <div class="form-group">
-   {{field|label_class:"control-label col-lg-1"}}
-   <div class="col-lg-11 controls">
-  {%if field.errors %}
-   {%for e in field.errors%}
- <div class="alert alert-danger">{{e}}</div>
-   {%endfor%}
-  {%endif%}
-{{field|field_class:"form-control"}}
-{%if field.help_text%}<br/>{{field.help_text|safe}}{%endif%}</div>
- </div>
-{%endfor%}
-
- <div class="form-group">
-  <div class="col-lg-12">
-   <div class="control"><input type="submit" class="btn btn-default" name="submit" value="Add email"></div>
-  </div>
- </div>
-</form>
 {%endblock%}
index e08aa66b7d6df09118493b5a63883a99719c080a..92359b7ab974fad5f4a205e626bccd4a6f649a9c 100644 (file)
@@ -1,21 +1,7 @@
-from Crypto.Hash import SHA256
-from Crypto import Random
 from email.utils import formataddr
 from email.header import Header
 
-from .models import UserProfile
-
-
-def generate_random_token():
-    """
-    Generate a random token of 64 characters. This token will be
-    generated using a strong random number, and then hex encoded to make
-    sure all characters are safe to put in emails and URLs.
-    """
-    s = SHA256.new()
-    r = Random.new()
-    s.update(r.read(250))
-    return s.hexdigest()
+from .models import UserProfile, UserExtraEmail
 
 
 class UserWrapper(object):
@@ -26,7 +12,7 @@ class UserWrapper(object):
     def email(self):
         try:
             up = UserProfile.objects.get(user=self.user)
-            if up.selectedemail and up.selectedemail.confirmed:
+            if up.selectedemail:
                 return up.selectedemail.email
             else:
                 return self.user.email
@@ -38,3 +24,19 @@ class UserWrapper(object):
         return formataddr((
             str(Header("%s %s" % (self.user.first_name, self.user.last_name), 'utf-8')),
             self.email))
+
+
+def handle_user_data(sender, **kwargs):
+    user = kwargs.pop('user')
+    userdata = kwargs.pop('userdata')
+
+    secondary = userdata.get('secondaryemails', [])
+
+    # Remove any email attached to this user that are not upstream. Since the foreign keys
+    # are set to SET_NULL, they will all revert to being the users default in this case.
+    UserExtraEmail.objects.filter(user=user).exclude(email__in=secondary).delete()
+
+    # Then add back any of the ones that aren't there
+    current = set([e.email for e in UserExtraEmail.objects.filter(user=user)])
+    for e in set(secondary).difference(current):
+        UserExtraEmail(user=user, email=e).save()
index 577f94abca3d293aa097f5c4cdec917c033e0aa4..4c22bac0326ba97c47d48fcbf886daeb25f79676 100644 (file)
 from django.shortcuts import render
 from django.http import HttpResponseRedirect
-from django.template import RequestContext
 from django.db import transaction
 from django.contrib import messages
 from django.contrib.auth.decorators import login_required
-from django.conf import settings
 
-from datetime import datetime
-
-from pgcommitfest.mailqueue.util import send_template_mail
-
-from .models import UserProfile, UserExtraEmail
-from .forms import UserProfileForm, MailForm
-from .util import generate_random_token
+from .models import UserProfile
+from .forms import UserProfileForm
 
 
 @login_required
 @transaction.atomic
 def userprofile(request):
     (profile, created) = UserProfile.objects.get_or_create(user=request.user)
-    form = mailform = None
 
     if request.method == 'POST':
-        if request.POST['submit'] == 'Save':
-            form = UserProfileForm(request.user, request.POST, instance=profile)
-            if form.is_valid():
-                form.save()
-                messages.add_message(request, messages.INFO, "User profile saved.")
-                return HttpResponseRedirect('.')
-        elif request.POST['submit'] == 'Add email':
-            mailform = MailForm(request.POST)
-            if mailform.is_valid():
-                m = UserExtraEmail(user=request.user,
-                                   email=mailform.cleaned_data['email'],
-                                   confirmed=False,
-                                   token=generate_random_token(),
-                                   tokensent=datetime.now())
-                m.save()
-                send_template_mail(settings.NOTIFICATION_FROM,
-                                   request.user.username,
-                                   m.email,
-                                   'Your email address for commitfest.postgresql.org',
-                                   'extra_email_mail.txt',
-                                   {'token': m.token, 'user': m.user})
-                messages.info(request, "A confirmation token has been sent to %s" % m.email)
-                return HttpResponseRedirect('.')
-        else:
-            messages.error(request, "Invalid submit button pressed! Nothing saved.")
+        form = UserProfileForm(request.user, request.POST, instance=profile)
+        if form.is_valid():
+            form.save()
+            messages.add_message(request, messages.INFO, "User profile saved.")
             return HttpResponseRedirect('.')
-
-    if not form:
+    else:
         form = UserProfileForm(request.user, instance=profile)
-    if not mailform:
-        mailform = MailForm()
-
-    extramails = UserExtraEmail.objects.filter(user=request.user)
 
     return render(request, 'userprofileform.html', {
         'form': form,
-        'extramails': extramails,
-        'mailform': mailform,
     })
-
-
-@login_required
-@transaction.atomic
-def deletemail(request):
-    try:
-        id = int(request.META['QUERY_STRING'])
-    except ValueError:
-        messages.error(request, "Invalid format of id in query string")
-        return HttpResponseRedirect('../')
-
-    try:
-        e = UserExtraEmail.objects.get(user=request.user, id=id)
-    except UserExtraEmail.DoesNotExist:
-        messages.error(request, "Specified email address does not exist on this user")
-        return HttpResponseRedirect('../')
-
-    messages.info(request, "Email address %s deleted." % e.email)
-    e.delete()
-    return HttpResponseRedirect('../')
-
-
-@login_required
-@transaction.atomic
-def confirmemail(request, tokenhash):
-    try:
-        e = UserExtraEmail.objects.get(user=request.user, token=tokenhash)
-        if e.confirmed:
-            messages.warning(request, "This email address has already been confirmed.")
-        else:
-            # Ok, it's not confirmed. So let's do that now
-            e.confirmed = True
-            e.token = ''
-            e.save()
-            messages.info(request, "Email address %s added to profile." % e.email)
-    except UserExtraEmail.DoesNotExist:
-        messages.error(request, "Token %s was not found for your user. It may be because it has already been used?" % tokenhash)
-
-    return HttpResponseRedirect("../../")