From 03855d3df78b4a71b880a068939e8708b0315be9 Mon Sep 17 00:00:00 2001 From: Folker Bernitt Date: Wed, 24 Feb 2016 11:06:08 +0100 Subject: Recreate session on soledad problems - Issue #615 - invalid token raised by soledad after timeout, if we see this, recreate the session on next login --- service/pixelated/bitmask_libraries/session.py | 21 +++++++++--- service/pixelated/config/leap.py | 31 ++++++++++++++---- service/pixelated/resources/auth.py | 1 - .../test/unit/bitmask_libraries/test_session.py | 12 ++++++- service/test/unit/config/test_leap.py | 38 ++++++++++++++++++++++ 5 files changed, 89 insertions(+), 14 deletions(-) create mode 100644 service/test/unit/config/test_leap.py diff --git a/service/pixelated/bitmask_libraries/session.py b/service/pixelated/bitmask_libraries/session.py index f28d9f59..5d8b13af 100644 --- a/service/pixelated/bitmask_libraries/session.py +++ b/service/pixelated/bitmask_libraries/session.py @@ -53,18 +53,19 @@ class LeapSession(object): self.fresh_account = False self.incoming_mail_fetcher = None self.account = None - self._has_been_synced = False + self._has_been_initially_synced = False self._sem_intial_sync = defer.DeferredLock() + self._is_closed = False register(events.KEYMANAGER_FINISHED_KEY_GENERATION, self._set_fresh_account, uid=self.account_email()) @defer.inlineCallbacks def initial_sync(self): yield self._sem_intial_sync.acquire() try: - if not self._has_been_synced: - yield self.sync() + yield self.sync() + if not self._has_been_initially_synced: yield self.after_first_sync() - self._has_been_synced = True + self._has_been_initially_synced = True finally: yield self._sem_intial_sync.release() defer.returnValue(self) @@ -94,12 +95,17 @@ class LeapSession(object): return self.provider.address_for(name) def close(self): + self._is_closed = True self.stop_background_jobs() unregister(events.KEYMANAGER_FINISHED_KEY_GENERATION, uid=self.account_email()) self.soledad.close() self.remove_from_cache() self._close_account() + @property + def is_closed(self): + return self._is_closed + def _close_account(self): if self.account: self.account.end_session() @@ -284,7 +290,12 @@ class SessionCache(object): @staticmethod def lookup_session(key): - return SessionCache.sessions.get(key, None) + session = SessionCache.sessions.get(key, None) + if session is not None and session.is_closed: + SessionCache.remove_session(key) + return None + else: + return session @staticmethod def remember_session(key, session): diff --git a/service/pixelated/config/leap.py b/service/pixelated/config/leap.py index a8666086..17a69406 100644 --- a/service/pixelated/config/leap.py +++ b/service/pixelated/config/leap.py @@ -1,14 +1,13 @@ from __future__ import absolute_import -from leap.common.events import (server as events_server, - register, catalog as events) +from leap.common.events import (server as events_server) +from leap.soledad.common.errors import InvalidAuthTokenError + from pixelated.config import credentials from pixelated.bitmask_libraries.config import LeapConfig from pixelated.bitmask_libraries.certs import LeapCertificate from pixelated.bitmask_libraries.provider import LeapProvider from pixelated.bitmask_libraries.session import LeapSessionFactory from twisted.internet import defer -import os -import logging import logging log = logging.getLogger(__name__) @@ -39,11 +38,29 @@ def initialize_leap_multi_user(provider_hostname, defer.returnValue((config, provider)) +def _create_session(provider, username, password, auth): + return LeapSessionFactory(provider).create(username, password, auth) + + +def _force_close_session(session): + try: + session.close() + except Exception, e: + log.error(e) + + @defer.inlineCallbacks def authenticate_user(provider, username, password, initial_sync=True, auth=None): - leap_session = LeapSessionFactory(provider).create(username, password, auth) - if initial_sync: - yield leap_session.initial_sync() + leap_session = _create_session(provider, username, password, auth) + try: + if initial_sync: + yield leap_session.initial_sync() + except InvalidAuthTokenError: + _force_close_session(leap_session) + + leap_session = _create_session(provider, username, password, auth) + if initial_sync: + yield leap_session.initial_sync() defer.returnValue(leap_session) diff --git a/service/pixelated/resources/auth.py b/service/pixelated/resources/auth.py index 02729a01..a6ab5396 100644 --- a/service/pixelated/resources/auth.py +++ b/service/pixelated/resources/auth.py @@ -41,7 +41,6 @@ log = logging.getLogger(__name__) class LeapPasswordChecker(object): credentialInterfaces = ( credentials.IUsernamePassword, - credentials.IUsernameHashedPassword ) def __init__(self, leap_provider): diff --git a/service/test/unit/bitmask_libraries/test_session.py b/service/test/unit/bitmask_libraries/test_session.py index a41cb805..aad2cac2 100644 --- a/service/test/unit/bitmask_libraries/test_session.py +++ b/service/test/unit/bitmask_libraries/test_session.py @@ -132,6 +132,16 @@ class SessionTest(AbstractLeapTest): self.assertTrue(session.fresh_account) + @patch('pixelated.bitmask_libraries.session.register') + def test_closed_session_not_reused(self, _): + session = self._create_session() + SessionCache.remember_session('somekey', session) + session._is_closed = True + + result = SessionCache.lookup_session('somekey') + + self.assertIsNone(result) + @patch('pixelated.bitmask_libraries.session.register') def test_session_does_not_set_status_fresh_for_unkown_emails(self, _): session = self._create_session() @@ -150,7 +160,7 @@ class SessionTest(AbstractLeapTest): with patch('pixelated.bitmask_libraries.session.reactor.callFromThread', new=_execute_func) as _: with patch.object(LeapSession, '_create_incoming_mail_fetcher', return_value=mailFetcherMock) as _: session = self._create_session() - session._has_been_synced = True + session._has_been_initially_synced = True yield session.initial_sync() self.assertFalse(mailFetcherMock.startService.called) diff --git a/service/test/unit/config/test_leap.py b/service/test/unit/config/test_leap.py new file mode 100644 index 00000000..6b34d717 --- /dev/null +++ b/service/test/unit/config/test_leap.py @@ -0,0 +1,38 @@ +from leap.soledad.common.errors import InvalidAuthTokenError +from mock import MagicMock, patch +from twisted.trial import unittest +from twisted.internet import defer +from pixelated.config.leap import authenticate_user + + +class TestAuth(unittest.TestCase): + + @patch('pixelated.config.leap.LeapSessionFactory') + @defer.inlineCallbacks + def test_authenticate_user_calls_initinal_sync(self, session_factory__ctor_mock): + session_factory_mock = session_factory__ctor_mock.return_value + provider_mock = MagicMock() + auth_mock = MagicMock() + session = MagicMock() + + session_factory_mock.create.return_value = session + + yield authenticate_user(provider_mock, 'username', 'password', auth=auth_mock) + + session.initial_sync.assert_called_with() + + @patch('pixelated.config.leap.LeapSessionFactory') + @defer.inlineCallbacks + def test_authenticate_user_calls_initial_sync_a_second_time_if_invalid_auth_exception_is_raised(self, session_factory__ctor_mock): + session_factory_mock = session_factory__ctor_mock.return_value + provider_mock = MagicMock() + auth_mock = MagicMock() + session = MagicMock() + + session.initial_sync.side_effect = [InvalidAuthTokenError, defer.succeed(None)] + session_factory_mock.create.return_value = session + + yield authenticate_user(provider_mock, 'username', 'password', auth=auth_mock) + + session.close.assert_called_with() + self.assertEqual(2, session.initial_sync.call_count) -- cgit v1.2.3