Merge lp:~mbp/launchpad/701545-oauth into lp:launchpad

Proposed by Martin Pool
Status: Rejected
Rejected by: Martin Pool
Proposed branch: lp:~mbp/launchpad/701545-oauth
Merge into: lp:launchpad
Diff against target: 624 lines (+9/-537)
6 files modified
lib/canonical/launchpad/webapp/authentication.py (+2/-2)
lib/canonical/launchpad/webapp/tests/test_authentication.py (+5/-2)
lib/canonical/launchpad/webapp/tests/test_publication.py (+2/-2)
lib/contrib/oauth.py (+0/-529)
setup.py (+0/-1)
versions.cfg (+0/-1)
To merge this branch: bzr merge lp:~mbp/launchpad/701545-oauth
Reviewer Review Type Date Requested Status
Curtis Hovey (community) code Approve
j.c.sackett code* Pending
Review via email: mp+49541@code.launchpad.net

This proposal supersedes a proposal from 2011-02-03.

Description of the change

Remove redundant copy of oauth.py.

Launchpad contains a copy of oauth.py, which is a bit out of date. It also has a proper copy of the python-oauth module in lp-sourcedeps.

This was previously attempted in <https://code.launchpad.net/~mbp/launchpad/701545-oauth/+merge/48431> but it failed because the same bug 314507 is in our contrib/oauth.py and in the egg too. The bug has been fixed in Ubuntu's oauth.py since karmic so this in conjunction with <https://code.edge.launchpad.net/~mbp/meta-lp-deps/701545-add-dependency/+merge/49536> will get to just using the packaged version.

This can't/shouldn't land until the meta-lp-deps landing has been pushed all the way through.

The actual patch is similar to what was previously submitted with the addition of removing the setuptools dependency

To post a comment you must log in.
Revision history for this message
j.c.sackett (jcsackett) wrote : Posted in a previous version of this proposal

Martin--

This looks good. Thanks!

review: Approve (code*)
Revision history for this message
Curtis Hovey (sinzui) wrote : Posted in a previous version of this proposal

Hi Martin.

Thank you for cleaning up the code. We normally write copyrights as a range. Are you aware of utilities/update-copyright? It will update the copyright in all the modified files in your tree. I think this serial date style breaks the update-copyright script, subsequent updates will require the engineer to edit the copyright afterwards. You can either change the dates to ranges or verify that the script will work on these files in 2012.

review: Approve (code)
Revision history for this message
Francis J. Lacoste (flacoste) wrote : Posted in a previous version of this proposal

On February 3, 2011, Martin Pool wrote:
> If this is acceptable would someone please sponsor landing of it?

All Canonical Bazaar Developers can land branches directly through PQM
nowawayds :-)

Revision history for this message
Martin Pool (mbp) wrote : Posted in a previous version of this proposal

Thanks, Curtis. I was not aware of that. I will send this to pqm.

Revision history for this message
Martin Pool (mbp) wrote : Posted in a previous version of this proposal

On 4 February 2011 03:52, Francis J. Lacoste <email address hidden> wrote:
> On February 3, 2011, Martin Pool wrote:
>> If this is acceptable would someone please sponsor landing of it?
>
> All Canonical Bazaar Developers can land branches directly through PQM
> nowawayds :-)

No, I still get a gpgv error from pqm.

Revision history for this message
Martin Pool (mbp) wrote : Posted in a previous version of this proposal

spm helped me sort this out and it's in the queue now.

- Martin
On 04/02/2011 11:35 AM, "Martin Pool" <email address hidden> wrote:
> On 4 February 2011 03:52, Francis J. Lacoste <email address hidden>
wrote:
>> On February 3, 2011, Martin Pool wrote:
>>> If this is acceptable would someone please sponsor landing of it?
>>
>> All Canonical Bazaar Developers can land branches directly through PQM
>> nowawayds :-)
>
> No, I still get a gpgv error from pqm.

