# HG changeset patch # User Goffi # Date 1489014373 -3600 # Node ID 61128d260eefac74afb59758ac598ade38764802 # Parent 75002ac3380108bf396e79d7175ff9452dc135ba plugin account: removed dependency to Prosody/prosodyctl and properly use in-band registration instead diff -r 75002ac33801 -r 61128d260eef src/core/sat_main.py --- a/src/core/sat_main.py Thu Mar 09 00:06:13 2017 +0100 +++ b/src/core/sat_main.py Thu Mar 09 00:06:13 2017 +0100 @@ -439,7 +439,7 @@ @return: client or None if it doesn't exist @raise exceptions.ProfileKeyUnknown: the profile or profile key doesn't exist @raise exceptions.NotFound: client is not available - This happen if profile has not been use yet + This happen if profile has not been used yet """ profile = self.memory.getProfileName(profile_key) if not profile: diff -r 75002ac33801 -r 61128d260eef src/plugins/plugin_misc_account.py --- a/src/plugins/plugin_misc_account.py Thu Mar 09 00:06:13 2017 +0100 +++ b/src/plugins/plugin_misc_account.py Thu Mar 09 00:06:13 2017 +0100 @@ -26,22 +26,24 @@ 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 @@ "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 +class MiscAccount(object): + """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 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 def __init__(self, host): log.info(_(u"Plugin Account initialization")) @@ -138,14 +85,6 @@ 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 @@ @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 @@ @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 @@ 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 @@ 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 @@ 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