blob: f824f60ed475db3ac73677521087776b09ede2cc [file] [log] [blame]
Joe Gregorioccc79542011-02-19 00:05:26 -05001#!/usr/bin/python2.4
2#
3# Copyright 2010 Google Inc.
4#
5# Licensed under the Apache License, Version 2.0 (the "License");
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9# http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16
17
Joe Gregorio0bc70912011-05-24 15:30:49 -040018"""Oauth2client tests
Joe Gregorioccc79542011-02-19 00:05:26 -050019
Joe Gregorio0bc70912011-05-24 15:30:49 -040020Unit tests for oauth2client.
Joe Gregorioccc79542011-02-19 00:05:26 -050021"""
22
23__author__ = 'jcgregorio@google.com (Joe Gregorio)'
24
Joe Gregorio8b4c1732011-12-06 11:28:29 -050025import base64
Joe Gregorio562b7312011-09-15 09:06:38 -040026import datetime
Joe Gregorioe1de4162011-02-23 11:30:29 -050027import httplib2
Joe Gregorio32d852d2012-06-14 09:08:18 -040028import os
Joe Gregorioccc79542011-02-19 00:05:26 -050029import unittest
30import urlparse
Joe Gregorioe1de4162011-02-23 11:30:29 -050031
Joe Gregorio83f2ee62012-12-06 15:25:54 -050032from apiclient.http import HttpMock
Joe Gregorioccc79542011-02-19 00:05:26 -050033from apiclient.http import HttpMockSequence
dhermes@google.coma9eb0bb2013-02-06 09:19:01 -080034from oauth2client import GOOGLE_REVOKE_URI
35from oauth2client import GOOGLE_TOKEN_URI
Joe Gregorio549230c2012-01-11 10:38:05 -050036from oauth2client.anyjson import simplejson
Joe Gregorioccc79542011-02-19 00:05:26 -050037from oauth2client.client import AccessTokenCredentials
38from oauth2client.client import AccessTokenCredentialsError
39from oauth2client.client import AccessTokenRefreshError
JacobMoshenko8e905102011-06-20 09:53:10 -040040from oauth2client.client import AssertionCredentials
Joe Gregorio08cdcb82012-03-14 00:09:33 -040041from oauth2client.client import Credentials
Joe Gregorioccc79542011-02-19 00:05:26 -050042from oauth2client.client import FlowExchangeError
Joe Gregorio08cdcb82012-03-14 00:09:33 -040043from oauth2client.client import MemoryCache
Joe Gregorio83f2ee62012-12-06 15:25:54 -050044from oauth2client.client import NonAsciiHeaderError
Joe Gregorioccc79542011-02-19 00:05:26 -050045from oauth2client.client import OAuth2Credentials
46from oauth2client.client import OAuth2WebServerFlow
Joe Gregoriof2326c02012-02-09 12:18:44 -050047from oauth2client.client import OOB_CALLBACK_URN
Joe Gregorio0bd8c412013-01-03 17:17:46 -050048from oauth2client.client import REFRESH_STATUS_CODES
dhermes@google.coma9eb0bb2013-02-06 09:19:01 -080049from oauth2client.client import Storage
50from oauth2client.client import TokenRevokeError
Joe Gregorio8b4c1732011-12-06 11:28:29 -050051from oauth2client.client import VerifyJwtTokenError
52from oauth2client.client import _extract_id_token
dhermes@google.coma9eb0bb2013-02-06 09:19:01 -080053from oauth2client.client import _update_query_params
Joe Gregorio32d852d2012-06-14 09:08:18 -040054from oauth2client.client import credentials_from_clientsecrets_and_code
Joe Gregorio83f2ee62012-12-06 15:25:54 -050055from oauth2client.client import credentials_from_code
Joe Gregorioc29aaa92012-07-16 16:16:31 -040056from oauth2client.client import flow_from_clientsecrets
Joe Gregorio0bd8c412013-01-03 17:17:46 -050057from oauth2client.clientsecrets import _loadfile
dhermes@google.coma9eb0bb2013-02-06 09:19:01 -080058from test_discovery import assertUrisEqual
59
Joe Gregorio32d852d2012-06-14 09:08:18 -040060
61DATA_DIR = os.path.join(os.path.dirname(__file__), 'data')
62
Joe Gregorio68a8cfe2012-08-03 16:17:40 -040063
Joe Gregorio32d852d2012-06-14 09:08:18 -040064def datafile(filename):
65 return os.path.join(DATA_DIR, filename)
Joe Gregorioccc79542011-02-19 00:05:26 -050066
Joe Gregorio68a8cfe2012-08-03 16:17:40 -040067
Joe Gregorioc29aaa92012-07-16 16:16:31 -040068def load_and_cache(existing_file, fakename, cache_mock):
69 client_type, client_info = _loadfile(datafile(existing_file))
70 cache_mock.cache[fakename] = {client_type: client_info}
71
Joe Gregorio68a8cfe2012-08-03 16:17:40 -040072
Joe Gregorioc29aaa92012-07-16 16:16:31 -040073class CacheMock(object):
74 def __init__(self):
75 self.cache = {}
76
77 def get(self, key, namespace=''):
78 # ignoring namespace for easier testing
79 return self.cache.get(key, None)
80
81 def set(self, key, value, namespace=''):
82 # ignoring namespace for easier testing
83 self.cache[key] = value
84
Joe Gregorioccc79542011-02-19 00:05:26 -050085
Joe Gregorio08cdcb82012-03-14 00:09:33 -040086class CredentialsTests(unittest.TestCase):
87
88 def test_to_from_json(self):
89 credentials = Credentials()
90 json = credentials.to_json()
91 restored = Credentials.new_from_json(json)
92
93
dhermes@google.coma9eb0bb2013-02-06 09:19:01 -080094class DummyDeleteStorage(Storage):
95 delete_called = False
96
97 def locked_delete(self):
98 self.delete_called = True
99
100
101def _token_revoke_test_helper(testcase, status, revoke_raise,
102 valid_bool_value, token_attr):
103 current_store = getattr(testcase.credentials, 'store', None)
104
105 dummy_store = DummyDeleteStorage()
106 testcase.credentials.set_store(dummy_store)
107
108 actual_do_revoke = testcase.credentials._do_revoke
109 testcase.token_from_revoke = None
110 def do_revoke_stub(http_request, token):
111 testcase.token_from_revoke = token
112 return actual_do_revoke(http_request, token)
113 testcase.credentials._do_revoke = do_revoke_stub
114
115 http = HttpMock(headers={'status': status})
116 if revoke_raise:
117 testcase.assertRaises(TokenRevokeError, testcase.credentials.revoke, http)
118 else:
119 testcase.credentials.revoke(http)
120
121 testcase.assertEqual(getattr(testcase.credentials, token_attr),
122 testcase.token_from_revoke)
123 testcase.assertEqual(valid_bool_value, testcase.credentials.invalid)
124 testcase.assertEqual(valid_bool_value, dummy_store.delete_called)
125
126 testcase.credentials.set_store(current_store)
127
128
Joe Gregorio83f2ee62012-12-06 15:25:54 -0500129class BasicCredentialsTests(unittest.TestCase):
Joe Gregorioccc79542011-02-19 00:05:26 -0500130
131 def setUp(self):
dhermes@google.coma9eb0bb2013-02-06 09:19:01 -0800132 access_token = 'foo'
133 client_id = 'some_client_id'
134 client_secret = 'cOuDdkfjxxnv+'
135 refresh_token = '1/0/a.df219fjls0'
Joe Gregorio562b7312011-09-15 09:06:38 -0400136 token_expiry = datetime.datetime.utcnow()
dhermes@google.coma9eb0bb2013-02-06 09:19:01 -0800137 user_agent = 'refresh_checker/1.0'
Joe Gregorioccc79542011-02-19 00:05:26 -0500138 self.credentials = OAuth2Credentials(
dhermes@google.coma9eb0bb2013-02-06 09:19:01 -0800139 access_token, client_id, client_secret,
140 refresh_token, token_expiry, GOOGLE_TOKEN_URI,
141 user_agent, revoke_uri=GOOGLE_REVOKE_URI)
Joe Gregorioccc79542011-02-19 00:05:26 -0500142
143 def test_token_refresh_success(self):
Joe Gregorio0bd8c412013-01-03 17:17:46 -0500144 for status_code in REFRESH_STATUS_CODES:
Joe Gregoriocda87522013-02-22 16:22:48 -0500145 token_response = {'access_token': '1/3w', 'expires_in': 3600}
Joe Gregorio7c7c6b12012-07-16 16:31:01 -0400146 http = HttpMockSequence([
147 ({'status': status_code}, ''),
Joe Gregoriocda87522013-02-22 16:22:48 -0500148 ({'status': '200'}, simplejson.dumps(token_response)),
Joe Gregorio7c7c6b12012-07-16 16:31:01 -0400149 ({'status': '200'}, 'echo_request_headers'),
150 ])
151 http = self.credentials.authorize(http)
dhermes@google.coma9eb0bb2013-02-06 09:19:01 -0800152 resp, content = http.request('http://example.com')
Joe Gregorio7c7c6b12012-07-16 16:31:01 -0400153 self.assertEqual('Bearer 1/3w', content['Authorization'])
154 self.assertFalse(self.credentials.access_token_expired)
Joe Gregoriocda87522013-02-22 16:22:48 -0500155 self.assertEqual(token_response, self.credentials.token_response)
Joe Gregorioccc79542011-02-19 00:05:26 -0500156
157 def test_token_refresh_failure(self):
Joe Gregorio0bd8c412013-01-03 17:17:46 -0500158 for status_code in REFRESH_STATUS_CODES:
Joe Gregorio7c7c6b12012-07-16 16:31:01 -0400159 http = HttpMockSequence([
160 ({'status': status_code}, ''),
161 ({'status': '400'}, '{"error":"access_denied"}'),
162 ])
163 http = self.credentials.authorize(http)
164 try:
dhermes@google.coma9eb0bb2013-02-06 09:19:01 -0800165 http.request('http://example.com')
166 self.fail('should raise AccessTokenRefreshError exception')
Joe Gregorio7c7c6b12012-07-16 16:31:01 -0400167 except AccessTokenRefreshError:
168 pass
169 self.assertTrue(self.credentials.access_token_expired)
Joe Gregoriocda87522013-02-22 16:22:48 -0500170 self.assertEqual(None, self.credentials.token_response)
Joe Gregorioccc79542011-02-19 00:05:26 -0500171
dhermes@google.coma9eb0bb2013-02-06 09:19:01 -0800172 def test_token_revoke_success(self):
173 _token_revoke_test_helper(
174 self, '200', revoke_raise=False,
175 valid_bool_value=True, token_attr='refresh_token')
176
177 def test_token_revoke_failure(self):
178 _token_revoke_test_helper(
179 self, '400', revoke_raise=True,
180 valid_bool_value=False, token_attr='refresh_token')
181
Joe Gregorioccc79542011-02-19 00:05:26 -0500182 def test_non_401_error_response(self):
183 http = HttpMockSequence([
184 ({'status': '400'}, ''),
185 ])
186 http = self.credentials.authorize(http)
dhermes@google.coma9eb0bb2013-02-06 09:19:01 -0800187 resp, content = http.request('http://example.com')
Joe Gregorioccc79542011-02-19 00:05:26 -0500188 self.assertEqual(400, resp.status)
Joe Gregoriocda87522013-02-22 16:22:48 -0500189 self.assertEqual(None, self.credentials.token_response)
Joe Gregorioccc79542011-02-19 00:05:26 -0500190
Joe Gregorio562b7312011-09-15 09:06:38 -0400191 def test_to_from_json(self):
192 json = self.credentials.to_json()
193 instance = OAuth2Credentials.from_json(json)
Joe Gregorio654f4a22012-02-09 14:15:44 -0500194 self.assertEqual(OAuth2Credentials, type(instance))
Joe Gregorio1daa71b2011-09-15 18:12:14 -0400195 instance.token_expiry = None
196 self.credentials.token_expiry = None
197
Joe Gregorio654f4a22012-02-09 14:15:44 -0500198 self.assertEqual(instance.__dict__, self.credentials.__dict__)
Joe Gregorio562b7312011-09-15 09:06:38 -0400199
Joe Gregorio83f2ee62012-12-06 15:25:54 -0500200 def test_no_unicode_in_request_params(self):
201 access_token = u'foo'
202 client_id = u'some_client_id'
203 client_secret = u'cOuDdkfjxxnv+'
204 refresh_token = u'1/0/a.df219fjls0'
205 token_expiry = unicode(datetime.datetime.utcnow())
dhermes@google.coma9eb0bb2013-02-06 09:19:01 -0800206 token_uri = unicode(GOOGLE_TOKEN_URI)
207 revoke_uri = unicode(GOOGLE_REVOKE_URI)
Joe Gregorio83f2ee62012-12-06 15:25:54 -0500208 user_agent = u'refresh_checker/1.0'
209 credentials = OAuth2Credentials(access_token, client_id, client_secret,
210 refresh_token, token_expiry, token_uri,
dhermes@google.coma9eb0bb2013-02-06 09:19:01 -0800211 user_agent, revoke_uri=revoke_uri)
Joe Gregorio83f2ee62012-12-06 15:25:54 -0500212
213 http = HttpMock(headers={'status': '200'})
214 http = credentials.authorize(http)
dhermes@google.coma9eb0bb2013-02-06 09:19:01 -0800215 http.request(u'http://example.com', method=u'GET', headers={u'foo': u'bar'})
Joe Gregorio83f2ee62012-12-06 15:25:54 -0500216 for k, v in http.headers.iteritems():
217 self.assertEqual(str, type(k))
218 self.assertEqual(str, type(v))
219
220 # Test again with unicode strings that can't simple be converted to ASCII.
221 try:
222 http.request(
223 u'http://example.com', method=u'GET', headers={u'foo': u'\N{COMET}'})
224 self.fail('Expected exception to be raised.')
225 except NonAsciiHeaderError:
226 pass
227
Joe Gregoriocda87522013-02-22 16:22:48 -0500228 self.credentials.token_response = 'foobar'
229 instance = OAuth2Credentials.from_json(self.credentials.to_json())
230 self.assertEqual('foobar', instance.token_response)
231
Joe Gregorioccc79542011-02-19 00:05:26 -0500232
233class AccessTokenCredentialsTests(unittest.TestCase):
234
235 def setUp(self):
dhermes@google.coma9eb0bb2013-02-06 09:19:01 -0800236 access_token = 'foo'
237 user_agent = 'refresh_checker/1.0'
238 self.credentials = AccessTokenCredentials(access_token, user_agent,
239 revoke_uri=GOOGLE_REVOKE_URI)
Joe Gregorioccc79542011-02-19 00:05:26 -0500240
241 def test_token_refresh_success(self):
Joe Gregorio0bd8c412013-01-03 17:17:46 -0500242 for status_code in REFRESH_STATUS_CODES:
Joe Gregorio7c7c6b12012-07-16 16:31:01 -0400243 http = HttpMockSequence([
244 ({'status': status_code}, ''),
245 ])
246 http = self.credentials.authorize(http)
247 try:
dhermes@google.coma9eb0bb2013-02-06 09:19:01 -0800248 resp, content = http.request('http://example.com')
249 self.fail('should throw exception if token expires')
Joe Gregorio7c7c6b12012-07-16 16:31:01 -0400250 except AccessTokenCredentialsError:
251 pass
252 except Exception:
dhermes@google.coma9eb0bb2013-02-06 09:19:01 -0800253 self.fail('should only throw AccessTokenCredentialsError')
254
255 def test_token_revoke_success(self):
256 _token_revoke_test_helper(
257 self, '200', revoke_raise=False,
258 valid_bool_value=True, token_attr='access_token')
259
260 def test_token_revoke_failure(self):
261 _token_revoke_test_helper(
262 self, '400', revoke_raise=True,
263 valid_bool_value=False, token_attr='access_token')
Joe Gregorioccc79542011-02-19 00:05:26 -0500264
265 def test_non_401_error_response(self):
266 http = HttpMockSequence([
267 ({'status': '400'}, ''),
268 ])
269 http = self.credentials.authorize(http)
Joe Gregorio83cd4392011-06-20 10:11:35 -0400270 resp, content = http.request('http://example.com')
Joe Gregorioccc79542011-02-19 00:05:26 -0500271 self.assertEqual(400, resp.status)
272
Joe Gregorio83cd4392011-06-20 10:11:35 -0400273 def test_auth_header_sent(self):
274 http = HttpMockSequence([
275 ({'status': '200'}, 'echo_request_headers'),
276 ])
277 http = self.credentials.authorize(http)
278 resp, content = http.request('http://example.com')
Joe Gregorio654f4a22012-02-09 14:15:44 -0500279 self.assertEqual('Bearer foo', content['Authorization'])
Joe Gregorioccc79542011-02-19 00:05:26 -0500280
Joe Gregorio8b4c1732011-12-06 11:28:29 -0500281
JacobMoshenko8e905102011-06-20 09:53:10 -0400282class TestAssertionCredentials(unittest.TestCase):
dhermes@google.coma9eb0bb2013-02-06 09:19:01 -0800283 assertion_text = 'This is the assertion'
284 assertion_type = 'http://www.google.com/assertionType'
JacobMoshenko8e905102011-06-20 09:53:10 -0400285
286 class AssertionCredentialsTestImpl(AssertionCredentials):
287
288 def _generate_assertion(self):
289 return TestAssertionCredentials.assertion_text
290
291 def setUp(self):
dhermes@google.coma9eb0bb2013-02-06 09:19:01 -0800292 user_agent = 'fun/2.0'
JacobMoshenko8e905102011-06-20 09:53:10 -0400293 self.credentials = self.AssertionCredentialsTestImpl(self.assertion_type,
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400294 user_agent=user_agent)
JacobMoshenko8e905102011-06-20 09:53:10 -0400295
296 def test_assertion_body(self):
297 body = urlparse.parse_qs(self.credentials._generate_refresh_request_body())
Joe Gregorio654f4a22012-02-09 14:15:44 -0500298 self.assertEqual(self.assertion_text, body['assertion'][0])
Joe Gregoriocdc350f2013-02-07 10:52:26 -0500299 self.assertEqual('urn:ietf:params:oauth:grant-type:jwt-bearer',
300 body['grant_type'][0])
JacobMoshenko8e905102011-06-20 09:53:10 -0400301
302 def test_assertion_refresh(self):
303 http = HttpMockSequence([
304 ({'status': '200'}, '{"access_token":"1/3w"}'),
305 ({'status': '200'}, 'echo_request_headers'),
306 ])
307 http = self.credentials.authorize(http)
dhermes@google.coma9eb0bb2013-02-06 09:19:01 -0800308 resp, content = http.request('http://example.com')
Joe Gregorio654f4a22012-02-09 14:15:44 -0500309 self.assertEqual('Bearer 1/3w', content['Authorization'])
JacobMoshenko8e905102011-06-20 09:53:10 -0400310
dhermes@google.coma9eb0bb2013-02-06 09:19:01 -0800311 def test_token_revoke_success(self):
312 _token_revoke_test_helper(
313 self, '200', revoke_raise=False,
314 valid_bool_value=True, token_attr='access_token')
JacobMoshenko8e905102011-06-20 09:53:10 -0400315
dhermes@google.coma9eb0bb2013-02-06 09:19:01 -0800316 def test_token_revoke_failure(self):
317 _token_revoke_test_helper(
318 self, '400', revoke_raise=True,
319 valid_bool_value=False, token_attr='access_token')
320
321
322class UpdateQueryParamsTest(unittest.TestCase):
323 def test_update_query_params_no_params(self):
324 uri = 'http://www.google.com'
325 updated = _update_query_params(uri, {'a': 'b'})
326 self.assertEqual(updated, uri + '?a=b')
327
328 def test_update_query_params_existing_params(self):
329 uri = 'http://www.google.com?x=y'
330 updated = _update_query_params(uri, {'a': 'b', 'c': 'd&'})
331 hardcoded_update = uri + '&a=b&c=d%26'
332 assertUrisEqual(self, updated, hardcoded_update)
333
334
335class ExtractIdTokenTest(unittest.TestCase):
Joe Gregorio8b4c1732011-12-06 11:28:29 -0500336 """Tests _extract_id_token()."""
337
338 def test_extract_success(self):
339 body = {'foo': 'bar'}
340 payload = base64.urlsafe_b64encode(simplejson.dumps(body)).strip('=')
341 jwt = 'stuff.' + payload + '.signature'
342
343 extracted = _extract_id_token(jwt)
Joe Gregorio654f4a22012-02-09 14:15:44 -0500344 self.assertEqual(extracted, body)
Joe Gregorio8b4c1732011-12-06 11:28:29 -0500345
346 def test_extract_failure(self):
347 body = {'foo': 'bar'}
348 payload = base64.urlsafe_b64encode(simplejson.dumps(body)).strip('=')
349 jwt = 'stuff.' + payload
350
351 self.assertRaises(VerifyJwtTokenError, _extract_id_token, jwt)
352
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400353
Joe Gregorioccc79542011-02-19 00:05:26 -0500354class OAuth2WebServerFlowTest(unittest.TestCase):
355
356 def setUp(self):
357 self.flow = OAuth2WebServerFlow(
358 client_id='client_id+1',
359 client_secret='secret+1',
360 scope='foo',
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400361 redirect_uri=OOB_CALLBACK_URN,
Joe Gregorioccc79542011-02-19 00:05:26 -0500362 user_agent='unittest-sample/1.0',
dhermes@google.coma9eb0bb2013-02-06 09:19:01 -0800363 revoke_uri='dummy_revoke_uri',
Joe Gregorioccc79542011-02-19 00:05:26 -0500364 )
365
366 def test_construct_authorize_url(self):
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400367 authorize_url = self.flow.step1_get_authorize_url()
Joe Gregorioccc79542011-02-19 00:05:26 -0500368
369 parsed = urlparse.urlparse(authorize_url)
dhermes@google.coma9eb0bb2013-02-06 09:19:01 -0800370 q = urlparse.parse_qs(parsed[4])
Joe Gregorio654f4a22012-02-09 14:15:44 -0500371 self.assertEqual('client_id+1', q['client_id'][0])
372 self.assertEqual('code', q['response_type'][0])
373 self.assertEqual('foo', q['scope'][0])
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400374 self.assertEqual(OOB_CALLBACK_URN, q['redirect_uri'][0])
Joe Gregorio654f4a22012-02-09 14:15:44 -0500375 self.assertEqual('offline', q['access_type'][0])
Joe Gregorio69a0aca2011-11-03 10:47:32 -0400376
Joe Gregorio32f73192012-10-23 16:13:44 -0400377 def test_override_flow_via_kwargs(self):
378 """Passing kwargs to override defaults."""
Joe Gregorio69a0aca2011-11-03 10:47:32 -0400379 flow = OAuth2WebServerFlow(
380 client_id='client_id+1',
381 client_secret='secret+1',
382 scope='foo',
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400383 redirect_uri=OOB_CALLBACK_URN,
Joe Gregorio69a0aca2011-11-03 10:47:32 -0400384 user_agent='unittest-sample/1.0',
Joe Gregorio32f73192012-10-23 16:13:44 -0400385 access_type='online',
386 response_type='token'
Joe Gregorio69a0aca2011-11-03 10:47:32 -0400387 )
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400388 authorize_url = flow.step1_get_authorize_url()
Joe Gregorio69a0aca2011-11-03 10:47:32 -0400389
390 parsed = urlparse.urlparse(authorize_url)
dhermes@google.coma9eb0bb2013-02-06 09:19:01 -0800391 q = urlparse.parse_qs(parsed[4])
Joe Gregorio654f4a22012-02-09 14:15:44 -0500392 self.assertEqual('client_id+1', q['client_id'][0])
Joe Gregorio32f73192012-10-23 16:13:44 -0400393 self.assertEqual('token', q['response_type'][0])
Joe Gregorio654f4a22012-02-09 14:15:44 -0500394 self.assertEqual('foo', q['scope'][0])
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400395 self.assertEqual(OOB_CALLBACK_URN, q['redirect_uri'][0])
Joe Gregorio654f4a22012-02-09 14:15:44 -0500396 self.assertEqual('online', q['access_type'][0])
Joe Gregorioccc79542011-02-19 00:05:26 -0500397
398 def test_exchange_failure(self):
399 http = HttpMockSequence([
JacobMoshenko8e905102011-06-20 09:53:10 -0400400 ({'status': '400'}, '{"error":"invalid_request"}'),
Joe Gregorioccc79542011-02-19 00:05:26 -0500401 ])
402
403 try:
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400404 credentials = self.flow.step2_exchange('some random code', http=http)
dhermes@google.coma9eb0bb2013-02-06 09:19:01 -0800405 self.fail('should raise exception if exchange doesn\'t get 200')
Joe Gregorioccc79542011-02-19 00:05:26 -0500406 except FlowExchangeError:
407 pass
408
Joe Gregorioddb969a2012-07-11 11:04:12 -0400409 def test_urlencoded_exchange_failure(self):
410 http = HttpMockSequence([
dhermes@google.coma9eb0bb2013-02-06 09:19:01 -0800411 ({'status': '400'}, 'error=invalid_request'),
Joe Gregorioddb969a2012-07-11 11:04:12 -0400412 ])
413
414 try:
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400415 credentials = self.flow.step2_exchange('some random code', http=http)
dhermes@google.coma9eb0bb2013-02-06 09:19:01 -0800416 self.fail('should raise exception if exchange doesn\'t get 200')
Joe Gregorioddb969a2012-07-11 11:04:12 -0400417 except FlowExchangeError, e:
418 self.assertEquals('invalid_request', str(e))
419
420 def test_exchange_failure_with_json_error(self):
dhermes@google.coma9eb0bb2013-02-06 09:19:01 -0800421 # Some providers have 'error' attribute as a JSON object
Joe Gregorioddb969a2012-07-11 11:04:12 -0400422 # in place of regular string.
423 # This test makes sure no strange object-to-string coversion
424 # exceptions are being raised instead of FlowExchangeError.
425 http = HttpMockSequence([
426 ({'status': '400'},
427 """ {"error": {
428 "type": "OAuthException",
429 "message": "Error validating verification code."} }"""),
430 ])
431
432 try:
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400433 credentials = self.flow.step2_exchange('some random code', http=http)
dhermes@google.coma9eb0bb2013-02-06 09:19:01 -0800434 self.fail('should raise exception if exchange doesn\'t get 200')
Joe Gregorioddb969a2012-07-11 11:04:12 -0400435 except FlowExchangeError, e:
436 pass
437
Joe Gregorioccc79542011-02-19 00:05:26 -0500438 def test_exchange_success(self):
439 http = HttpMockSequence([
440 ({'status': '200'},
441 """{ "access_token":"SlAV32hkKG",
442 "expires_in":3600,
443 "refresh_token":"8xLOxBtZp8" }"""),
444 ])
445
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400446 credentials = self.flow.step2_exchange('some random code', http=http)
Joe Gregorio654f4a22012-02-09 14:15:44 -0500447 self.assertEqual('SlAV32hkKG', credentials.access_token)
448 self.assertNotEqual(None, credentials.token_expiry)
449 self.assertEqual('8xLOxBtZp8', credentials.refresh_token)
dhermes@google.coma9eb0bb2013-02-06 09:19:01 -0800450 self.assertEqual('dummy_revoke_uri', credentials.revoke_uri)
Joe Gregorioccc79542011-02-19 00:05:26 -0500451
Joe Gregorioddb969a2012-07-11 11:04:12 -0400452 def test_urlencoded_exchange_success(self):
453 http = HttpMockSequence([
dhermes@google.coma9eb0bb2013-02-06 09:19:01 -0800454 ({'status': '200'}, 'access_token=SlAV32hkKG&expires_in=3600'),
Joe Gregorioddb969a2012-07-11 11:04:12 -0400455 ])
456
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400457 credentials = self.flow.step2_exchange('some random code', http=http)
Joe Gregorioddb969a2012-07-11 11:04:12 -0400458 self.assertEqual('SlAV32hkKG', credentials.access_token)
459 self.assertNotEqual(None, credentials.token_expiry)
460
461 def test_urlencoded_expires_param(self):
462 http = HttpMockSequence([
dhermes@google.coma9eb0bb2013-02-06 09:19:01 -0800463 # Note the 'expires=3600' where you'd normally
464 # have if named 'expires_in'
465 ({'status': '200'}, 'access_token=SlAV32hkKG&expires=3600'),
Joe Gregorioddb969a2012-07-11 11:04:12 -0400466 ])
467
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400468 credentials = self.flow.step2_exchange('some random code', http=http)
Joe Gregorioddb969a2012-07-11 11:04:12 -0400469 self.assertNotEqual(None, credentials.token_expiry)
470
Joe Gregorioccc79542011-02-19 00:05:26 -0500471 def test_exchange_no_expires_in(self):
472 http = HttpMockSequence([
473 ({'status': '200'}, """{ "access_token":"SlAV32hkKG",
474 "refresh_token":"8xLOxBtZp8" }"""),
475 ])
476
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400477 credentials = self.flow.step2_exchange('some random code', http=http)
Joe Gregorio654f4a22012-02-09 14:15:44 -0500478 self.assertEqual(None, credentials.token_expiry)
Joe Gregorioccc79542011-02-19 00:05:26 -0500479
Joe Gregorioddb969a2012-07-11 11:04:12 -0400480 def test_urlencoded_exchange_no_expires_in(self):
481 http = HttpMockSequence([
482 # This might be redundant but just to make sure
483 # urlencoded access_token gets parsed correctly
dhermes@google.coma9eb0bb2013-02-06 09:19:01 -0800484 ({'status': '200'}, 'access_token=SlAV32hkKG'),
Joe Gregorioddb969a2012-07-11 11:04:12 -0400485 ])
486
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400487 credentials = self.flow.step2_exchange('some random code', http=http)
Joe Gregorioddb969a2012-07-11 11:04:12 -0400488 self.assertEqual(None, credentials.token_expiry)
489
Joe Gregorio4b4002f2012-06-14 15:41:01 -0400490 def test_exchange_fails_if_no_code(self):
491 http = HttpMockSequence([
492 ({'status': '200'}, """{ "access_token":"SlAV32hkKG",
493 "refresh_token":"8xLOxBtZp8" }"""),
494 ])
495
496 code = {'error': 'thou shall not pass'}
497 try:
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400498 credentials = self.flow.step2_exchange(code, http=http)
Joe Gregorio4b4002f2012-06-14 15:41:01 -0400499 self.fail('should raise exception if no code in dictionary.')
500 except FlowExchangeError, e:
501 self.assertTrue('shall not pass' in str(e))
502
Joe Gregorio8b4c1732011-12-06 11:28:29 -0500503 def test_exchange_id_token_fail(self):
504 http = HttpMockSequence([
505 ({'status': '200'}, """{ "access_token":"SlAV32hkKG",
506 "refresh_token":"8xLOxBtZp8",
507 "id_token": "stuff.payload"}"""),
508 ])
509
510 self.assertRaises(VerifyJwtTokenError, self.flow.step2_exchange,
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400511 'some random code', http=http)
Joe Gregorio8b4c1732011-12-06 11:28:29 -0500512
513 def test_exchange_id_token_fail(self):
514 body = {'foo': 'bar'}
515 payload = base64.urlsafe_b64encode(simplejson.dumps(body)).strip('=')
Joe Gregoriobd512b52011-12-06 15:39:26 -0500516 jwt = (base64.urlsafe_b64encode('stuff')+ '.' + payload + '.' +
517 base64.urlsafe_b64encode('signature'))
Joe Gregorio8b4c1732011-12-06 11:28:29 -0500518
519 http = HttpMockSequence([
520 ({'status': '200'}, """{ "access_token":"SlAV32hkKG",
521 "refresh_token":"8xLOxBtZp8",
522 "id_token": "%s"}""" % jwt),
523 ])
524
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400525 credentials = self.flow.step2_exchange('some random code', http=http)
Joe Gregorio654f4a22012-02-09 14:15:44 -0500526 self.assertEqual(credentials.id_token, body)
Joe Gregorio8b4c1732011-12-06 11:28:29 -0500527
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400528
529class FlowFromCachedClientsecrets(unittest.TestCase):
Joe Gregorioc29aaa92012-07-16 16:16:31 -0400530
531 def test_flow_from_clientsecrets_cached(self):
532 cache_mock = CacheMock()
533 load_and_cache('client_secrets.json', 'some_secrets', cache_mock)
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400534
535 flow = flow_from_clientsecrets(
536 'some_secrets', '', redirect_uri='oob', cache=cache_mock)
Joe Gregorioc29aaa92012-07-16 16:16:31 -0400537 self.assertEquals('foo_client_secret', flow.client_secret)
538
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400539
Joe Gregorio32d852d2012-06-14 09:08:18 -0400540class CredentialsFromCodeTests(unittest.TestCase):
541 def setUp(self):
542 self.client_id = 'client_id_abc'
543 self.client_secret = 'secret_use_code'
544 self.scope = 'foo'
545 self.code = '12345abcde'
546 self.redirect_uri = 'postmessage'
547
548 def test_exchange_code_for_token(self):
dhermes@google.coma9eb0bb2013-02-06 09:19:01 -0800549 token = 'asdfghjkl'
550 payload =simplejson.dumps({'access_token': token, 'expires_in': 3600})
Joe Gregorio32d852d2012-06-14 09:08:18 -0400551 http = HttpMockSequence([
dhermes@google.coma9eb0bb2013-02-06 09:19:01 -0800552 ({'status': '200'}, payload),
Joe Gregorio32d852d2012-06-14 09:08:18 -0400553 ])
554 credentials = credentials_from_code(self.client_id, self.client_secret,
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400555 self.scope, self.code, redirect_uri=self.redirect_uri,
556 http=http)
dhermes@google.coma9eb0bb2013-02-06 09:19:01 -0800557 self.assertEquals(credentials.access_token, token)
Joe Gregorio32d852d2012-06-14 09:08:18 -0400558 self.assertNotEqual(None, credentials.token_expiry)
559
560 def test_exchange_code_for_token_fail(self):
561 http = HttpMockSequence([
562 ({'status': '400'}, '{"error":"invalid_request"}'),
563 ])
564
565 try:
566 credentials = credentials_from_code(self.client_id, self.client_secret,
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400567 self.scope, self.code, redirect_uri=self.redirect_uri,
568 http=http)
dhermes@google.coma9eb0bb2013-02-06 09:19:01 -0800569 self.fail('should raise exception if exchange doesn\'t get 200')
Joe Gregorio32d852d2012-06-14 09:08:18 -0400570 except FlowExchangeError:
571 pass
572
Joe Gregorio32d852d2012-06-14 09:08:18 -0400573 def test_exchange_code_and_file_for_token(self):
574 http = HttpMockSequence([
575 ({'status': '200'},
576 """{ "access_token":"asdfghjkl",
577 "expires_in":3600 }"""),
578 ])
579 credentials = credentials_from_clientsecrets_and_code(
580 datafile('client_secrets.json'), self.scope,
581 self.code, http=http)
582 self.assertEquals(credentials.access_token, 'asdfghjkl')
583 self.assertNotEqual(None, credentials.token_expiry)
584
Joe Gregorioc29aaa92012-07-16 16:16:31 -0400585 def test_exchange_code_and_cached_file_for_token(self):
586 http = HttpMockSequence([
587 ({'status': '200'}, '{ "access_token":"asdfghjkl"}'),
588 ])
589 cache_mock = CacheMock()
590 load_and_cache('client_secrets.json', 'some_secrets', cache_mock)
591
592 credentials = credentials_from_clientsecrets_and_code(
dhermes@google.coma9eb0bb2013-02-06 09:19:01 -0800593 'some_secrets', self.scope,
594 self.code, http=http, cache=cache_mock)
Joe Gregorioc29aaa92012-07-16 16:16:31 -0400595 self.assertEquals(credentials.access_token, 'asdfghjkl')
596
Joe Gregorio32d852d2012-06-14 09:08:18 -0400597 def test_exchange_code_and_file_for_token_fail(self):
598 http = HttpMockSequence([
599 ({'status': '400'}, '{"error":"invalid_request"}'),
600 ])
601
602 try:
603 credentials = credentials_from_clientsecrets_and_code(
604 datafile('client_secrets.json'), self.scope,
605 self.code, http=http)
dhermes@google.coma9eb0bb2013-02-06 09:19:01 -0800606 self.fail('should raise exception if exchange doesn\'t get 200')
Joe Gregorio32d852d2012-06-14 09:08:18 -0400607 except FlowExchangeError:
608 pass
609
610
Joe Gregorio08cdcb82012-03-14 00:09:33 -0400611class MemoryCacheTests(unittest.TestCase):
612
613 def test_get_set_delete(self):
614 m = MemoryCache()
615 self.assertEqual(None, m.get('foo'))
616 self.assertEqual(None, m.delete('foo'))
617 m.set('foo', 'bar')
618 self.assertEqual('bar', m.get('foo'))
619 m.delete('foo')
620 self.assertEqual(None, m.get('foo'))
621
622
Joe Gregorioccc79542011-02-19 00:05:26 -0500623if __name__ == '__main__':
624 unittest.main()