lp:~mbp/launchpad/701545-oauth updated
12370. By Martin Pool

Also remove oauth from versions.cfg

Revision history for this message
Curtis Hovey (sinzui) wrote :

This is good to land once the deps are in place.

review: Approve (code)
Revision history for this message
Martin Pool (mbp) wrote :

Per my comments on bug 701545, getting the dependencies to work is too hard to be worthwhile.

Unmerged revisions

12370. By Martin Pool

Also remove oauth from versions.cfg

12369. By Martin Pool

Use external python-oauth and remove contrib/oauth.py

12368. By Martin Pool

Remove dependency on oauth egg

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'lib/canonical/launchpad/webapp/authentication.py'
--- lib/canonical/launchpad/webapp/authentication.py 2011-02-04 14:41:18 +0000
+++ lib/canonical/launchpad/webapp/authentication.py 2011-02-13 12:17:26 +0000
@@ -1,4 +1,4 @@
1# Copyright 2009 Canonical Ltd. This software is licensed under the1# Copyright 2009-2011 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).2# GNU Affero General Public License version 3 (see the file LICENSE).
33
4__metaclass__ = type4__metaclass__ = type
@@ -18,7 +18,7 @@
18import random18import random
19from UserDict import UserDict19from UserDict import UserDict
2020
21from contrib.oauth import OAuthRequest21from oauth.oauth import OAuthRequest
22from zope.annotation.interfaces import IAnnotations22from zope.annotation.interfaces import IAnnotations
23from zope.app.security.interfaces import ILoginPassword23from zope.app.security.interfaces import ILoginPassword
24from zope.app.security.principalregistry import UnauthenticatedPrincipal24from zope.app.security.principalregistry import UnauthenticatedPrincipal
2525
=== modified file 'lib/canonical/launchpad/webapp/tests/test_authentication.py'
--- lib/canonical/launchpad/webapp/tests/test_authentication.py 2011-02-04 14:41:18 +0000
+++ lib/canonical/launchpad/webapp/tests/test_authentication.py 2011-02-13 12:17:26 +0000
@@ -1,4 +1,4 @@
1# Copyright 2009 Canonical Ltd. This software is licensed under the1# Copyright 2009-2011 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).2# GNU Affero General Public License version 3 (see the file LICENSE).
33
4"""Tests authentication.py"""4"""Tests authentication.py"""
@@ -10,7 +10,7 @@
1010
11from zope.app.security.principalregistry import UnauthenticatedPrincipal11from zope.app.security.principalregistry import UnauthenticatedPrincipal
1212
13from contrib.oauth import OAuthRequest13from oauth.oauth import OAuthRequest
1414
15from canonical.config import config15from canonical.config import config
16from canonical.launchpad.ftests import login16from canonical.launchpad.ftests import login
@@ -67,6 +67,9 @@
67 #67 #
68 # Note that the 'realm' parameter is not returned, because it's not68 # Note that the 'realm' parameter is not returned, because it's not
69 # included in the OAuth calculations.69 # included in the OAuth calculations.
70 #
71 # Now we use the separate oauth module (bug 701545), and
72 # this has been fixed upstream, but we might as well keep the test.
70 headers = OAuthRequest._split_header(73 headers = OAuthRequest._split_header(
71 'OAuth realm="foo", oauth_consumer_key="justtesting"')74 'OAuth realm="foo", oauth_consumer_key="justtesting"')
72 self.assertEquals(headers,75 self.assertEquals(headers,
7376
=== modified file 'lib/canonical/launchpad/webapp/tests/test_publication.py'
--- lib/canonical/launchpad/webapp/tests/test_publication.py 2011-02-04 14:41:18 +0000
+++ lib/canonical/launchpad/webapp/tests/test_publication.py 2011-02-13 12:17:26 +0000
@@ -1,4 +1,4 @@
1# Copyright 2009 Canonical Ltd. This software is licensed under the1# Copyright 2009-2011 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).2# GNU Affero General Public License version 3 (see the file LICENSE).
33
4"""Tests publication.py"""4"""Tests publication.py"""
@@ -9,7 +9,7 @@
9import sys9import sys
10import unittest10import unittest
1111
12from contrib.oauth import (12from oauth.oauth import (
13 OAuthRequest,13 OAuthRequest,
14 OAuthSignatureMethod_PLAINTEXT,14 OAuthSignatureMethod_PLAINTEXT,
15 )15 )
1616
=== removed file 'lib/contrib/oauth.py'
--- lib/contrib/oauth.py 2011-02-04 14:41:18 +0000
+++ lib/contrib/oauth.py 1970-01-01 00:00:00 +0000
@@ -1,529 +0,0 @@
1# pylint: disable-msg=C0301,E0602,E0211,E0213,W0105,W0231,W0702
2
3import cgi
4import urllib
5import time
6import random
7import urlparse
8import hmac
9import base64
10
11VERSION = '1.0' # Hi Blaine!
12HTTP_METHOD = 'GET'
13SIGNATURE_METHOD = 'PLAINTEXT'
14
15# Generic exception class
16class OAuthError(RuntimeError):
17 def __init__(self, message='OAuth error occured'):
18 self.message = message
19
20# optional WWW-Authenticate header (401 error)
21def build_authenticate_header(realm=''):
22 return {'WWW-Authenticate': 'OAuth realm="%s"' % realm}
23
24# url escape
25def escape(s):
26 # escape '/' too
27 return urllib.quote(s, safe='~')
28
29# util function: current timestamp
30# seconds since epoch (UTC)
31def generate_timestamp():
32 return int(time.time())
33
34# util function: nonce
35# pseudorandom number
36def generate_nonce(length=8):
37 return ''.join(str(random.randint(0, 9)) for i in range(length))
38
39# OAuthConsumer is a data type that represents the identity of the Consumer
40# via its shared secret with the Service Provider.
41class OAuthConsumer(object):
42 key = None
43 secret = None
44
45 def __init__(self, key, secret):
46 self.key = key
47 self.secret = secret
48
49# OAuthToken is a data type that represents an End User via either an access
50# or request token.
51class OAuthToken(object):
52 # access tokens and request tokens
53 key = None
54 secret = None
55
56 '''
57 key = the token
58 secret = the token secret
59 '''
60 def __init__(self, key, secret):
61 self.key = key
62 self.secret = secret
63
64 def to_string(self):
65 return urllib.urlencode({'oauth_token': self.key, 'oauth_token_secret': self.secret})
66
67 # return a token from something like:
68 # oauth_token_secret=digg&oauth_token=digg
69 @staticmethod
70 def from_string(s):
71 params = cgi.parse_qs(s, keep_blank_values=False)
72 key = params['oauth_token'][0]
73 secret = params['oauth_token_secret'][0]
74 return OAuthToken(key, secret)
75
76 def __str__(self):
77 return self.to_string()
78
79# OAuthRequest represents the request and can be serialized
80class OAuthRequest(object):
81 '''
82 OAuth parameters:
83 - oauth_consumer_key
84 - oauth_token
85 - oauth_signature_method
86 - oauth_signature
87 - oauth_timestamp
88 - oauth_nonce
89 - oauth_version
90 ... any additional parameters, as defined by the Service Provider.
91 '''
92 parameters = None # oauth parameters
93 http_method = HTTP_METHOD
94 http_url = None
95 version = VERSION
96
97 def __init__(self, http_method=HTTP_METHOD, http_url=None, parameters=None):
98 self.http_method = http_method
99 self.http_url = http_url
100 self.parameters = parameters or {}
101
102 def set_parameter(self, parameter, value):
103 self.parameters[parameter] = value
104
105 def get_parameter(self, parameter):
106 try:
107 return self.parameters[parameter]
108 except:
109 raise OAuthError('Parameter not found: %s' % parameter)
110
111 def _get_timestamp_nonce(self):
112 return self.get_parameter('oauth_timestamp'), self.get_parameter('oauth_nonce')
113
114 # get any non-oauth parameters
115 def get_nonoauth_parameters(self):
116 parameters = {}
117 for k, v in self.parameters.iteritems():
118 # ignore oauth parameters
119 if k.find('oauth_') < 0:
120 parameters[k] = v
121 return parameters
122
123 # serialize as a header for an HTTPAuth request
124 def to_header(self, realm=''):
125 auth_header = 'OAuth realm="%s"' % realm
126 # add the oauth parameters
127 if self.parameters:
128 for k, v in self.parameters.iteritems():
129 auth_header += ', %s="%s"' % (k, v)
130 return {'Authorization': auth_header}
131
132 # serialize as post data for a POST request
133 def to_postdata(self):
134 return '&'.join('%s=%s' % (escape(str(k)), escape(str(v))) for k, v in self.parameters.iteritems())
135
136 # serialize as a url for a GET request
137 def to_url(self):
138 return '%s?%s' % (self.get_normalized_http_url(), self.to_postdata())
139
140 # return a string that consists of all the parameters that need to be signed
141 def get_normalized_parameters(self):
142 params = self.parameters
143 try:
144 # exclude the signature if it exists
145 del params['oauth_signature']
146 except:
147 pass
148 key_values = params.items()
149 # sort lexicographically, first after key, then after value
150 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)
153
154 # just uppercases the http method
155 def get_normalized_http_method(self):
156 return self.http_method.upper()
157
158 # parses the url and rebuilds it to be scheme://host/path
159 def get_normalized_http_url(self):
160 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
165 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))
170
171 def build_signature(self, signature_method, consumer, token):
172 # call the build signature method within the signature method
173 return signature_method.build_signature(self, consumer, token)
174
175 @staticmethod
176 def from_request(http_method, http_url, headers=None, postdata=None, parameters=None):
177
178 # let the library user override things however they'd like, if they know
179 # which parameters to use then go for it, for example XMLRPC might want to
180 # do this
181 if parameters is not None:
182 return OAuthRequest(http_method, http_url, parameters)
183
184 # from the headers
185 if headers is not None:
186 try:
187 auth_header = headers['Authorization']
188 # check that the authorization header is OAuth
189 auth_header.index('OAuth')
190 # get the parameters from the header
191 parameters = OAuthRequest._split_header(auth_header)
192 return OAuthRequest(http_method, http_url, parameters)
193 except:
194 raise OAuthError('Unable to parse OAuth parameters from Authorization header.')
195
196 # from the parameter string (post body)
197 if http_method == 'POST' and postdata is not None:
198 parameters = OAuthRequest._split_url_string(postdata)
199
200 # from the url string
201 elif http_method == 'GET':
202 param_str = urlparse.urlparse(http_url).query
203 parameters = OAuthRequest._split_url_string(param_str)
204
205 if parameters:
206 return OAuthRequest(http_method, http_url, parameters)
207
208 raise OAuthError('Missing all OAuth parameters. OAuth parameters must be in the headers, post body, or url.')
209
210 @staticmethod
211 def from_consumer_and_token(oauth_consumer, token=None, http_method=HTTP_METHOD, http_url=None, parameters=None):
212 if not parameters:
213 parameters = {}
214
215 defaults = {
216 'oauth_consumer_key': oauth_consumer.key,
217 'oauth_timestamp': generate_timestamp(),
218 'oauth_nonce': generate_nonce(),
219 'oauth_version': OAuthRequest.version,
220 }
221
222 defaults.update(parameters)
223 parameters = defaults
224
225 if token:
226 parameters['oauth_token'] = token.key
227
228 return OAuthRequest(http_method, http_url, parameters)
229
230 @staticmethod
231 def from_token_and_callback(token, callback=None, http_method=HTTP_METHOD, http_url=None, parameters=None):
232 if not parameters:
233 parameters = {}
234
235 parameters['oauth_token'] = token.key
236
237 if callback:
238 parameters['oauth_callback'] = escape(callback)
239
240 return OAuthRequest(http_method, http_url, parameters)
241
242 # util function: turn Authorization: header into parameters, has to do some unescaping
243 @staticmethod
244 def _split_header(header):
245 params = {}
246 header = header.lstrip()
247 if not header.startswith('OAuth '):
248 raise ValueError("not an OAuth header: %r" % header)
249 header = header[6:]
250 parts = header.split(',')
251 for param in parts:
252 # remove whitespace
253 param = param.strip()
254 # split key-value
255 param_parts = param.split('=', 1)
256 if param_parts[0] == 'realm':
257 # Realm header is not an OAuth parameter according to rfc5849
258 # section 3.4.1.3.1.
259 continue
260 # remove quotes and unescape the value
261 params[param_parts[0]] = urllib.unquote(param_parts[1].strip('\"'))
262 return params
263
264 # util function: turn url string into parameters, has to do some unescaping
265 @staticmethod
266 def _split_url_string(param_str):
267 parameters = cgi.parse_qs(param_str, keep_blank_values=False)
268 for k, v in parameters.iteritems():
269 parameters[k] = urllib.unquote(v[0])
270 return parameters
271
272# OAuthServer is a worker to check a requests validity against a data store
273class OAuthServer(object):
274 timestamp_threshold = 300 # in seconds, five minutes
275 version = VERSION
276 signature_methods = None
277 data_store = None
278
279 def __init__(self, data_store=None, signature_methods=None):
280 self.data_store = data_store
281 self.signature_methods = signature_methods or {}
282
283 def set_data_store(self, oauth_data_store):
284 self.data_store = oauth_data_store
285
286 def get_data_store(self):
287 return self.data_store
288
289 def add_signature_method(self, signature_method):
290 self.signature_methods[signature_method.get_name()] = signature_method
291 return self.signature_methods
292
293 # process a request_token request
294 # returns the request token on success
295 def fetch_request_token(self, oauth_request):
296 try:
297 # get the request token for authorization
298 token = self._get_token(oauth_request, 'request')
299 except:
300 # no token required for the initial token request
301 version = self._get_version(oauth_request)
302 consumer = self._get_consumer(oauth_request)
303 self._check_signature(oauth_request, consumer, None)
304 # fetch a new token
305 token = self.data_store.fetch_request_token(consumer)
306 return token
307
308 # process an access_token request
309 # returns the access token on success
310 def fetch_access_token(self, oauth_request):
311 version = self._get_version(oauth_request)
312 consumer = self._get_consumer(oauth_request)
313 # get the request token
314 token = self._get_token(oauth_request, 'request')
315 self._check_signature(oauth_request, consumer, token)
316 new_token = self.data_store.fetch_access_token(consumer, token)
317 return new_token
318
319 # verify an api call, checks all the parameters
320 def verify_request(self, oauth_request):
321 # -> consumer and token
322 version = self._get_version(oauth_request)
323 consumer = self._get_consumer(oauth_request)
324 # get the access token
325 token = self._get_token(oauth_request, 'access')
326 self._check_signature(oauth_request, consumer, token)
327 parameters = oauth_request.get_nonoauth_parameters()
328 return consumer, token, parameters
329
330 # authorize a request token
331 def authorize_token(self, token, user):
332 return self.data_store.authorize_request_token(token, user)
333
334 # get the callback url
335 def get_callback(self, oauth_request):
336 return oauth_request.get_parameter('oauth_callback')
337
338 # optional support for the authenticate header
339 def build_authenticate_header(self, realm=''):
340 return {'WWW-Authenticate': 'OAuth realm="%s"' % realm}
341
342 # verify the correct version request for this server
343 def _get_version(self, oauth_request):
344 try:
345 version = oauth_request.get_parameter('oauth_version')
346 except:
347 version = VERSION
348 if version and version != self.version:
349 raise OAuthError('OAuth version %s not supported' % str(version))
350 return version
351
352 # figure out the signature with some defaults
353 def _get_signature_method(self, oauth_request):
354 try:
355 signature_method = oauth_request.get_parameter('oauth_signature_method')
356 except:
357 signature_method = SIGNATURE_METHOD
358 try:
359 # get the signature method object
360 signature_method = self.signature_methods[signature_method]
361 except:
362 signature_method_names = ', '.join(self.signature_methods.keys())
363 raise OAuthError('Signature method %s not supported try one of the following: %s' % (signature_method, signature_method_names))
364
365 return signature_method
366
367 def _get_consumer(self, oauth_request):
368 consumer_key = oauth_request.get_parameter('oauth_consumer_key')
369 if not consumer_key:
370 raise OAuthError('Invalid consumer key')
371 consumer = self.data_store.lookup_consumer(consumer_key)
372 if not consumer:
373 raise OAuthError('Invalid consumer')
374 return consumer
375
376 # try to find the token for the provided request token key
377 def _get_token(self, oauth_request, token_type='access'):
378 token_field = oauth_request.get_parameter('oauth_token')
379 token = self.data_store.lookup_token(token_type, token_field)
380 if not token:
381 raise OAuthError('Invalid %s token: %s' % (token_type, token_field))
382 return token
383
384 def _check_signature(self, oauth_request, consumer, token):
385 timestamp, nonce = oauth_request._get_timestamp_nonce()
386 self._check_timestamp(timestamp)
387 self._check_nonce(consumer, token, nonce)
388 signature_method = self._get_signature_method(oauth_request)
389 try:
390 signature = oauth_request.get_parameter('oauth_signature')
391 except:
392 raise OAuthError('Missing signature')
393 # attempt to construct the same signature
394 built = signature_method.build_signature(oauth_request, consumer, token)
395 if signature != built:
396 key, base = signature_method.build_signature_base_string(oauth_request, consumer, token)
397 raise OAuthError('Signature does not match. Expected: %s Got: %s Expected signature base string: %s' % (built, signature, base))
398
399 def _check_timestamp(self, timestamp):
400 # verify that timestamp is recentish
401 timestamp = int(timestamp)
402 now = int(time.time())
403 lapsed = now - timestamp
404 if lapsed > self.timestamp_threshold:
405 raise OAuthError('Expired timestamp: given %d and now %s has a greater difference than threshold %d' % (timestamp, now, self.timestamp_threshold))
406
407 def _check_nonce(self, consumer, token, nonce):
408 # verify that the nonce is uniqueish
409 try:
410 self.data_store.lookup_nonce(consumer, token, nonce)
411 raise OAuthError('Nonce already used: %s' % str(nonce))
412 except:
413 pass
414
415# OAuthClient is a worker to attempt to execute a request
416class OAuthClient(object):
417 consumer = None
418 token = None
419
420 def __init__(self, oauth_consumer, oauth_token):
421 self.consumer = oauth_consumer
422 self.token = oauth_token
423
424 def get_consumer(self):
425 return self.consumer
426
427 def get_token(self):
428 return self.token
429
430 def fetch_request_token(self, oauth_request):
431 # -> OAuthToken
432 raise NotImplementedError
433
434 def fetch_access_token(self, oauth_request):
435 # -> OAuthToken
436 raise NotImplementedError
437
438 def access_resource(self, oauth_request):
439 # -> some protected resource
440 raise NotImplementedError
441
442# OAuthDataStore is a database abstraction used to lookup consumers and tokens
443class OAuthDataStore(object):
444
445 def lookup_consumer(self, key):
446 # -> OAuthConsumer
447 raise NotImplementedError
448
449 def lookup_token(self, oauth_consumer, token_type, token_token):
450 # -> OAuthToken
451 raise NotImplementedError
452
453 def lookup_nonce(self, oauth_consumer, oauth_token, nonce, timestamp):
454 # -> OAuthToken
455 raise NotImplementedError
456
457 def fetch_request_token(self, oauth_consumer):
458 # -> OAuthToken
459 raise NotImplementedError
460
461 def fetch_access_token(self, oauth_consumer, oauth_token):
462 # -> OAuthToken
463 raise NotImplementedError
464
465 def authorize_request_token(self, oauth_token, user):
466 # -> OAuthToken
467 raise NotImplementedError
468
469# OAuthSignatureMethod is a strategy class that implements a signature method
470class OAuthSignatureMethod(object):
471 def get_name():
472 # -> str
473 raise NotImplementedError
474
475 def build_signature_base_string(oauth_request, oauth_consumer, oauth_token):
476 # -> str key, str raw
477 raise NotImplementedError
478
479 def build_signature(oauth_request, oauth_consumer, oauth_token):
480 # -> str
481 raise NotImplementedError
482
483class OAuthSignatureMethod_HMAC_SHA1(OAuthSignatureMethod):
484
485 def get_name(self):
486 return 'HMAC-SHA1'
487
488 def build_signature_base_string(self, oauth_request, consumer, token):
489 sig = (
490 escape(oauth_request.get_normalized_http_method()),
491 escape(oauth_request.get_normalized_http_url()),
492 escape(oauth_request.get_normalized_parameters()),
493 )
494
495 key = '%s&' % escape(consumer.secret)
496 if token:
497 key += escape(token.secret)
498 raw = '&'.join(sig)
499 return key, raw
500
501 def build_signature(self, oauth_request, consumer, token):
502 # build the base signature string
503 key, raw = self.build_signature_base_string(oauth_request, consumer, token)
504
505 # hmac object
506 try:
507 import hashlib # 2.5
508 hashed = hmac.new(key, raw, hashlib.sha1)
509 except:
510 import sha # deprecated
511 hashed = hmac.new(key, raw, sha)
512
513 # calculate the digest base 64
514 return base64.b64encode(hashed.digest())
515
516class OAuthSignatureMethod_PLAINTEXT(OAuthSignatureMethod):
517
518 def get_name(self):
519 return 'PLAINTEXT'
520
521 def build_signature_base_string(self, oauth_request, consumer, token):
522 # concatenate the consumer key and secret
523 sig = escape(consumer.secret)
524 if token:
525 sig = '&'.join((sig, escape(token.secret)))
526 return sig
527
528 def build_signature(self, oauth_request, consumer, token):
529 return self.build_signature_base_string(oauth_request, consumer, token)
5300
=== modified file 'setup.py'
--- setup.py 2011-01-06 22:21:02 +0000
+++ setup.py 2011-02-13 12:17:26 +0000
@@ -56,7 +56,6 @@
56 'meliae',56 'meliae',
57 'mercurial',57 'mercurial',
58 'mocker',58 'mocker',
59 'oauth',
60 'paramiko',59 'paramiko',
61 'psycopg2',60 'psycopg2',
62 'python-memcached',61 'python-memcached',
6362
=== modified file 'versions.cfg'
--- versions.cfg 2011-02-09 15:25:57 +0000
+++ versions.cfg 2011-02-13 12:17:26 +0000
@@ -46,7 +46,6 @@
46mercurial = 1.6.246mercurial = 1.6.2
47mocker = 0.10.147mocker = 0.10.1
48mozrunner = 1.3.448mozrunner = 1.3.4
49oauth = 1.0
50paramiko = 1.7.449paramiko = 1.7.4
51Paste = 1.7.250Paste = 1.7.2
52PasteDeploy = 1.3.351PasteDeploy = 1.3.3