Merge lp:~nataliabidart/ubuntu-sso-client/sign-ui-with-query-params into lp:ubuntu-sso-client

Proposed by Natalia Bidart on 2011-08-12
Status: Merged
Approved by: Natalia Bidart on 2011-08-12
Approved revision: 759
Merged at revision: 758
Proposed branch: lp:~nataliabidart/ubuntu-sso-client/sign-ui-with-query-params
Merge into: lp:ubuntu-sso-client
Diff against target: 593 lines (+255/-72)
5 files modified
ubuntu_sso/credentials.py (+18/-18)
ubuntu_sso/tests/__init__.py (+10/-10)
ubuntu_sso/tests/test_credentials.py (+83/-44)
ubuntu_sso/utils/__init__.py (+35/-0)
ubuntu_sso/utils/tests/test_oauth_headers.py (+109/-0)
To merge this branch: bzr merge lp:~nataliabidart/ubuntu-sso-client/sign-ui-with-query-params
Reviewer Review Type Date Requested Status
Roberto Alsina (community) 2011-08-12 Approve on 2011-08-12
Review via email: mp+71352@code.launchpad.net

Commit message

- URL oauth signing properly handles query params (LP: #824815).

To post a comment you must log in.
757. By Natalia Bidart on 2011-08-12

Lint issues.

758. By Natalia Bidart on 2011-08-12

Merged trunk in.

759. By Natalia Bidart on 2011-08-12

Properly handle formatting args in the ping url.

Roberto Alsina (ralsina) wrote :

+1

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'ubuntu_sso/credentials.py'
2--- ubuntu_sso/credentials.py 2011-07-29 14:03:01 +0000
3+++ ubuntu_sso/credentials.py 2011-08-12 15:53:34 +0000
4@@ -43,10 +43,9 @@
5
6 from functools import wraps
7
8-from oauth import oauth
9 from twisted.internet.defer import inlineCallbacks, returnValue
10
11-from ubuntu_sso import NO_OP
12+from ubuntu_sso import NO_OP, utils
13 from ubuntu_sso.keyring import Keyring
14 from ubuntu_sso.logger import setup_logging
15
16@@ -226,27 +225,28 @@
17 defined if this method is being called.
18
19 """
20- logger.info('Pinging server for app_name "%s", ping_url: "%s", '
21- 'email "%s".', app_name, self.ping_url, email)
22- url = self.ping_url + email
23- consumer = oauth.OAuthConsumer(credentials['consumer_key'],
24- credentials['consumer_secret'])
25- token = oauth.OAuthToken(credentials['token'],
26- credentials['token_secret'])
27- get_request = oauth.OAuthRequest.from_consumer_and_token
28- oauth_req = get_request(oauth_consumer=consumer, token=token,
29- http_method='GET', http_url=url)
30- oauth_req.sign_request(oauth.OAuthSignatureMethod_HMAC_SHA1(),
31- consumer, token)
32- request = urllib2.Request(url, headers=oauth_req.to_header())
33- logger.debug('Opening the url "%s" with urllib2.urlopen.',
34+ logger.info('Pinging server for app_name %r, ping_url: %r, '
35+ 'email %r.', app_name, self.ping_url, email)
36+ try:
37+ url = self.ping_url.format(email=email)
38+ except IndexError: # tuple index out of range
39+ url = self.ping_url.format(email) # format the first substitution
40+
41+ if url == self.ping_url:
42+ logger.debug('Original url (%r) could not be formatted, '
43+ 'appending email (%r).', self.ping_url, email)
44+ url = self.ping_url + email
45+
46+ headers = utils.oauth_headers(url, credentials)
47+ request = urllib2.Request(url, headers=headers)
48+ logger.debug('Opening the url %r with urllib2.urlopen.',
49 request.get_full_url())
50 # This code is blocking, we should change this.
51 # I've tried with deferToThread an twisted.web.client.getPage
52 # but the returned deferred will never be fired (nataliabidart).
53 response = urllib2.urlopen(request)
54 logger.debug('Url opened. Response: %s.', response.code)
55- returnValue(response.code)
56+ returnValue(response)
57
58 @handle_exceptions(msg='Problem opening the Ubuntu SSO user interface')
59 def _show_ui(self, login_only):
60@@ -316,7 +316,7 @@
61 def find_credentials(self):
62 """Get the credentials for 'self.app_name'. Return {} if not there."""
63 creds = yield Keyring().get_credentials(self.app_name)
64- logger.info('find_credentials: self.app_name "%s", '
65+ logger.info('find_credentials: self.app_name %r, '
66 'result is {}? %s', self.app_name, creds is None)
67 returnValue(creds if creds is not None else {})
68
69
70=== modified file 'ubuntu_sso/tests/__init__.py'
71--- ubuntu_sso/tests/__init__.py 2011-01-11 19:13:19 +0000
72+++ ubuntu_sso/tests/__init__.py 2011-08-12 15:53:34 +0000
73@@ -22,30 +22,30 @@
74
75 from ubuntu_sso.keyring import get_token_name
76
77-APP_NAME = 'The Super App!'
78-CAPTCHA_ID = 'test'
79+APP_NAME = u'The Super App!'
80+CAPTCHA_ID = u'test'
81 CAPTCHA_PATH = os.path.abspath(os.path.join(os.curdir, 'ubuntu_sso', 'tests',
82 'files', 'captcha.png'))
83-CAPTCHA_SOLUTION = 'william Byrd'
84-EMAIL = 'test@example.com'
85-EMAIL_TOKEN = 'B2Pgtf'
86+CAPTCHA_SOLUTION = u'william Byrd'
87+EMAIL = u'test@example.com'
88+EMAIL_TOKEN = u'B2Pgtf'
89 GTK_GUI_CLASS = 'UbuntuSSOClientGUI'
90 GTK_GUI_MODULE = 'ubuntu_sso.gtk.gui'
91 HELP_TEXT = """Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam sed
92 lorem nibh. Suspendisse gravida nulla non nunc suscipit pulvinar tempus ut
93 augue. Morbi consequat, ligula a elementum pretium, dolor nulla tempus metus,
94 sed viverra nisi risus non velit."""
95-NAME = 'Juanito Pérez'
96-PASSWORD = 'h3lloWorld'
97-PING_URL = 'http://localhost/ping-me/'
98-RESET_PASSWORD_TOKEN = '8G5Wtq'
99+NAME = u'Juanito Pérez'
100+PASSWORD = u'h3lloWorld'
101+PING_URL = u'http://localhost/ping-me/'
102+RESET_PASSWORD_TOKEN = u'8G5Wtq'
103 TOKEN = {u'consumer_key': u'xQ7xDAz',
104 u'consumer_secret': u'KzCJWCTNbbntwfyCKKjomJDzlgqxLy',
105 u'token_name': u'test',
106 u'token': u'GkInOfSMGwTXAUoVQwLUoPxElEEUdhsLVNTPhxHJDUIeHCPNEo',
107 u'token_secret': u'qFYImEtlczPbsCnYyuwLoPDlPEnvNcIktZphPQklAWrvyfFMV'}
108 TOKEN_NAME = get_token_name(APP_NAME)
109-TC_URL = 'http://localhost/'
110+TC_URL = u'http://localhost/'
111 WINDOW_ID = 5
112
113
114
115=== modified file 'ubuntu_sso/tests/test_credentials.py'
116--- ubuntu_sso/tests/test_credentials.py 2011-07-29 14:20:06 +0000
117+++ ubuntu_sso/tests/test_credentials.py 2011-08-12 15:53:34 +0000
118@@ -20,10 +20,9 @@
119
120 import logging
121 import os
122-import urllib
123+import urllib2
124
125 from twisted.internet import defer
126-from twisted.internet.defer import inlineCallbacks
127 from twisted.trial.unittest import TestCase, FailTest
128 from ubuntuone.devtools.handlers import MementoHandler
129 from ubuntuone.devtools.testcase import skipIfOS
130@@ -246,7 +245,7 @@
131 class CredentialsLoginSuccessTestCase(CredentialsTestCase):
132 """Test suite for the Credentials login success callback."""
133
134- @inlineCallbacks
135+ @defer.inlineCallbacks
136 def test_cred_not_found(self):
137 """On auth success, if cred not found, self.error_cb is called."""
138 self.patch(credentials.Keyring, 'get_credentials',
139@@ -259,7 +258,7 @@
140 detailed_error=AssertionError(msg))
141 self.assertEqual(result, None)
142
143- @inlineCallbacks
144+ @defer.inlineCallbacks
145 def test_cred_error(self):
146 """On auth success, if cred failed, self.error_cb is called."""
147 expected_error = SampleMiscException()
148@@ -273,7 +272,7 @@
149 self.assertEqual(result, None)
150 self.assertTrue(self.memento.check_exception(SampleMiscException))
151
152- @inlineCallbacks
153+ @defer.inlineCallbacks
154 def test_ping_success(self):
155 """Auth success + cred found + ping success, success_cb is called."""
156 self.patch(credentials.Keyring, 'get_credentials',
157@@ -285,7 +284,7 @@
158 self.assertEqual(self._called, (('success', APP_NAME, TOKEN), {}))
159 self.assertEqual(result, 0)
160
161- @inlineCallbacks
162+ @defer.inlineCallbacks
163 def test_ping_error(self):
164 """Auth success + cred found + ping error, error_cb is called.
165
166@@ -313,7 +312,7 @@
167 # exception logged
168 self.assertTrue(self.memento.check_exception(FailTest, error))
169
170- @inlineCallbacks
171+ @defer.inlineCallbacks
172 def test_pings_url(self):
173 """On auth success, self.ping_url is opened."""
174 self.patch(credentials.Keyring, 'get_credentials',
175@@ -329,7 +328,7 @@
176
177 self.assertEqual(self._url_pinged, ((APP_NAME, EMAIL, TOKEN), {}))
178
179- @inlineCallbacks
180+ @defer.inlineCallbacks
181 def test_no_ping_url_is_success(self):
182 """Auth success + cred found + no ping url, success_cb is called.
183
184@@ -367,15 +366,15 @@
185 def faked_urlopen(request):
186 """Fake urlopener."""
187 self._request = request
188- response = urllib.addinfourl(fp=open(os.path.devnull),
189- headers=request.headers,
190- url=request.get_full_url(),
191- code=200)
192+ response = urllib2.addinfourl(fp=open(os.path.devnull),
193+ headers=request.headers,
194+ url=request.get_full_url(),
195+ code=200)
196 return response
197
198 self.patch(credentials.urllib2, 'urlopen', faked_urlopen)
199
200- @inlineCallbacks
201+ @defer.inlineCallbacks
202 def test_ping_url_if_url_is_none(self):
203 """self.ping_url is opened."""
204 self.patch(credentials.urllib2, 'urlopen', self.fail)
205@@ -384,7 +383,7 @@
206 credentials=TOKEN)
207 # no failure
208
209- @inlineCallbacks
210+ @defer.inlineCallbacks
211 def test_ping_url(self):
212 """On auth success, self.ping_url is opened."""
213 yield self.obj._ping_url(app_name=APP_NAME, email=EMAIL,
214@@ -394,23 +393,23 @@
215 self.assertEqual(self._request.get_full_url(),
216 self.obj.ping_url + EMAIL)
217
218- @inlineCallbacks
219+ @defer.inlineCallbacks
220 def test_request_is_signed_with_credentials(self):
221 """The http request to self.ping_url is signed with the credentials."""
222+
223+ def fake_it(*a, **kw):
224+ """Fake oauth_headers."""
225+ self._set_called(*a, **kw)
226+ return {}
227+
228+ self.patch(credentials.utils, 'oauth_headers', fake_it)
229 result = yield self.obj._ping_url(APP_NAME, EMAIL, TOKEN)
230- headers = self._request.headers
231-
232- self.assertIn('Authorization', headers)
233- oauth_stuff = headers['Authorization']
234-
235- expected = 'oauth_consumer_key="xQ7xDAz", ' \
236- 'oauth_signature_method="HMAC-SHA1", oauth_version="1.0", ' \
237- 'oauth_token="GkInOfSMGwTXAUoVQwLUoPxElEEUdhsLVNTPhxHJDUIeHCPNEo' \
238- '", oauth_signature="'
239- self.assertIn(expected, oauth_stuff)
240- self.assertEqual(result, 200)
241-
242- @inlineCallbacks
243+
244+ self.assertEqual(self._called,
245+ ((self.obj.ping_url + EMAIL, TOKEN), {}))
246+ self.assertEqual(result.code, 200)
247+
248+ @defer.inlineCallbacks
249 def test_ping_url_error(self):
250 """Exception is handled if ping fails."""
251 error = 'Blu'
252@@ -422,12 +421,52 @@
253 self.assert_error_cb_called(msg=msg, detailed_error=FailTest(error))
254 self.assertTrue(self.memento.check_exception(FailTest, error))
255
256+ @defer.inlineCallbacks
257+ def test_ping_url_formatting(self):
258+ """The email is added as the first formatting argument."""
259+ self.obj.ping_url = u'http://example.com/{email}/something/else'
260+ result = yield self.obj._ping_url(app_name=APP_NAME, email=EMAIL,
261+ credentials=TOKEN)
262+
263+ expected = self.obj.ping_url.format(email=EMAIL)
264+ self.assertEqual(expected, result.url)
265+
266+ @defer.inlineCallbacks
267+ def test_ping_url_formatting_with_query_params(self):
268+ """The email is added as the first formatting argument."""
269+ self.obj.ping_url = u'http://example.com/{email}?something=else'
270+ result = yield self.obj._ping_url(app_name=APP_NAME, email=EMAIL,
271+ credentials=TOKEN)
272+
273+ expected = self.obj.ping_url.format(email=EMAIL)
274+ self.assertEqual(expected, result.url)
275+
276+ @defer.inlineCallbacks
277+ def test_ping_url_formatting_no_email_kwarg(self):
278+ """The email is added as the first formatting argument."""
279+ self.obj.ping_url = u'http://example.com/{0}/yadda/?something=else'
280+ result = yield self.obj._ping_url(app_name=APP_NAME, email=EMAIL,
281+ credentials=TOKEN)
282+
283+ expected = self.obj.ping_url.format(EMAIL)
284+ self.assertEqual(expected, result.url)
285+
286+ @defer.inlineCallbacks
287+ def test_ping_url_formatting_no_format(self):
288+ """The email is appended if formatting could not be accomplished."""
289+ self.obj.ping_url = u'http://example.com/yadda/'
290+ result = yield self.obj._ping_url(app_name=APP_NAME, email=EMAIL,
291+ credentials=TOKEN)
292+
293+ expected = self.obj.ping_url + EMAIL
294+ self.assertEqual(expected, result.url)
295+
296
297 class FindCredentialsTestCase(CredentialsTestCase):
298 """Test suite for the find_credentials method."""
299 timeout = 5
300
301- @inlineCallbacks
302+ @defer.inlineCallbacks
303 def test_find_credentials(self):
304 """A deferred with credentials is returned when found."""
305 self.patch(credentials.Keyring, 'get_credentials',
306@@ -436,7 +475,7 @@
307 token = yield self.obj.find_credentials()
308 self.assertEqual(token, TOKEN)
309
310- @inlineCallbacks
311+ @defer.inlineCallbacks
312 def test_credentials_not_found(self):
313 """find_credentials returns {} when no credentials are found."""
314 self.patch(credentials.Keyring, 'get_credentials',
315@@ -445,7 +484,7 @@
316 token = yield self.obj.find_credentials()
317 self.assertEqual(token, {})
318
319- @inlineCallbacks
320+ @defer.inlineCallbacks
321 def test_keyring_failure(self):
322 """Failures from the keyring are handled."""
323 expected_error = SampleMiscException()
324@@ -459,7 +498,7 @@
325 class ClearCredentialsTestCase(CredentialsTestCase):
326 """Test suite for the clear_credentials method."""
327
328- @inlineCallbacks
329+ @defer.inlineCallbacks
330 def test_clear_credentials(self):
331 """The credentials are cleared."""
332 self.patch(credentials.Keyring, 'delete_credentials',
333@@ -468,7 +507,7 @@
334 yield self.obj.clear_credentials()
335 self.assertEqual(self._called, ((APP_NAME,), {}))
336
337- @inlineCallbacks
338+ @defer.inlineCallbacks
339 def test_keyring_failure(self):
340 """Failures from the keyring are handled."""
341 expected_error = SampleMiscException()
342@@ -482,7 +521,7 @@
343 class StoreCredentialsTestCase(CredentialsTestCase):
344 """Test suite for the store_credentials method."""
345
346- @inlineCallbacks
347+ @defer.inlineCallbacks
348 def test_store_credentials(self):
349 """The credentials are stored."""
350 self.patch(credentials.Keyring, 'set_credentials',
351@@ -491,7 +530,7 @@
352 yield self.obj.store_credentials(TOKEN)
353 self.assertEqual(self._called, ((APP_NAME, TOKEN,), {}))
354
355- @inlineCallbacks
356+ @defer.inlineCallbacks
357 def test_keyring_failure(self):
358 """Failures from the keyring are handled."""
359 expected_error = SampleMiscException()
360@@ -513,7 +552,7 @@
361 self.ui_kwargs = UI_KWARGS.copy()
362 self.ui_kwargs['login_only'] = self.login_only
363
364- @inlineCallbacks
365+ @defer.inlineCallbacks
366 def test_with_existent_token(self):
367 """The operation returns the credentials if already in keyring."""
368 self.patch(credentials.Keyring, 'get_credentials',
369@@ -523,7 +562,7 @@
370
371 self.assertEqual(self._called, (('success', APP_NAME, TOKEN), {}))
372
373- @inlineCallbacks
374+ @defer.inlineCallbacks
375 def test_without_existent_token(self):
376 """The operation returns the credentials gathered by the GUI."""
377 self.patch(credentials.Keyring, 'get_credentials',
378@@ -533,7 +572,7 @@
379
380 self.assertEqual(self.obj.gui.kwargs, self.ui_kwargs)
381
382- @inlineCallbacks
383+ @defer.inlineCallbacks
384 def test_with_exception_on_credentials(self):
385 """The operation calls the error callback if a exception occurs."""
386 expected_error = SampleMiscException()
387@@ -546,7 +585,7 @@
388 self.assert_error_cb_called(msg=msg, detailed_error=expected_error)
389 self.assertTrue(self.memento.check_exception(SampleMiscException))
390
391- @inlineCallbacks
392+ @defer.inlineCallbacks
393 def test_with_exception_on_gui(self):
394 """The operation calls the error callback if a GUI exception occurs."""
395 self.patch(credentials.Keyring, 'get_credentials',
396@@ -561,7 +600,7 @@
397 detailed_error=SampleMiscException(err))
398 self.assertTrue(self.memento.check_exception(SampleMiscException, err))
399
400- @inlineCallbacks
401+ @defer.inlineCallbacks
402 def test_connects_gui_signals(self):
403 """GUI callbacks are properly connected."""
404 self.patch(credentials.Keyring, 'get_credentials',
405@@ -575,7 +614,7 @@
406 self.assertEqual(self.obj.gui.user_cancellation_callback,
407 self.obj._auth_denial_cb)
408
409- @inlineCallbacks
410+ @defer.inlineCallbacks
411 def test_gui_is_created(self):
412 """The GUI is created and stored."""
413 self.patch(credentials.Keyring, 'get_credentials',
414@@ -608,11 +647,11 @@
415 APP_NAME_KEY: APP_NAME,
416 'email': self.email,
417 'password': self.password,
418- }
419+ }
420 self.patch(ubuntu_sso.main, 'SSOLoginRoot', FakedSSOLoginRoot)
421
422 @skipIfOS('linux2', 'Not implemented on Linux yet.')
423- @inlineCallbacks
424+ @defer.inlineCallbacks
425 def test_with_existent_token(self):
426 """The operation returns the credentials if already in keyring."""
427 self.patch(credentials.Keyring, 'get_credentials',
428@@ -622,7 +661,7 @@
429 self.assertEqual(self._called, (('success', APP_NAME, TOKEN), {}))
430
431 @skipIfOS('linux2', 'Not implemented on Linux yet.')
432- @inlineCallbacks
433+ @defer.inlineCallbacks
434 def test_without_existent_token(self):
435 """The operation returns the credentials obtained."""
436 self.patch(credentials.Keyring, 'get_credentials',
437
438=== modified file 'ubuntu_sso/utils/__init__.py'
439--- ubuntu_sso/utils/__init__.py 2010-11-15 23:06:52 +0000
440+++ ubuntu_sso/utils/__init__.py 2011-08-12 15:53:34 +0000
441@@ -17,3 +17,38 @@
442 # with this program. If not, see <http://www.gnu.org/licenses/>.
443
444 """Utility modules that may find use outside ubuntu_sso."""
445+
446+import cgi
447+
448+from oauth import oauth
449+from urlparse import urlparse
450+
451+
452+def oauth_headers(url, credentials, http_method='GET'):
453+ """Sign 'url' using 'credentials'.
454+
455+ * 'url' must be a valid unicode url.
456+ * 'credentials' must be a valid OAuth token.
457+
458+ Return oauth headers that can be pass to any Request like object.
459+
460+ """
461+ assert isinstance(url, unicode)
462+ url = url.encode('utf-8')
463+ _, _, _, _, query, _ = urlparse(url)
464+ parameters = dict(cgi.parse_qsl(query))
465+
466+ consumer = oauth.OAuthConsumer(credentials['consumer_key'],
467+ credentials['consumer_secret'])
468+ token = oauth.OAuthToken(credentials['token'],
469+ credentials['token_secret'])
470+ kwargs = dict(oauth_consumer=consumer, token=token,
471+ http_method=http_method, http_url=url,
472+ parameters=parameters)
473+ get_request = oauth.OAuthRequest.from_consumer_and_token
474+ oauth_req = get_request(**kwargs)
475+ hmac_sha1 = oauth.OAuthSignatureMethod_HMAC_SHA1()
476+ oauth_req.sign_request(hmac_sha1, consumer, token)
477+ headers = oauth_req.to_header()
478+
479+ return headers
480
481=== added file 'ubuntu_sso/utils/tests/test_oauth_headers.py'
482--- ubuntu_sso/utils/tests/test_oauth_headers.py 1970-01-01 00:00:00 +0000
483+++ ubuntu_sso/utils/tests/test_oauth_headers.py 2011-08-12 15:53:34 +0000
484@@ -0,0 +1,109 @@
485+# -*- coding: utf-8 -*-
486+
487+# Author: Natalia B. Bidart <natalia.bidart@canonical.com>
488+#
489+# Copyright 2011 Canonical Ltd.
490+#
491+# This program is free software: you can redistribute it and/or modify it
492+# under the terms of the GNU General Public License version 3, as published
493+# by the Free Software Foundation.
494+#
495+# This program is distributed in the hope that it will be useful, but
496+# WITHOUT ANY WARRANTY; without even the implied warranties of
497+# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
498+# PURPOSE. See the GNU General Public License for more details.
499+#
500+# You should have received a copy of the GNU General Public License along
501+# with this program. If not, see <http://www.gnu.org/licenses/>.
502+
503+"""Tests for the oauth_headers helper function."""
504+
505+from twisted.trial.unittest import TestCase
506+
507+from ubuntu_sso.utils import oauth, oauth_headers
508+from ubuntu_sso.tests import TOKEN
509+
510+
511+class FakedOAuthRequest(object):
512+ """Replace the OAuthRequest class."""
513+
514+ params = {}
515+
516+ def __init__(self):
517+ self.sign_request = lambda *args, **kwargs: None
518+ self.to_header = lambda *args, **kwargs: {}
519+
520+ def from_consumer_and_token(oauth_consumer, **kwargs):
521+ """Fake the method storing the params for check."""
522+ FakedOAuthRequest.params.update(kwargs)
523+ return FakedOAuthRequest()
524+ from_consumer_and_token = staticmethod(from_consumer_and_token)
525+
526+
527+class SignWithCredentialsTestCase(TestCase):
528+ """Test suite for the oauth_headers method."""
529+
530+ url = u'http://example.com'
531+
532+ def build_header(self, url, http_method='GET'):
533+ """Build an Oauth header for comparison."""
534+ consumer = oauth.OAuthConsumer(TOKEN['consumer_key'],
535+ TOKEN['consumer_secret'])
536+ token = oauth.OAuthToken(TOKEN['token'],
537+ TOKEN['token_secret'])
538+ get_request = oauth.OAuthRequest.from_consumer_and_token
539+ oauth_req = get_request(oauth_consumer=consumer, token=token,
540+ http_method=http_method, http_url=url)
541+ oauth_req.sign_request(oauth.OAuthSignatureMethod_HMAC_SHA1(),
542+ consumer, token)
543+ return oauth_req.to_header()
544+
545+ def dictify_header(self, header):
546+ """Convert an OAuth header into a dict."""
547+ result = {}
548+ fields = header.split(', ')
549+ for field in fields:
550+ key, value = field.split('=')
551+ result[key] = value.strip('"')
552+
553+ return result
554+
555+ def assert_header_equal(self, expected, actual):
556+ """Is 'expected' equals to 'actual'?"""
557+ expected = self.dictify_header(expected['Authorization'])
558+ actual = self.dictify_header(actual['Authorization'])
559+ for header in (expected, actual):
560+ header.pop('oauth_nonce')
561+ header.pop('oauth_timestamp')
562+ header.pop('oauth_signature')
563+
564+ self.assertEqual(expected, actual)
565+
566+ def assert_method_called(self, path, query_str='', http_method='GET'):
567+ """Assert that the url build by joining 'paths' was called."""
568+ expected = (self.url, path, query_str)
569+ expected = ''.join(expected).encode('utf8')
570+ expected = self.build_header(expected, http_method=http_method)
571+ actual = oauth_headers(url=self.url + path, credentials=TOKEN)
572+ self.assert_header_equal(expected, actual)
573+
574+ def test_call(self):
575+ """Calling 'get' triggers an OAuth signed GET request."""
576+ path = u'/test/'
577+ self.assert_method_called(path)
578+
579+ def test_quotes_path(self):
580+ """Calling 'get' quotes the path."""
581+ path = u'/test me more, sí!/'
582+ self.assert_method_called(path)
583+
584+ def test_adds_parameters_to_oauth_request(self):
585+ """The query string from the path is used in the oauth request."""
586+ self.patch(oauth, 'OAuthRequest', FakedOAuthRequest)
587+
588+ path = u'/test/something?foo=bar'
589+ oauth_headers(url=self.url + path, credentials=TOKEN)
590+
591+ self.assertIn('parameters', FakedOAuthRequest.params)
592+ self.assertEqual(FakedOAuthRequest.params['parameters'],
593+ {'foo': 'bar'})

Subscribers

People subscribed via source and target branches