blob: 95ca0660f5814098025631394b66740ac8680df1 [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 Gregorioccc79542011-02-19 00:05:26 -050028import unittest
29import urlparse
Joe Gregorioe1de4162011-02-23 11:30:29 -050030
Joe Gregorioccc79542011-02-19 00:05:26 -050031try:
32 from urlparse import parse_qs
33except ImportError:
34 from cgi import parse_qs
35
36from apiclient.http import HttpMockSequence
Joe Gregorio549230c2012-01-11 10:38:05 -050037from oauth2client.anyjson import simplejson
Joe Gregorioccc79542011-02-19 00:05:26 -050038from oauth2client.client import AccessTokenCredentials
39from oauth2client.client import AccessTokenCredentialsError
40from oauth2client.client import AccessTokenRefreshError
JacobMoshenko8e905102011-06-20 09:53:10 -040041from oauth2client.client import AssertionCredentials
Joe Gregorio08cdcb82012-03-14 00:09:33 -040042from oauth2client.client import Credentials
Joe Gregorioccc79542011-02-19 00:05:26 -050043from oauth2client.client import FlowExchangeError
Joe Gregorio08cdcb82012-03-14 00:09:33 -040044from oauth2client.client import MemoryCache
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 Gregorio8b4c1732011-12-06 11:28:29 -050048from oauth2client.client import VerifyJwtTokenError
49from oauth2client.client import _extract_id_token
Joe Gregorioccc79542011-02-19 00:05:26 -050050
51
Joe Gregorio08cdcb82012-03-14 00:09:33 -040052class CredentialsTests(unittest.TestCase):
53
54 def test_to_from_json(self):
55 credentials = Credentials()
56 json = credentials.to_json()
57 restored = Credentials.new_from_json(json)
58
59
Joe Gregorioccc79542011-02-19 00:05:26 -050060class OAuth2CredentialsTests(unittest.TestCase):
61
62 def setUp(self):
63 access_token = "foo"
64 client_id = "some_client_id"
65 client_secret = "cOuDdkfjxxnv+"
66 refresh_token = "1/0/a.df219fjls0"
Joe Gregorio562b7312011-09-15 09:06:38 -040067 token_expiry = datetime.datetime.utcnow()
Joe Gregorioccc79542011-02-19 00:05:26 -050068 token_uri = "https://www.google.com/accounts/o8/oauth2/token"
69 user_agent = "refresh_checker/1.0"
70 self.credentials = OAuth2Credentials(
71 access_token, client_id, client_secret,
72 refresh_token, token_expiry, token_uri,
73 user_agent)
74
75 def test_token_refresh_success(self):
76 http = HttpMockSequence([
77 ({'status': '401'}, ''),
78 ({'status': '200'}, '{"access_token":"1/3w","expires_in":3600}'),
79 ({'status': '200'}, 'echo_request_headers'),
80 ])
81 http = self.credentials.authorize(http)
82 resp, content = http.request("http://example.com")
Joe Gregorio654f4a22012-02-09 14:15:44 -050083 self.assertEqual('Bearer 1/3w', content['Authorization'])
Joe Gregorio08cdcb82012-03-14 00:09:33 -040084 self.assertFalse(self.credentials.access_token_expired)
Joe Gregorioccc79542011-02-19 00:05:26 -050085
86 def test_token_refresh_failure(self):
87 http = HttpMockSequence([
88 ({'status': '401'}, ''),
89 ({'status': '400'}, '{"error":"access_denied"}'),
90 ])
91 http = self.credentials.authorize(http)
92 try:
93 http.request("http://example.com")
94 self.fail("should raise AccessTokenRefreshError exception")
95 except AccessTokenRefreshError:
96 pass
Joe Gregorio08cdcb82012-03-14 00:09:33 -040097 self.assertTrue(self.credentials.access_token_expired)
Joe Gregorioccc79542011-02-19 00:05:26 -050098
99 def test_non_401_error_response(self):
100 http = HttpMockSequence([
101 ({'status': '400'}, ''),
102 ])
103 http = self.credentials.authorize(http)
104 resp, content = http.request("http://example.com")
105 self.assertEqual(400, resp.status)
106
Joe Gregorio562b7312011-09-15 09:06:38 -0400107 def test_to_from_json(self):
108 json = self.credentials.to_json()
109 instance = OAuth2Credentials.from_json(json)
Joe Gregorio654f4a22012-02-09 14:15:44 -0500110 self.assertEqual(OAuth2Credentials, type(instance))
Joe Gregorio1daa71b2011-09-15 18:12:14 -0400111 instance.token_expiry = None
112 self.credentials.token_expiry = None
113
Joe Gregorio654f4a22012-02-09 14:15:44 -0500114 self.assertEqual(instance.__dict__, self.credentials.__dict__)
Joe Gregorio562b7312011-09-15 09:06:38 -0400115
Joe Gregorioccc79542011-02-19 00:05:26 -0500116
117class AccessTokenCredentialsTests(unittest.TestCase):
118
119 def setUp(self):
120 access_token = "foo"
121 user_agent = "refresh_checker/1.0"
122 self.credentials = AccessTokenCredentials(access_token, user_agent)
123
124 def test_token_refresh_success(self):
125 http = HttpMockSequence([
126 ({'status': '401'}, ''),
127 ])
128 http = self.credentials.authorize(http)
129 try:
130 resp, content = http.request("http://example.com")
131 self.fail("should throw exception if token expires")
132 except AccessTokenCredentialsError:
133 pass
134 except Exception:
135 self.fail("should only throw AccessTokenCredentialsError")
136
137 def test_non_401_error_response(self):
138 http = HttpMockSequence([
139 ({'status': '400'}, ''),
140 ])
141 http = self.credentials.authorize(http)
Joe Gregorio83cd4392011-06-20 10:11:35 -0400142 resp, content = http.request('http://example.com')
Joe Gregorioccc79542011-02-19 00:05:26 -0500143 self.assertEqual(400, resp.status)
144
Joe Gregorio83cd4392011-06-20 10:11:35 -0400145 def test_auth_header_sent(self):
146 http = HttpMockSequence([
147 ({'status': '200'}, 'echo_request_headers'),
148 ])
149 http = self.credentials.authorize(http)
150 resp, content = http.request('http://example.com')
Joe Gregorio654f4a22012-02-09 14:15:44 -0500151 self.assertEqual('Bearer foo', content['Authorization'])
Joe Gregorioccc79542011-02-19 00:05:26 -0500152
Joe Gregorio8b4c1732011-12-06 11:28:29 -0500153
JacobMoshenko8e905102011-06-20 09:53:10 -0400154class TestAssertionCredentials(unittest.TestCase):
155 assertion_text = "This is the assertion"
156 assertion_type = "http://www.google.com/assertionType"
157
158 class AssertionCredentialsTestImpl(AssertionCredentials):
159
160 def _generate_assertion(self):
161 return TestAssertionCredentials.assertion_text
162
163 def setUp(self):
164 user_agent = "fun/2.0"
165 self.credentials = self.AssertionCredentialsTestImpl(self.assertion_type,
166 user_agent)
167
168 def test_assertion_body(self):
169 body = urlparse.parse_qs(self.credentials._generate_refresh_request_body())
Joe Gregorio654f4a22012-02-09 14:15:44 -0500170 self.assertEqual(self.assertion_text, body['assertion'][0])
171 self.assertEqual(self.assertion_type, body['assertion_type'][0])
JacobMoshenko8e905102011-06-20 09:53:10 -0400172
173 def test_assertion_refresh(self):
174 http = HttpMockSequence([
175 ({'status': '200'}, '{"access_token":"1/3w"}'),
176 ({'status': '200'}, 'echo_request_headers'),
177 ])
178 http = self.credentials.authorize(http)
179 resp, content = http.request("http://example.com")
Joe Gregorio654f4a22012-02-09 14:15:44 -0500180 self.assertEqual('Bearer 1/3w', content['Authorization'])
JacobMoshenko8e905102011-06-20 09:53:10 -0400181
182
Joe Gregorio8b4c1732011-12-06 11:28:29 -0500183class ExtractIdTokenText(unittest.TestCase):
184 """Tests _extract_id_token()."""
185
186 def test_extract_success(self):
187 body = {'foo': 'bar'}
188 payload = base64.urlsafe_b64encode(simplejson.dumps(body)).strip('=')
189 jwt = 'stuff.' + payload + '.signature'
190
191 extracted = _extract_id_token(jwt)
Joe Gregorio654f4a22012-02-09 14:15:44 -0500192 self.assertEqual(extracted, body)
Joe Gregorio8b4c1732011-12-06 11:28:29 -0500193
194 def test_extract_failure(self):
195 body = {'foo': 'bar'}
196 payload = base64.urlsafe_b64encode(simplejson.dumps(body)).strip('=')
197 jwt = 'stuff.' + payload
198
199 self.assertRaises(VerifyJwtTokenError, _extract_id_token, jwt)
200
Joe Gregorioccc79542011-02-19 00:05:26 -0500201class OAuth2WebServerFlowTest(unittest.TestCase):
202
203 def setUp(self):
204 self.flow = OAuth2WebServerFlow(
205 client_id='client_id+1',
206 client_secret='secret+1',
207 scope='foo',
208 user_agent='unittest-sample/1.0',
209 )
210
211 def test_construct_authorize_url(self):
Joe Gregoriof2326c02012-02-09 12:18:44 -0500212 authorize_url = self.flow.step1_get_authorize_url('OOB_CALLBACK_URN')
Joe Gregorioccc79542011-02-19 00:05:26 -0500213
214 parsed = urlparse.urlparse(authorize_url)
215 q = parse_qs(parsed[4])
Joe Gregorio654f4a22012-02-09 14:15:44 -0500216 self.assertEqual('client_id+1', q['client_id'][0])
217 self.assertEqual('code', q['response_type'][0])
218 self.assertEqual('foo', q['scope'][0])
219 self.assertEqual('OOB_CALLBACK_URN', q['redirect_uri'][0])
220 self.assertEqual('offline', q['access_type'][0])
Joe Gregorio69a0aca2011-11-03 10:47:32 -0400221
222 def test_override_flow_access_type(self):
223 """Passing access_type overrides the default."""
224 flow = OAuth2WebServerFlow(
225 client_id='client_id+1',
226 client_secret='secret+1',
227 scope='foo',
228 user_agent='unittest-sample/1.0',
229 access_type='online'
230 )
Joe Gregoriof2326c02012-02-09 12:18:44 -0500231 authorize_url = flow.step1_get_authorize_url('OOB_CALLBACK_URN')
Joe Gregorio69a0aca2011-11-03 10:47:32 -0400232
233 parsed = urlparse.urlparse(authorize_url)
234 q = parse_qs(parsed[4])
Joe Gregorio654f4a22012-02-09 14:15:44 -0500235 self.assertEqual('client_id+1', q['client_id'][0])
236 self.assertEqual('code', q['response_type'][0])
237 self.assertEqual('foo', q['scope'][0])
238 self.assertEqual('OOB_CALLBACK_URN', q['redirect_uri'][0])
239 self.assertEqual('online', q['access_type'][0])
Joe Gregorioccc79542011-02-19 00:05:26 -0500240
241 def test_exchange_failure(self):
242 http = HttpMockSequence([
JacobMoshenko8e905102011-06-20 09:53:10 -0400243 ({'status': '400'}, '{"error":"invalid_request"}'),
Joe Gregorioccc79542011-02-19 00:05:26 -0500244 ])
245
246 try:
247 credentials = self.flow.step2_exchange('some random code', http)
248 self.fail("should raise exception if exchange doesn't get 200")
249 except FlowExchangeError:
250 pass
251
252 def test_exchange_success(self):
253 http = HttpMockSequence([
254 ({'status': '200'},
255 """{ "access_token":"SlAV32hkKG",
256 "expires_in":3600,
257 "refresh_token":"8xLOxBtZp8" }"""),
258 ])
259
260 credentials = self.flow.step2_exchange('some random code', http)
Joe Gregorio654f4a22012-02-09 14:15:44 -0500261 self.assertEqual('SlAV32hkKG', credentials.access_token)
262 self.assertNotEqual(None, credentials.token_expiry)
263 self.assertEqual('8xLOxBtZp8', credentials.refresh_token)
Joe Gregorioccc79542011-02-19 00:05:26 -0500264
Joe Gregorioccc79542011-02-19 00:05:26 -0500265 def test_exchange_no_expires_in(self):
266 http = HttpMockSequence([
267 ({'status': '200'}, """{ "access_token":"SlAV32hkKG",
268 "refresh_token":"8xLOxBtZp8" }"""),
269 ])
270
271 credentials = self.flow.step2_exchange('some random code', http)
Joe Gregorio654f4a22012-02-09 14:15:44 -0500272 self.assertEqual(None, credentials.token_expiry)
Joe Gregorioccc79542011-02-19 00:05:26 -0500273
Joe Gregorio8b4c1732011-12-06 11:28:29 -0500274 def test_exchange_id_token_fail(self):
275 http = HttpMockSequence([
276 ({'status': '200'}, """{ "access_token":"SlAV32hkKG",
277 "refresh_token":"8xLOxBtZp8",
278 "id_token": "stuff.payload"}"""),
279 ])
280
281 self.assertRaises(VerifyJwtTokenError, self.flow.step2_exchange,
282 'some random code', http)
283
284 def test_exchange_id_token_fail(self):
285 body = {'foo': 'bar'}
286 payload = base64.urlsafe_b64encode(simplejson.dumps(body)).strip('=')
Joe Gregoriobd512b52011-12-06 15:39:26 -0500287 jwt = (base64.urlsafe_b64encode('stuff')+ '.' + payload + '.' +
288 base64.urlsafe_b64encode('signature'))
Joe Gregorio8b4c1732011-12-06 11:28:29 -0500289
290 http = HttpMockSequence([
291 ({'status': '200'}, """{ "access_token":"SlAV32hkKG",
292 "refresh_token":"8xLOxBtZp8",
293 "id_token": "%s"}""" % jwt),
294 ])
295
296 credentials = self.flow.step2_exchange('some random code', http)
Joe Gregorio654f4a22012-02-09 14:15:44 -0500297 self.assertEqual(credentials.id_token, body)
Joe Gregorio8b4c1732011-12-06 11:28:29 -0500298
Joe Gregorioccc79542011-02-19 00:05:26 -0500299
Joe Gregorio08cdcb82012-03-14 00:09:33 -0400300class MemoryCacheTests(unittest.TestCase):
301
302 def test_get_set_delete(self):
303 m = MemoryCache()
304 self.assertEqual(None, m.get('foo'))
305 self.assertEqual(None, m.delete('foo'))
306 m.set('foo', 'bar')
307 self.assertEqual('bar', m.get('foo'))
308 m.delete('foo')
309 self.assertEqual(None, m.get('foo'))
310
311
Joe Gregorioccc79542011-02-19 00:05:26 -0500312if __name__ == '__main__':
313 unittest.main()