Skip to content

Commit

Permalink
Add separate ORCiD activate user view (#2339)
Browse files Browse the repository at this point in the history
After we deployed the ORCiD log-in to Healthdata Nexus, we discovered a
bug with SSO (eduGain) registration. To fix that, we need to introduce a
separate activation path for the ORCiD flow so that it does not
interfere with the SSO log-in.
  • Loading branch information
matkaczmarek authored Feb 4, 2025
2 parents 88a42d3 + e91680c commit 247801a
Show file tree
Hide file tree
Showing 6 changed files with 67 additions and 9 deletions.
12 changes: 9 additions & 3 deletions physionet-django/notification/utility.py
Original file line number Diff line number Diff line change
Expand Up @@ -1000,26 +1000,32 @@ def task_rescheduled_notify(name, attempts, last_error, date_time, task_name, ta
mail_admins(subject, body, settings.DEFAULT_FROM_EMAIL)


def notify_account_registration(request, user, uidb64, token, sso=False):
def notify_account_registration(request, user, uidb64, token, activation_type):
"""
Send the registration email.
"""
# Send an email with the activation link
subject = f"{settings.SITE_NAME} Account Activation"
activation_link = _get_activation_link(request, uidb64, token, activation_type)
context = {
'name': user.get_full_name(),
'domain': get_current_site(request),
'url_prefix': get_url_prefix(request),
'uidb64': uidb64,
'token': token,
'sso': sso,
'activation_link': activation_link,
'SITE_NAME': settings.SITE_NAME,
}
body = loader.render_to_string('user/email/register_email.html', context)
# Not resend the email if there was an integrity error
send_mail(subject, body, settings.DEFAULT_FROM_EMAIL, [user.email], fail_silently=False)


def _get_activation_link(request, uidb64, token, activation_type):
url_prefix = get_url_prefix(request)

return f"{url_prefix}{reverse(activation_type.value, kwargs={'uidb64': uidb64, 'token': token})}"


def notify_participant_event_waitlist(request, user, event):
"""
Send the event registration email to participant.
Expand Down
3 changes: 2 additions & 1 deletion physionet-django/sso/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
from notification.utility import notify_account_registration
from physionet.middleware.maintenance import disallow_during_maintenance
from sso import forms
from user.enums import ActivateUserType
from user.models import User

logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -83,7 +84,7 @@ def sso_register(request):
user = form.save()
uidb64 = force_str(urlsafe_base64_encode(force_bytes(user.pk)))
token = default_token_generator.make_token(user)
notify_account_registration(request, user, uidb64, token, sso=True)
notify_account_registration(request, user, uidb64, token, activation_type=ActivateUserType.SSO)
return render(request, 'user/register_done.html', {'email': user.email, 'sso': True})
else:
try:
Expand Down
12 changes: 11 additions & 1 deletion physionet-django/user/enums.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from enum import IntEnum
from enum import IntEnum, Enum

from django.db import models

Expand All @@ -23,3 +23,13 @@ class RequiredField(IntEnum):
@classmethod
def choices(cls):
return tuple((option.value, option.name) for option in cls)


class ActivateUserType(Enum):
DEFAULT = 'activate_user'
SSO = 'sso_activate_user'
ORCID = 'orcid_activate_user'

@classmethod
def choices(cls):
return tuple((option.value, option.name) for option in cls)
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

Thanks for registering for an account on {{ domain }}. Please activate your account by clicking the following activation link or by copying and pasting the link into your web browser:

{{ url_prefix }}{% url sso|yesno:"sso_activate_user,activate_user" uidb64=uidb64 token=token %}
{{ activation_link }}

This link will expire in 3 days or after the account has been activated.

Expand Down
5 changes: 5 additions & 0 deletions physionet-django/user/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,11 @@
path("authorcid_login/", views.auth_orcid_login, name="auth_orcid_login"),
path("orcid_init_login", views.orcid_init_login, name="orcid_init_login"),
path("orcid_register/", views.orcid_register, name="orcid_register"),
re_path(
r"^orcid_activate/(?P<uidb64>[0-9A-Za-z_\-]+)/(?P<token>[0-9A-Za-z]{1,13}-[0-9A-Za-z]{1,32})/$",
views.activate_orcid_user,
name="orcid_activate_user"
),
]
)

Expand Down
42 changes: 39 additions & 3 deletions physionet-django/user/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@
TrainingType,
)
from user.userfiles import UserFiles
from user.enums import RequiredField
from user.enums import RequiredField, ActivateUserType
from physionet.models import StaticPage
from django.db.models import F

Expand Down Expand Up @@ -601,7 +601,7 @@ def orcid_register(request):
user = form.save()
uidb64 = force_str(urlsafe_base64_encode(force_bytes(user.pk)))
token = default_token_generator.make_token(user)
notify_account_registration(request, user, uidb64, token, sso=settings.ENABLE_SSO)
notify_account_registration(request, user, uidb64, token, activation_type=ActivateUserType.ORCID)

return render(
request, 'user/register_done.html', {'email': user.email, 'sso': settings.ENABLE_SSO}
Expand Down Expand Up @@ -630,6 +630,42 @@ def orcid_init_login(request):
)


@disallow_during_maintenance
def activate_orcid_user(request, uidb64, token):
"""Orcid Registration view
This view activates user and initiate orcid log in flow automatically.
"""
if not settings.ORCID_LOGIN_ENABLED:
return redirect('home')

context = {'title': 'Invalid Activation Link', 'isvalid': False}

try:
uid = force_str(urlsafe_base64_decode(uidb64))
user = User.objects.get(pk=uid)
except (TypeError, ValueError, OverflowError, User.DoesNotExist):
user = None

if user and user.is_active:
messages.success(request, 'The account is active.')
return redirect('sso_login')

if default_token_generator.check_token(user, token):
with transaction.atomic():
user.is_active = True
user.save()
email = user.associated_emails.first()
email.verification_date = timezone.now()
email.is_verified = True
email.save()
logger.info('User activated - {0}'.format(user.email))
messages.success(request, 'The account has been activated.')

return redirect('orcid_init_login')

return render(request, 'user/activate_user_complete.html', context)


@login_required
def edit_password_complete(request):
Expand Down Expand Up @@ -697,7 +733,7 @@ def register(request):
uidb64 = force_str(urlsafe_base64_encode(force_bytes(
user.pk)))
token = default_token_generator.make_token(user)
notify_account_registration(request, user, uidb64, token)
notify_account_registration(request, user, uidb64, token, activation_type=ActivateUserType.DEFAULT)

return render(request, 'user/register_done.html', {
'email': user.email})
Expand Down

0 comments on commit 247801a

Please sign in to comment.