summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRuben Pollan <meskio@sindominio.net>2015-05-26 22:04:52 +0200
committerRuben Pollan <meskio@sindominio.net>2015-05-27 00:41:09 +0200
commit91674a40edb19cd241c76b27ad998bb5df404570 (patch)
tree2c4890d6f91f857e6c7e1ff053107b4d264de34c
parentfe19e46c8f11f5b27164112a61a7405aa0e0d455 (diff)
[refactor] move the twisted http code to leap.common
-rw-r--r--client/src/leap/soledad/client/http_client.py194
-rw-r--r--client/src/leap/soledad/client/http_target.py38
2 files changed, 31 insertions, 201 deletions
diff --git a/client/src/leap/soledad/client/http_client.py b/client/src/leap/soledad/client/http_client.py
deleted file mode 100644
index b08d199e..00000000
--- a/client/src/leap/soledad/client/http_client.py
+++ /dev/null
@@ -1,194 +0,0 @@
-# -*- coding: utf-8 -*-
-# http_client.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 HTTP/HTTPS client.
-"""
-
-import os
-
-from zope.interface import implements
-
-from OpenSSL.crypto import load_certificate
-from OpenSSL.crypto import FILETYPE_PEM
-
-from twisted.internet import reactor
-from twisted.internet.ssl import ClientContextFactory
-from twisted.internet.ssl import CertificateOptions
-from twisted.internet.defer import succeed
-
-from twisted.web.client import Agent
-from twisted.web.client import HTTPConnectionPool
-from twisted.web.client import readBody
-from twisted.web.http_headers import Headers
-from twisted.web.error import Error
-from twisted.web.iweb import IBodyProducer
-
-
-from leap.soledad.common.errors import InvalidAuthTokenError
-
-
-#
-# Setup a pool of connections
-#
-
-_pool = HTTPConnectionPool(reactor, persistent=True)
-_pool.maxPersistentPerHost = 10
-_agent = None
-
-# if we ever want to trust the system's CAs, we should use an agent like this:
-# from twisted.web.client import BrowserLikePolicyForHTTPS
-# _agent = Agent(reactor, BrowserLikePolicyForHTTPS(), pool=_pool)
-
-
-#
-# SSL/TLS certificate configuration
-#
-
-def configure_certificate(cert_file):
- """
- Configure an agent that verifies server certificates against a CA cert
- file.
-
- :param cert_file: The path to the certificate file.
- :type cert_file: str
- """
- global _agent
- cert = _load_cert(cert_file)
- _agent = Agent(
- reactor,
- SoledadClientContextFactory(cert),
- pool=_pool)
-
-
-def _load_cert(cert_file):
- """
- Load a X509 certificate from a file.
-
- :param cert_file: The path to the certificate file.
- :type cert_file: str
-
- :return: The X509 certificate.
- :rtype: OpenSSL.crypto.X509
- """
- if os.path.exists(cert_file):
- with open(cert_file) as f:
- data = f.read()
- return load_certificate(FILETYPE_PEM, data)
-
-
-class SoledadClientContextFactory(ClientContextFactory):
- """
- A context factory that will verify the server's certificate against a
- given CA certificate.
- """
-
- def __init__(self, cacert):
- """
- Initialize the context factory.
-
- :param cacert: The CA certificate.
- :type cacert: OpenSSL.crypto.X509
- """
- self._cacert = cacert
-
- def getContext(self, hostname, port):
- opts = CertificateOptions(verify=True, caCerts=[self._cacert])
- return opts.getContext()
-
-
-#
-# HTTP request facilities
-#
-
-def _unauth_to_invalid_token_error(failure):
- """
- An errback to translate unauthorized errors to our own invalid token
- class.
-
- :param failure: The original failure.
- :type failure: twisted.python.failure.Failure
-
- :return: Either the original failure or an invalid auth token error.
- :rtype: twisted.python.failure.Failure
- """
- failure.trap(Error)
- if failure.getErrorMessage() == "401 Unauthorized":
- raise InvalidAuthTokenError
- return failure
-
-
-class StringBodyProducer(object):
- """
- A producer that writes the body of a request to a consumer.
- """
-
- implements(IBodyProducer)
-
- def __init__(self, body):
- """
- Initialize the string produer.
-
- :param body: The body of the request.
- :type body: str
- """
- self.body = body
- self.length = len(body)
-
- def startProducing(self, consumer):
- """
- Write the body to the consumer.
-
- :param consumer: Any IConsumer provider.
- :type consumer: twisted.internet.interfaces.IConsumer
-
- :return: A successful deferred.
- :rtype: twisted.internet.defer.Deferred
- """
- consumer.write(self.body)
- return succeed(None)
-
- def pauseProducing(self):
- pass
-
- def stopProducing(self):
- pass
-
-
-def httpRequest(url, method='GET', body=None, headers={}):
- """
- Perform an HTTP request.
-
- :param url: The URL for the request.
- :type url: str
- :param method: The HTTP method of the request.
- :type method: str
- :param body: The body of the request, if any.
- :type body: str
- :param headers: The headers of the request.
- :type headers: dict
-
- :return: A deferred that fires with the body of the request.
- :rtype: twisted.internet.defer.Deferred
- """
- if body:
- body = StringBodyProducer(body)
- d = _agent.request(
- method, url, headers=Headers(headers), bodyProducer=body)
- d.addCallbacks(readBody, _unauth_to_invalid_token_error)
- return d
diff --git a/client/src/leap/soledad/client/http_target.py b/client/src/leap/soledad/client/http_target.py
index dc6c0e0a..5eef2df3 100644
--- a/client/src/leap/soledad/client/http_target.py
+++ b/client/src/leap/soledad/client/http_target.py
@@ -31,12 +31,16 @@ from functools import partial
from twisted.internet import defer
from twisted.internet import reactor
+from twisted.web.error import Error
from u1db import errors
from u1db import SyncTarget
from u1db.remote import utils
+from leap.common.http import HTTPClient
+
from leap.soledad.common.document import SoledadDocument
+from leap.soledad.common.errors import InvalidAuthTokenError
from leap.soledad.client.crypto import is_symmetrically_encrypted
from leap.soledad.client.crypto import encrypt_doc
@@ -45,8 +49,6 @@ from leap.soledad.client.events import SOLEDAD_SYNC_SEND_STATUS
from leap.soledad.client.events import SOLEDAD_SYNC_RECEIVE_STATUS
from leap.soledad.client.events import signal
from leap.soledad.client.encdecpool import SyncDecrypterPool
-from leap.soledad.client.http_client import httpRequest
-from leap.soledad.client.http_client import configure_certificate
logger = logging.getLogger(__name__)
@@ -107,7 +109,7 @@ class SoledadHTTPSyncTarget(SyncTarget):
# asynchronous encryption/decryption attributes
self._decryption_callback = None
self._sync_decr_pool = None
- configure_certificate(cert_file)
+ self._http = HTTPClient(cert_file)
def set_creds(self, creds):
"""
@@ -148,7 +150,7 @@ class SoledadHTTPSyncTarget(SyncTarget):
source_replica_last_known_transaction_id)
:rtype: twisted.internet.defer.Deferred
"""
- raw = yield httpRequest(self._url, headers=self._auth_header)
+ raw = yield self._http_request(self._url, headers=self._auth_header)
res = json.loads(raw)
defer.returnValue([
res['target_replica_uid'],
@@ -193,7 +195,7 @@ class SoledadHTTPSyncTarget(SyncTarget):
})
headers = self._auth_header.copy()
headers.update({'content-type': ['application/json']})
- return httpRequest(
+ return self._http_request(
self._url,
method='PUT',
headers=headers,
@@ -330,7 +332,7 @@ class SoledadHTTPSyncTarget(SyncTarget):
doc_idx=doc_idx)
entries.append('\r\n]')
data = ''.join(entries)
- result = yield httpRequest(
+ result = yield self._http_request(
self._url,
method='POST',
headers=headers,
@@ -481,7 +483,7 @@ class SoledadHTTPSyncTarget(SyncTarget):
',', entries, received=received)
entries.append('\r\n]')
# send headers
- return httpRequest(
+ return self._http_request(
self._url,
method='POST',
headers=headers,
@@ -596,3 +598,25 @@ class SoledadHTTPSyncTarget(SyncTarget):
self._sync_db,
insert_doc_cb=self._insert_doc_cb,
source_replica_uid=self.source_replica_uid)
+
+ def _http_request(self, url, method='GET', body=None, headers={}):
+ d = self._http.request(url, method, body, headers)
+ d.addErrback(_unauth_to_invalid_token_error)
+ return d
+
+
+def _unauth_to_invalid_token_error(failure):
+ """
+ An errback to translate unauthorized errors to our own invalid token
+ class.
+
+ :param failure: The original failure.
+ :type failure: twisted.python.failure.Failure
+
+ :return: Either the original failure or an invalid auth token error.
+ :rtype: twisted.python.failure.Failure
+ """
+ failure.trap(Error)
+ if failure.getErrorMessage() == "401 Unauthorized":
+ raise InvalidAuthTokenError
+ return failure