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

Subscribers

People subscribed via source and target branches

to status/vote changes: