From 5728511e094aff3978c98fc1d616dce4b60a5a23 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Fri, 4 Sep 2015 00:38:59 -0400 Subject: factor out http utils, add decorator for authenticated methods --- src/leap/bonafide/_decorators.py | 33 ++++++++++++ src/leap/bonafide/_http.py | 90 +++++++++++++++++++++++++++++++ src/leap/bonafide/session.py | 113 +++++++++++---------------------------- src/leap/bonafide/srp_auth.py | 23 ++++---- 4 files changed, 164 insertions(+), 95 deletions(-) create mode 100644 src/leap/bonafide/_decorators.py create mode 100644 src/leap/bonafide/_http.py diff --git a/src/leap/bonafide/_decorators.py b/src/leap/bonafide/_decorators.py new file mode 100644 index 0000000..cfd6ec0 --- /dev/null +++ b/src/leap/bonafide/_decorators.py @@ -0,0 +1,33 @@ +# -*- coding: utf-8 -*- +# _decorators.py +# Copyright (C) 2015 LEAP +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +""" +Decorators used in bonafide. +""" + + +def needs_authentication(func): + """ + Decorate a method so that it will not be called if the instance + attribute `is_authenticated` does not evaluate to True. + """ + def decorated(*args, **kwargs): + instance = args[0] + allowed = getattr(instance, 'is_authenticated') + if not allowed: + raise RuntimeError('This method requires authentication') + return func(*args, **kwargs) + return decorated diff --git a/src/leap/bonafide/_http.py b/src/leap/bonafide/_http.py new file mode 100644 index 0000000..6510e84 --- /dev/null +++ b/src/leap/bonafide/_http.py @@ -0,0 +1,90 @@ +# -*- coding: utf-8 -*- +# _http.py +# Copyright (C) 2015 LEAP +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +""" +twisted.web utils for bonafide. +""" +import cookielib +import urllib + +from twisted.internet import defer, protocol, reactor +from twisted.internet.ssl import Certificate +from twisted.python.filepath import FilePath +from twisted.web.client import Agent, CookieAgent +from twisted.web.client import BrowserLikePolicyForHTTPS +from twisted.web.http_headers import Headers +from twisted.web.iweb import IBodyProducer +from zope.interface import implements + + +def cookieAgentFactory(verify_path, connectTimeout=30): + customPolicy = BrowserLikePolicyForHTTPS( + Certificate.loadPEM(FilePath(verify_path).getContent())) + agent = Agent(reactor, customPolicy, connectTimeout=connectTimeout) + cookiejar = cookielib.CookieJar() + return CookieAgent(agent, cookiejar) + + +def httpRequest(agent, url, values={}, headers={}, method='POST'): + data = '' + if values: + data = urllib.urlencode(values) + headers['Content-Type'] = ['application/x-www-form-urlencoded'] + + def handle_response(response): + if response.code == 204: + d = defer.succeed('') + else: + class SimpleReceiver(protocol.Protocol): + def __init__(s, d): + s.buf = '' + s.d = d + + def dataReceived(s, data): + s.buf += data + + def connectionLost(s, reason): + # TODO: test if reason is twisted.web.client.ResponseDone, + # if not, do an errback + s.d.callback(s.buf) + d = defer.Deferred() + response.deliverBody(SimpleReceiver(d)) + return d + + d = agent.request(method, url, Headers(headers), + StringProducer(data) if data else None) + d.addCallback(handle_response) + return d + + +class StringProducer(object): + + implements(IBodyProducer) + + def __init__(self, body): + self.body = body + self.length = len(body) + + def startProducing(self, consumer): + consumer.write(self.body) + return defer.succeed(None) + + def pauseProducing(self): + pass + + def stopProducing(self): + pass diff --git a/src/leap/bonafide/session.py b/src/leap/bonafide/session.py index a113d0a..85e49e0 100644 --- a/src/leap/bonafide/session.py +++ b/src/leap/bonafide/session.py @@ -17,45 +17,35 @@ """ LEAP Session management. """ -import cookielib -import urllib - -from twisted.internet import defer, reactor, protocol -from twisted.internet.ssl import Certificate -from twisted.web.client import Agent, CookieAgent, HTTPConnectionPool -from twisted.web.client import BrowserLikePolicyForHTTPS -from twisted.web.http_headers import Headers -from twisted.web.iweb import IBodyProducer +from twisted.internet import defer, reactor from twisted.python import log -from twisted.python.filepath import FilePath -from twisted.python import log -from zope.interface import implements from leap.bonafide import srp_auth +from leap.bonafide._decorators import needs_authentication +from leap.bonafide._http import httpRequest, cookieAgentFactory class LeapSession(object): def __init__(self, credentials, api, provider_cert): - # TODO check if an anonymous credentials is passed - # TODO -- we could decorate some methods so that they - # complain if we're not authenticated. + # TODO check if an anonymous credentials is passed. + # TODO move provider_cert to api object. + # On creation, it should be able to retrieve all the info it needs + # (calling bootstrap). + # TODO could get a "provider" object instead. + # this provider can have an api attribute, + # and a "autoconfig" attribute passed on initialization. + # TODO get a file-descriptor for password if not in credentials self.username = credentials.username self.password = credentials.password - self._api = api - customPolicy = BrowserLikePolicyForHTTPS( - Certificate.loadPEM(FilePath(provider_cert).getContent())) - - # BUG XXX See https://twistedmatrix.com/trac/ticket/7843 - pool = HTTPConnectionPool(reactor, persistent=False) - agent = Agent(reactor, customPolicy, connectTimeout=30, pool=pool) - cookiejar = cookielib.CookieJar() - self._agent = CookieAgent(agent, cookiejar) + self._agent = cookieAgentFactory(provider_cert) self._srp_auth = srp_auth.SRPAuthMechanism() self._srp_user = None + self._token = None + self._uuid = None @defer.inlineCallbacks def authenticate(self): @@ -66,6 +56,7 @@ class LeapSession(object): uri, method = self._api.get_uri_and_method('handshake') log.msg("%s to %s" % (method, uri)) params = self._srp_auth.get_handshake_params(self.username, A) + handshake = yield httpRequest(self._agent, uri, values=params, method=method) @@ -74,72 +65,28 @@ class LeapSession(object): 'authenticate', login=self.username) log.msg("%s to %s" % (method, uri)) params = self._srp_auth.get_authentication_params(M, A) + auth = yield httpRequest(self._agent, uri, values=params, method=method) uuid, token, M2 = self._srp_auth.process_authentication(auth) self._srp_auth.verify_authentication(srpuser, M2) - defer.succeed('ok') - # XXX get_session_id?? - # XXX return defer.succeed + self._uuid = uuid + self._token = token + defer.returnValue('[OK] Credentias Authenticated through SRP') + + @needs_authentication + def logout(self): + print "Should logout..." + + @property def is_authenticated(self): if not self._srp_user: return False return self._srp_user.authenticated() -def httpRequest(agent, url, values={}, headers={}, method='POST'): - headers['Content-Type'] = ['application/x-www-form-urlencoded'] - data = urllib.urlencode(values) - d = agent.request(method, url, Headers(headers), - StringProducer(data) if data else None) - - def handle_response(response): - if response.code == 204: - d = defer.succeed('') - else: - class SimpleReceiver(protocol.Protocol): - def __init__(s, d): - s.buf = '' - s.d = d - - def dataReceived(s, data): - print "----> handle response: GOT DATA" - s.buf += data - - def connectionLost(s, reason): - print "CONNECTION LOST ---", reason - # TODO: test if reason is twisted.web.client.ResponseDone, - # if not, do an errback - s.d.callback(s.buf) - - d = defer.Deferred() - response.deliverBody(SimpleReceiver(d)) - return d - - d.addCallback(handle_response) - return d - - -class StringProducer(object): - - implements(IBodyProducer) - - def __init__(self, body): - self.body = body - self.length = len(body) - - def startProducing(self, consumer): - consumer.write(self.body) - return defer.succeed(None) - - def pauseProducing(self): - pass - - def stopProducing(self): - pass - if __name__ == "__main__": from leap.bonafide import provider from twisted.cred.credentials import UsernamePassword @@ -151,14 +98,18 @@ if __name__ == "__main__": session = LeapSession(credentials, api, cdev_pem) def print_result(result): - print "Auth OK" - print "result" + print result + return result def cbShutDown(ignored): reactor.stop() + def auth_eb(failure): + print "[ERROR!]", failure.getErrorMessage() + d = session.authenticate() d.addCallback(print_result) - d.addErrback(lambda f: log.err(f)) + d.addErrback(auth_eb) + d.addCallback(lambda _: session.logout()) d.addBoth(cbShutDown) reactor.run() diff --git a/src/leap/bonafide/srp_auth.py b/src/leap/bonafide/srp_auth.py index ac2cd67..d48214f 100644 --- a/src/leap/bonafide/srp_auth.py +++ b/src/leap/bonafide/srp_auth.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# srp.py -# Copyright (C) 2014 LEAP +# srp_auth.py +# Copyright (C) 2015 LEAP # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -20,12 +20,10 @@ SRP Authentication. """ import binascii -import logging import json import srp -logger = logging.getLogger(__name__) class SRPAuthMechanism(object): @@ -54,11 +52,12 @@ class SRPAuthMechanism(object): return M def get_authentication_params(self, M, A): - # I think A is not used in the server side + # It looks A is not used server side return {'client_auth': binascii.hexlify(M), 'A': binascii.hexlify(A)} def process_authentication(self, authentication_response): auth = json.loads(authentication_response) + self._check_for_errors(auth) uuid = auth.get('id', None) token = auth.get('token', None) M2 = auth.get('M2', None) @@ -70,9 +69,9 @@ class SRPAuthMechanism(object): srp_user.verify_session(unhex_M2) assert srp_user.authenticated() - def _check_for_errors(self, challenge): - if 'errors' in challenge: - msg = challenge['errors']['base'] + def _check_for_errors(self, response): + if 'errors' in response: + msg = response['errors']['base'] raise SRPAuthError(msg) def _unhex_salt_B(self, salt, B): @@ -89,14 +88,9 @@ class SRPAuthMechanism(object): def _check_auth_params(self, uuid, token, M2): if not all((uuid, token, M2)): - msg = '%r' % (M2, uuid, token,) + msg = '%s' % str((M2, uuid, token)) raise SRPAuthBadDataFromServer(msg) - #XXX move to session ----------------------- - def get_session_id(self, cookies): - return cookies.get('_session_id', None) - #XXX move to session ----------------------- - def _safe_unhexlify(val): return binascii.unhexlify(val) \ @@ -116,5 +110,6 @@ class SRPAuthNoSalt(SRPAuthError): class SRPAuthNoB(SRPAuthError): message = 'The server didn\'t send the B parameter' + class SRPAuthBadDataFromServer(SRPAuthError): pass -- cgit v1.2.3