comparison src/plugins/plugin_misc_account.py @ 1653:200efadcab76

plugin misc_account: add method asyncConnectWithXMPPCredentials
author souliane <souliane@mailoo.org>
date Mon, 23 Nov 2015 18:50:02 +0100
parents 5d42e2219d7c
children cec204c6360c
comparison
equal deleted inserted replaced
1652:fd7f41d8cbdf 1653:200efadcab76
30 from twisted.python.procutils import which 30 from twisted.python.procutils import which
31 from twisted.python.failure import Failure 31 from twisted.python.failure import Failure
32 from twisted.mail.smtp import sendmail 32 from twisted.mail.smtp import sendmail
33 from os.path import join, dirname 33 from os.path import join, dirname
34 from email.mime.text import MIMEText 34 from email.mime.text import MIMEText
35
36 import random
37
35 38
36 PLUGIN_INFO = { 39 PLUGIN_INFO = {
37 "name": "Account Plugin", 40 "name": "Account Plugin",
38 "import_name": "MISC-ACCOUNT", 41 "import_name": "MISC-ACCOUNT",
39 "type": "MISC", 42 "type": "MISC",
54 default_conf = {"email_from": "NOREPLY@example.net", 57 default_conf = {"email_from": "NOREPLY@example.net",
55 "email_server": "localhost", 58 "email_server": "localhost",
56 "admin_email": "admin@example.net", 59 "admin_email": "admin@example.net",
57 "new_account_server": "localhost", 60 "new_account_server": "localhost",
58 "new_account_domain": "example.net", 61 "new_account_domain": "example.net",
59 "new_account_resource": "libervia",
60 "prosody_path": None, # prosody path (where prosodyctl will be executed from), or None to automaticaly find it 62 "prosody_path": None, # prosody path (where prosodyctl will be executed from), or None to automaticaly find it
61 "prosodyctl": "prosodyctl", 63 "prosodyctl": "prosodyctl",
62 "reserved_list": ['libervia'] # profiles which can't be used 64 "reserved_list": ['libervia'] # profiles which can't be used
63 } 65 }
64 66
100 self.deferred.errback(Failure(exceptions.InternalError)) 102 self.deferred.errback(Failure(exceptions.InternalError))
101 103
102 @classmethod 104 @classmethod
103 def prosodyctl(cls, plugin, command, password=None, profile=None): 105 def prosodyctl(cls, plugin, command, password=None, profile=None):
104 """Create a new ProsodyRegisterProtocol and execute the given prosodyctl command. 106 """Create a new ProsodyRegisterProtocol and execute the given prosodyctl command.
107
105 @param plugin: instance of MiscAccount 108 @param plugin: instance of MiscAccount
106 @param command: the command to execute: "adduser", "passwd" or "deluser" 109 @param command: the command to execute: "adduser", "passwd" or "deluser"
107 @param password: the user new password (leave to None for "deluser" command) 110 @param password: the user new password (leave to None for "deluser" command)
108 @param profile: the user profile 111 @param profile: the user profile
109 @return: a Deferred instance 112 @return: a Deferred instance
125 log.info(_(u"Plugin Account initialization")) 128 log.info(_(u"Plugin Account initialization"))
126 self.host = host 129 self.host = host
127 host.bridge.addMethod("registerSatAccount", ".plugin", in_sign='sss', out_sign='', method=self._registerAccount, async=True) 130 host.bridge.addMethod("registerSatAccount", ".plugin", in_sign='sss', out_sign='', method=self._registerAccount, async=True)
128 host.bridge.addMethod("getNewAccountDomain", ".plugin", in_sign='', out_sign='s', method=self.getNewAccountDomain, async=False) 131 host.bridge.addMethod("getNewAccountDomain", ".plugin", in_sign='', out_sign='s', method=self.getNewAccountDomain, async=False)
129 host.bridge.addMethod("getAccountDialogUI", ".plugin", in_sign='s', out_sign='s', method=self._getAccountDialogUI, async=False) 132 host.bridge.addMethod("getAccountDialogUI", ".plugin", in_sign='s', out_sign='s', method=self._getAccountDialogUI, async=False)
133 host.bridge.addMethod("asyncConnectWithXMPPCredentials", ".plugin", in_sign='ss', out_sign='b', method=self.asyncConnectWithXMPPCredentials, async=True)
130 self._prosody_path = self.getConfig('prosody_path') 134 self._prosody_path = self.getConfig('prosody_path')
131 if self._prosody_path is None: 135 if self._prosody_path is None:
132 paths = which(self.getConfig('prosodyctl')) 136 paths = which(self.getConfig('prosodyctl'))
133 if not paths: 137 if not paths:
134 log.error(_(u"Can't find %s") % (self.getConfig('prosodyctl'),)) 138 log.error(_(u"Can't find %s") % (self.getConfig('prosodyctl'),))
151 self.__delete_account_id = host.registerCallback(self.__deleteAccountCb, with_data=True) 155 self.__delete_account_id = host.registerCallback(self.__deleteAccountCb, with_data=True)
152 156
153 def getConfig(self, name): 157 def getConfig(self, name):
154 return self.host.memory.getConfig(CONFIG_SECTION, name, default_conf[name]) 158 return self.host.memory.getConfig(CONFIG_SECTION, name, default_conf[name])
155 159
160 def generatePassword(self):
161 """Generate a password with random characters.
162
163 @return unicode
164 """
165 random.seed()
166 #charset = [chr(i) for i in range(0x21,0x7F)] #XXX: this charset seems to have some issues with openfire
167 charset = [chr(i) for i in range(0x30,0x3A) + range(0x41,0x5B) + range (0x61,0x7B)]
168 return ''.join([random.choice(charset) for i in range(15)])
169
156 def _registerAccount(self, email, password, profile): 170 def _registerAccount(self, email, password, profile):
157 171 return self.registerAccount(email, password, None, profile)
158 """ 172
159 #Password Generation 173 def registerAccount(self, email, password, jid_s, profile):
160 #_charset = [chr(i) for i in range(0x21,0x7F)] #XXX: this charset seems to have some issues with openfire 174 """Register a new profile and its associated XMPP account.
161 _charset = [chr(i) for i in range(0x30,0x3A) + range(0x41,0x5B) + range (0x61,0x7B)] 175
162 import random 176 @param email (unicode): where to send to confirmation email to
163 random.seed() 177 @param password (unicode): password chosen by the user
164 password = ''.join([random.choice(_charset) for i in range(15)]) 178 @param jid_s (unicode): JID to re-use or to register:
165 """ 179 - non empty value: bind this JID to the new sat profile
166 if not email or not password or not profile: 180 - None or "": register a new JID on the local XMPP server
181 @param profile
182 @return Deferred
183 """
184 if not password or not profile:
167 raise exceptions.DataError 185 raise exceptions.DataError
168 186
169 if profile.lower() in self.getConfig('reserved_list'): 187 if profile.lower() in self.getConfig('reserved_list'):
170 return defer.fail(Failure(exceptions.ConflictError)) 188 return defer.fail(Failure(exceptions.ConflictError))
171 189
172 d = self.host.memory.asyncCreateProfile(profile, password) 190 d = self.host.memory.asyncCreateProfile(profile, password)
173 d.addCallback(self._profileRegistered, email, password, profile) 191 d.addCallback(lambda dummy: self.profileRegistered(email, password, jid_s, profile))
174 return d 192 return d
175 193
176 def _profileRegistered(self, result, email, password, profile): 194 def profileRegistered(self, email, password, jid_s, profile):
177 """Create the XMPP account and send the confirmation email. 195 """Create the XMPP account, set the profile connection parameters and send the confirmation email.
178 196
179 @param result: result of asyncCreateProfile 197 @param email (unicode): where to send to confirmation email to
180 @param email: user email 198 @param password (unicode): password chosen by the user
181 @param password: chosen xmpp password 199 @param jid_s (unicode): JID to re-use or to register:
182 @param profile: %(doc_profile)s 200 - non empty value: bind this JID to the new sat profile
183 @return: a deferred None value when all the processing is done 201 - None or empty: register a new JID on the local XMPP server
184 """ 202 @param profile
185 # XXX: we use "prosodyctl adduser" because "register" doesn't check conflict 203 @return: Deferred
186 # and just change the password if the account already exists 204 """
187 d = ProsodyRegisterProtocol.prosodyctl(self, 'adduser', password, profile) 205 # XXX: we use "prosodyctl adduser" because "register" doesn't check conflict and just change the password if the account already exists
188 d.addCallback(self._sendEmails, profile, email, password) 206 if jid_s:
189 d.addCallback(lambda ignore: None) 207 d = defer.succeed(None)
208 else:
209 d = ProsodyRegisterProtocol.prosodyctl(self, 'adduser', password, profile)
210 jid_s = "%s@%s" % (profile, self.getConfig('new_account_domain'))
211 if email:
212 d.addCallback(lambda dummy: self.sendEmails(email, jid_s, password, profile))
190 213
191 def setParams(dummy): 214 def setParams(dummy):
192 jid_s = "%s@%s/%s" % (profile, self.getConfig('new_account_domain'), self.getConfig('new_account_resource'))
193 self.host.memory.setParam("JabberID", jid_s, "Connection", profile_key=profile) 215 self.host.memory.setParam("JabberID", jid_s, "Connection", profile_key=profile)
194 d = self.host.memory.setParam("Password", password, "Connection", profile_key=profile) 216 d = self.host.memory.setParam("Password", password, "Connection", profile_key=profile)
195 d.addCallback(lambda dummy: self.host.memory.auth_sessions.profileDelUnique(profile)) 217 return d.addCallback(lambda dummy: self.host.memory.auth_sessions.profileDelUnique(profile))
196 218
197 def removeProfile(failure): 219 def removeProfile(failure):
198 self.host.memory.asyncDeleteProfile(profile) 220 self.host.memory.asyncDeleteProfile(profile)
199 return failure 221 return failure
200 222
201 d.addCallback(setParams) 223 d.addCallback(setParams)
202 d.addErrback(removeProfile) 224 d.addErrback(removeProfile)
203 return d 225 return d
204 226
205 def _sendEmails(self, result, login, email, password): 227 def sendEmails(self, email, jid_s, password, profile):
206 # time to send the email 228 # time to send the email
207 229
208 _email_host = self.getConfig('email_server') 230 _email_host = self.getConfig('email_server')
209 _email_from = self.getConfig("email_from") 231 _email_from = self.getConfig("email_from")
210 domain = self.getConfig('new_account_domain') 232 domain = self.getConfig('new_account_domain')
211 233
234
212 def email_ok(ignore): 235 def email_ok(ignore):
213 log.debug(u"Account creation email sent to %s" % email) 236 log.debug(u"Account creation email sent to %s" % email)
214 237
215 def email_ko(ignore): 238 def email_ko(ignore):
216 # TODO: return error code to user 239 # TODO: return error code to user
220 243
221 Your account on %(domain)s has been successfully created. This is a demonstration version to show you the current status of the project. It is still under development, it misses some features and it's not stable enough to be used for any serious purpose. Please keep it in mind! 244 Your account on %(domain)s has been successfully created. This is a demonstration version to show you the current status of the project. It is still under development, it misses some features and it's not stable enough to be used for any serious purpose. Please keep it in mind!
222 245
223 Here is your connection information: 246 Here is your connection information:
224 247
225 Login on libervia.org: %(login)s 248 Login on libervia.org: %(profile)s
226 Jabber ID (JID): %(login)s@%(domain)s 249 Jabber ID (JID): %(jid_s)s
227 250
228 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: 251 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:
229 252
230 http://salut-a-toi.org/faq.html#certificate 253 http://salut-a-toi.org/faq.html#certificate
231 254
235 258
236 Any feedback welcome. Thank you! 259 Any feedback welcome. Thank you!
237 260
238 Salut à Toi association 261 Salut à Toi association
239 http://www.salut-a-toi.org 262 http://www.salut-a-toi.org
240 """) % {'login': login, 'domain': domain}).encode('utf-8') 263 """) % {'profile': profile, 'jid_s': jid_s, 'domain': domain}).encode('utf-8')
241 msg = MIMEText(body, 'plain', 'UTF-8') 264 msg = MIMEText(body, 'plain', 'UTF-8')
242 msg['Subject'] = _(u'Libervia account created') 265 msg['Subject'] = _(u'Libervia account created')
243 msg['From'] = _email_from 266 msg['From'] = _email_from
244 msg['To'] = email 267 msg['To'] = email
245 268
246 d_user = sendmail(_email_host, _email_from, email, msg.as_string()) 269 d_user = sendmail(_email_host, _email_from, email, msg.as_string())
247 d_user.addCallbacks(email_ok, email_ko) 270 d_user.addCallbacks(email_ok, email_ko)
248 271
249 # email to the administrator 272 # email to the administrator
250 body = (u"""New account created: %(login)s [%(email)s]""" % {'login': login, 'email': email}).encode('utf-8') 273 body = (u"""New account created: %(profile)s [%(email)s]""" % {'profile': profile, 'email': email}).encode('utf-8')
251 msg = MIMEText(body, 'plain', 'UTF-8') 274 msg = MIMEText(body, 'plain', 'UTF-8')
252 msg['Subject'] = _('New Libervia account created') 275 msg['Subject'] = _('New Libervia account created')
253 msg['From'] = _email_from 276 msg['From'] = _email_from
254 msg['To'] = self.getConfig('admin_email') 277 msg['To'] = self.getConfig('admin_email')
255 278
482 return defer.succeed({'xmlui': error_ui.toXml()}) 505 return defer.succeed({'xmlui': error_ui.toXml()})
483 506
484 d.addCallbacks(deleted, errback) 507 d.addCallbacks(deleted, errback)
485 return d 508 return d
486 509
510 def asyncConnectWithXMPPCredentials(self, jid_s, password):
511 """Create and connect a new SàT profile using the given XMPP credentials.
512
513 Re-use given JID and XMPP password for the profile name and profile password.
514 @param jid_s (unicode): JID
515 @param password (unicode): XMPP password
516 @return Deferred(bool)
517 @raise exceptions.PasswordError, exceptions.ConflictError
518 """
519 try: # be sure that the profile doesn't exist yet
520 self.host.memory.getProfileName(jid_s)
521 except exceptions.ProfileUnknownError:
522 pass
523 else:
524 raise exceptions.ConflictError
525
526 d = self.registerAccount(None, password, jid_s, jid_s)
527 d.addCallback(lambda dummy: self.host.memory.getProfileName(jid_s)) # checks if the profile has been successfuly created
528 d.addCallback(self.host.asyncConnect, password)
529
530 def removeProfile(failure): # profile has been successfully created but the XMPP credentials are wrong!
531 log.debug("Removing previously auto-created profile: %s" % failure.getErrorMessage())
532 self.host.memory.asyncDeleteProfile(jid_s)
533 raise failure
534
535 d.addErrback(removeProfile)
536 return d