-
Notifications
You must be signed in to change notification settings - Fork 174
/
hook.py
153 lines (120 loc) · 5.88 KB
/
hook.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
'''
webauth.hook
------------
A hook for the Python Requests package that provides OAuth 1.0/a client
support.
'''
import time
import random
from hashlib import sha1
from urllib import quote, urlencode
from webauth.oauth import HmacSha1Signature, Token, Consumer
class OAuth1Hook(object):
'''Provides a pre-request hook into requests for OAuth 1.0/a services.
This package is built on the excellent Python Requests package. It
functions by "hooking" into a request and appending various attributes to
it which allow a client to interact with a standardized OAuth 1.0/a
provider.
You might intialize :class:`OAuthHook` something like this::
oauth = OAuthHook(consumer_key=1234,
consumer_secret=5678)
oauth_session = requests.session(hooks={'pre_request': oauth})
This establishes a requests session that is wrapped if the OAuth-capable
hook. Using this session, an OAuth provider may be interacted with and
will receive the proper formatting for requests.
Note that this is normally used as a starting from which a request token
would be generated whereupon an access token is received. Once such a token
has been received, the wrapper should be reinitalized with this token::
# we provide our consumer pair as well as the access pair as returned
# by the provider endpoint
oauth = OAuthHook(consumer_key=1234,
consumer_secret=5678,
access_token=4321,
access_token_secret=8765)
oauth_session = requests.session(hooks={'pre_request': oauth})
The session is now ready to make calls to the endpoints made available by
the provider.
Additionally some services will make use of header authentication. This is
provided by passing :class:`__init__` the `auth_header` parameter as
`True`.
:param consumer_key: Client consumer key.
:param consumer_secret: Client consumer secret.
:param access_token: Access token key.
:param access_token_secret: Access token secret.
:param header_auth: Authenication via header, defauls to False.
:param signature: A signature method used to sign request parameters.
Defaults to None. If None the `HmacSha1Signature` method is used as
default.
'''
OAUTH_VERSION = '1.0'
def __init__(self, consumer_key, consumer_secret, access_token=None,
access_token_secret=None, header_auth=False, signature=None):
self.consumer = Consumer(consumer_key, consumer_secret)
# intitialize the token and then set it if possible
self.token = None
if not None in (access_token, access_token_secret):
self.token = Token(access_token, access_token_secret)
self.header_auth = header_auth
self.signature = HmacSha1Signature()
# override the default signature object if available
if signature is not None:
self.signature = signature
def __call__(self, request):
# this is a workaround for a known bug that will be patched
if isinstance(request.params, list):
request.params = dict(request.params)
if isinstance(request.data, list):
request.data = dict(request.data)
# generate the necessary request params
request.oauth_params = self.generate_oauth_params()
# here we append an oauth_callback parameter if any
if 'oauth_callback' in request.data:
request.oauth_params['oauth_callback'] = \
request.data.pop('oauth_callback')
if 'oauth_callback' in request.params:
request.oauth_params['oauth_callback'] = \
request.params.pop('oauth_callback')
# this is used in the Normalize Request Parameters step
request.data_and_params = request.oauth_params.copy()
# sign and add the signature to the request params
self.signature.sign(request, self.consumer, self.token)
if self.header_auth:
# authenticate in the header
request.headers['Authorization'] = \
self.generate_authorization_header(request.data_and_params)
elif request.method == 'POST':
# add data_and_params to the body of the POST
request.data = request.data_and_params
request.headers['content-type'] = \
'application/x-www-form-urlencoded'
else:
# add data_and_params to the URL parameters
request.url = request.url + '?' + \
urlencode(request.data_and_params)
# we're done with these now
del request.data_and_params
def generate_oauth_params(self):
'''This method handles generating the necessary URL parameters the
OAuth provider will expect.'''
oauth_params = {}
oauth_params['oauth_consumer_key'] = self.consumer.key
oauth_params['oauth_timestamp'] = int(time.time())
oauth_params['oauth_nonce'] = sha1(str(random.random())).hexdigest()
oauth_params['oauth_version'] = self.OAUTH_VERSION
if self.token:
oauth_params['oauth_token'] = self.token.key
# this must be set upon recieving a verifier
oauth_params['oauth_verifier'] = self.token.verifier or ''
oauth_params['oauth_signature_method'] = self.signature.NAME
return oauth_params
def generate_authorization_header(self, oauth_params, realm=None):
'''This method constructs an authorization header.
:param oauth_params: The OAuth parameters to be added to the header.
:param realm: The authentication realm. Defaults to None.
'''
auth_header = 'OAuth realm="{0}"'.format(realm)
params = ''
for k, v in oauth_params.items():
params += ',{0}="{1}"'.format(k, quote(str(v)))
auth_header += params
return auth_header