# HG changeset patch # User souliane # Date 1448301002 -3600 # Node ID 200efadcab76bf61d7767ec7ace1ce5ac9c68546 # Parent fd7f41d8cbdf323e1599f931856d3ffbdc887bc2 plugin misc_account: add method asyncConnectWithXMPPCredentials diff -r fd7f41d8cbdf -r 200efadcab76 src/plugins/plugin_misc_account.py --- a/src/plugins/plugin_misc_account.py Mon Nov 23 18:51:15 2015 +0100 +++ b/src/plugins/plugin_misc_account.py Mon Nov 23 18:50:02 2015 +0100 @@ -33,6 +33,9 @@ from os.path import join, dirname from email.mime.text import MIMEText +import random + + PLUGIN_INFO = { "name": "Account Plugin", "import_name": "MISC-ACCOUNT", @@ -56,7 +59,6 @@ "admin_email": "admin@example.net", "new_account_server": "localhost", "new_account_domain": "example.net", - "new_account_resource": "libervia", "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 @@ -102,6 +104,7 @@ @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) @@ -127,6 +130,7 @@ host.bridge.addMethod("registerSatAccount", ".plugin", in_sign='sss', out_sign='', method=self._registerAccount, async=True) 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')) @@ -153,46 +157,64 @@ def getConfig(self, name): return self.host.memory.getConfig(CONFIG_SECTION, name, default_conf[name]) - def _registerAccount(self, email, password, profile): - + def generatePassword(self): + """Generate a password with random characters. + + @return unicode """ - #Password Generation - #_charset = [chr(i) for i in range(0x21,0x7F)] #XXX: this charset seems to have some issues with openfire - _charset = [chr(i) for i in range(0x30,0x3A) + range(0x41,0x5B) + range (0x61,0x7B)] - import random random.seed() - password = ''.join([random.choice(_charset) for i in range(15)]) + #charset = [chr(i) for i in range(0x21,0x7F)] #XXX: this charset seems to have some issues with openfire + charset = [chr(i) for i in range(0x30,0x3A) + range(0x41,0x5B) + range (0x61,0x7B)] + return ''.join([random.choice(charset) for i in range(15)]) + + def _registerAccount(self, email, password, profile): + return self.registerAccount(email, password, None, profile) + + def registerAccount(self, email, password, jid_s, profile): + """Register a new profile and its associated XMPP account. + + @param email (unicode): where to send to confirmation email to + @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 "": register a new JID on the local XMPP server + @param profile + @return Deferred """ - if not email or not password or not profile: + if not password or not profile: raise exceptions.DataError if profile.lower() in self.getConfig('reserved_list'): return defer.fail(Failure(exceptions.ConflictError)) d = self.host.memory.asyncCreateProfile(profile, password) - d.addCallback(self._profileRegistered, email, password, profile) + d.addCallback(lambda dummy: self.profileRegistered(email, password, jid_s, profile)) return d - def _profileRegistered(self, result, email, password, profile): - """Create the XMPP account and send the confirmation email. + def profileRegistered(self, email, password, jid_s, profile): + """Create the XMPP account, set the profile connection parameters and send the confirmation email. - @param result: result of asyncCreateProfile - @param email: user email - @param password: chosen xmpp password - @param profile: %(doc_profile)s - @return: a deferred None value when all the processing is done + @param email (unicode): where to send to confirmation email to + @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 """ - # XXX: we use "prosodyctl adduser" because "register" doesn't check conflict - # and just change the password if the account already exists - d = ProsodyRegisterProtocol.prosodyctl(self, 'adduser', password, profile) - d.addCallback(self._sendEmails, profile, email, password) - d.addCallback(lambda ignore: None) + # 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) + else: + d = ProsodyRegisterProtocol.prosodyctl(self, 'adduser', password, profile) + jid_s = "%s@%s" % (profile, self.getConfig('new_account_domain')) + if email: + d.addCallback(lambda dummy: self.sendEmails(email, jid_s, password, profile)) def setParams(dummy): - jid_s = "%s@%s/%s" % (profile, self.getConfig('new_account_domain'), self.getConfig('new_account_resource')) self.host.memory.setParam("JabberID", jid_s, "Connection", profile_key=profile) d = self.host.memory.setParam("Password", password, "Connection", profile_key=profile) - d.addCallback(lambda dummy: self.host.memory.auth_sessions.profileDelUnique(profile)) + return d.addCallback(lambda dummy: self.host.memory.auth_sessions.profileDelUnique(profile)) def removeProfile(failure): self.host.memory.asyncDeleteProfile(profile) @@ -202,13 +224,14 @@ d.addErrback(removeProfile) return d - def _sendEmails(self, result, login, email, password): + def sendEmails(self, email, jid_s, password, profile): # time to send the email _email_host = self.getConfig('email_server') _email_from = self.getConfig("email_from") domain = self.getConfig('new_account_domain') + def email_ok(ignore): log.debug(u"Account creation email sent to %s" % email) @@ -222,8 +245,8 @@ Here is your connection information: -Login on libervia.org: %(login)s -Jabber ID (JID): %(login)s@%(domain)s +Login on libervia.org: %(profile)s +Jabber ID (JID): %(jid_s)s Your password has been chosen by yourself. If you haven't read our security notice regarding the connection to https://libervia.org, please do it now: @@ -237,7 +260,7 @@ Salut à Toi association http://www.salut-a-toi.org -""") % {'login': login, 'domain': domain}).encode('utf-8') +""") % {'profile': profile, 'jid_s': jid_s, 'domain': domain}).encode('utf-8') msg = MIMEText(body, 'plain', 'UTF-8') msg['Subject'] = _(u'Libervia account created') msg['From'] = _email_from @@ -247,7 +270,7 @@ d_user.addCallbacks(email_ok, email_ko) # email to the administrator - body = (u"""New account created: %(login)s [%(email)s]""" % {'login': login, 'email': email}).encode('utf-8') + body = (u"""New account created: %(profile)s [%(email)s]""" % {'profile': profile, 'email': email}).encode('utf-8') msg = MIMEText(body, 'plain', 'UTF-8') msg['Subject'] = _('New Libervia account created') msg['From'] = _email_from @@ -484,3 +507,30 @@ d.addCallbacks(deleted, errback) return d + def asyncConnectWithXMPPCredentials(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.getProfileName(jid_s) + except exceptions.ProfileUnknownError: + pass + else: + raise exceptions.ConflictError + + d = self.registerAccount(None, password, jid_s, jid_s) + d.addCallback(lambda dummy: self.host.memory.getProfileName(jid_s)) # checks if the profile has been successfuly created + d.addCallback(self.host.asyncConnect, password) + + def removeProfile(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.asyncDeleteProfile(jid_s) + raise failure + + d.addErrback(removeProfile) + return d