changeset 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 ab54af2a9ab2
children d6d655238a93
files src/core/sat_main.py src/memory/memory.py
diffstat 2 files changed, 98 insertions(+), 62 deletions(-) [+]
line wrap: on
line diff
--- a/src/core/sat_main.py	Sat Nov 14 19:18:10 2015 +0100
+++ b/src/core/sat_main.py	Sat Nov 14 19:18:10 2015 +0100
@@ -18,7 +18,7 @@
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 import sat
-from sat.core.i18n import _, D_, languageSwitch
+from sat.core.i18n import _, languageSwitch
 from twisted.application import service
 from twisted.internet import defer
 from twisted.words.protocols.jabber import jid
@@ -32,7 +32,6 @@
 log = getLogger(__name__)
 from sat.core.constants import Const as C
 from sat.memory.memory import Memory
-from sat.memory.crypto import PasswordHasher
 from sat.tools import trigger
 from sat.tools import utils
 from sat.stdui import ui_contact_list, ui_profile_manager
@@ -54,7 +53,7 @@
         self._cb_map = {}  # map from callback_id to callbacks
         self._menus = OrderedDict()  # dynamic menus. key: callback_id, value: menu data (dictionnary)
         self.__private_data = {}  # used for internal callbacks (key = id) FIXME: to be removed
-        self._initialised = defer.Deferred()
+        self.initialised = defer.Deferred()
         self.profiles = {}
         self.plugins = {}
 
@@ -66,7 +65,7 @@
         except exceptions.BridgeInitError:
             log.error(u"Bridge can't be initialised, can't start SàT core")
             sys.exit(1)
-        self.bridge.register("getReady", lambda: self._initialised)
+        self.bridge.register("getReady", lambda: self.initialised)
         self.bridge.register("getVersion", lambda: self.full_version)
         self.bridge.register("getFeatures", self.getFeatures)
         self.bridge.register("getProfileName", self.memory.getProfileName)
@@ -75,7 +74,7 @@
         self.bridge.register("getEntitiesData", self.memory._getEntitiesData)
         self.bridge.register("asyncCreateProfile", self.memory.asyncCreateProfile)
         self.bridge.register("asyncDeleteProfile", self.memory.asyncDeleteProfile)
-        self.bridge.register("asyncConnect", self.asyncConnect)
+        self.bridge.register("asyncConnect", self._asyncConnect)
         self.bridge.register("disconnect", self.disconnect)
         self.bridge.register("getContacts", self.getContacts)
         self.bridge.register("getContactsFromGroup", self.getContactsFromGroup)
@@ -137,7 +136,7 @@
         self._import_plugins()
         ui_contact_list.ContactList(self)
         ui_profile_manager.ProfileManager(self)
-        self._initialised.callback(None)
+        self.initialised.callback(None)
         log.info(_("Backend is ready"))
 
     def _import_plugins(self):
@@ -244,39 +243,31 @@
                 defers_list.append(defer.maybeDeferred(unload))
         return defers_list
 
-    def asyncConnect(self, profile_key=C.PROF_KEY_NONE, password=''):
+    def _asyncConnect(self, profile_key, password=''):
+        profile = self.memory.getProfileName(profile_key)
+        return self.asyncConnect(profile, password)
+
+    def asyncConnect(self, profile, password=''):
         """Retrieve the individual parameters, authenticate the profile
         and initiate the connection to the associated XMPP server.
 
         @param password (string): the SàT profile password
-        @param profile_key: %(doc_profile_key)s
-        @return: Deferred:
-            - a deferred boolean if the profile authentication succeed:
-                - True if the XMPP connection was already established
-                - False if the XMPP connection has been initiated (it may still fail)
-            - a Failure if the profile authentication failed
+        @param profile: %(doc_profile)s
+        @return (D(bool)):
+            - True if the XMPP connection was already established
+            - False if the XMPP connection has been initiated (it may still fail)
+        @raise exceptions.PasswordError: Profile password is wrong
         """
-        def backendInitialised(dummy):
-            profile = self.memory.getProfileName(profile_key)
-            if not profile:
-                log.error(_('Trying to connect a non-existant profile'))
-                raise exceptions.ProfileUnknownError(profile_key)
+        def connectXMPPClient(dummy=None):
+            if self.isConnected(profile):
+                log.info(_("already connected !"))
+                return True
+            d = self._connectXMPPClient(profile)
+            return d.addCallback(lambda dummy: False)
 
-            def connectXMPPClient(dummy=None):
-                if self.isConnected(profile):
-                    log.info(_("already connected !"))
-                    return True
-                self.memory.startProfileSession(profile)
-                d = self.memory.loadIndividualParams(profile)
-                d.addCallback(lambda dummy: self._connectXMPPClient(profile))
-                return d.addCallback(lambda dummy: False)
-
-            d = self._authenticateProfile(password, profile)
-            d.addCallback(connectXMPPClient)
-            return d
-
-        self._initialised.addErrback(lambda dummy: None)  # allow successive attempts
-        return self._initialised.addCallback(backendInitialised)
+        d = self.memory.startSession(password, profile)
+        d.addCallback(connectXMPPClient)
+        return d
 
     @defer.inlineCallbacks
     def _connectXMPPClient(self, profile):
