diff options
author | NavaL <ayoyo@thoughtworks.com> | 2016-10-26 15:55:29 +0200 |
---|---|---|
committer | NavaL <ayoyo@thoughtworks.com> | 2016-10-28 18:02:25 +0200 |
commit | 423ca8f9fb7636b336b24ba28bde5d61538bf5fc (patch) | |
tree | 6d8f95fafe4f08b4ca557d52bc45d310fa8c37af /service/pixelated | |
parent | 3df56a4f3c411c3bde51c88e6e0bf34d5e582119 (diff) |
authentication now returns Authentication
leap session creation is only done post-interstitial
and that logic is also extracted into its own class
#795
Diffstat (limited to 'service/pixelated')
-rw-r--r-- | service/pixelated/application.py | 9 | ||||
-rw-r--r-- | service/pixelated/authentication.py | 63 | ||||
-rw-r--r-- | service/pixelated/config/leap.py | 61 | ||||
-rw-r--r-- | service/pixelated/config/services.py | 1 | ||||
-rw-r--r-- | service/pixelated/register.py | 2 | ||||
-rw-r--r-- | service/pixelated/resources/login_resource.py | 61 | ||||
-rw-r--r-- | service/pixelated/resources/root_resource.py | 11 |
7 files changed, 125 insertions, 83 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 |