core: profile session starting and connection are now separated. Moved profile session starting/authentication to memory module
This commit is contained in:
parent
736dbfa1b4
commit
e0a28858a8
2 changed files with 99 additions and 63 deletions
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
Loading…
Reference in a new issue