Commit 82c0fb12 authored by akuegel@chromium.org's avatar akuegel@chromium.org

Revert of Upgrade 3rd packages (patchset #4 id:60001 of...

Revert of Upgrade 3rd packages (patchset #4 id:60001 of https://codereview.chromium.org/1085893002/)

Reason for revert:
This might have broken some internal tryserver bots.

Original issue's description:
> Upgrade 3rd packages
> 
> BUG=461614
> R=nodir@chromium.org
> 
> Committed: https://src.chromium.org/viewvc/chrome?view=rev&revision=294835

TBR=nodir@chromium.org,sheyang@chromium.org
NOPRESUBMIT=true
NOTREECHECKS=true
NOTRY=true
BUG=461614

Review URL: https://codereview.chromium.org/1094533003

git-svn-id: svn://svn.chromium.org/chrome/trunk/tools/depot_tools@294841 0039d316-1c4b-4281-b951-d872f2087c98
parent 7e3b6fdd
diff --git a/third_party/google_api_python_client/apiclient/__init__.py b/third_party/google_api_python_client/apiclient/__init__.py
index 5efb142..acb1a23 100644
--- a/third_party/google_api_python_client/apiclient/__init__.py
+++ b/third_party/google_api_python_client/apiclient/__init__.py
@@ -3,7 +3,7 @@
import googleapiclient
try:
- import oauth2client
+ from third_party import oauth2client
except ImportError:
raise RuntimeError(
'Previous version of google-api-python-client detected; due to a '
diff --git a/third_party/google_api_python_client/googleapiclient/channel.py b/third_party/google_api_python_client/googleapiclient/channel.py
index 68a3b89..4626094 100644
--- a/third_party/google_api_python_client/googleapiclient/channel.py
+++ b/third_party/google_api_python_client/googleapiclient/channel.py
@@ -60,7 +60,7 @@ import datetime
import uuid
from googleapiclient import errors
-from ...oauth2client import util
+from third_party.oauth2client import util
# The unix time epoch starts at midnight 1970.
diff --git a/third_party/google_api_python_client/googleapiclient/discovery.py b/third_party/google_api_python_client/googleapiclient/discovery.py
index 3ddac57..0e9e5cf 100644
--- a/third_party/google_api_python_client/googleapiclient/discovery.py
+++ b/third_party/google_api_python_client/googleapiclient/discovery.py
@@ -47,9 +47,9 @@ except ImportError:
from cgi import parse_qsl
# Third-party imports
-from ... import httplib2
+from third_party import httplib2
+from third_party.uritemplate import uritemplate
import mimeparse
-from ... import uritemplate
# Local imports
from googleapiclient.errors import HttpError
@@ -65,9 +65,9 @@ from googleapiclient.model import JsonModel
from googleapiclient.model import MediaModel
from googleapiclient.model import RawModel
from googleapiclient.schema import Schemas
-from oauth2client.client import GoogleCredentials
-from oauth2client.util import _add_query_parameter
-from oauth2client.util import positional
+from third_party.oauth2client.client import GoogleCredentials
+from third_party.oauth2client.util import _add_query_parameter
+from third_party.oauth2client.util import positional
# The client library requires a version of httplib2 that supports RETRIES.
diff --git a/third_party/google_api_python_client/googleapiclient/errors.py b/third_party/google_api_python_client/googleapiclient/errors.py
index a1999fd..18c52e6 100644
--- a/third_party/google_api_python_client/googleapiclient/errors.py
+++ b/third_party/google_api_python_client/googleapiclient/errors.py
@@ -24,7 +24,7 @@ __author__ = 'jcgregorio@google.com (Joe Gregorio)'
import json
-from ...oauth2client import util
+from third_party.oauth2client import util
class Error(Exception):
diff --git a/third_party/google_api_python_client/googleapiclient/http.py b/third_party/google_api_python_client/googleapiclient/http.py
index 8638279..d2ce70c 100644
--- a/third_party/google_api_python_client/googleapiclient/http.py
+++ b/third_party/google_api_python_client/googleapiclient/http.py
@@ -25,7 +25,6 @@ import StringIO
import base64
import copy
import gzip
-import httplib2
import json
import logging
import mimeparse
@@ -49,7 +48,8 @@ from errors import ResumableUploadError
from errors import UnexpectedBodyError
from errors import UnexpectedMethodError
from model import JsonModel
-from ...oauth2client import util
+from third_party import httplib2
+from third_party.oauth2client import util
DEFAULT_CHUNK_SIZE = 512*1024
diff --git a/third_party/google_api_python_client/googleapiclient/sample_tools.py b/third_party/google_api_python_client/googleapiclient/sample_tools.py
index cbd6d6f..cc0790b 100644
--- a/third_party/google_api_python_client/googleapiclient/sample_tools.py
+++ b/third_party/google_api_python_client/googleapiclient/sample_tools.py
@@ -22,13 +22,13 @@ __all__ = ['init']
import argparse
-import httplib2
import os
from googleapiclient import discovery
-from ...oauth2client import client
-from ...oauth2client import file
-from ...oauth2client import tools
+from third_party import httplib2
+from third_party.oauth2client import client
+from third_party.oauth2client import file
+from third_party.oauth2client import tools
def init(argv, name, version, doc, filename, scope=None, parents=[], discovery_filename=None):
diff --git a/third_party/google_api_python_client/googleapiclient/schema.py b/third_party/google_api_python_client/googleapiclient/schema.py
index af41317..92543ec 100644
--- a/third_party/google_api_python_client/googleapiclient/schema.py
+++ b/third_party/google_api_python_client/googleapiclient/schema.py
@@ -63,7 +63,7 @@ __author__ = 'jcgregorio@google.com (Joe Gregorio)'
import copy
-from oauth2client import util
+from third_party.oauth2client import util
class Schemas(object):
...@@ -3,8 +3,4 @@ Version: v1.3.1 ...@@ -3,8 +3,4 @@ Version: v1.3.1
Revision: 49d45a6c3318b75e551c3022020f46c78655f365 Revision: 49d45a6c3318b75e551c3022020f46c78655f365
License: Apache License, Version 2.0 (the "License") License: Apache License, Version 2.0 (the "License")
Local modifications: No local changes
See also MODIFICATIONS.diff
Notes:
Requires the httplib2 and oauth2client library.
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
import googleapiclient import googleapiclient
try: try:
from third_party import oauth2client import oauth2client
except ImportError: except ImportError:
raise RuntimeError( raise RuntimeError(
'Previous version of google-api-python-client detected; due to a ' 'Previous version of google-api-python-client detected; due to a '
......
...@@ -60,7 +60,7 @@ import datetime ...@@ -60,7 +60,7 @@ import datetime
import uuid import uuid
from googleapiclient import errors from googleapiclient import errors
from third_party.oauth2client import util from ...oauth2client import util
# The unix time epoch starts at midnight 1970. # The unix time epoch starts at midnight 1970.
......
...@@ -47,9 +47,9 @@ except ImportError: ...@@ -47,9 +47,9 @@ except ImportError:
from cgi import parse_qsl from cgi import parse_qsl
# Third-party imports # Third-party imports
from third_party import httplib2 from ... import httplib2
from third_party.uritemplate import uritemplate
import mimeparse import mimeparse
from ... import uritemplate
# Local imports # Local imports
from googleapiclient.errors import HttpError from googleapiclient.errors import HttpError
...@@ -65,9 +65,9 @@ from googleapiclient.model import JsonModel ...@@ -65,9 +65,9 @@ from googleapiclient.model import JsonModel
from googleapiclient.model import MediaModel from googleapiclient.model import MediaModel
from googleapiclient.model import RawModel from googleapiclient.model import RawModel
from googleapiclient.schema import Schemas from googleapiclient.schema import Schemas
from third_party.oauth2client.client import GoogleCredentials from oauth2client.client import GoogleCredentials
from third_party.oauth2client.util import _add_query_parameter from oauth2client.util import _add_query_parameter
from third_party.oauth2client.util import positional from oauth2client.util import positional
# The client library requires a version of httplib2 that supports RETRIES. # The client library requires a version of httplib2 that supports RETRIES.
......
...@@ -24,7 +24,7 @@ __author__ = 'jcgregorio@google.com (Joe Gregorio)' ...@@ -24,7 +24,7 @@ __author__ = 'jcgregorio@google.com (Joe Gregorio)'
import json import json
from third_party.oauth2client import util from ...oauth2client import util
class Error(Exception): class Error(Exception):
......
...@@ -25,6 +25,7 @@ import StringIO ...@@ -25,6 +25,7 @@ import StringIO
import base64 import base64
import copy import copy
import gzip import gzip
import httplib2
import json import json
import logging import logging
import mimeparse import mimeparse
...@@ -48,8 +49,7 @@ from errors import ResumableUploadError ...@@ -48,8 +49,7 @@ from errors import ResumableUploadError
from errors import UnexpectedBodyError from errors import UnexpectedBodyError
from errors import UnexpectedMethodError from errors import UnexpectedMethodError
from model import JsonModel from model import JsonModel
from third_party import httplib2 from ...oauth2client import util
from third_party.oauth2client import util
DEFAULT_CHUNK_SIZE = 512*1024 DEFAULT_CHUNK_SIZE = 512*1024
......
...@@ -22,13 +22,13 @@ __all__ = ['init'] ...@@ -22,13 +22,13 @@ __all__ = ['init']
import argparse import argparse
import httplib2
import os import os
from googleapiclient import discovery from googleapiclient import discovery
from third_party import httplib2 from ...oauth2client import client
from third_party.oauth2client import client from ...oauth2client import file
from third_party.oauth2client import file from ...oauth2client import tools
from third_party.oauth2client import tools
def init(argv, name, version, doc, filename, scope=None, parents=[], discovery_filename=None): def init(argv, name, version, doc, filename, scope=None, parents=[], discovery_filename=None):
......
...@@ -63,7 +63,7 @@ __author__ = 'jcgregorio@google.com (Joe Gregorio)' ...@@ -63,7 +63,7 @@ __author__ = 'jcgregorio@google.com (Joe Gregorio)'
import copy import copy
from third_party.oauth2client import util from oauth2client import util
class Schemas(object): class Schemas(object):
......
Name: oauth2client Name: oauth2client
Short Name: oauth2client Short Name: oauth2client
URL: https://pypi.python.org/packages/source/o/oauth2client/oauth2client-1.4.7.tar.gz URL: https://pypi.python.org/packages/source/o/oauth2client/oauth2client-1.2.tar.gz
Version: 1.4.7 Version: 1.2
License: Apache License 2.0 License: Apache License 2.0
Description: Description:
......
"""Client library for using OAuth2, especially with Google APIs.""" __version__ = "1.2"
__version__ = '1.4.7'
GOOGLE_AUTH_URI = 'https://accounts.google.com/o/oauth2/auth' GOOGLE_AUTH_URI = 'https://accounts.google.com/o/oauth2/auth'
GOOGLE_DEVICE_URI = 'https://accounts.google.com/o/oauth2/device/code'
GOOGLE_REVOKE_URI = 'https://accounts.google.com/o/oauth2/revoke' GOOGLE_REVOKE_URI = 'https://accounts.google.com/o/oauth2/revoke'
GOOGLE_TOKEN_URI = 'https://accounts.google.com/o/oauth2/token' GOOGLE_TOKEN_URI = 'https://accounts.google.com/o/oauth2/token'
\ No newline at end of file
# Copyright (C) 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.
"""Utility module to import a JSON module
Hides all the messy details of exactly where
we get a simplejson module from.
"""
__author__ = 'jcgregorio@google.com (Joe Gregorio)'
try: # pragma: no cover
# Should work for Python2.6 and higher.
import json as simplejson
except ImportError: # pragma: no cover
try:
import simplejson
except ImportError:
# Try to import from django, should work on App Engine
from django.utils import simplejson
# Copyright 2014 Google Inc. All rights reserved. # Copyright (C) 2010 Google Inc.
# #
# Licensed under the Apache License, Version 2.0 (the "License"); # Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License. # you may not use this file except in compliance with the License.
...@@ -19,14 +19,14 @@ Utilities for making it easier to use OAuth 2.0 on Google App Engine. ...@@ -19,14 +19,14 @@ Utilities for making it easier to use OAuth 2.0 on Google App Engine.
__author__ = 'jcgregorio@google.com (Joe Gregorio)' __author__ = 'jcgregorio@google.com (Joe Gregorio)'
import base64
import cgi import cgi
import json import httplib2
import logging import logging
import os import os
import pickle import pickle
import threading import threading
import time
import httplib2
from google.appengine.api import app_identity from google.appengine.api import app_identity
from google.appengine.api import memcache from google.appengine.api import memcache
...@@ -41,6 +41,7 @@ from oauth2client import GOOGLE_TOKEN_URI ...@@ -41,6 +41,7 @@ from oauth2client import GOOGLE_TOKEN_URI
from oauth2client import clientsecrets from oauth2client import clientsecrets
from oauth2client import util from oauth2client import util
from oauth2client import xsrfutil from oauth2client import xsrfutil
from oauth2client.anyjson import simplejson
from oauth2client.client import AccessTokenRefreshError from oauth2client.client import AccessTokenRefreshError
from oauth2client.client import AssertionCredentials from oauth2client.client import AssertionCredentials
from oauth2client.client import Credentials from oauth2client.client import Credentials
...@@ -158,20 +159,15 @@ class AppAssertionCredentials(AssertionCredentials): ...@@ -158,20 +159,15 @@ class AppAssertionCredentials(AssertionCredentials):
Args: Args:
scope: string or iterable of strings, scope(s) of the credentials being scope: string or iterable of strings, scope(s) of the credentials being
requested. requested.
**kwargs: optional keyword args, including:
service_account_id: service account id of the application. If None or
unspecified, the default service account for the app is used.
""" """
self.scope = util.scopes_to_string(scope) self.scope = util.scopes_to_string(scope)
self._kwargs = kwargs
self.service_account_id = kwargs.get('service_account_id', None)
# Assertion type is no longer used, but still in the parent class signature. # Assertion type is no longer used, but still in the parent class signature.
super(AppAssertionCredentials, self).__init__(None) super(AppAssertionCredentials, self).__init__(None)
@classmethod @classmethod
def from_json(cls, json_data): def from_json(cls, json):
data = json.loads(json_data) data = simplejson.loads(json)
return AppAssertionCredentials(data['scope']) return AppAssertionCredentials(data['scope'])
def _refresh(self, http_request): def _refresh(self, http_request):
...@@ -190,22 +186,11 @@ class AppAssertionCredentials(AssertionCredentials): ...@@ -190,22 +186,11 @@ class AppAssertionCredentials(AssertionCredentials):
""" """
try: try:
scopes = self.scope.split() scopes = self.scope.split()
(token, _) = app_identity.get_access_token( (token, _) = app_identity.get_access_token(scopes)
scopes, service_account_id=self.service_account_id) except app_identity.Error, e:
except app_identity.Error as e:
raise AccessTokenRefreshError(str(e)) raise AccessTokenRefreshError(str(e))
self.access_token = token self.access_token = token
@property
def serialization_data(self):
raise NotImplementedError('Cannot serialize credentials for AppEngine.')
def create_scoped_required(self):
return not self.scope
def create_scoped(self, scopes):
return AppAssertionCredentials(scopes, **self._kwargs)
class FlowProperty(db.Property): class FlowProperty(db.Property):
"""App Engine datastore Property for Flow. """App Engine datastore Property for Flow.
...@@ -449,7 +434,6 @@ class StorageByKeyName(Storage): ...@@ -449,7 +434,6 @@ class StorageByKeyName(Storage):
entity_key = db.Key.from_path(self._model.kind(), self._key_name) entity_key = db.Key.from_path(self._model.kind(), self._key_name)
db.delete(entity_key) db.delete(entity_key)
@db.non_transactional(allow_existing=True)
def locked_get(self): def locked_get(self):
"""Retrieve Credential from datastore. """Retrieve Credential from datastore.
...@@ -472,7 +456,6 @@ class StorageByKeyName(Storage): ...@@ -472,7 +456,6 @@ class StorageByKeyName(Storage):
credentials.set_store(self) credentials.set_store(self)
return credentials return credentials
@db.non_transactional(allow_existing=True)
def locked_put(self, credentials): def locked_put(self, credentials):
"""Write a Credentials to the datastore. """Write a Credentials to the datastore.
...@@ -485,7 +468,6 @@ class StorageByKeyName(Storage): ...@@ -485,7 +468,6 @@ class StorageByKeyName(Storage):
if self._cache: if self._cache:
self._cache.set(self._key_name, credentials.to_json()) self._cache.set(self._key_name, credentials.to_json())
@db.non_transactional(allow_existing=True)
def locked_delete(self): def locked_delete(self):
"""Delete Credential from datastore.""" """Delete Credential from datastore."""
...@@ -571,14 +553,16 @@ class OAuth2Decorator(object): ...@@ -571,14 +553,16 @@ class OAuth2Decorator(object):
Instantiate and then use with oauth_required or oauth_aware Instantiate and then use with oauth_required or oauth_aware
as decorators on webapp.RequestHandler methods. as decorators on webapp.RequestHandler methods.
:: Example:
decorator = OAuth2Decorator( decorator = OAuth2Decorator(
client_id='837...ent.com', client_id='837...ent.com',
client_secret='Qh...wwI', client_secret='Qh...wwI',
scope='https://www.googleapis.com/auth/plus') scope='https://www.googleapis.com/auth/plus')
class MainHandler(webapp.RequestHandler): class MainHandler(webapp.RequestHandler):
@decorator.oauth_required @decorator.oauth_required
def get(self): def get(self):
http = decorator.http() http = decorator.http()
...@@ -666,9 +650,8 @@ class OAuth2Decorator(object): ...@@ -666,9 +650,8 @@ class OAuth2Decorator(object):
provided to this constructor. A string indicating the name of the field provided to this constructor. A string indicating the name of the field
on the _credentials_class where a Credentials object will be stored. on the _credentials_class where a Credentials object will be stored.
Defaults to 'credentials'. Defaults to 'credentials'.
**kwargs: dict, Keyword arguments are passed along as kwargs to **kwargs: dict, Keyword arguments are be passed along as kwargs to the
the OAuth2WebServerFlow constructor. OAuth2WebServerFlow constructor.
""" """
self._tls = threading.local() self._tls = threading.local()
self.flow = None self.flow = None
...@@ -815,18 +798,14 @@ class OAuth2Decorator(object): ...@@ -815,18 +798,14 @@ class OAuth2Decorator(object):
url = self.flow.step1_get_authorize_url() url = self.flow.step1_get_authorize_url()
return str(url) return str(url)
def http(self, *args, **kwargs): def http(self):
"""Returns an authorized http instance. """Returns an authorized http instance.
Must only be called from within an @oauth_required decorated method, or Must only be called from within an @oauth_required decorated method, or
from within an @oauth_aware decorated method where has_credentials() from within an @oauth_aware decorated method where has_credentials()
returns True. returns True.
Args:
*args: Positional arguments passed to httplib2.Http constructor.
**kwargs: Positional arguments passed to httplib2.Http constructor.
""" """
return self.credentials.authorize(httplib2.Http(*args, **kwargs)) return self.credentials.authorize(httplib2.Http())
@property @property
def callback_path(self): def callback_path(self):
...@@ -845,8 +824,7 @@ class OAuth2Decorator(object): ...@@ -845,8 +824,7 @@ class OAuth2Decorator(object):
def callback_handler(self): def callback_handler(self):
"""RequestHandler for the OAuth 2.0 redirect callback. """RequestHandler for the OAuth 2.0 redirect callback.
Usage:: Usage:
app = webapp.WSGIApplication([ app = webapp.WSGIApplication([
('/index', MyIndexHandler), ('/index', MyIndexHandler),
..., ...,
...@@ -880,7 +858,7 @@ class OAuth2Decorator(object): ...@@ -880,7 +858,7 @@ class OAuth2Decorator(object):
user) user)
if decorator._token_response_param and credentials.token_response: if decorator._token_response_param and credentials.token_response:
resp_json = json.dumps(credentials.token_response) resp_json = simplejson.dumps(credentials.token_response)
redirect_uri = util._add_query_parameter( redirect_uri = util._add_query_parameter(
redirect_uri, decorator._token_response_param, resp_json) redirect_uri, decorator._token_response_param, resp_json)
...@@ -909,23 +887,24 @@ class OAuth2DecoratorFromClientSecrets(OAuth2Decorator): ...@@ -909,23 +887,24 @@ class OAuth2DecoratorFromClientSecrets(OAuth2Decorator):
Uses a clientsecrets file as the source for all the information when Uses a clientsecrets file as the source for all the information when
constructing an OAuth2Decorator. constructing an OAuth2Decorator.
:: Example:
decorator = OAuth2DecoratorFromClientSecrets( decorator = OAuth2DecoratorFromClientSecrets(
os.path.join(os.path.dirname(__file__), 'client_secrets.json') os.path.join(os.path.dirname(__file__), 'client_secrets.json')
scope='https://www.googleapis.com/auth/plus') scope='https://www.googleapis.com/auth/plus')
class MainHandler(webapp.RequestHandler): class MainHandler(webapp.RequestHandler):
@decorator.oauth_required @decorator.oauth_required
def get(self): def get(self):
http = decorator.http() http = decorator.http()
# http is authorized with the user's Credentials and can be used # http is authorized with the user's Credentials and can be used
# in API calls # in API calls
""" """
@util.positional(3) @util.positional(3)
def __init__(self, filename, scope, message=None, cache=None, **kwargs): def __init__(self, filename, scope, message=None, cache=None):
"""Constructor """Constructor
Args: Args:
...@@ -938,20 +917,17 @@ class OAuth2DecoratorFromClientSecrets(OAuth2Decorator): ...@@ -938,20 +917,17 @@ class OAuth2DecoratorFromClientSecrets(OAuth2Decorator):
decorator. decorator.
cache: An optional cache service client that implements get() and set() cache: An optional cache service client that implements get() and set()
methods. See clientsecrets.loadfile() for details. methods. See clientsecrets.loadfile() for details.
**kwargs: dict, Keyword arguments are passed along as kwargs to
the OAuth2WebServerFlow constructor.
""" """
client_type, client_info = clientsecrets.loadfile(filename, cache=cache) client_type, client_info = clientsecrets.loadfile(filename, cache=cache)
if client_type not in [ if client_type not in [
clientsecrets.TYPE_WEB, clientsecrets.TYPE_INSTALLED]: clientsecrets.TYPE_WEB, clientsecrets.TYPE_INSTALLED]:
raise InvalidClientSecretsError( raise InvalidClientSecretsError(
"OAuth2Decorator doesn't support this OAuth 2.0 flow.") 'OAuth2Decorator doesn\'t support this OAuth 2.0 flow.')
constructor_kwargs = dict(kwargs) constructor_kwargs = {
constructor_kwargs.update({ 'auth_uri': client_info['auth_uri'],
'auth_uri': client_info['auth_uri'], 'token_uri': client_info['token_uri'],
'token_uri': client_info['token_uri'], 'message': message,
'message': message, }
})
revoke_uri = client_info.get('revoke_uri') revoke_uri = client_info.get('revoke_uri')
if revoke_uri is not None: if revoke_uri is not None:
constructor_kwargs['revoke_uri'] = revoke_uri constructor_kwargs['revoke_uri'] = revoke_uri
...@@ -984,4 +960,4 @@ def oauth2decorator_from_clientsecrets(filename, scope, ...@@ -984,4 +960,4 @@ def oauth2decorator_from_clientsecrets(filename, scope,
""" """
return OAuth2DecoratorFromClientSecrets(filename, scope, return OAuth2DecoratorFromClientSecrets(filename, scope,
message=message, cache=cache) message=message, cache=cache)
\ No newline at end of file
This diff is collapsed.
# Copyright 2014 Google Inc. All rights reserved. # Copyright (C) 2011 Google Inc.
# #
# Licensed under the Apache License, Version 2.0 (the "License"); # Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License. # you may not use this file except in compliance with the License.
...@@ -20,10 +20,8 @@ an OAuth 2.0 protected service. ...@@ -20,10 +20,8 @@ an OAuth 2.0 protected service.
__author__ = 'jcgregorio@google.com (Joe Gregorio)' __author__ = 'jcgregorio@google.com (Joe Gregorio)'
import json
from third_party import six
from anyjson import simplejson
# Properties that make a client_secrets.json file valid. # Properties that make a client_secrets.json file valid.
TYPE_WEB = 'web' TYPE_WEB = 'web'
...@@ -70,21 +68,11 @@ class InvalidClientSecretsError(Error): ...@@ -70,21 +68,11 @@ class InvalidClientSecretsError(Error):
def _validate_clientsecrets(obj): def _validate_clientsecrets(obj):
_INVALID_FILE_FORMAT_MSG = ( if obj is None or len(obj) != 1:
'Invalid file format. See ' raise InvalidClientSecretsError('Invalid file format.')
'https://developers.google.com/api-client-library/' client_type = obj.keys()[0]
'python/guide/aaa_client_secrets') if client_type not in VALID_CLIENT.keys():
raise InvalidClientSecretsError('Unknown client type: %s.' % client_type)
if obj is None:
raise InvalidClientSecretsError(_INVALID_FILE_FORMAT_MSG)
if len(obj) != 1:
raise InvalidClientSecretsError(
_INVALID_FILE_FORMAT_MSG + ' '
'Expected a JSON object with a single property for a "web" or '
'"installed" application')
client_type = tuple(obj)[0]
if client_type not in VALID_CLIENT:
raise InvalidClientSecretsError('Unknown client type: %s.' % (client_type,))
client_info = obj[client_type] client_info = obj[client_type]
for prop_name in VALID_CLIENT[client_type]['required']: for prop_name in VALID_CLIENT[client_type]['required']:
if prop_name not in client_info: if prop_name not in client_info:
...@@ -99,19 +87,22 @@ def _validate_clientsecrets(obj): ...@@ -99,19 +87,22 @@ def _validate_clientsecrets(obj):
def load(fp): def load(fp):
obj = json.load(fp) obj = simplejson.load(fp)
return _validate_clientsecrets(obj) return _validate_clientsecrets(obj)
def loads(s): def loads(s):
obj = json.loads(s) obj = simplejson.loads(s)
return _validate_clientsecrets(obj) return _validate_clientsecrets(obj)
def _loadfile(filename): def _loadfile(filename):
try: try:
with open(filename, 'r') as fp: fp = file(filename, 'r')
obj = json.load(fp) try:
obj = simplejson.load(fp)
finally:
fp.close()
except IOError: except IOError:
raise InvalidClientSecretsError('File not found: "%s"' % filename) raise InvalidClientSecretsError('File not found: "%s"' % filename)
return _validate_clientsecrets(obj) return _validate_clientsecrets(obj)
...@@ -123,12 +114,10 @@ def loadfile(filename, cache=None): ...@@ -123,12 +114,10 @@ def loadfile(filename, cache=None):
Typical cache storage would be App Engine memcache service, Typical cache storage would be App Engine memcache service,
but you can pass in any other cache client that implements but you can pass in any other cache client that implements
these methods: these methods:
- get(key, namespace=ns)
- set(key, value, namespace=ns)
* ``get(key, namespace=ns)`` Usage:
* ``set(key, value, namespace=ns)``
Usage::
# without caching # without caching
client_type, client_info = loadfile('secrets.json') client_type, client_info = loadfile('secrets.json')
# using App Engine memcache service # using App Engine memcache service
...@@ -161,4 +150,4 @@ def loadfile(filename, cache=None): ...@@ -161,4 +150,4 @@ def loadfile(filename, cache=None):
obj = {client_type: client_info} obj = {client_type: client_info}
cache.set(filename, obj, namespace=_SECRET_NAMESPACE) cache.set(filename, obj, namespace=_SECRET_NAMESPACE)
return next(six.iteritems(obj)) return obj.iteritems().next()
\ No newline at end of file
#!/usr/bin/python2.4
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
# Copyright 2014 Google Inc. All rights reserved. # Copyright (C) 2011 Google Inc.
# #
# Licensed under the Apache License, Version 2.0 (the "License"); # Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License. # you may not use this file except in compliance with the License.
...@@ -13,15 +14,13 @@ ...@@ -13,15 +14,13 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
"""Crypto-related routines for oauth2client."""
import base64 import base64
import json import hashlib
import logging import logging
import sys
import time import time
from third_party import six from anyjson import simplejson
CLOCK_SKEW_SECS = 300 # 5 minutes in seconds CLOCK_SKEW_SECS = 300 # 5 minutes in seconds
...@@ -39,6 +38,7 @@ class AppIdentityError(Exception): ...@@ -39,6 +38,7 @@ class AppIdentityError(Exception):
try: try:
from OpenSSL import crypto from OpenSSL import crypto
class OpenSSLVerifier(object): class OpenSSLVerifier(object):
"""Verifies the signature on a message.""" """Verifies the signature on a message."""
...@@ -62,8 +62,6 @@ try: ...@@ -62,8 +62,6 @@ try:
key that this object was constructed with. key that this object was constructed with.
""" """
try: try:
if isinstance(message, six.text_type):
message = message.encode('utf-8')
crypto.verify(self._pubkey, signature, message, 'sha256') crypto.verify(self._pubkey, signature, message, 'sha256')
return True return True
except: except:
...@@ -106,17 +104,15 @@ try: ...@@ -106,17 +104,15 @@ try:
"""Signs a message. """Signs a message.
Args: Args:
message: bytes, Message to be signed. message: string, Message to be signed.
Returns: Returns:
string, The signature of the message for the given key. string, The signature of the message for the given key.
""" """
if isinstance(message, six.text_type):
message = message.encode('utf-8')
return crypto.sign(self._key, message, 'sha256') return crypto.sign(self._key, message, 'sha256')
@staticmethod @staticmethod
def from_string(key, password=b'notasecret'): def from_string(key, password='notasecret'):
"""Construct a Signer instance from a string. """Construct a Signer instance from a string.
Args: Args:
...@@ -129,45 +125,21 @@ try: ...@@ -129,45 +125,21 @@ try:
Raises: Raises:
OpenSSL.crypto.Error if the key can't be parsed. OpenSSL.crypto.Error if the key can't be parsed.
""" """
parsed_pem_key = _parse_pem_key(key) if key.startswith('-----BEGIN '):
if parsed_pem_key: pkey = crypto.load_privatekey(crypto.FILETYPE_PEM, key)
pkey = crypto.load_privatekey(crypto.FILETYPE_PEM, parsed_pem_key)
else: else:
if isinstance(password, six.text_type):
password = password.encode('utf-8')
pkey = crypto.load_pkcs12(key, password).get_privatekey() pkey = crypto.load_pkcs12(key, password).get_privatekey()
return OpenSSLSigner(pkey) return OpenSSLSigner(pkey)
def pkcs12_key_as_pem(private_key_text, private_key_password):
"""Convert the contents of a PKCS12 key to PEM using OpenSSL.
Args:
private_key_text: String. Private key.
private_key_password: String. Password for PKCS12.
Returns:
String. PEM contents of ``private_key_text``.
"""
decoded_body = base64.b64decode(private_key_text)
if isinstance(private_key_password, six.string_types):
private_key_password = private_key_password.encode('ascii')
pkcs12 = crypto.load_pkcs12(decoded_body, private_key_password)
return crypto.dump_privatekey(crypto.FILETYPE_PEM,
pkcs12.get_privatekey())
except ImportError: except ImportError:
OpenSSLVerifier = None OpenSSLVerifier = None
OpenSSLSigner = None OpenSSLSigner = None
def pkcs12_key_as_pem(*args, **kwargs):
raise NotImplementedError('pkcs12_key_as_pem requires OpenSSL.')
try: try:
from Crypto.PublicKey import RSA from Crypto.PublicKey import RSA
from Crypto.Hash import SHA256 from Crypto.Hash import SHA256
from Crypto.Signature import PKCS1_v1_5 from Crypto.Signature import PKCS1_v1_5
from Crypto.Util.asn1 import DerSequence
class PyCryptoVerifier(object): class PyCryptoVerifier(object):
...@@ -209,17 +181,14 @@ try: ...@@ -209,17 +181,14 @@ try:
Returns: Returns:
Verifier instance. Verifier instance.
Raises:
NotImplementedError if is_x509_cert is true.
""" """
if is_x509_cert: if is_x509_cert:
if isinstance(key_pem, six.text_type): raise NotImplementedError(
key_pem = key_pem.encode('ascii') 'X509 certs are not supported by the PyCrypto library. '
pemLines = key_pem.replace(b' ', b'').split() 'Try using PyOpenSSL if native code is an option.')
certDer = _urlsafe_b64decode(b''.join(pemLines[1:-1]))
certSeq = DerSequence()
certSeq.decode(certDer)
tbsSeq = DerSequence()
tbsSeq.decode(certSeq[0])
pubkey = RSA.importKey(tbsSeq[6])
else: else:
pubkey = RSA.importKey(key_pem) pubkey = RSA.importKey(key_pem)
return PyCryptoVerifier(pubkey) return PyCryptoVerifier(pubkey)
...@@ -245,8 +214,6 @@ try: ...@@ -245,8 +214,6 @@ try:
Returns: Returns:
string, The signature of the message for the given key. string, The signature of the message for the given key.
""" """
if isinstance(message, six.text_type):
message = message.encode('utf-8')
return PKCS1_v1_5.new(self._key).sign(SHA256.new(message)) return PKCS1_v1_5.new(self._key).sign(SHA256.new(message))
@staticmethod @staticmethod
...@@ -263,12 +230,11 @@ try: ...@@ -263,12 +230,11 @@ try:
Raises: Raises:
NotImplementedError if they key isn't in PEM format. NotImplementedError if they key isn't in PEM format.
""" """
parsed_pem_key = _parse_pem_key(key) if key.startswith('-----BEGIN '):
if parsed_pem_key: pkey = RSA.importKey(key)
pkey = RSA.importKey(parsed_pem_key)
else: else:
raise NotImplementedError( raise NotImplementedError(
'PKCS12 format is not supported by the PyCrypto library. ' 'PKCS12 format is not supported by the PyCrpto library. '
'Try converting to a "PEM" ' 'Try converting to a "PEM" '
'(openssl pkcs12 -in xxxxx.p12 -nodes -nocerts > privatekey.pem) ' '(openssl pkcs12 -in xxxxx.p12 -nodes -nocerts > privatekey.pem) '
'or using PyOpenSSL if native code is an option.') 'or using PyOpenSSL if native code is an option.')
...@@ -290,39 +256,19 @@ else: ...@@ -290,39 +256,19 @@ else:
'PyOpenSSL, or PyCrypto 2.6 or later') 'PyOpenSSL, or PyCrypto 2.6 or later')
def _parse_pem_key(raw_key_input):
"""Identify and extract PEM keys.
Determines whether the given key is in the format of PEM key, and extracts
the relevant part of the key if it is.
Args:
raw_key_input: The contents of a private key file (either PEM or PKCS12).
Returns:
string, The actual key if the contents are from a PEM file, or else None.
"""
offset = raw_key_input.find(b'-----BEGIN ')
if offset != -1:
return raw_key_input[offset:]
def _urlsafe_b64encode(raw_bytes): def _urlsafe_b64encode(raw_bytes):
if isinstance(raw_bytes, six.text_type): return base64.urlsafe_b64encode(raw_bytes).rstrip('=')
raw_bytes = raw_bytes.encode('utf-8')
return base64.urlsafe_b64encode(raw_bytes).decode('ascii').rstrip('=')
def _urlsafe_b64decode(b64string): def _urlsafe_b64decode(b64string):
# Guard against unicode strings, which base64 can't handle. # Guard against unicode strings, which base64 can't handle.
if isinstance(b64string, six.text_type): b64string = b64string.encode('ascii')
b64string = b64string.encode('ascii') padded = b64string + '=' * (4 - len(b64string) % 4)
padded = b64string + b'=' * (4 - len(b64string) % 4)
return base64.urlsafe_b64decode(padded) return base64.urlsafe_b64decode(padded)
def _json_encode(data): def _json_encode(data):
return json.dumps(data, separators=(',', ':')) return simplejson.dumps(data, separators = (',', ':'))
def make_signed_jwt(signer, payload): def make_signed_jwt(signer, payload):
...@@ -340,8 +286,8 @@ def make_signed_jwt(signer, payload): ...@@ -340,8 +286,8 @@ def make_signed_jwt(signer, payload):
header = {'typ': 'JWT', 'alg': 'RS256'} header = {'typ': 'JWT', 'alg': 'RS256'}
segments = [ segments = [
_urlsafe_b64encode(_json_encode(header)), _urlsafe_b64encode(_json_encode(header)),
_urlsafe_b64encode(_json_encode(payload)), _urlsafe_b64encode(_json_encode(payload)),
] ]
signing_input = '.'.join(segments) signing_input = '.'.join(segments)
...@@ -372,8 +318,9 @@ def verify_signed_jwt_with_certs(jwt, certs, audience): ...@@ -372,8 +318,9 @@ def verify_signed_jwt_with_certs(jwt, certs, audience):
""" """
segments = jwt.split('.') segments = jwt.split('.')
if len(segments) != 3: if (len(segments) != 3):
raise AppIdentityError('Wrong number of segments in token: %s' % jwt) raise AppIdentityError(
'Wrong number of segments in token: %s' % jwt)
signed = '%s.%s' % (segments[0], segments[1]) signed = '%s.%s' % (segments[0], segments[1])
signature = _urlsafe_b64decode(segments[2]) signature = _urlsafe_b64decode(segments[2])
...@@ -381,15 +328,15 @@ def verify_signed_jwt_with_certs(jwt, certs, audience): ...@@ -381,15 +328,15 @@ def verify_signed_jwt_with_certs(jwt, certs, audience):
# Parse token. # Parse token.
json_body = _urlsafe_b64decode(segments[1]) json_body = _urlsafe_b64decode(segments[1])
try: try:
parsed = json.loads(json_body.decode('utf-8')) parsed = simplejson.loads(json_body)
except: except:
raise AppIdentityError('Can\'t parse token: %s' % json_body) raise AppIdentityError('Can\'t parse token: %s' % json_body)
# Check signature. # Check signature.
verified = False verified = False
for pem in certs.values(): for (keyname, pem) in certs.items():
verifier = Verifier.from_string(pem, True) verifier = Verifier.from_string(pem, True)
if verifier.verify(signed, signature): if (verifier.verify(signed, signature)):
verified = True verified = True
break break
if not verified: if not verified:
...@@ -402,20 +349,21 @@ def verify_signed_jwt_with_certs(jwt, certs, audience): ...@@ -402,20 +349,21 @@ def verify_signed_jwt_with_certs(jwt, certs, audience):
earliest = iat - CLOCK_SKEW_SECS earliest = iat - CLOCK_SKEW_SECS
# Check expiration timestamp. # Check expiration timestamp.
now = int(time.time()) now = long(time.time())
exp = parsed.get('exp') exp = parsed.get('exp')
if exp is None: if exp is None:
raise AppIdentityError('No exp field in token: %s' % json_body) raise AppIdentityError('No exp field in token: %s' % json_body)
if exp >= now + MAX_TOKEN_LIFETIME_SECS: if exp >= now + MAX_TOKEN_LIFETIME_SECS:
raise AppIdentityError('exp field too far in future: %s' % json_body) raise AppIdentityError(
'exp field too far in future: %s' % json_body)
latest = exp + CLOCK_SKEW_SECS latest = exp + CLOCK_SKEW_SECS
if now < earliest: if now < earliest:
raise AppIdentityError('Token used too early, %d < %d: %s' % raise AppIdentityError('Token used too early, %d < %d: %s' %
(now, earliest, json_body)) (now, earliest, json_body))
if now > latest: if now > latest:
raise AppIdentityError('Token used too late, %d > %d: %s' % raise AppIdentityError('Token used too late, %d > %d: %s' %
(now, latest, json_body)) (now, latest, json_body))
# Check audience. # Check audience.
if audience is not None: if audience is not None:
...@@ -424,6 +372,6 @@ def verify_signed_jwt_with_certs(jwt, certs, audience): ...@@ -424,6 +372,6 @@ def verify_signed_jwt_with_certs(jwt, certs, audience):
raise AppIdentityError('No aud field in token: %s' % json_body) raise AppIdentityError('No aud field in token: %s' % json_body)
if aud != audience: if aud != audience:
raise AppIdentityError('Wrong recipient, %s != %s: %s' % raise AppIdentityError('Wrong recipient, %s != %s: %s' %
(aud, audience, json_body)) (aud, audience, json_body))
return parsed return parsed
\ No newline at end of file
# Copyright 2015 Google Inc. All Rights Reserved.
#
# 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.
"""OAuth 2.0 utitilies for Google Developer Shell environment."""
import json
import os
from . import client
DEVSHELL_ENV = 'DEVSHELL_CLIENT_PORT'
class Error(Exception):
"""Errors for this module."""
pass
class CommunicationError(Error):
"""Errors for communication with the Developer Shell server."""
class NoDevshellServer(Error):
"""Error when no Developer Shell server can be contacted."""
# The request for credential information to the Developer Shell client socket is
# always an empty PBLite-formatted JSON object, so just define it as a constant.
CREDENTIAL_INFO_REQUEST_JSON = '[]'
class CredentialInfoResponse(object):
"""Credential information response from Developer Shell server.
The credential information response from Developer Shell socket is a
PBLite-formatted JSON array with fields encoded by their index in the array:
* Index 0 - user email
* Index 1 - default project ID. None if the project context is not known.
* Index 2 - OAuth2 access token. None if there is no valid auth context.
"""
def __init__(self, json_string):
"""Initialize the response data from JSON PBLite array."""
pbl = json.loads(json_string)
if not isinstance(pbl, list):
raise ValueError('Not a list: ' + str(pbl))
pbl_len = len(pbl)
self.user_email = pbl[0] if pbl_len > 0 else None
self.project_id = pbl[1] if pbl_len > 1 else None
self.access_token = pbl[2] if pbl_len > 2 else None
def _SendRecv():
"""Communicate with the Developer Shell server socket."""
port = int(os.getenv(DEVSHELL_ENV, 0))
if port == 0:
raise NoDevshellServer()
import socket
sock = socket.socket()
sock.connect(('localhost', port))
data = CREDENTIAL_INFO_REQUEST_JSON
msg = '%s\n%s' % (len(data), data)
sock.sendall(msg.encode())
header = sock.recv(6).decode()
if '\n' not in header:
raise CommunicationError('saw no newline in the first 6 bytes')
len_str, json_str = header.split('\n', 1)
to_read = int(len_str) - len(json_str)
if to_read > 0:
json_str += sock.recv(to_read, socket.MSG_WAITALL).decode()
return CredentialInfoResponse(json_str)
class DevshellCredentials(client.GoogleCredentials):
"""Credentials object for Google Developer Shell environment.
This object will allow a Google Developer Shell session to identify its user
to Google and other OAuth 2.0 servers that can verify assertions. It can be
used for the purpose of accessing data stored under the user account.
This credential does not require a flow to instantiate because it represents
a two legged flow, and therefore has all of the required information to
generate and refresh its own access tokens.
"""
def __init__(self, user_agent=None):
super(DevshellCredentials, self).__init__(
None, # access_token, initialized below
None, # client_id
None, # client_secret
None, # refresh_token
None, # token_expiry
None, # token_uri
user_agent)
self._refresh(None)
def _refresh(self, http_request):
self.devshell_response = _SendRecv()
self.access_token = self.devshell_response.access_token
@property
def user_email(self):
return self.devshell_response.user_email
@property
def project_id(self):
return self.devshell_response.project_id
@classmethod
def from_json(cls, json_data):
raise NotImplementedError(
'Cannot load Developer Shell credentials from JSON.')
@property
def serialization_data(self):
raise NotImplementedError(
'Cannot serialize Developer Shell credentials.')
# Copyright 2014 Google Inc. All rights reserved. # Copyright (C) 2010 Google Inc.
# #
# Licensed under the Apache License, Version 2.0 (the "License"); # Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License. # you may not use this file except in compliance with the License.
...@@ -116,21 +116,14 @@ class Storage(BaseStorage): ...@@ -116,21 +116,14 @@ class Storage(BaseStorage):
credential.set_store(self) credential.set_store(self)
return credential return credential
def locked_put(self, credentials, overwrite=False): def locked_put(self, credentials):
"""Write a Credentials to the datastore. """Write a Credentials to the datastore.
Args: Args:
credentials: Credentials, the credentials to store. credentials: Credentials, the credentials to store.
overwrite: Boolean, indicates whether you would like these credentials to
overwrite any existing stored credentials.
""" """
args = {self.key_name: self.key_value} args = {self.key_name: self.key_value}
entity = self.model_class(**args)
if overwrite:
entity, unused_is_new = self.model_class.objects.get_or_create(**args)
else:
entity = self.model_class(**args)
setattr(entity, self.property_name, credentials) setattr(entity, self.property_name, credentials)
entity.save() entity.save()
...@@ -138,4 +131,4 @@ class Storage(BaseStorage): ...@@ -138,4 +131,4 @@ class Storage(BaseStorage):
"""Delete Credentials from the datastore.""" """Delete Credentials from the datastore."""
query = {self.key_name: self.key_value} query = {self.key_name: self.key_value}
entities = self.model_class.objects.filter(**query).delete() entities = self.model_class.objects.filter(**query).delete()
\ No newline at end of file
# Copyright 2014 Google Inc. All rights reserved. # Copyright (C) 2010 Google Inc.
# #
# Licensed under the Apache License, Version 2.0 (the "License"); # Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License. # you may not use this file except in compliance with the License.
...@@ -21,10 +21,12 @@ credentials. ...@@ -21,10 +21,12 @@ credentials.
__author__ = 'jcgregorio@google.com (Joe Gregorio)' __author__ = 'jcgregorio@google.com (Joe Gregorio)'
import os import os
import stat
import threading import threading
from client import Credentials from anyjson import simplejson
from client import Storage as BaseStorage from client import Storage as BaseStorage
from client import Credentials
class CredentialsFileSymbolicLinkError(Exception): class CredentialsFileSymbolicLinkError(Exception):
...@@ -90,7 +92,7 @@ class Storage(BaseStorage): ...@@ -90,7 +92,7 @@ class Storage(BaseStorage):
simple version of "touch" to ensure the file has been created. simple version of "touch" to ensure the file has been created.
""" """
if not os.path.exists(self._filename): if not os.path.exists(self._filename):
old_umask = os.umask(0o177) old_umask = os.umask(0177)
try: try:
open(self._filename, 'a+b').close() open(self._filename, 'a+b').close()
finally: finally:
...@@ -108,7 +110,7 @@ class Storage(BaseStorage): ...@@ -108,7 +110,7 @@ class Storage(BaseStorage):
self._create_file_if_needed() self._create_file_if_needed()
self._validate_file() self._validate_file()
f = open(self._filename, 'w') f = open(self._filename, 'wb')
f.write(credentials.to_json()) f.write(credentials.to_json())
f.close() f.close()
...@@ -119,4 +121,4 @@ class Storage(BaseStorage): ...@@ -119,4 +121,4 @@ class Storage(BaseStorage):
credentials: Credentials, the credentials to store. credentials: Credentials, the credentials to store.
""" """
os.unlink(self._filename) os.unlink(self._filename)
\ No newline at end of file
# Copyright 2014 Google Inc. All rights reserved. # Copyright (C) 2012 Google Inc.
# #
# Licensed under the Apache License, Version 2.0 (the "License"); # Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License. # you may not use this file except in compliance with the License.
...@@ -19,13 +19,14 @@ Utilities for making it easier to use OAuth 2.0 on Google Compute Engine. ...@@ -19,13 +19,14 @@ Utilities for making it easier to use OAuth 2.0 on Google Compute Engine.
__author__ = 'jcgregorio@google.com (Joe Gregorio)' __author__ = 'jcgregorio@google.com (Joe Gregorio)'
import json import httplib2
import logging import logging
from third_party.six.moves import urllib import uritemplate
from third_party.oauth2client import util from oauth2client import util
from third_party.oauth2client.client import AccessTokenRefreshError from oauth2client.anyjson import simplejson
from third_party.oauth2client.client import AssertionCredentials from oauth2client.client import AccessTokenRefreshError
from oauth2client.client import AssertionCredentials
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
...@@ -56,14 +57,13 @@ class AppAssertionCredentials(AssertionCredentials): ...@@ -56,14 +57,13 @@ class AppAssertionCredentials(AssertionCredentials):
requested. requested.
""" """
self.scope = util.scopes_to_string(scope) self.scope = util.scopes_to_string(scope)
self.kwargs = kwargs
# Assertion type is no longer used, but still in the parent class signature. # Assertion type is no longer used, but still in the parent class signature.
super(AppAssertionCredentials, self).__init__(None) super(AppAssertionCredentials, self).__init__(None)
@classmethod @classmethod
def from_json(cls, json_data): def from_json(cls, json):
data = json.loads(json_data) data = simplejson.loads(json)
return AppAssertionCredentials(data['scope']) return AppAssertionCredentials(data['scope'])
def _refresh(self, http_request): def _refresh(self, http_request):
...@@ -78,28 +78,13 @@ class AppAssertionCredentials(AssertionCredentials): ...@@ -78,28 +78,13 @@ class AppAssertionCredentials(AssertionCredentials):
Raises: Raises:
AccessTokenRefreshError: When the refresh fails. AccessTokenRefreshError: When the refresh fails.
""" """
query = '?scope=%s' % urllib.parse.quote(self.scope, '') uri = uritemplate.expand(META, {'scope': self.scope})
uri = META.replace('{?scope}', query)
response, content = http_request(uri) response, content = http_request(uri)
if response.status == 200: if response.status == 200:
try: try:
d = json.loads(content) d = simplejson.loads(content)
except Exception as e: except StandardError, e:
raise AccessTokenRefreshError(str(e)) raise AccessTokenRefreshError(str(e))
self.access_token = d['accessToken'] self.access_token = d['accessToken']
else: else:
if response.status == 404:
content += (' This can occur if a VM was created'
' with no service account or scopes.')
raise AccessTokenRefreshError(content) raise AccessTokenRefreshError(content)
@property
def serialization_data(self):
raise NotImplementedError(
'Cannot serialize credentials for GCE service accounts.')
def create_scoped_required(self):
return not self.scope
def create_scoped(self, scopes):
return AppAssertionCredentials(scopes, **self.kwargs)
\ No newline at end of file
# Copyright 2014 Google Inc. All rights reserved. # Copyright (C) 2012 Google Inc.
# #
# Licensed under the Apache License, Version 2.0 (the "License"); # Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License. # you may not use this file except in compliance with the License.
...@@ -19,12 +19,11 @@ A Storage for Credentials that uses the keyring module. ...@@ -19,12 +19,11 @@ A Storage for Credentials that uses the keyring module.
__author__ = 'jcgregorio@google.com (Joe Gregorio)' __author__ = 'jcgregorio@google.com (Joe Gregorio)'
import threading
import keyring import keyring
import threading
from client import Credentials
from client import Storage as BaseStorage from client import Storage as BaseStorage
from client import Credentials
class Storage(BaseStorage): class Storage(BaseStorage):
...@@ -107,4 +106,4 @@ class Storage(BaseStorage): ...@@ -107,4 +106,4 @@ class Storage(BaseStorage):
Args: Args:
credentials: Credentials, the credentials to store. credentials: Credentials, the credentials to store.
""" """
keyring.set_password(self._service_name, self._user_name, '') keyring.set_password(self._service_name, self._user_name, '')
\ No newline at end of file
# Copyright 2014 Google Inc. All rights reserved. # Copyright 2011 Google Inc.
# #
# Licensed under the Apache License, Version 2.0 (the "License"); # Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License. # you may not use this file except in compliance with the License.
...@@ -17,21 +17,17 @@ ...@@ -17,21 +17,17 @@
This module first tries to use fcntl locking to ensure serialized access This module first tries to use fcntl locking to ensure serialized access
to a file, then falls back on a lock file if that is unavialable. to a file, then falls back on a lock file if that is unavialable.
Usage:: Usage:
f = LockedFile('filename', 'r+b', 'rb') f = LockedFile('filename', 'r+b', 'rb')
f.open_and_lock() f.open_and_lock()
if f.is_locked(): if f.is_locked():
print('Acquired filename with r+b mode') print 'Acquired filename with r+b mode'
f.file_handle().write('locked data') f.file_handle().write('locked data')
else: else:
print('Acquired filename with rb mode') print 'Aquired filename with rb mode'
f.unlock_and_close() f.unlock_and_close()
""" """
from __future__ import print_function
__author__ = 'cache@google.com (David T McWherter)' __author__ = 'cache@google.com (David T McWherter)'
import errno import errno
...@@ -74,7 +70,6 @@ class _Opener(object): ...@@ -74,7 +70,6 @@ class _Opener(object):
self._mode = mode self._mode = mode
self._fallback_mode = fallback_mode self._fallback_mode = fallback_mode
self._fh = None self._fh = None
self._lock_fd = None
def is_locked(self): def is_locked(self):
"""Was the file locked.""" """Was the file locked."""
...@@ -127,7 +122,7 @@ class _PosixOpener(_Opener): ...@@ -127,7 +122,7 @@ class _PosixOpener(_Opener):
validate_file(self._filename) validate_file(self._filename)
try: try:
self._fh = open(self._filename, self._mode) self._fh = open(self._filename, self._mode)
except IOError as e: except IOError, e:
# If we can't access with _mode, try _fallback_mode and don't lock. # If we can't access with _mode, try _fallback_mode and don't lock.
if e.errno == errno.EACCES: if e.errno == errno.EACCES:
self._fh = open(self._filename, self._fallback_mode) self._fh = open(self._filename, self._fallback_mode)
...@@ -142,12 +137,12 @@ class _PosixOpener(_Opener): ...@@ -142,12 +137,12 @@ class _PosixOpener(_Opener):
self._locked = True self._locked = True
break break
except OSError as e: except OSError, e:
if e.errno != errno.EEXIST: if e.errno != errno.EEXIST:
raise raise
if (time.time() - start_time) >= timeout: if (time.time() - start_time) >= timeout:
logger.warn('Could not acquire lock %s in %s seconds', logger.warn('Could not acquire lock %s in %s seconds' % (
lock_filename, timeout) lock_filename, timeout))
# Close the file and open in fallback_mode. # Close the file and open in fallback_mode.
if self._fh: if self._fh:
self._fh.close() self._fh.close()
...@@ -197,9 +192,9 @@ try: ...@@ -197,9 +192,9 @@ try:
validate_file(self._filename) validate_file(self._filename)
try: try:
self._fh = open(self._filename, self._mode) self._fh = open(self._filename, self._mode)
except IOError as e: except IOError, e:
# If we can't access with _mode, try _fallback_mode and don't lock. # If we can't access with _mode, try _fallback_mode and don't lock.
if e.errno in (errno.EPERM, errno.EACCES): if e.errno == errno.EACCES:
self._fh = open(self._filename, self._fallback_mode) self._fh = open(self._filename, self._fallback_mode)
return return
...@@ -209,16 +204,16 @@ try: ...@@ -209,16 +204,16 @@ try:
fcntl.lockf(self._fh.fileno(), fcntl.LOCK_EX) fcntl.lockf(self._fh.fileno(), fcntl.LOCK_EX)
self._locked = True self._locked = True
return return
except IOError as e: except IOError, e:
# If not retrying, then just pass on the error. # If not retrying, then just pass on the error.
if timeout == 0: if timeout == 0:
raise raise e
if e.errno != errno.EACCES: if e.errno != errno.EACCES:
raise raise e
# We could not acquire the lock. Try again. # We could not acquire the lock. Try again.
if (time.time() - start_time) >= timeout: if (time.time() - start_time) >= timeout:
logger.warn('Could not lock %s in %s seconds', logger.warn('Could not lock %s in %s seconds' % (
self._filename, timeout) self._filename, timeout))
if self._fh: if self._fh:
self._fh.close() self._fh.close()
self._fh = open(self._filename, self._fallback_mode) self._fh = open(self._filename, self._fallback_mode)
...@@ -272,7 +267,7 @@ try: ...@@ -272,7 +267,7 @@ try:
validate_file(self._filename) validate_file(self._filename)
try: try:
self._fh = open(self._filename, self._mode) self._fh = open(self._filename, self._mode)
except IOError as e: except IOError, e:
# If we can't access with _mode, try _fallback_mode and don't lock. # If we can't access with _mode, try _fallback_mode and don't lock.
if e.errno == errno.EACCES: if e.errno == errno.EACCES:
self._fh = open(self._filename, self._fallback_mode) self._fh = open(self._filename, self._fallback_mode)
...@@ -289,9 +284,9 @@ try: ...@@ -289,9 +284,9 @@ try:
pywintypes.OVERLAPPED()) pywintypes.OVERLAPPED())
self._locked = True self._locked = True
return return
except pywintypes.error as e: except pywintypes.error, e:
if timeout == 0: if timeout == 0:
raise raise e
# If the error is not that the file is already in use, raise. # If the error is not that the file is already in use, raise.
if e[0] != _Win32Opener.FILE_IN_USE_ERROR: if e[0] != _Win32Opener.FILE_IN_USE_ERROR:
...@@ -313,7 +308,7 @@ try: ...@@ -313,7 +308,7 @@ try:
try: try:
hfile = win32file._get_osfhandle(self._fh.fileno()) hfile = win32file._get_osfhandle(self._fh.fileno())
win32file.UnlockFileEx(hfile, 0, -0x10000, pywintypes.OVERLAPPED()) win32file.UnlockFileEx(hfile, 0, -0x10000, pywintypes.OVERLAPPED())
except pywintypes.error as e: except pywintypes.error, e:
if e[0] != _Win32Opener.FILE_ALREADY_UNLOCKED_ERROR: if e[0] != _Win32Opener.FILE_ALREADY_UNLOCKED_ERROR:
raise raise
self._locked = False self._locked = False
...@@ -375,4 +370,4 @@ class LockedFile(object): ...@@ -375,4 +370,4 @@ class LockedFile(object):
def unlock_and_close(self): def unlock_and_close(self):
"""Unlock and close a file.""" """Unlock and close a file."""
self._opener.unlock_and_close() self._opener.unlock_and_close()
\ No newline at end of file
# Copyright 2014 Google Inc. All rights reserved. # Copyright 2011 Google Inc.
# #
# Licensed under the Apache License, Version 2.0 (the "License"); # Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License. # you may not use this file except in compliance with the License.
...@@ -19,41 +19,39 @@ credentials can be stored in one file. That file supports locking ...@@ -19,41 +19,39 @@ credentials can be stored in one file. That file supports locking
both in a single process and across processes. both in a single process and across processes.
The credential themselves are keyed off of: The credential themselves are keyed off of:
* client_id * client_id
* user_agent * user_agent
* scope * scope
The format of the stored data is like so:: The format of the stored data is like so:
{
{ 'file_version': 1,
'file_version': 1, 'data': [
'data': [ {
{ 'key': {
'key': { 'clientId': '<client id>',
'clientId': '<client id>', 'userAgent': '<user agent>',
'userAgent': '<user agent>', 'scope': '<scope>'
'scope': '<scope>' },
}, 'credential': {
'credential': { # JSON serialized Credentials.
# JSON serialized Credentials.
}
} }
] }
} ]
}
""" """
__author__ = 'jbeda@google.com (Joe Beda)' __author__ = 'jbeda@google.com (Joe Beda)'
import base64
import errno import errno
import json
import logging import logging
import os import os
import threading import threading
from .client import Credentials from anyjson import simplejson
from .client import Storage as BaseStorage from .client import Storage as BaseStorage
from .client import Credentials
from . import util from . import util
from locked_file import LockedFile from locked_file import LockedFile
...@@ -66,10 +64,12 @@ _multistores_lock = threading.Lock() ...@@ -66,10 +64,12 @@ _multistores_lock = threading.Lock()
class Error(Exception): class Error(Exception):
"""Base error for this module.""" """Base error for this module."""
pass
class NewerCredentialStoreError(Error): class NewerCredentialStoreError(Error):
"""The credential store is a newer version than supported.""" """The credential store is a newer version that supported."""
pass
@util.positional(4) @util.positional(4)
...@@ -193,7 +193,7 @@ class _MultiStore(object): ...@@ -193,7 +193,7 @@ class _MultiStore(object):
This will create the file if necessary. This will create the file if necessary.
""" """
self._file = LockedFile(filename, 'r+', 'r') self._file = LockedFile(filename, 'r+b', 'rb')
self._thread_lock = threading.Lock() self._thread_lock = threading.Lock()
self._read_only = False self._read_only = False
self._warn_on_readonly = warn_on_readonly self._warn_on_readonly = warn_on_readonly
...@@ -271,7 +271,7 @@ class _MultiStore(object): ...@@ -271,7 +271,7 @@ class _MultiStore(object):
simple version of "touch" to ensure the file has been created. simple version of "touch" to ensure the file has been created.
""" """
if not os.path.exists(self._file.filename()): if not os.path.exists(self._file.filename()):
old_umask = os.umask(0o177) old_umask = os.umask(0177)
try: try:
open(self._file.filename(), 'a+b').close() open(self._file.filename(), 'a+b').close()
finally: finally:
...@@ -280,23 +280,13 @@ class _MultiStore(object): ...@@ -280,23 +280,13 @@ class _MultiStore(object):
def _lock(self): def _lock(self):
"""Lock the entire multistore.""" """Lock the entire multistore."""
self._thread_lock.acquire() self._thread_lock.acquire()
try: self._file.open_and_lock()
self._file.open_and_lock()
except IOError as e:
if e.errno == errno.ENOSYS:
logger.warn('File system does not support locking the credentials '
'file.')
elif e.errno == errno.ENOLCK:
logger.warn('File system is out of resources for writing the '
'credentials file (is your disk full?).')
else:
raise
if not self._file.is_locked(): if not self._file.is_locked():
self._read_only = True self._read_only = True
if self._warn_on_readonly: if self._warn_on_readonly:
logger.warn('The credentials file (%s) is not writable. Opening in ' logger.warn('The credentials file (%s) is not writable. Opening in '
'read-only mode. Any refreshed credentials will only be ' 'read-only mode. Any refreshed credentials will only be '
'valid for this run.', self._file.filename()) 'valid for this run.' % self._file.filename())
if os.path.getsize(self._file.filename()) == 0: if os.path.getsize(self._file.filename()) == 0:
logger.debug('Initializing empty multistore file') logger.debug('Initializing empty multistore file')
# The multistore is empty so write out an empty file. # The multistore is empty so write out an empty file.
...@@ -325,7 +315,7 @@ class _MultiStore(object): ...@@ -325,7 +315,7 @@ class _MultiStore(object):
""" """
assert self._thread_lock.locked() assert self._thread_lock.locked()
self._file.file_handle().seek(0) self._file.file_handle().seek(0)
return json.load(self._file.file_handle()) return simplejson.load(self._file.file_handle())
def _locked_json_write(self, data): def _locked_json_write(self, data):
"""Write a JSON serializable data structure to the multistore. """Write a JSON serializable data structure to the multistore.
...@@ -339,7 +329,7 @@ class _MultiStore(object): ...@@ -339,7 +329,7 @@ class _MultiStore(object):
if self._read_only: if self._read_only:
return return
self._file.file_handle().seek(0) self._file.file_handle().seek(0)
json.dump(data, self._file.file_handle(), sort_keys=True, indent=2, separators=(',', ': ')) simplejson.dump(data, self._file.file_handle(), sort_keys=True, indent=2)
self._file.file_handle().truncate() self._file.file_handle().truncate()
def _refresh_data_cache(self): def _refresh_data_cache(self):
...@@ -397,7 +387,7 @@ class _MultiStore(object): ...@@ -397,7 +387,7 @@ class _MultiStore(object):
raw_key = cred_entry['key'] raw_key = cred_entry['key']
key = util.dict_to_tuple_key(raw_key) key = util.dict_to_tuple_key(raw_key)
credential = None credential = None
credential = Credentials.new_from_json(json.dumps(cred_entry['credential'])) credential = Credentials.new_from_json(simplejson.dumps(cred_entry['credential']))
return (key, credential) return (key, credential)
def _write(self): def _write(self):
...@@ -410,7 +400,7 @@ class _MultiStore(object): ...@@ -410,7 +400,7 @@ class _MultiStore(object):
raw_data['data'] = raw_creds raw_data['data'] = raw_creds
for (cred_key, cred) in self._data.items(): for (cred_key, cred) in self._data.items():
raw_key = dict(cred_key) raw_key = dict(cred_key)
raw_cred = json.loads(cred.to_json()) raw_cred = simplejson.loads(cred.to_json())
raw_creds.append({'key': raw_key, 'credential': raw_cred}) raw_creds.append({'key': raw_key, 'credential': raw_cred})
self._locked_json_write(raw_data) self._locked_json_write(raw_data)
...@@ -472,4 +462,4 @@ class _MultiStore(object): ...@@ -472,4 +462,4 @@ class _MultiStore(object):
Returns: Returns:
A Storage object that can be used to get/set this cred A Storage object that can be used to get/set this cred
""" """
return self._Storage(self, key) return self._Storage(self, key)
\ No newline at end of file
# Copyright 2014 Google Inc. All rights reserved. # Copyright (C) 2013 Google Inc.
# #
# Licensed under the Apache License, Version 2.0 (the "License"); # Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License. # you may not use this file except in compliance with the License.
...@@ -15,7 +15,6 @@ ...@@ -15,7 +15,6 @@
"""This module holds the old run() function which is deprecated, the """This module holds the old run() function which is deprecated, the
tools.run_flow() function should be used in its place.""" tools.run_flow() function should be used in its place."""
from __future__ import print_function
import logging import logging
import socket import socket
...@@ -23,9 +22,9 @@ import sys ...@@ -23,9 +22,9 @@ import sys
import webbrowser import webbrowser
import gflags import gflags
from third_party.six.moves import input
from third_party.oauth2client import client from oauth2client import client
from third_party.oauth2client import util from oauth2client import util
from tools import ClientRedirectHandler from tools import ClientRedirectHandler
from tools import ClientRedirectServer from tools import ClientRedirectServer
...@@ -49,38 +48,39 @@ gflags.DEFINE_multi_int('auth_host_port', [8080, 8090], ...@@ -49,38 +48,39 @@ gflags.DEFINE_multi_int('auth_host_port', [8080, 8090],
def run(flow, storage, http=None): def run(flow, storage, http=None):
"""Core code for a command-line application. """Core code for a command-line application.
The ``run()`` function is called from your application and runs The run() function is called from your application and runs through all
through all the steps to obtain credentials. It takes a ``Flow`` the steps to obtain credentials. It takes a Flow argument and attempts to
argument and attempts to open an authorization server page in the open an authorization server page in the user's default web browser. The
user's default web browser. The server asks the user to grant your server asks the user to grant your application access to the user's data.
application access to the user's data. If the user grants access, If the user grants access, the run() function returns new credentials. The
the ``run()`` function returns new credentials. The new credentials new credentials are also stored in the Storage argument, which updates the
are also stored in the ``storage`` argument, which updates the file file associated with the Storage object.
associated with the ``Storage`` object.
It presumes it is run from a command-line application and supports the It presumes it is run from a command-line application and supports the
following flags: following flags:
``--auth_host_name`` (string, default: ``localhost``) --auth_host_name: Host name to use when running a local web server
Host name to use when running a local web server to handle to handle redirects during OAuth authorization.
redirects during OAuth authorization. (default: 'localhost')
``--auth_host_port`` (integer, default: ``[8080, 8090]``) --auth_host_port: Port to use when running a local web server to handle
Port to use when running a local web server to handle redirects redirects during OAuth authorization.;
during OAuth authorization. Repeat this option to specify a list repeat this option to specify a list of values
of values. (default: '[8080, 8090]')
(an integer)
``--[no]auth_local_webserver`` (boolean, default: ``True``) --[no]auth_local_webserver: Run a local web server to handle redirects
Run a local web server to handle redirects during OAuth authorization. during OAuth authorization.
(default: 'true')
Since it uses flags make sure to initialize the ``gflags`` module before Since it uses flags make sure to initialize the gflags module before
calling ``run()``. calling run().
Args: Args:
flow: Flow, an OAuth 2.0 Flow to step through. flow: Flow, an OAuth 2.0 Flow to step through.
storage: Storage, a ``Storage`` to store the credential in. storage: Storage, a Storage to store the credential in.
http: An instance of ``httplib2.Http.request`` or something that acts http: An instance of httplib2.Http.request
like it. or something that acts like it.
Returns: Returns:
Credentials, the obtained credential. Credentials, the obtained credential.
...@@ -96,20 +96,20 @@ def run(flow, storage, http=None): ...@@ -96,20 +96,20 @@ def run(flow, storage, http=None):
try: try:
httpd = ClientRedirectServer((FLAGS.auth_host_name, port), httpd = ClientRedirectServer((FLAGS.auth_host_name, port),
ClientRedirectHandler) ClientRedirectHandler)
except socket.error as e: except socket.error, e:
pass pass
else: else:
success = True success = True
break break
FLAGS.auth_local_webserver = success FLAGS.auth_local_webserver = success
if not success: if not success:
print('Failed to start a local webserver listening on either port 8080') print 'Failed to start a local webserver listening on either port 8080'
print('or port 9090. Please check your firewall settings and locally') print 'or port 9090. Please check your firewall settings and locally'
print('running programs that may be blocking or using those ports.') print 'running programs that may be blocking or using those ports.'
print() print
print('Falling back to --noauth_local_webserver and continuing with') print 'Falling back to --noauth_local_webserver and continuing with',
print('authorization.') print 'authorization.'
print() print
if FLAGS.auth_local_webserver: if FLAGS.auth_local_webserver:
oauth_callback = 'http://%s:%s/' % (FLAGS.auth_host_name, port_number) oauth_callback = 'http://%s:%s/' % (FLAGS.auth_host_name, port_number)
...@@ -120,20 +120,20 @@ def run(flow, storage, http=None): ...@@ -120,20 +120,20 @@ def run(flow, storage, http=None):
if FLAGS.auth_local_webserver: if FLAGS.auth_local_webserver:
webbrowser.open(authorize_url, new=1, autoraise=True) webbrowser.open(authorize_url, new=1, autoraise=True)
print('Your browser has been opened to visit:') print 'Your browser has been opened to visit:'
print() print
print(' ' + authorize_url) print ' ' + authorize_url
print() print
print('If your browser is on a different machine then exit and re-run') print 'If your browser is on a different machine then exit and re-run'
print('this application with the command-line parameter ') print 'this application with the command-line parameter '
print() print
print(' --noauth_local_webserver') print ' --noauth_local_webserver'
print() print
else: else:
print('Go to the following link in your browser:') print 'Go to the following link in your browser:'
print() print
print(' ' + authorize_url) print ' ' + authorize_url
print() print
code = None code = None
if FLAGS.auth_local_webserver: if FLAGS.auth_local_webserver:
...@@ -143,18 +143,18 @@ def run(flow, storage, http=None): ...@@ -143,18 +143,18 @@ def run(flow, storage, http=None):
if 'code' in httpd.query_params: if 'code' in httpd.query_params:
code = httpd.query_params['code'] code = httpd.query_params['code']
else: else:
print('Failed to find "code" in the query parameters of the redirect.') print 'Failed to find "code" in the query parameters of the redirect.'
sys.exit('Try running with --noauth_local_webserver.') sys.exit('Try running with --noauth_local_webserver.')
else: else:
code = input('Enter verification code: ').strip() code = raw_input('Enter verification code: ').strip()
try: try:
credential = flow.step2_exchange(code, http=http) credential = flow.step2_exchange(code, http=http)
except client.FlowExchangeError as e: except client.FlowExchangeError, e:
sys.exit('Authentication has failed: %s' % e) sys.exit('Authentication has failed: %s' % e)
storage.put(credential) storage.put(credential)
credential.set_store(storage) credential.set_store(storage)
print('Authentication successful.') print 'Authentication successful.'
return credential return credential
\ No newline at end of file
# Copyright 2014 Google Inc. All rights reserved.
#
# 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.
"""A service account credentials class.
This credentials class is implemented on top of rsa library.
"""
import base64
import json
import time
from pyasn1.codec.ber import decoder
from pyasn1_modules.rfc5208 import PrivateKeyInfo
import rsa
from . import GOOGLE_REVOKE_URI
from . import GOOGLE_TOKEN_URI
from . import util
from client import AssertionCredentials
from third_party import six
class _ServiceAccountCredentials(AssertionCredentials):
"""Class representing a service account (signed JWT) credential."""
MAX_TOKEN_LIFETIME_SECS = 3600 # 1 hour in seconds
def __init__(self, service_account_id, service_account_email, private_key_id,
private_key_pkcs8_text, scopes, user_agent=None,
token_uri=GOOGLE_TOKEN_URI, revoke_uri=GOOGLE_REVOKE_URI,
**kwargs):
super(_ServiceAccountCredentials, self).__init__(
None, user_agent=user_agent, token_uri=token_uri, revoke_uri=revoke_uri)
self._service_account_id = service_account_id
self._service_account_email = service_account_email
self._private_key_id = private_key_id
self._private_key = _get_private_key(private_key_pkcs8_text)
self._private_key_pkcs8_text = private_key_pkcs8_text
self._scopes = util.scopes_to_string(scopes)
self._user_agent = user_agent
self._token_uri = token_uri
self._revoke_uri = revoke_uri
self._kwargs = kwargs
def _generate_assertion(self):
"""Generate the assertion that will be used in the request."""
header = {
'alg': 'RS256',
'typ': 'JWT',
'kid': self._private_key_id
}
now = int(time.time())
payload = {
'aud': self._token_uri,
'scope': self._scopes,
'iat': now,
'exp': now + _ServiceAccountCredentials.MAX_TOKEN_LIFETIME_SECS,
'iss': self._service_account_email
}
payload.update(self._kwargs)
assertion_input = (_urlsafe_b64encode(header) + b'.' +
_urlsafe_b64encode(payload))
# Sign the assertion.
rsa_bytes = rsa.pkcs1.sign(assertion_input, self._private_key, 'SHA-256')
signature = base64.urlsafe_b64encode(rsa_bytes).rstrip(b'=')
return assertion_input + b'.' + signature
def sign_blob(self, blob):
# Ensure that it is bytes
try:
blob = blob.encode('utf-8')
except AttributeError:
pass
return (self._private_key_id,
rsa.pkcs1.sign(blob, self._private_key, 'SHA-256'))
@property
def service_account_email(self):
return self._service_account_email
@property
def serialization_data(self):
return {
'type': 'service_account',
'client_id': self._service_account_id,
'client_email': self._service_account_email,
'private_key_id': self._private_key_id,
'private_key': self._private_key_pkcs8_text
}
def create_scoped_required(self):
return not self._scopes
def create_scoped(self, scopes):
return _ServiceAccountCredentials(self._service_account_id,
self._service_account_email,
self._private_key_id,
self._private_key_pkcs8_text,
scopes,
user_agent=self._user_agent,
token_uri=self._token_uri,
revoke_uri=self._revoke_uri,
**self._kwargs)
def _urlsafe_b64encode(data):
return base64.urlsafe_b64encode(
json.dumps(data, separators=(',', ':')).encode('UTF-8')).rstrip(b'=')
def _get_private_key(private_key_pkcs8_text):
"""Get an RSA private key object from a pkcs8 representation."""
if not isinstance(private_key_pkcs8_text, six.binary_type):
private_key_pkcs8_text = private_key_pkcs8_text.encode('ascii')
der = rsa.pem.load_pem(private_key_pkcs8_text, 'PRIVATE KEY')
asn1_private_key, _ = decoder.decode(der, asn1Spec=PrivateKeyInfo())
return rsa.PrivateKey.load_pkcs1(
asn1_private_key.getComponentByName('privateKey').asOctets(),
format='DER')
\ No newline at end of file
This diff is collapsed.
#!/usr/bin/env python #!/usr/bin/env python
# #
# Copyright 2014 Google Inc. All rights reserved. # Copyright 2010 Google Inc.
# #
# Licensed under the Apache License, Version 2.0 (the "License"); # Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License. # you may not use this file except in compliance with the License.
...@@ -17,27 +17,26 @@ ...@@ -17,27 +17,26 @@
"""Common utility library.""" """Common utility library."""
__author__ = [ __author__ = ['rafek@google.com (Rafe Kaplan)',
'rafek@google.com (Rafe Kaplan)', 'guido@google.com (Guido van Rossum)',
'guido@google.com (Guido van Rossum)',
] ]
__all__ = [ __all__ = [
'positional', 'positional',
'POSITIONAL_WARNING', 'POSITIONAL_WARNING',
'POSITIONAL_EXCEPTION', 'POSITIONAL_EXCEPTION',
'POSITIONAL_IGNORE', 'POSITIONAL_IGNORE',
] ]
import functools
import inspect import inspect
import logging import logging
import sys
import types import types
import urllib
import urlparse
from third_party import six try:
from third_party.six.moves import urllib from urlparse import parse_qsl
except ImportError:
from cgi import parse_qsl
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
...@@ -52,58 +51,56 @@ positional_parameters_enforcement = POSITIONAL_WARNING ...@@ -52,58 +51,56 @@ positional_parameters_enforcement = POSITIONAL_WARNING
def positional(max_positional_args): def positional(max_positional_args):
"""A decorator to declare that only the first N arguments my be positional. """A decorator to declare that only the first N arguments my be positional.
This decorator makes it easy to support Python 3 style keyword-only This decorator makes it easy to support Python 3 style key-word only
parameters. For example, in Python 3 it is possible to write:: parameters. For example, in Python 3 it is possible to write:
def fn(pos1, *, kwonly1=None, kwonly1=None): def fn(pos1, *, kwonly1=None, kwonly1=None):
... ...
All named parameters after ``*`` must be a keyword:: All named parameters after * must be a keyword:
fn(10, 'kw1', 'kw2') # Raises exception. fn(10, 'kw1', 'kw2') # Raises exception.
fn(10, kwonly1='kw1') # Ok. fn(10, kwonly1='kw1') # Ok.
Example Example:
^^^^^^^ To define a function like above, do:
To define a function like above, do::
@positional(1) @positional(1)
def fn(pos1, kwonly1=None, kwonly2=None): def fn(pos1, kwonly1=None, kwonly2=None):
... ...
If no default value is provided to a keyword argument, it becomes a required If no default value is provided to a keyword argument, it becomes a required
keyword argument:: keyword argument:
@positional(0) @positional(0)
def fn(required_kw): def fn(required_kw):
... ...
This must be called with the keyword parameter:: This must be called with the keyword parameter:
fn() # Raises exception. fn() # Raises exception.
fn(10) # Raises exception. fn(10) # Raises exception.
fn(required_kw=10) # Ok. fn(required_kw=10) # Ok.
When defining instance or class methods always remember to account for When defining instance or class methods always remember to account for
``self`` and ``cls``:: 'self' and 'cls':
class MyClass(object): class MyClass(object):
@positional(2) @positional(2)
def my_method(self, pos1, kwonly1=None): def my_method(self, pos1, kwonly1=None):
... ...
@classmethod @classmethod
@positional(2) @positional(2)
def my_method(cls, pos1, kwonly1=None): def my_method(cls, pos1, kwonly1=None):
... ...
The positional decorator behavior is controlled by The positional decorator behavior is controlled by
``util.positional_parameters_enforcement``, which may be set to util.positional_parameters_enforcement, which may be set to
``POSITIONAL_EXCEPTION``, ``POSITIONAL_WARNING`` or POSITIONAL_EXCEPTION, POSITIONAL_WARNING or POSITIONAL_IGNORE to raise an
``POSITIONAL_IGNORE`` to raise an exception, log a warning, or do exception, log a warning, or do nothing, respectively, if a declaration is
nothing, respectively, if a declaration is violated. violated.
Args: Args:
max_positional_arguments: Maximum number of positional arguments. All max_positional_arguments: Maximum number of positional arguments. All
...@@ -117,10 +114,8 @@ def positional(max_positional_args): ...@@ -117,10 +114,8 @@ def positional(max_positional_args):
TypeError if a key-word only argument is provided as a positional TypeError if a key-word only argument is provided as a positional
parameter, but only if util.positional_parameters_enforcement is set to parameter, but only if util.positional_parameters_enforcement is set to
POSITIONAL_EXCEPTION. POSITIONAL_EXCEPTION.
""" """
def positional_decorator(wrapped): def positional_decorator(wrapped):
@functools.wraps(wrapped)
def positional_wrapper(*args, **kwargs): def positional_wrapper(*args, **kwargs):
if len(args) > max_positional_args: if len(args) > max_positional_args:
plural_s = '' plural_s = ''
...@@ -137,7 +132,7 @@ def positional(max_positional_args): ...@@ -137,7 +132,7 @@ def positional(max_positional_args):
return wrapped(*args, **kwargs) return wrapped(*args, **kwargs)
return positional_wrapper return positional_wrapper
if isinstance(max_positional_args, six.integer_types): if isinstance(max_positional_args, (int, long)):
return positional_decorator return positional_decorator
else: else:
args, _, _, defaults = inspect.getargspec(max_positional_args) args, _, _, defaults = inspect.getargspec(max_positional_args)
...@@ -157,7 +152,7 @@ def scopes_to_string(scopes): ...@@ -157,7 +152,7 @@ def scopes_to_string(scopes):
Returns: Returns:
The scopes formatted as a single string. The scopes formatted as a single string.
""" """
if isinstance(scopes, six.string_types): if isinstance(scopes, types.StringTypes):
return scopes return scopes
else: else:
return ' '.join(scopes) return ' '.join(scopes)
...@@ -194,8 +189,8 @@ def _add_query_parameter(url, name, value): ...@@ -194,8 +189,8 @@ def _add_query_parameter(url, name, value):
if value is None: if value is None:
return url return url
else: else:
parsed = list(urllib.parse.urlparse(url)) parsed = list(urlparse.urlparse(url))
q = dict(urllib.parse.parse_qsl(parsed[4])) q = dict(parse_qsl(parsed[4]))
q[name] = value q[name] = value
parsed[4] = urllib.parse.urlencode(q) parsed[4] = urllib.urlencode(q)
return urllib.parse.urlunparse(parsed) return urlparse.urlunparse(parsed)
\ No newline at end of file
#!/usr/bin/python2.5
# #
# Copyright 2014 the Melange authors. # Copyright 2010 the Melange authors.
# #
# Licensed under the Apache License, Version 2.0 (the "License"); # Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License. # you may not use this file except in compliance with the License.
...@@ -16,36 +17,25 @@ ...@@ -16,36 +17,25 @@
"""Helper methods for creating & verifying XSRF tokens.""" """Helper methods for creating & verifying XSRF tokens."""
__authors__ = [ __authors__ = [
'"Doug Coker" <dcoker@google.com>', '"Doug Coker" <dcoker@google.com>',
'"Joe Gregorio" <jcgregorio@google.com>', '"Joe Gregorio" <jcgregorio@google.com>',
] ]
import base64 import base64
import hmac import hmac
import os # for urandom
import time import time
import six
from oauth2client import util from oauth2client import util
# Delimiter character # Delimiter character
DELIMITER = b':' DELIMITER = ':'
# 1 hour in seconds # 1 hour in seconds
DEFAULT_TIMEOUT_SECS = 1*60*60 DEFAULT_TIMEOUT_SECS = 1*60*60
def _force_bytes(s):
if isinstance(s, bytes):
return s
s = str(s)
if isinstance(s, six.text_type):
return s.encode('utf-8')
return s
@util.positional(2) @util.positional(2)
def generate_token(key, user_id, action_id="", when=None): def generate_token(key, user_id, action_id="", when=None):
"""Generates a URL-safe token for the given user, action, time tuple. """Generates a URL-safe token for the given user, action, time tuple.
...@@ -61,16 +51,18 @@ def generate_token(key, user_id, action_id="", when=None): ...@@ -61,16 +51,18 @@ def generate_token(key, user_id, action_id="", when=None):
Returns: Returns:
A string XSRF protection token. A string XSRF protection token.
""" """
when = _force_bytes(when or int(time.time())) when = when or int(time.time())
digester = hmac.new(_force_bytes(key)) digester = hmac.new(key)
digester.update(_force_bytes(user_id)) digester.update(str(user_id))
digester.update(DELIMITER) digester.update(DELIMITER)
digester.update(_force_bytes(action_id)) digester.update(action_id)
digester.update(DELIMITER) digester.update(DELIMITER)
digester.update(when) digester.update(str(when))
digest = digester.digest() digest = digester.digest()
token = base64.urlsafe_b64encode(digest + DELIMITER + when) token = base64.urlsafe_b64encode('%s%s%d' % (digest,
DELIMITER,
when))
return token return token
...@@ -95,8 +87,8 @@ def validate_token(key, token, user_id, action_id="", current_time=None): ...@@ -95,8 +87,8 @@ def validate_token(key, token, user_id, action_id="", current_time=None):
if not token: if not token:
return False return False
try: try:
decoded = base64.urlsafe_b64decode(token) decoded = base64.urlsafe_b64decode(str(token))
token_time = int(decoded.split(DELIMITER)[-1]) token_time = long(decoded.split(DELIMITER)[-1])
except (TypeError, ValueError): except (TypeError, ValueError):
return False return False
if current_time is None: if current_time is None:
...@@ -113,6 +105,9 @@ def validate_token(key, token, user_id, action_id="", current_time=None): ...@@ -113,6 +105,9 @@ def validate_token(key, token, user_id, action_id="", current_time=None):
# Perform constant time comparison to avoid timing attacks # Perform constant time comparison to avoid timing attacks
different = 0 different = 0
for x, y in zip(bytearray(token), bytearray(expected_token)): for x, y in zip(token, expected_token):
different |= x ^ y different |= ord(x) ^ ord(y)
return not different if different:
\ No newline at end of file return False
return True
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment