core: profile session starting and connection are now separated. Moved profile session starting/authentication to memory module

This commit is contained in:
Goffi 2015-11-14 19:18:10 +01:00
commit e0a28858a8
2 changed files with 99 additions and 63 deletions

View file

@ -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 @@ from sat.core.log import getLogger
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 @@ class SAT(service.Service):
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 @@ class SAT(service.Service):
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 @@ class SAT(service.Service):
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 @@ class SAT(service.Service):
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 @@ class SAT(service.Service):
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 @@ class SAT(service.Service):
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):

View file

@ -37,6 +37,7 @@ from sat.memory.persistent import PersistentDict
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 @@ class Memory(object):
## 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.