Skip to content

Commit 647c077

Browse files
committed
Add checkbox to user form to set random password and send invitation
1 parent 93d2bc6 commit 647c077

File tree

8 files changed

+109
-48
lines changed

8 files changed

+109
-48
lines changed

src/controllers/users_controller.py

Lines changed: 64 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ def __init__(self, app, handler, mail):
2727

2828
# send mail
2929
app.add_url_rule(
30-
'/%s/<int:id>/sendmail' % self.base_route, 'sendmail_%s' % self.endpoint_suffix, self.sendmail,
30+
'/%s/<int:id>/sendmail' % self.base_route, 'sendmail_%s' % self.endpoint_suffix, self.reset_password_send_invite,
3131
methods=['GET']
3232
)
3333

@@ -121,13 +121,22 @@ def create_or_update_resources(self, resource, form, session):
121121
# update existing user
122122
user = resource
123123

124+
set_random_password_send_invite = form.set_random_password_send_invite.data
125+
124126
# update user
125127
user.name = form.name.data
126128
user.description = form.description.data
127129
user.email = form.email.data
128-
if form.password.data:
129-
user.set_password(form.password.data)
130-
user.force_password_change = form.force_password_change.data
130+
131+
if not set_random_password_send_invite:
132+
if form.password.data:
133+
user.set_password(form.password.data)
134+
user.force_password_change = form.force_password_change.data
135+
else:
136+
password = secrets.token_urlsafe(8).replace('-','0')
137+
user.set_password(password)
138+
user.force_password_change = True
139+
131140
user.failed_sign_in_count = form.failed_sign_in_count.data or 0
132141

133142
totp_enabled = self.handler().config().get(
@@ -169,8 +178,11 @@ def create_or_update_resources(self, resource, form, session):
169178
user.roles_collection, form.roles, self.Role, 'id', session
170179
)
171180

