summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNavaL <ayoyo@thoughtworks.com>2016-10-26 15:55:29 +0200
committerNavaL <ayoyo@thoughtworks.com>2016-10-28 18:02:25 +0200
commit423ca8f9fb7636b336b24ba28bde5d61538bf5fc (patch)
tree6d8f95fafe4f08b4ca557d52bc45d310fa8c37af
parent3df56a4f3c411c3bde51c88e6e0bf34d5e582119 (diff)
authentication now returns Authentication
leap session creation is only done post-interstitial and that logic is also extracted into its own class #795
-rw-r--r--service/pixelated/application.py9
-rw-r--r--service/pixelated/authentication.py63
-rw-r--r--service/pixelated/config/leap.py61
-rw-r--r--service/pixelated/config/services.py1
-rw-r--r--service/pixelated/register.py2
-rw-r--r--service/pixelated/resources/login_resource.py61
-rw-r--r--service/pixelated/resources/root_resource.py11
-rw-r--r--service/test/integration/test_logout.py1
-rw-r--r--service/test/integration/test_multi_user_login.py1
-rw-r--r--service/test/support/integration/app_test_client.py31
-rw-r--r--service/test/support/integration/multi_user_client.py13
-rw-r--r--service/test/unit/config/test_leap.py70
-rw-r--r--service/test/unit/config/test_services.py58
-rw-r--r--service/test/unit/resources/test_login_resource.py109
-rw-r--r--service/test/unit/test_authentication.py50
15 files changed, 378 insertions, 163 deletions
diff --git a/service/pixelated/application.py b/service/pixelated/application.py
index d4d8c280..d393b656 100644
--- a/service/pixelated/application.py
+++ b/service/pixelated/application.py
@@ -152,17 +152,16 @@ def _setup_multi_user(args, root_resource, services_factory):
return protected_resource
-def set_up_protected_resources(root_resource, provider, services_factory, checker=None, banner=None):
- if not checker:
- checker = LeapPasswordChecker(provider)
+def set_up_protected_resources(root_resource, provider, services_factory, checker=None, banner=None, authenticator=None):
+ checker = checker or LeapPasswordChecker(provider)
session_checker = SessionChecker(services_factory)
realm = PixelatedRealm()
_portal = portal.Portal(realm, [checker, session_checker, AllowAnonymousAccess()])
- anonymous_resource = LoginResource(services_factory, _portal, disclaimer_banner=banner)
+ anonymous_resource = LoginResource(services_factory, provider, disclaimer_banner=banner, authenticator=authenticator)
protected_resource = PixelatedAuthSessionWrapper(_portal, root_resource, anonymous_resource, [])
- root_resource.initialize(_portal, disclaimer_banner=banner)
+ root_resource.initialize(provider, disclaimer_banner=banner, authenticator=authenticator)
return protected_resource
diff --git a/service/pixelated/authentication.py b/service/pixelated/authentication.py
index 02b43a1e..aa1d8b5d 100644
--- a/service/pixelated/authentication.py
+++ b/service/pixelated/authentication.py
@@ -1,9 +1,29 @@
+#
+# Copyright (c) 2014 ThoughtWorks, Inc.
+#
+# Pixelated is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Pixelated is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with Pixelated. If not, see <http://www.gnu.org/licenses/>.
import re
-from pixelated.config.leap import authenticate
+from collections import namedtuple
+
+from leap.bitmask.bonafide.provider import Api
+from leap.bitmask.bonafide.session import Session
from leap.bitmask.bonafide._srp import SRPAuthError
from twisted.cred.error import UnauthorizedLogin
-from twisted.internet.defer import inlineCallbacks
+from twisted.internet.defer import inlineCallbacks, returnValue
+
+Credentials = namedtuple('Credentials', 'username, password')
class Authenticator(object):
@@ -13,27 +33,48 @@ class Authenticator(object):
@inlineCallbacks
def authenticate(self, username, password):
- if self.validate_username(username):
- yield self._srp_auth(username, password)
- else:
- raise UnauthorizedLogin()
+ username = self.clean_username(username)
+ auth = yield self._srp_auth(username, password)
+ returnValue(auth)
@inlineCallbacks
def _srp_auth(self, username, password):
try:
- extracted_username = self.extract_username(username)
- auth = yield authenticate(self._leap_provider, extracted_username, password)
+ auth = yield self._bonafide_auth(username, password)
except SRPAuthError:
raise UnauthorizedLogin()
+ returnValue(auth)
+
+ @inlineCallbacks
+ def _bonafide_auth(self, user, password):
+ srp_provider = Api(self._leap_provider.api_uri)
+ credentials = Credentials(user, password)
+ srp_auth = Session(credentials, srp_provider, self._leap_provider.local_ca_crt)
+ yield srp_auth.authenticate()
+ returnValue(Authentication(user, srp_auth.token, srp_auth.uuid, 'session_id', {'is_admin': False}))
- def validate_username(self, username):
+ def clean_username(self, username):
if '@' not in username:
- return True
+ return username
extracted_username = self.extract_username(username)
- return self.username_with_domain(extracted_username) == username
+ if self.username_with_domain(extracted_username) == username:
+ return extracted_username
+ raise UnauthorizedLogin()
def extract_username(self, username):
return re.search('^([^@]+)@?.*$', username).group(1)
def username_with_domain(self, username):
return '%s@%s' % (username, self.domain)
+
+
+class Authentication(object):
+ def __init__(self, username, token, uuid, session_id, user_attributes):
+ self.username = username
+ self.token = token
+ self.uuid = uuid
+ self.session_id = session_id
+ self._user_attributes = user_attributes
+
+ def is_admin(self):
+ return self._user_attributes.get('is_admin', False)
diff --git a/service/pixelated/config/leap.py b/service/pixelated/config/leap.py
index b86b756e..e8814038 100644
--- a/service/pixelated/config/leap.py
+++ b/service/pixelated/config/leap.py
@@ -1,28 +1,21 @@
from __future__ import absolute_import
-from collections import namedtuple
-from twisted.cred.error import UnauthorizedLogin
-from twisted.internet import defer, threads
-from twisted.logger import Logger
-
-from leap.common.events import (server as events_server)
-from leap.soledad.common.errors import InvalidAuthTokenError
-from leap.bitmask.bonafide._srp import SRPAuthError
-from leap.bitmask.bonafide.session import Session
from leap.bitmask.bonafide.provider import Api
-
-from pixelated.config import credentials
-from pixelated.config import leap_config
+from leap.bitmask.bonafide.session import Session
+from leap.common.events import (server as events_server)
+from pixelated.adapter.welcome_mail import add_welcome_mail
+from pixelated.authentication import Authenticator, Credentials, Authentication
from pixelated.bitmask_libraries.certs import LeapCertificate
from pixelated.bitmask_libraries.provider import LeapProvider
+from pixelated.config import credentials
+from pixelated.config import leap_config
from pixelated.config.sessions import LeapSessionFactory
+from twisted.internet import defer
+from twisted.logger import Logger
log = Logger()
-Credentials = namedtuple('Credentials', 'username, password')
-
-
def initialize_leap_provider(provider_hostname, provider_cert, provider_fingerprint, leap_home):
LeapCertificate.set_cert_and_fingerprint(provider_cert,
provider_fingerprint)
@@ -64,10 +57,7 @@ def initialize_leap_single_user(leap_provider_cert,
provider = initialize_leap_provider(provider, leap_provider_cert, leap_provider_cert_fingerprint, leap_home)
- try:
- auth = yield authenticate(provider, username, password)
- except SRPAuthError:
- raise UnauthorizedLogin()
+ auth = Authenticator(provider).authenticate(username, password)
leap_session = yield create_leap_session(provider, username, password, auth)
@@ -84,16 +74,29 @@ def authenticate(provider, user, password):
def init_monkeypatches():
- import pixelated.extensions.requests_urllib3
+ pass
+
+
+class BootstrapUserServices(object):
+
+ def __init__(self, services_factory, provider):
+ self._services_factory = services_factory
+ self._provider = provider
+ @defer.inlineCallbacks
+ def setup(self, user_auth, password, language='pt-BR'):
+ leap_session = yield create_leap_session(self._provider, user_auth.username, password, user_auth)
+ yield self._setup_user_services(leap_session)
+ yield self._add_welcome_email(leap_session, language)
-class Authentication(object):
- def __init__(self, username, token, uuid, session_id, user_attributes):
- self.username = username
- self.token = token
- self.uuid = uuid
- self.session_id = session_id
- self._user_attributes = user_attributes
+ @defer.inlineCallbacks
+ def _setup_user_services(self, leap_session):
+ user_id = leap_session.user_auth.uuid
+ if not self._services_factory.has_session(user_id):
+ yield self._services_factory.create_services_from(leap_session)
+ self._services_factory.map_email(leap_session.user_auth.username, user_id)
- def is_admin(self):
- return self._user_attributes.get('is_admin', False)
+ @defer.inlineCallbacks
+ def _add_welcome_email(self, leap_session, language):
+ if leap_session.fresh_account:
+ yield add_welcome_mail(leap_session.mail_store, language)
diff --git a/service/pixelated/config/services.py b/service/pixelated/config/services.py
index 1f59c255..9e4de84e 100644
--- a/service/pixelated/config/services.py
+++ b/service/pixelated/config/services.py
@@ -39,7 +39,6 @@ class Services(object):
self.keymanager = self._leap_session.keymanager
self.draft_service = self._setup_draft_service(self._leap_session.mail_store)
self.feedback_service = self._setup_feedback_service()
-
yield self._index_all_mails()
def close(self):
diff --git a/service/pixelated/register.py b/service/pixelated/register.py
index 66ceea41..a91628e8 100644
--- a/service/pixelated/register.py
+++ b/service/pixelated/register.py
@@ -25,7 +25,7 @@ from pixelated.bitmask_libraries.certs import LeapCertificate
from pixelated.bitmask_libraries.provider import LeapProvider
from pixelated.config import arguments
from pixelated.config import logger as logger_config
-from pixelated.config.authentication import Authentication
+from pixelated.config.leap import Authentication
from pixelated.config.sessions import LeapSessionFactory
from twisted.internet.defer import inlineCallbacks
from twisted.logger import Logger
diff --git a/service/pixelated/resources/login_resource.py b/service/pixelated/resources/login_resource.py
index c0d9e874..d5555b90 100644
--- a/service/pixelated/resources/login_resource.py
+++ b/service/pixelated/resources/login_resource.py
@@ -17,20 +17,19 @@
import os
from xml.sax import SAXParseException
-from twisted.cred import credentials
+from pixelated.authentication import Authenticator
+from pixelated.config.leap import BootstrapUserServices
+from pixelated.resources import BaseResource, UnAuthorizedResource, IPixelatedSession
+from pixelated.resources import handle_error_deferred
from twisted.internet import defer
+from twisted.logger import Logger
from twisted.python.filepath import FilePath
from twisted.web import util
from twisted.web.http import UNAUTHORIZED, OK
-from twisted.web.resource import IResource, NoResource
+from twisted.web.resource import NoResource
from twisted.web.server import NOT_DONE_YET
from twisted.web.static import File
from twisted.web.template import Element, XMLFile, renderElement, renderer
-from twisted.logger import Logger
-
-from pixelated.resources import handle_error_deferred
-from pixelated.adapter.welcome_mail import add_welcome_mail
-from pixelated.resources import BaseResource, UnAuthorizedResource, IPixelatedSession
log = Logger()
@@ -53,8 +52,8 @@ def _get_static_folder():
def parse_accept_language(all_headers):
accepted_languages = ['pt-BR', 'en-US']
+ languages = all_headers.get('accept-language', '').split(';')[0]
for language in accepted_languages:
- languages = all_headers['accept-language'].split(';')[0]
if language in languages:
return language
return 'pt-BR'
@@ -105,12 +104,15 @@ class LoginWebSite(Element):
class LoginResource(BaseResource):
BASE_URL = 'login'
- def __init__(self, services_factory, portal=None, disclaimer_banner=None):
+ def __init__(self, services_factory, provider=None, disclaimer_banner=None, authenticator=None):
BaseResource.__init__(self, services_factory)
self._static_folder = _get_static_folder()
self._startup_folder = _get_startup_folder()
- self._portal = portal
self._disclaimer_banner = disclaimer_banner
+ self._provider = provider
+ self._authenticator = authenticator or Authenticator(provider)
+ self._bootstrap_user_services = BootstrapUserServices(services_factory, provider)
+
self.putChild('startup-assets', File(self._startup_folder))
with open(os.path.join(self._startup_folder, 'Interstitial.html')) as f:
self.interstitial = f.read()
@@ -136,11 +138,11 @@ class LoginResource(BaseResource):
if self.is_logged_in(request):
return util.redirectTo("/", request)
- def render_response(leap_session):
+ def render_response(user_auth):
request.setResponseCode(OK)
request.write(self.interstitial)
request.finish()
- self._setup_user_services(leap_session, request)
+ self._complete_bootstrap(user_auth, request)
def render_error(error):
log.info('Login Error for %s' % request.args['username'][0])
@@ -156,28 +158,21 @@ class LoginResource(BaseResource):
@defer.inlineCallbacks
def _handle_login(self, request):
- self.creds = self._get_creds_from(request)
- iface, leap_session, logout = yield self._portal.login(self.creds, None, IResource)
- defer.returnValue(leap_session)
-
- def _get_creds_from(self, request):
- username = request.args['username'][0].split('@')[0]
+ username = request.args['username'][0]
password = request.args['password'][0]
- return credentials.UsernamePassword(username, password)
+ user_auth = yield self._authenticator.authenticate(username, password)
+ defer.returnValue(user_auth)
- @defer.inlineCallbacks
- def _setup_user_services(self, leap_session, request):
- user_id = leap_session.user_auth.uuid
- if not self._services_factory.has_session(user_id):
- yield self._services_factory.create_services_from(leap_session)
- self._services_factory.map_email(self.creds.username, user_id)
-
- if leap_session.fresh_account:
- language = parse_accept_language(request.getAllHeaders())
- yield add_welcome_mail(leap_session.mail_store, language)
+ def _complete_bootstrap(self, user_auth, request):
+ def log_error(error):
+ log.error('Login error during %s services setup: %s' % (user_auth.username, error.getErrorMessage()))
- self._init_http_session(request, user_id)
+ def set_session_cookies(_):
+ session = IPixelatedSession(request.getSession())
+ session.user_uuid = user_auth.uuid
- def _init_http_session(self, request, user_id):
- session = IPixelatedSession(request.getSession())
- session.user_uuid = user_id
+ language = parse_accept_language(request.getAllHeaders())
+ password = request.args['password'][0]
+ d = self._bootstrap_user_services.setup(user_auth, password, language)
+ d.addCallback(set_session_cookies)
+ d.addErrback(log_error)
diff --git a/service/pixelated/resources/root_resource.py b/service/pixelated/resources/root_resource.py
index 504d156d..70d8a565 100644
--- a/service/pixelated/resources/root_resource.py
+++ b/service/pixelated/resources/root_resource.py
@@ -19,6 +19,7 @@ import os
from string import Template
from pixelated.resources import BaseResource, UnAuthorizedResource, UnavailableResource
+from pixelated.resources import IPixelatedSession
from pixelated.resources.attachments_resource import AttachmentsResource
from pixelated.resources.sandbox_resource import SandboxResource
from pixelated.resources.contacts_resource import ContactsResource
@@ -34,6 +35,10 @@ from pixelated.resources.keys_resource import KeysResource
from twisted.web.resource import NoResource
from twisted.web.static import File
+from twisted.logger import Logger
+
+log = Logger()
+
from pixelated.resources.users import UsersResource
CSRF_TOKEN_LENGTH = 32
@@ -82,13 +87,13 @@ class RootResource(BaseResource):
csrf_input = request.args.get('csrftoken', [None])[0] or json.loads(request.content.read()).get('csrftoken', [None])[0]
return csrf_input and csrf_input == xsrf_token
- def initialize(self, portal=None, disclaimer_banner=None):
+ def initialize(self, provider=None, disclaimer_banner=None, authenticator=None):
self._child_resources.add('sandbox', SandboxResource(self._static_folder))
self._child_resources.add('assets', File(self._static_folder))
self._child_resources.add('keys', KeysResource(self._services_factory))
self._child_resources.add(AttachmentsResource.BASE_URL, AttachmentsResource(self._services_factory))
self._child_resources.add('contacts', ContactsResource(self._services_factory))
- self._child_resources.add('features', FeaturesResource(portal))
+ self._child_resources.add('features', FeaturesResource(provider))
self._child_resources.add('tags', TagsResource(self._services_factory))
self._child_resources.add('mails', MailsResource(self._services_factory))
self._child_resources.add('mail', MailResource(self._services_factory))
@@ -96,7 +101,7 @@ class RootResource(BaseResource):
self._child_resources.add('user-settings', UserSettingsResource(self._services_factory))
self._child_resources.add('users', UsersResource(self._services_factory))
self._child_resources.add(LoginResource.BASE_URL,
- LoginResource(self._services_factory, portal, disclaimer_banner=disclaimer_banner))
+ LoginResource(self._services_factory, provider, disclaimer_banner=disclaimer_banner, authenticator=authenticator))
self._child_resources.add(LogoutResource.BASE_URL, LogoutResource(self._services_factory))
self._mode = MODE_RUNNING
diff --git a/service/test/integration/test_logout.py b/service/test/integration/test_logout.py
index e67fabab..c9d39d17 100644
--- a/service/test/integration/test_logout.py
+++ b/service/test/integration/test_logout.py
@@ -15,7 +15,6 @@
# along with Pixelated. If not, see <http://www.gnu.org/licenses/>.
import json
-from mock import patch
from mockito import verify
from twisted.internet import defer
diff --git a/service/test/integration/test_multi_user_login.py b/service/test/integration/test_multi_user_login.py
index c1500031..af2a81ac 100644
--- a/service/test/integration/test_multi_user_login.py
+++ b/service/test/integration/test_multi_user_login.py
@@ -35,6 +35,7 @@ class MultiUserLoginTest(MultiUserSoledadTestBase):
def test_logged_in_users_sees_resources(self):
response, login_request = yield self.app_test_client.login()
yield response
+
mail = load_mail_from_file('mbox00000000')
mail_id = yield self._create_mail_in_soledad(mail)
expected_mail_dict = {'body': u'Dignissimos ducimus veritatis. Est tenetur consequatur quia occaecati. Vel sit sit voluptas.\n\nEarum distinctio eos. Accusantium qui sint ut quia assumenda. Facere dignissimos inventore autem sit amet. Pariatur voluptatem sint est.\n\nUt recusandae praesentium aspernatur. Exercitationem amet placeat deserunt quae consequatur eum. Unde doloremque suscipit quia.\n\n', 'header': {u'date': u'Tue, 21 Apr 2015 08:43:27 +0000 (UTC)', u'to': [u'carmel@murazikortiz.name'], u'x-tw-pixelated-tags': u'nite, macro, trash', u'from': u'darby.senger@zemlak.biz', u'subject': u'Itaque consequatur repellendus provident sunt quia.'}, 'ident': mail_id, 'status': [], 'tags': [], 'textPlainBody': u'Dignissimos ducimus veritatis. Est tenetur consequatur quia occaecati. Vel sit sit voluptas.\n\nEarum distinctio eos. Accusantium qui sint ut quia assumenda. Facere dignissimos inventore autem sit amet. Pariatur voluptatem sint est.\n\nUt recusandae praesentium aspernatur. Exercitationem amet placeat deserunt quae consequatur eum. Unde doloremque suscipit quia.\n\n', 'mailbox': u'inbox', 'attachments': [], 'security_casing': {'imprints': [{'state': 'no_signature_information'}], 'locks': []}}
diff --git a/service/test/support/integration/app_test_client.py b/service/test/support/integration/app_test_client.py
index f982407e..93dfd812 100644
--- a/service/test/support/integration/app_test_client.py
+++ b/service/test/support/integration/app_test_client.py
@@ -52,6 +52,7 @@ from pixelated.resources.root_resource import RootResource
from test.support.integration.model import MailBuilder
from test.support.test_helper import request_mock
from test.support.integration.model import ResponseMail
+from pixelated.config.sessions import SessionCache
class AppTestAccount(object):
@@ -143,6 +144,27 @@ class StubSRPChecker(object):
return defer.fail()
+class StubAuthenticator(object):
+ def __init__(self, provider, credentials={}):
+ self._leap_provider = provider
+ self._credentials = credentials.copy()
+
+ def add_user(self, username, password):
+ self._credentials[username] = password
+
+ def _set_leap_session_cache(self, auth):
+ key = SessionCache.session_key(self._leap_provider, 'username')
+ SessionCache.remember_session(key, LeapSession(self._leap_provider, auth, None, None, None, None))
+
+ def authenticate(self, username, password):
+ if self._credentials[username] == password:
+ leap_auth = Authentication(username, uuid.uuid4(), uuid.uuid4(), uuid.uuid4(), {})
+ self._set_leap_session_cache(leap_auth)
+ return defer.succeed(leap_auth)
+ else:
+ return defer.fail()
+
+
class StubServicesFactory(ServicesFactory):
def __init__(self, accounts, mode):
@@ -196,13 +218,16 @@ class AppTestClient(object):
self.service_factory.add_session('someuserid', services)
self.resource = RootResource(self.service_factory)
- self.resource.initialize()
+ provider = mock()
+ self.resource.initialize(provider)
else:
self.service_factory = StubServicesFactory(self.accounts, mode)
provider = mock()
srp_checker = StubSRPChecker(provider)
- srp_checker.add_user('username', 'password')
- self.resource = set_up_protected_resources(RootResource(self.service_factory), provider, self.service_factory, checker=srp_checker)
+ bonafide_checker = StubAuthenticator(provider)
+ bonafide_checker.add_user('username', 'password')
+
+ self.resource = set_up_protected_resources(RootResource(self.service_factory), provider, self.service_factory, checker=srp_checker, authenticator=bonafide_checker)
@defer.inlineCallbacks
def create_user(self, account_name):
diff --git a/service/test/support/integration/multi_user_client.py b/service/test/support/integration/multi_user_client.py
index 0257214f..28316d5b 100644
--- a/service/test/support/integration/multi_user_client.py
+++ b/service/test/support/integration/multi_user_client.py
@@ -13,8 +13,10 @@
#
# You should have received a copy of the GNU Affero General Public License
# along with Pixelated. If not, see <http://www.gnu.org/licenses/>.
+from leap.bitmask.bonafide._srp import SRPAuthError
from mock import patch
from mockito import mock, when, any as ANY
+from pixelated.authentication import Authenticator
from pixelated.config.leap import Authentication
from twisted.internet import defer
@@ -49,9 +51,14 @@ class MultiUserClient(AppTestClient):
self.credentials_checker = StubSRPChecker(leap_provider)
self.resource = set_up_protected_resources(root_resource, leap_provider, self.service_factory, checker=self.credentials_checker)
- def login(self, username='username', password='password'):
- if(username == 'username' and password == 'password'):
+ def _mock_bonafide_auth(self, username, password):
+ if username == 'username' and password == 'password':
self.credentials_checker.add_user(username, password)
+ when(Authenticator)._bonafide_auth(username, password).thenReturn(self.user_auth)
+ else:
+ when(Authenticator)._bonafide_auth(username, password).thenRaise(SRPAuthError)
+
+ def login(self, username='username', password='password'):
session = Authentication(username, 'some_user_token', 'some_user_uuid', 'session_id', {'is_admin': False})
leap_session = self._test_account.leap_session
leap_session.user_auth = session
@@ -63,6 +70,8 @@ class MultiUserClient(AppTestClient):
self.services = self._test_account.services
self.user_auth = session
+ self._mock_bonafide_auth(username, password)
+
when(LeapSessionFactory).create(username, password, session).thenReturn(leap_session)
with patch('mockito.invocation.AnswerSelector', AnswerSelector):
when(leap_session).initial_sync().thenAnswer(lambda: defer.succeed(None))
diff --git a/service/test/unit/config/test_leap.py b/service/test/unit/config/test_leap.py
index ce533c7f..8f582dd8 100644
--- a/service/test/unit/config/test_leap.py
+++ b/service/test/unit/config/test_leap.py
@@ -1,8 +1,8 @@
from leap.soledad.common.errors import InvalidAuthTokenError
-from mock import MagicMock, patch
+from mock import MagicMock, patch, Mock
from twisted.trial import unittest
from twisted.internet import defer
-from pixelated.config.leap import create_leap_session
+from pixelated.config.leap import create_leap_session, BootstrapUserServices
from pixelated.config.sessions import LeapSessionFactory, SessionCache
@@ -35,3 +35,69 @@ class TestAuth(unittest.TestCase):
self.assertFalse(session.first_required_sync.called)
self.assertEqual(session, returned_session)
+
+
+class TestUserBootstrap(unittest.TestCase):
+
+ def setUp(self):
+ self._service_factory = Mock()
+ self._provider = Mock()
+ self._user_bootstrap = BootstrapUserServices(self._service_factory, self._provider)
+
+ username = 'ayoyo'
+ password = 'ayoyo_password'
+ self.username = username
+ self.password = password
+
+ user_auth = Mock()
+ user_auth.username = username
+ self.uuid = 'some_user_uuid'
+ user_auth.uuid = self.uuid
+ self.user_auth = user_auth
+
+ leap_session = Mock()
+ leap_session.user_auth = user_auth
+ leap_session.fresh_account = False
+ self.leap_session = leap_session
+
+ @patch('pixelated.config.leap.create_leap_session')
+ def test_should_create_leap_session(self, mock_create_leap_session):
+ mock_create_leap_session.return_value = self.leap_session
+ self._service_factory.has_session.return_value = False
+
+ self._user_bootstrap.setup(self.user_auth, self.password)
+
+ mock_create_leap_session.called_once_with(self._provider, self.username, self.password, self.user_auth)
+
+ @patch('pixelated.config.leap.create_leap_session')
+ def test_should_setup_user_services_and_map_email(self, mock_create_leap_session):
+ mock_create_leap_session.return_value = self.leap_session
+ self._service_factory.has_session.return_value = False
+
+ self._user_bootstrap.setup(self.user_auth, self.password)
+
+ self._service_factory.create_services_from.assert_called_once_with(self.leap_session)
+ self._service_factory.map_email.assert_called_once_with(self.username, self.uuid)
+
+ @patch('pixelated.config.leap.create_leap_session')
+ def test_should_not_user_services_if_there_is_already_a_session(self, mock_create_leap_session):
+ mock_create_leap_session.return_value = self.leap_session
+ self._service_factory.has_session.return_value = True
+
+ self._user_bootstrap.setup(self.user_auth, self.password)
+
+ self.assertFalse(self._service_factory.create_services_from.called)
+
+ @patch('pixelated.config.leap.add_welcome_mail')
+ @patch('pixelated.config.leap.create_leap_session')
+ def test_should_add_welcome_email_on_a_fresh_account(self, mock_create_leap_session, mock_add_welcome_email):
+ self.leap_session.fresh_account = True
+ mail_store = Mock()
+ self.leap_session.mail_store = mail_store
+ mock_create_leap_session.return_value = self.leap_session
+ self._service_factory.has_session.return_value = False
+ some_language = 'en-US'
+
+ self._user_bootstrap.setup(self.user_auth, self.password, '')
+
+ mock_add_welcome_email.called_once_with(mail_store, some_language)
diff --git a/service/test/unit/config/test_services.py b/service/test/unit/config/test_services.py
index 8277c919..ed221261 100644
--- a/service/test/unit/config/test_services.py
+++ b/service/test/unit/config/test_services.py
@@ -15,6 +15,7 @@
# along with Pixelated. If not, see <http://www.gnu.org/licenses/>.
import unittest
+from mock import Mock, ANY, patch
from mockito import mock, verify
from pixelated.config.services import Services, ServicesFactory
@@ -36,9 +37,58 @@ class ServicesTest(unittest.TestCase):
class ServicesFactoryTest(unittest.TestCase):
+ def setUp(self):
+ self.service_factory = ServicesFactory(Mock())
+
+ def test_users_has_no_default_sessions(self):
+ user_id = ANY
+ self.assertFalse(self.service_factory.has_session(user_id))
+
+ def test_add_user_sessions(self):
+ user_id = 'irrelevant'
+ some_service = Mock()
+
+ self.service_factory.add_session(user_id, some_service)
+
+ self.assertTrue(self.service_factory.has_session(user_id))
+ self.assertEqual(some_service, self.service_factory.services(user_id))
+
def test_online_sessions_counts_logged_in_users(self):
- service_factory = ServicesFactory(mock())
- service_factory.add_session('some_id1', mock())
- service_factory.add_session('some_id2', mock())
+ self.service_factory.add_session('some_id1', mock())
+ self.service_factory.add_session('some_id2', mock())
+
+ self.assertEqual(2, self.service_factory.online_sessions())
+
+ @patch('pixelated.config.services.Services.setup')
+ def test_create_services_from_leap_session_sets_up_services_and_add_a_user_session(self, mock_setup_services):
+ leap_session = Mock()
+ user_id = 'irrelevant'
+ leap_session.user_auth.uuid = user_id
+
+ self.service_factory.create_services_from(leap_session)
+
+ self.assertTrue(mock_setup_services.called)
+ self.assertTrue(self.service_factory.has_session(user_id))
+
+ def test_destroy_session_using_close_user_services_and_deletes_sessions(self):
+ user_id = 'irrelevant'
+ some_service = Mock()
+ self.service_factory.add_session(user_id, some_service)
+
+ self.service_factory.destroy_session(user_id)
+
+ self.assertFalse(self.service_factory.has_session(user_id))
+ self.assertTrue(some_service.close.called)
+
+ def test_sessions_can_be_destroyed_using_email_rather_than_uuid(self):
+ user_id = 'irrelevant'
+ username = 'haha'
+ email = '%s@ha.ha' % username
+ some_service = Mock()
+ self.service_factory.add_session(user_id, some_service)
+ self.service_factory.map_email(username, user_id)
+
+ self.service_factory.destroy_session(email, using_email=True)
- self.assertEqual(2, service_factory.online_sessions())
+ self.assertFalse(self.service_factory.has_session(user_id))
+ self.assertTrue(some_service.close.called)
diff --git a/service/test/unit/resources/test_login_resource.py b/service/test/unit/resources/test_login_resource.py
index 5843ef28..d3d7ba64 100644
--- a/service/test/unit/resources/test_login_resource.py
+++ b/service/test/unit/resources/test_login_resource.py
@@ -1,17 +1,15 @@
import os
-from leap.bitmask.bonafide._srp import SRPAuthError
from mock import patch
-from mockito import mock, when, any as ANY, verify, verifyZeroInteractions, verifyNoMoreInteractions
+from mockito import mock, when, any as ANY
+from twisted.cred.error import UnauthorizedLogin
+from twisted.internet import defer
from twisted.trial import unittest
-from twisted.web.resource import IResource
from twisted.web.test.requesthelper import DummyRequest
-from pixelated.config.sessions import LeapSession
from pixelated.resources.login_resource import LoginResource
from pixelated.resources.login_resource import parse_accept_language
from test.unit.resources import DummySite
-from test.support.mockito import AnswerSelector
class TestParseAcceptLanguage(unittest.TestCase):
@@ -163,9 +161,8 @@ class TestLoginResource(unittest.TestCase):
class TestLoginPOST(unittest.TestCase):
def setUp(self):
self.services_factory = mock()
- self.portal = mock()
self.provider = mock()
- self.resource = LoginResource(self.services_factory, self.portal)
+ self.resource = LoginResource(self.services_factory, self.provider)
self.web = DummySite(self.resource)
self.request = DummyRequest([''])
@@ -176,83 +173,85 @@ class TestLoginPOST(unittest.TestCase):
self.password = password
self.request.addArg('password', password)
self.request.method = 'POST'
- leap_session = mock(LeapSession)
user_auth = mock()
user_auth.uuid = 'some_user_uuid'
- leap_session.user_auth = user_auth
- config = mock()
- config.leap_home = 'some_folder'
- leap_session.config = config
- leap_session.fresh_account = False
- self.leap_session = leap_session
self.user_auth = user_auth
- def mock_user_has_services_setup(self):
- when(self.services_factory).has_session('some_user_uuid').thenReturn(True)
+ @patch('pixelated.authentication.Authenticator.authenticate')
+ @patch('twisted.web.util.redirectTo')
+ @patch('pixelated.resources.session.PixelatedSession.is_logged_in')
+ def test_should_redirect_to_home_if_user_if_already_logged_in(self, mock_logged_in, mock_redirect, mock_authenticate):
+ mock_logged_in.return_value = True
+ when(self.services_factory).has_session(ANY()).thenReturn(True)
+ mock_redirect.return_value = "mocked redirection"
- def test_login_responds_interstitial_and_add_corresponding_session_to_services_factory(self):
- irrelevant = None
- when(self.portal).login(ANY(), None, IResource).thenReturn((irrelevant, self.leap_session, irrelevant))
- with patch('mockito.invocation.AnswerSelector', AnswerSelector):
- when(self.services_factory).create_services_from(self.leap_session).thenAnswer(self.mock_user_has_services_setup)
+ d = self.web.get(self.request)
+
+ def assert_redirected_to_home(_):
+ mock_redirect.assert_called_once_with('/', self.request)
+ self.assertFalse(mock_authenticate.called)
+
+ d.addCallback(assert_redirected_to_home)
+ return d
+
+ @patch('pixelated.config.leap.BootstrapUserServices.setup')
+ @patch('pixelated.authentication.Authenticator.authenticate')
+ def test_should_return_form_back_with_error_message_when_login_fails(self, mock_authenticate,
+ mock_user_bootstrap_setup):
+ mock_authenticate.side_effect = UnauthorizedLogin()
d = self.web.get(self.request)
- def assert_login_setup_service_for_user(_):
- verify(self.portal).login(ANY(), None, IResource)
- verify(self.services_factory).create_services_from(self.leap_session)
- verify(self.services_factory).map_email('ayoyo', 'some_user_uuid')
- interstitial_js_in_template = '<script src="startup-assets/Interstitial.js"></script>'
- self.assertIn(interstitial_js_in_template, self.request.written[0])
- self.assertTrue(self.resource.is_logged_in(self.request))
+ def assert_error_response_and_user_services_not_setup(_):
+ mock_authenticate.assert_called_once_with(self.username, self.password)
+ self.assertEqual(401, self.request.responseCode)
+ written_response = ''.join(self.request.written)
+ self.assertIn('Invalid credentials', written_response)
+ self.assertFalse(mock_user_bootstrap_setup.called)
+ self.assertFalse(self.resource.get_session(self.request).is_logged_in())
- d.addCallback(assert_login_setup_service_for_user)
+ d.addCallback(assert_error_response_and_user_services_not_setup)
return d
- def test_login_does_not_reload_services_if_already_loaded(self):
- irrelevant = None
- when(self.portal).login(ANY(), None, IResource).thenReturn((irrelevant, self.leap_session, irrelevant))
- when(self.services_factory).has_session('some_user_uuid').thenReturn(True)
+ @patch('pixelated.config.leap.BootstrapUserServices.setup')
+ @patch('pixelated.authentication.Authenticator.authenticate')
+ def test_successful_login_responds_interstitial(self, mock_authenticate, mock_user_bootstrap_setup):
+ mock_authenticate.return_value = self.user_auth
d = self.web.get(self.request)
- def assert_login_setup_service_for_user(_):
- verify(self.portal).login(ANY(), None, IResource)
- verify(self.services_factory).has_session('some_user_uuid')
- verifyNoMoreInteractions(self.services_factory)
+ def assert_interstitial_in_response(_):
+ mock_authenticate.assert_called_once_with(self.username, self.password)
interstitial_js_in_template = '<script src="startup-assets/Interstitial.js"></script>'
self.assertIn(interstitial_js_in_template, self.request.written[0])
- self.assertTrue(self.resource.is_logged_in(self.request))
- d.addCallback(assert_login_setup_service_for_user)
+ d.addCallback(assert_interstitial_in_response)
return d
- def test_should_return_form_back_with_error_message_when_login_fails(self):
- when(self.portal).login(ANY(), None, IResource).thenRaise(Exception())
+ @patch('pixelated.config.leap.BootstrapUserServices.setup')
+ @patch('pixelated.authentication.Authenticator.authenticate')
+ def test_successful_login_runs_user_services_bootstrap_when_interstitial_loaded(self, mock_authenticate, mock_user_bootstrap_setup):
+ mock_authenticate.return_value = self.user_auth
+
d = self.web.get(self.request)
def assert_login_setup_service_for_user(_):
- verify(self.portal).login(ANY(), None, IResource)
- self.assertEqual(401, self.request.responseCode)
- written_response = ''.join(self.request.written)
- self.assertIn('Invalid credentials', written_response)
- self.assertFalse(self.resource.is_logged_in(self.request))
+ mock_user_bootstrap_setup.assert_called_once_with(self.user_auth, self.password, 'pt-BR')
d.addCallback(assert_login_setup_service_for_user)
return d
- @patch('twisted.web.util.redirectTo')
- @patch('pixelated.resources.session.PixelatedSession.is_logged_in')
- def test_should_not_process_login_if_already_logged_in(self, mock_logged_in, mock_redirect):
- mock_logged_in.return_value = True
- when(self.services_factory).has_session(ANY()).thenReturn(True)
- mock_redirect.return_value = "mocked redirection"
- when(self.portal).login(ANY(), None, IResource).thenRaise(Exception())
+ @patch('pixelated.config.leap.BootstrapUserServices.setup')
+ @patch('pixelated.authentication.Authenticator.authenticate')
+ def test_successful_adds_cookies_to_indicat_logged_in_status_when_services_are_loaded(self, mock_authenticate, mock_user_bootstrap_setup):
+ mock_authenticate.return_value = self.user_auth
+ irrelevant = None
+ mock_user_bootstrap_setup.return_value = defer.succeed(irrelevant)
+
d = self.web.get(self.request)
def assert_login_setup_service_for_user(_):
- verifyZeroInteractions(self.portal)
- mock_redirect.assert_called_once_with('/', self.request)
+ self.assertTrue(self.resource.get_session(self.request).is_logged_in())
d.addCallback(assert_login_setup_service_for_user)
return d
diff --git a/service/test/unit/test_authentication.py b/service/test/unit/test_authentication.py
index cebb6543..b729e64f 100644
--- a/service/test/unit/test_authentication.py
+++ b/service/test/unit/test_authentication.py
@@ -8,7 +8,7 @@ from mock import patch, Mock
from pixelated.authentication import Authenticator
from pixelated.bitmask_libraries.provider import LeapProvider
-
+from pixelated.config.leap import Authentication
PROVIDER_JSON = {
"api_uri": "https://api.domain.org:4430",
@@ -21,36 +21,60 @@ PROVIDER_JSON = {
class AuthenticatorTest(unittest.TestCase):
def setUp(self):
+ self._domain = 'domain.org'
with patch.object(LeapProvider, 'fetch_provider_json', return_value=PROVIDER_JSON):
- self._leap_provider = LeapProvider('domain.org')
+ self._leap_provider = LeapProvider(self._domain)
@inlineCallbacks
def test_bonafide_srp_exceptions_should_raise_unauthorized_login(self):
auth = Authenticator(self._leap_provider)
mock_bonafide_session = Mock()
mock_bonafide_session.authenticate = Mock(side_effect=SRPAuthError())
- with patch('pixelated.config.leap.Session', return_value=mock_bonafide_session):
+ with patch('pixelated.authentication.Session', return_value=mock_bonafide_session):
with self.assertRaises(UnauthorizedLogin):
yield auth.authenticate('username', 'password')
@inlineCallbacks
- def test_auth_username_with_domain_only_makes_bonafide_auth_with_username(self):
+ def test_domain_name_is_stripped_before_making_bonafide_srp_auth(self):
+ username_without_domain = 'username'
+ username_with_domain = '%s@%s' % (username_without_domain, self._domain)
auth = Authenticator(self._leap_provider)
- with patch('pixelated.authentication.authenticate') as mock_leap_authenticate:
- yield auth.authenticate('username@domain.org', 'password')
- mock_leap_authenticate.assert_called_once_with(self._leap_provider, 'username', 'password')
+ with patch.object(Authenticator, '_bonafide_auth') as mock_leap_authenticate:
+ yield auth.authenticate(username_with_domain, 'password')
+ mock_leap_authenticate.assert_called_once_with(username_without_domain, 'password')
+
+ @inlineCallbacks
+ def test_successful_bonafide_auth_should_return_the_user_authentication_object(self):
+ auth = Authenticator(self._leap_provider)
+ mock_bonafide_session = Mock()
+ mock_srp_auth = Mock()
+ mock_srp_auth.token = 'some_token'
+ mock_srp_auth.uuid = 'some_uuid'
+ mock_bonafide_session.authenticate = Mock(return_value=mock_srp_auth)
+ with patch('pixelated.authentication.Session', return_value=mock_srp_auth):
+ resulting_auth = yield auth.authenticate('username@domain.org', 'password')
+ self.assertIsInstance(resulting_auth, Authentication)
+ self.assertEquals('username', resulting_auth.username)
+ self.assertEquals('some_token', resulting_auth.token)
+ self.assertEquals('some_uuid', resulting_auth.uuid)
- def test_validate_username_accepts_username(self):
+ def test_username_without_domain_is_not_changed(self):
+ username_without_domain = 'username'
auth = Authenticator(self._leap_provider)
- self.assertTrue(auth.validate_username('username'))
+ self.assertEqual(username_without_domain, auth.clean_username(username_without_domain))
- def test_validate_username_accepts_email_address(self):
+ def test_username_with_domain_is_stripped(self):
+ username_without_domain = 'username'
+ username_with_domain = '%s@%s' % (username_without_domain, self._domain)
auth = Authenticator(self._leap_provider)
- self.assertTrue(auth.validate_username('username@domain.org'))
+ self.assertEqual(username_without_domain, auth.clean_username(username_with_domain))
- def test_validate_username_denies_other_domains(self):
+ def test_username_with_wrong_domain_raises_exception(self):
+ username_without_domain = 'username'
+ username_with_wrong_domain = '%s@%s' % (username_without_domain, 'wrongdomain.org')
auth = Authenticator(self._leap_provider)
- self.assertFalse(auth.validate_username('username@wrongdomain.org'))
+ with self.assertRaises(UnauthorizedLogin):
+ auth.clean_username(username_with_wrong_domain)
def test_username_with_domain(self):
auth = Authenticator(self._leap_provider)