comparison src/core/sat_main.py @ 1591:0df9c6247474

core: profile session starting and connection are now separated. Moved profile session starting/authentication to memory module
author Goffi <goffi@goffi.org>
date Sat, 14 Nov 2015 19:18:10 +0100
parents 698d6755d62a
children d6d655238a93
comparison
equal deleted inserted replaced
1590:ab54af2a9ab2 1591:0df9c6247474
16 16
17 # You should have received a copy of the GNU Affero General Public License 17 # You should have received a copy of the GNU Affero General Public License
18 # along with this program. If not, see <http://www.gnu.org/licenses/>. 18 # along with this program. If not, see <http://www.gnu.org/licenses/>.
19 19
20 import sat 20 import sat
21 from sat.core.i18n import _, D_, languageSwitch 21 from sat.core.i18n import _, languageSwitch
22 from twisted.application import service 22 from twisted.application import service
23 from twisted.internet import defer 23 from twisted.internet import defer
24 from twisted.words.protocols.jabber import jid 24 from twisted.words.protocols.jabber import jid
25 from twisted.words.xish import domish 25 from twisted.words.xish import domish
26 from twisted.internet import reactor 26 from twisted.internet import reactor
30 from sat.core import exceptions 30 from sat.core import exceptions
31 from sat.core.log import getLogger 31 from sat.core.log import getLogger
32 log = getLogger(__name__) 32 log = getLogger(__name__)
33 from sat.core.constants import Const as C 33 from sat.core.constants import Const as C
34 from sat.memory.memory import Memory 34 from sat.memory.memory import Memory
35 from sat.memory.crypto import PasswordHasher
36 from sat.tools import trigger 35 from sat.tools import trigger
37 from sat.tools import utils 36 from sat.tools import utils
38 from sat.stdui import ui_contact_list, ui_profile_manager 37 from sat.stdui import ui_contact_list, ui_profile_manager
39 from glob import glob 38 from glob import glob
40 from uuid import uuid4 39 from uuid import uuid4
52 51
53 def __init__(self): 52 def __init__(self):
54 self._cb_map = {} # map from callback_id to callbacks 53 self._cb_map = {} # map from callback_id to callbacks
55 self._menus = OrderedDict() # dynamic menus. key: callback_id, value: menu data (dictionnary) 54 self._menus = OrderedDict() # dynamic menus. key: callback_id, value: menu data (dictionnary)
56 self.__private_data = {} # used for internal callbacks (key = id) FIXME: to be removed 55 self.__private_data = {} # used for internal callbacks (key = id) FIXME: to be removed
57 self._initialised = defer.Deferred() 56 self.initialised = defer.Deferred()
58 self.profiles = {} 57 self.profiles = {}
59 self.plugins = {} 58 self.plugins = {}
60 59
61 self.memory = Memory(self) 60 self.memory = Memory(self)
62 self.trigger = trigger.TriggerManager() # trigger are used to change SàT behaviour 61 self.trigger = trigger.TriggerManager() # trigger are used to change SàT behaviour
64 try: 63 try:
65 self.bridge = DBusBridge() 64 self.bridge = DBusBridge()
66 except exceptions.BridgeInitError: 65 except exceptions.BridgeInitError:
67 log.error(u"Bridge can't be initialised, can't start SàT core") 66 log.error(u"Bridge can't be initialised, can't start SàT core")
68 sys.exit(1) 67 sys.exit(1)
69 self.bridge.register("getReady", lambda: self._initialised) 68 self.bridge.register("getReady", lambda: self.initialised)
70 self.bridge.register("getVersion", lambda: self.full_version) 69 self.bridge.register("getVersion", lambda: self.full_version)
71 self.bridge.register("getFeatures", self.getFeatures) 70 self.bridge.register("getFeatures", self.getFeatures)
72 self.bridge.register("getProfileName", self.memory.getProfileName) 71 self.bridge.register("getProfileName", self.memory.getProfileName)
73 self.bridge.register("getProfilesList", self.memory.getProfilesList) 72 self.bridge.register("getProfilesList", self.memory.getProfilesList)
74 self.bridge.register("getEntityData", lambda jid_, keys, profile: self.memory.getEntityData(jid.JID(jid_), keys, profile)) 73 self.bridge.register("getEntityData", lambda jid_, keys, profile: self.memory.getEntityData(jid.JID(jid_), keys, profile))
75 self.bridge.register("getEntitiesData", self.memory._getEntitiesData) 74 self.bridge.register("getEntitiesData", self.memory._getEntitiesData)
76 self.bridge.register("asyncCreateProfile", self.memory.asyncCreateProfile) 75 self.bridge.register("asyncCreateProfile", self.memory.asyncCreateProfile)
77 self.bridge.register("asyncDeleteProfile", self.memory.asyncDeleteProfile) 76 self.bridge.register("asyncDeleteProfile", self.memory.asyncDeleteProfile)
78 self.bridge.register("asyncConnect", self.asyncConnect) 77 self.bridge.register("asyncConnect", self._asyncConnect)
79 self.bridge.register("disconnect", self.disconnect) 78 self.bridge.register("disconnect", self.disconnect)
80 self.bridge.register("getContacts", self.getContacts) 79 self.bridge.register("getContacts", self.getContacts)
81 self.bridge.register("getContactsFromGroup", self.getContactsFromGroup) 80 self.bridge.register("getContactsFromGroup", self.getContactsFromGroup)
82 self.bridge.register("getMainResource", self.memory._getMainResource) 81 self.bridge.register("getMainResource", self.memory._getMainResource)
83 self.bridge.register("getPresenceStatuses", self.memory._getPresenceStatuses) 82 self.bridge.register("getPresenceStatuses", self.memory._getPresenceStatuses)
135 """Method called after memory initialization is done""" 134 """Method called after memory initialization is done"""
136 log.info(_("Memory initialised")) 135 log.info(_("Memory initialised"))
137 self._import_plugins() 136 self._import_plugins()
138 ui_contact_list.ContactList(self) 137 ui_contact_list.ContactList(self)
139 ui_profile_manager.ProfileManager(self) 138 ui_profile_manager.ProfileManager(self)
140 self._initialised.callback(None) 139 self.initialised.callback(None)
141 log.info(_("Backend is ready")) 140 log.info(_("Backend is ready"))
142 141
143 def _import_plugins(self): 142 def _import_plugins(self):
144 """Import all plugins found in plugins directory""" 143 """Import all plugins found in plugins directory"""
145 import sat.plugins 144 import sat.plugins
242 continue 241 continue
243 else: 242 else:
244 defers_list.append(defer.maybeDeferred(unload)) 243 defers_list.append(defer.maybeDeferred(unload))
245 return defers_list 244 return defers_list
246 245
247 def asyncConnect(self, profile_key=C.PROF_KEY_NONE, password=''): 246 def _asyncConnect(self, profile_key, password=''):
247 profile = self.memory.getProfileName(profile_key)
248 return self.asyncConnect(profile, password)
249
250 def asyncConnect(self, profile, password=''):
248 """Retrieve the individual parameters, authenticate the profile 251 """Retrieve the individual parameters, authenticate the profile
249 and initiate the connection to the associated XMPP server. 252 and initiate the connection to the associated XMPP server.
250 253
251 @param password (string): the SàT profile password 254 @param password (string): the SàT profile password
252 @param profile_key: %(doc_profile_key)s 255 @param profile: %(doc_profile)s
253 @return: Deferred: 256 @return (D(bool)):
254 - a deferred boolean if the profile authentication succeed: 257 - True if the XMPP connection was already established
255 - True if the XMPP connection was already established 258 - False if the XMPP connection has been initiated (it may still fail)
256 - False if the XMPP connection has been initiated (it may still fail) 259 @raise exceptions.PasswordError: Profile password is wrong
257 - a Failure if the profile authentication failed 260 """
258 """ 261 def connectXMPPClient(dummy=None):
259 def backendInitialised(dummy): 262 if self.isConnected(profile):
260 profile = self.memory.getProfileName(profile_key) 263 log.info(_("already connected !"))
261 if not profile: 264 return True
262 log.error(_('Trying to connect a non-existant profile')) 265 d = self._connectXMPPClient(profile)
263 raise exceptions.ProfileUnknownError(profile_key) 266 return d.addCallback(lambda dummy: False)
264 267
265 def connectXMPPClient(dummy=None): 268 d = self.memory.startSession(password, profile)
266 if self.isConnected(profile): 269 d.addCallback(connectXMPPClient)
267 log.info(_("already connected !")) 270 return d
268 return True
269 self.memory.startProfileSession(profile)
270 d = self.memory.loadIndividualParams(profile)
271 d.addCallback(lambda dummy: self._connectXMPPClient(profile))
272 return d.addCallback(lambda dummy: False)
273
274 d = self._authenticateProfile(password, profile)
275 d.addCallback(connectXMPPClient)
276 return d
277
278 self._initialised.addErrback(lambda dummy: None) # allow successive attempts
279 return self._initialised.addCallback(backendInitialised)
280 271
281 @defer.inlineCallbacks 272 @defer.inlineCallbacks
282 def _connectXMPPClient(self, profile): 273 def _connectXMPPClient(self, profile):
283 """This part is called from asyncConnect when we have loaded individual parameters from memory""" 274 """This part is called from asyncConnect when we have loaded individual parameters from memory"""
284 try: 275 try:
345 log.error(u"error (plugin %(name)s): %(failure)s" % 336 log.error(u"error (plugin %(name)s): %(failure)s" %
346 {'name': plugin_conn_cb[idx][0], 'failure': result}) 337 {'name': plugin_conn_cb[idx][0], 'failure': result})
347 338
348 yield list_d.addCallback(logPluginResults) # FIXME: we should have a timeout here, and a way to know if a plugin freeze 339 yield list_d.addCallback(logPluginResults) # FIXME: we should have a timeout here, and a way to know if a plugin freeze
349 # TODO: mesure launch time of each plugin 340 # TODO: mesure launch time of each plugin
350
351 def _authenticateProfile(self, password, profile):
352 """Authenticate the profile.
353
354 @param password (string): the SàT profile password
355 @param profile: %(doc_profile)s
356 @return: Deferred: a deferred None in case of success, a failure otherwise.
357 """
358 session_data = self.memory.auth_sessions.profileGetUnique(profile)
359 if not password and session_data:
360 # XXX: this allows any frontend to connect with the empty password as soon as
361 # the profile has been authenticated at least once before. It is OK as long as
362 # submitting a form with empty passwords is restricted to local frontends.
363 return defer.succeed(None)
364
365 def check_result(result):
366 if not result:
367 log.warning(_(u'Authentication failure of profile %s') % profile)
368 raise exceptions.PasswordError(D_("The provided profile password doesn't match."))
369 if not session_data: # avoid to create two profile sessions when password if specified
370 return self.memory.newAuthSession(password, profile)
371
372 d = self.memory.asyncGetParamA(C.PROFILE_PASS_PATH[1], C.PROFILE_PASS_PATH[0], profile_key=profile)
373 d.addCallback(lambda sat_cipher: PasswordHasher.verify(password, sat_cipher))
374 return d.addCallback(check_result)
375 341
376 def disconnect(self, profile_key): 342 def disconnect(self, profile_key):
377 """disconnect from jabber server""" 343 """disconnect from jabber server"""
378 if not self.isConnected(profile_key): 344 if not self.isConnected(profile_key):
379 log.info(_("not connected !")) 345 log.info(_("not connected !"))