Skip to content

Commit a6c134b

Browse files
committed
Allow sending invitation mails to users
1 parent dbb87f9 commit a6c134b

File tree

12 files changed

+174
-3
lines changed

12 files changed

+174
-3
lines changed

schemas/qwc-admin-gui.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,14 @@
124124
"default_qgis_server_url": {
125125
"description": "The default Qgis server URL. Required for 'themes' plugin.",
126126
"type": "string"
127+
},
128+
"application_name": {
129+
"description": "The application name to display in the invite emails. Default: 'QWC'",
130+
"type": "string"
131+
},
132+
"application_url": {
133+
"description": "The application URL to display in the invite emails. Default: computed from the Admin GUI URL'",
134+
"type": "string"
127135
}
128136
},
129137
"required": [

src/controllers/users_controller.py

Lines changed: 85 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,36 @@
11
import os
2+
import secrets
23

3-
from flask import json
4+
from flask import json, flash, redirect, render_template, url_for
5+
from flask_mail import Message
46

57
from .controller import Controller
68
from forms import UserForm
79

10+
from utils import i18n
811

912
class UsersController(Controller):
1013
"""Controller for user model"""
1114

12-
def __init__(self, app, handler):
15+
def __init__(self, app, handler, mail):
1316
"""Constructor
1417
1518
:param Flask app: Flask application
1619
:param handler: Tenant config handler
20+
:param flask_mail.Mail mail: Application mailer
1721
"""
1822
super(UsersController, self).__init__(
1923
"User", 'users', 'user', 'users', app, handler
2024
)
2125

26+
self.mail = mail
27+
28+
# send mail
29+
app.add_url_rule(
30+
'/%s/<int:id>/sendmail' % self.base_route, 'sendmail_%s' % self.endpoint_suffix, self.sendmail,
31+
methods=['GET']
32+
)
33+
2234
def resources_for_index_query(self, search_text, session):
2335
"""Return query for users list.
2436
@@ -156,3 +168,74 @@ def create_or_update_resources(self, resource, form, session):
156168
self.update_collection(
157169
user.roles_collection, form.roles, self.Role, 'id', session
158170
)
171+
172+
def sendmail(self, id):
173+
"""Send mail with access link.
174+
175+
:param int id: User ID
176+
"""
177+
if not self.app.config.get("MAIL_USERNAME", None):
178+
flash(
179+
i18n('interface.users.no_mail_config'),
180+
'error'
181+
)
182+
return redirect(url_for(self.base_route))
183+
184+
self.setup_models()
185+
# find user
186+
with self.session() as session, session.begin():
187+
user = self.find_resource(id, session)
188+
189+
if not user or not user.email:
190+
flash(
191+
i18n('interface.users.no_user_email'),
192+
'error'
193+
)
194+
return redirect(url_for(self.base_route))
195+
196+
password = secrets.token_urlsafe(8).replace('-','0')
197+
user.set_password(password)
198+
user.force_password_change = True
199+
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+
)
204+
205+
try:
206+
body = render_template(
207+
'%s/invite_email_body.%s.txt' % (self.templates_dir, i18n.get('locale')),
208+
user=user, password=password, app_name=app_name
209+
)
210+
except:
211+
body = render_template(
212+
'%s/invite_email_body.en.txt' % (self.templates_dir),
213+
user=user, password=password, app_name=app_name, app_url=app_url
214+
)
215+
216+
try:
217+
msg = Message(
218+
i18n('interface.users.mail_subject', [app_name]),
219+
recipients=[user.email]
220+
)
221+
# set message body from template
222+
msg.body = body
223+
224+
# send message
225+
self.logger.debug(msg)
226+
self.mail.send(msg)
227+
flash(
228+
i18n('interface.users.send_mail_success'),
229+
'success'
230+
)
231+
except Exception as e:
232+
self.logger.error(
233+
"Could not send mail to user '%s':\n%s" %
234+
(user.email, e)
235+
)
236+
flash(
237+
i18n('interface.users.send_mail_failure'),
238+
'error'
239+
)
240+
241+
return redirect(url_for(self.base_route))

src/server.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ def auth_path_prefix():
118118

119119

120120
# create controllers (including their routes)
121-
UsersController(app, handler)
121+
UsersController(app, handler, mail)
122122
GroupsController(app, handler)
123123
RolesController(app, handler)
124124
ResourcesController(app, handler)

src/templates/base_index.html

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,8 @@ <h1>{{ self.title() }}</h1>
176176
</a>
177177
{% endif %}
178178
{% endif %}
179+
{% block resource_actions scoped %}
180+
{% endblock %}
179181
<form action="{{ url_for('destroy_%s' % endpoint_suffix, id=resource[pkey]) }}" method="post" style="display: inline;">
180182
<input type="hidden" name="_method" value="DELETE" />
181183
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>

src/templates/users/index.html

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,14 @@
1414
<td>{{ resource.name }}</td>
1515
{% endblock %}
1616

17+
{% block resource_actions %}
18+
<a href="#" class="btn btn-primary" onclick="
19+
if (confirm('{{ i18n('interface.users.confirm_sendmail', [resource.email]) }}')) {
20+
location.href = '{{ url_for('sendmail_%s' % endpoint_suffix, id=resource[pkey]) }}';
21+
}
22+
" role="button">
23+
{{ utils.render_icon('envelope') }} {{ i18n('interface.users.sendmail') }}
24+
</a>
25+
{% endblock %}
26+
1727
{% block delete_resource_confirmation %}{{ i18n('interface.users.confirm_message_delete') }}{% endblock %}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
Hallo {{ user.name }}
2+
3+
Es wurde für Sie ein Konto für {{ app_name }} erstellt.
4+
5+
Sie können auf die Anwendung unter {{ app_url }} zugreifen.
6+
7+
Ihre Anmeldedaten sind:
8+
9+
* Benutzername: {{ user.name }}
10+
* Passwort: {{ password }}
11+
12+
Bei der ersten Anmeldung werden Sie aufgefordert, Ihr Passwort zu ändern.
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
Hello {{ user.name }}
2+
3+
An account has been created for you for {{ app_name }}.
4+
5+
You can access the application at {{ app_url }}.
6+
7+
Your credentials are:
8+
9+
* Username: {{ user.name }}
10+
* Password: {{ password }}
11+
12+
You will be prompted to change your password on first login.
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
Bonjour {{ user.name }}
2+
3+
Un compte a été créé pour vous pour {{ app_name }}.
4+
5+
Vous pouvez accéder à l'application à l'adresse {{ app_url }}.
6+
7+
Vos informations d'identification sont les suivantes :
8+
9+
* Nom d'utilisateur : {{ user.name }}
10+
* Mot de passe : {{ password }}
11+
12+
Vous serez invité à modifier votre mot de passe lors de votre première connexion.

src/translations/de.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,8 @@
153153
"users": {
154154
"authentication": "Authentifizierung",
155155
"confirm_message_delete": "Benutzer entfenrnen?",
156+
"confirm_sendmail": "Das Passwort wird zurückgesetzt und eine Einladungs-E-Mail an {} gesendet, weiter?",
157+
"force_password_change": "Passwortänderung bei nächster Anmeldung erzwingen",
156158
"form_email": "Email",
157159
"form_email_error": "Bitte eine andere Email Adresse verwenden.",
158160
"form_failed_login": "Fehlgeschlagenen Loginversuche",
@@ -163,7 +165,13 @@
163165
"form_password_repeat": "Passwort wiederholen",
164166
"form_totp": "TOTP Geheimniss",
165167
"groups_roles": "Gruppen und Rollen",
168+
"mail_subject": "Ihr Zugang zu {}",
166169
"new_user": "Neuer Benutzer",
170+
"no_mail_config": "Keine Absender-E-Mail ist konfiguriert, kann keine Einladungs-E-Mail senden.",
171+
"no_user_email": "Keine E-Mail für den Benutzer gespeichert, kann keine E-Mail senden.",
172+
"send_mail_failure": "Die Einladungs-E-Mail konnte nicht gesendet werden.",
173+
"send_mail_success": "Einladungs-E-Mail erfolgreich gesendet.",
174+
"sendmail": "Einladung versenden",
167175
"title": "Benutzer",
168176
"user_info": "Benutzerinfo"
169177
}

src/translations/en.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,8 @@
153153
"users": {
154154
"authentication": "Authentication",
155155
"confirm_message_delete": "Remove user?",
156+
"confirm_sendmail": "The password will be reset and an invitation mail sent to {}, proceed?",
157+
"force_password_change": "Force password change on next login",
156158
"form_email": "Email",
157159
"form_email_error": "Please use a different email address.",
158160
"form_failed_login": "Failed login attempts",
@@ -163,7 +165,13 @@
163165
"form_password_repeat": "Repeat Password",
164166
"form_totp": "TOTP secret",
165167
"groups_roles": "Groups and roles",
168+
"mail_subject": "Your access to {}",
166169
"new_user": "New user",
170+
"no_mail_config": "No sender email is configured, cannot send invitation e-mail.",
171+
"no_user_email": "No email stored for the user, cannot send e-mail.",
172+
"send_mail_failure": "Failed to send the invitation e-mail.",
173+
"send_mail_success": "Invitation e-mail sent successfully.",
174+
"sendmail": "Send invitation",
167175
"title": "Users",
168176
"user_info": "User info"
169177
}

src/translations/fr.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,8 @@
153153
"users": {
154154
"authentication": "Authenticafition",
155155
"confirm_message_delete": "Voulez-vous supprimer cet utilisateur?",
156+
"confirm_sendmail": "Le mot de passe sera réinitialisé et un e-mail d'invitation sera envoyé à {}, procéder?",
157+
"force_password_change": "Forcer le changement de mot de passe lors de la prochaine connexion",
156158
"form_email": "Email",
157159
"form_email_error": "Utilisez une adresse email différente SVP.",
158160
"form_failed_login": "Nombre de tentatives de connexion infructueuses",
@@ -163,7 +165,13 @@
163165
"form_password_repeat": "Répéter le mot de passe",
164166
"form_totp": "TOTP secret",
165167
"groups_roles": "Groupes et rôles",
168+
"mail_subject": "Votre accès à {}",
166169
"new_user": "Nouvel utilisateur",
170+
"no_mail_config": "Aucun e-mail d'expéditeur n'est configuré, il n'est pas possible d'envoyer un e-mail d'invitation.",
171+
"no_user_email": "Aucun e-mail n'est enregistré pour l'utilisateur, il ne peut pas envoyer d'e-mail.",
172+
"send_mail_failure": "L'envoi de l'e-mail d'invitation a échoué",
173+
"send_mail_success": "L'e-mail d'invitation a été envoyé avec succès",
174+
"sendmail": "Envoyer une invitation",
167175
"title": "Utilisateurs",
168176
"user_info": "Information utilisateur"
169177
}

src/translations/tsconfig.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,8 @@
142142
"interface.roles.title",
143143
"interface.users.authentication",
144144
"interface.users.confirm_message_delete",
145+
"interface.users.confirm_sendmail",
146+
"interface.users.force_password_change",
145147
"interface.users.form_email",
146148
"interface.users.form_email_error",
147149
"interface.users.form_failed_login",
@@ -152,7 +154,13 @@
152154
"interface.users.form_password_repeat",
153155
"interface.users.form_totp",
154156
"interface.users.groups_roles",
157+
"interface.users.mail_subject",
155158
"interface.users.new_user",
159+
"interface.users.no_mail_config",
160+
"interface.users.no_user_email",
161+
"interface.users.send_mail_failure",
162+
"interface.users.send_mail_success",
163+
"interface.users.sendmail",
156164
"interface.users.title",
157165
"interface.users.user_info",
158166
"plugins.config_editor.edit_message_error",

0 commit comments

Comments
 (0)