Mercurial > libervia-backend
comparison libervia/backend/plugins/plugin_misc_account.py @ 4071:4b842c1fb686
refactoring: renamed `sat` package to `libervia.backend`
author | Goffi <goffi@goffi.org> |
---|---|
date | Fri, 02 Jun 2023 11:49:51 +0200 |
parents | sat/plugins/plugin_misc_account.py@524856bd7b19 |
children | 0d7bb4df2343 |
comparison
equal
deleted
inserted
replaced
4070:d10748475025 | 4071:4b842c1fb686 |
---|---|
1 #!/usr/bin/env python3 | |
2 | |
3 # Libervia plugin for account creation | |
4 # Copyright (C) 2009-2021 Jérôme Poisson (goffi@goffi.org) | |
5 | |
6 # This program is free software: you can redistribute it and/or modify | |
7 # it under the terms of the GNU Affero General Public License as published by | |
8 # the Free Software Foundation, either version 3 of the License, or | |
9 # (at your option) any later version. | |
10 | |
11 # This program is distributed in the hope that it will be useful, | |
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of | |
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
14 # GNU Affero General Public License for more details. | |
15 | |
16 # You should have received a copy of the GNU Affero General Public License | |
17 # along with this program. If not, see <http://www.gnu.org/licenses/>. | |
18 | |
19 from libervia.backend.core.i18n import _, D_ | |
20 from libervia.backend.core.log import getLogger | |
21 | |
22 from libervia.backend.core import exceptions | |
23 from libervia.backend.tools import xml_tools | |
24 from libervia.backend.memory.memory import Sessions | |
25 from libervia.backend.memory.crypto import PasswordHasher | |
26 from libervia.backend.core.constants import Const as C | |
27 import configparser | |
28 from twisted.internet import defer | |
29 from twisted.python.failure import Failure | |
30 from twisted.words.protocols.jabber import jid | |
31 from libervia.backend.tools.common import email as sat_email | |
32 | |
33 | |
34 log = getLogger(__name__) | |
35 | |
36 | |
37 # FIXME: this plugin code is old and need a cleaning | |
38 # TODO: account deletion/password change need testing | |
39 | |
40 | |
41 PLUGIN_INFO = { | |
42 C.PI_NAME: "Account Plugin", | |
43 C.PI_IMPORT_NAME: "MISC-ACCOUNT", | |
44 C.PI_TYPE: "MISC", | |
45 C.PI_PROTOCOLS: [], | |
46 C.PI_DEPENDENCIES: ["XEP-0077"], | |
47 C.PI_RECOMMENDATIONS: ["GROUPBLOG"], | |
48 C.PI_MAIN: "MiscAccount", | |
49 C.PI_HANDLER: "no", | |
50 C.PI_DESCRIPTION: _("""Libervia account creation"""), | |
51 } | |
52 | |
53 CONFIG_SECTION = "plugin account" | |
54 | |
55 # You need do adapt the following consts to your server | |
56 # all theses values (key=option name, value=default) can (and should) be overriden | |
57 # in libervia.conf in section CONFIG_SECTION | |
58 | |
59 default_conf = { | |
60 "email_from": "NOREPLY@example.net", | |
61 "email_server": "localhost", | |
62 "email_sender_domain": "", | |
63 "email_port": 25, | |
64 "email_username": "", | |
65 "email_password": "", | |
66 "email_starttls": "false", | |
67 "email_auth": "false", | |
68 "email_admins_list": [], | |
69 "admin_email": "", | |
70 "new_account_server": "localhost", | |
71 "new_account_domain": "", # use xmpp_domain if not found | |
72 "reserved_list": ["libervia"], # profiles which can't be used | |
73 } | |
74 | |
75 WELCOME_MSG = D_( | |
76 """Welcome to Libervia, the web interface of Salut à Toi. | |
77 | |
78 Your account on {domain} has been successfully created. | |
79 This is a demonstration version to show you the current status of the project. | |
80 It is still under development, please keep it in mind! | |
81 | |
82 Here is your connection information: | |
83 | |
84 Login on {domain}: {profile} | |
85 Jabber ID (JID): {jid} | |
86 Your password has been chosen by yourself during registration. | |
87 | |
88 In the beginning, you have nobody to talk to. To find some contacts, you may use the users' directory: | |
89 - make yourself visible in "Service / Directory subscription". | |
90 - search for people with "Contacts" / Search directory". | |
91 | |
92 Any feedback welcome. Thank you! | |
93 | |
94 Salut à Toi association | |
95 https://www.salut-a-toi.org | |
96 """ | |
97 ) | |
98 | |
99 DEFAULT_DOMAIN = "example.net" | |
100 | |
101 | |
102 class MiscAccount(object): | |
103 """Account plugin: create a SàT + XMPP account, used by Libervia""" | |
104 | |
105 # XXX: This plugin was initialy a Q&D one used for the demo. | |
106 # TODO: cleaning, separate email handling, more configuration/tests, fixes | |
107 | |
108 def __init__(self, host): | |
109 log.info(_("Plugin Account initialization")) | |
110 self.host = host | |
111 host.bridge.add_method( | |
112 "libervia_account_register", | |
113 ".plugin", | |
114 in_sign="sss", | |
115 out_sign="", | |
116 method=self._register_account, | |
117 async_=True, | |
118 ) | |
119 host.bridge.add_method( | |
120 "account_domain_new_get", | |
121 ".plugin", | |
122 in_sign="", | |
123 out_sign="s", | |
124 method=self.account_domain_new_get, | |
125 async_=False, | |
126 ) | |
127 host.bridge.add_method( | |
128 "account_dialog_ui_get", | |
129 ".plugin", | |
130 in_sign="s", | |
131 out_sign="s", | |
132 method=self._get_account_dialog_ui, | |
133 async_=False, | |
134 ) | |
135 host.bridge.add_method( | |
136 "credentials_xmpp_connect", | |
137 ".plugin", | |
138 in_sign="ss", | |
139 out_sign="b", | |
140 method=self.credentials_xmpp_connect, | |
141 async_=True, | |
142 ) | |
143 | |
144 self.fix_email_admins() | |
145 self._sessions = Sessions() | |
146 | |
147 self.__account_cb_id = host.register_callback( | |
148 self._account_dialog_cb, with_data=True | |
149 ) | |
150 self.__change_password_id = host.register_callback( | |
151 self.__change_password_cb, with_data=True | |
152 ) | |
153 | |
154 def delete_blog_callback(posts, comments): | |
155 return lambda data, profile: self.__delete_blog_posts_cb( | |
156 posts, comments, data, profile | |
157 ) | |
158 | |
159 self.__delete_posts_id = host.register_callback( | |
160 delete_blog_callback(True, False), with_data=True | |
161 ) | |
162 self.__delete_comments_id = host.register_callback( | |
163 delete_blog_callback(False, True), with_data=True | |
164 ) | |
165 self.__delete_posts_comments_id = host.register_callback( | |
166 delete_blog_callback(True, True), with_data=True | |
167 ) | |
168 | |
169 self.__delete_account_id = host.register_callback( | |
170 self.__delete_account_cb, with_data=True | |
171 ) | |
172 | |
173 # FIXME: remove this after some time, when the deprecated parameter is really abandoned | |
174 def fix_email_admins(self): | |
175 """Handle deprecated config option "admin_email" to fix the admin emails list""" | |
176 admin_email = self.config_get("admin_email") | |
177 if not admin_email: | |
178 return | |
179 log.warning( | |
180 "admin_email parameter is deprecated, please use email_admins_list instead" | |
181 ) | |
182 param_name = "email_admins_list" | |
183 try: | |
184 section = "" | |
185 value = self.host.memory.config_get(section, param_name, Exception) | |
186 except (configparser.NoOptionError, configparser.NoSectionError): | |
187 section = CONFIG_SECTION | |
188 value = self.host.memory.config_get( | |
189 section, param_name, default_conf[param_name] | |
190 ) | |
191 | |
192 value = set(value) | |
193 value.add(admin_email) | |
194 self.host.memory.config.set(section, param_name, ",".join(value)) | |
195 | |
196 def config_get(self, name, section=CONFIG_SECTION): | |
197 if name.startswith("email_"): | |
198 # XXX: email_ parameters were first in [plugin account] section | |
199 # but as it make more sense to have them in common with other plugins, | |
200 # they can now be in [DEFAULT] section | |
201 try: | |
202 value = self.host.memory.config_get(None, name, Exception) | |
203 except (configparser.NoOptionError, configparser.NoSectionError): | |
204 pass | |
205 else: | |
206 return value | |
207 | |
208 if section == CONFIG_SECTION: | |
209 default = default_conf[name] | |
210 else: | |
211 default = None | |
212 return self.host.memory.config_get(section, name, default) | |
213 | |
214 def _register_account(self, email, password, profile): | |
215 return self.registerAccount(email, password, None, profile) | |
216 | |
217 def registerAccount(self, email, password, jid_s, profile): | |
218 """Register a new profile, its associated XMPP account, send the confirmation emails. | |
219 | |
220 @param email (unicode): where to send to confirmation email to | |
221 @param password (unicode): password chosen by the user | |
222 while be used for profile *and* XMPP account | |
223 @param jid_s (unicode): JID to re-use or to register: | |
224 - non empty value: bind this JID to the new sat profile | |
225 - None or "": register a new JID on the local XMPP server | |
226 @param profile | |
227 @return Deferred | |
228 """ | |
229 d = self.create_profile(password, jid_s, profile) | |
230 d.addCallback(lambda __: self.send_emails(email, profile)) | |
231 return d | |
232 | |
233 def create_profile(self, password, jid_s, profile): | |
234 """Register a new profile and its associated XMPP account. | |
235 | |
236 @param password (unicode): password chosen by the user | |
237 while be used for profile *and* XMPP account | |
238 @param jid_s (unicode): JID to re-use or to register: | |
239 - non empty value: bind this JID to the new sat profile | |
240 - None or "": register a new JID on the local XMPP server | |
241 @param profile | |
242 @return Deferred | |
243 """ | |
244 if not password or not profile: | |
245 raise exceptions.DataError | |
246 | |
247 if profile.lower() in self.config_get("reserved_list"): | |
248 return defer.fail(Failure(exceptions.ConflictError)) | |
249 | |
250 d = self.host.memory.create_profile(profile, password) | |
251 d.addCallback(lambda __: self.profile_created(password, jid_s, profile)) | |
252 return d | |
253 | |
254 def profile_created(self, password, jid_s, profile): | |
255 """Create the XMPP account and set the profile connection parameters. | |
256 | |
257 @param password (unicode): password chosen by the user | |
258 @param jid_s (unicode): JID to re-use or to register: | |
259 - non empty value: bind this JID to the new sat profile | |
260 - None or empty: register a new JID on the local XMPP server | |
261 @param profile | |
262 @return: Deferred | |
263 """ | |
264 if jid_s: | |
265 d = defer.succeed(None) | |
266 jid_ = jid.JID(jid_s) | |
267 else: | |
268 jid_s = profile + "@" + self.account_domain_new_get() | |
269 jid_ = jid.JID(jid_s) | |
270 d = self.host.plugins["XEP-0077"].register_new_account(jid_, password) | |
271 | |
272 def setParams(__): | |
273 self.host.memory.param_set( | |
274 "JabberID", jid_s, "Connection", profile_key=profile | |
275 ) | |
276 d = self.host.memory.param_set( | |
277 "Password", password, "Connection", profile_key=profile | |
278 ) | |
279 return d | |
280 | |
281 def remove_profile(failure): | |
282 self.host.memory.profile_delete_async(profile) | |
283 return failure | |
284 | |
285 d.addCallback(lambda __: self.host.memory.start_session(password, profile)) | |
286 d.addCallback(setParams) | |
287 d.addCallback(lambda __: self.host.memory.stop_session(profile)) | |
288 d.addErrback(remove_profile) | |
289 return d | |
290 | |
291 def _send_email_eb(self, failure_, email): | |
292 # TODO: return error code to user | |
293 log.error( | |
294 _("Failed to send account creation confirmation to {email}: {msg}").format( | |
295 email=email, msg=failure_ | |
296 ) | |
297 ) | |
298 | |
299 def send_emails(self, email, profile): | |
300 # time to send the email | |
301 | |
302 domain = self.account_domain_new_get() | |
303 | |
304 # email to the administrators | |
305 admins_emails = self.config_get("email_admins_list") | |
306 if not admins_emails: | |
307 log.warning( | |
308 "No known admin email, we can't send email to administrator(s).\n" | |
309 "Please fill email_admins_list parameter" | |
310 ) | |
311 d_admin = defer.fail(exceptions.DataError("no admin email")) | |
312 else: | |
313 subject = _("New Libervia account created") | |
314 # there is no email when an existing XMPP account is used | |
315 body = f"New account created on {domain}: {profile} [{email or '<no email>'}]" | |
316 d_admin = sat_email.send_email( | |
317 self.host.memory.config, admins_emails, subject, body) | |
318 | |
319 admins_emails_txt = ", ".join(["<" + addr + ">" for addr in admins_emails]) | |
320 d_admin.addCallbacks( | |
321 lambda __: log.debug( | |
322 "Account creation notification sent to admin(s) {}".format( | |
323 admins_emails_txt | |
324 ) | |
325 ), | |
326 lambda __: log.error( | |
327 "Failed to send account creation notification to admin {}".format( | |
328 admins_emails_txt | |
329 ) | |
330 ), | |
331 ) | |
332 if not email: | |
333 # TODO: if use register with an existing account, an XMPP message should be sent | |
334 return d_admin | |
335 | |
336 jid_s = self.host.memory.param_get_a( | |
337 "JabberID", "Connection", profile_key=profile | |
338 ) | |
339 subject = _("Your Libervia account has been created") | |
340 body = _(WELCOME_MSG).format(profile=profile, jid=jid_s, domain=domain) | |
341 | |
342 # XXX: this will not fail when the email address doesn't exist | |
343 # FIXME: check email reception to validate email given by the user | |
344 # FIXME: delete the profile if the email could not been sent? | |
345 d_user = sat_email.send_email(self.host.memory.config, [email], subject, body) | |
346 d_user.addCallbacks( | |
347 lambda __: log.debug( | |
348 "Account creation confirmation sent to <{}>".format(email) | |
349 ), | |
350 self._send_email_eb, | |
351 errbackArgs=[email] | |
352 ) | |
353 return defer.DeferredList([d_user, d_admin]) | |
354 | |
355 def account_domain_new_get(self): | |
356 """get the domain that will be set to new account""" | |
357 | |
358 domain = self.config_get("new_account_domain") or self.config_get( | |
359 "xmpp_domain", None | |
360 ) | |
361 if not domain: | |
362 log.warning( | |
363 _( | |
364 'xmpp_domain needs to be set in sat.conf. Using "{default}" meanwhile' | |
365 ).format(default=DEFAULT_DOMAIN) | |
366 ) | |
367 return DEFAULT_DOMAIN | |
368 return domain | |
369 | |
370 def _get_account_dialog_ui(self, profile): | |
371 """Get the main dialog to manage your account | |
372 @param menu_data | |
373 @param profile: %(doc_profile)s | |
374 @return: XML of the dialog | |
375 """ | |
376 form_ui = xml_tools.XMLUI( | |
377 "form", | |
378 "tabs", | |
379 title=D_("Manage your account"), | |
380 submit_id=self.__account_cb_id, | |
381 ) | |
382 tab_container = form_ui.current_container | |
383 | |
384 tab_container.add_tab( | |
385 "update", D_("Change your password"), container=xml_tools.PairsContainer | |
386 ) | |
387 form_ui.addLabel(D_("Current profile password")) | |
388 form_ui.addPassword("current_passwd", value="") | |
389 form_ui.addLabel(D_("New password")) | |
390 form_ui.addPassword("new_passwd1", value="") | |
391 form_ui.addLabel(D_("New password (again)")) | |
392 form_ui.addPassword("new_passwd2", value="") | |
393 | |
394 # FIXME: uncomment and fix these features | |
395 """ | |
396 if 'GROUPBLOG' in self.host.plugins: | |
397 tab_container.add_tab("delete_posts", D_("Delete your posts"), container=xml_tools.PairsContainer) | |
398 form_ui.addLabel(D_("Current profile password")) | |
399 form_ui.addPassword("delete_posts_passwd", value="") | |
400 form_ui.addLabel(D_("Delete all your posts and their comments")) | |
401 form_ui.addBool("delete_posts_checkbox", "false") | |
402 form_ui.addLabel(D_("Delete all your comments on other's posts")) | |
403 form_ui.addBool("delete_comments_checkbox", "false") | |
404 | |
405 tab_container.add_tab("delete", D_("Delete your account"), container=xml_tools.PairsContainer) | |
406 form_ui.addLabel(D_("Current profile password")) | |
407 form_ui.addPassword("delete_passwd", value="") | |
408 form_ui.addLabel(D_("Delete your account")) | |
409 form_ui.addBool("delete_checkbox", "false") | |
410 """ | |
411 | |
412 return form_ui.toXml() | |
413 | |
414 @defer.inlineCallbacks | |
415 def _account_dialog_cb(self, data, profile): | |
416 """Called when the user submits the main account dialog | |
417 @param data | |
418 @param profile | |
419 """ | |
420 sat_cipher = yield self.host.memory.param_get_a_async( | |
421 C.PROFILE_PASS_PATH[1], C.PROFILE_PASS_PATH[0], profile_key=profile | |
422 ) | |
423 | |
424 @defer.inlineCallbacks | |
425 def verify(attempt): | |
426 auth = yield PasswordHasher.verify(attempt, sat_cipher) | |
427 defer.returnValue(auth) | |
428 | |
429 def error_ui(message=None): | |
430 if not message: | |
431 message = D_("The provided profile password doesn't match.") | |
432 error_ui = xml_tools.XMLUI("popup", title=D_("Attempt failure")) | |
433 error_ui.addText(message) | |
434 return {"xmlui": error_ui.toXml()} | |
435 | |
436 # check for account deletion | |
437 # FIXME: uncomment and fix these features | |
438 """ | |
439 delete_passwd = data[xml_tools.SAT_FORM_PREFIX + 'delete_passwd'] | |
440 delete_checkbox = data[xml_tools.SAT_FORM_PREFIX + 'delete_checkbox'] | |
441 if delete_checkbox == 'true': | |
442 verified = yield verify(delete_passwd) | |
443 assert isinstance(verified, bool) | |
444 if verified: | |
445 defer.returnValue(self.__delete_account(profile)) | |
446 defer.returnValue(error_ui()) | |
447 | |
448 # check for blog posts deletion | |
449 if 'GROUPBLOG' in self.host.plugins: | |
450 delete_posts_passwd = data[xml_tools.SAT_FORM_PREFIX + 'delete_posts_passwd'] | |
451 delete_posts_checkbox = data[xml_tools.SAT_FORM_PREFIX + 'delete_posts_checkbox'] | |
452 delete_comments_checkbox = data[xml_tools.SAT_FORM_PREFIX + 'delete_comments_checkbox'] | |
453 posts = delete_posts_checkbox == 'true' | |
454 comments = delete_comments_checkbox == 'true' | |
455 if posts or comments: | |
456 verified = yield verify(delete_posts_passwd) | |
457 assert isinstance(verified, bool) | |
458 if verified: | |
459 defer.returnValue(self.__delete_blog_posts(posts, comments, profile)) | |
460 defer.returnValue(error_ui()) | |
461 """ | |
462 | |
463 # check for password modification | |
464 current_passwd = data[xml_tools.SAT_FORM_PREFIX + "current_passwd"] | |
465 new_passwd1 = data[xml_tools.SAT_FORM_PREFIX + "new_passwd1"] | |
466 new_passwd2 = data[xml_tools.SAT_FORM_PREFIX + "new_passwd2"] | |
467 if new_passwd1 or new_passwd2: | |
468 verified = yield verify(current_passwd) | |
469 assert isinstance(verified, bool) | |
470 if verified: | |
471 if new_passwd1 == new_passwd2: | |
472 data = yield self.__change_password(new_passwd1, profile=profile) | |
473 defer.returnValue(data) | |
474 else: | |
475 defer.returnValue( | |
476 error_ui( | |
477 D_("The values entered for the new password are not equal.") | |
478 ) | |
479 ) | |
480 defer.returnValue(error_ui()) | |
481 | |
482 defer.returnValue({}) | |
483 | |
484 def __change_password(self, password, profile): | |
485 """Ask for a confirmation before changing the XMPP account and SàT profile passwords. | |
486 | |
487 @param password (str): the new password | |
488 @param profile (str): %(doc_profile)s | |
489 """ | |
490 session_id, __ = self._sessions.new_session( | |
491 {"new_password": password}, profile=profile | |
492 ) | |
493 form_ui = xml_tools.XMLUI( | |
494 "form", | |
495 title=D_("Change your password?"), | |
496 submit_id=self.__change_password_id, | |
497 session_id=session_id, | |
498 ) | |
499 form_ui.addText( | |
500 D_( | |
501 "Note for advanced users: this will actually change both your SàT profile password AND your XMPP account password." | |
502 ) | |
503 ) | |
504 form_ui.addText(D_("Continue with changing the password?")) | |
505 return {"xmlui": form_ui.toXml()} | |
506 | |
507 def __change_password_cb(self, data, profile): | |
508 """Actually change the user XMPP account and SàT profile password | |
509 @param data (dict) | |
510 @profile (str): %(doc_profile)s | |
511 """ | |
512 client = self.host.get_client(profile) | |
513 password = self._sessions.profile_get(data["session_id"], profile)["new_password"] | |
514 del self._sessions[data["session_id"]] | |
515 | |
516 def password_changed(__): | |
517 d = self.host.memory.param_set( | |
518 C.PROFILE_PASS_PATH[1], | |
519 password, | |
520 C.PROFILE_PASS_PATH[0], | |
521 profile_key=profile, | |
522 ) | |
523 d.addCallback( | |
524 lambda __: self.host.memory.param_set( | |
525 "Password", password, "Connection", profile_key=profile | |
526 ) | |
527 ) | |
528 confirm_ui = xml_tools.XMLUI("popup", title=D_("Confirmation")) | |
529 confirm_ui.addText(D_("Your password has been changed.")) | |
530 return defer.succeed({"xmlui": confirm_ui.toXml()}) | |
531 | |
532 def errback(failure): | |
533 error_ui = xml_tools.XMLUI("popup", title=D_("Error")) | |
534 error_ui.addText( | |
535 D_("Your password could not be changed: %s") % failure.getErrorMessage() | |
536 ) | |
537 return defer.succeed({"xmlui": error_ui.toXml()}) | |
538 | |
539 d = self.host.plugins["XEP-0077"].change_password(client, password) | |
540 d.addCallbacks(password_changed, errback) | |
541 return d | |
542 | |
543 def __delete_account(self, profile): | |
544 """Ask for a confirmation before deleting the XMPP account and SàT profile | |
545 @param profile | |
546 """ | |
547 form_ui = xml_tools.XMLUI( | |
548 "form", title=D_("Delete your account?"), submit_id=self.__delete_account_id | |
549 ) | |
550 form_ui.addText( | |
551 D_( | |
552 "If you confirm this dialog, you will be disconnected and then your XMPP account AND your SàT profile will both be DELETED." | |
553 ) | |
554 ) | |
555 target = D_( | |
556 "contact list, messages history, blog posts and comments" | |
557 if "GROUPBLOG" in self.host.plugins | |
558 else D_("contact list and messages history") | |
559 ) | |
560 form_ui.addText( | |
561 D_( | |
562 "All your data stored on %(server)s, including your %(target)s will be erased." | |
563 ) | |
564 % {"server": self.account_domain_new_get(), "target": target} | |
565 ) | |
566 form_ui.addText( | |
567 D_( | |
568 "There is no other confirmation dialog, this is the very last one! Are you sure?" | |
569 ) | |
570 ) | |
571 return {"xmlui": form_ui.toXml()} | |
572 | |
573 def __delete_account_cb(self, data, profile): | |
574 """Actually delete the XMPP account and SàT profile | |
575 | |
576 @param data | |
577 @param profile | |
578 """ | |
579 client = self.host.get_client(profile) | |
580 | |
581 def user_deleted(__): | |
582 | |
583 # FIXME: client should be disconnected at this point, so 2 next loop should be removed (to be confirmed) | |
584 for jid_ in client.roster._jids: # empty roster | |
585 client.presence.unsubscribe(jid_) | |
586 | |
587 for jid_ in self.host.memory.sub_waiting_get( | |
588 profile | |
589 ): # delete waiting subscriptions | |
590 self.host.memory.del_waiting_sub(jid_) | |
591 | |
592 delete_profile = lambda: self.host.memory.profile_delete_async( | |
593 profile, force=True | |
594 ) | |
595 if "GROUPBLOG" in self.host.plugins: | |
596 d = self.host.plugins["GROUPBLOG"].deleteAllGroupBlogsAndComments( | |
597 profile_key=profile | |
598 ) | |
599 d.addCallback(lambda __: delete_profile()) | |
600 else: | |
601 delete_profile() | |
602 | |
603 return defer.succeed({}) | |
604 | |
605 def errback(failure): | |
606 error_ui = xml_tools.XMLUI("popup", title=D_("Error")) | |
607 error_ui.addText( | |
608 D_("Your XMPP account could not be deleted: %s") | |
609 % failure.getErrorMessage() | |
610 ) | |
611 return defer.succeed({"xmlui": error_ui.toXml()}) | |
612 | |
613 d = self.host.plugins["XEP-0077"].unregister(client, jid.JID(client.jid.host)) | |
614 d.addCallbacks(user_deleted, errback) | |
615 return d | |
616 | |
617 def __delete_blog_posts(self, posts, comments, profile): | |
618 """Ask for a confirmation before deleting the blog posts | |
619 @param posts: delete all posts of the user (and their comments) | |
620 @param comments: delete all the comments of the user on other's posts | |
621 @param data | |
622 @param profile | |
623 """ | |
624 if posts: | |
625 if comments: # delete everything | |
626 form_ui = xml_tools.XMLUI( | |
627 "form", | |
628 title=D_("Delete all your (micro-)blog posts and comments?"), | |
629 submit_id=self.__delete_posts_comments_id, | |
630 ) | |
631 form_ui.addText( | |
632 D_( | |
633 "If you confirm this dialog, all the (micro-)blog data you submitted will be erased." | |
634 ) | |
635 ) | |
636 form_ui.addText( | |
637 D_( | |
638 "These are the public and private posts and comments you sent to any group." | |
639 ) | |
640 ) | |
641 form_ui.addText( | |
642 D_( | |
643 "There is no other confirmation dialog, this is the very last one! Are you sure?" | |
644 ) | |
645 ) | |
646 else: # delete only the posts | |
647 form_ui = xml_tools.XMLUI( | |
648 "form", | |
649 title=D_("Delete all your (micro-)blog posts?"), | |
650 submit_id=self.__delete_posts_id, | |
651 ) | |
652 form_ui.addText( | |
653 D_( | |
654 "If you confirm this dialog, all the public and private posts you sent to any group will be erased." | |
655 ) | |
656 ) | |
657 form_ui.addText( | |
658 D_( | |
659 "There is no other confirmation dialog, this is the very last one! Are you sure?" | |
660 ) | |
661 ) | |
662 elif comments: # delete only the comments | |
663 form_ui = xml_tools.XMLUI( | |
664 "form", | |
665 title=D_("Delete all your (micro-)blog comments?"), | |
666 submit_id=self.__delete_comments_id, | |
667 ) | |
668 form_ui.addText( | |
669 D_( | |
670 "If you confirm this dialog, all the public and private comments you made on other people's posts will be erased." | |
671 ) | |
672 ) | |
673 form_ui.addText( | |
674 D_( | |
675 "There is no other confirmation dialog, this is the very last one! Are you sure?" | |
676 ) | |
677 ) | |
678 | |
679 return {"xmlui": form_ui.toXml()} | |
680 | |
681 def __delete_blog_posts_cb(self, posts, comments, data, profile): | |
682 """Actually delete the XMPP account and SàT profile | |
683 @param posts: delete all posts of the user (and their comments) | |
684 @param comments: delete all the comments of the user on other's posts | |
685 @param profile | |
686 """ | |
687 if posts: | |
688 if comments: | |
689 target = D_("blog posts and comments") | |
690 d = self.host.plugins["GROUPBLOG"].deleteAllGroupBlogsAndComments( | |
691 profile_key=profile | |
692 ) | |
693 else: | |
694 target = D_("blog posts") | |
695 d = self.host.plugins["GROUPBLOG"].deleteAllGroupBlogs( | |
696 profile_key=profile | |
697 ) | |
698 elif comments: | |
699 target = D_("comments") | |
700 d = self.host.plugins["GROUPBLOG"].deleteAllGroupBlogsComments( | |
701 profile_key=profile | |
702 ) | |
703 | |
704 def deleted(result): | |
705 ui = xml_tools.XMLUI("popup", title=D_("Deletion confirmation")) | |
706 # TODO: change the message when delete/retract notifications are done with XEP-0060 | |
707 ui.addText(D_("Your %(target)s have been deleted.") % {"target": target}) | |
708 ui.addText( | |
709 D_( | |
710 "Known issue of the demo version: you need to refresh the page to make the deleted posts actually disappear." | |
711 ) | |
712 ) | |
713 return defer.succeed({"xmlui": ui.toXml()}) | |
714 | |
715 def errback(failure): | |
716 error_ui = xml_tools.XMLUI("popup", title=D_("Error")) | |
717 error_ui.addText( | |
718 D_("Your %(target)s could not be deleted: %(message)s") | |
719 % {"target": target, "message": failure.getErrorMessage()} | |
720 ) | |
721 return defer.succeed({"xmlui": error_ui.toXml()}) | |
722 | |
723 d.addCallbacks(deleted, errback) | |
724 return d | |
725 | |
726 def credentials_xmpp_connect(self, jid_s, password): | |
727 """Create and connect a new SàT profile using the given XMPP credentials. | |
728 | |
729 Re-use given JID and XMPP password for the profile name and profile password. | |
730 @param jid_s (unicode): JID | |
731 @param password (unicode): XMPP password | |
732 @return Deferred(bool) | |
733 @raise exceptions.PasswordError, exceptions.ConflictError | |
734 """ | |
735 try: # be sure that the profile doesn't exist yet | |
736 self.host.memory.get_profile_name(jid_s) | |
737 except exceptions.ProfileUnknownError: | |
738 pass | |
739 else: | |
740 raise exceptions.ConflictError | |
741 | |
742 d = self.create_profile(password, jid_s, jid_s) | |
743 d.addCallback( | |
744 lambda __: self.host.memory.get_profile_name(jid_s) | |
745 ) # checks if the profile has been successfuly created | |
746 d.addCallback(lambda profile: defer.ensureDeferred( | |
747 self.host.connect(profile, password, {}, 0))) | |
748 | |
749 def connected(result): | |
750 self.send_emails(None, profile=jid_s) | |
751 return result | |
752 | |
753 def remove_profile( | |
754 failure | |
755 ): # profile has been successfully created but the XMPP credentials are wrong! | |
756 log.debug( | |
757 "Removing previously auto-created profile: %s" % failure.getErrorMessage() | |
758 ) | |
759 self.host.memory.profile_delete_async(jid_s) | |
760 raise failure | |
761 | |
762 # FIXME: we don't catch the case where the JID host is not an XMPP server, and the user | |
763 # has to wait until the DBUS timeout ; as a consequence, emails are sent to the admins | |
764 # and the profile is not deleted. When the host exists, remove_profile is well called. | |
765 d.addCallbacks(connected, remove_profile) | |
766 return d |