172-
def sendmail(self, id):
173-
"""Send mail with access link.
181+
if set_random_password_send_invite:
182+
self.send_invite(user, password)
183+
184+
def reset_password_send_invite(self, id):
185+
"""Resets the password and sends mail with access link.
174186
175187
:param int id: User ID
176188
"""
@@ -197,46 +209,53 @@ def sendmail(self, id):
197209
user.set_password(password)
198210
user.force_password_change = True
199211

200-
app_name = self.handler().config().get("application_name", "QWC")
201-
app_url = self.handler().config().get("application_url",
202-
os.path.dirname(url_for('home', _external=True).rstrip("/")) + "/"
203-
)
212+
self.send_invite(user, password)
204213

205-
locale = os.environ.get('DEFAULT_LOCALE', 'en')
206-
try:
207-
body = render_template(
208-
'%s/invite_email_body.%s.txt' % (self.templates_dir, locale),
209-
user=user, password=password, app_name=app_name, app_url=app_url
210-
)
211-
except:
212-
body = render_template(
213-
'%s/invite_email_body.en.txt' % (self.templates_dir),
214-
user=user, password=password, app_name=app_name, app_url=app_url
215-
)
214+
return redirect(url_for(self.base_route))
216215

217-
try:
218-
msg = Message(
219-
i18n('interface.users.mail_subject', [app_name]),
220-
recipients=[user.email]
221-
)
222-
# set message body from template
223-
msg.body = body
216+
def send_invite(self, user, password):
217+
"""Sends mail with access link.
224218
225-
# send message
226-
self.logger.debug(msg)
227-
self.mail.send(msg)
228-
flash(
229-
i18n('interface.users.send_mail_success'),
230-
'success'
231-
)
232-
except Exception as e:
233-
self.logger.error(
234-
"Could not send mail to user '%s':\n%s" %
235-
(user.email, e)
236-
)
237-
flash(
238-
i18n('interface.users.send_mail_failure'),
239-
'error'
240-
)
219+
:param obj user: The user resource
220+
"""
221+
app_name = self.handler().config().get("application_name", "QWC")
222+
app_url = self.handler().config().get("application_url",
223+
os.path.dirname(url_for('home', _external=True).rstrip("/")) + "/"
224+
)
241225

242-
return redirect(url_for(self.base_route))
226+
locale = os.environ.get('DEFAULT_LOCALE', 'en')
227+
try:
228+
body = render_template(
229+
'%s/invite_email_body.%s.txt' % (self.templates_dir, locale),
230+
user=user, password=password, app_name=app_name, app_url=app_url
231+
)
232+
except:
233+
body = render_template(
234+
'%s/invite_email_body.en.txt' % (self.templates_dir),
235+
user=user, password=password, app_name=app_name, app_url=app_url
236+
)
237+
238+
try:
239+
msg = Message(
240+
i18n('interface.users.mail_subject', [app_name]),
241+
recipients=[user.email]
242+
)
243+
# set message body from template
244+
msg.body = body
245+
246+
# send message
247+
self.logger.debug(msg)
248+
self.mail.send(msg)
249+
flash(
250+
i18n('interface.users.send_mail_success'),
251+
'success'
252+
)
253+
except Exception as e:
254+
self.logger.error(
255+
"Could not send mail to user '%s':\n%s" %
256+
(user.email, e)
257+
)
258+
flash(
259+
i18n('interface.users.send_mail_failure'),
260+
'error'
261+
)

src/forms/user_form.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ class UserForm(FlaskForm):
1919
# NOTE: actual subform added in add_custom_fields()
2020
user_info = FormField(FlaskForm, i18n('interface.users.user_info'), _meta={'csrf': False})
2121

22+
set_random_password_send_invite = BooleanField(i18n('interface.users.set_random_password_send_invite'))
2223
password = PasswordField(i18n('interface.users.form_password'))
2324
password2 = PasswordField(
2425
i18n('interface.users.form_password_repeat'), validators=[EqualTo('password')])

src/templates/users/form.html

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,33 @@
55
{{super()}}
66
<script src="{{ url_for('static', filename='js/jquery.multi-select.js') }}"></script>
77
<script type="text/javascript">
8+
function confirm_send_invitation(ev) {
9+
if (!$('#set_random_password_send_invite').prop('checked')) {
10+
return true;
11+
}
12+
let email = $('#email').val();
13+
if (!email) {
14+
alert('{{ i18n('interface.users.no_user_email').replace("'", "\\'") }}');
15+
ev.preventDefault();
16+
return false;
17+
}
18+
let confirm_text = '{{ i18n('interface.users.confirm_sendmail', ['$email$']).replace("'", "\\'") }}';
19+
if (!confirm(confirm_text.replace('$email$', email))) {
20+
ev.preventDefault();
21+
return false;
22+
}
23+
}
24+
function configure_invite_form() {
25+
let checked = $('#set_random_password_send_invite').prop('checked');
26+
$('#password').attr('disabled', checked);
27+
$('#password2').attr('disabled', checked);
28+
$('#force_password_change').attr('disabled', checked);
29+
if (checked) {
30+
$('#submit').val('{{ i18n('interface.users.save_and_send').replace("'", "\\'") }}');
31+
} else {
32+
$('#submit').val('{{ i18n('interface.common.form_submit').replace("'", "\\'") }}');
33+
}
34+
}
835
$(function() {
936
// initialize multi-select
1037
$('#groups').multiSelectWithSearch({
@@ -15,6 +42,8 @@
1542
selectableHeader: '<div class="ms-header">{{ i18n('interface.common.roles') }}</div>',
1643
selectionHeader: '<div class="ms-header">{{ i18n('interface.common.assigned_roles') }}</div>'
1744
});
45+
$('#set_random_password_send_invite').change(configure_invite_form);
46+
configure_invite_form();
1847
});
1948
</script>
2049
{% endblock %}
@@ -28,7 +57,7 @@
2857
{% block container %}
2958
<h1>{{ title }}</h1>
3059

31-
<form class="form form-horizontal" action="{{ action }}" method="post">
60+
<form class="form form-horizontal" action="{{ action }}" method="post" onsubmit="confirm_send_invitation(event)">
3261
{% if method != 'POST' %}
3362
<input type="hidden" name="_method" value="{{method}}" />
3463
{% endif %}
@@ -48,6 +77,8 @@ <h1>{{ title }}</h1>
4877
{% endif %}
4978

5079
<legend>{{ i18n('interface.users.authentication') }}</legend>
80+
{{ wtf.render_field(form.set_random_password_send_invite, form_type="horizontal", horizontal_columns=('sm', 2, 5)) }}
81+
5182
{{ wtf.render_field(form.password, form_type="horizontal", horizontal_columns=('sm', 2, 5)) }}
5283
{{ wtf.render_field(form.password2, form_type="horizontal", horizontal_columns=('sm', 2, 5)) }}
5384
{{ wtf.render_field(form.force_password_change, form_type="horizontal", horizontal_columns=('sm', 2, 5)) }}

src/translations/de.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -169,9 +169,11 @@
169169
"new_user": "Neuer Benutzer",
170170
"no_mail_config": "Keine Absender-E-Mail ist konfiguriert, kann keine Einladungs-E-Mail senden.",
171171
"no_user_email": "Keine E-Mail für den Benutzer gespeichert, kann keine E-Mail senden.",
172+
"save_and_send": "Speicher und Einladung senden",
172173
"send_mail_failure": "Die Einladungs-E-Mail konnte nicht gesendet werden.",
173174
"send_mail_success": "Einladungs-E-Mail erfolgreich gesendet.",
174-
"sendmail": "Einladung versenden",
175+
"sendmail": "Einladung senden",
176+
"set_random_password_send_invite": "Zufallspasswort setzten und Einladung senden",
175177
"title": "Benutzer",
176178
"user_info": "Benutzerinfo"
177179
}

src/translations/en.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,9 +169,11 @@
169169
"new_user": "New user",
170170
"no_mail_config": "No sender email is configured, cannot send invitation e-mail.",
171171
"no_user_email": "No email stored for the user, cannot send e-mail.",
172+
"save_and_send": "Save and send invitation",
172173
"send_mail_failure": "Failed to send the invitation e-mail.",
173174
"send_mail_success": "Invitation e-mail sent successfully.",
174175
"sendmail": "Send invitation",
176+
"set_random_password_send_invite": "Set random password and send invitation mail",
175177
"title": "Users",
176178
"user_info": "User info"
177179
}

src/translations/es.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,9 +169,11 @@
169169
"new_user": "Nuevo usuario",
170170
"no_mail_config": "No hay correo remitente configurado, no se puede enviar correo de invitación.",
171171
"no_user_email": "No hay correo almacenado para el usuario, no se puede enviar correo.",
172+
"save_and_send": "Guardar y enviar invitación",
172173
"send_mail_failure": "Error al enviar el correo de invitación.",
173174
"send_mail_success": "Correo de invitación enviado exitosamente.",
174175
"sendmail": "Enviar invitación",
176+
"set_random_password_send_invite": "Establecer contraseña aleatoria y enviar correo de invitación",
175177
"title": "Usuarios",
176178
"user_info": "Información del usuario"
177179
}

src/translations/fr.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -169,9 +169,11 @@
169169
"new_user": "Nouvel utilisateur",
170170
"no_mail_config": "Aucun e-mail d'expéditeur n'est configuré, il n'est pas possible d'envoyer un e-mail d'invitation.",
171171
"no_user_email": "Aucun e-mail n'est enregistré pour l'utilisateur, il ne peut pas envoyer d'e-mail.",
172+
"save_and_send": "Enregistrer et envoyer invitation",
172173
"send_mail_failure": "L'envoi de l'e-mail d'invitation a échoué",
173174
"send_mail_success": "L'e-mail d'invitation a été envoyé avec succès",
174-
"sendmail": "Envoyer une invitation",
175+
"sendmail": "Envoyer invitation",
176+
"set_random_password_send_invite": "Définir un mot de passe aléatoire et envoyer un e-mail d'invitation",
175177
"title": "Utilisateurs",
176178
"user_info": "Information utilisateur"
177179
}

src/translations/tsconfig.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,8 @@
144144
"interface.users.confirm_message_delete",
145145
"interface.users.confirm_sendmail",
146146
"interface.users.force_password_change",
147+
"interface.users.save_and_send",
148+
"interface.users.set_random_password_send_invite",
147149
"interface.users.form_email",
148150
"interface.users.form_email_error",
149151
"interface.users.form_failed_login",

0 commit comments

Comments
 (0)