david / django-oauth (http://oauth.net/)
Support of OAuth in Django. Note that http://code.welldev.org/django-oauth-plus will use python-oauth2 if you're interested in it.
| commit 27: | c038f3e47c5f |
| parent 26: | f5d507cac89c |
| branch: | default |
Update python-oauth to the latest tip, Store's methods signatures changed but tests still pass, good.
12 months ago
Changed (Δ4.1 KB):
raw changeset »
.hgignore (3 lines added, 0 lines removed)
oauth/oauth.py (259 lines added, 135 lines removed)
oauth_provider/stores.py (2 lines added, 2 lines removed)
| … | … | @@ -9,3 +9,6 @@ syntax:glob |
9 |
9 |
*.tmp |
10 |
10 |
*.DS_Store |
11 |
11 |
testdb.sqlite |
12 |
dist/* |
|
13 |
django_oauth.egg-info/* |
|
14 |
setuptools_hg-0.1-py2.5.egg |
Up to file-list oauth/oauth.py:
1 |
""" |
|
2 |
The MIT License |
|
3 |
||
4 |
Copyright (c) 2007 Leah Culver |
|
5 |
||
6 |
Permission is hereby granted, free of charge, to any person obtaining a copy |
|
7 |
of this software and associated documentation files (the "Software"), to deal |
|
8 |
in the Software without restriction, including without limitation the rights |
|
9 |
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
|
10 |
copies of the Software, and to permit persons to whom the Software is |
|
11 |
furnished to do so, subject to the following conditions: |
|
12 |
||
13 |
The above copyright notice and this permission notice shall be included in |
|
14 |
all copies or substantial portions of the Software. |
|
15 |
||
16 |
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
|
17 |
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
|
18 |
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
|
19 |
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
|
20 |
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
|
21 |
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN |
|
22 |
THE SOFTWARE. |
|
23 |
""" |
|
24 |
||
1 |
25 |
import cgi |
2 |
26 |
import urllib |
3 |
27 |
import time |
| … | … | @@ -6,37 +30,52 @@ import urlparse |
6 |
30 |
import hmac |
7 |
31 |
import binascii |
8 |
32 |
|
33 |
||
9 |
34 |
VERSION = '1.0' # Hi Blaine! |
10 |
35 |
HTTP_METHOD = 'GET' |
11 |
36 |
SIGNATURE_METHOD = 'PLAINTEXT' |
12 |
37 |
|
13 |
# Generic exception class |
|
38 |
||
14 |
39 |
class OAuthError(RuntimeError): |
40 |
"""Generic exception class.""" |
|
15 |
41 |
def __init__(self, message='OAuth error occured.'): |
16 |
42 |
self.message = message |
17 |
43 |
|
18 |
# optional WWW-Authenticate header (401 error) |
|
19 |
44 |
def build_authenticate_header(realm=''): |
45 |
"""Optional WWW-Authenticate header (401 error)""" |
|
20 |
46 |
return {'WWW-Authenticate': 'OAuth realm="%s"' % realm} |
21 |
47 |
|
22 |
# url escape |
|
23 |
48 |
def escape(s): |
24 |
|
|
49 |
"""Escape a URL including any /.""" |
|
25 |
50 |
return urllib.quote(s, safe='~') |
26 |
51 |
|
27 |
# util function: current timestamp |
|
28 |
# seconds since epoch (UTC) |
|
52 |
def _utf8_str(s): |
|
53 |
"""Convert unicode to utf-8.""" |
|
54 |
if isinstance(s, unicode): |
|
55 |
return s.encode("utf-8") |
|
56 |
else: |
|
57 |
return str(s) |
|
58 |
||
29 |
59 |
def generate_timestamp(): |
60 |
"""Get seconds since epoch (UTC).""" |
|
30 |
61 |
return int(time.time()) |
31 |
62 |
|
32 |
# util function: nonce |
|
33 |
# pseudorandom number |
|
34 |
63 |
def generate_nonce(length=8): |
64 |
"""Generate pseudorandom number.""" |
|
35 |
65 |
return ''.join([str(random.randint(0, 9)) for i in range(length)]) |
36 |
66 |
|
37 |
# OAuthConsumer is a data type that represents the identity of the Consumer |
|
38 |
# via its shared secret with the Service Provider. |
|
67 |
def generate_verifier(length=8): |
|
68 |
"""Generate pseudorandom number.""" |
|
69 |
return ''.join([str(random.randint(0, 9)) for i in range(length)]) |
|
70 |
||
71 |
||
39 |
72 |
class OAuthConsumer(object): |
73 |
"""Consumer of OAuth authentication. |
|
74 |
||
75 |
OAuthConsumer is a data type that represents the identity of the Consumer |
|
76 |
via its shared secret with the Service Provider. |
|
77 |
||
78 |
""" |
|
40 |
79 |
key = None |
41 |
80 |
secret = None |
42 |
81 |
|
| … | … | @@ -44,40 +83,79 @@ class OAuthConsumer(object): |
44 |
83 |
self.key = key |
45 |
84 |
self.secret = secret |
46 |
85 |
|
47 |
# OAuthToken is a data type that represents an End User via either an access |
|
48 |
# or request token. |
|
86 |
||
49 |
87 |
class OAuthToken(object): |
50 |
|
|
88 |
"""OAuthToken is a data type that represents an End User via either an access |
|
89 |
or request token. |
|
90 |
||
91 |
key -- the token |
|
92 |
secret -- the token secret |
|
93 |
||
94 |
""" |
|
51 |
95 |
key = None |
52 |
96 |
secret = None |
97 |
callback = None |
|
98 |
callback_confirmed = None |
|
99 |
verifier = None |
|
53 |
100 |
|
54 |
''' |
|
55 |
key = the token |
|
56 |
secret = the token secret |
|
57 |
''' |
|
58 |
101 |
def __init__(self, key, secret): |
59 |
102 |
self.key = key |
60 |
103 |
self.secret = secret |
61 |
104 |
|
105 |
def set_callback(self, callback): |
|
106 |
self.callback = callback |
|
107 |
self.callback_confirmed = 'true' |
|
108 |
||
109 |
def set_verifier(self, verifier=None): |
|
110 |
if verifier is not None: |
|
111 |
self.verifier = verifier |
|
112 |
else: |
|
113 |
self.verifier = generate_verifier() |
|
114 |
||
115 |
def get_callback_url(self): |
|
116 |
if self.callback and self.verifier: |
|
117 |
# Append the oauth_verifier. |
|
118 |
parts = urlparse.urlparse(self.callback) |
|
119 |
scheme, netloc, path, params, query, fragment = parts[:6] |
|
120 |
if query: |
|
121 |
query = '%s&oauth_verifier=%s' % (query, self.verifier) |
|
122 |
else: |
|
123 |
query = 'oauth_verifier=%s' % self.verifier |
|
124 |
return urlparse.urlunparse((scheme, netloc, path, params, |
|
125 |
query, fragment)) |
|
126 |
return self.callback |
|
127 |
||
62 |
128 |
def to_string(self): |
63 |
return urllib.urlencode({'oauth_token': self.key, 'oauth_token_secret': self.secret}) |
|
64 |
||
65 |
# return a token from something like: |
|
66 |
# oauth_token_secret=digg&oauth_token=digg |
|
67 |
|
|
129 |
data = { |
|
130 |
'oauth_token': self.key, |
|
131 |
'oauth_token_secret': self.secret, |
|
132 |
} |
|
133 |
if self.callback_confirmed is not None: |
|
134 |
data['oauth_callback_confirmed'] = self.callback_confirmed |
|
135 |
return urllib.urlencode(data) |
|
136 |
||
68 |
137 |
def from_string(s): |
138 |
""" Returns a token from something like: |
|
139 |
oauth_token_secret=xxx&oauth_token=xxx |
|
140 |
""" |
|
69 |
141 |
params = cgi.parse_qs(s, keep_blank_values=False) |
70 |
142 |
key = params['oauth_token'][0] |
71 |
143 |
secret = params['oauth_token_secret'][0] |
72 |
|
|
144 |
token = OAuthToken(key, secret) |
|
145 |
try: |
|
146 |
token.callback_confirmed = params['oauth_callback_confirmed'][0] |
|
147 |
except KeyError: |
|
148 |
pass # 1.0, no callback confirmed. |
|
149 |
return token |
|
73 |
150 |
from_string = staticmethod(from_string) |
74 |
151 |
|
75 |
152 |
def __str__(self): |
76 |
153 |
return self.to_string() |
77 |
154 |
|
78 |
# OAuthRequest represents the request and can be serialized |
|
155 |
||
79 |
156 |
class OAuthRequest(object): |
80 |
|
|
157 |
"""OAuthRequest represents the request and can be serialized. |
|
158 |
||
81 |
159 |
OAuth parameters: |
82 |
160 |
- oauth_consumer_key |
83 |
161 |
- oauth_token |
| … | … | @@ -86,9 +164,10 @@ class OAuthRequest(object): |
86 |
164 |
- oauth_timestamp |
87 |
165 |
- oauth_nonce |
88 |
166 |
- oauth_version |
167 |
- oauth_verifier |
|
89 |
168 |
... any additional parameters, as defined by the Service Provider. |
90 |
''' |
|
91 |
parameters = None # oauth parameters |
|
169 |
""" |
|
170 |
parameters = None # OAuth parameters. |
|
92 |
171 |
http_method = HTTP_METHOD |
93 |
172 |
http_url = None |
94 |
173 |
version = VERSION |
| … | … | @@ -108,94 +187,107 @@ class OAuthRequest(object): |
108 |
187 |
raise OAuthError('Parameter not found: %s' % parameter) |
109 |
188 |
|
110 |
189 |
def _get_timestamp_nonce(self): |
111 |
return self.get_parameter('oauth_timestamp'), self.get_parameter( |
|
190 |
return self.get_parameter('oauth_timestamp'), self.get_parameter( |
|
191 |
'oauth_nonce') |
|
112 |
192 |
|
113 |
# get any non-oauth parameters |
|
114 |
193 |
def get_nonoauth_parameters(self): |
194 |
"""Get any non-OAuth parameters.""" |
|
115 |
195 |
parameters = {} |
116 |
196 |
for k, v in self.parameters.iteritems(): |
117 |
# |
|
197 |
# Ignore oauth parameters. |
|
118 |
198 |
if k.find('oauth_') < 0: |
119 |
199 |
parameters[k] = v |
120 |
200 |
return parameters |
121 |
201 |
|
122 |
# serialize as a header for an HTTPAuth request |
|
123 |
202 |
def to_header(self, realm=''): |
203 |
"""Serialize as a header for an HTTPAuth request.""" |
|
124 |
204 |
auth_header = 'OAuth realm="%s"' % realm |
125 |
# |
|
205 |
# Add the oauth parameters. |
|
126 |
206 |
if self.parameters: |
127 |
207 |
for k, v in self.parameters.iteritems(): |
128 |
208 |
if k[:6] == 'oauth_': |
129 |
209 |
auth_header += ', %s="%s"' % (k, escape(str(v))) |
130 |
210 |
return {'Authorization': auth_header} |
131 |
211 |
|
132 |
# serialize as post data for a POST request |
|
133 |
212 |
def to_postdata(self): |
134 |
|
|
213 |
"""Serialize as post data for a POST request.""" |
|
214 |
return '&'.join(['%s=%s' % (escape(str(k)), escape(str(v))) \ |
|
215 |
for k, v in self.parameters.iteritems()]) |
|
135 |
216 |
|
136 |
# serialize as a url for a GET request |
|
137 |
217 |
def to_url(self): |
218 |
"""Serialize as a URL for a GET request.""" |
|
138 |
219 |
return '%s?%s' % (self.get_normalized_http_url(), self.to_postdata()) |
139 |
220 |
|
140 |
# return a string that consists of all the parameters that need to be signed |
|
141 |
221 |
def get_normalized_parameters(self): |
222 |
"""Return a string that contains the parameters that must be signed.""" |
|
142 |
223 |
params = self.parameters |
143 |
224 |
try: |
144 |
# |
|
225 |
# Exclude the signature if it exists. |
|
145 |
226 |
del params['oauth_signature'] |
146 |
227 |
except: |
147 |
228 |
pass |
148 |
key_values = params.items() |
|
149 |
# sort lexicographically, first after key, then after value |
|
229 |
# Escape key values before sorting. |
|
230 |
key_values = [(escape(_utf8_str(k)), escape(_utf8_str(v))) \ |
|
231 |
for k,v in params.items()] |
|
232 |
# Sort lexicographically, first after key, then after value. |
|
150 |
233 |
key_values.sort() |
151 |
# combine key value pairs in string and escape |
|
152 |
return '&'.join(['%s=%s' % (escape(str(k)), escape(str(v))) for k, v in key_values]) |
|
234 |
# Combine key value pairs into a string. |
|
235 |
return '&'.join(['%s=%s' % (k, v) for k, v in key_values]) |
|
153 |
236 |
|
154 |
# just uppercases the http method |
|
155 |
237 |
def get_normalized_http_method(self): |
238 |
"""Uppercases the http method.""" |
|
156 |
239 |
return self.http_method.upper() |
157 |
240 |
|
158 |
# parses the url and rebuilds it to be scheme://host/path |
|
159 |
241 |
def get_normalized_http_url(self): |
242 |
"""Parses the URL and rebuilds it to be scheme://host/path.""" |
|
160 |
243 |
parts = urlparse.urlparse(self.http_url) |
161 |
url_string = '%s://%s%s' % (parts[0], parts[1], parts[2]) # scheme, netloc, path |
|
162 |
return url_string |
|
163 |
||
164 |
# set the signature parameter to the result of build_signature |
|
244 |
scheme, netloc, path = parts[:3] |
|
245 |
# Exclude default port numbers. |
|
246 |
if scheme == 'http' and netloc[-3:] == ':80': |
|
247 |
netloc = netloc[:-3] |
|
248 |
elif scheme == 'https' and netloc[-4:] == ':443': |
|
249 |
netloc = netloc[:-4] |
|
250 |
return '%s://%s%s' % (scheme, netloc, path) |
|
251 |
||
165 |
252 |
def sign_request(self, signature_method, consumer, token): |
166 |
# set the signature method |
|
167 |
self.set_parameter('oauth_signature_method', signature_method.get_name()) |
|
168 |
# set the signature |
|
169 |
self.set_parameter('oauth_signature', self.build_signature(signature_method, consumer, token)) |
|
253 |
"""Set the signature parameter to the result of build_signature.""" |
|
254 |
# Set the signature method. |
|
255 |
self.set_parameter('oauth_signature_method', |
|
256 |
signature_method.get_name()) |
|
257 |
# Set the signature. |
|
258 |
self.set_parameter('oauth_signature', |
|
259 |
self.build_signature(signature_method, consumer, token)) |
|
170 |
260 |
|
171 |
261 |
def build_signature(self, signature_method, consumer, token): |
172 |
|
|
262 |
"""Calls the build signature method within the signature method.""" |
|
173 |
263 |
return signature_method.build_signature(self, consumer, token) |
174 |
264 |
|
175 |
# @staticmethod |
|
176 |
def from_request(http_method, http_url, headers=None, parameters=None, query_string=None): |
|
177 |
|
|
265 |
def from_request(http_method, http_url, headers=None, parameters=None, |
|
266 |
query_string=None): |
|
267 |
"""Combines multiple parameter sources.""" |
|
178 |
268 |
if parameters is None: |
179 |
269 |
parameters = {} |
180 |
270 |
|
181 |
# |
|
271 |
# Headers |
|
182 |
272 |
if headers and 'Authorization' in headers: |
183 |
273 |
auth_header = headers['Authorization'] |
184 |
# check that the authorization header is OAuth |
|
185 |
if auth_header.index('OAuth') > -1: |
|
274 |
# Check that the authorization header is OAuth. |
|
275 |
if auth_header[:6] == 'OAuth ': |
|
276 |
auth_header = auth_header[6:] |
|
186 |
277 |
try: |
187 |
# |
|
278 |
# Get the parameters from the header. |
|
188 |
279 |
header_params = OAuthRequest._split_header(auth_header) |
189 |
280 |
parameters.update(header_params) |
190 |
281 |
except: |
191 |
raise OAuthError('Unable to parse OAuth parameters from |
|
282 |
raise OAuthError('Unable to parse OAuth parameters from ' |
|
283 |
'Authorization header.') |
|
192 |
284 |
|
193 |
# GET or POST query string |
|
285 |
# GET or POST query string. |
|
194 |
286 |
if query_string: |
195 |
287 |
query_params = OAuthRequest._split_url_string(query_string) |
196 |
288 |
parameters.update(query_params) |
197 |
289 |
|
198 |
# URL parameters |
|
290 |
# URL parameters. |
|
199 |
291 |
param_str = urlparse.urlparse(http_url)[4] # query |
200 |
292 |
url_params = OAuthRequest._split_url_string(param_str) |
201 |
293 |
parameters.update(url_params) |
| … | … | @@ -206,8 +298,9 @@ class OAuthRequest(object): |
206 |
298 |
return None |
207 |
299 |
from_request = staticmethod(from_request) |
208 |
300 |
|
209 |
# @staticmethod |
|
210 |
def from_consumer_and_token(oauth_consumer, token=None, http_method=HTTP_METHOD, http_url=None, parameters=None): |
|
301 |
def from_consumer_and_token(oauth_consumer, token=None, |
|
302 |
callback=None, verifier=None, http_method=HTTP_METHOD, |
|
303 |
http_url=None, parameters=None): |
|
211 |
304 |
if not parameters: |
212 |
305 |
parameters = {} |
213 |
306 |
|
| … | … | @@ -223,12 +316,20 @@ class OAuthRequest(object): |
223 |
316 |
|
224 |
317 |
if token: |
225 |
318 |
parameters['oauth_token'] = token.key |
319 |
if token.callback: |
|
320 |
parameters['oauth_callback'] = token.callback |
|
321 |
# 1.0a support for verifier. |
|
322 |
if verifier: |
|
323 |
parameters['oauth_verifier'] = verifier |
|
324 |
elif callback: |
|
325 |
# 1.0a support for callback in the request token request. |
|
326 |
parameters['oauth_callback'] = callback |
|
226 |
327 |
|
227 |
328 |
return OAuthRequest(http_method, http_url, parameters) |
228 |
329 |
from_consumer_and_token = staticmethod(from_consumer_and_token) |
229 |
330 |
|
230 |
# @staticmethod |
|
231 |
def from_token_and_callback(token, callback=None, http_method=HTTP_METHOD, http_url=None, parameters=None): |
|
331 |
def from_token_and_callback(token, callback=None, http_method=HTTP_METHOD, |
|
332 |
http_url=None, parameters=None): |
|
232 |
333 |
if not parameters: |
233 |
334 |
parameters = {} |
234 |
335 |
|
| … | … | @@ -240,36 +341,34 @@ class OAuthRequest(object): |
240 |
341 |
return OAuthRequest(http_method, http_url, parameters) |
241 |
342 |
from_token_and_callback = staticmethod(from_token_and_callback) |
242 |
343 |
|
243 |
# util function: turn Authorization: header into parameters, has to do some unescaping |
|
244 |
# @staticmethod |
|
245 |
344 |
def _split_header(header): |
345 |
"""Turn Authorization: header into parameters.""" |
|
246 |
346 |
params = {} |
247 |
347 |
parts = header.split(',') |
248 |
348 |
for param in parts: |
249 |
# ignore realm parameter |
|
250 |
if param.find('OAuth realm') > -1: |
|
349 |
# Ignore realm parameter. |
|
350 |
if param.find('realm') > -1: |
|
251 |
351 |
continue |
252 |
# |
|
352 |
# Remove whitespace. |
|
253 |
353 |
param = param.strip() |
254 |
# |
|
354 |
# Split key-value. |
|
255 |
355 |
param_parts = param.split('=', 1) |
256 |
# |
|
356 |
# Remove quotes and unescape the value. |
|
257 |
357 |
params[param_parts[0]] = urllib.unquote(param_parts[1].strip('\"')) |
258 |
358 |
return params |
259 |
359 |
_split_header = staticmethod(_split_header) |
260 |
||
261 |
# util function: turn url string into parameters, has to do some unescaping |
|
262 |
# @staticmethod |
|
360 |
||
263 |
361 |
def _split_url_string(param_str): |
362 |
"""Turn URL string into parameters.""" |
|
264 |
363 |
parameters = cgi.parse_qs(param_str, keep_blank_values=False) |
265 |
364 |
for k, v in parameters.iteritems(): |
266 |
365 |
parameters[k] = urllib.unquote(v[0]) |
267 |
366 |
return parameters |
268 |
367 |
_split_url_string = staticmethod(_split_url_string) |
269 |
368 |
|
270 |
# OAuthServer is a worker to check a requests validity against a data store |
|
271 |
369 |
class OAuthServer(object): |
272 |
|
|
370 |
"""A worker to check the validity of a request against a data store.""" |
|
371 |
timestamp_threshold = 300 # In seconds, five minutes. |
|
273 |
372 |
version = VERSION |
274 |
373 |
signature_methods = None |
275 |
374 |
data_store = None |
| … | … | @@ -288,57 +387,67 @@ class OAuthServer(object): |
288 |
387 |
self.signature_methods[signature_method.get_name()] = signature_method |
289 |
388 |
return self.signature_methods |
290 |
389 |
|
291 |
# process a request_token request |
|
292 |
# returns the request token on success |
|
293 |
390 |
def fetch_request_token(self, oauth_request): |
391 |
"""Processes a request_token request and returns the |
|
392 |
request token on success. |
|
393 |
""" |
|
294 |
394 |
try: |
295 |
# |
|
395 |
# Get the request token for authorization. |
|
296 |
396 |
token = self._get_token(oauth_request, 'request') |
297 |
397 |
except OAuthError: |
298 |
# |
|
398 |
# No token required for the initial token request. |
|
299 |
399 |
version = self._get_version(oauth_request) |
300 |
400 |
consumer = self._get_consumer(oauth_request) |
401 |
try: |
|
402 |
callback = self.get_callback(oauth_request) |
|
403 |
except OAuthError: |
|
404 |
callback = None # 1.0, no callback specified. |
|
301 |
405 |
self._check_signature(oauth_request, consumer, None) |
302 |
# fetch a new token |
|
303 |
token = self.data_store.fetch_request_token(consumer) |
|
406 |
# Fetch a new token. |
|
407 |
token = self.data_store.fetch_request_token(consumer, callback) |
|
304 |
408 |
return token |
305 |
409 |
|
306 |
# process an access_token request |
|
307 |
# returns the access token on success |
|
308 |
410 |
def fetch_access_token(self, oauth_request): |
411 |
"""Processes an access_token request and returns the |
|
412 |
access token on success. |
|
413 |
""" |
|
309 |
414 |
version = self._get_version(oauth_request) |
310 |
415 |
consumer = self._get_consumer(oauth_request) |
311 |
|
|
416 |
try: |
|
417 |
verifier = self._get_verifier(oauth_request) |
|
418 |
except OAuthError: |
|
419 |
verifier = None |
|
420 |
# Get the request token. |
|
312 |
421 |
token = self._get_token(oauth_request, 'request') |
313 |
422 |
self._check_signature(oauth_request, consumer, token) |
314 |
new_token = self.data_store.fetch_access_token(consumer, token |
|
423 |
new_token = self.data_store.fetch_access_token(consumer, token, verifier) |
|
315 |
424 |
return new_token |
316 |
425 |
|
317 |
# verify an api call, checks all the parameters |
|
318 |
426 |
def verify_request(self, oauth_request): |
427 |
"""Verifies an api call and checks all the parameters.""" |
|
319 |
428 |
# -> consumer and token |
320 |
429 |
version = self._get_version(oauth_request) |
321 |
430 |
consumer = self._get_consumer(oauth_request) |
322 |
# |
|
431 |
# Get the access token. |
|
323 |
432 |
token = self._get_token(oauth_request, 'access') |
324 |
433 |
self._check_signature(oauth_request, consumer, token) |
325 |
434 |
parameters = oauth_request.get_nonoauth_parameters() |
326 |
435 |
return consumer, token, parameters |
327 |
436 |
|
328 |
# authorize a request token |
|
329 |
437 |
def authorize_token(self, token, user): |
438 |
"""Authorize a request token.""" |
|
330 |
439 |
return self.data_store.authorize_request_token(token, user) |
331 |
||
332 |
# get the callback url |
|
440 |
||
333 |
441 |
def get_callback(self, oauth_request): |
442 |
"""Get the callback URL.""" |
|
334 |
443 |
return oauth_request.get_parameter('oauth_callback') |
335 |
||
336 |
# optional support for the authenticate header |
|
444 |
||
337 |
445 |
def build_authenticate_header(self, realm=''): |
446 |
"""Optional support for the authenticate header.""" |
|
338 |
447 |
return {'WWW-Authenticate': 'OAuth realm="%s"' % realm} |
339 |
448 |
|
340 |
# verify the correct version request for this server |
|
341 |
449 |
def _get_version(self, oauth_request): |
450 |
"""Verify the correct version request for this server.""" |
|
342 |
451 |
try: |
343 |
452 |
version = oauth_request.get_parameter('oauth_version') |
344 |
453 |
except: |
| … | … | @@ -347,37 +456,40 @@ class OAuthServer(object): |
347 |
456 |
raise OAuthError('OAuth version %s not supported.' % str(version)) |
348 |
457 |
return version |
349 |
458 |
|
350 |
# figure out the signature with some defaults |
|
351 |
459 |
def _get_signature_method(self, oauth_request): |
460 |
"""Figure out the signature with some defaults.""" |
|
352 |
461 |
try: |
353 |
signature_method = oauth_request.get_parameter( |
|
462 |
signature_method = oauth_request.get_parameter( |
|
463 |
'oauth_signature_method') |
|
354 |
464 |
except: |
355 |
465 |
signature_method = SIGNATURE_METHOD |
356 |
466 |
try: |
357 |
# |
|
467 |
# Get the signature method object. |
|
358 |
468 |
signature_method = self.signature_methods[signature_method] |
359 |
469 |
except: |
360 |
470 |
signature_method_names = ', '.join(self.signature_methods.keys()) |
361 |
raise OAuthError('Signature method %s not supported try one of the |
|
471 |
raise OAuthError('Signature method %s not supported try one of the ' |
|
472 |
'following: %s' % (signature_method, signature_method_names)) |
|
362 |
473 |
|
363 |
474 |
return signature_method |
364 |
475 |
|
365 |
476 |
def _get_consumer(self, oauth_request): |
366 |
477 |
consumer_key = oauth_request.get_parameter('oauth_consumer_key') |
367 |
if not consumer_key: |
|
368 |
raise OAuthError('Invalid consumer key.') |
|
369 |
478 |
consumer = self.data_store.lookup_consumer(consumer_key) |
370 |
479 |
if not consumer: |
371 |
480 |
raise OAuthError('Invalid consumer.') |
372 |
481 |
return consumer |
373 |
482 |
|
374 |
# try to find the token for the provided request token key |
|
375 |
483 |
def _get_token(self, oauth_request, token_type='access'): |
484 |
"""Try to find the token for the provided request token key.""" |
|
376 |
485 |
token_field = oauth_request.get_parameter('oauth_token') |
377 |
486 |
token = self.data_store.lookup_token(token_type, token_field) |
378 |
487 |
if not token: |
379 |
488 |
raise OAuthError('Invalid %s token: %s' % (token_type, token_field)) |
380 |
489 |
return token |
490 |
||
491 |
def _get_verifier(self, oauth_request): |
|
492 |
return oauth_request.get_parameter('oauth_verifier') |
|
381 |
493 |
|
382 |
494 |
def _check_signature(self, oauth_request, consumer, token): |
383 |
495 |
timestamp, nonce = oauth_request._get_timestamp_nonce() |
| … | … | @@ -388,29 +500,35 @@ class OAuthServer(object): |
388 |
500 |
signature = oauth_request.get_parameter('oauth_signature') |
389 |
501 |
except: |
390 |
502 |
raise OAuthError('Missing signature.') |
391 |
# validate the signature |
|
392 |
valid_sig = signature_method.check_signature(oauth_request, consumer, token, signature) |
|
503 |
# Validate the signature. |
|
504 |
valid_sig = signature_method.check_signature(oauth_request, consumer, |
|
505 |
token, signature) |
|
393 |
506 |
if not valid_sig: |
394 |
key, base = signature_method.build_signature_base_string(oauth_request, consumer, token) |
|
395 |
raise OAuthError('Invalid signature. Expected signature base string: %s' % base) |
|
507 |
key, base = signature_method.build_signature_base_string( |
|
508 |
oauth_request, consumer, token) |
|
509 |
raise OAuthError('Invalid signature. Expected signature base ' |
|
510 |
'string: %s' % base) |
|
396 |
511 |
built = signature_method.build_signature(oauth_request, consumer, token) |
397 |
512 |
|
398 |
513 |
def _check_timestamp(self, timestamp): |
399 |
|
|
514 |
"""Verify that timestamp is recentish.""" |
|
400 |
515 |
timestamp = int(timestamp) |
401 |
516 |
now = int(time.time()) |
402 |
517 |
lapsed = now - timestamp |
403 |
518 |
if lapsed > self.timestamp_threshold: |
404 |
raise OAuthError('Expired timestamp: given %d and now %s has a |
|
519 |
raise OAuthError('Expired timestamp: given %d and now %s has a ' |
|
520 |
'greater difference than threshold %d' % |
|
521 |
(timestamp, now, self.timestamp_threshold)) |
|
405 |
522 |
|
406 |
523 |
def _check_nonce(self, consumer, token, nonce): |
407 |
|
|
524 |
"""Verify that the nonce is uniqueish.""" |
|
408 |
525 |
nonce = self.data_store.lookup_nonce(consumer, token, nonce) |
409 |
526 |
if nonce: |
410 |
527 |
raise OAuthError('Nonce already used: %s' % str(nonce)) |
411 |
528 |
|
412 |
# OAuthClient is a worker to attempt to execute a request |
|
529 |
||
413 |
530 |
class OAuthClient(object): |
531 |
"""OAuthClient is a worker to attempt to execute a request.""" |
|
414 |
532 |
consumer = None |
415 |
533 |
token = None |
416 |
534 |
|
| … | … | @@ -425,62 +543,65 @@ class OAuthClient(object): |
425 |
543 |
return self.token |
426 |
544 |
|
427 |
545 |
def fetch_request_token(self, oauth_request): |
428 |
|
|
546 |
"""-> OAuthToken.""" |
|
429 |
547 |
raise NotImplementedError |
430 |
548 |
|
431 |
549 |
def fetch_access_token(self, oauth_request): |
432 |
|
|
550 |
"""-> OAuthToken.""" |
|
433 |
551 |
raise NotImplementedError |
434 |
552 |
|
435 |
553 |
def access_resource(self, oauth_request): |
436 |
|
|
554 |
"""-> Some protected resource.""" |
|
437 |
555 |
raise NotImplementedError |
438 |
556 |
|
439 |
# OAuthDataStore is a database abstraction used to lookup consumers and tokens |
|
557 |
||
440 |
558 |
class OAuthDataStore(object): |
559 |
"""A database abstraction used to lookup consumers and tokens.""" |
|
441 |
560 |
|
442 |
561 |
def lookup_consumer(self, key): |
443 |
|
|
562 |
"""-> OAuthConsumer.""" |
|
444 |
563 |
raise NotImplementedError |
445 |
564 |
|
446 |
565 |
def lookup_token(self, oauth_consumer, token_type, token_token): |
447 |
|
|
566 |
"""-> OAuthToken.""" |
|
448 |
567 |
raise NotImplementedError |
449 |
568 |
|
450 |
def lookup_nonce(self, oauth_consumer, oauth_token, nonce, timestamp): |
|
451 |
# -> OAuthToken |
|
569 |
def lookup_nonce(self, oauth_consumer, oauth_token, nonce): |
|
570 |
"""-> OAuthToken.""" |
|
452 |
571 |
raise NotImplementedError |
453 |
572 |
|
454 |
def fetch_request_token(self, oauth_consumer): |
|
455 |
# -> OAuthToken |
|
573 |
def fetch_request_token(self, oauth_consumer, oauth_callback): |
|
574 |
"""-> OAuthToken.""" |
|
456 |
575 |
raise NotImplementedError |
457 |
576 |
|
458 |
def fetch_access_token(self, oauth_consumer, oauth_token): |
|
459 |
# -> OAuthToken |
|
577 |
def fetch_access_token(self, oauth_consumer, oauth_token, oauth_verifier): |
|
578 |
"""-> OAuthToken.""" |
|
460 |
579 |
raise NotImplementedError |
461 |
580 |
|
462 |
581 |
def authorize_request_token(self, oauth_token, user): |
463 |
|
|
582 |
"""-> OAuthToken.""" |
|
464 |
583 |
raise NotImplementedError |
465 |
584 |
|
466 |
# OAuthSignatureMethod is a strategy class that implements a signature method |
|
585 |
||
467 |
586 |
class OAuthSignatureMethod(object): |
587 |
"""A strategy class that implements a signature method.""" |
|
468 |
588 |
def get_name(self): |
469 |
|
|
589 |
"""-> str.""" |
|
470 |
590 |
raise NotImplementedError |
471 |
591 |
|
472 |
592 |
def build_signature_base_string(self, oauth_request, oauth_consumer, oauth_token): |
473 |
|
|
593 |
"""-> str key, str raw.""" |
|
474 |
594 |
raise NotImplementedError |
475 |
595 |
|
476 |
596 |
def build_signature(self, oauth_request, oauth_consumer, oauth_token): |
477 |
|
|
597 |
"""-> str.""" |
|
478 |
598 |
raise NotImplementedError |
479 |
599 |
|
480 |
600 |
def check_signature(self, oauth_request, consumer, token, signature): |
481 |
601 |
built = self.build_signature(oauth_request, consumer, token) |
482 |
602 |
return built == signature |
483 |
603 |
|
604 |
||
484 |
605 |
class OAuthSignatureMethod_HMAC_SHA1(OAuthSignatureMethod): |
485 |
606 |
|
486 |
607 |
def get_name(self): |
| … | … | @@ -500,32 +621,35 @@ class OAuthSignatureMethod_HMAC_SHA1(OAu |
500 |
621 |
return key, raw |
501 |
622 |
|
502 |
623 |
def build_signature(self, oauth_request, consumer, token): |
503 |
# build the base signature string |
|
504 |
key, raw = self.build_signature_base_string(oauth_request, consumer, token) |
|
624 |
"""Builds the base signature string.""" |
|
625 |
key, raw = self.build_signature_base_string(oauth_request, consumer, |
|
626 |
token) |
|
505 |
627 |
|
506 |
# |
|
628 |
# HMAC object. |
|
507 |
629 |
try: |
508 |
630 |
import hashlib # 2.5 |
509 |
631 |
hashed = hmac.new(key, raw, hashlib.sha1) |
510 |
632 |
except: |
511 |
import sha # |
|
633 |
import sha # Deprecated |
|
512 |
634 |
hashed = hmac.new(key, raw, sha) |
513 |
635 |
|
514 |
# |
|
636 |
# Calculate the digest base 64. |
|
515 |
637 |
return binascii.b2a_base64(hashed.digest())[:-1] |
516 |
638 |
|
639 |
||
517 |
640 |
class OAuthSignatureMethod_PLAINTEXT(OAuthSignatureMethod): |
518 |
641 |
|
519 |
642 |
def get_name(self): |
520 |
643 |
return 'PLAINTEXT' |
521 |
644 |
|
522 |
645 |
def build_signature_base_string(self, oauth_request, consumer, token): |
523 |
|
|
646 |
"""Concatenates the consumer key and secret.""" |
|
524 |
647 |
sig = '%s&' % escape(consumer.secret) |
525 |
648 |
if token: |
526 |
649 |
sig = sig + escape(token.secret) |
527 |
650 |
return sig, sig |
528 |
651 |
|
529 |
652 |
def build_signature(self, oauth_request, consumer, token): |
530 |
key, raw = self.build_signature_base_string(oauth_request, consumer, |
|
653 |
key, raw = self.build_signature_base_string(oauth_request, consumer, |
|
654 |
token) |
|
531 |
655 |
return key |
Up to file-list oauth_provider/stores.py:
| … | … | @@ -40,7 +40,7 @@ class DataStore(OAuthDataStore): |
40 |
40 |
else: |
41 |
41 |
return nonce.key |
42 |
42 |
|
43 |
def fetch_request_token(self, oauth_consumer |
|
43 |
def fetch_request_token(self, oauth_consumer, oauth_callback): |
|
44 |
44 |
if oauth_consumer.key == self.consumer.key: |
45 |
45 |
try: |
46 |
46 |
resource = Resource.objects.get(name=self.scope) |
| … | … | @@ -53,7 +53,7 @@ class DataStore(OAuthDataStore): |
53 |
53 |
return self.request_token |
54 |
54 |
raise OAuthError('Consumer key does not match.') |
55 |
55 |
|
56 |
def fetch_access_token(self, oauth_consumer, oauth_token |
|
56 |
def fetch_access_token(self, oauth_consumer, oauth_token, oauth_verifier): |
|
57 |
57 |
if oauth_consumer.key == self.consumer.key \ |
58 |
58 |
and oauth_token.key == self.request_token.key \ |
59 |
59 |
and self.request_token.is_approved: |
