This is a huge configuration refactoring. The configuration system now uses Pydantic settings, with better validation, better support for environment variables and other goodies. The main config files now use TOML instead of INI, this allow for better handling of complex types, notably dict. Plugins, components and frontends can declare their config settings in a class which will be imported dynamically.
738 lines
28 KiB
Python
738 lines
28 KiB
Python
#!/usr/bin/env python3
|
||
|
||
# Libervia plugin for account creation
|
||
# Copyright (C) 2009-2021 Jérôme Poisson (goffi@goffi.org)
|
||
|
||
# This program is free software: you can redistribute it and/or modify
|
||
# it under the terms of the GNU Affero General Public License as published by
|
||
# the Free Software Foundation, either version 3 of the License, or
|
||
# (at your option) any later version.
|
||
|
||
# This program is distributed in the hope that it will be useful,
|
||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||
# GNU Affero General Public License for more details.
|
||
|
||
# You should have received a copy of the GNU Affero General Public License
|
||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||
|
||
from typing import cast
|
||
from pydantic import Field
|
||
from libervia.backend import G
|
||
from libervia.backend.core.i18n import _, D_
|
||
from libervia.backend.core.log import getLogger
|
||
|
||
from libervia.backend.core import exceptions
|
||
from libervia.backend.tools import xml_tools
|
||
from libervia.backend.memory.memory import Sessions
|
||
from libervia.backend.memory.crypto import PasswordHasher
|
||
from libervia.backend.core.constants import Const as C
|
||
from twisted.internet import defer
|
||
from twisted.python.failure import Failure
|
||
from twisted.words.protocols.jabber import jid
|
||
from libervia.backend.tools.common import email as sat_email
|
||
from libervia.backend.tools.config import EmailSettings, PluginSettings
|
||
|
||
|
||
log = getLogger(__name__)
|
||
|
||
|
||
# FIXME: this plugin code is old and need a cleaning
|
||
# TODO: account deletion/password change need testing
|
||
|
||
|
||
PLUGIN_INFO = {
|
||
C.PI_NAME: "Account Plugin",
|
||
C.PI_IMPORT_NAME: "account",
|
||
C.PI_TYPE: "MISC",
|
||
C.PI_PROTOCOLS: [],
|
||
C.PI_DEPENDENCIES: ["XEP-0077"],
|
||
C.PI_RECOMMENDATIONS: ["GROUPBLOG"],
|
||
C.PI_MAIN: "MiscAccount",
|
||
C.PI_HANDLER: "no",
|
||
C.PI_DESCRIPTION: _("""Libervia account creation"""),
|
||
}
|
||
|
||
CONFIG_SECTION = "plugin account"
|
||
|
||
# You need do adapt the following consts to your server
|
||
# all theses values (key=option name, value=default) can (and should) be overriden
|
||
# in libervia.conf in section CONFIG_SECTION
|
||
|
||
default_conf = {
|
||
"email_from": "NOREPLY@example.net",
|
||
"email_server": "localhost",
|
||
"email_sender_domain": "",
|
||
"email_port": 25,
|
||
"email_username": "",
|
||
"email_password": "",
|
||
"email_starttls": "false",
|
||
"email_auth": "false",
|
||
"email_admins_list": [],
|
||
"admin_email": "",
|
||
"new_account_server": "localhost",
|
||
"new_account_domain": "", # use xmpp_domain if not found
|
||
}
|
||
|
||
# profiles which can't be used for accound creation. Must be lowercase.
|
||
RESERVED_LIST: list[str] = ["libervia"]
|
||
|
||
|
||
WELCOME_MSG = D_(
|
||
"""Welcome to Libervia.
|
||
|
||
Your account on {domain} has been successfully created.
|
||
This is a demonstration version to show you the current status of the project.
|
||
It is still under development, please keep it in mind!
|
||
|
||
Here is your connection information:
|
||
|
||
Login on {domain}: {profile}
|
||
Jabber ID (JID): {jid}
|
||
Your password has been chosen by yourself during registration.
|
||
|
||
In the beginning, you have nobody to talk to. To find some contacts, you may use the users' directory:
|
||
- make yourself visible in "Service / Directory subscription".
|
||
- search for people with "Contacts" / Search directory".
|
||
|
||
Any feedback welcome. Thank you!
|
||
|
||
The Libervia team.
|
||
https://www.libervia.org
|
||
"""
|
||
)
|
||
|
||
|
||
class PluginsAccountSettings(PluginSettings, plugin_info=PLUGIN_INFO):
|
||
# Use global email settings by default.
|
||
email: EmailSettings | None = Field(
|
||
default=None,
|
||
description="Email settings, default to settings from ``email`` section, use "
|
||
"this if you want different ones for account creation.",
|
||
)
|
||
|
||
|
||
class MiscAccount(object):
|
||
"""Account plugin: create a Libervia + XMPP account."""
|
||
|
||
# XXX: This plugin was initialy a Q&D one used for the demo.
|
||
# TODO: cleaning, separate email handling, more configuration/tests, fixes
|
||
|
||
def __init__(self, host):
|
||
log.info(_("Plugin Account initialization"))
|
||
self.host = host
|
||
host.bridge.add_method(
|
||
"libervia_account_register",
|
||
".plugin",
|
||
in_sign="sss",
|
||
out_sign="",
|
||
method=self._register_account,
|
||
async_=True,
|
||
)
|
||
host.bridge.add_method(
|
||
"account_domain_new_get",
|
||
".plugin",
|
||
in_sign="",
|
||
out_sign="s",
|
||
method=lambda: G.config.common.new_account_domain,
|
||
async_=False,
|
||
)
|
||
host.bridge.add_method(
|
||
"account_dialog_ui_get",
|
||
".plugin",
|
||
in_sign="s",
|
||
out_sign="s",
|
||
method=self._get_account_dialog_ui,
|
||
async_=False,
|
||
)
|
||
host.bridge.add_method(
|
||
"credentials_xmpp_connect",
|
||
".plugin",
|
||
in_sign="ss",
|
||
out_sign="b",
|
||
method=self.credentials_xmpp_connect,
|
||
async_=True,
|
||
)
|
||
|
||
self._sessions = Sessions()
|
||
|
||
self.__account_cb_id = host.register_callback(
|
||
self._account_dialog_cb, with_data=True
|
||
)
|
||
self.__change_password_id = host.register_callback(
|
||
self.__change_password_cb, with_data=True
|
||
)
|
||
|
||
def delete_blog_callback(posts, comments):
|
||
return lambda data, profile: self.__delete_blog_posts_cb(
|
||
posts, comments, data, profile
|
||
)
|
||
|
||
self.__delete_posts_id = host.register_callback(
|
||
delete_blog_callback(True, False), with_data=True
|
||
)
|
||
self.__delete_comments_id = host.register_callback(
|
||
delete_blog_callback(False, True), with_data=True
|
||
)
|
||
self.__delete_posts_comments_id = host.register_callback(
|
||
delete_blog_callback(True, True), with_data=True
|
||
)
|
||
|
||
self.__delete_account_id = host.register_callback(
|
||
self.__delete_account_cb, with_data=True
|
||
)
|
||
|
||
def get_email_conf(self) -> EmailSettings:
|
||
"""Return settings to send an email.
|
||
|
||
Will use settings of this plugins is set, otherwise global ones.
|
||
"""
|
||
plugin_settings = G.config.get_settings(PluginsAccountSettings)
|
||
if plugin_settings.email is not None:
|
||
return plugin_settings.email
|
||
else:
|
||
return G.config.email
|
||
|
||
def _register_account(self, email, password, profile):
|
||
return self.registerAccount(email, password, None, profile)
|
||
|
||
def registerAccount(self, email, password, jid_s, profile):
|
||
"""Register a new profile, its associated XMPP account, send the confirmation emails.
|
||
|
||
@param email (unicode): where to send to confirmation email to
|
||
@param password (unicode): password chosen by the user
|
||
while be used for profile *and* XMPP account
|
||
@param jid_s (unicode): JID to re-use or to register:
|
||
- non empty value: bind this JID to the new sat profile
|
||
- None or "": register a new JID on the local XMPP server
|
||
@param profile
|
||
@return Deferred
|
||
"""
|
||
d = self.create_profile(password, jid_s, profile)
|
||
d.addCallback(lambda __: self.send_emails(email, profile))
|
||
return d
|
||
|
||
def create_profile(self, password, jid_s, profile):
|
||
"""Register a new profile and its associated XMPP account.
|
||
|
||
@param password (unicode): password chosen by the user
|
||
while be used for profile *and* XMPP account
|
||
@param jid_s (unicode): JID to re-use or to register:
|
||
- non empty value: bind this JID to the new sat profile
|
||
- None or "": register a new JID on the local XMPP server
|
||
@param profile
|
||
@return Deferred
|
||
"""
|
||
if not password or not profile:
|
||
raise exceptions.DataError
|
||
|
||
if profile.lower() in RESERVED_LIST:
|
||
return defer.fail(Failure(exceptions.ConflictError))
|
||
|
||
d = self.host.memory.create_profile(profile, password)
|
||
d.addCallback(lambda __: self.profile_created(password, jid_s, profile))
|
||
return d
|
||
|
||
def profile_created(self, password, jid_s, profile):
|
||
"""Create the XMPP account and set the profile connection parameters.
|
||
|
||
@param password (unicode): password chosen by the user
|
||
@param jid_s (unicode): JID to re-use or to register:
|
||
- non empty value: bind this JID to the new sat profile
|
||
- None or empty: register a new JID on the local XMPP server
|
||
@param profile
|
||
@return: Deferred
|
||
"""
|
||
if jid_s:
|
||
d = defer.succeed(None)
|
||
jid_ = jid.JID(jid_s)
|
||
else:
|
||
jid_s = f"{profile}@{G.config.common.new_account_domain}"
|
||
jid_ = jid.JID(jid_s)
|
||
d = self.host.plugins["XEP-0077"].register_new_account(jid_, password)
|
||
|
||
def setParams(__):
|
||
self.host.memory.param_set(
|
||
"JabberID", jid_s, "Connection", profile_key=profile
|
||
)
|
||
d = self.host.memory.param_set(
|
||
"Password", password, "Connection", profile_key=profile
|
||
)
|
||
return d
|
||
|
||
def remove_profile(failure):
|
||
self.host.memory.profile_delete_async(profile)
|
||
return failure
|
||
|
||
d.addCallback(lambda __: self.host.memory.start_session(password, profile))
|
||
d.addCallback(setParams)
|
||
d.addCallback(lambda __: self.host.memory.stop_session(profile))
|
||
d.addErrback(remove_profile)
|
||
return d
|
||
|
||
def _send_email_eb(self, failure_, email):
|
||
# TODO: return error code to user
|
||
log.error(
|
||
_("Failed to send account creation confirmation to {email}: {msg}").format(
|
||
email=email, msg=failure_
|
||
)
|
||
)
|
||
|
||
def send_emails(self, email, profile):
|
||
# time to send the email
|
||
|
||
domain = G.config.common.new_account_domain
|
||
|
||
# email to the administrators
|
||
admins_emails = G.config.common.email_admins_list
|
||
if not admins_emails:
|
||
log.warning(
|
||
"No known admin email, we can't send email to administrator(s).\n"
|
||
"Please fill email_admins_list parameter"
|
||
)
|
||
d_admin = defer.fail(exceptions.DataError("no admin email"))
|
||
else:
|
||
subject = _("New Libervia account created")
|
||
# there is no email when an existing XMPP account is used
|
||
body = f"New account created on {domain}: {profile} [{email or '<no email>'}]"
|
||
d_admin = sat_email.send_email(
|
||
self.get_email_conf(),
|
||
self.host.memory.config,
|
||
admins_emails,
|
||
subject,
|
||
body,
|
||
)
|
||
|
||
admins_emails_txt = ", ".join(["<" + addr + ">" for addr in admins_emails])
|
||
d_admin.addCallbacks(
|
||
lambda __: log.debug(
|
||
"Account creation notification sent to admin(s) {}".format(
|
||
admins_emails_txt
|
||
)
|
||
),
|
||
lambda __: log.error(
|
||
"Failed to send account creation notification to admin {}".format(
|
||
admins_emails_txt
|
||
)
|
||
),
|
||
)
|
||
if not email:
|
||
# TODO: if use register with an existing account, an XMPP message should be sent
|
||
return d_admin
|
||
|
||
jid_s = self.host.memory.param_get_a(
|
||
"JabberID", "Connection", profile_key=profile
|
||
)
|
||
subject = _("Your Libervia account has been created")
|
||
body = _(WELCOME_MSG).format(profile=profile, jid=jid_s, domain=domain)
|
||
|
||
# XXX: this will not fail when the email address doesn't exist
|
||
# FIXME: check email reception to validate email given by the user
|
||
# FIXME: delete the profile if the email could not been sent?
|
||
d_user = sat_email.send_email(self.get_email_conf, [email], subject, body)
|
||
d_user.addCallbacks(
|
||
lambda __: log.debug(
|
||
"Account creation confirmation sent to <{}>".format(email)
|
||
),
|
||
self._send_email_eb,
|
||
errbackArgs=[email],
|
||
)
|
||
return defer.DeferredList([d_user, d_admin])
|
||
|
||
def _get_account_dialog_ui(self, profile):
|
||
"""Get the main dialog to manage your account
|
||
@param menu_data
|
||
@param profile: %(doc_profile)s
|
||
@return: XML of the dialog
|
||
"""
|
||
form_ui = xml_tools.XMLUI(
|
||
"form",
|
||
"tabs",
|
||
title=D_("Manage your account"),
|
||
submit_id=self.__account_cb_id,
|
||
)
|
||
tab_container = form_ui.current_container
|
||
|
||
tab_container.add_tab(
|
||
"update", D_("Change your password"), container=xml_tools.PairsContainer
|
||
)
|
||
form_ui.addLabel(D_("Current profile password"))
|
||
form_ui.addPassword("current_passwd", value="")
|
||
form_ui.addLabel(D_("New password"))
|
||
form_ui.addPassword("new_passwd1", value="")
|
||
form_ui.addLabel(D_("New password (again)"))
|
||
form_ui.addPassword("new_passwd2", value="")
|
||
|
||
# FIXME: uncomment and fix these features
|
||
"""
|
||
if 'GROUPBLOG' in self.host.plugins:
|
||
tab_container.add_tab("delete_posts", D_("Delete your posts"), container=xml_tools.PairsContainer)
|
||
form_ui.addLabel(D_("Current profile password"))
|
||
form_ui.addPassword("delete_posts_passwd", value="")
|
||
form_ui.addLabel(D_("Delete all your posts and their comments"))
|
||
form_ui.addBool("delete_posts_checkbox", "false")
|
||
form_ui.addLabel(D_("Delete all your comments on other's posts"))
|
||
form_ui.addBool("delete_comments_checkbox", "false")
|
||
|
||
tab_container.add_tab("delete", D_("Delete your account"), container=xml_tools.PairsContainer)
|
||
form_ui.addLabel(D_("Current profile password"))
|
||
form_ui.addPassword("delete_passwd", value="")
|
||
form_ui.addLabel(D_("Delete your account"))
|
||
form_ui.addBool("delete_checkbox", "false")
|
||
"""
|
||
|
||
return form_ui.toXml()
|
||
|
||
@defer.inlineCallbacks
|
||
def _account_dialog_cb(self, data, profile):
|
||
"""Called when the user submits the main account dialog
|
||
@param data
|
||
@param profile
|
||
"""
|
||
sat_cipher = yield self.host.memory.param_get_a_async(
|
||
C.PROFILE_PASS_PATH[1], C.PROFILE_PASS_PATH[0], profile_key=profile
|
||
)
|
||
|
||
@defer.inlineCallbacks
|
||
def verify(attempt):
|
||
auth = yield PasswordHasher.verify(attempt, sat_cipher)
|
||
return auth
|
||
|
||
def error_ui(message=None):
|
||
if not message:
|
||
message = D_("The provided profile password doesn't match.")
|
||
error_ui = xml_tools.XMLUI("popup", title=D_("Attempt failure"))
|
||
error_ui.addText(message)
|
||
return {"xmlui": error_ui.toXml()}
|
||
|
||
# check for account deletion
|
||
# FIXME: uncomment and fix these features
|
||
"""
|
||
delete_passwd = data[xml_tools.SAT_FORM_PREFIX + 'delete_passwd']
|
||
delete_checkbox = data[xml_tools.SAT_FORM_PREFIX + 'delete_checkbox']
|
||
if delete_checkbox == 'true':
|
||
verified = yield verify(delete_passwd)
|
||
assert isinstance(verified, bool)
|
||
if verified:
|
||
return self.__delete_account(profile)
|
||
return error_ui()
|
||
|
||
# check for blog posts deletion
|
||
if 'GROUPBLOG' in self.host.plugins:
|
||
delete_posts_passwd = data[xml_tools.SAT_FORM_PREFIX + 'delete_posts_passwd']
|
||
delete_posts_checkbox = data[xml_tools.SAT_FORM_PREFIX + 'delete_posts_checkbox']
|
||
delete_comments_checkbox = data[xml_tools.SAT_FORM_PREFIX + 'delete_comments_checkbox']
|
||
posts = delete_posts_checkbox == 'true'
|
||
comments = delete_comments_checkbox == 'true'
|
||
if posts or comments:
|
||
verified = yield verify(delete_posts_passwd)
|
||
assert isinstance(verified, bool)
|
||
if verified:
|
||
return self.__delete_blog_posts(posts, comments, profile)
|
||
return error_ui()
|
||
"""
|
||
|
||
# check for password modification
|
||
current_passwd = data[xml_tools.SAT_FORM_PREFIX + "current_passwd"]
|
||
new_passwd1 = data[xml_tools.SAT_FORM_PREFIX + "new_passwd1"]
|
||
new_passwd2 = data[xml_tools.SAT_FORM_PREFIX + "new_passwd2"]
|
||
if new_passwd1 or new_passwd2:
|
||
verified = yield verify(current_passwd)
|
||
assert isinstance(verified, bool)
|
||
if verified:
|
||
if new_passwd1 == new_passwd2:
|
||
data = yield self.__change_password(new_passwd1, profile=profile)
|
||
return data
|
||
else:
|
||
return error_ui(
|
||
D_("The values entered for the new password are not equal.")
|
||
)
|
||
return error_ui()
|
||
|
||
return {}
|
||
|
||
def __change_password(self, password, profile):
|
||
"""Ask for a confirmation before changing the XMPP account and SàT profile passwords.
|
||
|
||
@param password (str): the new password
|
||
@param profile (str): %(doc_profile)s
|
||
"""
|
||
session_id, __ = self._sessions.new_session(
|
||
{"new_password": password}, profile=profile
|
||
)
|
||
form_ui = xml_tools.XMLUI(
|
||
"form",
|
||
title=D_("Change your password?"),
|
||
submit_id=self.__change_password_id,
|
||
session_id=session_id,
|
||
)
|
||
form_ui.addText(
|
||
D_(
|
||
"Note for advanced users: this will actually change both your SàT profile password AND your XMPP account password."
|
||
)
|
||
)
|
||
form_ui.addText(D_("Continue with changing the password?"))
|
||
return {"xmlui": form_ui.toXml()}
|
||
|
||
def __change_password_cb(self, data, profile):
|
||
"""Actually change the user XMPP account and SàT profile password
|
||
@param data (dict)
|
||
@profile (str): %(doc_profile)s
|
||
"""
|
||
client = self.host.get_client(profile)
|
||
password = self._sessions.profile_get(data["session_id"], profile)["new_password"]
|
||
del self._sessions[data["session_id"]]
|
||
|
||
def password_changed(__):
|
||
d = self.host.memory.param_set(
|
||
C.PROFILE_PASS_PATH[1],
|
||
password,
|
||
C.PROFILE_PASS_PATH[0],
|
||
profile_key=profile,
|
||
)
|
||
d.addCallback(
|
||
lambda __: self.host.memory.param_set(
|
||
"Password", password, "Connection", profile_key=profile
|
||
)
|
||
)
|
||
confirm_ui = xml_tools.XMLUI("popup", title=D_("Confirmation"))
|
||
confirm_ui.addText(D_("Your password has been changed."))
|
||
return defer.succeed({"xmlui": confirm_ui.toXml()})
|
||
|
||
def errback(failure):
|
||
error_ui = xml_tools.XMLUI("popup", title=D_("Error"))
|
||
error_ui.addText(
|
||
D_("Your password could not be changed: %s") % failure.getErrorMessage()
|
||
)
|
||
return defer.succeed({"xmlui": error_ui.toXml()})
|
||
|
||
d = self.host.plugins["XEP-0077"].change_password(client, password)
|
||
d.addCallbacks(password_changed, errback)
|
||
return d
|
||
|
||
def __delete_account(self, profile):
|
||
"""Ask for a confirmation before deleting the XMPP account and Libervia profile
|
||
@param profile
|
||
"""
|
||
form_ui = xml_tools.XMLUI(
|
||
"form", title=D_("Delete your account?"), submit_id=self.__delete_account_id
|
||
)
|
||
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."
|
||
)
|
||
)
|
||
target = D_(
|
||
"contact list, messages history, blog posts and comments"
|
||
if "GROUPBLOG" in self.host.plugins
|
||
else D_("contact list and messages history")
|
||
)
|
||
form_ui.addText(
|
||
D_(
|
||
"All your data stored on {server}, including your {target} will be erased."
|
||
).format(server=G.config.common.new_account_domain, target=target)
|
||
)
|
||
form_ui.addText(
|
||
D_(
|
||
"There is no other confirmation dialog, this is the very last one! Are you sure?"
|
||
)
|
||
)
|
||
return {"xmlui": form_ui.toXml()}
|
||
|
||
def __delete_account_cb(self, data, profile):
|
||
"""Actually delete the XMPP account and SàT profile
|
||
|
||
@param data
|
||
@param profile
|
||
"""
|
||
client = self.host.get_client(profile)
|
||
|
||
def user_deleted(__):
|
||
|
||
# FIXME: client should be disconnected at this point, so 2 next loop should be removed (to be confirmed)
|
||
for jid_ in client.roster._jids: # empty roster
|
||
client.presence.unsubscribe(jid_)
|
||
|
||
for jid_ in self.host.memory.sub_waiting_get(
|
||
profile
|
||
): # delete waiting subscriptions
|
||
self.host.memory.del_waiting_sub(jid_)
|
||
|
||
delete_profile = lambda: self.host.memory.profile_delete_async(
|
||
profile, force=True
|
||
)
|
||
if "GROUPBLOG" in self.host.plugins:
|
||
d = self.host.plugins["GROUPBLOG"].deleteAllGroupBlogsAndComments(
|
||
profile_key=profile
|
||
)
|
||
d.addCallback(lambda __: delete_profile())
|
||
else:
|
||
delete_profile()
|
||
|
||
return defer.succeed({})
|
||
|
||
def errback(failure):
|
||
error_ui = xml_tools.XMLUI("popup", title=D_("Error"))
|
||
error_ui.addText(
|
||
D_("Your XMPP account could not be deleted: %s")
|
||
% failure.getErrorMessage()
|
||
)
|
||
return defer.succeed({"xmlui": error_ui.toXml()})
|
||
|
||
d = self.host.plugins["XEP-0077"].unregister(client, jid.JID(client.jid.host))
|
||
d.addCallbacks(user_deleted, errback)
|
||
return d
|
||
|
||
def __delete_blog_posts(self, posts, comments, profile):
|
||
"""Ask for a confirmation before deleting the blog posts
|
||
@param posts: delete all posts of the user (and their comments)
|
||
@param comments: delete all the comments of the user on other's posts
|
||
@param data
|
||
@param profile
|
||
"""
|
||
if posts:
|
||
if comments: # delete everything
|
||
form_ui = xml_tools.XMLUI(
|
||
"form",
|
||
title=D_("Delete all your (micro-)blog posts and comments?"),
|
||
submit_id=self.__delete_posts_comments_id,
|
||
)
|
||
form_ui.addText(
|
||
D_(
|
||
"If you confirm this dialog, all the (micro-)blog data you submitted will be erased."
|
||
)
|
||
)
|
||
form_ui.addText(
|
||
D_(
|
||
"These are the public and private posts and comments you sent to any group."
|
||
)
|
||
)
|
||
form_ui.addText(
|
||
D_(
|
||
"There is no other confirmation dialog, this is the very last one! Are you sure?"
|
||
)
|
||
)
|
||
else: # delete only the posts
|
||
form_ui = xml_tools.XMLUI(
|
||
"form",
|
||
title=D_("Delete all your (micro-)blog posts?"),
|
||
submit_id=self.__delete_posts_id,
|
||
)
|
||
form_ui.addText(
|
||
D_(
|
||
"If you confirm this dialog, all the public and private posts you sent to any group will be erased."
|
||
)
|
||
)
|
||
form_ui.addText(
|
||
D_(
|
||
"There is no other confirmation dialog, this is the very last one! Are you sure?"
|
||
)
|
||
)
|
||
elif comments: # delete only the comments
|
||
form_ui = xml_tools.XMLUI(
|
||
"form",
|
||
title=D_("Delete all your (micro-)blog comments?"),
|
||
submit_id=self.__delete_comments_id,
|
||
)
|
||
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."
|
||
)
|
||
)
|
||
form_ui.addText(
|
||
D_(
|
||
"There is no other confirmation dialog, this is the very last one! Are you sure?"
|
||
)
|
||
)
|
||
|
||
return {"xmlui": form_ui.toXml()}
|
||
|
||
def __delete_blog_posts_cb(self, posts, comments, data, profile):
|
||
"""Actually delete the XMPP account and SàT profile
|
||
@param posts: delete all posts of the user (and their comments)
|
||
@param comments: delete all the comments of the user on other's posts
|
||
@param profile
|
||
"""
|
||
if posts:
|
||
if comments:
|
||
target = D_("blog posts and comments")
|
||
d = self.host.plugins["GROUPBLOG"].deleteAllGroupBlogsAndComments(
|
||
profile_key=profile
|
||
)
|
||
else:
|
||
target = D_("blog posts")
|
||
d = self.host.plugins["GROUPBLOG"].deleteAllGroupBlogs(
|
||
profile_key=profile
|
||
)
|
||
elif comments:
|
||
target = D_("comments")
|
||
d = self.host.plugins["GROUPBLOG"].deleteAllGroupBlogsComments(
|
||
profile_key=profile
|
||
)
|
||
|
||
def deleted(result):
|
||
ui = xml_tools.XMLUI("popup", title=D_("Deletion confirmation"))
|
||
# TODO: change the message when delete/retract notifications are done with XEP-0060
|
||
ui.addText(D_("Your %(target)s have been deleted.") % {"target": target})
|
||
ui.addText(
|
||
D_(
|
||
"Known issue of the demo version: you need to refresh the page to make the deleted posts actually disappear."
|
||
)
|
||
)
|
||
return defer.succeed({"xmlui": ui.toXml()})
|
||
|
||
def errback(failure):
|
||
error_ui = xml_tools.XMLUI("popup", title=D_("Error"))
|
||
error_ui.addText(
|
||
D_("Your %(target)s could not be deleted: %(message)s")
|
||
% {"target": target, "message": failure.getErrorMessage()}
|
||
)
|
||
return defer.succeed({"xmlui": error_ui.toXml()})
|
||
|
||
d.addCallbacks(deleted, errback)
|
||
return d
|
||
|
||
def credentials_xmpp_connect(self, jid_s, password):
|
||
"""Create and connect a new SàT profile using the given XMPP credentials.
|
||
|
||
Re-use given JID and XMPP password for the profile name and profile password.
|
||
@param jid_s (unicode): JID
|
||
@param password (unicode): XMPP password
|
||
@return Deferred(bool)
|
||
@raise exceptions.PasswordError, exceptions.ConflictError
|
||
"""
|
||
try: # be sure that the profile doesn't exist yet
|
||
self.host.memory.get_profile_name(jid_s)
|
||
except exceptions.ProfileUnknownError:
|
||
pass
|
||
else:
|
||
raise exceptions.ConflictError
|
||
|
||
d = self.create_profile(password, jid_s, jid_s)
|
||
d.addCallback(
|
||
lambda __: self.host.memory.get_profile_name(jid_s)
|
||
) # checks if the profile has been successfuly created
|
||
d.addCallback(
|
||
lambda profile: defer.ensureDeferred(
|
||
self.host.connect(profile, password, {}, 0)
|
||
)
|
||
)
|
||
|
||
def connected(result):
|
||
self.send_emails(None, profile=jid_s)
|
||
return result
|
||
|
||
def remove_profile(
|
||
failure,
|
||
): # profile has been successfully created but the XMPP credentials are wrong!
|
||
log.debug(
|
||
"Removing previously auto-created profile: %s" % failure.getErrorMessage()
|
||
)
|
||
self.host.memory.profile_delete_async(jid_s)
|
||
raise failure
|
||
|
||
# FIXME: we don't catch the case where the JID host is not an XMPP server, and the user
|
||
# has to wait until the DBUS timeout ; as a consequence, emails are sent to the admins
|
||
# and the profile is not deleted. When the host exists, remove_profile is well called.
|
||
d.addCallbacks(connected, remove_profile)
|
||
return d
|