Skip to content

Commit

Permalink
Notify new user if their email domain matches existing Perma partner(…
Browse files Browse the repository at this point in the history
…s) (#3596)

* Initial pass at user email/registrar domain match function

* Implement basic suggested registrars test and fix existing tests

* Use SQL regex pattern to check hostname match

* Support multiple suggested registrars; fix mutable default arg bug

* Fix suggest_registrars so it ignores path after base domain
  • Loading branch information
cmsetzer authored Sep 5, 2024
1 parent a956b98 commit 6bcf8d2
Show file tree
Hide file tree
Showing 3 changed files with 68 additions and 8 deletions.
6 changes: 5 additions & 1 deletion perma_web/perma/templates/email/includes/activation.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
In order to complete your account activation, please confirm your email address and set a password. It’s as simple as clicking the link below. The link will be valid for the next {% widthratio activation_expires 3600 1 %} hours.

{{ activation_route }}
{{ activation_route }}

{% if suggested_registrars %}Note: We’ve also found {% if suggested_registrars|length == 1 %}a Perma.cc partner institution{% else %}some Perma.cc partner institutions{% endif %} with whom you might be affiliated. You may be able to request a sponsored account by contacting them:
{% for suggested_registrar in suggested_registrars %}
- {{ suggested_registrar.name }}: {{ suggested_registrar.email }}{% endfor %}{% endif %}

Thanks,
The Perma.cc Team
29 changes: 28 additions & 1 deletion perma_web/perma/tests/test_views_user_management.py
Original file line number Diff line number Diff line change
Expand Up @@ -2585,7 +2585,9 @@ def check_new_activation_email(self, message, user_email):
self.assertEqual(message.from_email, settings.DEFAULT_FROM_EMAIL)
self.assertEqual(message.recipients(), [user_email])

activation_url = next(line for line in message.body.rstrip().split("\n") if line.startswith('http'))
activation_url = next(
line for line in message.body.rstrip().split('\n') if line.strip().startswith('http')
)
return activation_url

@override_settings(REQUIRE_JS_FORM_SUBMISSIONS=False)
Expand Down Expand Up @@ -2637,6 +2639,31 @@ def test_account_creation_views(self):
response = self.client.post(reverse('user_management_limited_login'), {'username': new_user_raw_email, 'password': 'Anewpass1'}, follow=True, secure=True)
self.assertContains(response, 'Enter any URL to preserve it forever')

@override_settings(REQUIRE_JS_FORM_SUBMISSIONS=False)
def test_suggested_registrars(self):
# Register user
_, registrar_domain = self.registrar.email.split('@')
new_user_email = f'new_user@{registrar_domain}'
self.submit_form(
'sign_up',
{'e-address': new_user_email, 'first_name': 'Test', 'last_name': 'Test'},
success_url=reverse('register_email_instructions'),
success_query=LinkUser.objects.filter(email=new_user_email),
)
self.assertEqual(len(mail.outbox), 1)

# Obtain suggested registrar(s) from activation email message
message = mail.outbox[0]
lines = message.body.splitlines()
captures = []
for line in lines:
if line.lstrip().startswith('- '):
captures.append(line.strip('- '))

# Validate suggested registrar(s)
self.assertEqual(len(captures), 1)
self.assertEqual(captures[0], f'{self.registrar.name}: {self.registrar.email}')

@override_settings(REQUIRE_JS_FORM_SUBMISSIONS=False)
def test_signup_with_existing_email_rejected(self):
self.assertEqual(LinkUser.objects.filter(email__iexact=self.registrar_user.email).count(), 1)
Expand Down
41 changes: 35 additions & 6 deletions perma_web/perma/views/user_management.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from datetime import timedelta
import itertools
import logging
import re
from typing import Literal

import celery
Expand Down Expand Up @@ -1988,7 +1989,26 @@ def firm_request_response(request):
return render(request, 'registration/firm_request.html')


def email_new_user(request, user, template="email/new_user.txt", context={}):
def suggest_registrars(user: LinkUser, limit: int = 5) -> BaseManager[Registrar]:
"""Suggest potential registrars for a user based on email domain.
This queries the database for registrars whose website matches the
base domain from the user's email address. For example, if the
user's email is `[email protected]`, this will suggest
registrars whose domains end with `harvard.edu`.
"""
_, email_domain = user.email.split('@')
base_domain = '.'.join(email_domain.rsplit('.', 2)[-2:])
pattern = f'^https?://([a-zA-Z0-9\\-\\.]+\\.)?{re.escape(base_domain)}(/.*)?$'
registrars = (
Registrar.objects.exclude(status='pending')
.filter(website__iregex=pattern)
.order_by('-link_count', 'name')[:limit]
)
return registrars


def email_new_user(request, user, template='email/new_user.txt', context=None):
"""
Send email to newly created accounts
"""
Expand All @@ -1997,11 +2017,20 @@ def email_new_user(request, user, template="email/new_user.txt", context={}):
urlsafe_base64_encode(force_bytes(user.pk)),
default_token_generator.make_token(user),
]))
context.update({
'activation_route': activation_route,
'activation_expires': settings.PASSWORD_RESET_TIMEOUT,
'request': request
})

# Include context variables
template_is_default = template == 'email/new_user.txt'
context = context if context is not None else {}
context.update(
{
'activation_expires': settings.PASSWORD_RESET_TIMEOUT,
'activation_route': activation_route,
'request': request,
# Only query DB if we're using the default template; otherwise there's no need
'suggested_registrars': suggest_registrars(user) if template_is_default else [],
}
)

send_user_email(
user.raw_email,
template,
Expand Down

0 comments on commit 6bcf8d2

Please sign in to comment.