changeset 2172:545a1261ac3b

core, plugin XEP-0077: in-band registration fix and move: in-band was partially in core for historical reason, it has been moved to XEP-0077, and fixed. It is still incomplete, but should work for basic accounts creation.
author Goffi <goffi@goffi.org>
date Wed, 08 Mar 2017 20:59:31 +0100
parents 978011533892
children c9c5fdd54ca5
files src/core/xmpp.py src/plugins/plugin_misc_register_account.py src/plugins/plugin_xep_0077.py
diffstat 3 files changed, 93 insertions(+), 84 deletions(-) [+]
line wrap: on
line diff
--- a/src/core/xmpp.py	Wed Mar 08 20:44:28 2017 +0100
+++ b/src/core/xmpp.py	Wed Mar 08 20:59:31 2017 +0100
@@ -1090,47 +1090,6 @@
         generic.FallbackHandler.iqFallback(self, iq)
 
 
-class RegisteringAuthenticator(xmlstream.ConnectAuthenticator):
-
-    def __init__(self, host, jabber_host, user_login, user_pass, email, deferred, profile):
-        xmlstream.ConnectAuthenticator.__init__(self, jabber_host)
-        self.host = host
-        self.jabber_host = jabber_host
-        self.user_login = user_login
-        self.user_pass = user_pass
-        self.user_email = email
-        self.deferred = deferred
-        self.profile = profile
-        log.debug(_(u"Registration asked for %(user)s@%(host)s") % {'user': user_login, 'host': jabber_host})
-
-    def connectionMade(self):
-        log.debug(_(u"Connection made with %s" % self.jabber_host))
-        self.xmlstream.namespace = C.NS_CLIENT
-        self.xmlstream.sendHeader()
-
-        iq = xmlstream.IQ(self.xmlstream, 'set')
-        iq["to"] = self.jabber_host
-        query = iq.addElement(('jabber:iq:register', 'query'))
-        _user = query.addElement('username')
-        _user.addContent(self.user_login)
-        _pass = query.addElement('password')
-        _pass.addContent(self.user_pass)
-        if self.user_email:
-            _email = query.addElement('email')
-            _email.addContent(self.user_email)
-        d = iq.send(self.jabber_host).addCallbacks(self.registrationAnswer, self.registrationFailure)
-        d.chainDeferred(self.deferred)
-
-    def registrationAnswer(self, answer):
-        log.debug(_(u"Registration answer: %s") % answer.toXml())
-        self.xmlstream.sendFooter()
-
-    def registrationFailure(self, failure_):
-        log.info(_("Registration failure: %s") % unicode(failure_.value))
-        self.xmlstream.sendFooter()
-        raise failure_.value
-
-
 class SatVersionHandler(generic.VersionHandler):
 
     def getDiscoInfo(self, requestor, target, node):
--- a/src/plugins/plugin_misc_register_account.py	Wed Mar 08 20:44:28 2017 +0100
+++ b/src/plugins/plugin_misc_register_account.py	Wed Mar 08 20:59:31 2017 +0100
@@ -22,10 +22,8 @@
 from sat.core.log import getLogger
 log = getLogger(__name__)
 from sat.core.constants import Const as C
-from twisted.words.protocols.jabber import jid, xmlstream
-from sat.core import xmpp
+from twisted.words.protocols.jabber import jid
 from sat.memory.memory import Sessions
-from twisted.internet import reactor, defer
 from sat.tools import xml_tools
 from sat.tools.xml_tools import SAT_FORM_PREFIX, SAT_PARAM_SEPARATOR
 
@@ -35,7 +33,7 @@
     C.PI_IMPORT_NAME: "REGISTER-ACCOUNT",
     C.PI_TYPE: "MISC",
     C.PI_PROTOCOLS: [],
-    C.PI_DEPENDENCIES: [],
+    C.PI_DEPENDENCIES: ["XEP-0077"],
     C.PI_RECOMMENDATIONS: [],
     C.PI_MAIN: "RegisterAccount",
     C.PI_HANDLER: "no",
@@ -44,6 +42,7 @@
 
 
 class RegisterAccount(object):
+    # FIXME: this plugin is messy and difficult to read, it needs to be cleaned up and documented
 
     def __init__(self, host):
         log.info(_(u"Plugin Register Account initialization"))
@@ -53,30 +52,35 @@
         self.__register_account_id = host.registerCallback(self._registerConfirmation, with_data=True)
 
     def registerNewAccountCB(self, data, profile):
-        """Called when the use click on the "New account" button."""
+        """Called when the user click on the "New account" button."""
         session_data = {}
-        for param in ('JabberID', 'Password', C.FORCE_PORT_PARAM, C.FORCE_SERVER_PARAM):
+
+        # FIXME: following loop is overcomplicated, hard to read
+        # FIXME: while used with parameters, hashed password is used and overwrite clear one
+        for param in (u'JabberID', u'Password', C.FORCE_PORT_PARAM, C.FORCE_SERVER_PARAM):
             try:
-                session_data[param] = data["%s%s%s%s" % (SAT_FORM_PREFIX, "Connection", SAT_PARAM_SEPARATOR, param)]
+                session_data[param] = data[SAT_FORM_PREFIX + u"Connection" + SAT_PARAM_SEPARATOR + param]
             except KeyError:
                 if param in (C.FORCE_PORT_PARAM, C.FORCE_SERVER_PARAM):
                     session_data[param] = ''
 
-        for param in ('JabberID', 'Password'):
+        for param in (u'JabberID', u'Password'):
             if not session_data[param]:
-                form_ui = xml_tools.XMLUI("popup", title=D_("Missing values"))
-                form_ui.addText(D_("No user JID or password given: can't register new account."))
-                return  {'xmlui': form_ui.toXml()}
+                form_ui = xml_tools.XMLUI(u"popup", title=D_(u"Missing values"))
+                form_ui.addText(D_(u"No user JID or password given: can't register new account."))
+                return  {u'xmlui': form_ui.toXml()}
 
         session_data['user'], host, resource = jid.parse(session_data['JabberID'])
         session_data['server'] = session_data[C.FORCE_SERVER_PARAM] or host
         session_id, dummy = self._sessions.newSession(session_data, profile=profile)
         form_ui = xml_tools.XMLUI("form", title=D_("Register new account"), submit_id=self.__register_account_id, session_id=session_id)
-        form_ui.addText(D_("Do you want to register a new XMPP account [%(user)s] on server %(server)s ?") % {'user': session_data['user'], 'server': session_data['server']})
+        form_ui.addText(D_(u"Do you want to register a new XMPP account {jid}?").format(
+            jid = session_data['JabberID']))
         return  {'xmlui': form_ui.toXml()}
 
     def _registerConfirmation(self, data, profile):
         """Save the related parameters and proceed the registration."""
+        client = self.host.getClient(profile)
         session_data = self._sessions.profileGet(data['session_id'], profile)
 
         self.host.memory.setParam("JabberID", session_data["JabberID"], "Connection", profile_key=profile)
@@ -84,32 +88,18 @@
         self.host.memory.setParam(C.FORCE_SERVER_PARAM, session_data[C.FORCE_SERVER_PARAM], "Connection", profile_key=profile)
         self.host.memory.setParam(C.FORCE_PORT_PARAM, session_data[C.FORCE_PORT_PARAM], "Connection", profile_key=profile)
 
-        d = self._registerNewAccount(session_data['user'], session_data["Password"], None, session_data['server'], profile_key=profile)
+        d = self._registerNewAccount(client, jid.JID(session_data['JabberID']), session_data["Password"], None, session_data['server'])
         del self._sessions[data['session_id']]
         return d
 
