Mercurial > libervia-backend
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 (2015-11-14) |
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 !")) |