Mercurial > libervia-backend
comparison sat/plugins/plugin_misc_account.py @ 2562:26edcf3a30eb
core, setup: huge cleaning:
- moved directories from src and frontends/src to sat and sat_frontends, which is the recommanded naming convention
- move twisted directory to root
- removed all hacks from setup.py, and added missing dependencies, it is now clean
- use https URL for website in setup.py
- removed "Environment :: X11 Applications :: GTK", as wix is deprecated and removed
- renamed sat.sh to sat and fixed its installation
- added python_requires to specify Python version needed
- replaced glib2reactor which use deprecated code by gtk3reactor
sat can now be installed directly from virtualenv without using --system-site-packages anymore \o/
author | Goffi <goffi@goffi.org> |
---|---|
date | Mon, 02 Apr 2018 19:44:50 +0200 |
parents | src/plugins/plugin_misc_account.py@0046283a285d |
children | 56f94936df1e |
comparison
equal
deleted
inserted
replaced
2561:bd30dc3ffe5a | 2562:26edcf3a30eb |
---|---|
1 #!/usr/bin/env python2 | |
2 # -*- coding: utf-8 -*- | |
3 | |
4 # SAT plugin for account creation (experimental) | |
5 # Copyright (C) 2009-2018 Jérôme Poisson (goffi@goffi.org) | |
6 | |
7 # This program is free software: you can redistribute it and/or modify | |
8 # it under the terms of the GNU Affero General Public License as published by | |
9 # the Free Software Foundation, either version 3 of the License, or | |
10 # (at your option) any later version. | |
11 | |
12 # This program is distributed in the hope that it will be useful, | |
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of | |
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
15 # GNU Affero General Public License for more details. | |
16 | |
17 # You should have received a copy of the GNU Affero General Public License | |
18 # along with this program. If not, see <http://www.gnu.org/licenses/>. | |
19 | |
20 from sat.core.i18n import _, D_ | |
21 from sat.core.log import getLogger | |
22 log = getLogger(__name__) | |
23 from sat.core import exceptions | |
24 from sat.tools import xml_tools | |
25 from sat.memory.memory import Sessions | |
26 from sat.memory.crypto import PasswordHasher | |
27 from sat.core.constants import Const as C | |
28 import ConfigParser | |
29 from twisted.internet import defer | |
30 from twisted.python.failure import Failure | |
31 from twisted.words.protocols.jabber import jid | |
32 from sat.tools import email as sat_email | |
33 | |
34 # FIXME: this plugin code is old and need a cleaning | |
35 # TODO: account deletion/password change need testing | |
36 | |
37 | |
38 PLUGIN_INFO = { | |
39 C.PI_NAME: "Account Plugin", | |
40 C.PI_IMPORT_NAME: "MISC-ACCOUNT", | |
41 C.PI_TYPE: "MISC", | |
42 C.PI_PROTOCOLS: [], | |
43 C.PI_DEPENDENCIES: ["XEP-0077"], | |
44 C.PI_RECOMMENDATIONS: ['GROUPBLOG'], | |
45 C.PI_MAIN: "MiscAccount", | |
46 C.PI_HANDLER: "no", | |
47 C.PI_DESCRIPTION: _(u"""SàT account creation""") | |
48 } | |
49 | |
50 CONFIG_SECTION = "plugin account" | |
51 | |
52 # You need do adapt the following consts to your server | |
53 # all theses values (key=option name, value=default) can (and should) be overriden in sat.conf | |
54 # in section CONFIG_SECTION | |
55 | |
56 default_conf = {"email_from": "NOREPLY@example.net", | |
57 "email_server": "localhost", | |
58 "email_sender_domain": "", | |
59 "email_port": 25, | |
60 "email_username": "", | |
61 "email_password": "", | |
62 "email_starttls": "false", | |
63 "email_auth": "false", | |
64 "email_admins_list": [], | |
65 "admin_email": "", | |
66 "new_account_server": "localhost", | |
67 "new_account_domain": "", # use xmpp_domain if not found | |
68 "reserved_list": ['libervia'] # profiles which can't be used | |
69 } | |
70 | |
71 WELCOME_MSG = D_(u"""Welcome to Libervia, the web interface of Salut à Toi. | |
72 | |
73 Your account on {domain} has been successfully created. | |
74 This is a demonstration version to show you the current status of the project. | |
75 It is still under development, please keep it in mind! | |
76 | |
77 Here is your connection information: | |
78 | |
79 Login on {domain}: {profile} | |
80 Jabber ID (JID): {jid} | |
81 Your password has been chosen by yourself during registration. | |
82 | |
83 In the beginning, you have nobody to talk to. To find some contacts, you may use the users' directory: | |
84 - make yourself visible in "Service / Directory subscription". | |
85 - search for people with "Contacts" / Search directory". | |
86 | |
87 Any feedback welcome. Thank you! | |
88 | |
89 Salut à Toi association | |
90 https://www.salut-a-toi.org | |
91 """) | |
92 | |
93 DEFAULT_DOMAIN = u"example.net" | |
94 | |
95 | |
96 class MiscAccount(object): | |
97 """Account plugin: create a SàT + XMPP account, used by Libervia""" | |
98 # XXX: This plugin was initialy a Q&D one used for the demo. | |
99 # TODO: cleaning, separate email handling, more configuration/tests, fixes | |
100 | |
101 | |
102 def __init__(self, host): | |
103 log.info(_(u"Plugin Account initialization")) | |
104 self.host = host | |
105 host.bridge.addMethod("registerSatAccount", ".plugin", in_sign='sss', out_sign='', method=self._registerAccount, async=True) | |
106 host.bridge.addMethod("getNewAccountDomain", ".plugin", in_sign='', out_sign='s', method=self.getNewAccountDomain, async=False) | |
107 host.bridge.addMethod("getAccountDialogUI", ".plugin", in_sign='s', out_sign='s', method=self._getAccountDialogUI, async=False) | |
108 host.bridge.addMethod("asyncConnectWithXMPPCredentials", ".plugin", in_sign='ss', out_sign='b', method=self.asyncConnectWithXMPPCredentials, async=True) | |
109 | |
110 self.fixEmailAdmins() | |
111 self._sessions = Sessions() | |
112 | |
113 self.__account_cb_id = host.registerCallback(self._accountDialogCb, with_data=True) | |
114 self.__change_password_id = host.registerCallback(self.__changePasswordCb, with_data=True) | |
115 | |
116 def deleteBlogCallback(posts, comments): | |
117 return lambda data, profile: self.__deleteBlogPostsCb(posts, comments, data, profile) | |
118 | |
119 self.__delete_posts_id = host.registerCallback(deleteBlogCallback(True, False), with_data=True) | |
120 self.__delete_comments_id = host.registerCallback(deleteBlogCallback(False, True), with_data=True) | |
121 self.__delete_posts_comments_id = host.registerCallback(deleteBlogCallback(True, True), with_data=True) | |
122 | |
123 self.__delete_account_id = host.registerCallback(self.__deleteAccountCb, with_data=True) | |
124 | |
125 | |
126 # FIXME: remove this after some time, when the deprecated parameter is really abandoned | |
127 def fixEmailAdmins(self): | |
128 """Handle deprecated config option "admin_email" to fix the admin emails list""" | |
129 admin_email = self.getConfig('admin_email') | |
130 if not admin_email: | |
131 return | |
132 log.warning(u"admin_email parameter is deprecated, please use email_admins_list instead") | |
133 param_name = "email_admins_list" | |
134 try: | |
135 section = "" | |
136 value = self.host.memory.getConfig(section, param_name, Exception) | |
137 except (ConfigParser.NoOptionError, ConfigParser.NoSectionError): | |
138 section = CONFIG_SECTION | |
139 value = self.host.memory.getConfig(section, param_name, default_conf[param_name]) | |
140 | |
141 value = set(value) | |
142 value.add(admin_email) | |
143 self.host.memory.config.set(section, param_name, ",".join(value)) | |
144 | |
145 def getConfig(self, name, section=CONFIG_SECTION): | |
146 if name.startswith("email_"): | |
147 # XXX: email_ parameters were first in [plugin account] section | |
148 # but as it make more sense to have them in common with other plugins, | |
149 # they can now be in [DEFAULT] section | |
150 try: | |
151 value = self.host.memory.getConfig(None, name, Exception) | |
152 except (ConfigParser.NoOptionError, ConfigParser.NoSectionError): | |
153 pass | |
154 else: | |
155 return value | |
156 | |
157 if section == CONFIG_SECTION: | |
158 default = default_conf[name] | |
159 else: | |
160 default = None | |
161 return self.host.memory.getConfig(section, name, default) | |
162 | |
163 def _registerAccount(self, email, password, profile): | |
164 return self.registerAccount(email, password, None, profile) | |
165 | |
166 def registerAccount(self, email, password, jid_s, profile): | |
167 """Register a new profile, its associated XMPP account, send the confirmation emails. | |
168 | |
169 @param email (unicode): where to send to confirmation email to | |
170 @param password (unicode): password chosen by the user | |
171 while be used for profile *and* XMPP account | |
172 @param jid_s (unicode): JID to re-use or to register: | |
173 - non empty value: bind this JID to the new sat profile | |
174 - None or "": register a new JID on the local XMPP server | |
175 @param profile | |
176 @return Deferred | |
177 """ | |
178 d = self.createProfile(password, jid_s, profile) | |
179 d.addCallback(lambda dummy: self.sendEmails(email, profile)) | |
180 return d | |
181 | |
182 def createProfile(self, password, jid_s, profile): | |
183 """Register a new profile and its associated XMPP account. | |
184 | |
185 @param password (unicode): password chosen by the user | |
186 while be used for profile *and* XMPP account | |
187 @param jid_s (unicode): JID to re-use or to register: | |
188 - non empty value: bind this JID to the new sat profile | |
189 - None or "": register a new JID on the local XMPP server | |
190 @param profile | |
191 @return Deferred | |
192 """ | |
193 if not password or not profile: | |
194 raise exceptions.DataError | |
195 | |
196 if profile.lower() in self.getConfig('reserved_list'): | |
197 return defer.fail(Failure(exceptions.ConflictError)) | |
198 | |
199 d = self.host.memory.createProfile(profile, password) | |
200 d.addCallback(lambda dummy: self.profileCreated(password, jid_s, profile)) | |
201 return d | |
202 | |
203 def profileCreated(self, password, jid_s, profile): | |
204 """Create the XMPP account and set the profile connection parameters. | |
205 | |
206 @param password (unicode): password chosen by the user | |
207 @param jid_s (unicode): JID to re-use or to register: | |
208 - non empty value: bind this JID to the new sat profile | |
209 - None or empty: register a new JID on the local XMPP server | |
210 @param profile | |
211 @return: Deferred | |
212 """ | |
213 if jid_s: | |
214 d = defer.succeed(None) | |
215 jid_ = jid.JID(jid_s) | |
216 else: | |
217 jid_s = profile + u"@" + self.getNewAccountDomain() | |
218 jid_ = jid.JID(jid_s) | |
219 d = self.host.plugins['XEP-0077'].registerNewAccount(jid_, password) | |
220 | |
221 def setParams(dummy): | |
222 self.host.memory.setParam("JabberID", jid_s, "Connection", profile_key=profile) | |
223 d = self.host.memory.setParam("Password", password, "Connection", profile_key=profile) | |
224 return d | |
225 | |
226 def removeProfile(failure): | |
227 self.host.memory.asyncDeleteProfile(profile) | |
228 return failure | |
229 | |
230 d.addCallback(lambda dummy: self.host.memory.startSession(password, profile)) | |
231 d.addCallback(setParams) | |
232 d.addCallback(lambda dummy: self.host.memory.stopSession(profile)) | |
233 d.addErrback(removeProfile) | |
234 return d | |
235 | |
236 def _sendEmailEb(self, failure_, email): | |
237 # TODO: return error code to user | |
238 log.error(_(u"Failed to send account creation confirmation to {email}: {msg}").format( | |
239 email = email, | |
240 msg = failure_)) | |
241 | |
242 def sendEmails(self, email, profile): | |
243 # time to send the email | |
244 | |
245 domain = self.getNewAccountDomain() | |
246 | |
247 # email to the administrators | |
248 admins_emails = self.getConfig('email_admins_list') | |
249 if not admins_emails: | |
250 log.warning(u"No known admin email, we can't send email to administrator(s).\nPlease fill email_admins_list parameter") | |
251 d_admin = defer.fail(exceptions.DataError("no admin email")) | |
252 else: | |
253 subject = _(u'New Libervia account created') | |
254 body = (u"""New account created: {profile} [{email}]""".format( | |
255 profile = profile, | |
256 # there is no email when an existing XMPP account is used | |
257 email = email or u"<no email>")) | |
258 d_admin = sat_email.sendEmail(self.host, admins_emails, subject, body) | |
259 | |
260 admins_emails_txt = u', '.join([u'<' + addr + u'>' for addr in admins_emails]) | |
261 d_admin.addCallbacks(lambda dummy: log.debug(u"Account creation notification sent to admin(s) {}".format(admins_emails_txt)), | |
262 lambda dummy: log.error(u"Failed to send account creation notification to admin {}".format(admins_emails_txt))) | |
263 if not email: | |
264 # TODO: if use register with an existing account, an XMPP message should be sent | |
265 return d_admin | |
266 | |
267 jid_s = self.host.memory.getParamA(u"JabberID", u"Connection", profile_key=profile) | |
268 subject = _(u'Your Libervia account has been created') | |
269 body = (_(WELCOME_MSG).format(profile=profile, jid=jid_s, domain=domain)) | |
270 | |
271 # XXX: this will not fail when the email address doesn't exist | |
272 # FIXME: check email reception to validate email given by the user | |
273 # FIXME: delete the profile if the email could not been sent? | |
274 d_user = sat_email.sendEmail(self.host, [email], subject, body) | |
275 d_user.addCallbacks(lambda dummy: log.debug(u"Account creation confirmation sent to <{}>".format(email)), | |
276 self._sendEmailEb) | |
277 return defer.DeferredList([d_user, d_admin]) | |
278 | |
279 def getNewAccountDomain(self): | |
280 """get the domain that will be set to new account""" | |
281 | |
282 domain = self.getConfig('new_account_domain') or self.getConfig('xmpp_domain', None) | |
283 if not domain: | |
284 log.warning(_(u'xmpp_domain needs to be set in sat.conf. Using "{default}" meanwhile').format(default=DEFAULT_DOMAIN)) | |
285 return DEFAULT_DOMAIN | |
286 return domain | |
287 | |
288 def _getAccountDialogUI(self, profile): | |
289 """Get the main dialog to manage your account | |
290 @param menu_data | |
291 @param profile: %(doc_profile)s | |
292 @return: XML of the dialog | |
293 """ | |
294 form_ui = xml_tools.XMLUI("form", "tabs", title=D_("Manage your account"), submit_id=self.__account_cb_id) | |
295 tab_container = form_ui.current_container | |
296 | |
297 tab_container.addTab("update", D_("Change your password"), container=xml_tools.PairsContainer) | |
298 form_ui.addLabel(D_("Current profile password")) | |
299 form_ui.addPassword("current_passwd", value="") | |
300 form_ui.addLabel(D_("New password")) | |
301 form_ui.addPassword("new_passwd1", value="") | |
302 form_ui.addLabel(D_("New password (again)")) | |
303 form_ui.addPassword("new_passwd2", value="") | |
304 | |
305 # FIXME: uncomment and fix these features | |
306 """ | |
307 if 'GROUPBLOG' in self.host.plugins: | |
308 tab_container.addTab("delete_posts", D_("Delete your posts"), container=xml_tools.PairsContainer) | |
309 form_ui.addLabel(D_("Current profile password")) | |
310 form_ui.addPassword("delete_posts_passwd", value="") | |
311 form_ui.addLabel(D_("Delete all your posts and their comments")) | |
312 form_ui.addBool("delete_posts_checkbox", "false") | |
313 form_ui.addLabel(D_("Delete all your comments on other's posts")) | |
314 form_ui.addBool("delete_comments_checkbox", "false") | |
315 | |
316 tab_container.addTab("delete", D_("Delete your account"), container=xml_tools.PairsContainer) | |
317 form_ui.addLabel(D_("Current profile password")) | |
318 form_ui.addPassword("delete_passwd", value="") | |
319 form_ui.addLabel(D_("Delete your account")) | |
320 form_ui.addBool("delete_checkbox", "false") | |
321 """ | |
322 | |
323 return form_ui.toXml() | |
324 | |
325 @defer.inlineCallbacks | |
326 def _accountDialogCb(self, data, profile): | |
327 """Called when the user submits the main account dialog | |
328 @param data | |
329 @param profile | |
330 """ | |
331 sat_cipher = yield self.host.memory.asyncGetParamA(C.PROFILE_PASS_PATH[1], C.PROFILE_PASS_PATH[0], profile_key=profile) | |
332 | |
333 @defer.inlineCallbacks | |
334 def verify(attempt): | |
335 auth = yield PasswordHasher.verify(attempt, sat_cipher) | |
336 defer.returnValue(auth) | |
337 | |
338 def error_ui(message=None): | |
339 if not message: | |
340 message = D_("The provided profile password doesn't match.") | |
341 error_ui = xml_tools.XMLUI("popup", title=D_("Attempt failure")) | |
342 error_ui.addText(message) | |
343 return {'xmlui': error_ui.toXml()} | |
344 | |
345 # check for account deletion | |
346 # FIXME: uncomment and fix these features | |
347 """ | |
348 delete_passwd = data[xml_tools.SAT_FORM_PREFIX + 'delete_passwd'] | |
349 delete_checkbox = data[xml_tools.SAT_FORM_PREFIX + 'delete_checkbox'] | |
350 if delete_checkbox == 'true': | |
351 verified = yield verify(delete_passwd) | |
352 assert isinstance(verified, bool) | |
353 if verified: | |
354 defer.returnValue(self.__deleteAccount(profile)) | |
355 defer.returnValue(error_ui()) | |
356 | |
357 # check for blog posts deletion | |
358 if 'GROUPBLOG' in self.host.plugins: | |
359 delete_posts_passwd = data[xml_tools.SAT_FORM_PREFIX + 'delete_posts_passwd'] | |
360 delete_posts_checkbox = data[xml_tools.SAT_FORM_PREFIX + 'delete_posts_checkbox'] | |
361 delete_comments_checkbox = data[xml_tools.SAT_FORM_PREFIX + 'delete_comments_checkbox'] | |
362 posts = delete_posts_checkbox == 'true' | |
363 comments = delete_comments_checkbox == 'true' | |
364 if posts or comments: | |
365 verified = yield verify(delete_posts_passwd) | |
366 assert isinstance(verified, bool) | |
367 if verified: | |
368 defer.returnValue(self.__deleteBlogPosts(posts, comments, profile)) | |
369 defer.returnValue(error_ui()) | |
370 """ | |
371 | |
372 # check for password modification | |
373 current_passwd = data[xml_tools.SAT_FORM_PREFIX + 'current_passwd'] | |
374 new_passwd1 = data[xml_tools.SAT_FORM_PREFIX + 'new_passwd1'] | |
375 new_passwd2 = data[xml_tools.SAT_FORM_PREFIX + 'new_passwd2'] | |
376 if new_passwd1 or new_passwd2: | |
377 verified = yield verify(current_passwd) | |
378 assert isinstance(verified, bool) | |
379 if verified: | |
380 if new_passwd1 == new_passwd2: | |
381 data = yield self.__changePassword(new_passwd1, profile=profile) | |
382 defer.returnValue(data) | |
383 else: | |
384 defer.returnValue(error_ui(D_("The values entered for the new password are not equal."))) | |
385 defer.returnValue(error_ui()) | |
386 | |
387 defer.returnValue({}) | |
388 | |
389 def __changePassword(self, password, profile): | |
390 """Ask for a confirmation before changing the XMPP account and SàT profile passwords. | |
391 | |
392 @param password (str): the new password | |
393 @param profile (str): %(doc_profile)s | |
394 """ | |
395 session_id, dummy = self._sessions.newSession({'new_password': password}, profile=profile) | |
396 form_ui = xml_tools.XMLUI("form", title=D_("Change your password?"), submit_id=self.__change_password_id, session_id=session_id) | |
397 form_ui.addText(D_("Note for advanced users: this will actually change both your SàT profile password AND your XMPP account password.")) | |
398 form_ui.addText(D_("Continue with changing the password?")) | |
399 return {'xmlui': form_ui.toXml()} | |
400 | |
401 def __changePasswordCb(self, data, profile): | |
402 """Actually change the user XMPP account and SàT profile password | |
403 @param data (dict) | |
404 @profile (str): %(doc_profile)s | |
405 """ | |
406 client = self.host.getClient(profile) | |
407 password = self._sessions.profileGet(data['session_id'], profile)['new_password'] | |
408 del self._sessions[data['session_id']] | |
409 | |
410 def passwordChanged(dummy): | |
411 d = self.host.memory.setParam(C.PROFILE_PASS_PATH[1], password, C.PROFILE_PASS_PATH[0], profile_key=profile) | |
412 d.addCallback(lambda dummy: self.host.memory.setParam("Password", password, "Connection", profile_key=profile)) | |
413 confirm_ui = xml_tools.XMLUI("popup", title=D_("Confirmation")) | |
414 confirm_ui.addText(D_("Your password has been changed.")) | |
415 return defer.succeed({'xmlui': confirm_ui.toXml()}) | |
416 | |
417 def errback(failure): | |
418 error_ui = xml_tools.XMLUI("popup", title=D_("Error")) | |
419 error_ui.addText(D_("Your password could not be changed: %s") % failure.getErrorMessage()) | |
420 return defer.succeed({'xmlui': error_ui.toXml()}) | |
421 | |
422 d = self.host.plugins['XEP-0077'].changePassword(client, password) | |
423 d.addCallbacks(passwordChanged, errback) | |
424 return d | |
425 | |
426 def __deleteAccount(self, profile): | |
427 """Ask for a confirmation before deleting the XMPP account and SàT profile | |
428 @param profile | |
429 """ | |
430 form_ui = xml_tools.XMLUI("form", title=D_("Delete your account?"), submit_id=self.__delete_account_id) | |
431 form_ui.addText(D_("If you confirm this dialog, you will be disconnected and then your XMPP account AND your SàT profile will both be DELETED.")) | |
432 target = D_('contact list, messages history, blog posts and comments' if 'GROUPBLOG' in self.host.plugins else D_('contact list and messages history')) | |
433 form_ui.addText(D_("All your data stored on %(server)s, including your %(target)s will be erased.") % {'server': self.getNewAccountDomain(), 'target': target}) | |
434 form_ui.addText(D_("There is no other confirmation dialog, this is the very last one! Are you sure?")) | |
435 return {'xmlui': form_ui.toXml()} | |
436 | |
437 def __deleteAccountCb(self, data, profile): | |
438 """Actually delete the XMPP account and SàT profile | |
439 | |
440 @param data | |
441 @param profile | |
442 """ | |
443 client = self.host.getClient(profile) | |
444 def userDeleted(dummy): | |
445 | |
446 # FIXME: client should be disconnected at this point, so 2 next loop should be removed (to be confirmed) | |
447 for jid_ in client.roster._jids: # empty roster | |
448 client.presence.unsubscribe(jid_) | |
449 | |
450 for jid_ in self.host.memory.getWaitingSub(profile): # delete waiting subscriptions | |
451 self.host.memory.delWaitingSub(jid_) | |
452 | |
453 delete_profile = lambda: self.host.memory.asyncDeleteProfile(profile, force=True) | |
454 if 'GROUPBLOG' in self.host.plugins: | |
455 d = self.host.plugins['GROUPBLOG'].deleteAllGroupBlogsAndComments(profile_key=profile) | |
456 d.addCallback(lambda dummy: delete_profile()) | |
457 else: | |
458 delete_profile() | |
459 | |
460 return defer.succeed({}) | |
461 | |
462 def errback(failure): | |
463 error_ui = xml_tools.XMLUI("popup", title=D_("Error")) | |
464 error_ui.addText(D_("Your XMPP account could not be deleted: %s") % failure.getErrorMessage()) | |
465 return defer.succeed({'xmlui': error_ui.toXml()}) | |
466 | |
467 d = self.host.plugins['XEP-0077'].unregister(client, jid.JID(client.jid.host)) | |
468 d.addCallbacks(userDeleted, errback) | |
469 return d | |
470 | |
471 def __deleteBlogPosts(self, posts, comments, profile): | |
472 """Ask for a confirmation before deleting the blog posts | |
473 @param posts: delete all posts of the user (and their comments) | |
474 @param comments: delete all the comments of the user on other's posts | |
475 @param data | |
476 @param profile | |
477 """ | |
478 if posts: | |
479 if comments: # delete everything | |
480 form_ui = xml_tools.XMLUI("form", title=D_("Delete all your (micro-)blog posts and comments?"), submit_id=self.__delete_posts_comments_id) | |
481 form_ui.addText(D_("If you confirm this dialog, all the (micro-)blog data you submitted will be erased.")) | |
482 form_ui.addText(D_("These are the public and private posts and comments you sent to any group.")) | |
483 form_ui.addText(D_("There is no other confirmation dialog, this is the very last one! Are you sure?")) | |
484 else: # delete only the posts | |
485 form_ui = xml_tools.XMLUI("form", title=D_("Delete all your (micro-)blog posts?"), submit_id=self.__delete_posts_id) | |
486 form_ui.addText(D_("If you confirm this dialog, all the public and private posts you sent to any group will be erased.")) | |
487 form_ui.addText(D_("There is no other confirmation dialog, this is the very last one! Are you sure?")) | |
488 elif comments: # delete only the comments | |
489 form_ui = xml_tools.XMLUI("form", title=D_("Delete all your (micro-)blog comments?"), submit_id=self.__delete_comments_id) | |
490 form_ui.addText(D_("If you confirm this dialog, all the public and private comments you made on other people's posts will be erased.")) | |
491 form_ui.addText(D_("There is no other confirmation dialog, this is the very last one! Are you sure?")) | |
492 | |
493 return {'xmlui': form_ui.toXml()} | |
494 | |
495 def __deleteBlogPostsCb(self, posts, comments, data, profile): | |
496 """Actually delete the XMPP account and SàT profile | |
497 @param posts: delete all posts of the user (and their comments) | |
498 @param comments: delete all the comments of the user on other's posts | |
499 @param profile | |
500 """ | |
501 if posts: | |
502 if comments: | |
503 target = D_('blog posts and comments') | |
504 d = self.host.plugins['GROUPBLOG'].deleteAllGroupBlogsAndComments(profile_key=profile) | |
505 else: | |
506 target = D_('blog posts') | |
507 d = self.host.plugins['GROUPBLOG'].deleteAllGroupBlogs(profile_key=profile) | |
508 elif comments: | |
509 target = D_('comments') | |
510 d = self.host.plugins['GROUPBLOG'].deleteAllGroupBlogsComments(profile_key=profile) | |
511 | |
512 def deleted(result): | |
513 ui = xml_tools.XMLUI("popup", title=D_("Deletion confirmation")) | |
514 # TODO: change the message when delete/retract notifications are done with XEP-0060 | |
515 ui.addText(D_("Your %(target)s have been deleted.") % {'target': target}) | |
516 ui.addText(D_("Known issue of the demo version: you need to refresh the page to make the deleted posts actually disappear.")) | |
517 return defer.succeed({'xmlui': ui.toXml()}) | |
518 | |
519 def errback(failure): | |
520 error_ui = xml_tools.XMLUI("popup", title=D_("Error")) | |
521 error_ui.addText(D_("Your %(target)s could not be deleted: %(message)s") % {'target': target, 'message': failure.getErrorMessage()}) | |
522 return defer.succeed({'xmlui': error_ui.toXml()}) | |
523 | |
524 d.addCallbacks(deleted, errback) | |
525 return d | |
526 | |
527 def asyncConnectWithXMPPCredentials(self, jid_s, password): | |
528 """Create and connect a new SàT profile using the given XMPP credentials. | |
529 | |
530 Re-use given JID and XMPP password for the profile name and profile password. | |
531 @param jid_s (unicode): JID | |
532 @param password (unicode): XMPP password | |
533 @return Deferred(bool) | |
534 @raise exceptions.PasswordError, exceptions.ConflictError | |
535 """ | |
536 try: # be sure that the profile doesn't exist yet | |
537 self.host.memory.getProfileName(jid_s) | |
538 except exceptions.ProfileUnknownError: | |
539 pass | |
540 else: | |
541 raise exceptions.ConflictError | |
542 | |
543 d = self.createProfile(password, jid_s, jid_s) | |
544 d.addCallback(lambda dummy: self.host.memory.getProfileName(jid_s)) # checks if the profile has been successfuly created | |
545 d.addCallback(self.host.connect, password, {}, 0) | |
546 | |
547 | |
548 def connected(result): | |
549 self.sendEmails(None, profile=jid_s) | |
550 return result | |
551 | |
552 def removeProfile(failure): # profile has been successfully created but the XMPP credentials are wrong! | |
553 log.debug("Removing previously auto-created profile: %s" % failure.getErrorMessage()) | |
554 self.host.memory.asyncDeleteProfile(jid_s) | |
555 raise failure | |
556 | |
557 # FIXME: we don't catch the case where the JID host is not an XMPP server, and the user | |
558 # has to wait until the DBUS timeout ; as a consequence, emails are sent to the admins | |
559 # and the profile is not deleted. When the host exists, removeProfile is well called. | |
560 d.addCallbacks(connected, removeProfile) | |
561 return d |