Add support for client_secrets.json file format.
Reviewed in http://codereview.appspot.com/4956059/.
This commit is contained in:
@@ -35,6 +35,8 @@ except ImportError: # pragma: no cover
|
||||
# Should work for Python2.6 and higher.
|
||||
import json as simplejson
|
||||
|
||||
import clientsecrets
|
||||
|
||||
from client import AccessTokenRefreshError
|
||||
from client import AssertionCredentials
|
||||
from client import Credentials
|
||||
@@ -52,6 +54,11 @@ from google.appengine.ext.webapp.util import run_wsgi_app
|
||||
OAUTH2CLIENT_NAMESPACE = 'oauth2client#ns'
|
||||
|
||||
|
||||
class InvalidClientSecretsError(Exception):
|
||||
"""The client_secrets.json file is malformed or missing required fields."""
|
||||
pass
|
||||
|
||||
|
||||
class AppAssertionCredentials(AssertionCredentials):
|
||||
"""Credentials object for App Engine Assertion Grants
|
||||
|
||||
@@ -303,7 +310,8 @@ class OAuth2Decorator(object):
|
||||
|
||||
def __init__(self, client_id, client_secret, scope,
|
||||
auth_uri='https://accounts.google.com/o/oauth2/auth',
|
||||
token_uri='https://accounts.google.com/o/oauth2/token'):
|
||||
token_uri='https://accounts.google.com/o/oauth2/token',
|
||||
message=None):
|
||||
|
||||
"""Constructor for OAuth2Decorator
|
||||
|
||||
@@ -315,11 +323,21 @@ class OAuth2Decorator(object):
|
||||
defaults to Google's endpoints but any OAuth 2.0 provider can be used.
|
||||
token_uri: string, URI for token endpoint. For convenience
|
||||
defaults to Google's endpoints but any OAuth 2.0 provider can be used.
|
||||
message: Message to display if there are problems with the OAuth 2.0
|
||||
configuration. The message may contain HTML and will be presented on the
|
||||
web interface for any method that uses the decorator.
|
||||
"""
|
||||
self.flow = OAuth2WebServerFlow(client_id, client_secret, scope, None,
|
||||
auth_uri, token_uri)
|
||||
self.credentials = None
|
||||
self._request_handler = None
|
||||
self._message = message
|
||||
self._in_error = False
|
||||
|
||||
def _display_error_message(self, request_handler):
|
||||
request_handler.response.out.write('<html><body>')
|
||||
request_handler.response.out.write(self._message)
|
||||
request_handler.response.out.write('</body></html>')
|
||||
|
||||
def oauth_required(self, method):
|
||||
"""Decorator that starts the OAuth 2.0 dance.
|
||||
@@ -333,6 +351,10 @@ class OAuth2Decorator(object):
|
||||
"""
|
||||
|
||||
def check_oauth(request_handler, *args):
|
||||
if self._in_error:
|
||||
self._display_error_message(request_handler)
|
||||
return
|
||||
|
||||
user = users.get_current_user()
|
||||
# Don't use @login_decorator as this could be used in a POST request.
|
||||
if not user:
|
||||
@@ -369,12 +391,18 @@ class OAuth2Decorator(object):
|
||||
"""
|
||||
|
||||
def setup_oauth(request_handler, *args):
|
||||
if self._in_error:
|
||||
self._display_error_message(request_handler)
|
||||
return
|
||||
|
||||
user = users.get_current_user()
|
||||
# Don't use @login_decorator as this could be used in a POST request.
|
||||
if not user:
|
||||
request_handler.redirect(users.create_login_url(
|
||||
request_handler.request.uri))
|
||||
return
|
||||
|
||||
|
||||
self.flow.params['state'] = request_handler.request.url
|
||||
self._request_handler = request_handler
|
||||
self.credentials = StorageByKeyName(
|
||||
@@ -413,6 +441,76 @@ class OAuth2Decorator(object):
|
||||
return self.credentials.authorize(httplib2.Http())
|
||||
|
||||
|
||||
class OAuth2DecoratorFromClientSecrets(OAuth2Decorator):
|
||||
"""An OAuth2Decorator that builds from a clientsecrets file.
|
||||
|
||||
Uses a clientsecrets file as the source for all the information when
|
||||
constructing an OAuth2Decorator.
|
||||
|
||||
Example:
|
||||
|
||||
decorator = OAuth2DecoratorFromClientSecrets(
|
||||
os.path.join(os.path.dirname(__file__), 'client_secrets.json')
|
||||
scope='https://www.googleapis.com/auth/buzz')
|
||||
|
||||
|
||||
class MainHandler(webapp.RequestHandler):
|
||||
|
||||
@decorator.oauth_required
|
||||
def get(self):
|
||||
http = decorator.http()
|
||||
# http is authorized with the user's Credentials and can be used
|
||||
# in API calls
|
||||
"""
|
||||
|
||||
def __init__(self, filename, scope, message=None):
|
||||
"""Constructor
|
||||
|
||||
Args:
|
||||
filename: string, File name of client secrets.
|
||||
scope: string, Space separated list of scopes.
|
||||
message: string, A friendly string to display to the user if the
|
||||
clientsecrets file is missing or invalid. The message may contain HTML and
|
||||
will be presented on the web interface for any method that uses the
|
||||
decorator.
|
||||
"""
|
||||
try:
|
||||
client_type, client_info = clientsecrets.loadfile(filename)
|
||||
if client_type not in [clientsecrets.TYPE_WEB, clientsecrets.TYPE_INSTALLED]:
|
||||
raise InvalidClientSecretsError('OAuth2Decorator doesn\'t support this OAuth 2.0 flow.')
|
||||
super(OAuth2DecoratorFromClientSecrets,
|
||||
self).__init__(
|
||||
client_info['client_id'],
|
||||
client_info['client_secret'],
|
||||
scope,
|
||||
client_info['auth_uri'],
|
||||
client_info['token_uri'],
|
||||
message)
|
||||
except clientsecrets.InvalidClientSecretsError:
|
||||
self._in_error = True
|
||||
if message is not None:
|
||||
self._message = message
|
||||
else:
|
||||
self._message = "Please configure your application for OAuth 2.0"
|
||||
|
||||
|
||||
def oauth2decorator_from_clientsecrets(filename, scope, message=None):
|
||||
"""Creates an OAuth2Decorator populated from a clientsecrets file.
|
||||
|
||||
Args:
|
||||
filename: string, File name of client secrets.
|
||||
scope: string, Space separated list of scopes.
|
||||
message: string, A friendly string to display to the user if the
|
||||
clientsecrets file is missing or invalid. The message may contain HTML and
|
||||
will be presented on the web interface for any method that uses the
|
||||
decorator.
|
||||
|
||||
Returns: An OAuth2Decorator
|
||||
|
||||
"""
|
||||
return OAuth2DecoratorFromClientSecrets(filename, scope, message)
|
||||
|
||||
|
||||
class OAuth2Handler(webapp.RequestHandler):
|
||||
"""Handler for the redirect_uri of the OAuth 2.0 dance."""
|
||||
|
||||
|
||||
@@ -19,10 +19,12 @@ Tools for interacting with OAuth 2.0 protected resources.
|
||||
|
||||
__author__ = 'jcgregorio@google.com (Joe Gregorio)'
|
||||
|
||||
import clientsecrets
|
||||
import copy
|
||||
import datetime
|
||||
import httplib2
|
||||
import logging
|
||||
import sys
|
||||
import urllib
|
||||
import urlparse
|
||||
|
||||
@@ -61,6 +63,10 @@ class AccessTokenRefreshError(Error):
|
||||
"""Error trying to refresh an expired access token."""
|
||||
pass
|
||||
|
||||
class UnknownClientSecretsFlowError(Error):
|
||||
"""The client secrets file called for an unknown type of OAuth 2.0 flow. """
|
||||
pass
|
||||
|
||||
|
||||
class AccessTokenCredentialsError(Error):
|
||||
"""Having only the access_token means no refresh is possible."""
|
||||
@@ -610,7 +616,7 @@ class OAuth2WebServerFlow(Flow):
|
||||
OAuth2Credentials objects may be safely pickled and unpickled.
|
||||
"""
|
||||
|
||||
def __init__(self, client_id, client_secret, scope, user_agent,
|
||||
def __init__(self, client_id, client_secret, scope, user_agent=None,
|
||||
auth_uri='https://accounts.google.com/o/oauth2/auth',
|
||||
token_uri='https://accounts.google.com/o/oauth2/token',
|
||||
**kwargs):
|
||||
@@ -721,3 +727,71 @@ class OAuth2WebServerFlow(Flow):
|
||||
pass
|
||||
|
||||
raise FlowExchangeError(error_msg)
|
||||
|
||||
def flow_from_clientsecrets(filename, scope, message=None):
|
||||
"""Create a Flow from a clientsecrets file.
|
||||
|
||||
Will create the right kind of Flow based on the contents of the clientsecrets
|
||||
file or will raise InvalidClientSecretsError for unknown types of Flows.
|
||||
|
||||
Args:
|
||||
filename: string, File name of client secrets.
|
||||
scope: string, Space separated list of scopes.
|
||||
message: string, A friendly string to display to the user if the
|
||||
clientsecrets file is missing or invalid. If message is provided then
|
||||
sys.exit will be called in the case of an error. If message in not
|
||||
provided then clientsecrets.InvalidClientSecretsError will be raised.
|
||||
|
||||
Returns:
|
||||
A Flow object.
|
||||
|
||||
Raises:
|
||||
UnknownClientSecretsFlowError if the file describes an unknown kind of Flow.
|
||||
clientsecrets.InvalidClientSecretsError if the clientsecrets file is
|
||||
invalid.
|
||||
"""
|
||||
client_type, client_info = clientsecrets.loadfile(filename)
|
||||
if client_type in [clientsecrets.TYPE_WEB, clientsecrets.TYPE_INSTALLED]:
|
||||
return OAuth2WebServerFlow(
|
||||
client_info['client_id'],
|
||||
client_info['client_secret'],
|
||||
scope,
|
||||
None, # user_agent
|
||||
client_info['auth_uri'],
|
||||
client_info['token_uri'])
|
||||
else:
|
||||
raise UnknownClientSecretsFlowError(
|
||||
'This OAuth 2.0 flow is unsupported: "%s"' * client_type)
|
||||
|
||||
|
||||
class OAuth2WebServerFlowFromClientSecrets(Flow):
|
||||
"""Does the Web Server Flow for OAuth 2.0.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, client_secrets, scope, user_agent,
|
||||
auth_uri='https://accounts.google.com/o/oauth2/auth',
|
||||
token_uri='https://accounts.google.com/o/oauth2/token',
|
||||
**kwargs):
|
||||
"""Constructor for OAuth2WebServerFlow
|
||||
|
||||
Args:
|
||||
client_id: string, client identifier.
|
||||
client_secret: string client secret.
|
||||
scope: string, scope of the credentials being requested.
|
||||
user_agent: string, HTTP User-Agent to provide for this application.
|
||||
auth_uri: string, URI for authorization endpoint. For convenience
|
||||
defaults to Google's endpoints but any OAuth 2.0 provider can be used.
|
||||
token_uri: string, URI for token endpoint. For convenience
|
||||
defaults to Google's endpoints but any OAuth 2.0 provider can be used.
|
||||
**kwargs: dict, The keyword arguments are all optional and required
|
||||
parameters for the OAuth calls.
|
||||
"""
|
||||
self.client_id = client_id
|
||||
self.client_secret = client_secret
|
||||
self.scope = scope
|
||||
self.user_agent = user_agent
|
||||
self.auth_uri = auth_uri
|
||||
self.token_uri = token_uri
|
||||
self.params = kwargs
|
||||
self.redirect_uri = None
|
||||
|
||||
113
oauth2client/clientsecrets.py
Normal file
113
oauth2client/clientsecrets.py
Normal file
@@ -0,0 +1,113 @@
|
||||
# Copyright (C) 2011 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.
|
||||
|
||||
"""Utilities for reading OAuth 2.0 client secret files.
|
||||
|
||||
A client_secrets.json file contains all the information needed to interact with
|
||||
an OAuth 2.0 protected service.
|
||||
"""
|
||||
|
||||
__author__ = 'jcgregorio@google.com (Joe Gregorio)'
|
||||
|
||||
|
||||
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
|
||||
|
||||
# Properties that make a client_secrets.json file valid.
|
||||
TYPE_WEB = 'web'
|
||||
TYPE_INSTALLED = 'installed'
|
||||
|
||||
VALID_CLIENT = {
|
||||
TYPE_WEB: {
|
||||
'required': [
|
||||
'client_id',
|
||||
'client_secret',
|
||||
'redirect_uris',
|
||||
'auth_uri',
|
||||
'token_uri'],
|
||||
'string': [
|
||||
'client_id',
|
||||
'client_secret'
|
||||
]
|
||||
},
|
||||
TYPE_INSTALLED: {
|
||||
'required': [
|
||||
'client_id',
|
||||
'client_secret',
|
||||
'redirect_uris',
|
||||
'auth_uri',
|
||||
'token_uri'],
|
||||
'string': [
|
||||
'client_id',
|
||||
'client_secret'
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
class Error(Exception):
|
||||
"""Base error for this module."""
|
||||
pass
|
||||
|
||||
|
||||
class InvalidClientSecretsError(Error):
|
||||
"""Format of ClientSecrets file is invalid."""
|
||||
pass
|
||||
|
||||
|
||||
def _validate_clientsecrets(obj):
|
||||
if obj is None or len(obj) != 1:
|
||||
raise InvalidClientSecretsError('Invalid file format.')
|
||||
client_type = obj.keys()[0]
|
||||
if client_type not in VALID_CLIENT.keys():
|
||||
raise InvalidClientSecretsError('Unknown client type: %s.' % client_type)
|
||||
client_info = obj[client_type]
|
||||
for prop_name in VALID_CLIENT[client_type]['required']:
|
||||
if prop_name not in client_info:
|
||||
raise InvalidClientSecretsError(
|
||||
'Missing property "%s" in a client type of "%s".' % (prop_name,
|
||||
client_type))
|
||||
for prop_name in VALID_CLIENT[client_type]['string']:
|
||||
if client_info[prop_name].startswith('[['):
|
||||
raise InvalidClientSecretsError(
|
||||
'Property "%s" is not configured.' % prop_name)
|
||||
return client_type, client_info
|
||||
|
||||
|
||||
def load(fp):
|
||||
obj = simplejson.load(fp)
|
||||
return _validate_clientsecrets(obj)
|
||||
|
||||
|
||||
def loads(s):
|
||||
obj = simplejson.loads(s)
|
||||
return _validate_clientsecrets(obj)
|
||||
|
||||
|
||||
def loadfile(filename):
|
||||
try:
|
||||
fp = file(filename, 'r')
|
||||
try:
|
||||
obj = simplejson.load(fp)
|
||||
finally:
|
||||
fp.close()
|
||||
except IOError:
|
||||
raise InvalidClientSecretsError('File not found: "%s"' % filename)
|
||||
return _validate_clientsecrets(obj)
|
||||
9
samples/appengine_with_decorator2/client_secrets.json
Normal file
9
samples/appengine_with_decorator2/client_secrets.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"web": {
|
||||
"client_id": "[[INSERT CLIENT ID HERE]]",
|
||||
"client_secret": "[[INSERT CLIENT SECRET HERE]]",
|
||||
"redirect_uris": [],
|
||||
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
|
||||
"token_uri": "https://accounts.google.com/o/oauth2/token"
|
||||
}
|
||||
}
|
||||
@@ -17,9 +17,9 @@
|
||||
"""Starting template for Google App Engine applications.
|
||||
|
||||
Use this project as a starting point if you are just beginning to build a Google
|
||||
App Engine project. Remember to fill in the OAuth 2.0 client_id and
|
||||
client_secret which can be obtained from the Developer Console
|
||||
<https://code.google.com/apis/console/>
|
||||
App Engine project. Remember to download the OAuth 2.0 client secrets which can
|
||||
be obtained from the Developer Console <https://code.google.com/apis/console/>
|
||||
and save them as 'client_secrets.json' in the project directory.
|
||||
"""
|
||||
|
||||
__author__ = 'jcgregorio@google.com (Joe Gregorio)'
|
||||
@@ -31,23 +31,43 @@ import os
|
||||
import pickle
|
||||
|
||||
from apiclient.discovery import build
|
||||
from oauth2client.appengine import OAuth2Decorator
|
||||
from oauth2client.appengine import oauth2decorator_from_clientsecrets
|
||||
from oauth2client.client import AccessTokenRefreshError
|
||||
from google.appengine.api import memcache
|
||||
from google.appengine.ext import webapp
|
||||
from google.appengine.ext.webapp import template
|
||||
from google.appengine.ext.webapp.util import run_wsgi_app
|
||||
|
||||
# The client_id and client_secret are copied from the API Access tab on
|
||||
# the Google APIs Console <http://code.google.com/apis/console>
|
||||
decorator = OAuth2Decorator(
|
||||
client_id='837647042410-49mlotv28bfpn5a0igtinipsb8so5eob.apps.googleusercontent.com',
|
||||
client_secret='d4BSDjl4rmFmk-wh28_aK1Oz',
|
||||
scope='https://www.googleapis.com/auth/buzz')
|
||||
|
||||
# CLIENT_SECRETS, name of a file containing the OAuth 2.0 information for this
|
||||
# application, including client_id and client_secret, which are found
|
||||
# on the API Access tab on the Google APIs
|
||||
# Console <http://code.google.com/apis/console>
|
||||
CLIENT_SECRETS = os.path.join(os.path.dirname(__file__), 'client_secrets.json')
|
||||
|
||||
# Helpful message to display in the browser if the CLIENT_SECRETS file
|
||||
# is missing.
|
||||
MISSING_CLIENT_SECRETS_MESSAGE = """
|
||||
<h1>Warning: Please configure OAuth 2.0</h1>
|
||||
<p>
|
||||
To make this sample run you will need to populate the client_secrets.json file
|
||||
found at:
|
||||
</p>
|
||||
<p>
|
||||
<code>%s</code>.
|
||||
</p>
|
||||
<p>with information found on the <a
|
||||
href="https://code.google.com/apis/console">APIs Console</a>.
|
||||
</p>
|
||||
""" % CLIENT_SECRETS
|
||||
|
||||
|
||||
http = httplib2.Http(memcache)
|
||||
service = build("buzz", "v1", http=http)
|
||||
|
||||
decorator = oauth2decorator_from_clientsecrets(
|
||||
CLIENT_SECRETS,
|
||||
'https://www.googleapis.com/auth/buzz',
|
||||
MISSING_CLIENT_SECRETS_MESSAGE)
|
||||
|
||||
class MainHandler(webapp.RequestHandler):
|
||||
|
||||
|
||||
@@ -38,31 +38,44 @@ __author__ = 'jcgregorio@google.com (Joe Gregorio)'
|
||||
import gflags
|
||||
import httplib2
|
||||
import logging
|
||||
import os
|
||||
import pprint
|
||||
import sys
|
||||
|
||||
from apiclient.discovery import build
|
||||
from oauth2client.file import Storage
|
||||
from oauth2client.client import AccessTokenRefreshError
|
||||
from oauth2client.client import OAuth2WebServerFlow
|
||||
from oauth2client.client import flow_from_clientsecrets
|
||||
from oauth2client.tools import run
|
||||
|
||||
|
||||
FLAGS = gflags.FLAGS
|
||||
|
||||
# Set up a Flow object to be used if we need to authenticate. This
|
||||
# sample uses OAuth 2.0, and we set up the OAuth2WebServerFlow with
|
||||
# the information it needs to authenticate. Note that it is called
|
||||
# the Web Server Flow, but it can also handle the flow for native
|
||||
# applications <http://code.google.com/apis/accounts/docs/OAuth2.html#IA>
|
||||
# The client_id client_secret are copied from the API Access tab on
|
||||
# the Google APIs Console <http://code.google.com/apis/console>. When
|
||||
# creating credentials for this application be sure to choose an Application
|
||||
# type of "Installed application".
|
||||
FLOW = OAuth2WebServerFlow(
|
||||
client_id='433807057907.apps.googleusercontent.com',
|
||||
client_secret='jigtZpMApkRxncxikFpR+SFg',
|
||||
# CLIENT_SECRETS, name of a file containing the OAuth 2.0 information for this
|
||||
# application, including client_id and client_secret, which are found
|
||||
# on the API Access tab on the Google APIs
|
||||
# Console <http://code.google.com/apis/console>
|
||||
CLIENT_SECRETS = 'client_secrets.json'
|
||||
|
||||
# Helpful message to display in the browser if the CLIENT_SECRETS file
|
||||
# is missing.
|
||||
MISSING_CLIENT_SECRETS_MESSAGE = """
|
||||
WARNING: Please configure OAuth 2.0
|
||||
|
||||
To make this sample run you will need to populate the client_secrets.json file
|
||||
found at:
|
||||
|
||||
%s
|
||||
|
||||
with information from the APIs Console <https://code.google.com/apis/console>.
|
||||
|
||||
""" % os.path.join(os.path.dirname(__file__), CLIENT_SECRETS)
|
||||
|
||||
# Set up a Flow object to be used if we need to authenticate.
|
||||
FLOW = flow_from_clientsecrets(CLIENT_SECRETS,
|
||||
scope='https://www.googleapis.com/auth/buzz',
|
||||
user_agent='buzz-cmdline-sample/1.0')
|
||||
message=MISSING_CLIENT_SECRETS_MESSAGE)
|
||||
|
||||
|
||||
# The gflags module makes defining command-line options easy for
|
||||
# applications. Run this program with the '--help' argument to see
|
||||
|
||||
9
samples/buzz/client_secrets.json
Normal file
9
samples/buzz/client_secrets.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"web": {
|
||||
"client_id": "[[INSERT CLIENT ID HERE]]",
|
||||
"client_secret": "[[INSERT CLIENT SECRET HERE]]",
|
||||
"redirect_uris": [],
|
||||
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
|
||||
"token_uri": "https://accounts.google.com/o/oauth2/token"
|
||||
}
|
||||
}
|
||||
80
tests/test_oauth2client_clientsecrets.py
Normal file
80
tests/test_oauth2client_clientsecrets.py
Normal file
@@ -0,0 +1,80 @@
|
||||
# Copyright 2011 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.
|
||||
|
||||
"""Unit tests for oauth2client.clientsecrets."""
|
||||
|
||||
__author__ = 'jcgregorio@google.com (Joe Gregorio)'
|
||||
|
||||
|
||||
import os
|
||||
import unittest
|
||||
import StringIO
|
||||
|
||||
|
||||
import oauth2client.clientsecrets as clientsecrets
|
||||
|
||||
|
||||
class OAuth2CredentialsTests(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
pass
|
||||
|
||||
def tearDown(self):
|
||||
pass
|
||||
|
||||
def test_validate_error(self):
|
||||
ERRORS = [
|
||||
(None, 'Invalid'),
|
||||
('{}', 'Invalid'),
|
||||
('{"foo": {}}', 'Unknown'),
|
||||
('{"web": {}}', 'Missing'),
|
||||
('{"web": {"client_id": "dkkd"}}', 'Missing'),
|
||||
("""{
|
||||
"web": {
|
||||
"client_id": "[[CLIENT ID REQUIRED]]",
|
||||
"client_secret": "[[CLIENT SECRET REQUIRED]]",
|
||||
"redirect_uris": ["http://localhost:8080/oauth2callback"],
|
||||
"auth_uri": "",
|
||||
"token_uri": ""
|
||||
}
|
||||
}
|
||||
""", 'Property'),
|
||||
]
|
||||
for src, match in ERRORS:
|
||||
# Test load(s)
|
||||
try:
|
||||
clientsecrets.loads(src)
|
||||
self.fail(src + ' should not be a valid client_secrets file.')
|
||||
except clientsecrets.InvalidClientSecretsError, e:
|
||||
self.assertTrue(str(e).startswith(match))
|
||||
|
||||
# Test loads(fp)
|
||||
try:
|
||||
fp = StringIO.StringIO(src)
|
||||
clientsecrets.load(fp)
|
||||
self.fail(src + ' should not be a valid client_secrets file.')
|
||||
except clientsecrets.InvalidClientSecretsError, e:
|
||||
self.assertTrue(str(e).startswith(match))
|
||||
|
||||
def test_load_by_filename(self):
|
||||
try:
|
||||
clientsecrets.loadfile(os.path.join(__file__, '..',
|
||||
'afilethatisntthere.json'))
|
||||
self.fail('should fail to load a missing client_secrets file.')
|
||||
except clientsecrets.InvalidClientSecretsError, e:
|
||||
self.assertTrue(str(e).startswith('File'))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user