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.

Clone this repository (size: 114.7 KB): HTTPS / SSH
$ hg clone http://code.welldev.org/django-oauth
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.
David Larlet / david
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)

Up to file-list .hgignore:

@@ -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
    # escape '/' too
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
    # access tokens and request tokens
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
    # @staticmethod   
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
        return OAuthToken(key, secret)
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('oauth_nonce')
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
            # ignore oauth parameters
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
        # add the oauth parameters
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
        return '&'.join(['%s=%s' % (escape(str(k)), escape(str(v))) for k, v in self.parameters.iteritems()])
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
            # exclude the signature if it exists
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
        # call the build signature method within the signature method
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
        # combine multiple parameter sources
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
        # headers
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
                    # get the parameters from the header
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 Authorization header.')
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
            # remove whitespace
352
            # Remove whitespace.
253
353
            param = param.strip()
254
            # split key-value
354
            # Split key-value.
255
355
            param_parts = param.split('=', 1)
256
            # remove quotes and unescape the value
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
    timestamp_threshold = 300 # in seconds, five minutes
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
            # get the request token for authorization
395
            # Get the request token for authorization.
296
396
            token = self._get_token(oauth_request, 'request')
297
397
        except OAuthError:
298
            # no token required for the initial token request
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
        # get the request token
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
        # get the access token
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('oauth_signature_method')
462
            signature_method = oauth_request.get_parameter(
463
                'oauth_signature_method')
354
464
        except:
355
465
            signature_method = SIGNATURE_METHOD
356
466
        try:
357
            # get the signature method object
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 following: %s' % (signature_method, signature_method_names))
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
        # verify that timestamp is recentish
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 greater difference than threshold %d' % (timestamp, now, self.timestamp_threshold))
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
        # verify that the nonce is uniqueish
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
        # -> OAuthToken
546
        """-> OAuthToken."""
429
547
        raise NotImplementedError
430
548
431
549
    def fetch_access_token(self, oauth_request):
432
        # -> OAuthToken
550
        """-> OAuthToken."""
433
551
        raise NotImplementedError
434
552
435
553
    def access_resource(self, oauth_request):
436
        # -> some protected resource
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
        # -> OAuthConsumer
562
        """-> OAuthConsumer."""
444
563
        raise NotImplementedError
445
564
446
565
    def lookup_token(self, oauth_consumer, token_type, token_token):
447
        # -> OAuthToken
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
        # -> OAuthToken
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
        # -> str
589
        """-> str."""
470
590
        raise NotImplementedError
471
591
472
592
    def build_signature_base_string(self, oauth_request, oauth_consumer, oauth_token):
473
        # -> str key, str raw
593
        """-> str key, str raw."""
474
594
        raise NotImplementedError
475
595
476
596
    def build_signature(self, oauth_request, oauth_consumer, oauth_token):
477
        # -> str
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
        # hmac object
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 # deprecated
633
            import sha # Deprecated
512
634
            hashed = hmac.new(key, raw, sha)
513
635
514
        # calculate the digest base 64
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
        # concatenate the consumer key and secret
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, token)
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: