Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

3899 register account with next #4322

Draft
wants to merge 4 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 8 additions & 17 deletions src/core/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ class AccountAdmin(UserAdmin):
admin_utils.RepositoryRoleInline,
admin_utils.EditorialGroupMemberInline,
admin_utils.StaffGroupMemberInline,
admin_utils.PasswordResetInline,
admin_utils.AccountTokenInline,
]

def _roles_in(self, obj):
Expand All @@ -100,16 +100,6 @@ class RoleAdmin(admin.ModelAdmin):
search_fields = ('slug', 'name',)


class PasswordResetAdmin(admin.ModelAdmin):
"""Displays Password Reset Data"""
list_display = ('account', 'expiry', 'expired')
search_fields = ('account__first_name', 'account__last_name',
'account__orcid', 'account__email')
list_filter = ('expired', 'expiry')
raw_id_fields = ('account',)
date_hierarchy = ('expiry')


class SettingValueAdmin(admin.ModelAdmin):
list_display = ('_setting_name', 'value', 'journal')
list_filter = ('journal', )
Expand Down Expand Up @@ -281,10 +271,12 @@ def _workflow_element(self, obj):
return obj.element.element_name if obj else ''


class OrcidTokenAdmin(admin.ModelAdmin):
list_display = ('token', 'orcid', 'expiry')
list_filter = ('expiry', )
search_fields = ('orcid', )
class AccountTokenAdmin(admin.ModelAdmin):
list_display = ('account', 'token', 'identifier', 'expiry')
list_filter = ('expired', 'expiry')
search_fields = ('account__first_name', 'account__last_name',
'account__email', 'token', 'identifier')
raw_id_fields = ('account',)
date_hierarchy = ('expiry')


Expand Down Expand Up @@ -407,8 +399,7 @@ class AccessRequestAdmin(admin.ModelAdmin):
(models.Galley, GalleyAdmin),
(models.EditorialGroup, EditorialGroupAdmin),
(models.EditorialGroupMember, EditorialMemberAdmin),
(models.PasswordResetToken, PasswordResetAdmin),
(models.OrcidToken, OrcidTokenAdmin),
(models.AccountToken, AccountTokenAdmin),
(models.DomainAlias, DomainAliasAdmin),
(models.Country, CountryAdmin),
(models.WorkflowElement, WorkflowElementAdmin),
Expand Down
2 changes: 1 addition & 1 deletion src/core/forms/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -163,10 +163,10 @@ def save(self, commit=True):
user = super(RegistrationForm, self).save(commit=False)
user.set_password(self.cleaned_data["password_1"])
user.is_active = False
user.confirmation_code = uuid.uuid4()
user.email_sent = timezone.now()