-    def _registerNewAccount(self, user, password, email, host, port=C.XMPP_C2S_PORT, profile_key=C.PROF_KEY_NONE):
-        """Connect to a server and create a new account using in-band registration.
-        @param user: login of the account
-        @param password: password of the account
-        @param email: email of the account
-        @param host: host of the server to register to
-        @param port: port of the server to register to
-        @param profile_key: %(doc_profile_key)s
-        """
-        profile = self.host.memory.getProfileName(profile_key)
-
-        d = defer.Deferred()
-        serverRegistrer = xmlstream.XmlStreamFactory(xmpp.RegisteringAuthenticator(self, host, user, password, email, d, profile))
-        connector = reactor.connectTCP(host, port or C.XMPP_C2S_PORT, serverRegistrer)
-        serverRegistrer.clientConnectionLost = lambda conn, reason: connector.disconnect()
-
-        def cb(dummy):
-            xmlui = xml_tools.XMLUI("popup", title=D_("Confirmation"))
+    def _registerNewAccount(self, client, jid_, password, email, server):
+        # FIXME: port is not set here
+        def registeredCb(dummy):
+            xmlui = xml_tools.XMLUI(u"popup", title=D_(u"Confirmation"))
             xmlui.addText(D_("Registration successful."))
             return ({'xmlui': xmlui.toXml()})
 
-        def eb(failure):
+        def registeredEb(failure):
             xmlui = xml_tools.XMLUI("popup", title=D_("Failure"))
             xmlui.addText(D_("Registration failed: %s") % failure.getErrorMessage())
             try:
@@ -119,5 +109,6 @@
                 pass
             return ({'xmlui': xmlui.toXml()})
 
-        d.addCallbacks(cb, eb)
-        return d
+        registered_d = self.host.plugins['XEP-0077'].registerNewAccount(client, jid_, password, email=email, host=server, port=C.XMPP_C2S_PORT)
+        registered_d.addCallbacks(registeredCb, registeredEb)
+        return registered_d
--- a/src/plugins/plugin_xep_0077.py	Wed Mar 08 20:44:28 2017 +0100
+++ b/src/plugins/plugin_xep_0077.py	Wed Mar 08 20:59:31 2017 +0100
@@ -23,10 +23,11 @@
 from sat.core.log import getLogger
 log = getLogger(__name__)
 from twisted.words.protocols.jabber import jid
-from twisted.words.protocols.jabber.xmlstream import IQ
+from twisted.words.protocols.jabber import xmlstream
+from twisted.internet import defer, reactor
 from sat.tools import xml_tools
 
-from wokkel import data_form, compat
+from wokkel import data_form
 
 NS_REG = 'jabber:iq:register'
 
@@ -40,18 +41,60 @@
     C.PI_DESCRIPTION: _("""Implementation of in-band registration""")
 }
 
+# FIXME: this implementation is incomplete
+
+class RegisteringAuthenticator(xmlstream.ConnectAuthenticator):
+    # FIXME: request IQ is not send to check available fields, while XEP recommand to use it
+    # FIXME: doesn't handle data form or oob
+
+    def __init__(self, jid_, password, email=None):
+        xmlstream.ConnectAuthenticator.__init__(self, jid_.host)
+        self.jid = jid_
+        self.password = password
+        self.email = email
+        self.registered = defer.Deferred()
+        log.debug(_(u"Registration asked for {jid}").format(
+            jid = jid_))
+
+    def connectionMade(self):
+        log.debug(_(u"Connection made with {server}".format(server=self.jid.host)))
+        self.xmlstream.otherEntity = jid.JID(self.jid.host)
+        self.xmlstream.namespace = C.NS_CLIENT
+        self.xmlstream.sendHeader()
+
+        iq = xmlstream.IQ(self.xmlstream, 'set')
+        iq["to"] = self.jid.host
+        query_elt = iq.addElement(('jabber:iq:register', 'query'))
+        username_elt = query_elt.addElement('username')
+        username_elt.addContent(self.jid.user)
+        password_elt = query_elt.addElement('password')
+        password_elt.addContent(self.password)
+        if self.email is not None:
+            email_elt = query_elt.addElement('email')
+            email_elt.addContent(self.email)
+        d = iq.send(self.jid.host).addCallbacks(self.registrationCb, self.registrationEb)
+        d.chainDeferred(self.registered)
+
+    def registrationCb(self, answer):
+        log.debug(_(u"Registration answer: {}").format(answer.toXml()))
+        self.xmlstream.sendFooter()
+
+    def registrationEb(self, failure_):
+        log.info(_("Registration failure: {}").format(unicode(failure_.value)))
+        self.xmlstream.sendFooter()
+        raise failure_.value
+
 
 class XEP_0077(object):
 
     def __init__(self, host):
         log.info(_("Plugin XEP_0077 initialization"))
         self.host = host
