plugin account: removed dependency to Prosody/prosodyctl and properly use in-band registration instead

This commit is contained in:
Goffi 2017-03-09 00:06:13 +01:00
commit 06be5a58aa

View file

@ -26,22 +26,24 @@ from sat.memory.memory import Sessions
from sat.memory.crypto import PasswordHasher
from sat.core.constants import Const as C
import ConfigParser
from twisted.internet import reactor, defer, protocol
from twisted.python.procutils import which
from twisted.internet import defer
from twisted.python.failure import Failure
from twisted.words.protocols.jabber import jid
from twisted.mail.smtp import sendmail
from os.path import join, dirname
from email.mime.text import MIMEText
import random
# 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: "MISC-ACCOUNT",
C.PI_TYPE: "MISC",
C.PI_PROTOCOLS: [],
C.PI_DEPENDENCIES: [],
C.PI_DEPENDENCIES: ["XEP-0077"],
C.PI_RECOMMENDATIONS: ['GROUPBLOG'],
C.PI_MAIN: "MiscAccount",
C.PI_HANDLER: "no",
@ -66,70 +68,15 @@ default_conf = {"email_from": "NOREPLY@example.net",
"admin_email": "",
"new_account_server": "localhost",
"new_account_domain": "example.net",
"prosody_path": None, # prosody path (where prosodyctl will be executed from), or None to automaticaly find it
"prosodyctl": "prosodyctl",
"reserved_list": ['libervia'] # profiles which can't be used
}
class PasswordsMatchingError(Exception):
pass
class ProsodyRegisterProtocol(protocol.ProcessProtocol):
""" Try to register an account with prosody """
def __init__(self, password=None, deferred=None):
"""
@param password: new user password
@param deferred
"""
self.password = password
self.deferred = deferred
self.data = ''
def connectionMade(self):
if self.password is None:
return
self.transport.write("%s\n%s" % ((self.password.encode('utf-8'),) * 2))
self.transport.closeStdin()
def outReceived(self, data):
self.data += data
def errReceived(self, data):
self.data += data
def processEnded(self, reason):
if (reason.value.exitCode == 0):
log.info(_('Prosody command succeed'))
self.deferred.callback(None)
else:
log.error(_(u"Can't complete Prosody command (error code: %(code)d): %(message)s") % {'code': reason.value.exitCode, 'message': self.data})
self.deferred.errback(Failure(exceptions.InternalError))
@classmethod
def prosodyctl(cls, plugin, command, password=None, profile=None):
"""Create a new ProsodyRegisterProtocol and execute the given prosodyctl command.
@param plugin: instance of MiscAccount
@param command: the command to execute: "adduser", "passwd" or "deluser"
@param password: the user new password (leave to None for "deluser" command)
@param profile: the user profile
@return: a Deferred instance
"""
d = defer.Deferred()
prosody_reg = ProsodyRegisterProtocol(password, d)
prosody_exe = join(plugin._prosody_path, plugin.getConfig('prosodyctl'))
# TODO delete account which are not on the same host
reactor.spawnProcess(prosody_reg, prosody_exe, [prosody_exe, command, "%s@%s" % (profile, plugin.getConfig('new_account_domain'))], path=plugin._prosody_path)
return d
class MiscAccount(object):
"""Account plugin: create a SàT + Prosody account, used by Libervia"""
#XXX: This plugin is a Q&D one used for the demo. Something more generic (and not
# only focused on Prosody) is planed
"""Account plugin: create a SàT + XMPP account, used by Libervia"""
# 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(_(u"Plugin Account initialization"))
@ -138,14 +85,6 @@ class MiscAccount(object):
host.bridge.addMethod("getNewAccountDomain", ".plugin", in_sign='', out_sign='s', method=self.getNewAccountDomain, async=False)
host.bridge.addMethod("getAccountDialogUI", ".plugin", in_sign='s', out_sign='s', method=self._getAccountDialogUI, async=False)
host.bridge.addMethod("asyncConnectWithXMPPCredentials", ".plugin", in_sign='ss', out_sign='b', method=self.asyncConnectWithXMPPCredentials, async=True)
self._prosody_path = self.getConfig('prosody_path')
if self._prosody_path is None:
paths = which(self.getConfig('prosodyctl'))
if not paths:
log.error(_(u"Can't find %s") % (self.getConfig('prosodyctl'),))
else:
self._prosody_path = dirname(paths[0])
log.info(_(u'Prosody path found: %s') % (self._prosody_path,))
self.fixEmailAdmins()
self._sessions = Sessions()
@ -256,12 +195,13 @@ class MiscAccount(object):
@param profile
@return: Deferred
"""
# XXX: we use "prosodyctl adduser" because "register" doesn't check conflict and just change the password if the account already exists
if jid_s:
d = defer.succeed(None)
jid_ = jid.JID(jid_s)
else:
d = ProsodyRegisterProtocol.prosodyctl(self, 'adduser', password, profile)
jid_s = "%s@%s" % (profile, self.getConfig('new_account_domain'))
jid_s = profile + u"@" + self.getConfig('new_account_domain')
jid_ = jid.JID(jid_s)
d = self.host.plugins['XEP-0077'].registerNewAccount(jid_, password)
def setParams(dummy):
self.host.memory.setParam("JabberID", jid_s, "Connection", profile_key=profile)
@ -481,10 +421,11 @@ http://www.salut-a-toi.org
@param data (dict)
@profile (str): %(doc_profile)s
"""
client = self.host.getClient(profile)
password = self._sessions.profileGet(data['session_id'], profile)['new_password']
del self._sessions[data['session_id']]
def passwordChanged(result):
def passwordChanged(dummy):
d = self.host.memory.setParam(C.PROFILE_PASS_PATH[1], password, C.PROFILE_PASS_PATH[0], profile_key=profile)
d.addCallback(lambda dummy: self.host.memory.setParam("Password", password, "Connection", profile_key=profile))
confirm_ui = xml_tools.XMLUI("popup", title=D_("Confirmation"))
@ -496,7 +437,7 @@ http://www.salut-a-toi.org
error_ui.addText(D_("Your password could not be changed: %s") % failure.getErrorMessage())
return defer.succeed({'xmlui': error_ui.toXml()})
d = ProsodyRegisterProtocol.prosodyctl(self, 'passwd', password, profile=profile)
d = self.host.plugins['XEP-0077'].changePassword(client, password)
d.addCallbacks(passwordChanged, errback)
return d
@ -513,12 +454,14 @@ http://www.salut-a-toi.org
def __deleteAccountCb(self, data, profile):
"""Actually delete the XMPP account and SàT profile
@param data
@param profile
"""
def userDeleted(result):
client = self.host.profiles[profile]
client = self.host.getClient(profile)
def userDeleted(dummy):
# 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_)
@ -539,7 +482,7 @@ http://www.salut-a-toi.org
error_ui.addText(D_("Your XMPP account could not be deleted: %s") % failure.getErrorMessage())
return defer.succeed({'xmlui': error_ui.toXml()})
d = ProsodyRegisterProtocol.prosodyctl(self, 'deluser', profile=profile)
d = self.host.plugins['XEP-0077'].removeRegistration(client, jid.JID(client.jid.host))
d.addCallbacks(userDeleted, errback)
return d