@@ -348,31 +339,6 @@
         yield list_d.addCallback(logPluginResults) # FIXME: we should have a timeout here, and a way to know if a plugin freeze
         # TODO: mesure launch time of each plugin
 
-    def _authenticateProfile(self, password, profile):
-        """Authenticate the profile.
-
-        @param password (string): the SàT profile password
-        @param profile: %(doc_profile)s
-        @return: Deferred: a deferred None in case of success, a failure otherwise.
-        """
-        session_data = self.memory.auth_sessions.profileGetUnique(profile)
-        if not password and session_data:
-            # XXX: this allows any frontend to connect with the empty password as soon as
-            # the profile has been authenticated at least once before. It is OK as long as
-            # submitting a form with empty passwords is restricted to local frontends.
-            return defer.succeed(None)
-
-        def check_result(result):
-            if not result:
-                log.warning(_(u'Authentication failure of profile %s') % profile)
-                raise exceptions.PasswordError(D_("The provided profile password doesn't match."))
-            if not session_data:  # avoid to create two profile sessions when password if specified
-                return self.memory.newAuthSession(password, profile)
-
-        d = self.memory.asyncGetParamA(C.PROFILE_PASS_PATH[1], C.PROFILE_PASS_PATH[0], profile_key=profile)
-        d.addCallback(lambda sat_cipher: PasswordHasher.verify(password, sat_cipher))
-        return d.addCallback(check_result)
-
     def disconnect(self, profile_key):
         """disconnect from jabber server"""
         if not self.isConnected(profile_key):
--- a/src/memory/memory.py	Sat Nov 14 19:18:10 2015 +0100
+++ b/src/memory/memory.py	Sat Nov 14 19:18:10 2015 +0100
@@ -37,6 +37,7 @@
 from sat.memory.params import Params
 from sat.memory.disco import Discovery
 from sat.memory.crypto import BlockCipher
+from sat.memory.crypto import PasswordHasher
 from sat.tools import config as tools_config
 
 
@@ -316,11 +317,80 @@
 
     ## Profiles/Sessions management ##
 
-    def startProfileSession(self, profile):
+    def _startSession(self, password, profile_key):
+        profile = self.getProfileName(profile_key)
+        return self.startSession(password, profile)
+
+    def startSession(self, password, profile):
         """"Iniatialise session for a profile
-        @param profile: %(doc_profile)s"""
-        log.info(_("[%s] Profile session started" % profile))
-        self._entities_cache[profile] = {}
+
+        @param password(unicode): profile session password
+            or empty string is no password is set
+        @param profile: %(doc_profile)s
+        """
+        def createSession(dummy):
+            """Called once params are loaded."""
+            self._entities_cache[profile] = {}
+            log.info(u"[{}] Profile session started".format(profile))
+            return False
+
+        def backendInitialised(dummy):
+            def doStartSession(dummy=None):
+                if self.isSessionStarted(profile):
+                    log.info("Session already started!")
+                    return True
+                try:
+                    # if there is a value at this point in self._entities_cache,
+                    # it is the loadIndividualParams Deferred, the session is starting
+                    session_d = self._entities_cache[profile]
+                except KeyError:
+                    # else we do request the params
+                    session_d = self._entities_cache[profile] = self.loadIndividualParams(profile)
+                    session_d.addCallback(createSession)
+                finally:
+                    return session_d
+
+            auth_d = self.profileAuthenticate(password, profile)
+            auth_d.addCallback(doStartSession)
+            return auth_d
+
+        return self.host.initialised.addCallback(backendInitialised)
+
+    def _isSessionStarted(self, profile_key):
+        return self.isSessionStarted(self.getProfileName(profile_key))
+
+    def isSessionStarted(self, profile):
+        try:
+            # XXX: if the value in self._entities_cache is a Deferred,
+            #      the session is starting but not started yet
+            return not isinstance(self._entities_cache[profile], defer.Deferred)
+        except KeyError:
+            return False
+
+    def profileAuthenticate(self, password, profile):
+        """Authenticate the profile.
+
+        @param password (unicode): the SàT profile password
+        @param profile: %(doc_profile)s
+        @return (D): a deferred None in case of success, a failure otherwise.
+        """
+        session_data = self.auth_sessions.profileGetUnique(profile)
+        if not password and session_data:
+            # XXX: this allows any frontend to connect with the empty password as soon as
+            # the profile has been authenticated at least once before. It is OK as long as
+            # submitting a form with empty passwords is restricted to local frontends.
+            return defer.succeed(None)
+
+        def check_result(result):
+            if not result:
+                log.warning(u'Authentication failure of profile {}'.format(profile))
+                raise exceptions.PasswordError(u"The provided profile password doesn't match.")
+            if not session_data:  # avoid to create two profile sessions when password if specified
+                return self.newAuthSession(password, profile)
+
+        d = self.asyncGetParamA(C.PROFILE_PASS_PATH[1], C.PROFILE_PASS_PATH[0], profile_key=profile)
+        d.addCallback(lambda sat_cipher: PasswordHasher.verify(password, sat_cipher))
+        return d.addCallback(check_result)
 
     def newAuthSession(self, key, profile):
         """Start a new session for the authenticated profile.