summaryrefslogtreecommitdiff
path: root/service/pixelated
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 /service/pixelated
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
Diffstat (limited to 'service/pixelated')
-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
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