Merge ~cjwatson/launchpad:oauthlib into launchpad:master

Proposed by Colin Watson
Status: Merged
Approved by: Colin Watson
Approved revision: bc855e779a25db270488e87c029533772e2d95ed
Merge reported by: Otto Co-Pilot
Merged at revision: not available
Proposed branch: ~cjwatson/launchpad:oauthlib
Merge into: launchpad:master
Diff against target: 750 lines (+44/-587)
6 files modified
dev/null (+0/-536)
lib/lp/services/webapp/authentication.py (+12/-5)
lib/lp/services/webapp/tests/test_authentication.py (+10/-8)
lib/lp/services/webapp/tests/test_publication.py (+11/-16)
lib/lp/testing/pages.py (+10/-22)
setup.py (+1/-0)
Reviewer Review Type Date Requested Status
Tom Wardill (community) Approve
Review via email: mp+393828@code.launchpad.net

Commit message

Port from contrib.oauth to oauthlib

Description of the change

Let's prefer code that somebody else is maintaining.

To post a comment you must log in.
Revision history for this message
Tom Wardill (twom) wrote :

This is a good amount of red.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
diff --git a/lib/contrib/oauth.py b/lib/contrib/oauth.py
0deleted file mode 1006440deleted file mode 100644
index 2d11e6c..0000000
--- a/lib/contrib/oauth.py
+++ /dev/null
@@ -1,536 +0,0 @@
1import base64
2import hmac
3import random
4import time
5
6import six
7from six.moves.urllib.parse import (
8 parse_qs,
9 quote,
10 unquote,
11 urlencode,
12 urlparse,
13 )
14
15
16VERSION = '1.0' # Hi Blaine!
17HTTP_METHOD = 'GET'
18SIGNATURE_METHOD = 'PLAINTEXT'
19
20# Generic exception class
21class OAuthError(RuntimeError):
22 def __init__(self, message='OAuth error occured'):
23 self.message = message
24
25# optional WWW-Authenticate header (401 error)
26def build_authenticate_header(realm=''):
27 return {'WWW-Authenticate': 'OAuth realm="%s"' % realm}
28
29# url escape
30def escape(s):
31 # escape '/' too
32 return quote(s, safe='~')
33
34# util function: current timestamp
35# seconds since epoch (UTC)
36def generate_timestamp():
37 return int(time.time())
38
39# util function: nonce
40# pseudorandom number
41def generate_nonce(length=8):
42 return ''.join(str(random.randint(0, 9)) for i in range(length))
43
44# OAuthConsumer is a data type that represents the identity of the Consumer
45# via its shared secret with the Service Provider.
46class OAuthConsumer(object):
47 key = None
48 secret = None
49
50 def __init__(self, key, secret):
51 self.key = key
52 self.secret = secret
53
54# OAuthToken is a data type that represents an End User via either an access
55# or request token.
56class OAuthToken(object):
57 # access tokens and request tokens
58 key = None
59 secret = None
60
61 '''
62 key = the token
63 secret = the token secret
64 '''
65 def __init__(self, key, secret):
66 self.key = key
67 self.secret = secret
68
69 def to_string(self):
70 return urlencode({'oauth_token': self.key, 'oauth_token_secret': self.secret})
71
72 # return a token from something like:
73 # oauth_token_secret=digg&oauth_token=digg
74 @staticmethod
75 def from_string(s):
76 params = parse_qs(s, keep_blank_values=False)
77 key = params['oauth_token'][0]
78 secret = params['oauth_token_secret'][0]
79 return OAuthToken(key, secret)
80
81 def __str__(self):
82 return self.to_string()
83
84# OAuthRequest represents the request and can be serialized
85class OAuthRequest(object):
86 '''
87 OAuth parameters:
88 - oauth_consumer_key
89 - oauth_token
90 - oauth_signature_method
91 - oauth_signature
92 - oauth_timestamp
93 - oauth_nonce
94 - oauth_version
95 ... any additional parameters, as defined by the Service Provider.
96 '''
97 parameters = None # oauth parameters
98 http_method = HTTP_METHOD
99 http_url = None
100 version = VERSION
101
102 def __init__(self, http_method=HTTP_METHOD, http_url=None, parameters=None):
103 self.http_method = http_method
104 self.http_url = http_url
105 self.parameters = parameters or {}
106
107 def set_parameter(self, parameter, value):
108 self.parameters[parameter] = value
109
110 def get_parameter(self, parameter):
111 try:
112 return self.parameters[parameter]
113 except:
114 raise OAuthError('Parameter not found: %s' % parameter)
115
116 def _get_timestamp_nonce(self):
117 return self.get_parameter('oauth_timestamp'), self.get_parameter('oauth_nonce')
118
119 # get any non-oauth parameters
120 def get_nonoauth_parameters(self):
121 parameters = {}
122 for k, v in six.iteritems(self.parameters):
123 # ignore oauth parameters
124 if k.find('oauth_') < 0:
125 parameters[k] = v
126 return parameters
127
128 # serialize as a header for an HTTPAuth request
129 def to_header(self, realm=''):
130 auth_header = 'OAuth realm="%s"' % realm
131 # add the oauth parameters
132 if self.parameters:
133 for k, v in six.iteritems(self.parameters):
134 auth_header += ', %s="%s"' % (k, v)
135 return {'Authorization': auth_header}
136
137 # serialize as post data for a POST request
138 def to_postdata(self):
139 return '&'.join(
140 '%s=%s' % (escape(str(k)), escape(str(v)))
141 for k, v in six.iteritems(self.parameters))
142
143 # serialize as a url for a GET request
144 def to_url(self):
145 return '%s?%s' % (self.get_normalized_http_url(), self.to_postdata())
146
147 # return a string that consists of all the parameters that need to be signed
148 def get_normalized_parameters(self):
149 params = self.parameters
150 try:
151 # exclude the signature if it exists
152 del params['oauth_signature']
153 except:
154 pass
155 key_values = params.items()
156 # sort lexicographically, first after key, then after value
157 key_values.sort()
158 # combine key value pairs in string and escape
159 return '&'.join('%s=%s' % (escape(str(k)), escape(str(v))) for k, v in key_values)
160
161 # just uppercases the http method
162 def get_normalized_http_method(self):
163 return self.http_method.upper()
164
165 # parses the url and rebuilds it to be scheme://host/path
166 def get_normalized_http_url(self):
167 parts = urlparse(self.http_url)
168 url_string = '%s://%s%s' % (parts[0], parts[1], parts[2]) # scheme, netloc, path
169 return url_string
170
171 # set the signature parameter to the result of build_signature
172 def sign_request(self, signature_method, consumer, token):
173 # set the signature method
174 self.set_parameter('oauth_signature_method', signature_method.get_name())
175 # set the signature
176 self.set_parameter('oauth_signature', self.build_signature(signature_method, consumer, token))
177
178 def build_signature(self, signature_method, consumer, token):
179 # call the build signature method within the signature method
180 return signature_method.build_signature(self, consumer, token)
181
182 @staticmethod
183 def from_request(http_method, http_url, headers=None, postdata=None, parameters=None):
184
185 # let the library user override things however they'd like, if they know
186 # which parameters to use then go for it, for example XMLRPC might want to
187 # do this
188 if parameters is not None:
189 return OAuthRequest(http_method, http_url, parameters)
190
191 # from the headers
192 if headers is not None:
193 try:
194 auth_header = headers['Authorization']
195 # check that the authorization header is OAuth
196 auth_header.index('OAuth')
197 # get the parameters from the header
198 parameters = OAuthRequest._split_header(auth_header)
199 return OAuthRequest(http_method, http_url, parameters)
200 except:
201 raise OAuthError('Unable to parse OAuth parameters from Authorization header.')
202
203 # from the parameter string (post body)
204 if http_method == 'POST' and postdata is not None:
205 parameters = OAuthRequest._split_url_string(postdata)
206
207 # from the url string
208 elif http_method == 'GET':
209 param_str = urlparse(http_url).query
210 parameters = OAuthRequest._split_url_string(param_str)
211
212 if parameters:
213 return OAuthRequest(http_method, http_url, parameters)
214
215 raise OAuthError('Missing all OAuth parameters. OAuth parameters must be in the headers, post body, or url.')
216
217 @staticmethod
218 def from_consumer_and_token(oauth_consumer, token=None, http_method=HTTP_METHOD, http_url=None, parameters=None):
219 if not parameters:
220 parameters = {}
221
222 defaults = {
223 'oauth_consumer_key': oauth_consumer.key,
224 'oauth_timestamp': generate_timestamp(),
225 'oauth_nonce': generate_nonce(),
226 'oauth_version': OAuthRequest.version,
227 }
228
229 defaults.update(parameters)
230 parameters = defaults
231
232 if token:
233 parameters['oauth_token'] = token.key
234
235 return OAuthRequest(http_method, http_url, parameters)
236
237 @staticmethod
238 def from_token_and_callback(token, callback=None, http_method=HTTP_METHOD, http_url=None, parameters=None):
239 if not parameters:
240 parameters = {}
241
242 parameters['oauth_token'] = token.key
243
244 if callback:
245 parameters['oauth_callback'] = escape(callback)
246
247 return OAuthRequest(http_method, http_url, parameters)
248
249 # util function: turn Authorization: header into parameters, has to do some unescaping
250 @staticmethod
251 def _split_header(header):
252 params = {}
253 header = header.lstrip()
254 if not header.startswith('OAuth '):
255 raise ValueError("not an OAuth header: %r" % header)
256 header = header[6:]
257 parts = header.split(',')
258 for param in parts:
259 # remove whitespace
260 param = param.strip()
261 # split key-value
262 param_parts = param.split('=', 1)
263 if param_parts[0] == 'realm':
264 # Realm header is not an OAuth parameter according to rfc5849
265 # section 3.4.1.3.1.
266 continue
267 # remove quotes and unescape the value
268 params[param_parts[0]] = unquote(param_parts[1].strip('\"'))
269 return params
270
271 # util function: turn url string into parameters, has to do some unescaping
272 @staticmethod
273 def _split_url_string(param_str):
274 parameters = parse_qs(param_str, keep_blank_values=False)
275 for k, v in six.iteritems(parameters):
276 parameters[k] = unquote(v[0])
277 return parameters
278
279# OAuthServer is a worker to check a requests validity against a data store
280class OAuthServer(object):
281 timestamp_threshold = 300 # in seconds, five minutes
282 version = VERSION
283 signature_methods = None
284 data_store = None
285
286 def __init__(self, data_store=None, signature_methods=None):
287 self.data_store = data_store
288 self.signature_methods = signature_methods or {}
289
290 def set_data_store(self, oauth_data_store):
291 self.data_store = oauth_data_store
292
293 def get_data_store(self):
294 return self.data_store
295
296 def add_signature_method(self, signature_method):
297 self.signature_methods[signature_method.get_name()] = signature_method
298 return self.signature_methods
299
300 # process a request_token request
301 # returns the request token on success
302 def fetch_request_token(self, oauth_request):
303 try:
304 # get the request token for authorization
305 token = self._get_token(oauth_request, 'request')
306 except:
307 # no token required for the initial token request
308 version = self._get_version(oauth_request)
309 consumer = self._get_consumer(oauth_request)
310 self._check_signature(oauth_request, consumer, None)
311 # fetch a new token
312 token = self.data_store.fetch_request_token(consumer)
313 return token
314
315 # process an access_token request
316 # returns the access token on success
317 def fetch_access_token(self, oauth_request):
318 version = self._get_version(oauth_request)
319 consumer = self._get_consumer(oauth_request)
320 # get the request token
321 token = self._get_token(oauth_request, 'request')
322 self._check_signature(oauth_request, consumer, token)
323 new_token = self.data_store.fetch_access_token(consumer, token)
324 return new_token
325
326 # verify an api call, checks all the parameters
327 def verify_request(self, oauth_request):
328 # -> consumer and token
329 version = self._get_version(oauth_request)
330 consumer = self._get_consumer(oauth_request)
331 # get the access token
332 token = self._get_token(oauth_request, 'access')
333 self._check_signature(oauth_request, consumer, token)
334 parameters = oauth_request.get_nonoauth_parameters()
335 return consumer, token, parameters
336
337 # authorize a request token
338 def authorize_token(self, token, user):
339 return self.data_store.authorize_request_token(token, user)
340
341 # get the callback url
342 def get_callback(self, oauth_request):
343 return oauth_request.get_parameter('oauth_callback')
344
345 # optional support for the authenticate header
346 def build_authenticate_header(self, realm=''):
347 return {'WWW-Authenticate': 'OAuth realm="%s"' % realm}
348
349 # verify the correct version request for this server
350 def _get_version(self, oauth_request):
351 try:
352 version = oauth_request.get_parameter('oauth_version')
353 except:
354 version = VERSION
355 if version and version != self.version:
356 raise OAuthError('OAuth version %s not supported' % str(version))
357 return version
358
359 # figure out the signature with some defaults
360 def _get_signature_method(self, oauth_request):
361 try:
362 signature_method = oauth_request.get_parameter('oauth_signature_method')
363 except:
364 signature_method = SIGNATURE_METHOD
365 try:
366 # get the signature method object
367 signature_method = self.signature_methods[signature_method]
368 except:
369 signature_method_names = ', '.join(self.signature_methods.keys())
370 raise OAuthError('Signature method %s not supported try one of the following: %s' % (signature_method, signature_method_names))
371
372 return signature_method
373
374 def _get_consumer(self, oauth_request):
375 consumer_key = oauth_request.get_parameter('oauth_consumer_key')
376 if not consumer_key:
377 raise OAuthError('Invalid consumer key')
378 consumer = self.data_store.lookup_consumer(consumer_key)
379 if not consumer:
380 raise OAuthError('Invalid consumer')
381 return consumer
382
383 # try to find the token for the provided request token key
384 def _get_token(self, oauth_request, token_type='access'):
385 token_field = oauth_request.get_parameter('oauth_token')
386 token = self.data_store.lookup_token(token_type, token_field)
387 if not token:
388 raise OAuthError('Invalid %s token: %s' % (token_type, token_field))
389 return token
390
391 def _check_signature(self, oauth_request, consumer, token):
392 timestamp, nonce = oauth_request._get_timestamp_nonce()
393 self._check_timestamp(timestamp)
394 self._check_nonce(consumer, token, nonce)
395 signature_method = self._get_signature_method(oauth_request)
396 try:
397 signature = oauth_request.get_parameter('oauth_signature')
398 except:
399 raise OAuthError('Missing signature')
400 # attempt to construct the same signature
401 built = signature_method.build_signature(oauth_request, consumer, token)
402 if signature != built:
403 key, base = signature_method.build_signature_base_string(oauth_request, consumer, token)
404 raise OAuthError('Signature does not match. Expected: %s Got: %s Expected signature base string: %s' % (built, signature, base))
405
406 def _check_timestamp(self, timestamp):
407 # verify that timestamp is recentish
408 timestamp = int(timestamp)
409 now = int(time.time())
410 lapsed = now - timestamp
411 if lapsed > self.timestamp_threshold:
412 raise OAuthError('Expired timestamp: given %d and now %s has a greater difference than threshold %d' % (timestamp, now, self.timestamp_threshold))
413
414 def _check_nonce(self, consumer, token, nonce):
415 # verify that the nonce is uniqueish
416 try:
417 self.data_store.lookup_nonce(consumer, token, nonce)
418 raise OAuthError('Nonce already used: %s' % str(nonce))
419 except:
420 pass
421
422# OAuthClient is a worker to attempt to execute a request
423class OAuthClient(object):
424 consumer = None
425 token = None
426
427 def __init__(self, oauth_consumer, oauth_token):
428 self.consumer = oauth_consumer
429 self.token = oauth_token
430
431 def get_consumer(self):
432 return self.consumer
433
434 def get_token(self):
435 return self.token
436
437 def fetch_request_token(self, oauth_request):
438 # -> OAuthToken
439 raise NotImplementedError
440
441 def fetch_access_token(self, oauth_request):
442 # -> OAuthToken
443 raise NotImplementedError
444
445 def access_resource(self, oauth_request):
446 # -> some protected resource
447 raise NotImplementedError
448
449# OAuthDataStore is a database abstraction used to lookup consumers and tokens
450class OAuthDataStore(object):
451
452 def lookup_consumer(self, key):
453 # -> OAuthConsumer
454 raise NotImplementedError
455
456 def lookup_token(self, oauth_consumer, token_type, token_token):
457 # -> OAuthToken
458 raise NotImplementedError
459
460 def lookup_nonce(self, oauth_consumer, oauth_token, nonce, timestamp):
461 # -> OAuthToken
462 raise NotImplementedError
463
464 def fetch_request_token(self, oauth_consumer):
465 # -> OAuthToken
466 raise NotImplementedError
467
468 def fetch_access_token(self, oauth_consumer, oauth_token):
469 # -> OAuthToken
470 raise NotImplementedError
471
472 def authorize_request_token(self, oauth_token, user):
473 # -> OAuthToken
474 raise NotImplementedError
475
476# OAuthSignatureMethod is a strategy class that implements a signature method
477class OAuthSignatureMethod(object):
478 def get_name():
479 # -> str
480 raise NotImplementedError
481
482 def build_signature_base_string(oauth_request, oauth_consumer, oauth_token):
483 # -> str key, str raw
484 raise NotImplementedError
485
486 def build_signature(oauth_request, oauth_consumer, oauth_token):
487 # -> str
488 raise NotImplementedError
489
490class OAuthSignatureMethod_HMAC_SHA1(OAuthSignatureMethod):
491
492 def get_name(self):
493 return 'HMAC-SHA1'
494
495 def build_signature_base_string(self, oauth_request, consumer, token):
496 sig = (
497 escape(oauth_request.get_normalized_http_method()),
498 escape(oauth_request.get_normalized_http_url()),
499 escape(oauth_request.get_normalized_parameters()),
500 )
501
502 key = '%s&' % escape(consumer.secret)
503 if token:
504 key += escape(token.secret)
505 raw = '&'.join(sig)
506 return key, raw
507
508 def build_signature(self, oauth_request, consumer, token):
509 # build the base signature string
510 key, raw = self.build_signature_base_string(oauth_request, consumer, token)
511
512 # hmac object
513 try:
514 import hashlib # 2.5
515 hashed = hmac.new(key, raw, hashlib.sha1)
516 except:
517 import sha # deprecated
518 hashed = hmac.new(key, raw, sha)
519
520 # calculate the digest base 64
521 return base64.b64encode(hashed.digest())
522
523class OAuthSignatureMethod_PLAINTEXT(OAuthSignatureMethod):
524
525 def get_name(self):
526 return 'PLAINTEXT'
527
528 def build_signature_base_string(self, oauth_request, consumer, token):
529 # concatenate the consumer key and secret
530 sig = escape(consumer.secret)
531 if token:
532 sig = '&'.join((sig, escape(token.secret)))
533 return sig
534
535 def build_signature(self, oauth_request, consumer, token):
536 return self.build_signature_base_string(oauth_request, consumer, token)
diff --git a/lib/lp/services/webapp/authentication.py b/lib/lp/services/webapp/authentication.py
index acab459..81a2384 100644
--- a/lib/lp/services/webapp/authentication.py
+++ b/lib/lp/services/webapp/authentication.py
@@ -14,7 +14,8 @@ __all__ = [
1414
15import binascii15import binascii
1616
17from contrib.oauth import OAuthRequest17from oauthlib import oauth1
18from oauthlib.oauth1.rfc5849.utils import parse_authorization_header
18import six19import six
19from zope.authentication.interfaces import ILoginPassword20from zope.authentication.interfaces import ILoginPassword
20from zope.component import getUtility21from zope.component import getUtility
@@ -274,6 +275,15 @@ class LaunchpadPrincipal:
274 return self.title275 return self.title
275276
276277
278def _parse_oauth_authorization_header(header):
279 # http://oauth.net/core/1.0/#encoding_parameters says "Text names
280 # and values MUST be encoded as UTF-8 octets before percent-encoding
281 # them", so we can reasonably fail if this hasn't been done.
282 return dict(oauth1.rfc5849.signature.collect_parameters(
283 headers={"Authorization": six.ensure_text(header)},
284 exclude_oauth_signature=False))
285
286
277def get_oauth_authorization(request):287def get_oauth_authorization(request):
278 """Retrieve OAuth authorization information from a request.288 """Retrieve OAuth authorization information from a request.
279289
@@ -286,10 +296,7 @@ def get_oauth_authorization(request):
286 """296 """
287 header = request._auth297 header = request._auth
288 if header is not None and header.startswith("OAuth "):298 if header is not None and header.startswith("OAuth "):
289 # http://oauth.net/core/1.0/#encoding_parameters says "Text names299 return _parse_oauth_authorization_header(header)
290 # and values MUST be encoded as UTF-8 octets before percent-encoding
291 # them", so we can reasonably fail if this hasn't been done.
292 return OAuthRequest._split_header(six.ensure_text(header))
293 else:300 else:
294 return request.form301 return request.form
295302
diff --git a/lib/lp/services/webapp/tests/test_authentication.py b/lib/lp/services/webapp/tests/test_authentication.py
index d69e248..a348b0b 100644
--- a/lib/lp/services/webapp/tests/test_authentication.py
+++ b/lib/lp/services/webapp/tests/test_authentication.py
@@ -8,9 +8,10 @@ __metaclass__ = type
88
9import unittest9import unittest
1010
11from contrib.oauth import OAuthRequest11from lp.services.webapp.authentication import (
1212 _parse_oauth_authorization_header,
13from lp.services.webapp.authentication import check_oauth_signature13 check_oauth_signature,
14 )
14from lp.services.webapp.servers import LaunchpadTestRequest15from lp.services.webapp.servers import LaunchpadTestRequest
15from lp.testing import (16from lp.testing import (
16 TestCase,17 TestCase,
@@ -31,20 +32,21 @@ class TestOAuthParsing(TestCase):
3132
32 def test_split_oauth(self):33 def test_split_oauth(self):
33 # OAuth headers are parsed correctly: see bug 314507.34 # OAuth headers are parsed correctly: see bug 314507.
34 # This was really a bug in the underlying contrib/oauth.py module, but35 # This was originally a bug in the underlying contrib/oauth.py
35 # it has no standalone test case.36 # module, but it's useful to test that parsing works as we expect
37 # for whatever OAuth library we're currently using.
36 #38 #
37 # Note that the 'realm' parameter is not returned, because it's not39 # Note that the 'realm' parameter is not returned, because it's not
38 # included in the OAuth calculations.40 # included in the OAuth calculations.
39 headers = OAuthRequest._split_header(41 headers = _parse_oauth_authorization_header(
40 'OAuth realm="foo", oauth_consumer_key="justtesting"')42 'OAuth realm="foo", oauth_consumer_key="justtesting"')
41 self.assertEqual(headers,43 self.assertEqual(headers,
42 {'oauth_consumer_key': 'justtesting'})44 {'oauth_consumer_key': 'justtesting'})
43 headers = OAuthRequest._split_header(45 headers = _parse_oauth_authorization_header(
44 'OAuth oauth_consumer_key="justtesting"')46 'OAuth oauth_consumer_key="justtesting"')
45 self.assertEqual(headers,47 self.assertEqual(headers,
46 {'oauth_consumer_key': 'justtesting'})48 {'oauth_consumer_key': 'justtesting'})
47 headers = OAuthRequest._split_header(49 headers = _parse_oauth_authorization_header(
48 'OAuth oauth_consumer_key="justtesting", realm="realm"')50 'OAuth oauth_consumer_key="justtesting", realm="realm"')
49 self.assertEqual(headers,51 self.assertEqual(headers,
50 {'oauth_consumer_key': 'justtesting'})52 {'oauth_consumer_key': 'justtesting'})
diff --git a/lib/lp/services/webapp/tests/test_publication.py b/lib/lp/services/webapp/tests/test_publication.py
index a2a4953..7a539d4 100644
--- a/lib/lp/services/webapp/tests/test_publication.py
+++ b/lib/lp/services/webapp/tests/test_publication.py
@@ -7,13 +7,8 @@ __metaclass__ = type
77
8import sys8import sys
99
10from contrib.oauth import (
11 OAuthConsumer,
12 OAuthRequest,
13 OAuthSignatureMethod_PLAINTEXT,
14 OAuthToken,
15 )
16from fixtures import FakeLogger10from fixtures import FakeLogger
11from oauthlib import oauth1
17from storm.database import (12from storm.database import (
18 STATE_DISCONNECTED,13 STATE_DISCONNECTED,
19 STATE_RECONNECT,14 STATE_RECONNECT,
@@ -115,16 +110,16 @@ class TestWebServicePublication(TestCaseWithFactory):
115 person, permission=OAuthPermission.READ_PUBLIC, context=None)110 person, permission=OAuthPermission.READ_PUBLIC, context=None)
116 access_token, access_secret = request_token.createAccessToken()111 access_token, access_secret = request_token.createAccessToken()
117112
118 # Use oauth.OAuthRequest just to generate a dictionary containing all113 # Make an OAuth signature using the access token we just created for
119 # the parameters we need to use in a valid OAuth request, using the114 # our new person.
120 # access token we just created for our new person.115 client = oauth1.Client(
121 oauth_consumer = OAuthConsumer(consumer.key, u'')116 consumer.key,
122 oauth_token = OAuthToken(access_token.key, access_secret)117 resource_owner_key=access_token.key,
123 oauth_request = OAuthRequest.from_consumer_and_token(118 resource_owner_secret=access_secret,
124 oauth_consumer, oauth_token)119 signature_method=oauth1.SIGNATURE_PLAINTEXT)
125 oauth_request.sign_request(120 _, headers, _ = client.sign('/dummy')
126 OAuthSignatureMethod_PLAINTEXT(), oauth_consumer, oauth_token)121 return LaunchpadTestRequest(
127 return LaunchpadTestRequest(form=oauth_request.parameters)122 environ={'HTTP_AUTHORIZATION': headers['Authorization']})
128123
129 def test_getPrincipal_for_person_and_account_with_different_ids(self):124 def test_getPrincipal_for_person_and_account_with_different_ids(self):
130 # WebServicePublication.getPrincipal() does not rely on accounts125 # WebServicePublication.getPrincipal() does not rely on accounts
diff --git a/lib/lp/testing/pages.py b/lib/lp/testing/pages.py
index 71045f0..f0d37ed 100644
--- a/lib/lp/testing/pages.py
+++ b/lib/lp/testing/pages.py
@@ -27,13 +27,8 @@ from bs4.element import (
27 ProcessingInstruction,27 ProcessingInstruction,
28 Tag,28 Tag,
29 )29 )
30from contrib.oauth import (
31 OAuthConsumer,
32 OAuthRequest,
33 OAuthSignatureMethod_PLAINTEXT,
34 OAuthToken,
35 )
36from lazr.restful.testing.webservice import WebServiceCaller30from lazr.restful.testing.webservice import WebServiceCaller
31from oauthlib import oauth1
37import six32import six
38from six.moves.urllib.parse import urljoin33from six.moves.urllib.parse import urljoin
39from soupsieve import escape as css_escape34from soupsieve import escape as css_escape
@@ -147,20 +142,17 @@ class LaunchpadWebServiceCaller(WebServiceCaller):
147 calls.142 calls.
148 """143 """
149 if oauth_consumer_key is not None and oauth_access_key is not None:144 if oauth_consumer_key is not None and oauth_access_key is not None:
150 # XXX cjwatson 2016-01-25: Callers should be updated to pass
151 # Unicode directly, but that's a big change.
152 oauth_consumer_key = six.ensure_text(oauth_consumer_key)
153 self.consumer = OAuthConsumer(oauth_consumer_key, u'')
154 if oauth_access_secret is None:145 if oauth_access_secret is None:
155 oauth_access_secret = SAMPLEDATA_ACCESS_SECRETS.get(146 oauth_access_secret = SAMPLEDATA_ACCESS_SECRETS.get(
156 oauth_access_key, u'')147 oauth_access_key, u'')
157 self.access_token = OAuthToken(148 self.oauth_client = oauth1.Client(
158 oauth_access_key, oauth_access_secret)149 oauth_consumer_key,
159 # This shouldn't be here, but many old tests expect it.150 resource_owner_key=oauth_access_key,
151 resource_owner_secret=oauth_access_secret,
152 signature_method=oauth1.SIGNATURE_PLAINTEXT)
160 logout()153 logout()
161 else:154 else:
162 self.consumer = None155 self.oauth_client = None
163 self.access_token = None
164 self.handle_errors = handle_errors156 self.handle_errors = handle_errors
165 if default_api_version is not None:157 if default_api_version is not None:
166 self.default_api_version = default_api_version158 self.default_api_version = default_api_version
@@ -169,13 +161,9 @@ class LaunchpadWebServiceCaller(WebServiceCaller):
169 default_api_version = "beta"161 default_api_version = "beta"
170162
171 def addHeadersTo(self, full_url, full_headers):163 def addHeadersTo(self, full_url, full_headers):
172 if self.consumer is not None and self.access_token is not None:164 if self.oauth_client is not None:
173 request = OAuthRequest.from_consumer_and_token(165 _, oauth_headers, _ = self.oauth_client.sign(
174 self.consumer, self.access_token, http_url=full_url)166 full_url, realm=OAUTH_REALM)
175 request.sign_request(
176 OAuthSignatureMethod_PLAINTEXT(), self.consumer,
177 self.access_token)
178 oauth_headers = request.to_header(OAUTH_REALM)
179 full_headers.update({167 full_headers.update({
180 wsgi_native_string(key): wsgi_native_string(value)168 wsgi_native_string(key): wsgi_native_string(value)
181 for key, value in oauth_headers.items()})169 for key, value in oauth_headers.items()})
diff --git a/setup.py b/setup.py
index f6d5b6b..173e1c9 100644
--- a/setup.py
+++ b/setup.py
@@ -199,6 +199,7 @@ setup(
199 'meliae',199 'meliae',
200 'mock',200 'mock',
201 'oauth',201 'oauth',
202 'oauthlib',
202 'oops',203 'oops',
203 'oops_amqp',204 'oops_amqp',
204 'oops_datedir_repo',205 'oops_datedir_repo',

Subscribers

People subscribed via source and target branches

to status/vote changes: