[mq]: id_token2
diff --git a/tests/data/certs.json b/tests/data/certs.json
new file mode 100644
index 0000000..fa09416
--- /dev/null
+++ b/tests/data/certs.json
@@ -0,0 +1,3 @@
+{
+"foo": "-----BEGIN CERTIFICATE-----\r\nMIIDIzCCAgugAwIBAgIJAMfISuBQ5m+5MA0GCSqGSIb3DQEBBQUAMBUxEzARBgNV\r\nBAMTCnVuaXQtdGVzdHMwHhcNMTExMjA2MTYyNjAyWhcNMjExMjAzMTYyNjAyWjAV\r\nMRMwEQYDVQQDEwp1bml0LXRlc3RzMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB\r\nCgKCAQEA4ej0p7bQ7L/r4rVGUz9RN4VQWoej1Bg1mYWIDYslvKrk1gpj7wZgkdmM\r\n7oVK2OfgrSj/FCTkInKPqaCR0gD7K80q+mLBrN3PUkDrJQZpvRZIff3/xmVU1Wer\r\nuQLFJjnFb2dqu0s/FY/2kWiJtBCakXvXEOb7zfbINuayL+MSsCGSdVYsSliS5qQp\r\ngyDap+8b5fpXZVJkq92hrcNtbkg7hCYUJczt8n9hcCTJCfUpApvaFQ18pe+zpyl4\r\n+WzkP66I28hniMQyUlA1hBiskT7qiouq0m8IOodhv2fagSZKjOTTU2xkSBc//fy3\r\nZpsL7WqgsZS7Q+0VRK8gKfqkxg5OYQIDAQABo3YwdDAdBgNVHQ4EFgQU2RQ8yO+O\r\ngN8oVW2SW7RLrfYd9jEwRQYDVR0jBD4wPIAU2RQ8yO+OgN8oVW2SW7RLrfYd9jGh\r\nGaQXMBUxEzARBgNVBAMTCnVuaXQtdGVzdHOCCQDHyErgUOZvuTAMBgNVHRMEBTAD\r\nAQH/MA0GCSqGSIb3DQEBBQUAA4IBAQBRv+M/6+FiVu7KXNjFI5pSN17OcW5QUtPr\r\nodJMlWrJBtynn/TA1oJlYu3yV5clc/71Vr/AxuX5xGP+IXL32YDF9lTUJXG/uUGk\r\n+JETpKmQviPbRsvzYhz4pf6ZIOZMc3/GIcNq92ECbseGO+yAgyWUVKMmZM0HqXC9\r\novNslqe0M8C1sLm1zAR5z/h/litE7/8O2ietija3Q/qtl2TOXJdCA6sgjJX2WUql\r\nybrC55ct18NKf3qhpcEkGQvFU40rVYApJpi98DiZPYFdx1oBDp/f4uZ3ojpxRVFT\r\ncDwcJLfNRCPUhormsY7fDS9xSyThiHsW9mjJYdcaKQkwYZ0F11yB\r\n-----END CERTIFICATE-----\r\n"
+}
diff --git a/tests/data/create-private-keys.sh b/tests/data/create-private-keys.sh
new file mode 100644
index 0000000..b21cbdb
--- /dev/null
+++ b/tests/data/create-private-keys.sh
@@ -0,0 +1,9 @@
+#!/bin/bash
+
+openssl req -new -newkey rsa:2048 -days 3650 -nodes -x509 \
+ -keyout privatekey.pem -out publickey.pem \
+ -subj "/CN=unit-tests"
+
+openssl pkcs12 -export -out privatekey.p12 \
+ -inkey privatekey.pem -in publickey.pem \
+ -name "key" -passout pass:notasecret
diff --git a/tests/data/privatekey.p12 b/tests/data/privatekey.p12
new file mode 100644
index 0000000..c369ecb
--- /dev/null
+++ b/tests/data/privatekey.p12
Binary files differ
diff --git a/tests/data/privatekey.pem b/tests/data/privatekey.pem
new file mode 100644
index 0000000..5744354
--- /dev/null
+++ b/tests/data/privatekey.pem
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpAIBAAKCAQEA4ej0p7bQ7L/r4rVGUz9RN4VQWoej1Bg1mYWIDYslvKrk1gpj
+7wZgkdmM7oVK2OfgrSj/FCTkInKPqaCR0gD7K80q+mLBrN3PUkDrJQZpvRZIff3/
+xmVU1WeruQLFJjnFb2dqu0s/FY/2kWiJtBCakXvXEOb7zfbINuayL+MSsCGSdVYs
+SliS5qQpgyDap+8b5fpXZVJkq92hrcNtbkg7hCYUJczt8n9hcCTJCfUpApvaFQ18
+pe+zpyl4+WzkP66I28hniMQyUlA1hBiskT7qiouq0m8IOodhv2fagSZKjOTTU2xk
+SBc//fy3ZpsL7WqgsZS7Q+0VRK8gKfqkxg5OYQIDAQABAoIBAQDGGHzQxGKX+ANk
+nQi53v/c6632dJKYXVJC+PDAz4+bzU800Y+n/bOYsWf/kCp94XcG4Lgsdd0Gx+Zq
+HD9CI1IcqqBRR2AFscsmmX6YzPLTuEKBGMW8twaYy3utlFxElMwoUEsrSWRcCA1y
+nHSDzTt871c7nxCXHxuZ6Nm/XCL7Bg8uidRTSC1sQrQyKgTPhtQdYrPQ4WZ1A4J9
+IisyDYmZodSNZe5P+LTJ6M1SCgH8KH9ZGIxv3diMwzNNpk3kxJc9yCnja4mjiGE2
+YCNusSycU5IhZwVeCTlhQGcNeV/skfg64xkiJE34c2y2ttFbdwBTPixStGaF09nU
+Z422D40BAoGBAPvVyRRsC3BF+qZdaSMFwI1yiXY7vQw5+JZh01tD28NuYdRFzjcJ
+vzT2n8LFpj5ZfZFvSMLMVEFVMgQvWnN0O6xdXvGov6qlRUSGaH9u+TCPNnIldjMP
+B8+xTwFMqI7uQr54wBB+Poq7dVRP+0oHb0NYAwUBXoEuvYo3c/nDoRcZAoGBAOWl
+aLHjMv4CJbArzT8sPfic/8waSiLV9Ixs3Re5YREUTtnLq7LoymqB57UXJB3BNz/2
+eCueuW71avlWlRtE/wXASj5jx6y5mIrlV4nZbVuyYff0QlcG+fgb6pcJQuO9DxMI
+aqFGrWP3zye+LK87a6iR76dS9vRU+bHZpSVvGMKJAoGAFGt3TIKeQtJJyqeUWNSk
+klORNdcOMymYMIlqG+JatXQD1rR6ThgqOt8sgRyJqFCVT++YFMOAqXOBBLnaObZZ
+CFbh1fJ66BlSjoXff0W+SuOx5HuJJAa5+WtFHrPajwxeuRcNa8jwxUsB7n41wADu
+UqWWSRedVBg4Ijbw3nWwYDECgYB0pLew4z4bVuvdt+HgnJA9n0EuYowVdadpTEJg
+soBjNHV4msLzdNqbjrAqgz6M/n8Ztg8D2PNHMNDNJPVHjJwcR7duSTA6w2p/4k28
+bvvk/45Ta3XmzlxZcZSOct3O31Cw0i2XDVc018IY5be8qendDYM08icNo7vQYkRH
+504kQQKBgQDjx60zpz8ozvm1XAj0wVhi7GwXe+5lTxiLi9Fxq721WDxPMiHDW2XL
+YXfFVy/9/GIMvEiGYdmarK1NW+VhWl1DC5xhDg0kvMfxplt4tynoq1uTsQTY31Mx
+BeF5CT/JuNYk3bEBF0H/Q3VGO1/ggVS+YezdFbLWIRoMnLj6XCFEGg==
+-----END RSA PRIVATE KEY-----
diff --git a/tests/data/publickey.pem b/tests/data/publickey.pem
new file mode 100644
index 0000000..7af6ca3
--- /dev/null
+++ b/tests/data/publickey.pem
@@ -0,0 +1,19 @@
+-----BEGIN CERTIFICATE-----
+MIIDIzCCAgugAwIBAgIJAMfISuBQ5m+5MA0GCSqGSIb3DQEBBQUAMBUxEzARBgNV
+BAMTCnVuaXQtdGVzdHMwHhcNMTExMjA2MTYyNjAyWhcNMjExMjAzMTYyNjAyWjAV
+MRMwEQYDVQQDEwp1bml0LXRlc3RzMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB
+CgKCAQEA4ej0p7bQ7L/r4rVGUz9RN4VQWoej1Bg1mYWIDYslvKrk1gpj7wZgkdmM
+7oVK2OfgrSj/FCTkInKPqaCR0gD7K80q+mLBrN3PUkDrJQZpvRZIff3/xmVU1Wer
+uQLFJjnFb2dqu0s/FY/2kWiJtBCakXvXEOb7zfbINuayL+MSsCGSdVYsSliS5qQp
+gyDap+8b5fpXZVJkq92hrcNtbkg7hCYUJczt8n9hcCTJCfUpApvaFQ18pe+zpyl4
++WzkP66I28hniMQyUlA1hBiskT7qiouq0m8IOodhv2fagSZKjOTTU2xkSBc//fy3
+ZpsL7WqgsZS7Q+0VRK8gKfqkxg5OYQIDAQABo3YwdDAdBgNVHQ4EFgQU2RQ8yO+O
+gN8oVW2SW7RLrfYd9jEwRQYDVR0jBD4wPIAU2RQ8yO+OgN8oVW2SW7RLrfYd9jGh
+GaQXMBUxEzARBgNVBAMTCnVuaXQtdGVzdHOCCQDHyErgUOZvuTAMBgNVHRMEBTAD
+AQH/MA0GCSqGSIb3DQEBBQUAA4IBAQBRv+M/6+FiVu7KXNjFI5pSN17OcW5QUtPr
+odJMlWrJBtynn/TA1oJlYu3yV5clc/71Vr/AxuX5xGP+IXL32YDF9lTUJXG/uUGk
++JETpKmQviPbRsvzYhz4pf6ZIOZMc3/GIcNq92ECbseGO+yAgyWUVKMmZM0HqXC9
+ovNslqe0M8C1sLm1zAR5z/h/litE7/8O2ietija3Q/qtl2TOXJdCA6sgjJX2WUql
+ybrC55ct18NKf3qhpcEkGQvFU40rVYApJpi98DiZPYFdx1oBDp/f4uZ3ojpxRVFT
+cDwcJLfNRCPUhormsY7fDS9xSyThiHsW9mjJYdcaKQkwYZ0F11yB
+-----END CERTIFICATE-----
diff --git a/tests/test_oauth2client.py b/tests/test_oauth2client.py
index a23d1da..9acc9cf 100644
--- a/tests/test_oauth2client.py
+++ b/tests/test_oauth2client.py
@@ -22,6 +22,7 @@
__author__ = 'jcgregorio@google.com (Joe Gregorio)'
+import base64
import datetime
import httplib2
import unittest
@@ -50,6 +51,8 @@
from oauth2client.client import FlowExchangeError
from oauth2client.client import OAuth2Credentials
from oauth2client.client import OAuth2WebServerFlow
+from oauth2client.client import VerifyJwtTokenError
+from oauth2client.client import _extract_id_token
class OAuth2CredentialsTests(unittest.TestCase):
@@ -143,6 +146,7 @@
resp, content = http.request('http://example.com')
self.assertEqual(content['authorization'], 'OAuth foo')
+
class TestAssertionCredentials(unittest.TestCase):
assertion_text = "This is the assertion"
assertion_type = "http://www.google.com/assertionType"
@@ -172,6 +176,24 @@
self.assertEqual(content['authorization'], 'OAuth 1/3w')
+class ExtractIdTokenText(unittest.TestCase):
+ """Tests _extract_id_token()."""
+
+ def test_extract_success(self):
+ body = {'foo': 'bar'}
+ payload = base64.urlsafe_b64encode(simplejson.dumps(body)).strip('=')
+ jwt = 'stuff.' + payload + '.signature'
+
+ extracted = _extract_id_token(jwt)
+ self.assertEqual(body, extracted)
+
+ def test_extract_failure(self):
+ body = {'foo': 'bar'}
+ payload = base64.urlsafe_b64encode(simplejson.dumps(body)).strip('=')
+ jwt = 'stuff.' + payload
+
+ self.assertRaises(VerifyJwtTokenError, _extract_id_token, jwt)
+
class OAuth2WebServerFlowTest(unittest.TestCase):
def setUp(self):
@@ -245,6 +267,30 @@
credentials = self.flow.step2_exchange('some random code', http)
self.assertEqual(credentials.token_expiry, None)
+ def test_exchange_id_token_fail(self):
+ http = HttpMockSequence([
+ ({'status': '200'}, """{ "access_token":"SlAV32hkKG",
+ "refresh_token":"8xLOxBtZp8",
+ "id_token": "stuff.payload"}"""),
+ ])
+
+ self.assertRaises(VerifyJwtTokenError, self.flow.step2_exchange,
+ 'some random code', http)
+
+ def test_exchange_id_token_fail(self):
+ body = {'foo': 'bar'}
+ payload = base64.urlsafe_b64encode(simplejson.dumps(body)).strip('=')
+ jwt = 'stuff.' + payload + '.signature'
+
+ http = HttpMockSequence([
+ ({'status': '200'}, """{ "access_token":"SlAV32hkKG",
+ "refresh_token":"8xLOxBtZp8",
+ "id_token": "%s"}""" % jwt),
+ ])
+
+ credentials = self.flow.step2_exchange('some random code', http)
+ self.assertEquals(body, credentials.id_token)
+
if __name__ == '__main__':
unittest.main()
diff --git a/tests/test_oauth2client_appengine.py b/tests/test_oauth2client_appengine.py
index 8d8064c..ca9d940 100644
--- a/tests/test_oauth2client_appengine.py
+++ b/tests/test_oauth2client_appengine.py
@@ -32,6 +32,9 @@
except ImportError:
from cgi import parse_qs
+import dev_appserver
+dev_appserver.fix_sys_path()
+
from apiclient.anyjson import simplejson
from apiclient.http import HttpMockSequence
from google.appengine.api import apiproxy_stub
diff --git a/tests/test_oauth2client_django_orm.py b/tests/test_oauth2client_django_orm.py
index 67ec314..dbd2e20 100644
--- a/tests/test_oauth2client_django_orm.py
+++ b/tests/test_oauth2client_django_orm.py
@@ -73,3 +73,7 @@
def test_field_pickled(self):
prep_value = self.field.get_db_prep_value(self.flow, connection=None)
self.assertEqual(prep_value, self.pickle)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/test_oauth2client_jwt.py b/tests/test_oauth2client_jwt.py
new file mode 100644
index 0000000..01a7331
--- /dev/null
+++ b/tests/test_oauth2client_jwt.py
@@ -0,0 +1,211 @@
+#!/usr/bin/python2.4
+#
+# Copyright 2010 Google Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+"""Oauth2client tests
+
+Unit tests for oauth2client.
+"""
+
+__author__ = 'jcgregorio@google.com (Joe Gregorio)'
+
+import httplib2
+import os
+import sys
+import time
+import unittest
+import urlparse
+
+try:
+ from urlparse import parse_qs
+except ImportError:
+ from cgi import parse_qs
+
+try: # pragma: no cover
+ import simplejson
+except ImportError: # pragma: no cover
+ try:
+ # Try to import from django, should work on App Engine
+ from django.utils import simplejson
+ except ImportError:
+ # Should work for Python2.6 and higher.
+ import json as simplejson
+
+from apiclient.http import HttpMockSequence
+from oauth2client import crypt
+from oauth2client.client import SignedJwtAssertionCredentials
+from oauth2client.client import verify_id_token
+from oauth2client.client import VerifyJwtTokenError
+
+
+def datafile(filename):
+ f = open(os.path.join(os.path.dirname(__file__), 'data', filename), 'r')
+ data = f.read()
+ f.close()
+ return data
+
+
+class CryptTests(unittest.TestCase):
+
+ def test_sign_and_verify(self):
+ private_key = datafile('privatekey.p12')
+ public_key = datafile('publickey.pem')
+
+ signer = crypt.Signer.from_string(private_key)
+ signature = signer.sign('foo')
+
+ verifier = crypt.Verifier.from_string(public_key, True)
+
+ self.assertTrue(verifier.verify('foo', signature))
+
+ self.assertFalse(verifier.verify('bar', signature))
+ self.assertFalse(verifier.verify('foo', 'bad signagure'))
+
+ def _check_jwt_failure(self, jwt, expected_error):
+ try:
+ public_key = datafile('publickey.pem')
+ certs = {'foo': public_key}
+ audience = 'https://www.googleapis.com/auth/id?client_id=' + \
+ 'external_public_key@testing.gserviceaccount.com'
+ contents = crypt.verify_signed_jwt_with_certs(jwt, certs, audience)
+ self.fail('Should have thrown for %s' % jwt)
+ except:
+ e = sys.exc_info()[1]
+ msg = e.args[0]
+ self.assertTrue(expected_error in msg)
+
+ def _create_signed_jwt(self):
+ private_key = datafile('privatekey.p12')
+ signer = crypt.Signer.from_string(private_key)
+ audience = 'some_audience_address@testing.gserviceaccount.com'
+ now = long(time.time())
+
+ return crypt.make_signed_jwt(
+ signer,
+ {
+ 'aud': audience,
+ 'iat': now,
+ 'exp': now + 300,
+ 'user': 'billy bob',
+ 'metadata': {'meta': 'data'},
+ })
+
+ def test_verify_id_token(self):
+ jwt = self._create_signed_jwt()
+ public_key = datafile('publickey.pem')
+ certs = {'foo': public_key }
+ audience = 'some_audience_address@testing.gserviceaccount.com'
+ contents = crypt.verify_signed_jwt_with_certs(jwt, certs, audience)
+ self.assertEquals('billy bob', contents['user'])
+ self.assertEquals('data', contents['metadata']['meta'])
+
+ def test_verify_id_token_with_certs_uri(self):
+ jwt = self._create_signed_jwt()
+
+ http = HttpMockSequence([
+ ({'status': '200'}, datafile('certs.json')),
+ ])
+
+ contents = verify_id_token(jwt,
+ 'some_audience_address@testing.gserviceaccount.com', http)
+ self.assertEquals('billy bob', contents['user'])
+ self.assertEquals('data', contents['metadata']['meta'])
+
+
+ def test_verify_id_token_with_certs_uri_fails(self):
+ jwt = self._create_signed_jwt()
+
+ http = HttpMockSequence([
+ ({'status': '404'}, datafile('certs.json')),
+ ])
+
+ self.assertRaises(VerifyJwtTokenError, verify_id_token, jwt,
+ 'some_audience_address@testing.gserviceaccount.com', http)
+
+ def test_verify_id_token_bad_tokens(self):
+ private_key = datafile('privatekey.p12')
+
+ # Wrong number of segments
+ self._check_jwt_failure('foo', 'Wrong number of segments')
+
+ # Not json
+ self._check_jwt_failure('foo.bar.baz',
+ 'Can\'t parse token')
+
+ # Bad signature
+ jwt = 'foo.%s.baz' % crypt._urlsafe_b64encode('{"a":"b"}')
+ self._check_jwt_failure(jwt, 'Invalid token signature')
+
+ # No expiration
+ signer = crypt.Signer.from_string(private_key)
+ audience = 'https:#www.googleapis.com/auth/id?client_id=' + \
+ 'external_public_key@testing.gserviceaccount.com'
+ jwt = crypt.make_signed_jwt(signer, {
+ 'aud': 'audience',
+ 'iat': time.time(),
+ }
+ )
+ self._check_jwt_failure(jwt, 'No exp field in token')
+
+ # No issued at
+ jwt = crypt.make_signed_jwt(signer, {
+ 'aud': 'audience',
+ 'exp': time.time() + 400,
+ }
+ )
+ self._check_jwt_failure(jwt, 'No iat field in token')
+
+ # Too early
+ jwt = crypt.make_signed_jwt(signer, {
+ 'aud': 'audience',
+ 'iat': time.time() + 301,
+ 'exp': time.time() + 400,
+ })
+ self._check_jwt_failure(jwt, 'Token used too early')
+
+ # Too late
+ jwt = crypt.make_signed_jwt(signer, {
+ 'aud': 'audience',
+ 'iat': time.time() - 500,
+ 'exp': time.time() - 301,
+ })
+ self._check_jwt_failure(jwt, 'Token used too late')
+
+ # Wrong target
+ jwt = crypt.make_signed_jwt(signer, {
+ 'aud': 'somebody else',
+ 'iat': time.time(),
+ 'exp': time.time() + 300,
+ })
+ self._check_jwt_failure(jwt, 'Wrong recipient')
+
+ def test_signed_jwt_assertion_credentials(self):
+ private_key = datafile('privatekey.p12')
+ credentials = SignedJwtAssertionCredentials(
+ 'some_account@example.com',
+ private_key,
+ scope='read+write',
+ prn='joe@example.org')
+ http = HttpMockSequence([
+ ({'status': '200'}, '{"access_token":"1/3w","expires_in":3600}'),
+ ({'status': '200'}, 'echo_request_headers'),
+ ])
+ http = credentials.authorize(http)
+ resp, content = http.request('http://example.org')
+ self.assertEquals(content['authorization'], 'OAuth 1/3w')
+
+if __name__ == '__main__':
+ unittest.main()