Skip to content

Commit 13e6a87

Browse files
authored
[chores] Improved password expiration: sleep and email text #367
- added a random sleep between 1 and 2 secs every 10 emails - improved the email text (show username and expiry date) Closes #367
1 parent 7e93b75 commit 13e6a87

File tree

14 files changed

+206
-14
lines changed

14 files changed

+206
-14
lines changed

openwisp_users/accounts/locale/de/LC_MESSAGES/django.po

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,3 +40,21 @@ msgstr "Die Seite kann geschlossen werden."
4040
#: accounts/templates/account/logout_success.html:18
4141
msgid "Logout successful."
4242
msgstr "Sie wurden erfolgreich abgemeldet."
43+
44+
#: accounts/templates/account/email/password_expiration_message.html:3
45+
#: accounts/templates/account/email/password_expiration_message.txt:3
46+
#, python-format
47+
msgid ""
48+
"We inform you that the password for your account %(username)s will expire in "
49+
"7 days, precisely on %(expiry_date)s."
50+
msgstr ""
51+
"Wir informieren Sie darüber, dass das Passwort für Ihr Konto %(username)s in "
52+
"7 Tagen abläuft, genau am %(expiry_date)s."
53+
54+
#: accounts/templates/account/email/password_expiration_message.html:5
55+
#: accounts/templates/account/email/password_expiration_message.txt:5
56+
msgid ""
57+
"Kindly proceed with updating your password by clicking on the button below."
58+
msgstr ""
59+
"Bitte aktualisieren Sie Ihr Passwort, indem Sie auf die Schaltfläche unten "
60+
"klicken."

openwisp_users/accounts/locale/fur/LC_MESSAGES/django.po

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,3 +69,20 @@ msgstr "O confermi"
6969
#: accounts/templates/account/logout_success.html:18
7070
msgid "Logout successful."
7171
msgstr "Logout fat cun sucès."
72+
73+
#: accounts/templates/account/email/password_expiration_message.html:3
74+
#: accounts/templates/account/email/password_expiration_message.txt:3
75+
#, python-format
76+
msgid ""
77+
"We inform you that the password for your account %(username)s will expire in "
78+
"7 days, precisely on %(expiry_date)s."
79+
msgstr ""
80+
"O ditin che la password pal to cont %(username)s e je sulecite a finî in "
81+
"7 dîs, precisament il %(expiry_date)s."
82+
83+
#: accounts/templates/account/email/password_expiration_message.html:5
84+
#: accounts/templates/account/email/password_expiration_message.txt:5
85+
msgid ""
86+
"Kindly proceed with updating your password by clicking on the button below."
87+
msgstr ""
88+
"Va dongje par aggiornâ la tô password cliccand sul buton sot. "

openwisp_users/accounts/locale/it/LC_MESSAGES/django.po

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,3 +84,21 @@ msgstr "La pagina può essere chiusa."
8484
#: accounts/templates/account/logout_success.html:18
8585
msgid "Logout successful."
8686
msgstr "Logout effettuato con successo."
87+
88+
#: accounts/templates/account/email/password_expiration_message.html:3
89+
#: accounts/templates/account/email/password_expiration_message.txt:3
90+
#, python-format
91+
msgid ""
92+
"We inform you that the password for your account %(username)s will expire in "
93+
"7 days, precisely on %(expiry_date)s."
94+
msgstr ""
95+
"Ti informiamo che la password per il tuo account %(username)s scadrà tra "
96+
"7 giorni, precisamente il %(expiry_date)s."
97+
98+
#: accounts/templates/account/email/password_expiration_message.html:5
99+
#: accounts/templates/account/email/password_expiration_message.txt:5
100+
msgid ""
101+
"Kindly proceed with updating your password by clicking on the button below."
102+
msgstr ""
103+
"Ti preghiamo di procedere con l'aggiornamento della tua password cliccando "
104+
"sul pulsante qui sotto."

openwisp_users/accounts/locale/ru/LC_MESSAGES/django.po

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,3 +69,20 @@ msgstr "Подтверждать"
6969
#: accounts/templates/account/logout_success.html:18
7070
msgid "Logout successful."
7171
msgstr "Выход прошел успешно."
72+
73+
#: accounts/templates/account/email/password_expiration_message.html:3
74+
#: accounts/templates/account/email/password_expiration_message.txt:3
75+
#, python-format
76+
msgid ""
77+
"We inform you that the password for your account %(username)s will expire in "
78+
"7 days, precisely on %(expiry_date)s."
79+
msgstr ""
80+
"Мы информируем вас о том, что пароль для вашей учетной записи %(username)s "
81+
"истекает через 7 дней, точно %(expiry_date)s."
82+
83+
#: accounts/templates/account/email/password_expiration_message.html:5
84+
#: accounts/templates/account/email/password_expiration_message.txt:5
85+
msgid ""
86+
"Kindly proceed with updating your password by clicking on the button below."
87+
msgstr ""
88+
"Просим вас обновить ваш пароль, нажав на кнопку ниже."

openwisp_users/accounts/locale/sl/LC_MESSAGES/django.po

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,3 +40,19 @@ msgstr "Lahko zaprete stran."
4040
#: accounts/templates/account/logout_success.html:18
4141
msgid "Logout successful."
4242
msgstr "Odjava uspešna."
43+
44+
#: accounts/templates/account/email/password_expiration_message.html:3
45+
#: accounts/templates/account/email/password_expiration_message.txt:3
46+
#, python-format
47+
msgid ""
48+
"We inform you that the password for your account %(username)s will expire in "
49+
"7 days, precisely on %(expiry_date)s."
50+
msgstr ""
51+
"Obveščamo vas, da bo geslo za vaš račun %(username)s poteklo v 7 dneh, natančneje dne %(expiry_date)s."
52+
53+
#: accounts/templates/account/email/password_expiration_message.html:5
54+
#: accounts/templates/account/email/password_expiration_message.txt:5
55+
msgid ""
56+
"Kindly proceed with updating your password by clicking on the button below."
57+
msgstr ""
58+
"Prosimo, da nadaljujete z posodabljanjem svojega gesla tako, da kliknete na spodnji gumb."
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{% load i18n %}
2+
{% load l10n %}
3+
4+
<p>{% blocktrans with expiry_date=expiry_date|localize %}We inform you that the password for your account {{ username }} will expire in 7 days, precisely on {{ expiry_date }}.{% endblocktrans %}<p>
5+
6+
<p>{% trans "Kindly proceed with updating your password by clicking on the button below." %}<p>
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{% load i18n %}
2+
{% load l10n %}
3+
4+
{% blocktrans with expiry_date=expiry_date|localize %}We inform you that the password for your account {{ username }} will expire in 7 days, precisely on {{ expiry_date }}.{% endblocktrans %}
5+
6+
{% trans "Kindly proceed with updating your password by clicking on the button below." %}

openwisp_users/locale/de/LC_MESSAGES/django.po

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,11 @@ msgstr "Neues Passwort darf nicht mit Ihrem alten Passwort identisch sein."
1919
#: views.py:198
2020
msgid "Password updated successfully"
2121
msgstr "Passwort erfolgreich aktualisiert"
22+
23+
#: tasks.py:58
24+
msgid "Action Required: Password Expiry Notice"
25+
msgstr "Aktion erforderlich: Passwortablaufbenachrichtigung"
26+
27+
#: tasks.py:73
28+
msgid "Change password"
29+
msgstr "Passwort ändern"

openwisp_users/locale/fur/LC_MESSAGES/django.po

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,11 @@ msgstr "La gnove password no pues jessi compagne de vecje password."
1919
#: views.py:198
2020
msgid "Password updated successfully"
2121
msgstr "Password inzornade cun sucès"
22+
23+
#: tasks.py:58
24+
msgid "Action Required: Password Expiry Notice"
25+
msgstr "Aziòn richieste: Notifiche di scadenze de password"
26+
27+
#: tasks.py:73
28+
msgid "Change password"
29+
msgstr "Canbia la password"

openwisp_users/locale/it/LC_MESSAGES/django.po

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,11 @@ msgstr "La nuova password non può essere uguale alla vecchia password."
1919
#: views.py:198
2020
msgid "Password updated successfully"
2121
msgstr "Password aggiornata con successo"
22+
23+
#: tasks.py:58
24+
msgid "Action Required: Password Expiry Notice"
25+
msgstr "Azione richiesta: Avviso di scadenza della password"
26+
27+
#: tasks.py:73
28+
msgid "Change password"
29+
msgstr "Cambia password"

openwisp_users/locale/ru/LC_MESSAGES/django.po

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,11 @@ msgstr "Новый пароль не может совпадать с вашим
1919
#: views.py:198
2020
msgid "Password updated successfully"
2121
msgstr "Пароль успешно обновлен"
22+
23+
#: tasks.py:58
24+
msgid "Action Required: Password Expiry Notice"
25+
msgstr "Требуется действие: Уведомление о сроке действия пароля"
26+
27+
#: tasks.py:73
28+
msgid "Change password"
29+
msgstr "Изменить пароль"

openwisp_users/locale/sl/LC_MESSAGES/django.po

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,11 @@ msgstr "Novo geslo ne more biti enako staremu."
1919
#: views.py:198
2020
msgid "Password updated successfully"
2121
msgstr "Geslo je bilo uspešno posodobljeno"
22+
23+
#: tasks.py:58
24+
msgid "Action Required: Password Expiry Notice"
25+
msgstr "Potrebno dejanje: Obvestilo o poteku gesla"
26+
27+
#: tasks.py:73
28+
msgid "Change password"
29+
msgstr "Spremeni geslo"

openwisp_users/tasks.py

Lines changed: 33 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,14 @@
1+
import random
2+
from time import sleep
3+
14
from celery import shared_task
25
from django.contrib.auth import get_user_model
36
from django.contrib.auth.hashers import UNUSABLE_PASSWORD_PREFIX
47
from django.contrib.sites.models import Site
58
from django.db.models import Q
9+
from django.template.loader import render_to_string
610
from django.urls import reverse
11+
from django.utils import translation
712
from django.utils.timezone import now, timedelta
813
from django.utils.translation import gettext_lazy as _
914
from openwisp_utils.admin_theme.email import send_email
@@ -49,17 +54,32 @@ def password_expiration_email():
4954
)
5055
.filter(query)
5156
)
57+
email_counts = 1
5258
for user in qs.iterator():
53-
send_email(
54-
subject=_('Your password is about to expire'),
55-
body_text=_('Your password is about to expire in 7 days'),
56-
body_html=_('You password is about to expire in 7 days.'),
57-
recipients=[user.email],
58-
extra_context={
59-
'call_to_action_url': 'https://{0}{1}'.format(
60-
current_site.domain,
61-
reverse('account_change_password'),
62-
),
63-
'call_to_action_text': _('Change password'),
64-
},
65-
)
59+
with translation.override(user.language):
60+
send_email(
61+
subject=_('Action Required: Password Expiry Notice'),
62+
body_text=render_to_string(
63+
'account/email/password_expiration_message.txt',
64+
context={'username': user.username, 'expiry_date': expiry_date},
65+
).strip(),
66+
body_html=render_to_string(
67+
'account/email/password_expiration_message.html',
68+
context={'username': user.username, 'expiry_date': expiry_date},
69+
).strip(),
70+
recipients=[user.email],
71+
extra_context={
72+
'call_to_action_url': 'https://{0}{1}'.format(
73+
current_site.domain,
74+
reverse('account_change_password'),
75+
),
76+
'call_to_action_text': _('Change password'),
77+
},
78+
)
79+
# Avoid overloading the SMTP server by sending multiple
80+
# emails continuously.
81+
if email_counts >= 10:
82+
email_counts = 0
83+
sleep(random.randint(1, 2))
84+
else:
85+
email_counts += 1

openwisp_users/tests/test_models.py

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -428,12 +428,46 @@ def test_password_expiration_mail(self):
428428
password_updated=user_expiry_date
429429
)
430430
password_expiration_email.delay()
431+
formatted_expiry_date = (now() + timedelta(days=7)).strftime('%d %b %Y')
431432
self.assertEqual(len(mail.outbox), 1)
432433
email = mail.outbox.pop()
433434
self.assertEqual(email.to, [verified_email_user.email])
434-
self.assertEqual(email.subject, 'Your password is about to expire')
435+
self.assertEqual(email.subject, 'Action Required: Password Expiry Notice')
436+
self.assertEqual(
437+
email.body,
438+
'We inform you that the password for your account tester will expire'
439+
f' in 7 days, precisely on {formatted_expiry_date}.\n\n'
440+
'Kindly proceed with updating your password by clicking on the'
441+
' button below.',
442+
)
443+
self.assertIn(
444+
'<p>We inform you that the password for your account tester will expire'
445+
f' in 7 days, precisely on {formatted_expiry_date}.<p>\n\n<p>',
446+
email.alternatives[0][0],
447+
)
448+
self.assertIn(
449+
'Kindly proceed with updating your password by clicking on the button'
450+
' below.<p>',
451+
email.alternatives[0][0],
452+
)
435453
self.assertNotEqual(email.to, [unverified_email_user.email])
436454

455+
@patch.object(app_settings, 'USER_PASSWORD_EXPIRATION', 30)
456+
@patch('openwisp_users.tasks.sleep')
457+
def test_password_expiration_mail_sleep(self, mocked_sleep):
458+
user_expiry_date = now().today() - timedelta(
459+
days=(app_settings.USER_PASSWORD_EXPIRATION - 7)
460+
)
461+
for i in range(10):
462+
self._create_user(username=f'user{i}', email=f'user{i}@example.com')
463+
EmailAddress.objects.update(verified=True)
464+
self.assertEqual(User.objects.count(), 10)
465+
User.objects.update(password_updated=user_expiry_date)
466+
password_expiration_email.delay()
467+
mocked_sleep.assert_called()
468+
self.assertGreaterEqual(mocked_sleep.call_args[0][0], 1)
469+
self.assertLessEqual(mocked_sleep.call_args[0][0], 2)
470+
437471
@patch.object(app_settings, 'USER_PASSWORD_EXPIRATION', 0)
438472
@patch.object(app_settings, 'STAFF_USER_PASSWORD_EXPIRATION', 0)
439473
def test_password_expiration_mail_settings_disabled(self):

0 commit comments

Comments
 (0)