-        self.triggers = {}  # used by other protocol (e.g. XEP-0100) to finish registration. key = target_jid
         host.bridge.addMethod("inBandRegister", ".plugin", in_sign='ss', out_sign='s',
                               method=self._inBandRegister,
                               async=True)
 
-    def _regOk(self, answer, client, post_treat_cb):
+    def _regCb(self, answer, client, post_treat_cb):
         """Called after the first get IQ"""
         try:
             query_elt = answer.elements(NS_REG, 'query').next()
@@ -68,7 +111,7 @@
         def submitForm(data, profile):
             form_elt = xml_tools.XMLUIResultToElt(data)
 
-            iq_elt = compat.IQ(client.xmlstream, 'set')
+            iq_elt = client.IQ()
             iq_elt['id'] = answer['id']
             iq_elt['to'] = answer['from']
             query_elt = iq_elt.addElement("query", NS_REG)
@@ -82,7 +125,7 @@
         submit_reg_id = self.host.registerCallback(submitForm, with_data=True, one_shot=True)
         return xml_tools.dataForm2XMLUI(form, submit_reg_id)
 
-    def _regErr(self, failure, client):
+    def _regEb(self, failure, client):
         """Called when something is wrong with registration"""
         log.info(_("Registration failure: %s") % unicode(failure.value))
         raise failure
@@ -105,10 +148,26 @@
     def inBandRegister(self, to_jid, post_treat_cb=None, profile_key=C.PROF_KEY_NONE):
         """register to a target JID"""
         client = self.host.getClient(profile_key)
-        log.debug(_(u"Asking registration for [%s]") % to_jid.full())
-        reg_request = IQ(client.xmlstream, 'get')
+        log.debug(_(u"Asking registration for {}").format(to_jid.full()))
+        reg_request = client.IQ(u'get')
         reg_request["from"] = client.jid.full()
         reg_request["to"] = to_jid.full()
         reg_request.addElement('query', NS_REG)
-        d = reg_request.send(to_jid.full()).addCallbacks(self._regOk, self._regErr, callbackArgs=[client, post_treat_cb], errbackArgs=[client])
+        d = reg_request.send(to_jid.full()).addCallbacks(self._regCb, self._regEb, callbackArgs=[client, post_treat_cb], errbackArgs=[client])
         return d
+
+    def registerNewAccount(self, client, jid_, password, email=None, host="127.0.0.1", port=C.XMPP_C2S_PORT):
+        """register a new account on a XMPP server
+
+        @param jid_(jid.JID): request jid to register
+        @param password(unicode): password of the account
+        @param email(unicode): email of the account
+        @param host(unicode): host of the server to register to
+        @param port(int): port of the server to register to
+        """
+        authenticator = RegisteringAuthenticator(jid_, password, email)
+        registered_d = authenticator.registered
+        serverRegistrer = xmlstream.XmlStreamFactory(authenticator)
+        connector = reactor.connectTCP(host, port, serverRegistrer)
+        serverRegistrer.clientConnectionLost = lambda conn, reason: connector.disconnect()
+        return registered_d