comparison src/core/sat_main.py @ 1030:15f43b54d697

core, memory, bridge: added profile password + password encryption: /!\ This changeset updates the database version to 2 and modify the database content! Description: - new parameter General / Password to store the profile password - profile password is initialized with XMPP password value, it is stored hashed - bridge methods asyncCreateProfile/asyncConnect takes a new argument "password" (default = "") - bridge method asyncConnect returns a boolean (True = connection already established, False = connection initiated) - profile password is checked before initializing the XMPP connection - new private individual parameter to store the personal encryption key of each profile - personal key is randomly generated and encrypted with the profile password - personal key is decrypted after profile authentification and stored in a Sessions instance - personal key is used to encrypt/decrypt other passwords when they need to be retrieved/modified - modifying the profile password re-encrypt the personal key - Memory.setParam now returns a Deferred (the bridge method "setParam" is unchanged) - Memory.asyncGetParamA eventually decrypts the password, Memory.getParamA would fail on a password parameter TODO: - if profile authentication is OK but XMPP authentication is KO, prompt the user for another XMPP password - fix the method "registerNewAccount" (and move it to a plugin) - remove bridge method "connect", sole "asyncConnect" should be used
author souliane <souliane@mailoo.org>
date Wed, 07 May 2014 16:02:23 +0200
parents ee46515a12f2
children b262ae6d53af
comparison
equal deleted inserted replaced
1029:f6182f6418ea 1030:15f43b54d697
29 from sat.core import exceptions 29 from sat.core import exceptions
30 from sat.core.log import getLogger 30 from sat.core.log import getLogger
31 log = getLogger(__name__) 31 log = getLogger(__name__)
32 from sat.core.constants import Const as C 32 from sat.core.constants import Const as C
33 from sat.memory.memory import Memory 33 from sat.memory.memory import Memory
34 from sat.memory.crypto import PasswordHasher
34 from sat.tools.misc import TriggerManager 35 from sat.tools.misc import TriggerManager
35 from sat.stdui import ui_contact_list 36 from sat.stdui import ui_contact_list
36 from glob import glob 37 from glob import glob
37 from uuid import uuid4 38 from uuid import uuid4
38 import sys 39 import sys
196 self.plugins[import_name].is_handler = True 197 self.plugins[import_name].is_handler = True
197 else: 198 else:
198 self.plugins[import_name].is_handler = False 199 self.plugins[import_name].is_handler = False
199 #TODO: test xmppclient presence and register handler parent 200 #TODO: test xmppclient presence and register handler parent
200 201
201 def connect(self, profile_key=C.PROF_KEY_NONE): 202 def connect(self, profile_key=C.PROF_KEY_NONE, password=''):
202 """Connect to jabber server""" 203 """Connect to jabber server
203 self.asyncConnect(profile_key) 204
204 205 @param password (string): the SàT profile password
205 def asyncConnect(self, profile_key=C.PROF_KEY_NONE): 206 @param profile_key: %(doc_profile_key)s
206 """Connect to jabber server with asynchronous reply 207 """
207 @param profile_key: %(doc_profile)s 208 self.asyncConnect(profile_key, password)
209
210 def asyncConnect(self, profile_key=C.PROF_KEY_NONE, password=''):
211 """Retrieve the individual parameters, authenticate the profile
212 and initiate the connection to the associated XMPP server.
213
214 @param password (string): the SàT profile password
215 @param profile_key: %(doc_profile_key)s
216 @return: Deferred:
217 - a deferred boolean if the profile authentication succeed:
218 - True if the XMPP connection was already established
219 - False if the XMPP connection has been initiated (it may still fail)
220 - a Failure if the profile authentication failed
208 """ 221 """
209 def backendInitialised(dummy): 222 def backendInitialised(dummy):
210 profile = self.memory.getProfileName(profile_key) 223 profile = self.memory.getProfileName(profile_key)
211 if not profile: 224 if not profile:
212 log.error(_('Trying to connect a non-existant profile')) 225 log.error(_('Trying to connect a non-existant profile'))
213 raise exceptions.ProfileUnknownError(profile_key) 226 raise exceptions.ProfileUnknownError(profile_key)
214 227
215 if self.isConnected(profile): 228 def connectXMPPClient(dummy=None):
216 log.info(_("already connected !")) 229 if self.isConnected(profile):
217 return defer.succeed("None") 230 log.info(_("already connected !"))
218 231 return True
219 def afterMemoryInit(ignore): 232 self.memory.startProfileSession(profile)
220 """This part must be called when we have loaded individual parameters from memory""" 233 d = self.memory.loadIndividualParams(profile)
221 try: 234 d.addCallback(lambda dummy: self._connectXMPPClient(profile))
222 port = int(self.memory.getParamA("Port", "Connection", profile_key=profile)) 235 return d.addCallback(lambda dummy: False)
223 except ValueError: 236
224 log.error(_("Can't parse port value, using default value")) 237 d = self._authenticateProfile(password, profile)
225 port = 5222 238 d.addCallback(connectXMPPClient)
226 239 return d
227 current = self.profiles[profile] = xmpp.SatXMPPClient( 240
228 self, profile,
229 jid.JID(self.memory.getParamA("JabberID", "Connection", profile_key=profile), profile),
230 self.memory.getParamA("Password", "Connection", profile_key=profile),
231 self.memory.getParamA("Server", "Connection", profile_key=profile),
232 port)
233
234 current.messageProt = xmpp.SatMessageProtocol(self)
235 current.messageProt.setHandlerParent(current)
236
237 current.roster = xmpp.SatRosterProtocol(self)
238 current.roster.setHandlerParent(current)
239
240 current.presence = xmpp.SatPresenceProtocol(self)
241 current.presence.setHandlerParent(current)
242
243 current.fallBack = xmpp.SatFallbackHandler(self)
244 current.fallBack.setHandlerParent(current)
245
246 current.versionHandler = xmpp.SatVersionHandler(C.APP_NAME_FULL,
247 C.APP_VERSION)
248 current.versionHandler.setHandlerParent(current)
249
250 current.identityHandler = xmpp.SatIdentityHandler()
251 current.identityHandler.setHandlerParent(current)
252
253 log.debug(_("setting plugins parents"))
254
255 plugin_conn_cb = []
256 for plugin in self.plugins.iteritems():
257 if plugin[1].is_handler:
258 plugin[1].getHandler(profile).setHandlerParent(current)
259 connected_cb = getattr(plugin[1], "profileConnected", None)
260 if connected_cb:
261 plugin_conn_cb.append((plugin[0], connected_cb))
262
263 current.startService()
264
265 d = current.getConnectionDeferred()
266 d.addCallback(lambda dummy: current.roster.got_roster) # we want to be sure that we got the roster
267
268 def pluginsConnection(dummy):
269 """Call profileConnected callback for all plugins, and print error message if any of them fails"""
270 conn_cb_list = []
271 for dummy, callback in plugin_conn_cb:
272 conn_cb_list.append(defer.maybeDeferred(callback, profile))
273 list_d = defer.DeferredList(conn_cb_list)
274
275 def logPluginResults(results):
276 all_succeed = all([success for success, result in results])
277 if not all_succeed:
278 log.error(_("Plugins initialisation error"))
279 for idx, (success, result) in enumerate(results):
280 if not success:
281 log.error("error (plugin %(name)s): %(failure)s" % {'name': plugin_conn_cb[idx][0],
282 'failure': result})
283
284 list_d.addCallback(logPluginResults)
285 return list_d
286
287 d.addCallback(pluginsConnection)
288 return d
289
290 self.memory.startProfileSession(profile)
291 return self.memory.loadIndividualParams(profile).addCallback(afterMemoryInit)
292 return self._initialised.addCallback(backendInitialised) 241 return self._initialised.addCallback(backendInitialised)
242
243 @defer.inlineCallbacks
244 def _connectXMPPClient(self, profile):
245 """This part is called from asyncConnect when we have loaded individual parameters from memory"""
246 try:
247 port = int(self.memory.getParamA("Port", "Connection", profile_key=profile))
248 except ValueError:
249 log.error(_("Can't parse port value, using default value"))
250 port = 5222
251
252 password = yield self.memory.asyncGetParamA("Password", "Connection", profile_key=profile)
253 current = self.profiles[profile] = xmpp.SatXMPPClient(self, profile,
254 jid.JID(self.memory.getParamA("JabberID", "Connection", profile_key=profile)),
255 password, self.memory.getParamA("Server", "Connection", profile_key=profile), port)
256
257 current.messageProt = xmpp.SatMessageProtocol(self)
258 current.messageProt.setHandlerParent(current)
259
260 current.roster = xmpp.SatRosterProtocol(self)
261 current.roster.setHandlerParent(current)
262
263 current.presence = xmpp.SatPresenceProtocol(self)
264 current.presence.setHandlerParent(current)
265
266 current.fallBack = xmpp.SatFallbackHandler(self)
267 current.fallBack.setHandlerParent(current)
268
269 current.versionHandler = xmpp.SatVersionHandler(C.APP_NAME_FULL,
270 C.APP_VERSION)
271 current.versionHandler.setHandlerParent(current)
272
273 current.identityHandler = xmpp.SatIdentityHandler()
274 current.identityHandler.setHandlerParent(current)
275
276 log.debug(_("setting plugins parents"))
277
278 plugin_conn_cb = []
279 for plugin in self.plugins.iteritems():
280 if plugin[1].is_handler:
281 plugin[1].getHandler(profile).setHandlerParent(current)
282 connected_cb = getattr(plugin[1], "profileConnected", None)
283 if connected_cb:
284 plugin_conn_cb.append((plugin[0], connected_cb))
285
286 current.startService()
287
288 yield current.getConnectionDeferred()
289 yield current.roster.got_roster # we want to be sure that we got the roster
290
291 # Call profileConnected callback for all plugins, and print error message if any of them fails
292 conn_cb_list = []
293 for dummy, callback in plugin_conn_cb:
294 conn_cb_list.append(defer.maybeDeferred(callback, profile))
295 list_d = defer.DeferredList(conn_cb_list)
296
297 def logPluginResults(results):
298 all_succeed = all([success for success, result in results])
299 if not all_succeed:
300 log.error(_("Plugins initialisation error"))
301 for idx, (success, result) in enumerate(results):
302 if not success:
303 log.error("error (plugin %(name)s): %(failure)s" %
304 {'name': plugin_conn_cb[idx][0], 'failure': result})
305
306 yield list_d.addCallback(logPluginResults)
307
308 def _authenticateProfile(self, password, profile):
309 """Authenticate the profile.
310
311 @param password (string): the SàT profile password
312 @param profile_key: %(doc_profile_key)s
313 @return: Deferred: a deferred None in case of success, a failure otherwise.
314 """
315 session_data = self.memory.auth_sessions.profileGetUnique(profile)
316 if not password and session_data:
317 # XXX: this allows any frontend to connect with the empty password as soon as
318 # the profile has been authenticated at least once before. It is OK as long as
319 # submitting a form with empty passwords is restricted to local frontends.
320 return defer.succeed(None)
321
322 def check_result(result):
323 if not result:
324 log.warning(_('Authentication failure of profile %s') % profile)
325 raise exceptions.PasswordError(_("The provided profile password doesn't match."))
326 if not session_data: # avoid to create two profile sessions when password if specified
327 return self.memory.newAuthSession(password, profile)
328
329 d = self.memory.asyncGetParamA(C.PROFILE_PASS_PATH[1], C.PROFILE_PASS_PATH[0], profile_key=profile)
330 d.addCallback(lambda sat_cipher: PasswordHasher.verify(password, sat_cipher))
331 return d.addCallback(check_result)
293 332
294 def disconnect(self, profile_key): 333 def disconnect(self, profile_key):
295 """disconnect from jabber server""" 334 """disconnect from jabber server"""
296 if not self.isConnected(profile_key): 335 if not self.isConnected(profile_key):
297 log.info(_("not connected !")) 336 log.info(_("not connected !"))
386 return next_id 425 return next_id
387 426
388 def registerNewAccountCB(self, data, profile): 427 def registerNewAccountCB(self, data, profile):
389 # FIXME: to be removed/redone elsewhere 428 # FIXME: to be removed/redone elsewhere
390 user = jid.parse(self.memory.getParamA("JabberID", "Connection", profile_key=profile))[0] 429 user = jid.parse(self.memory.getParamA("JabberID", "Connection", profile_key=profile))[0]
391 password = self.memory.getParamA("Password", "Connection", profile_key=profile)
392 server = self.memory.getParamA("Server", "Connection", profile_key=profile) 430 server = self.memory.getParamA("Server", "Connection", profile_key=profile)
393 431 d = self.memory.asyncGetParamA("Password", "Connection", profile_key=profile)
394 if not user or not password or not server: 432
395 raise exceptions.DataError(_("No user, password or server given, can't register new account.")) 433 def gotPassword(password):
396 434 if not user or not password or not server:
397 # FIXME: to be fixed with XMLUI dialogs once their implemented 435 raise exceptions.DataError(_("No user, password or server given, can't register new account."))
398 confirm_id = sat_next_id() 436
399 self.__private_data[confirm_id] = (id, profile) 437 # FIXME: to be fixed with XMLUI dialogs once their implemented
400 438 confirm_id = sat_next_id()
401 self.askConfirmation( 439 self.__private_data[confirm_id] = (id, profile)
402 confirm_id, "YES/NO", 440
403 {"message": _("Are you sure to register new account [%(user)s] to server %(server)s ?") % {'user': user, 'server': server, 'profile': profile}}, 441 self.askConfirmation(
404 self.regisConfirmCB, profile) 442 confirm_id, "YES/NO",
405 print "===============+++++++++++ REGISTER NEW ACCOUNT++++++++++++++============" 443 {"message": _("Are you sure to register new account [%(user)s] to server %(server)s ?") % {'user': user, 'server': server, 'profile': profile}},
444 self.regisConfirmCB, profile)
445 print "===============+++++++++++ REGISTER NEW ACCOUNT++++++++++++++============"
446
447 d.addCallback(gotPassword)
406 448
407 def regisConfirmCB(self, id, accepted, data, profile): 449 def regisConfirmCB(self, id, accepted, data, profile):
408 print _("register Confirmation CB ! (%s)") % str(accepted) 450 print _("register Confirmation CB ! (%s)") % str(accepted)
409 action_id, profile = self.__private_data[id] 451 action_id, profile = self.__private_data[id]
410 del self.__private_data[id] 452 del self.__private_data[id]
411 if accepted: 453 if accepted:
412 user = jid.parse(self.memory.getParamA("JabberID", "Connection", profile_key=profile))[0] 454 user = jid.parse(self.memory.getParamA("JabberID", "Connection", profile_key=profile))[0]
413 password = self.memory.getParamA("Password", "Connection", profile_key=profile)
414 server = self.memory.getParamA("Server", "Connection", profile_key=profile) 455 server = self.memory.getParamA("Server", "Connection", profile_key=profile)
415 self.registerNewAccount(user, password, None, server, id=action_id) 456 d = self.memory.asyncGetParamA("Password", "Connection", profile_key=profile)
457 d.addCallback(lambda password: self.registerNewAccount(user, password, None, server, id=action_id))
416 else: 458 else:
417 self.actionResult(action_id, "SUPPRESS", {}, profile) 459 self.actionResult(action_id, "SUPPRESS", {}, profile)
418 460
419 ## Client management ## 461 ## Client management ##
420 462
421 def setParam(self, name, value, category, security_limit, profile_key): 463 def setParam(self, name, value, category, security_limit, profile_key):
422 """set wanted paramater and notice observers""" 464 """set wanted paramater and notice observers"""
423 log.info(_("setting param: %(name)s=%(value)s in category %(category)s") % {'name': name, 'value': value, 'category': category})
424 self.memory.setParam(name, value, category, security_limit, profile_key) 465 self.memory.setParam(name, value, category, security_limit, profile_key)
425 466
426 def isConnected(self, profile_key): 467 def isConnected(self, profile_key):
427 """Return connection status of profile 468 """Return connection status of profile
428 @param profile_key: key_word or profile name to determine profile name 469 @param profile_key: key_word or profile name to determine profile name
429 @return True if connected 470 @return: True if connected
430 """ 471 """
431 profile = self.memory.getProfileName(profile_key) 472 profile = self.memory.getProfileName(profile_key)
432 if not profile: 473 if not profile:
433 log.error(_('asking connection status for a non-existant profile')) 474 log.error(_('asking connection status for a non-existant profile'))
434 return 475 raise exceptions.ProfileUnknownError(profile_key)
435 if profile not in self.profiles: 476 if profile not in self.profiles:
436 return False 477 return False
437 return self.profiles[profile].isConnected() 478 return self.profiles[profile].isConnected()
438 479
439 480