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
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