/
http.py
executable file
·412 lines (322 loc) · 15.6 KB
/
http.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
# -*- coding: utf-8 -*-
"""Generic module to handle HTTP requests in libturpial"""
import os
import ssl
import sys
import socket
import base64
import logging
import requests
try:
import oauth.oauth as oauth
except:
import oauth
DEFAULT_TIMEOUT = 20
FORMAT_XML = 'xml'
FORMAT_JSON = 'json'
class TurpialHTTPBase:
"""
This class is the abstraction of the HTTP protocol for libturpial. It
handles all the magic behind http requests: building, authenticating and
fetching resources, in other words, it standarize the way you interact
with end points and services.
You shouldn't instantiate this class, instead you should use the proper
implementation for OAuth (:class:`libturpial.api.interfaces.http.TurpialHTTPOAuth`)
or Basic Auth (:class:`libturpial.api.interfaces.http.TurpialHTTPBasicAuth`)
or even develop your own implementation if the authentication method is
not supported.
*base_url* is the part of the URL common for all your requests
(http://api.twitter.com/1.1 for example). *proxies* is a dict where you
define HTTP/HTTPS proxies.
>>> proxies = {
"http": "http://10.10.1.10:3128",
"https": "https://10.10.1.10:1080",
}
If your proxy uses HTTP authentication then you can set user and password
with something like this:
>>> proxies = {
"http": "http://user:pass@10.10.1.10:3128",
}
*timeout* is the maximum time in seconds that TurpialHTTPBase will wait
before cancelling the current request. If *timeout* is not specified
then *DEFAULT_TIMEOUT* will be used.
"""
def __init__(self, base_url, proxies=None, timeout=None):
self.base_url = base_url
self.proxies = proxies or {}
self.timeout = timeout or DEFAULT_TIMEOUT
self.log = logging.getLogger('TurpialHTTP')
if getattr(sys, 'frozen', None):
basedir = sys._MEIPASS
self.ca_certs_file = os.path.realpath(os.path.join(basedir,
'cacert.pem'))
else:
basedir = os.path.dirname(__file__)
self.ca_certs_file = os.path.realpath(os.path.join(basedir,
'..', 'certs', 'cacert.pem'))
def __validate_ssl_cert(self, request):
req = request.split('://')[1]
host = req[:req.find('/')]
port = 443
ip = socket.getaddrinfo(host, port)[0][4][0]
sock = socket.socket()
sock.connect((ip, port))
sock = ssl.wrap_socket(sock, cert_reqs=ssl.CERT_REQUIRED,
ca_certs=self.ca_certs_file)
cert = sock.getpeercert()
for field in cert['subject']:
if field[0][0] == 'commonName':
certhost = field[0][1]
if certhost != host:
raise ssl.SSLError("Host name '%s' doesn't match certificate host '%s'" % (host, certhost))
self.log.debug('Validated SSL cert for host: %s' % host)
def __build_request(self, method, uri, args, _format, secure, id_in_url):
if 'id' in args and id_in_url:
uri = "%s/%s" % (uri, args['id'])
del args['id']
if _format is None:
uri = "%s" % (uri)
else:
uri = "%s.%s" % (uri, _format)
return TurpialHTTPRequest(method, uri, _format=_format, params=args,
secure=secure)
def __fetch_resource(self, httpreq):
if httpreq.method == 'GET':
req = requests.get(httpreq.uri, params=httpreq.params,
headers=httpreq.headers, verify=httpreq.secure,
proxies=self.proxies, timeout=self.timeout)
elif httpreq.method == 'POST':
req = requests.post(httpreq.uri, params=httpreq.params,
headers=httpreq.headers, verify=httpreq.secure,
proxies=self.proxies, timeout=self.timeout)
if httpreq._format == FORMAT_JSON:
return req.json()
else:
return req.text
def sign_request(self, httpreq):
"""
This is the method you need to overwrite if you subclass
:class:`libturpial.api.interfaces.http.TurpialHTTPBase`.
*httpreq* is the current request (a :class:`libturpial.api.interfaces.http.TurpialHTTPRequest`
object), you need to apply all the authentication/authorization methods
to that object and make it valid; then exit. There is no need to return
any value because all changes are done directly over the object.
If this method is not overwritten it will return a
**NotImplementedError** exception.
"""
raise NotImplementedError
# ------------------------------------------------------------
# Main Methods
# ------------------------------------------------------------
def set_timeout(self, timeout):
"""
Configure the maximum time (in seconds) to wait before killing the
current request.
"""
self.timeout = timeout
def set_proxy(self, host, port, username='', password='', https=False):
"""
Set an HTTP/HTTPS proxy for all requests. You must pass the *host*
and *port*. If your proxy uses HTTP authentication you can pass the
*username* and *password* too. If *https* is True your proxy will
use HTTPS, otherwise HTTP.
"""
proxy_auth = ''
if username and password:
proxy_auth = "%s:%s@" % (username, password)
protocol = 'https' if https else 'http'
self.proxies[protocol] = "%s://%s%s:%s" % (protocol, proxy_auth, host, port)
def get(self, uri, args=None, _format=FORMAT_JSON, base_url=None, secure=False, id_in_url=True):
"""
Performs a GET request against the *uri* resource with *args*. You
can specify the *_format* ('json' or 'xml') and can specify a different
*base_url*. If *secure* is True the request will be perform as HTTPS,
otherwise it will be performed as HTTP.
This method is an alias for **request** function using 'GET' as method.
"""
return self.request('GET', uri, args, _format, base_url, secure, id_in_url)
def post(self, uri, args=None, _format=FORMAT_JSON, base_url=None, secure=False, id_in_url=True):
"""
Performs a POST request against the *uri* resource with *args*. You
can specify the *_format* ('json' or 'xml') and can specify a different
*base_url*. If *secure* is True the request will be perform as HTTPS,
otherwise it will be performed as HTTP.
This method is an alias for **request** function using 'POST' as method.
"""
return self.request('POST', uri, args, _format, base_url, secure, id_in_url)
def request(self, method, uri, args=None, _format=FORMAT_JSON,
alt_base_url=None, secure=False, id_in_url=True):
"""
Performs a GET or POST request against the *uri* resource with
*args*. You can specify the *_format* ('json' or 'xml') and can specify
a different *base_url*. If *secure* is True the request will be perform
as HTTPS, otherwise it will be performed as HTTP
"""
args = args or {}
base_url = alt_base_url or self.base_url
if secure:
base_url = base_url.replace('http://', 'https://')
self.__validate_ssl_cert(base_url)
request_url = "%s%s" % (base_url, uri)
httpreq = self.__build_request(method, request_url, args, _format, secure, id_in_url)
self.sign_request(httpreq)
return self.__fetch_resource(httpreq)
class TurpialHTTPOAuth(TurpialHTTPBase):
"""
Implementation of TurpialHTTPBase for OAuth. *base_url* is the part of
the URL common for all your requests
(http://api.twitter.com/1.1 for example). *oauth_options* is a dict with
all the OAuth configuration parameters. It must looks like:
>>> oauth_options = {
'consumer_key': 'APP_CONSUMER_KEY',
'consumer_secret': 'APP_CONSUMER_SECRET',
'request_token_url': 'http://request_url',
'authorize_token_url': 'http://authorize_url',
'access_token_url': 'http://access_url',
}
*consumer_key* and *consumer_secret* are the credentials for your
application (they must be provided by the OAuth service).
*request_token_url*, *authorize_token_url* and *access_token_url* are
the URLs designed by the OAuth service to fetch and authorize an OAuth
token.
*user_key*, *user_secret* and *verifier* (a.k.a. PIN) are the token
credentials granted to the user after the OAuth dance. They are optional
and needed only if the user was already authenticated, otherwise you need
to fetch a new token.
*proxies* and *timeout* work in the same way that in
:class:`libturpial.api.interfaces.http.TurpialHTTPBase`.
* To request an OAuth token create a new TurpialHTTPOAuth object and
request a token to your service:
>>> http = TurpialHTTPOAuth(base_url, oauth_options)
>>> url_to_auth = http.request_token()
Ask the user go to the *url_to_auth* URL and authorize your app. Then get
the PIN the service will deliver to your user and authorize the token:
>>> token = http.authorize_token(pin)
Use this token to store the key, secret and pin in a safe place to use it
from now on. Then inform to TurpialHTTPOAuth that you have access granted:
>>> http.set_token_info(token.key, token.secret, token.verifier)
* To use an existing token to fetch a resource create a new TurpialHTTPOAuth
object and pass the user_key, user_secret and pin (verifier):
>>> http = TurpialHTTPOAuth(base_url, oauth_options, user_key, user_secret,
verifier)
* To perform a request use the get or post method:
>>> http.get('/my_first/end_point', args={'arg1': '1'}, _format='json')
>>> http.post('/my_second/end_point', args={'arg1': '2'}, _format='json')
"""
def __init__(self, base_url, oauth_options, user_key=None, user_secret=None,
verifier=None, proxies=None, timeout=None):
TurpialHTTPBase.__init__(self, base_url, proxies, timeout)
self.request_token_url = oauth_options['request_token_url']
self.authorize_token_url = oauth_options['authorize_token_url']
self.access_token_url = oauth_options['access_token_url']
self.consumer = oauth.OAuthConsumer(oauth_options['consumer_key'],
oauth_options['consumer_secret'])
self.sign_method_hmac_sha1 = oauth.OAuthSignatureMethod_HMAC_SHA1()
if user_key and user_secret and verifier:
self.token = oauth.OAuthToken(user_key, user_secret)
self.token.set_verifier(verifier)
else:
self.token = None
def sign_request(self, httpreq):
"""
Signs the *httpreq* for OAuth using the previously defined user token
"""
request = oauth.OAuthRequest.from_consumer_and_token(self.consumer,
token=self.token, http_method=httpreq.method,
http_url=httpreq.uri, parameters=httpreq.params)
request.sign_request(self.sign_method_hmac_sha1, self.consumer,
self.token)
httpreq.headers.update(request.to_header())
def set_token_info(self, user_key, user_secret, verifier):
"""
Creates a new token using the existing *user_key*, *user_secret* and
*verifier*. Use this method
"""
self.token = oauth.OAuthToken(user_key, user_secret)
self.token.set_verifier(verifier)
def request_token(self):
"""
Ask to the service for a fresh new token. Returns an URL that the
user must access in order to authorize the client.
"""
oauth_request = oauth.OAuthRequest.from_consumer_and_token(self.consumer,
http_url=self.request_token_url)
oauth_request.sign_request(self.sign_method_hmac_sha1, self.consumer, None)
req = requests.get(self.request_token_url, headers=oauth_request.to_header())
self.token = oauth.OAuthToken.from_string(req.text)
oauth_request = oauth.OAuthRequest.from_token_and_callback(token=self.token,
http_url=self.authorize_token_url)
return oauth_request.to_url()
def authorize_token(self, pin):
"""
Uses the *pin* returned by the service to authorize the current token.
Returns an :class:`oauth.OAuthToken` object.
"""
oauth_request = oauth.OAuthRequest.from_consumer_and_token(self.consumer,
token=self.token, verifier=pin, http_url=self.access_token_url)
oauth_request.sign_request(self.sign_method_hmac_sha1, self.consumer,
self.token)
req = requests.get(self.access_token_url, headers=oauth_request.to_header())
self.token = oauth.OAuthToken.from_string(req.text)
return self.token
def request_xauth_token(self, username, password):
"""
Request a limited token without using the whole OAuth flow, it just
uses the username and password through xAuth
"""
request = oauth.OAuthRequest.from_consumer_and_token(
oauth_consumer=self.consumer,
http_method='POST', http_url=self.access_token_url,
parameters = {
'x_auth_mode': 'client_auth',
'x_auth_username': username,
'x_auth_password': password
}
)
request.sign_request(self.sign_method_hmac_sha1, self.consumer, None)
req = requests.post(self.access_token_url, data=request.to_postdata())
self.token = oauth.OAuthToken.from_string(req.text)
return self.token
class TurpialHTTPBasicAuth(TurpialHTTPBase):
"""
Implementation of TurpialHTTPBase for the HTTP Basic Authentication.
*base_url* is the part of the URL common for all your requests
(http://identi.ca/api for example). *username* and *password* are the username
credentials. *proxies* and *timeout* work in the same way that in
:class:`libturpial.api.interfaces.http.TurpialHTTPBase`.
*proxies* and *timeout* work in the same way that in
:class:`libturpial.api.interfaces.http.TurpialHTTPBase`.
* To fetch a resource using Basic Authentication just create a new
TurpialHTTPBasicAuth object and pass the user credentials as parameters:
>>> http = TurpialHTTPBasicAuth(base_url, username, password)
Then perform the request desired using get or post methods:
>>> http.get('/my_first/end_point', args={'arg1': '1'}, _format='json')
>>> http.post('/my_second/end_point', args={'arg1': '2'}, _format='json')
"""
def __init__(self, base_url, proxies=None, timeout=None):
TurpialHTTPBase.__init__(self, base_url, proxies, timeout)
def set_user_info(self, username, password):
"""
Set the *username* and *password* for the basic authentication
"""
auth_info = base64.b64encode("%s:%s" % (username, password))
self.basic_auth_info = ''.join(["Basic ", auth_info])
def sign_request(self, httpreq):
"""
The *httpreq* is signed using the Authorization header as
documented in the `Basic Access Authentication Wiki
<http://en.wikipedia.org/wiki/Basic_access_authentication>`_
"""
httpreq.headers["Authorization"] = self.basic_auth_info
class TurpialHTTPRequest:
"""
Encapsulate an URL request into a python object
"""
def __init__(self, method, uri, headers=None, params=None,
_format=FORMAT_JSON, secure=False):
self.uri = uri
self.method = method
self._format = _format
self.secure = secure
self.params = params or {}
self.headers = headers or {}