summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKali Kaneko <kali@leap.se>2015-09-04 00:38:59 -0400
committerKali Kaneko <kali@leap.se>2015-09-04 00:42:22 -0400
commit47a1c704321f30c09007f7c60162c6c12c146b93 (patch)
tree68af00003ae055b8f6b909e51acf49b75a2111a9
parent32116997fb057e3c19edbd9a05022c72342a172f (diff)
factor out http utils, add decorator for authenticated methods
-rw-r--r--bonafide/src/leap/bonafide/_decorators.py33
-rw-r--r--bonafide/src/leap/bonafide/_http.py90
-rw-r--r--bonafide/src/leap/bonafide/session.py113
-rw-r--r--bonafide/src/leap/bonafide/srp_auth.py23
4 files changed, 164 insertions, 95 deletions
diff --git a/bonafide/src/leap/bonafide/_decorators.py b/bonafide/src/leap/bonafide/_decorators.py
new file mode 100644
index 0000000..cfd6ec0
--- /dev/null
+++ b/bonafide/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 <http://www.gnu.org/licenses/>.
+"""
+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/bonafide/src/leap/bonafide/_http.py b/bonafide/src/leap/bonafide/_http.py
new file mode 100644
index 0000000..6510e84
--- /dev/null
+++ b/bonafide/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 <http://www.gnu.org/licenses/>.
+
+"""
+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/bonafide/src/leap/bonafide/session.py b/bonafide/src/leap/bonafide/session.py
index a113d0a..85e49e0 100644
--- a/bonafide/src/leap/bonafide/session.py
+++ b/bonafide/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/bonafide/src/leap/bonafide/srp_auth.py b/bonafide/src/leap/bonafide/srp_auth.py
index ac2cd67..d48214f 100644
--- a/bonafide/src/leap/bonafide/srp_auth.py
+++ b/bonafide/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