blob: 9acc9cf5fa6bc7df90cc13113c3a8f114718ac49 [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
Joe Gregorio562b7312011-09-15 09:06:38 -040036try: # pragma: no cover
37 import simplejson
38except ImportError: # pragma: no cover
39 try:
40 # Try to import from django, should work on App Engine
41 from django.utils import simplejson
42 except ImportError:
43 # Should work for Python2.6 and higher.
44 import json as simplejson
45
Joe Gregorioccc79542011-02-19 00:05:26 -050046from apiclient.http import HttpMockSequence
47from oauth2client.client import AccessTokenCredentials
48from oauth2client.client import AccessTokenCredentialsError
49from oauth2client.client import AccessTokenRefreshError
JacobMoshenko8e905102011-06-20 09:53:10 -040050from oauth2client.client import AssertionCredentials
Joe Gregorioccc79542011-02-19 00:05:26 -050051from oauth2client.client import FlowExchangeError
52from oauth2client.client import OAuth2Credentials
53from oauth2client.client import OAuth2WebServerFlow
Joe Gregorio8b4c1732011-12-06 11:28:29 -050054from oauth2client.client import VerifyJwtTokenError
55from oauth2client.client import _extract_id_token
Joe Gregorioccc79542011-02-19 00:05:26 -050056
57
58class OAuth2CredentialsTests(unittest.TestCase):
59
60 def setUp(self):
61 access_token = "foo"
62 client_id = "some_client_id"
63 client_secret = "cOuDdkfjxxnv+"
64 refresh_token = "1/0/a.df219fjls0"
Joe Gregorio562b7312011-09-15 09:06:38 -040065 token_expiry = datetime.datetime.utcnow()
Joe Gregorioccc79542011-02-19 00:05:26 -050066 token_uri = "https://www.google.com/accounts/o8/oauth2/token"
67 user_agent = "refresh_checker/1.0"
68 self.credentials = OAuth2Credentials(
69 access_token, client_id, client_secret,
70 refresh_token, token_expiry, token_uri,
71 user_agent)
72
73 def test_token_refresh_success(self):
74 http = HttpMockSequence([
75 ({'status': '401'}, ''),
76 ({'status': '200'}, '{"access_token":"1/3w","expires_in":3600}'),
77 ({'status': '200'}, 'echo_request_headers'),
78 ])
79 http = self.credentials.authorize(http)
80 resp, content = http.request("http://example.com")
81 self.assertEqual(content['authorization'], 'OAuth 1/3w')
82
83 def test_token_refresh_failure(self):
84 http = HttpMockSequence([
85 ({'status': '401'}, ''),
86 ({'status': '400'}, '{"error":"access_denied"}'),
87 ])
88 http = self.credentials.authorize(http)
89 try:
90 http.request("http://example.com")
91 self.fail("should raise AccessTokenRefreshError exception")
92 except AccessTokenRefreshError:
93 pass
94
95 def test_non_401_error_response(self):
96 http = HttpMockSequence([
97 ({'status': '400'}, ''),
98 ])
99 http = self.credentials.authorize(http)
100 resp, content = http.request("http://example.com")
101 self.assertEqual(400, resp.status)
102
Joe Gregorio562b7312011-09-15 09:06:38 -0400103 def test_to_from_json(self):
104 json = self.credentials.to_json()
105 instance = OAuth2Credentials.from_json(json)
106 self.assertEquals(type(instance), OAuth2Credentials)
Joe Gregorio1daa71b2011-09-15 18:12:14 -0400107 instance.token_expiry = None
108 self.credentials.token_expiry = None
109
Joe Gregorio562b7312011-09-15 09:06:38 -0400110 self.assertEquals(self.credentials.__dict__, instance.__dict__)
111
Joe Gregorioccc79542011-02-19 00:05:26 -0500112
113class AccessTokenCredentialsTests(unittest.TestCase):
114
115 def setUp(self):
116 access_token = "foo"
117 user_agent = "refresh_checker/1.0"
118 self.credentials = AccessTokenCredentials(access_token, user_agent)
119
120 def test_token_refresh_success(self):
121 http = HttpMockSequence([
122 ({'status': '401'}, ''),
123 ])
124 http = self.credentials.authorize(http)
125 try:
126 resp, content = http.request("http://example.com")
127 self.fail("should throw exception if token expires")
128 except AccessTokenCredentialsError:
129 pass
130 except Exception:
131 self.fail("should only throw AccessTokenCredentialsError")
132
133 def test_non_401_error_response(self):
134 http = HttpMockSequence([
135 ({'status': '400'}, ''),
136 ])
137 http = self.credentials.authorize(http)
Joe Gregorio83cd4392011-06-20 10:11:35 -0400138 resp, content = http.request('http://example.com')
Joe Gregorioccc79542011-02-19 00:05:26 -0500139 self.assertEqual(400, resp.status)
140
Joe Gregorio83cd4392011-06-20 10:11:35 -0400141 def test_auth_header_sent(self):
142 http = HttpMockSequence([
143 ({'status': '200'}, 'echo_request_headers'),
144 ])
145 http = self.credentials.authorize(http)
146 resp, content = http.request('http://example.com')
147 self.assertEqual(content['authorization'], 'OAuth foo')
Joe Gregorioccc79542011-02-19 00:05:26 -0500148
Joe Gregorio8b4c1732011-12-06 11:28:29 -0500149
JacobMoshenko8e905102011-06-20 09:53:10 -0400150class TestAssertionCredentials(unittest.TestCase):
151 assertion_text = "This is the assertion"
152 assertion_type = "http://www.google.com/assertionType"
153
154 class AssertionCredentialsTestImpl(AssertionCredentials):
155
156 def _generate_assertion(self):
157 return TestAssertionCredentials.assertion_text
158
159 def setUp(self):
160 user_agent = "fun/2.0"
161 self.credentials = self.AssertionCredentialsTestImpl(self.assertion_type,
162 user_agent)
163
164 def test_assertion_body(self):
165 body = urlparse.parse_qs(self.credentials._generate_refresh_request_body())
166 self.assertEqual(body['assertion'][0], self.assertion_text)
167 self.assertEqual(body['assertion_type'][0], self.assertion_type)
168
169 def test_assertion_refresh(self):
170 http = HttpMockSequence([
171 ({'status': '200'}, '{"access_token":"1/3w"}'),
172 ({'status': '200'}, 'echo_request_headers'),
173 ])
174 http = self.credentials.authorize(http)
175 resp, content = http.request("http://example.com")
176 self.assertEqual(content['authorization'], 'OAuth 1/3w')
177
178
Joe Gregorio8b4c1732011-12-06 11:28:29 -0500179class ExtractIdTokenText(unittest.TestCase):
180 """Tests _extract_id_token()."""
181
182 def test_extract_success(self):
183 body = {'foo': 'bar'}
184 payload = base64.urlsafe_b64encode(simplejson.dumps(body)).strip('=')
185 jwt = 'stuff.' + payload + '.signature'
186
187 extracted = _extract_id_token(jwt)
188 self.assertEqual(body, extracted)
189
190 def test_extract_failure(self):
191 body = {'foo': 'bar'}
192 payload = base64.urlsafe_b64encode(simplejson.dumps(body)).strip('=')
193 jwt = 'stuff.' + payload
194
195 self.assertRaises(VerifyJwtTokenError, _extract_id_token, jwt)
196
Joe Gregorioccc79542011-02-19 00:05:26 -0500197class OAuth2WebServerFlowTest(unittest.TestCase):
198
199 def setUp(self):
200 self.flow = OAuth2WebServerFlow(
201 client_id='client_id+1',
202 client_secret='secret+1',
203 scope='foo',
204 user_agent='unittest-sample/1.0',
205 )
206
207 def test_construct_authorize_url(self):
208 authorize_url = self.flow.step1_get_authorize_url('oob')
209
210 parsed = urlparse.urlparse(authorize_url)
211 q = parse_qs(parsed[4])
212 self.assertEqual(q['client_id'][0], 'client_id+1')
213 self.assertEqual(q['response_type'][0], 'code')
214 self.assertEqual(q['scope'][0], 'foo')
215 self.assertEqual(q['redirect_uri'][0], 'oob')
Joe Gregorio69a0aca2011-11-03 10:47:32 -0400216 self.assertEqual(q['access_type'][0], 'offline')
217
218 def test_override_flow_access_type(self):
219 """Passing access_type overrides the default."""
220 flow = OAuth2WebServerFlow(
221 client_id='client_id+1',
222 client_secret='secret+1',
223 scope='foo',
224 user_agent='unittest-sample/1.0',
225 access_type='online'
226 )
227 authorize_url = flow.step1_get_authorize_url('oob')
228
229 parsed = urlparse.urlparse(authorize_url)
230 q = parse_qs(parsed[4])
231 self.assertEqual(q['client_id'][0], 'client_id+1')
232 self.assertEqual(q['response_type'][0], 'code')
233 self.assertEqual(q['scope'][0], 'foo')
234 self.assertEqual(q['redirect_uri'][0], 'oob')
235 self.assertEqual(q['access_type'][0], 'online')
Joe Gregorioccc79542011-02-19 00:05:26 -0500236
237 def test_exchange_failure(self):
238 http = HttpMockSequence([
JacobMoshenko8e905102011-06-20 09:53:10 -0400239 ({'status': '400'}, '{"error":"invalid_request"}'),
Joe Gregorioccc79542011-02-19 00:05:26 -0500240 ])
241
242 try:
243 credentials = self.flow.step2_exchange('some random code', http)
244 self.fail("should raise exception if exchange doesn't get 200")
245 except FlowExchangeError:
246 pass
247
248 def test_exchange_success(self):
249 http = HttpMockSequence([
250 ({'status': '200'},
251 """{ "access_token":"SlAV32hkKG",
252 "expires_in":3600,
253 "refresh_token":"8xLOxBtZp8" }"""),
254 ])
255
256 credentials = self.flow.step2_exchange('some random code', http)
257 self.assertEqual(credentials.access_token, 'SlAV32hkKG')
258 self.assertNotEqual(credentials.token_expiry, None)
259 self.assertEqual(credentials.refresh_token, '8xLOxBtZp8')
260
Joe Gregorioccc79542011-02-19 00:05:26 -0500261 def test_exchange_no_expires_in(self):
262 http = HttpMockSequence([
263 ({'status': '200'}, """{ "access_token":"SlAV32hkKG",
264 "refresh_token":"8xLOxBtZp8" }"""),
265 ])
266
267 credentials = self.flow.step2_exchange('some random code', http)
268 self.assertEqual(credentials.token_expiry, None)
269
Joe Gregorio8b4c1732011-12-06 11:28:29 -0500270 def test_exchange_id_token_fail(self):
271 http = HttpMockSequence([
272 ({'status': '200'}, """{ "access_token":"SlAV32hkKG",
273 "refresh_token":"8xLOxBtZp8",
274 "id_token": "stuff.payload"}"""),
275 ])
276
277 self.assertRaises(VerifyJwtTokenError, self.flow.step2_exchange,
278 'some random code', http)
279
280 def test_exchange_id_token_fail(self):
281 body = {'foo': 'bar'}
282 payload = base64.urlsafe_b64encode(simplejson.dumps(body)).strip('=')
283 jwt = 'stuff.' + payload + '.signature'
284
285 http = HttpMockSequence([
286 ({'status': '200'}, """{ "access_token":"SlAV32hkKG",
287 "refresh_token":"8xLOxBtZp8",
288 "id_token": "%s"}""" % jwt),
289 ])
290
291 credentials = self.flow.step2_exchange('some random code', http)
292 self.assertEquals(body, credentials.id_token)
293
Joe Gregorioccc79542011-02-19 00:05:26 -0500294
295if __name__ == '__main__':
296 unittest.main()