if commit:
models.AccountToken.objects.create(account=self)
user.save()
if self.cleaned_data.get('register_as_reader') and self.journal:
user.add_account_role(
Expand Down
2 changes: 1 addition & 1 deletion src/core/locales/cy/LC_MESSAGES/django.po
Original file line number Diff line number Diff line change
Expand Up @@ -393,7 +393,7 @@ msgstr ""

#: src/core/views.py:355
msgid ""
"Your account has been created, please follow theinstructions in the email "
"Your account has been created. Please follow theinstructions in the email "
"that has been sent to you."
msgstr ""

Expand Down
2 changes: 1 addition & 1 deletion src/core/locales/de/LC_MESSAGES/django.po
Original file line number Diff line number Diff line change
Expand Up @@ -382,7 +382,7 @@ msgstr "Falls Ihr Konto gefunden wurde, wurde Ihnen eine E-Mail geschickt."

#: src/core/views.py:355
msgid ""
"Your account has been created, please follow theinstructions in the email "
"Your account has been created. Please follow the instructions in the email "
"that has been sent to you."
msgstr ""
"Ihr Konto wurde angelegt. Bitte folgen Sie den Anweisungen aus der E-Mail, "
Expand Down
2 changes: 1 addition & 1 deletion src/core/locales/en_us/LC_MESSAGES/django.po
Original file line number Diff line number Diff line change
Expand Up @@ -374,7 +374,7 @@ msgstr ""

#: src/core/views.py:355
msgid ""
"Your account has been created, please follow theinstructions in the email "
"Your account has been created. Please follow the instructions in the email "
"that has been sent to you."
msgstr ""

Expand Down
4 changes: 3 additions & 1 deletion src/core/locales/es/LC_MESSAGES/django.po
Original file line number Diff line number Diff line change
Expand Up @@ -4440,7 +4440,9 @@ msgid "\n"
msgstr ""

#: src/core/views.py:355
msgid "Your account has been created, please follow theinstructions in the email that has been sent to you."
msgid ""
"Your account has been created. Please follow the instructions in the email "
"that has been sent to you."
msgstr ""

#: src/core/views.py:400
Expand Down
2 changes: 1 addition & 1 deletion src/core/locales/fr/LC_MESSAGES/django.po
Original file line number Diff line number Diff line change
Expand Up @@ -396,7 +396,7 @@ msgstr ""

#: src/core/views.py:355
msgid ""
"Your account has been created, please follow theinstructions in the email "
"Your account has been created. Please follow the instructions in the email "
"that has been sent to you."
msgstr ""

Expand Down
2 changes: 1 addition & 1 deletion src/core/locales/it/LC_MESSAGES/django.po
Original file line number Diff line number Diff line change
Expand Up @@ -395,7 +395,7 @@ msgstr ""

#: src/core/views.py:355
msgid ""
"Your account has been created, please follow theinstructions in the email "
"Your account has been created. Please follow the instructions in the email "
"that has been sent to you."
msgstr ""

Expand Down
2 changes: 1 addition & 1 deletion src/core/locales/nl/LC_MESSAGES/django.po
Original file line number Diff line number Diff line change
Expand Up @@ -389,7 +389,7 @@ msgstr ""

#: src/core/views.py:355
msgid ""
"Your account has been created, please follow theinstructions in the email "
"Your account has been created. Please follow the instructions in the email "
"that has been sent to you."
msgstr ""

Expand Down
128 changes: 76 additions & 52 deletions src/core/logic.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,14 @@
import operator
import re
from functools import reduce
from urllib.parse import unquote, urlparse

from django.conf import settings
from django.contrib.auth import logout
from django.contrib import messages
from django.template.loader import get_template
from django.db.models import Q
from django.http import JsonResponse
from django.http import JsonResponse, QueryDict
from django.forms.models import model_to_dict
from django.shortcuts import reverse
from django.utils import timezone
Expand Down Expand Up @@ -47,69 +48,87 @@ def send_reset_token(request, reset_token):
'core_reset_password_url': core_reset_password_url,
}
log_dict = {'level': 'Info', 'types': 'Reset Token', 'target': None}
if not request.journal:
message = render_template.get_message_content(
request,
context,
request.press.password_reset_text,
template_is_setting=True,
)
else:
message = render_template.get_message_content(
request,
context,
'password_reset',
)

subject = 'subject_password_reset'

notify_helpers.send_email_with_body_from_user(
notify_helpers.send_email_with_body_from_setting_template(
request,
subject,
'password_reset',
'subject_password_reset',
reset_token.account.email,
message,
context,
log_dict=log_dict,
)


def send_confirmation_link(request, new_user):
core_confirm_account_url = request.site_type.site_url(
def reverse_with_next(url_name, request, next_url='', args=None, kwargs=None):
"""
Reverse a URL but keep the 'next' parameter that exists on the request
or that the caller wants to introduce.
The value of 'next' can be in raw unicode or in an encoded form.
:param request: HttpRequest, optionally containing 'next' in GET or POST
:param next_url: an optional string with the path and query parts of
a destination URL -- overrides any 'next' in request data
:param args: args to pass to django.shortcuts.reverse, if no kwargs
:param kwargs: kwargs to pass to django.shortcuts.reverse, if no args
"""
# reverse can only except either args or kwargs
if args:
reversed_url = reverse(url_name, args=args)
elif kwargs:
reversed_url = reverse(url_name, kwargs=kwargs)
else:
reversed_url = reverse(url_name)

if not next_url:
next_url = request.GET.get('next', '') or request.POST.get('next', '')

if not next_url:
return reversed_url

# Parse the reversed URL string enough to safely update the query parameters.
# Then re-encode them into a query string and generate the final URL.
next_url = unquote(next_url)
parsed_url = urlparse(reversed_url)
parsed_query = QueryDict(parsed_url.query, mutable=True)
parsed_query.update({'next': next_url})
# We treat / as safe to match the default behavior
# of the |urlencode template filter
new_query_string = parsed_query.urlencode(safe="/")
return parsed_url._replace(query=new_query_string).geturl()


def get_confirm_account_url(request, user):
return request.site_type.site_url(
reverse(
'core_confirm_account',
kwargs={'token': new_user.confirmation_code},
kwargs={'token': user.confirmation_code},
)
)


def send_confirmation_link(request, new_user):
core_confirm_account_url = get_confirm_account_url(request, new_user)
if request.journal:
site_name = request.journal.name
elif request.repository:
site_name = request.repository.name
else:
site_name = request.press.name
context = {
'user': new_user,
'site_name': site_name,
'core_confirm_account_url': core_confirm_account_url,
}
if not request.journal:
message = render_template.get_message_content(
request,
context,
request.press.registration_text,
template_is_setting=True,
)
else:
message = render_template.get_message_content(
request,
context,
'new_user_registration',
)

subject = 'subject_new_user_registration'

notify_helpers.send_slack(
request,
'New registration: {0}'.format(new_user.full_name()),
['slack_admins'],
)
log_dict = {'level': 'Info', 'types': 'Account Confirmation', 'target': None}
notify_helpers.send_email_with_body_from_user(
notify_helpers.send_email_with_body_from_setting_template(
request,
subject,
'new_user_registration',
'subject_new_user_registration',
new_user.email,
message,
context,
log_dict=log_dict,
)

Expand Down Expand Up @@ -637,20 +656,22 @@ def handle_article_thumb_image_file(uploaded_file, article, request):
article.save()


def handle_email_change(request, email_address):
def handle_email_change(request, email_address, next_url=''):
request.user.email = email_address
request.user.is_active = False
request.user.confirmation_code = uuid.uuid4()
request.user.clean()
request.user.save()

core_confirm_account_url = request.site_type.site_url(
reverse(
'core_confirm_account',
kwargs={'token': request.user.confirmation_code},
)
# Expire existing tokens
models.AccountToken.objects.filter(account=account).update(expired=True)

# Create new token
models.AccountToken.objects.create(
account=request.user,
next_url=next_url,
)

core_confirm_account_url = get_confirm_account_url(request, request.user)
context = {
'user': request.user,
'core_confirm_account_url': core_confirm_account_url,
Expand Down Expand Up @@ -861,7 +882,7 @@ def check_for_bad_login_attempts(request):
time = timezone.now() - timedelta(minutes=10)

attempts = models.LoginAttempt.objects.filter(user_agent=user_agent, ip_address=ip_address, timestamp__gte=time)
print(time, attempts.count())
logger.debug(f'Bad login attempt {attempts.count()+1} at {time}')
return attempts.count()


Expand All @@ -884,10 +905,13 @@ def no_password_check(username):

def start_reset_process(request, account):
# Expire any existing tokens for this user
models.PasswordResetToken.objects.filter(account=account).update(expired=True)
models.AccountToken.objects.filter(account=account).update(expired=True)

# Create a new token
new_reset_token = models.PasswordResetToken.objects.create(account=account)
new_reset_token = models.AccountToken.objects.create(
account=account,
next_url=request.POST.get('next'),
)
send_reset_token(request, new_reset_token)


Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# Generated by Django 4.2.11 on 2024-06-24 15:31

import core.models
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
import uuid


class Migration(migrations.Migration):

dependencies = [
('core', '0094_alter_account_preferred_timezone'),
]

operations = [
migrations.RemoveField(
model_name='account',
name='confirmation_code',
),
migrations.CreateModel(
name='AccountToken',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('token', models.UUIDField(default=uuid.uuid4)),
('identifier', models.CharField(blank=True, help_text='The external personal identifier like an ORCID', max_length=200)),
('expiry', models.DateTimeField(default=core.models.generate_expiry_date, verbose_name='Expires on')),
('expired', models.BooleanField(default=False)),
('next_url', models.CharField(blank=True, help_text='The path where the user should be redirected after login', max_length=200)),
('account', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
options={
'ordering': ['-expiry'],
'get_latest_by': 'expiry',
},
),
]
3 changes: 2 additions & 1 deletion src/core/model_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,13 +98,14 @@ def get_by_domain(cls, domain):
obj = cls.objects.get(domain=domain)
return obj

def site_url(self, path=None):
def site_url(self, path=None, query=''):
# This is here to avoid circular imports
from utils import logic
return logic.build_url(
netloc=self.domain,
scheme=self._get_scheme(),
path=path or "",
query=query,
)
def _get_scheme(self):
scheme = self.SCHEMES[self.is_secure]
Expand Down
Loading