blob: fd06668fc0b82d5543dc03e917a4ec2a988005bf [file] [log] [blame]
Joe Gregorio432f17e2011-05-22 23:18:00 -04001#!/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
18"""Discovery document tests
19
20Unit tests for objects created from discovery documents.
21"""
22
23__author__ = 'jcgregorio@google.com (Joe Gregorio)'
24
JacobMoshenko8e905102011-06-20 09:53:10 -040025import base64
Joe Gregorio432f17e2011-05-22 23:18:00 -040026import httplib2
27import unittest
28import urlparse
29
30try:
31 from urlparse import parse_qs
32except ImportError:
33 from cgi import parse_qs
34
Joe Gregorio432f17e2011-05-22 23:18:00 -040035from apiclient.anyjson import simplejson
JacobMoshenko8e905102011-06-20 09:53:10 -040036from apiclient.http import HttpMockSequence
37from google.appengine.api import apiproxy_stub
38from google.appengine.api import apiproxy_stub_map
39from google.appengine.api import users
40from google.appengine.ext import testbed
41from google.appengine.ext import webapp
Joe Gregorio432f17e2011-05-22 23:18:00 -040042from oauth2client.client import AccessTokenRefreshError
43from oauth2client.client import FlowExchangeError
JacobMoshenko8e905102011-06-20 09:53:10 -040044from oauth2client.appengine import AppAssertionCredentials
Joe Gregorio432f17e2011-05-22 23:18:00 -040045from oauth2client.appengine import OAuth2Decorator
Joe Gregorio432f17e2011-05-22 23:18:00 -040046from oauth2client.appengine import OAuth2Handler
JacobMoshenko8e905102011-06-20 09:53:10 -040047from webtest import TestApp
Joe Gregorio432f17e2011-05-22 23:18:00 -040048
49
50class UserMock(object):
51 """Mock the app engine user service"""
JacobMoshenko8e905102011-06-20 09:53:10 -040052
Joe Gregorio432f17e2011-05-22 23:18:00 -040053 def user_id(self):
54 return 'foo_user'
55
56
57class Http2Mock(object):
58 """Mock httplib2.Http"""
59 status = 200
60 content = {
61 'access_token': 'foo_access_token',
62 'refresh_token': 'foo_refresh_token',
JacobMoshenko8e905102011-06-20 09:53:10 -040063 'expires_in': 3600,
Joe Gregorio432f17e2011-05-22 23:18:00 -040064 }
65
66 def request(self, token_uri, method, body, headers, *args, **kwargs):
67 self.body = body
68 self.headers = headers
69 return (self, simplejson.dumps(self.content))
70
71
JacobMoshenko8e905102011-06-20 09:53:10 -040072class TestAppAssertionCredentials(unittest.TestCase):
73 account_name = "service_account_name@appspot.com"
74 signature = "signature"
75
76 class AppIdentityStubImpl(apiproxy_stub.APIProxyStub):
77
78 def __init__(self):
79 super(TestAppAssertionCredentials.AppIdentityStubImpl, self).__init__(
80 'app_identity_service')
81
82 def _Dynamic_GetServiceAccountName(self, request, response):
83 return response.set_service_account_name(
84 TestAppAssertionCredentials.account_name)
85
86 def _Dynamic_SignForApp(self, request, response):
87 return response.set_signature_bytes(
88 TestAppAssertionCredentials.signature)
89
90 def setUp(self):
91 app_identity_stub = self.AppIdentityStubImpl()
92 apiproxy_stub_map.apiproxy.RegisterStub("app_identity_service",
93 app_identity_stub)
94
95 self.scope = "http://www.googleapis.com/scope"
JacobMoshenkocb6d8912011-07-08 13:35:15 -040096 self.credentials = AppAssertionCredentials(self.scope)
JacobMoshenko8e905102011-06-20 09:53:10 -040097
98 def test_assertion(self):
99 assertion = self.credentials._generate_assertion()
100
101 parts = assertion.split(".")
102 self.assertTrue(len(parts) == 3)
103
104 header, body, signature = [base64.b64decode(part) for part in parts]
105
106 header_dict = simplejson.loads(header)
107 self.assertEqual(header_dict['typ'], 'JWT')
108 self.assertEqual(header_dict['alg'], 'RS256')
109
110 body_dict = simplejson.loads(body)
111 self.assertEqual(body_dict['aud'],
112 'https://accounts.google.com/o/oauth2/token')
113 self.assertEqual(body_dict['scope'], self.scope)
114 self.assertEqual(body_dict['iss'], self.account_name)
115
116 issuedAt = body_dict['iat']
117 self.assertTrue(issuedAt > 0)
118 self.assertEqual(body_dict['exp'], issuedAt + 3600)
119
120 self.assertEqual(signature, self.signature)
121
122
Joe Gregorio432f17e2011-05-22 23:18:00 -0400123class DecoratorTests(unittest.TestCase):
124
125 def setUp(self):
126 self.testbed = testbed.Testbed()
127 self.testbed.activate()
128 self.testbed.init_datastore_v3_stub()
129 self.testbed.init_memcache_stub()
130 self.testbed.init_user_stub()
131
132 decorator = OAuth2Decorator(client_id='foo_client_id',
133 client_secret='foo_client_secret',
JacobMoshenkocb6d8912011-07-08 13:35:15 -0400134 scope='foo_scope')
Joe Gregorio432f17e2011-05-22 23:18:00 -0400135 self.decorator = decorator
136
Joe Gregorio432f17e2011-05-22 23:18:00 -0400137 class TestRequiredHandler(webapp.RequestHandler):
JacobMoshenko8e905102011-06-20 09:53:10 -0400138
Joe Gregorio432f17e2011-05-22 23:18:00 -0400139 @decorator.oauth_required
140 def get(self):
141 pass
142
Joe Gregorio432f17e2011-05-22 23:18:00 -0400143 class TestAwareHandler(webapp.RequestHandler):
JacobMoshenko8e905102011-06-20 09:53:10 -0400144
Joe Gregorio432f17e2011-05-22 23:18:00 -0400145 @decorator.oauth_aware
146 def get(self):
147 self.response.out.write('Hello World!')
148
149
150 application = webapp.WSGIApplication([('/oauth2callback', OAuth2Handler),
151 ('/foo_path', TestRequiredHandler),
152 ('/bar_path', TestAwareHandler)],
153 debug=True)
154 self.app = TestApp(application)
155 users.get_current_user = UserMock
Joe Gregorio922b78c2011-05-26 21:36:34 -0400156 self.httplib2_orig = httplib2.Http
Joe Gregorio432f17e2011-05-22 23:18:00 -0400157 httplib2.Http = Http2Mock
158
159 def tearDown(self):
160 self.testbed.deactivate()
Joe Gregorio922b78c2011-05-26 21:36:34 -0400161 httplib2.Http = self.httplib2_orig
Joe Gregorio432f17e2011-05-22 23:18:00 -0400162
163 def test_required(self):
164 # An initial request to an oauth_required decorated path should be a
165 # redirect to start the OAuth dance.
166 response = self.app.get('/foo_path')
167 self.assertTrue(response.status.startswith('302'))
168 q = parse_qs(response.headers['Location'].split('?', 1)[1])
169 self.assertEqual('http://localhost/oauth2callback', q['redirect_uri'][0])
170 self.assertEqual('foo_client_id', q['client_id'][0])
171 self.assertEqual('foo_scope', q['scope'][0])
172 self.assertEqual('http://localhost/foo_path', q['state'][0])
173 self.assertEqual('code', q['response_type'][0])
174 self.assertEqual(False, self.decorator.has_credentials())
175
176 # Now simulate the callback to /oauth2callback
177 response = self.app.get('/oauth2callback', {
178 'code': 'foo_access_code',
JacobMoshenko8e905102011-06-20 09:53:10 -0400179 'state': 'foo_path',
Joe Gregorio432f17e2011-05-22 23:18:00 -0400180 })
181 self.assertEqual('http://localhost/foo_path', response.headers['Location'])
182 self.assertEqual(None, self.decorator.credentials)
183
184 # Now requesting the decorated path should work
185 response = self.app.get('/foo_path')
186 self.assertEqual('200 OK', response.status)
187 self.assertEqual(True, self.decorator.has_credentials())
JacobMoshenko8e905102011-06-20 09:53:10 -0400188 self.assertEqual('foo_refresh_token',
189 self.decorator.credentials.refresh_token)
190 self.assertEqual('foo_access_token',
191 self.decorator.credentials.access_token)
Joe Gregorio432f17e2011-05-22 23:18:00 -0400192
193 # Invalidate the stored Credentials
194 self.decorator.credentials._invalid = True
195 self.decorator.credentials.store(self.decorator.credentials)
196
197 # Invalid Credentials should start the OAuth dance again
198 response = self.app.get('/foo_path')
199 self.assertTrue(response.status.startswith('302'))
200 q = parse_qs(response.headers['Location'].split('?', 1)[1])
201 self.assertEqual('http://localhost/oauth2callback', q['redirect_uri'][0])
202
203 def test_aware(self):
204 # An initial request to an oauth_aware decorated path should not redirect
205 response = self.app.get('/bar_path')
206 self.assertEqual('Hello World!', response.body)
207 self.assertEqual('200 OK', response.status)
208 self.assertEqual(False, self.decorator.has_credentials())
209 url = self.decorator.authorize_url()
210 q = parse_qs(url.split('?', 1)[1])
211 self.assertEqual('http://localhost/oauth2callback', q['redirect_uri'][0])
212 self.assertEqual('foo_client_id', q['client_id'][0])
213 self.assertEqual('foo_scope', q['scope'][0])
214 self.assertEqual('http://localhost/bar_path', q['state'][0])
215 self.assertEqual('code', q['response_type'][0])
216
217 # Now simulate the callback to /oauth2callback
218 url = self.decorator.authorize_url()
219 response = self.app.get('/oauth2callback', {
220 'code': 'foo_access_code',
JacobMoshenko8e905102011-06-20 09:53:10 -0400221 'state': 'bar_path',
Joe Gregorio432f17e2011-05-22 23:18:00 -0400222 })
223 self.assertEqual('http://localhost/bar_path', response.headers['Location'])
224 self.assertEqual(False, self.decorator.has_credentials())
225
226 # Now requesting the decorated path will have credentials
227 response = self.app.get('/bar_path')
228 self.assertEqual('200 OK', response.status)
229 self.assertEqual('Hello World!', response.body)
230 self.assertEqual(True, self.decorator.has_credentials())
JacobMoshenko8e905102011-06-20 09:53:10 -0400231 self.assertEqual('foo_refresh_token',
232 self.decorator.credentials.refresh_token)
233 self.assertEqual('foo_access_token',
234 self.decorator.credentials.access_token)
Joe Gregorio432f17e2011-05-22 23:18:00 -0400235
236if __name__ == '__main__':
237 unittest.main()